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