(root)/
util-linux-2.39/
misc-utils/
lsfd-file.c
       1  /*
       2   * lsfd(1) - list file descriptors
       3   *
       4   * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
       5   * Written by Masatake YAMATO <yamato@redhat.com>
       6   *
       7   * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
       8   * It supports multiple OSes. lsfd specializes to Linux.
       9   *
      10   * This program is free software; you can redistribute it and/or modify
      11   * it under the terms of the GNU General Public License as published by
      12   * the Free Software Foundation; either version 2 of the License, or
      13   * (at your option) any later version.
      14   *
      15   * This program is distributed in the hope that it would be useful,
      16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18   * GNU General Public License for more details.
      19   *
      20   * You should have received a copy of the GNU General Public License
      21   * along with this program; if not, write to the Free Software Foundation,
      22   * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      23   */
      24  
      25  #include <unistd.h>
      26  
      27  #ifdef HAVE_LINUX_NSFS_H
      28  # include <linux/nsfs.h>
      29  # if defined(NS_GET_NSTYPE)
      30  #  define USE_NS_GET_API	1
      31  #  include <sys/ioctl.h>
      32  # endif
      33  #endif
      34  #include <linux/sched.h>
      35  
      36  #include "xalloc.h"
      37  #include "nls.h"
      38  #include "buffer.h"
      39  #include "idcache.h"
      40  #include "strutils.h"
      41  
      42  #include "libsmartcols.h"
      43  
      44  #include "lsfd.h"
      45  
      46  static struct idcache *username_cache;
      47  
      48  static const char *assocstr[N_ASSOCS] = {
      49  	[ASSOC_CWD]       = "cwd",
      50  	[ASSOC_EXE]       = "exe",
      51  	/* "root" appears as user names, too.
      52  	 * So we use "rtd" here instead of "root". */
      53  	[ASSOC_ROOT]      = "rtd",
      54  	[ASSOC_NS_CGROUP] = "cgroup",
      55  	[ASSOC_NS_IPC]    = "ipc",
      56  	[ASSOC_NS_MNT]    = "mnt",
      57  	[ASSOC_NS_NET]    = "net",
      58  	[ASSOC_NS_PID]    = "pid",
      59  	[ASSOC_NS_PID4C]  = "pid4c",
      60  	[ASSOC_NS_TIME]   = "time",
      61  	[ASSOC_NS_TIME4C] = "time4c",
      62  	[ASSOC_NS_USER]   = "user",
      63  	[ASSOC_NS_UTS]    = "uts",
      64  	[ASSOC_MEM]       = "mem",
      65  	[ASSOC_SHM]       = "shm",
      66  };
      67  
      68  static const char *strftype(mode_t ftype)
      69  {
      70  	switch (ftype) {
      71  	case S_IFBLK:
      72  		return "BLK";
      73  	case S_IFCHR:
      74  		return "CHR";
      75  	case S_IFDIR:
      76  		return "DIR";
      77  	case S_IFIFO:
      78  		return "FIFO";
      79  	case S_IFLNK:
      80  		return "LINK";
      81  	case S_IFREG:
      82  		return "REG";
      83  	case S_IFSOCK:
      84  		return "SOCK";
      85  	default:
      86  		return "UNKN";
      87  	}
      88  }
      89  
      90  extern void lsfd_decode_file_flags(struct ul_buffer *buf, int flags);
      91  static void file_fill_flags_buf(struct ul_buffer *buf, int flags)
      92  {
      93  	lsfd_decode_file_flags(buf, flags);
      94  }
      95  
      96  #define does_file_has_fdinfo_alike(file)	\
      97  	((file)->association >= 0		\
      98  	 || (file)->association == -ASSOC_SHM	\
      99  	 || (file)->association == -ASSOC_MEM)
     100  
     101  static uint64_t get_map_length(struct file *file)
     102  {
     103  	uint64_t res = 0;
     104  
     105  	if (is_association(file, SHM) || is_association(file, MEM)) {
     106  		static size_t pagesize = 0;
     107  
     108  		if (!pagesize)
     109  			pagesize = getpagesize();
     110  
     111  		res = (file->map_end - file->map_start) / pagesize;
     112  	}
     113  
     114  	return res;
     115  }
     116  
     117  static bool file_fill_column(struct proc *proc,
     118  			     struct file *file,
     119  			     struct libscols_line *ln,
     120  			     int column_id,
     121  			     size_t column_index)
     122  {
     123  	char *str = NULL;
     124  	mode_t ftype;
     125  	const char *partition;
     126  
     127  	switch(column_id) {
     128  	case COL_COMMAND:
     129  		if (proc->command
     130  		    && scols_line_set_data(ln, column_index, proc->command))
     131  			err(EXIT_FAILURE, _("failed to add output data"));
     132  		return true;
     133  	case COL_KNAME:
     134  	case COL_NAME:
     135  		if (file->name
     136  		    && scols_line_set_data(ln, column_index, file->name))
     137  			err(EXIT_FAILURE, _("failed to add output data"));
     138  		return true;
     139  	case COL_STTYPE:
     140  	case COL_TYPE:
     141  		ftype = file->stat.st_mode & S_IFMT;
     142  		if (scols_line_set_data(ln, column_index, strftype(ftype)))
     143  			err(EXIT_FAILURE, _("failed to add output data"));
     144  		return true;
     145  	case COL_USER:
     146  		add_uid(username_cache, (int)proc->uid);
     147  		if (scols_line_set_data(ln, column_index,
     148  					get_id(username_cache,
     149  					       (int)proc->uid)->name))
     150  			err(EXIT_FAILURE, _("failed to add output data"));
     151  		return true;
     152  	case COL_OWNER:
     153  		add_uid(username_cache, (int)file->stat.st_uid);
     154  		if (scols_line_set_data(ln, column_index,
     155  					get_id(username_cache,
     156  					       (int)file->stat.st_uid)->name))
     157  			err(EXIT_FAILURE, _("failed to add output data"));
     158  		return true;
     159  	case COL_DEVTYPE:
     160  		if (scols_line_set_data(ln, column_index,
     161  					"nodev"))
     162  			err(EXIT_FAILURE, _("failed to add output data"));
     163  		return true;
     164  	case COL_FD:
     165  		if (!is_opened_file(file))
     166  			return false;
     167  		/* FALL THROUGH */
     168  	case COL_ASSOC:
     169  		if (is_opened_file(file))
     170  			xasprintf(&str, "%d", file->association);
     171  		else {
     172  			int assoc = file->association * -1;
     173  			if (assoc >= N_ASSOCS)
     174  				return false; /* INTERNAL ERROR */
     175  			xasprintf(&str, "%s", assocstr[assoc]);
     176  		}
     177  		break;
     178  	case COL_INODE:
     179  		xasprintf(&str, "%llu", (unsigned long long)file->stat.st_ino);
     180  		break;
     181  	case COL_SOURCE:
     182  		if (major(file->stat.st_dev) == 0) {
     183  			const char *filesystem = get_nodev_filesystem(minor(file->stat.st_dev));
     184  			if (filesystem) {
     185  				xasprintf(&str, "%s", filesystem);
     186  				break;
     187  			}
     188  		}
     189  		/* FALL THROUGH */
     190  	case COL_PARTITION:
     191  		partition = get_partition(file->stat.st_dev);
     192  		if (partition) {
     193  			str = xstrdup(partition);
     194  			break;
     195  		}
     196  		/* FALL THROUGH */
     197  	case COL_DEV:
     198  	case COL_MAJMIN:
     199  		xasprintf(&str, "%u:%u",
     200  			  major(file->stat.st_dev),
     201  			  minor(file->stat.st_dev));
     202  		break;
     203  	case COL_RDEV:
     204  		xasprintf(&str, "%u:%u",
     205  			  major(file->stat.st_rdev),
     206  			  minor(file->stat.st_rdev));
     207  		break;
     208  	case COL_PID:
     209  		xasprintf(&str, "%d", (int)proc->leader->pid);
     210  		break;
     211  	case COL_TID:
     212  		xasprintf(&str, "%d", (int)proc->pid);
     213  		break;
     214  	case COL_UID:
     215  		xasprintf(&str, "%d", (int)proc->uid);
     216  		break;
     217  	case COL_FUID:
     218  		xasprintf(&str, "%d", (int)file->stat.st_uid);
     219  		break;
     220  	case COL_SIZE:
     221  		xasprintf(&str, "%jd", (intmax_t)file->stat.st_size);
     222  		break;
     223  	case COL_NLINK:
     224  		xasprintf(&str, "%ju", (uintmax_t)file->stat.st_nlink);
     225  		break;
     226  	case COL_DELETED:
     227  		xasprintf(&str, "%d", file->stat.st_nlink == 0);
     228  		break;
     229  	case COL_KTHREAD:
     230  		xasprintf(&str, "%u", proc->kthread);
     231  		break;
     232  	case COL_MNT_ID:
     233  		xasprintf(&str, "%d", is_opened_file(file)? file->mnt_id: 0);
     234  		break;
     235  	case COL_MODE:
     236  		if (does_file_has_fdinfo_alike(file))
     237  			xasprintf(&str, "%c%c%c",
     238  				  file->mode & S_IRUSR? 'r': '-',
     239  				  file->mode & S_IWUSR? 'w': '-',
     240  				  (is_mapped_file(file)
     241  				   && file->mode & S_IXUSR)? 'x': '-');
     242  		else
     243  			xasprintf(&str, "---");
     244  		break;
     245  	case COL_POS:
     246  		xasprintf(&str, "%" PRIu64,
     247  			  (does_file_has_fdinfo_alike(file))? file->pos: 0);
     248  		break;
     249  	case COL_FLAGS: {
     250  		struct ul_buffer buf = UL_INIT_BUFFER;
     251  
     252  		if (!is_opened_file(file))
     253  			return true;
     254  
     255  		if (file->sys_flags == 0)
     256  			return true;
     257  
     258  		file_fill_flags_buf(&buf, file->sys_flags);
     259  		if (ul_buffer_is_empty(&buf))
     260  			return true;
     261  		str = ul_buffer_get_data(&buf, NULL, NULL);
     262  		break;
     263  	}
     264  	case COL_MAPLEN:
     265  		if (!is_mapped_file(file))
     266  			return true;
     267  		xasprintf(&str, "%ju", (uintmax_t)get_map_length(file));
     268  		break;
     269  	default:
     270  		return false;
     271  	};
     272  
     273  	if (!str)
     274  		err(EXIT_FAILURE, _("failed to add output data"));
     275  	if (scols_line_refer_data(ln, column_index, str))
     276  		err(EXIT_FAILURE, _("failed to add output data"));
     277  	return true;
     278  }
     279  
     280  static int file_handle_fdinfo(struct file *file, const char *key, const char* value)
     281  {
     282  	int rc;
     283  
     284  	if (strcmp(key, "pos") == 0) {
     285  		rc = ul_strtou64(value, &file->pos, 10);
     286  
     287  	} else if (strcmp(key, "flags") == 0) {
     288  		rc = ul_strtou32(value, &file->sys_flags, 8);
     289  
     290  	} else if (strcmp(key, "mnt_id") == 0) {
     291  		rc = ul_strtou32(value, &file->mnt_id, 10);
     292  
     293  	} else
     294  		return 0;	/* ignore -- unknown item */
     295  
     296  	if (rc < 0)
     297  		return 0;	/* ignore -- parse failed */
     298  
     299  	return 1;		/* success */
     300  }
     301  
     302  static void file_free_content(struct file *file)
     303  {
     304  	free(file->name);
     305  }
     306  
     307  static void file_class_initialize(void)
     308  {
     309  	username_cache = new_idcache();
     310  	if (!username_cache)
     311  		err(EXIT_FAILURE, _("failed to allocate UID cache"));
     312  }
     313  
     314  static void file_class_finalize(void)
     315  {
     316  	free_idcache(username_cache);
     317  }
     318  
     319  const struct file_class file_class = {
     320  	.super = NULL,
     321  	.size = sizeof(struct file),
     322  	.initialize_class = file_class_initialize,
     323  	.finalize_class = file_class_finalize,
     324  	.fill_column = file_fill_column,
     325  	.handle_fdinfo = file_handle_fdinfo,
     326  	.free_content = file_free_content,
     327  };
     328  
     329  /*
     330   * Regular files on NSFS
     331   */
     332  
     333  struct nsfs_file {
     334  	struct file file;
     335  	int clone_type;
     336  };
     337  
     338  static const char *get_ns_type_name(int clone_type)
     339  {
     340  	switch (clone_type) {
     341  #ifdef USE_NS_GET_API
     342  	case CLONE_NEWNS:
     343  		return "mnt";
     344  	case CLONE_NEWCGROUP:
     345  		return "cgroup";
     346  	case CLONE_NEWUTS:
     347  		return "uts";
     348  	case CLONE_NEWIPC:
     349  		return "ipc";
     350  	case CLONE_NEWUSER:
     351  		return "user";
     352  	case CLONE_NEWPID:
     353  		return "pid";
     354  	case CLONE_NEWNET:
     355  		return "net";
     356  #ifdef CLONE_NEWTIME
     357  	case CLONE_NEWTIME:
     358  		return "time";
     359  #endif	/* CLONE_NEWTIME */
     360  #endif	/* USE_NS_GET_API */
     361  	default:
     362  		return "unknown";
     363  	}
     364  }
     365  
     366  static void init_nsfs_file_content(struct file *file)
     367  {
     368  	struct nsfs_file *nsfs_file = (struct nsfs_file *)file;
     369  	nsfs_file->clone_type = -1;
     370  
     371  #ifdef USE_NS_GET_API
     372  	char *proc_fname = NULL;
     373  	int ns_fd;
     374  	int ns_type;
     375  
     376  	if (is_association (file, NS_CGROUP))
     377  		nsfs_file->clone_type = CLONE_NEWCGROUP;
     378  	else if (is_association (file, NS_IPC))
     379  		nsfs_file->clone_type = CLONE_NEWIPC;
     380  	else if (is_association (file, NS_MNT))
     381  		nsfs_file->clone_type = CLONE_NEWNS;
     382  	else if (is_association (file, NS_NET))
     383  		nsfs_file->clone_type = CLONE_NEWNET;
     384  	else if (is_association (file, NS_PID)
     385  		 || is_association (file, NS_PID4C))
     386  		nsfs_file->clone_type = CLONE_NEWPID;
     387  #ifdef CLONE_NEWTIME
     388  	else if (is_association (file, NS_TIME)
     389  		 || is_association (file, NS_TIME4C))
     390  		nsfs_file->clone_type = CLONE_NEWTIME;
     391  #endif
     392  	else if (is_association (file, NS_USER))
     393  		nsfs_file->clone_type = CLONE_NEWUSER;
     394  	else if (is_association (file, NS_UTS))
     395  		nsfs_file->clone_type = CLONE_NEWUTS;
     396  
     397  	if (nsfs_file->clone_type != -1)
     398  		return;
     399  
     400  	if (!is_opened_file(file))
     401  		return;
     402  
     403  	if (!file->name)
     404  		return;
     405  
     406  	xasprintf(&proc_fname, "/proc/%d/fd/%d",
     407  		  file->proc->pid, file->association);
     408  	ns_fd = open(proc_fname, O_RDONLY);
     409  	free(proc_fname);
     410  	if (ns_fd < 0)
     411  		return;
     412  
     413  	ns_type = ioctl(ns_fd, NS_GET_NSTYPE);
     414  	close(ns_fd);
     415  	if (ns_type < 0)
     416  		return;
     417  
     418  	nsfs_file->clone_type = ns_type;
     419  #endif	/* USE_NS_GET_API */
     420  }
     421  
     422  
     423  static bool nsfs_file_fill_column(struct proc *proc __attribute__((__unused__)),
     424  				  struct file *file,
     425  				  struct libscols_line *ln,
     426  				  int column_id,
     427  				  size_t column_index)
     428  {
     429  	struct nsfs_file *nsfs_file = (struct nsfs_file *)file;
     430  	char *name = NULL;
     431  
     432  	if (nsfs_file->clone_type == -1)
     433  		return false;
     434  
     435  	switch (column_id) {
     436  	case COL_NS_NAME:
     437  		xasprintf(&name, "%s:[%llu]",
     438  			  get_ns_type_name(nsfs_file->clone_type),
     439  			  (unsigned long long)file->stat.st_ino);
     440  		break;
     441  	case COL_NS_TYPE:
     442  		if (scols_line_set_data(ln, column_index,
     443  					get_ns_type_name(nsfs_file->clone_type)))
     444  			err(EXIT_FAILURE, _("failed to add output data"));
     445  		return true;
     446  	default:
     447  		return false;
     448  	}
     449  
     450  	if (name && scols_line_refer_data(ln, column_index, name))
     451  		err(EXIT_FAILURE, _("failed to add output data"));
     452  
     453  	return true;
     454  }
     455  
     456  const struct file_class nsfs_file_class = {
     457  	.super = &file_class,
     458  	.size = sizeof(struct nsfs_file),
     459  	.initialize_class = NULL,
     460  	.finalize_class = NULL,
     461  	.initialize_content = init_nsfs_file_content,
     462  	.free_content = NULL,
     463  	.fill_column = nsfs_file_fill_column,
     464  	.handle_fdinfo = NULL,
     465  };