]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/fincore.c
libblkid: make example more robust
[thirdparty/util-linux.git] / misc-utils / fincore.c
CommitLineData
a921a7de
MY
1/*
2 * fincore - count pages of file contents in core
3 *
4 * Copyright (C) 2017 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22#include <sys/mman.h>
23#include <sys/stat.h>
24#include <unistd.h>
25#include <getopt.h>
26#include <stdio.h>
27#include <string.h>
28
29#include "c.h"
30#include "nls.h"
31#include "closestream.h"
3e37d7b7
KZ
32#include "xalloc.h"
33#include "strutils.h"
ab0a7517 34#include "blkdev.h"
3e37d7b7
KZ
35
36#include "libsmartcols.h"
a921a7de
MY
37
38/* For large files, mmap is called in iterative way.
39 Window is the unit of vma prepared in each mmap
40 calling.
41
42 Window size depends on page size.
43 e.g. 128MB on x86_64. ( = N_PAGES_IN_WINDOW * 4096 ). */
452089fa 44#define N_PAGES_IN_WINDOW ((size_t)(32 * 1024))
a921a7de 45
c214cbeb
TW
46#ifndef HAVE_CACHESTAT
47
48#ifndef SYS_cachestat
49#define SYS_cachestat 451
50#endif
51
52struct cachestat_range {
53 uint64_t off;
54 uint64_t len;
55};
56
57struct cachestat {
58 uint64_t nr_cache;
59 uint64_t nr_dirty;
60 uint64_t nr_writeback;
61 uint64_t nr_evicted;
62 uint64_t nr_recently_evicted;
63};
64
65static inline int cachestat(unsigned int fd,
66 const struct cachestat_range *cstat_range,
67 struct cachestat *cstat, unsigned int flags)
68{
69 return syscall(SYS_cachestat, fd, cstat_range, cstat, flags);
70}
71
72#endif // HAVE_CACHESTAT
3e37d7b7
KZ
73
74struct colinfo {
37b2b3fa 75 const char * const name;
3e37d7b7
KZ
76 double whint;
77 int flags;
78 const char *help;
e5530e33 79 unsigned int pages : 1;
3e37d7b7
KZ
80};
81
82enum {
83 COL_PAGES,
84 COL_SIZE,
e4e8b57b 85 COL_FILE,
c214cbeb
TW
86 COL_RES,
87 COL_DIRTY_PAGES,
88 COL_DIRTY,
89 COL_WRITEBACK_PAGES,
90 COL_WRITEBACK,
91 COL_EVICTED_PAGES,
92 COL_EVICTED,
93 COL_RECENTLY_EVICTED_PAGES,
94 COL_RECENTLY_EVICTED,
3e37d7b7
KZ
95};
96
37b2b3fa 97static const struct colinfo infos[] = {
c214cbeb
TW
98 [COL_PAGES] = { "PAGES", 1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages"), 1},
99 [COL_RES] = { "RES", 5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")},
100 [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the file")},
101 [COL_FILE] = { "FILE", 4, 0, N_("file name")},
102 [COL_DIRTY_PAGES] = { "DIRTY_PAGES", 1, SCOLS_FL_RIGHT, N_("number of dirty pages"), 1},
103 [COL_DIRTY] = { "DIRTY", 5, SCOLS_FL_RIGHT, N_("number of dirty bytes")},
104 [COL_WRITEBACK_PAGES] = { "WRITEBACK_PAGES", 1, SCOLS_FL_RIGHT, N_("number of pages marked for writeback"), 1},
105 [COL_WRITEBACK] = { "WRITEBACK", 5, SCOLS_FL_RIGHT, N_("number of bytes marked for writeback")},
106 [COL_EVICTED_PAGES] = { "EVICTED_PAGES", 1, SCOLS_FL_RIGHT, N_("number of evicted pages"), 1},
107 [COL_EVICTED] = { "EVICTED", 5, SCOLS_FL_RIGHT, N_("number of evicted bytes")},
108 [COL_RECENTLY_EVICTED_PAGES] = { "RECENTLY_EVICTED_PAGES", 1, SCOLS_FL_RIGHT, N_("number of recently evicted pages"), 1},
109 [COL_RECENTLY_EVICTED] = { "RECENTLY_EVICTED", 5, SCOLS_FL_RIGHT, N_("number of recently evicted bytes")},
3e37d7b7
KZ
110};
111
112static int columns[ARRAY_SIZE(infos) * 2] = {-1};
113static size_t ncolumns;
114
115struct fincore_control {
452089fa 116 const size_t pagesize;
3e37d7b7
KZ
117
118 struct libscols_table *tb; /* output */
119
3f91dd88 120 unsigned int bytes : 1,
9b48766f
KZ
121 noheadings : 1,
122 raw : 1,
123 json : 1;
c214cbeb
TW
124
125};
126
127struct fincore_state {
128 const char * const name;
129 long long unsigned int file_size;
130
131 struct cachestat cstat;
132 struct {
133 unsigned int dirty : 1,
134 writeback : 1,
135 evicted : 1,
136 recently_evicted : 1;
137 } cstat_fields;
3e37d7b7
KZ
138};
139
a921a7de 140
3e37d7b7
KZ
141static int column_name_to_id(const char *name, size_t namesz)
142{
143 size_t i;
144
145 for (i = 0; i < ARRAY_SIZE(infos); i++) {
146 const char *cn = infos[i].name;
147
148 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
149 return i;
150 }
151 warnx(_("unknown column: %s"), name);
152 return -1;
153}
154
155static int get_column_id(int num)
a921a7de 156{
3e37d7b7
KZ
157 assert(num >= 0);
158 assert((size_t) num < ncolumns);
159 assert(columns[num] < (int) ARRAY_SIZE(infos));
160 return columns[num];
a921a7de
MY
161}
162
3e37d7b7 163static const struct colinfo *get_column_info(int num)
a921a7de 164{
3e37d7b7 165 return &infos[ get_column_id(num) ];
a921a7de
MY
166}
167
c214cbeb
TW
168static int get_cstat_value(const struct fincore_state *st, int column_id,
169 uint64_t *value)
170{
171 switch(column_id) {
172 case COL_PAGES:
173 case COL_RES:
174 *value = st->cstat.nr_cache;
175 return 1;
176 case COL_DIRTY_PAGES:
177 case COL_DIRTY:
178 if (!st->cstat_fields.dirty)
179 break;
180 *value = st->cstat.nr_dirty;
181 return 1;
182 case COL_WRITEBACK_PAGES:
183 case COL_WRITEBACK:
184 if (!st->cstat_fields.writeback)
185 *value = st->cstat.nr_writeback;
186 return 1;
187 case COL_EVICTED_PAGES:
188 case COL_EVICTED:
189 if (!st->cstat_fields.evicted)
190 break;
191 *value = st->cstat.nr_evicted;
192 return 1;
193 case COL_RECENTLY_EVICTED_PAGES:
194 case COL_RECENTLY_EVICTED:
195 if (!st->cstat_fields.recently_evicted)
196 break;
197 *value = st->cstat.nr_recently_evicted;
198 return 1;
199 default:
200 assert(0);
201 }
202 return 0;
203}
204
3e37d7b7 205static int add_output_data(struct fincore_control *ctl,
c214cbeb 206 struct fincore_state *st)
3e37d7b7
KZ
207{
208 size_t i;
209 char *tmp;
c214cbeb 210 uint64_t value = 0;
3e37d7b7
KZ
211 struct libscols_line *ln;
212
213 assert(ctl);
214 assert(ctl->tb);
215
216 ln = scols_table_new_line(ctl->tb, NULL);
217 if (!ln)
780ce22c 218 err(EXIT_FAILURE, _("failed to allocate output line"));
3e37d7b7
KZ
219
220 for (i = 0; i < ncolumns; i++) {
b7ebf49c 221 int rc = 0;
e5530e33
TW
222 int column_id = get_column_id(i);
223 int format_value = 0;
b7ebf49c 224
e5530e33 225 switch(column_id) {
3e37d7b7 226 case COL_FILE:
c214cbeb 227 rc = scols_line_set_data(ln, i, st->name);
e4e8b57b 228 break;
3e37d7b7
KZ
229 case COL_SIZE:
230 if (ctl->bytes)
c214cbeb 231 xasprintf(&tmp, "%jd", (intmax_t) st->file_size);
3e37d7b7 232 else
c214cbeb 233 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, st->file_size);
b7ebf49c 234 rc = scols_line_refer_data(ln, i, tmp);
3e37d7b7 235 break;
c214cbeb
TW
236 case COL_PAGES:
237 case COL_RES:
238 case COL_DIRTY_PAGES:
239 case COL_DIRTY:
240 case COL_WRITEBACK_PAGES:
241 case COL_WRITEBACK:
242 case COL_EVICTED:
243 case COL_EVICTED_PAGES:
244 case COL_RECENTLY_EVICTED:
245 case COL_RECENTLY_EVICTED_PAGES:
246 format_value = get_cstat_value(st, column_id, &value);
247 break;
3e37d7b7
KZ
248 default:
249 return -EINVAL;
250 }
b7ebf49c 251
e5530e33 252 if (format_value) {
c214cbeb 253 if (get_column_info(i)->pages) {
e5530e33
TW
254 xasprintf(&tmp, "%ju", (uintmax_t) value);
255 } else {
256 value *= ctl->pagesize;
257 if (ctl->bytes)
258 xasprintf(&tmp, "%ju", (uintmax_t) value);
259 else
260 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, value);
261 }
262 rc = scols_line_refer_data(ln, i, tmp);
263 }
264
b7ebf49c
KZ
265 if (rc)
266 err(EXIT_FAILURE, _("failed to add output data"));
3e37d7b7
KZ
267 }
268
269 return 0;
270}
271
272static int do_mincore(struct fincore_control *ctl,
273 void *window, const size_t len,
c214cbeb 274 struct fincore_state *st)
a921a7de
MY
275{
276 static unsigned char vec[N_PAGES_IN_WINDOW];
3e37d7b7 277 int n = (len / ctl->pagesize) + ((len % ctl->pagesize)? 1: 0);
a921a7de
MY
278
279 if (mincore (window, len, vec) < 0) {
c214cbeb 280 warn(_("failed to do mincore: %s"), st->name);
b57fe43c 281 return -errno;
a921a7de
MY
282 }
283
284 while (n > 0)
285 {
286 if (vec[--n] & 0x1)
287 {
288 vec[n] = 0;
c214cbeb 289 st->cstat.nr_cache++;
a921a7de
MY
290 }
291 }
292
b57fe43c 293 return 0;
a921a7de
MY
294}
295
c214cbeb 296static int mincore_fd (struct fincore_control *ctl,
3e37d7b7 297 int fd,
c214cbeb 298 struct fincore_state *st)
a921a7de 299{
452089fa 300 size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize;
c214cbeb 301 long long unsigned int file_offset, len;
b57fe43c 302 int rc = 0;
a921a7de 303
c214cbeb 304 for (file_offset = 0; file_offset < st->file_size; file_offset += len) {
57940872 305 void *window = NULL;
a921a7de 306
c214cbeb
TW
307 len = st->file_size - file_offset;
308 if (len >= window_size)
a921a7de
MY
309 len = window_size;
310
cf397444
TW
311 /* PROT_NONE is enough for Linux, but qemu-user wants PROT_READ */
312 window = mmap(window, len, PROT_READ, MAP_PRIVATE, fd, file_offset);
a921a7de 313 if (window == MAP_FAILED) {
374cb543 314 rc = -EINVAL;
c214cbeb 315 warn(_("failed to do mmap: %s"), st->name);
a921a7de
MY
316 break;
317 }
318
c214cbeb 319 rc = do_mincore(ctl, window, len, st);
b57fe43c
KZ
320 if (rc)
321 break;
a921a7de
MY
322
323 munmap (window, len);
324 }
325
b57fe43c 326 return rc;
a921a7de
MY
327}
328
c214cbeb
TW
329static int fincore_fd (struct fincore_control *ctl,
330 int fd,
331 struct fincore_state *st)
332{
333 int rc;
334 const struct cachestat_range cstat_range = { 0 };
335
336 rc = cachestat(fd, &cstat_range, &st->cstat, 0);
337 if (!rc) {
338 st->cstat_fields.dirty = 1;
339 st->cstat_fields.writeback = 1;
340 st->cstat_fields.evicted = 1;
341 st->cstat_fields.recently_evicted = 1;
342 return 0;
343 }
344
345 if (errno != ENOSYS)
346 warn(_("failed to do cachestat: %s"), st->name);
347
348 return mincore_fd(ctl, fd, st);
349}
350
3e37d7b7
KZ
351/*
352 * Returns: <0 on error, 0 success, 1 ignore.
353 */
354static int fincore_name(struct fincore_control *ctl,
c214cbeb 355 struct fincore_state *st)
a921a7de
MY
356{
357 int fd;
b57fe43c 358 int rc = 0;
ab0a7517 359 struct stat sb;
a921a7de 360
c214cbeb
TW
361 if ((fd = open (st->name, O_RDONLY)) < 0) {
362 warn(_("failed to open: %s"), st->name);
cff1c113 363 return -errno;
a921a7de
MY
364 }
365
ab0a7517 366 if (fstat (fd, &sb) < 0) {
c214cbeb 367 warn(_("failed to do fstat: %s"), st->name);
b9dc8d07 368 close (fd);
b57fe43c 369 return -errno;
a921a7de 370 }
c214cbeb 371 st->file_size = sb.st_size;
a921a7de 372
5a02eb13 373 if (S_ISBLK(sb.st_mode)) {
c214cbeb 374 rc = blkdev_get_size(fd, &st->file_size);
5a02eb13 375 if (rc)
c214cbeb 376 warn(_("failed ioctl to get size: %s"), st->name);
5a02eb13 377 } else if (S_ISREG(sb.st_mode)) {
c214cbeb 378 st->file_size = sb.st_size;
ab0a7517
JU
379 } else {
380 rc = 1; /* ignore things like symlinks
381 * and directories*/
382 }
a921a7de 383
c214cbeb
TW
384 if (!rc)
385 rc = fincore_fd(ctl, fd, st);
5a02eb13 386
a921a7de 387 close (fd);
b57fe43c 388 return rc;
a921a7de
MY
389}
390
86be6a32 391static void __attribute__((__noreturn__)) usage(void)
3f91dd88 392{
86be6a32 393 FILE *out = stdout;
c5cb5412
KZ
394 size_t i;
395
3f91dd88
KZ
396 fputs(USAGE_HEADER, out);
397 fprintf(out, _(" %s [options] file...\n"), program_invocation_short_name);
398
399 fputs(USAGE_OPTIONS, out);
9b48766f 400 fputs(_(" -J, --json use JSON output format\n"), out);
c5cb5412
KZ
401 fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out);
402 fputs(_(" -n, --noheadings don't print headings\n"), out);
403 fputs(_(" -o, --output <list> output columns\n"), out);
1f583cb9 404 fputs(_(" --output-all output all columns\n"), out);
9b48766f 405 fputs(_(" -r, --raw use raw output format\n"), out);
3f91dd88
KZ
406
407 fputs(USAGE_SEPARATOR, out);
bad4c729 408 fprintf(out, USAGE_HELP_OPTIONS(23));
3f91dd88 409
38b8eac6 410 fputs(USAGE_COLUMNS, out);
c5cb5412
KZ
411
412 for (i = 0; i < ARRAY_SIZE(infos); i++)
d9d53b89 413 fprintf(out, " %22s %s\n", infos[i].name, _(infos[i].help));
c5cb5412 414
bad4c729 415 fprintf(out, USAGE_MAN_TAIL("fincore(1)"));
3f91dd88 416
86be6a32 417 exit(EXIT_SUCCESS);
3f91dd88
KZ
418}
419
a921a7de
MY
420int main(int argc, char ** argv)
421{
422 int c;
3e37d7b7 423 size_t i;
b57fe43c 424 int rc = EXIT_SUCCESS;
c5cb5412 425 char *outarg = NULL;
a921a7de 426
3e37d7b7 427 struct fincore_control ctl = {
c5cb5412 428 .pagesize = getpagesize()
3e37d7b7
KZ
429 };
430
1f583cb9
TW
431 enum {
432 OPT_OUTPUT_ALL = CHAR_MAX + 1
433 };
a921a7de 434 static const struct option longopts[] = {
3f91dd88
KZ
435 { "bytes", no_argument, NULL, 'b' },
436 { "noheadings", no_argument, NULL, 'n' },
c5cb5412 437 { "output", required_argument, NULL, 'o' },
1f583cb9 438 { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
a921a7de
MY
439 { "version", no_argument, NULL, 'V' },
440 { "help", no_argument, NULL, 'h' },
9b48766f
KZ
441 { "json", no_argument, NULL, 'J' },
442 { "raw", no_argument, NULL, 'r' },
a921a7de
MY
443 { NULL, 0, NULL, 0 },
444 };
445
446 setlocale(LC_ALL, "");
447 bindtextdomain(PACKAGE, LOCALEDIR);
448 textdomain(PACKAGE);
2c308875 449 close_stdout_atexit();
a921a7de 450
9b48766f 451 while ((c = getopt_long (argc, argv, "bno:JrVh", longopts, NULL)) != -1) {
a921a7de 452 switch (c) {
3f91dd88
KZ
453 case 'b':
454 ctl.bytes = 1;
455 break;
456 case 'n':
457 ctl.noheadings = 1;
458 break;
c5cb5412
KZ
459 case 'o':
460 outarg = optarg;
461 break;
1f583cb9
TW
462 case OPT_OUTPUT_ALL:
463 for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
464 columns[ncolumns] = ncolumns;
465 break;
9b48766f
KZ
466 case 'J':
467 ctl.json = 1;
468 break;
469 case 'r':
470 ctl.raw = 1;
471 break;
a921a7de 472 case 'V':
2c308875 473 print_version(EXIT_SUCCESS);
a921a7de 474 case 'h':
86be6a32 475 usage();
a921a7de
MY
476 default:
477 errtryhelp(EXIT_FAILURE);
478 }
479 }
480
481 if (optind == argc) {
b57fe43c
KZ
482 warnx(_("no file specified"));
483 errtryhelp(EXIT_FAILURE);
a921a7de
MY
484 }
485
3e37d7b7 486 if (!ncolumns) {
e4e8b57b 487 columns[ncolumns++] = COL_RES;
3e37d7b7
KZ
488 columns[ncolumns++] = COL_PAGES;
489 columns[ncolumns++] = COL_SIZE;
490 columns[ncolumns++] = COL_FILE;
491 }
492
c5cb5412
KZ
493 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
494 &ncolumns, column_name_to_id) < 0)
495 return EXIT_FAILURE;
496
3e37d7b7
KZ
497 scols_init_debug(0);
498 ctl.tb = scols_new_table();
499 if (!ctl.tb)
780ce22c 500 err(EXIT_FAILURE, _("failed to allocate output table"));
3e37d7b7 501
3f91dd88 502 scols_table_enable_noheadings(ctl.tb, ctl.noheadings);
9b48766f
KZ
503 scols_table_enable_raw(ctl.tb, ctl.raw);
504 scols_table_enable_json(ctl.tb, ctl.json);
505 if (ctl.json)
506 scols_table_set_name(ctl.tb, "fincore");
3f91dd88 507
3e37d7b7
KZ
508 for (i = 0; i < ncolumns; i++) {
509 const struct colinfo *col = get_column_info(i);
8945d9dc 510 struct libscols_column *cl;
a921a7de 511
8945d9dc
KZ
512 cl = scols_table_new_column(ctl.tb, col->name, col->whint, col->flags);
513 if (!cl)
780ce22c 514 err(EXIT_FAILURE, _("failed to allocate output column"));
8945d9dc
KZ
515
516 if (ctl.json) {
517 int id = get_column_id(i);
518
519 switch (id) {
520 case COL_FILE:
521 scols_column_set_json_type(cl, SCOLS_JSON_STRING);
522 break;
523 case COL_SIZE:
524 case COL_RES:
525 if (!ctl.bytes)
526 break;
527 /* fallthrough */
528 default:
529 scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
530 break;
531 }
532 }
3e37d7b7 533 }
b57fe43c 534
a921a7de 535 for(; optind < argc; optind++) {
c214cbeb
TW
536 struct fincore_state st = {
537 .name = argv[optind],
538 };
a921a7de 539
c214cbeb 540 switch (fincore_name(&ctl, &st)) {
3e37d7b7 541 case 0:
c214cbeb 542 add_output_data(&ctl, &st);
3e37d7b7
KZ
543 break;
544 case 1:
545 break; /* ignore */
546 default:
b57fe43c 547 rc = EXIT_FAILURE;
3e37d7b7 548 break;
a921a7de
MY
549 }
550 }
551
3e37d7b7
KZ
552 scols_print_table(ctl.tb);
553 scols_unref_table(ctl.tb);
554
b57fe43c 555 return rc;
a921a7de 556}