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