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 };