(root)/
util-linux-2.39/
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  }