]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/fincore.c
fincore: refactor output formatting
[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
3e37d7b7
KZ
46
47struct colinfo {
37b2b3fa 48 const char * const name;
3e37d7b7
KZ
49 double whint;
50 int flags;
51 const char *help;
e5530e33 52 unsigned int pages : 1;
3e37d7b7
KZ
53};
54
55enum {
56 COL_PAGES,
57 COL_SIZE,
e4e8b57b
KZ
58 COL_FILE,
59 COL_RES
3e37d7b7
KZ
60};
61
37b2b3fa 62static const struct colinfo infos[] = {
e5530e33 63 [COL_PAGES] = { "PAGES", 1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages"), 1},
2bb3aa36 64 [COL_RES] = { "RES", 5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")},
3e37d7b7
KZ
65 [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the file")},
66 [COL_FILE] = { "FILE", 4, 0, N_("file name")},
67};
68
69static int columns[ARRAY_SIZE(infos) * 2] = {-1};
70static size_t ncolumns;
71
72struct fincore_control {
452089fa 73 const size_t pagesize;
3e37d7b7
KZ
74
75 struct libscols_table *tb; /* output */
76
3f91dd88 77 unsigned int bytes : 1,
9b48766f
KZ
78 noheadings : 1,
79 raw : 1,
80 json : 1;
3e37d7b7
KZ
81};
82
a921a7de 83
3e37d7b7
KZ
84static int column_name_to_id(const char *name, size_t namesz)
85{
86 size_t i;
87
88 for (i = 0; i < ARRAY_SIZE(infos); i++) {
89 const char *cn = infos[i].name;
90
91 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
92 return i;
93 }
94 warnx(_("unknown column: %s"), name);
95 return -1;
96}
97
98static int get_column_id(int num)
a921a7de 99{
3e37d7b7
KZ
100 assert(num >= 0);
101 assert((size_t) num < ncolumns);
102 assert(columns[num] < (int) ARRAY_SIZE(infos));
103 return columns[num];
a921a7de
MY
104}
105
3e37d7b7 106static const struct colinfo *get_column_info(int num)
a921a7de 107{
3e37d7b7 108 return &infos[ get_column_id(num) ];
a921a7de
MY
109}
110
3e37d7b7
KZ
111static int add_output_data(struct fincore_control *ctl,
112 const char *name,
113 off_t file_size,
114 off_t count_incore)
115{
116 size_t i;
117 char *tmp;
e5530e33 118 uint64_t value;
3e37d7b7
KZ
119 struct libscols_line *ln;
120
121 assert(ctl);
122 assert(ctl->tb);
123
124 ln = scols_table_new_line(ctl->tb, NULL);
125 if (!ln)
780ce22c 126 err(EXIT_FAILURE, _("failed to allocate output line"));
3e37d7b7
KZ
127
128 for (i = 0; i < ncolumns; i++) {
b7ebf49c 129 int rc = 0;
e5530e33
TW
130 int column_id = get_column_id(i);
131 int format_value = 0;
b7ebf49c 132
e5530e33 133 switch(column_id) {
3e37d7b7 134 case COL_FILE:
b7ebf49c 135 rc = scols_line_set_data(ln, i, name);
3e37d7b7
KZ
136 break;
137 case COL_PAGES:
e4e8b57b 138 case COL_RES:
e5530e33
TW
139 value = count_incore;
140 format_value = 1;
e4e8b57b 141 break;
3e37d7b7
KZ
142 case COL_SIZE:
143 if (ctl->bytes)
144 xasprintf(&tmp, "%jd", (intmax_t) file_size);
145 else
146 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, file_size);
b7ebf49c 147 rc = scols_line_refer_data(ln, i, tmp);
3e37d7b7
KZ
148 break;
149 default:
150 return -EINVAL;
151 }
b7ebf49c 152
e5530e33
TW
153 if (format_value) {
154 if (infos[column_id].pages) {
155 xasprintf(&tmp, "%ju", (uintmax_t) value);
156 } else {
157 value *= ctl->pagesize;
158 if (ctl->bytes)
159 xasprintf(&tmp, "%ju", (uintmax_t) value);
160 else
161 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, value);
162 }
163 rc = scols_line_refer_data(ln, i, tmp);
164 }
165
b7ebf49c
KZ
166 if (rc)
167 err(EXIT_FAILURE, _("failed to add output data"));
3e37d7b7
KZ
168 }
169
170 return 0;
171}
172
173static int do_mincore(struct fincore_control *ctl,
174 void *window, const size_t len,
175 const char *name,
176 off_t *count_incore)
a921a7de
MY
177{
178 static unsigned char vec[N_PAGES_IN_WINDOW];
3e37d7b7 179 int n = (len / ctl->pagesize) + ((len % ctl->pagesize)? 1: 0);
a921a7de
MY
180
181 if (mincore (window, len, vec) < 0) {
182 warn(_("failed to do mincore: %s"), name);
b57fe43c 183 return -errno;
a921a7de
MY
184 }
185
186 while (n > 0)
187 {
188 if (vec[--n] & 0x1)
189 {
190 vec[n] = 0;
191 (*count_incore)++;
192 }
193 }
194
b57fe43c 195 return 0;
a921a7de
MY
196}
197
3e37d7b7
KZ
198static int fincore_fd (struct fincore_control *ctl,
199 int fd,
200 const char *name,
a921a7de
MY
201 off_t file_size,
202 off_t *count_incore)
203{
452089fa 204 size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize;
fcfe1a5c 205 off_t file_offset, len;
b57fe43c 206 int rc = 0;
a921a7de 207
fcfe1a5c 208 for (file_offset = 0; file_offset < file_size; file_offset += len) {
57940872 209 void *window = NULL;
a921a7de
MY
210
211 len = file_size - file_offset;
ea62c152 212 if (len >= (off_t) window_size)
a921a7de
MY
213 len = window_size;
214
cf397444
TW
215 /* PROT_NONE is enough for Linux, but qemu-user wants PROT_READ */
216 window = mmap(window, len, PROT_READ, MAP_PRIVATE, fd, file_offset);
a921a7de 217 if (window == MAP_FAILED) {
374cb543
KZ
218 rc = -EINVAL;
219 warn(_("failed to do mmap: %s"), name);
a921a7de
MY
220 break;
221 }
222
3e37d7b7 223 rc = do_mincore(ctl, window, len, name, count_incore);
b57fe43c
KZ
224 if (rc)
225 break;
a921a7de
MY
226
227 munmap (window, len);
228 }
229
b57fe43c 230 return rc;
a921a7de
MY
231}
232
3e37d7b7
KZ
233/*
234 * Returns: <0 on error, 0 success, 1 ignore.
235 */
236static int fincore_name(struct fincore_control *ctl,
237 const char *name,
ab0a7517 238 unsigned long long *size,
3e37d7b7 239 off_t *count_incore)
a921a7de
MY
240{
241 int fd;
b57fe43c 242 int rc = 0;
ab0a7517 243 struct stat sb;
a921a7de
MY
244
245 if ((fd = open (name, O_RDONLY)) < 0) {
246 warn(_("failed to open: %s"), name);
cff1c113 247 return -errno;
a921a7de
MY
248 }
249
ab0a7517 250 if (fstat (fd, &sb) < 0) {
a921a7de 251 warn(_("failed to do fstat: %s"), name);
b9dc8d07 252 close (fd);
b57fe43c 253 return -errno;
a921a7de
MY
254 }
255
5a02eb13
JU
256 if (S_ISBLK(sb.st_mode)) {
257 rc = blkdev_get_size(fd, size);
258 if (rc)
ab0a7517 259 warn(_("failed ioctl to get size: %s"), name);
5a02eb13
JU
260 } else if (S_ISREG(sb.st_mode)) {
261 *size = sb.st_size;
ab0a7517
JU
262 } else {
263 rc = 1; /* ignore things like symlinks
264 * and directories*/
265 }
a921a7de 266
5a02eb13
JU
267 if (!rc) {
268 rc = fincore_fd(ctl, fd, name, *size, count_incore);
269 }
270
a921a7de 271 close (fd);
b57fe43c 272 return rc;
a921a7de
MY
273}
274
86be6a32 275static void __attribute__((__noreturn__)) usage(void)
3f91dd88 276{
86be6a32 277 FILE *out = stdout;
c5cb5412
KZ
278 size_t i;
279
3f91dd88
KZ
280 fputs(USAGE_HEADER, out);
281 fprintf(out, _(" %s [options] file...\n"), program_invocation_short_name);
282
283 fputs(USAGE_OPTIONS, out);
9b48766f 284 fputs(_(" -J, --json use JSON output format\n"), out);
c5cb5412
KZ
285 fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out);
286 fputs(_(" -n, --noheadings don't print headings\n"), out);
287 fputs(_(" -o, --output <list> output columns\n"), out);
1f583cb9 288 fputs(_(" --output-all output all columns\n"), out);
9b48766f 289 fputs(_(" -r, --raw use raw output format\n"), out);
3f91dd88
KZ
290
291 fputs(USAGE_SEPARATOR, out);
f45f3ec3 292 printf(USAGE_HELP_OPTIONS(23));
3f91dd88 293
c3a4cfc5 294 fprintf(out, USAGE_COLUMNS);
c5cb5412
KZ
295
296 for (i = 0; i < ARRAY_SIZE(infos); i++)
297 fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
298
f45f3ec3 299 printf(USAGE_MAN_TAIL("fincore(1)"));
3f91dd88 300
86be6a32 301 exit(EXIT_SUCCESS);
3f91dd88
KZ
302}
303
a921a7de
MY
304int main(int argc, char ** argv)
305{
306 int c;
3e37d7b7 307 size_t i;
b57fe43c 308 int rc = EXIT_SUCCESS;
c5cb5412 309 char *outarg = NULL;
a921a7de 310
3e37d7b7 311 struct fincore_control ctl = {
c5cb5412 312 .pagesize = getpagesize()
3e37d7b7
KZ
313 };
314
1f583cb9
TW
315 enum {
316 OPT_OUTPUT_ALL = CHAR_MAX + 1
317 };
a921a7de 318 static const struct option longopts[] = {
3f91dd88
KZ
319 { "bytes", no_argument, NULL, 'b' },
320 { "noheadings", no_argument, NULL, 'n' },
c5cb5412 321 { "output", required_argument, NULL, 'o' },
1f583cb9 322 { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
a921a7de
MY
323 { "version", no_argument, NULL, 'V' },
324 { "help", no_argument, NULL, 'h' },
9b48766f
KZ
325 { "json", no_argument, NULL, 'J' },
326 { "raw", no_argument, NULL, 'r' },
a921a7de
MY
327 { NULL, 0, NULL, 0 },
328 };
329
330 setlocale(LC_ALL, "");
331 bindtextdomain(PACKAGE, LOCALEDIR);
332 textdomain(PACKAGE);
2c308875 333 close_stdout_atexit();
a921a7de 334
9b48766f 335 while ((c = getopt_long (argc, argv, "bno:JrVh", longopts, NULL)) != -1) {
a921a7de 336 switch (c) {
3f91dd88
KZ
337 case 'b':
338 ctl.bytes = 1;
339 break;
340 case 'n':
341 ctl.noheadings = 1;
342 break;
c5cb5412
KZ
343 case 'o':
344 outarg = optarg;
345 break;
1f583cb9
TW
346 case OPT_OUTPUT_ALL:
347 for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
348 columns[ncolumns] = ncolumns;
349 break;
9b48766f
KZ
350 case 'J':
351 ctl.json = 1;
352 break;
353 case 'r':
354 ctl.raw = 1;
355 break;
a921a7de 356 case 'V':
2c308875 357 print_version(EXIT_SUCCESS);
a921a7de 358 case 'h':
86be6a32 359 usage();
a921a7de
MY
360 default:
361 errtryhelp(EXIT_FAILURE);
362 }
363 }
364
365 if (optind == argc) {
b57fe43c
KZ
366 warnx(_("no file specified"));
367 errtryhelp(EXIT_FAILURE);
a921a7de
MY
368 }
369
3e37d7b7 370 if (!ncolumns) {
e4e8b57b 371 columns[ncolumns++] = COL_RES;
3e37d7b7
KZ
372 columns[ncolumns++] = COL_PAGES;
373 columns[ncolumns++] = COL_SIZE;
374 columns[ncolumns++] = COL_FILE;
375 }
376
c5cb5412
KZ
377 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
378 &ncolumns, column_name_to_id) < 0)
379 return EXIT_FAILURE;
380
3e37d7b7
KZ
381 scols_init_debug(0);
382 ctl.tb = scols_new_table();
383 if (!ctl.tb)
780ce22c 384 err(EXIT_FAILURE, _("failed to allocate output table"));
3e37d7b7 385
3f91dd88 386 scols_table_enable_noheadings(ctl.tb, ctl.noheadings);
9b48766f
KZ
387 scols_table_enable_raw(ctl.tb, ctl.raw);
388 scols_table_enable_json(ctl.tb, ctl.json);
389 if (ctl.json)
390 scols_table_set_name(ctl.tb, "fincore");
3f91dd88 391
3e37d7b7
KZ
392 for (i = 0; i < ncolumns; i++) {
393 const struct colinfo *col = get_column_info(i);
8945d9dc 394 struct libscols_column *cl;
a921a7de 395
8945d9dc
KZ
396 cl = scols_table_new_column(ctl.tb, col->name, col->whint, col->flags);
397 if (!cl)
780ce22c 398 err(EXIT_FAILURE, _("failed to allocate output column"));
8945d9dc
KZ
399
400 if (ctl.json) {
401 int id = get_column_id(i);
402
403 switch (id) {
404 case COL_FILE:
405 scols_column_set_json_type(cl, SCOLS_JSON_STRING);
406 break;
407 case COL_SIZE:
408 case COL_RES:
409 if (!ctl.bytes)
410 break;
411 /* fallthrough */
412 default:
413 scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
414 break;
415 }
416 }
3e37d7b7 417 }
b57fe43c 418
a921a7de
MY
419 for(; optind < argc; optind++) {
420 char *name = argv[optind];
a921a7de 421 off_t count_incore = 0;
ab0a7517 422 unsigned long long size = 0;
a921a7de 423
ab0a7517 424 switch (fincore_name(&ctl, name, &size, &count_incore)) {
3e37d7b7 425 case 0:
ab0a7517 426 add_output_data(&ctl, name, size, count_incore);
3e37d7b7
KZ
427 break;
428 case 1:
429 break; /* ignore */
430 default:
b57fe43c 431 rc = EXIT_FAILURE;
3e37d7b7 432 break;
a921a7de
MY
433 }
434 }
435
3e37d7b7
KZ
436 scols_print_table(ctl.tb);
437 scols_unref_table(ctl.tb);
438
b57fe43c 439 return rc;
a921a7de 440}