2021-03-24 03:43:51 +09:00
|
|
|
/*
|
|
|
|
* lsfd(1) - list file descriptors
|
|
|
|
*
|
|
|
|
* Copyright (C) 2021 Red Hat, Inc. All rights reserved.
|
|
|
|
* Written by Masatake YAMATO <yamato@redhat.com>
|
2021-10-06 11:11:16 +02:00
|
|
|
* Karel Zak <kzak@redhat.com>
|
2021-03-24 03:43:51 +09:00
|
|
|
*
|
|
|
|
* Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
|
|
|
|
* It supports multiple OSes. lsfd specializes to Linux.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it would be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
|
2021-05-05 03:37:57 +09:00
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <linux/kcmp.h>
|
|
|
|
static int kcmp(pid_t pid1, pid_t pid2, int type,
|
|
|
|
unsigned long idx1, unsigned long idx2)
|
|
|
|
{
|
|
|
|
return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
|
|
|
|
}
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
#include "c.h"
|
|
|
|
#include "nls.h"
|
|
|
|
#include "xalloc.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "closestream.h"
|
|
|
|
#include "strutils.h"
|
2021-09-07 17:40:55 +02:00
|
|
|
#include "procfs.h"
|
2021-03-24 03:43:51 +09:00
|
|
|
#include "fileutils.h"
|
2021-03-26 16:55:57 +09:00
|
|
|
#include "idcache.h"
|
2021-09-08 10:28:14 +02:00
|
|
|
#include "pathnames.h"
|
2021-03-24 03:43:51 +09:00
|
|
|
|
|
|
|
#include "libsmartcols.h"
|
|
|
|
|
2021-03-25 07:42:15 +09:00
|
|
|
#include "lsfd.h"
|
2021-09-21 02:58:48 +09:00
|
|
|
#include "lsfd-filter.h"
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-05-09 10:41:34 +09:00
|
|
|
/*
|
|
|
|
* /proc/$pid/mountinfo entries
|
|
|
|
*/
|
|
|
|
struct nodev {
|
|
|
|
struct list_head nodevs;
|
|
|
|
unsigned long minor;
|
|
|
|
char *filesystem;
|
|
|
|
};
|
|
|
|
|
2021-05-14 05:24:21 +09:00
|
|
|
struct nodev_table {
|
|
|
|
#define NODEV_TABLE_SIZE 97
|
|
|
|
struct list_head tables[NODEV_TABLE_SIZE];
|
|
|
|
} nodev_table;
|
|
|
|
|
2021-09-01 18:12:09 +02:00
|
|
|
struct name_manager {
|
|
|
|
struct idcache *cache;
|
|
|
|
unsigned long next_id;
|
|
|
|
};
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
/*
|
|
|
|
* Column related stuffs
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* column names */
|
|
|
|
struct colinfo {
|
|
|
|
const char *name;
|
|
|
|
double whint;
|
|
|
|
int flags;
|
2021-05-03 15:33:56 +09:00
|
|
|
int json_type;
|
2021-03-24 03:43:51 +09:00
|
|
|
const char *help;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* columns descriptions */
|
|
|
|
static struct colinfo infos[] = {
|
2021-05-03 15:33:56 +09:00
|
|
|
[COL_ASSOC] = { "ASSOC", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("association between file and process") },
|
2021-05-08 03:51:25 +09:00
|
|
|
[COL_CHRDRV] = { "CHRDRV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
2021-10-01 20:39:48 +02:00
|
|
|
N_("character device driver name resolved by /proc/devices") },
|
2021-09-01 16:50:04 +02:00
|
|
|
[COL_COMMAND] = { "COMMAND",0.3, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
|
2021-05-03 15:33:56 +09:00
|
|
|
N_("command of the process opening the file") },
|
2021-05-03 16:22:29 +09:00
|
|
|
[COL_DELETED] = { "DELETED", 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
|
|
|
|
N_("reachability from the file system") },
|
2021-05-03 15:33:56 +09:00
|
|
|
[COL_DEV] = { "DEV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("ID of device containing file") },
|
2021-09-17 00:22:14 +09:00
|
|
|
[COL_DEVTYPE] = { "DEVTYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
2021-10-04 04:37:19 +09:00
|
|
|
N_("device type (blk, char, or nodev)") },
|
2021-05-06 14:28:58 +09:00
|
|
|
[COL_FLAGS] = { "FLAGS", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("flags specified when opening the file") },
|
2021-05-03 15:33:56 +09:00
|
|
|
[COL_FD] = { "FD", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("file descriptor for the file") },
|
|
|
|
[COL_INODE] = { "INODE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("inode number") },
|
2021-09-17 00:01:30 +09:00
|
|
|
[COL_MAJMIN] = { "MAJ:MIN", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("device ID for special, or ID of device containing file") },
|
2021-05-10 12:54:55 +09:00
|
|
|
[COL_MAPLEN] = { "MAPLEN", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("length of file mapping (in page)") },
|
2021-05-08 04:47:23 +09:00
|
|
|
[COL_MISCDEV] = { "MISCDEV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
2021-10-01 20:39:48 +02:00
|
|
|
N_("misc character device name resolved by /proc/misc") },
|
2021-05-06 14:28:58 +09:00
|
|
|
[COL_MNT_ID] = { "MNTID", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("mount id") },
|
2021-05-06 14:29:43 +09:00
|
|
|
[COL_MODE] = { "MODE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("access mode (rwx)") },
|
2021-09-01 16:50:04 +02:00
|
|
|
[COL_NAME] = { "NAME", 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
|
2021-05-03 15:33:56 +09:00
|
|
|
N_("name of the file") },
|
2021-05-03 16:22:29 +09:00
|
|
|
[COL_NLINK] = { "NLINK", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("link count") },
|
2021-05-03 15:47:02 +09:00
|
|
|
[COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
2021-05-03 15:33:56 +09:00
|
|
|
N_("PID of the process opening the file") },
|
2021-05-08 03:28:46 +09:00
|
|
|
[COL_PARTITION]={ "PARTITION",0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("block device name resolved by /proc/partition") },
|
2021-05-06 14:28:58 +09:00
|
|
|
[COL_POS] = { "POS", 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("file position") },
|
2021-05-07 03:57:45 +09:00
|
|
|
[COL_PROTONAME]={ "PROTONAME",0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("protocol name") },
|
2021-05-03 15:33:56 +09:00
|
|
|
[COL_RDEV] = { "RDEV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("device ID (if special file)") },
|
2021-05-03 15:59:26 +09:00
|
|
|
[COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("file size"), },
|
2021-09-17 01:04:48 +09:00
|
|
|
[COL_SOURCE] = { "SOURCE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("file system, partition, or device containing file") },
|
2021-05-05 01:47:49 +09:00
|
|
|
[COL_TID] = { "TID", 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("thread ID of the process opening the file") },
|
2021-05-03 15:33:56 +09:00
|
|
|
[COL_TYPE] = { "TYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("file type") },
|
|
|
|
[COL_UID] = { "UID", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
|
|
|
|
N_("user ID number") },
|
|
|
|
[COL_USER] = { "USER", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
|
|
|
|
N_("user of the process") },
|
2021-03-24 03:43:51 +09:00
|
|
|
};
|
|
|
|
|
2021-09-01 16:35:42 +02:00
|
|
|
static const int default_columns[] = {
|
2021-05-03 15:45:47 +09:00
|
|
|
COL_COMMAND,
|
|
|
|
COL_PID,
|
|
|
|
COL_USER,
|
|
|
|
COL_ASSOC,
|
2021-05-06 14:29:43 +09:00
|
|
|
COL_MODE,
|
2021-05-03 15:45:47 +09:00
|
|
|
COL_TYPE,
|
2021-09-17 01:04:48 +09:00
|
|
|
COL_SOURCE,
|
2021-05-06 14:45:11 +09:00
|
|
|
COL_MNT_ID,
|
2021-05-03 15:45:47 +09:00
|
|
|
COL_INODE,
|
|
|
|
COL_NAME,
|
|
|
|
};
|
|
|
|
|
2021-09-01 16:35:42 +02:00
|
|
|
static const int default_threads_columns[] = {
|
2021-05-05 01:47:49 +09:00
|
|
|
COL_COMMAND,
|
|
|
|
COL_PID,
|
|
|
|
COL_TID,
|
|
|
|
COL_USER,
|
|
|
|
COL_ASSOC,
|
2021-05-06 14:29:43 +09:00
|
|
|
COL_MODE,
|
2021-05-05 01:47:49 +09:00
|
|
|
COL_TYPE,
|
2021-09-17 01:04:48 +09:00
|
|
|
COL_SOURCE,
|
2021-05-06 14:45:11 +09:00
|
|
|
COL_MNT_ID,
|
2021-05-05 01:47:49 +09:00
|
|
|
COL_INODE,
|
|
|
|
COL_NAME,
|
|
|
|
};
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
static int columns[ARRAY_SIZE(infos) * 2] = {-1};
|
|
|
|
static size_t ncolumns;
|
|
|
|
|
|
|
|
struct lsfd_control {
|
|
|
|
struct libscols_table *tb; /* output */
|
2021-09-08 10:28:14 +02:00
|
|
|
struct list_head procs; /* list of all processes */
|
2021-09-01 17:36:29 +02:00
|
|
|
const char *sysroot; /* default is NULL */
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-09-01 16:50:04 +02:00
|
|
|
unsigned int noheadings : 1,
|
|
|
|
raw : 1,
|
|
|
|
json : 1,
|
|
|
|
notrunc : 1,
|
|
|
|
threads : 1;
|
2021-09-21 02:58:48 +09:00
|
|
|
|
|
|
|
struct lsfd_filter *filter;
|
2021-03-24 03:43:51 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
static int column_name_to_id(const char *name, size_t namesz)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(infos); i++) {
|
|
|
|
const char *cn = infos[i].name;
|
|
|
|
|
|
|
|
if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
warnx(_("unknown column: %s"), name);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
static int column_name_to_id_cb(const char *name, void *data __attribute__((__unused__)))
|
|
|
|
{
|
|
|
|
return column_name_to_id(name, strlen(name));
|
|
|
|
}
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
static int get_column_id(int num)
|
|
|
|
{
|
|
|
|
assert(num >= 0);
|
|
|
|
assert((size_t) num < ncolumns);
|
|
|
|
assert(columns[num] < (int) ARRAY_SIZE(infos));
|
|
|
|
|
|
|
|
return columns[num];
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct colinfo *get_column_info(int num)
|
|
|
|
{
|
|
|
|
return &infos[ get_column_id(num) ];
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
static struct libscols_column *add_column(struct libscols_table *tb, const struct colinfo *col)
|
|
|
|
{
|
|
|
|
struct libscols_column *cl;
|
|
|
|
int flags = col->flags;
|
|
|
|
|
|
|
|
cl = scols_table_new_column(tb, col->name, col->whint, flags);
|
|
|
|
if (cl)
|
|
|
|
scols_column_set_json_type(cl, col->json_type);
|
|
|
|
|
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
|
2021-09-28 21:57:31 +09:00
|
|
|
static struct libscols_column *add_column_by_id_cb(struct libscols_table *tb, int colid, void *data)
|
2021-09-21 02:58:48 +09:00
|
|
|
{
|
|
|
|
if (ncolumns >= ARRAY_SIZE(columns))
|
|
|
|
errx(EXIT_FAILURE, _("too many columns are added via filter expression"));
|
|
|
|
|
|
|
|
assert(colid < LSFD_N_COLS);
|
|
|
|
|
|
|
|
struct libscols_column *cl = add_column(tb, infos + colid);
|
|
|
|
if (!cl)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate output column"));
|
|
|
|
columns[ncolumns++] = colid;
|
2021-09-28 21:57:31 +09:00
|
|
|
|
|
|
|
if (colid == COL_TID) {
|
|
|
|
struct lsfd_control *ctl = data;
|
|
|
|
ctl->threads = 1;
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
|
2021-09-02 13:58:49 +02:00
|
|
|
static const struct file_class *stat2class(struct stat *sb)
|
|
|
|
{
|
|
|
|
assert(sb);
|
2021-09-01 18:12:09 +02:00
|
|
|
|
2021-09-02 13:58:49 +02:00
|
|
|
switch (sb->st_mode & S_IFMT) {
|
|
|
|
case S_IFCHR:
|
|
|
|
return &cdev_class;
|
|
|
|
case S_IFBLK:
|
|
|
|
return &bdev_class;
|
|
|
|
case S_IFSOCK:
|
|
|
|
return &sock_class;
|
|
|
|
case S_IFIFO:
|
|
|
|
return &fifo_class;
|
|
|
|
case S_IFLNK:
|
|
|
|
case S_IFREG:
|
|
|
|
case S_IFDIR:
|
|
|
|
return &file_class;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return &unkn_class;
|
|
|
|
}
|
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
static struct file *new_file(struct proc *proc, const struct file_class *class)
|
2021-09-01 19:10:20 +02:00
|
|
|
{
|
|
|
|
struct file *file;
|
|
|
|
|
|
|
|
file = xcalloc(1, class->size);
|
2021-09-02 14:30:58 +02:00
|
|
|
file->proc = proc;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&file->files);
|
|
|
|
list_add_tail(&file->files, &proc->files);
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
return file;
|
|
|
|
}
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-30 10:42:02 +02:00
|
|
|
static struct file *copy_file(struct file *old)
|
|
|
|
{
|
|
|
|
struct file *file = xcalloc(1, old->class->size);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&file->files);
|
|
|
|
file->proc = old->proc;
|
|
|
|
list_add_tail(&file->files, &old->proc->files);
|
|
|
|
|
|
|
|
file->class = old->class;
|
|
|
|
file->association = old->association;
|
|
|
|
file->name = xstrdup(old->name);
|
|
|
|
file->stat = old->stat;
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
static void file_set_path(struct file *file, struct stat *sb, const char *name, int association)
|
|
|
|
{
|
|
|
|
const struct file_class *class = stat2class(sb);
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
assert(class);
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
file->class = class;
|
|
|
|
file->association = association;
|
|
|
|
file->name = xstrdup(name);
|
|
|
|
file->stat = *sb;
|
|
|
|
}
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-20 12:28:50 +02:00
|
|
|
static void file_init_content(struct file *file)
|
|
|
|
{
|
|
|
|
if (file->class && file->class->initialize_content)
|
|
|
|
file->class->initialize_content(file);
|
2021-09-01 19:10:20 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 14:00:44 +02:00
|
|
|
static void free_file(struct file *file)
|
|
|
|
{
|
|
|
|
const struct file_class *class = file->class;
|
|
|
|
|
|
|
|
while (class) {
|
|
|
|
if (class->free_content)
|
|
|
|
class->free_content(file);
|
|
|
|
class = class->super;
|
|
|
|
}
|
|
|
|
free(file);
|
|
|
|
}
|
|
|
|
|
2021-09-01 19:10:20 +02:00
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
static struct proc *new_process(pid_t pid, struct proc *leader)
|
2021-03-24 03:43:51 +09:00
|
|
|
{
|
|
|
|
struct proc *proc = xcalloc(1, sizeof(*proc));
|
|
|
|
|
2021-05-05 01:47:49 +09:00
|
|
|
proc->pid = pid;
|
|
|
|
proc->leader = leader? leader: proc;
|
2021-03-24 03:43:51 +09:00
|
|
|
proc->command = NULL;
|
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
INIT_LIST_HEAD(&proc->files);
|
|
|
|
INIT_LIST_HEAD(&proc->procs);
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
return proc;
|
|
|
|
}
|
|
|
|
|
2021-09-02 14:00:44 +02:00
|
|
|
static void free_proc(struct proc *proc)
|
2021-03-24 03:43:51 +09:00
|
|
|
{
|
2021-09-02 14:00:44 +02:00
|
|
|
list_free(&proc->files, struct file, files, free_file);
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-09-02 14:00:44 +02:00
|
|
|
free(proc->command);
|
|
|
|
free(proc);
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
|
2021-05-06 14:25:56 +09:00
|
|
|
static void read_fdinfo(struct file *file, FILE *fdinfo)
|
|
|
|
{
|
|
|
|
char buf[1024];
|
|
|
|
|
|
|
|
while (fgets(buf, sizeof(buf), fdinfo)) {
|
2021-09-20 13:16:06 +02:00
|
|
|
const struct file_class *class;
|
|
|
|
char *val = strchr(buf, ':');
|
|
|
|
|
2021-05-06 14:25:56 +09:00
|
|
|
if (!val)
|
|
|
|
continue;
|
2021-09-20 13:16:06 +02:00
|
|
|
*val++ = '\0'; /* terminate key */
|
|
|
|
|
|
|
|
val = (char *) skip_space(val);
|
|
|
|
rtrim_whitespace((unsigned char *) val);
|
2021-05-06 14:25:56 +09:00
|
|
|
|
|
|
|
class = file->class;
|
|
|
|
while (class) {
|
|
|
|
if (class->handle_fdinfo
|
|
|
|
&& class->handle_fdinfo(file, buf, val))
|
|
|
|
break;
|
|
|
|
class = class->super;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
static struct file *collect_file_symlink(struct path_cxt *pc,
|
|
|
|
struct proc *proc,
|
|
|
|
const char *name,
|
|
|
|
int assoc)
|
2021-03-24 03:43:51 +09:00
|
|
|
{
|
2021-09-08 14:32:31 +02:00
|
|
|
char sym[PATH_MAX] = { '\0' };
|
|
|
|
struct stat sb;
|
2021-09-30 11:11:30 +02:00
|
|
|
struct file *f, *prev;
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
if (ul_path_readlink(pc, sym, sizeof(sym), name) < 0)
|
2021-03-24 03:43:51 +09:00
|
|
|
return NULL;
|
|
|
|
|
2021-09-30 11:11:30 +02:00
|
|
|
/* The /proc/#/{fd,ns} often contains the same file (e.g. /dev/tty)
|
|
|
|
* more than once. Let's try to reuse the previous file if the real
|
|
|
|
* path is the same to save stat() call.
|
|
|
|
*/
|
|
|
|
prev = list_last_entry(&proc->files, struct file, files);
|
|
|
|
if (prev && prev->name && strcmp(prev->name, sym) == 0) {
|
|
|
|
f = copy_file(prev);
|
|
|
|
f->association = assoc;
|
|
|
|
} else {
|
|
|
|
if (ul_path_stat(pc, &sb, 0, name) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
f = new_file(proc, stat2class(&sb));
|
|
|
|
file_set_path(f, &sb, sym, assoc);
|
|
|
|
}
|
2021-09-20 12:28:50 +02:00
|
|
|
|
|
|
|
file_init_content(f);
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
if (is_association(f, EXE))
|
|
|
|
proc->uid = sb.st_uid;
|
2021-05-06 14:29:43 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
else if (assoc >= 0) {
|
|
|
|
/* file-descriptor based association */
|
|
|
|
FILE *fdinfo;
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0)
|
|
|
|
f->mode = sb.st_mode;
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc);
|
|
|
|
if (fdinfo) {
|
|
|
|
read_fdinfo(f, fdinfo);
|
|
|
|
fclose(fdinfo);
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 12:28:50 +02:00
|
|
|
|
2021-05-06 14:25:56 +09:00
|
|
|
return f;
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
/* read symlinks from /proc/#/fd
|
|
|
|
*/
|
|
|
|
static void collect_fd_files(struct path_cxt *pc, struct proc *proc)
|
2021-04-17 07:33:07 +09:00
|
|
|
{
|
2021-09-08 14:32:31 +02:00
|
|
|
DIR *sub = NULL;
|
|
|
|
struct dirent *d = NULL;
|
|
|
|
char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))];
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) {
|
|
|
|
uint64_t num;
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */
|
|
|
|
continue;
|
2021-05-06 14:25:56 +09:00
|
|
|
|
2021-09-08 14:32:31 +02:00
|
|
|
snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num);
|
|
|
|
collect_file_symlink(pc, proc, path, num);
|
|
|
|
}
|
2021-04-17 07:33:07 +09:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
static void parse_maps_line(char *buf, struct proc *proc)
|
2021-05-06 22:15:24 +09:00
|
|
|
{
|
2021-09-20 15:23:58 +02:00
|
|
|
uint64_t start, end, offset, ino;
|
|
|
|
unsigned long major, minor;
|
|
|
|
enum association assoc = ASSOC_MEM;
|
|
|
|
struct stat sb;
|
2021-09-30 10:42:02 +02:00
|
|
|
struct file *f, *prev;
|
2021-09-20 15:23:58 +02:00
|
|
|
char *path, modestr[5];
|
2021-09-30 10:42:02 +02:00
|
|
|
dev_t devno;
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
/* ignore non-path entries */
|
|
|
|
path = strchr(buf, '/');
|
|
|
|
if (!path)
|
|
|
|
return;
|
|
|
|
rtrim_whitespace((unsigned char *) path);
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
/* read rest of the map */
|
|
|
|
if (sscanf(buf, "%"SCNx64 /* start */
|
|
|
|
"-%"SCNx64 /* end */
|
|
|
|
" %4[^ ]" /* mode */
|
|
|
|
" %"SCNx64 /* offset */
|
|
|
|
" %lx:%lx" /* maj:min */
|
|
|
|
" %"SCNu64, /* inode */
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
&start, &end, modestr, &offset,
|
|
|
|
&major, &minor, &ino) != 7)
|
|
|
|
return;
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-30 10:42:02 +02:00
|
|
|
devno = makedev(major, minor);
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-30 10:42:02 +02:00
|
|
|
if (modestr[3] == 's')
|
|
|
|
assoc = ASSOC_SHM;
|
|
|
|
|
|
|
|
/* The map usually contains the same file more than once, try to reuse
|
|
|
|
* the previous file (if devno and ino are the same) to save stat() call.
|
|
|
|
*/
|
|
|
|
prev = list_last_entry(&proc->files, struct file, files);
|
|
|
|
|
|
|
|
if (prev && prev->stat.st_dev == devno && prev->stat.st_ino == ino) {
|
|
|
|
f = copy_file(prev);
|
|
|
|
f->association = -assoc;
|
|
|
|
} else {
|
|
|
|
if (stat(path, &sb) < 0)
|
|
|
|
return;
|
|
|
|
f = new_file(proc, stat2class(&sb));
|
|
|
|
if (!f)
|
|
|
|
return;
|
|
|
|
|
|
|
|
file_set_path(f, &sb, path, -assoc);
|
|
|
|
}
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
if (modestr[0] == 'r')
|
|
|
|
f->mode |= S_IRUSR;
|
|
|
|
if (modestr[1] == 'w')
|
|
|
|
f->mode |= S_IWUSR;
|
|
|
|
if (modestr[2] == 'x')
|
|
|
|
f->mode |= S_IXUSR;
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
f->map_start = start;
|
|
|
|
f->map_end = end;
|
|
|
|
f->pos = offset;
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
file_init_content(f);
|
2021-05-06 22:15:24 +09:00
|
|
|
}
|
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
static void collect_mem_files(struct path_cxt *pc, struct proc *proc)
|
2021-04-17 07:33:07 +09:00
|
|
|
{
|
2021-05-06 22:15:24 +09:00
|
|
|
FILE *fp;
|
2021-09-20 15:23:58 +02:00
|
|
|
char buf[BUFSIZ];
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
fp = ul_path_fopen(pc, "r", "maps");
|
|
|
|
if (!fp)
|
|
|
|
return;
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
while (fgets(buf, sizeof(buf), fp))
|
|
|
|
parse_maps_line(buf, proc);
|
2021-05-06 22:15:24 +09:00
|
|
|
|
2021-09-20 15:23:58 +02:00
|
|
|
fclose(fp);
|
2021-04-17 07:33:07 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 11:22:15 +02:00
|
|
|
static void collect_outofbox_files(struct path_cxt *pc,
|
|
|
|
struct proc *proc,
|
2021-04-17 07:15:50 +09:00
|
|
|
enum association assocs[],
|
2021-09-08 11:22:15 +02:00
|
|
|
const char *names[],
|
|
|
|
size_t count)
|
2021-04-17 06:50:00 +09:00
|
|
|
{
|
2021-09-08 11:22:15 +02:00
|
|
|
size_t i;
|
2021-05-05 03:36:09 +09:00
|
|
|
|
2021-09-08 11:22:15 +02:00
|
|
|
for (i = 0; i < count; i++)
|
2021-09-08 14:32:31 +02:00
|
|
|
collect_file_symlink(pc, proc, names[assocs[i]], assocs[i] * -1);
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 11:22:15 +02:00
|
|
|
static void collect_execve_file(struct path_cxt *pc, struct proc *proc)
|
2021-03-24 03:43:51 +09:00
|
|
|
{
|
2021-09-08 11:22:15 +02:00
|
|
|
enum association assocs[] = { ASSOC_EXE };
|
|
|
|
const char *names[] = {
|
2021-05-05 03:37:57 +09:00
|
|
|
[ASSOC_EXE] = "exe",
|
|
|
|
};
|
2021-09-08 11:22:15 +02:00
|
|
|
collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs));
|
2021-05-05 03:37:57 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 14:42:05 +02:00
|
|
|
static void collect_fs_files(struct path_cxt *pc, struct proc *proc)
|
2021-05-05 03:37:57 +09:00
|
|
|
{
|
2021-09-08 11:22:15 +02:00
|
|
|
enum association assocs[] = { ASSOC_EXE, ASSOC_CWD, ASSOC_ROOT };
|
|
|
|
const char *names[] = {
|
2021-05-05 03:37:57 +09:00
|
|
|
[ASSOC_CWD] = "cwd",
|
2021-04-17 07:15:50 +09:00
|
|
|
[ASSOC_ROOT] = "root",
|
|
|
|
};
|
2021-09-08 11:22:15 +02:00
|
|
|
collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs));
|
2021-04-17 07:38:48 +09:00
|
|
|
}
|
2021-04-17 07:15:50 +09:00
|
|
|
|
2021-09-08 11:22:15 +02:00
|
|
|
static void collect_namespace_files(struct path_cxt *pc, struct proc *proc)
|
2021-04-17 07:38:48 +09:00
|
|
|
{
|
2021-09-08 11:22:15 +02:00
|
|
|
enum association assocs[] = {
|
2021-04-17 07:15:50 +09:00
|
|
|
ASSOC_NS_CGROUP,
|
|
|
|
ASSOC_NS_IPC,
|
|
|
|
ASSOC_NS_MNT,
|
|
|
|
ASSOC_NS_NET,
|
|
|
|
ASSOC_NS_PID,
|
|
|
|
ASSOC_NS_PID4C,
|
|
|
|
ASSOC_NS_TIME,
|
|
|
|
ASSOC_NS_TIME4C,
|
|
|
|
ASSOC_NS_USER,
|
|
|
|
ASSOC_NS_UTS,
|
|
|
|
};
|
2021-09-08 11:22:15 +02:00
|
|
|
const char *names[] = {
|
|
|
|
[ASSOC_NS_CGROUP] = "ns/cgroup",
|
|
|
|
[ASSOC_NS_IPC] = "ns/ipc",
|
|
|
|
[ASSOC_NS_MNT] = "ns/mnt",
|
|
|
|
[ASSOC_NS_NET] = "ns/net",
|
|
|
|
[ASSOC_NS_PID] = "ns/pid",
|
|
|
|
[ASSOC_NS_PID4C] = "ns/pid_for_children",
|
|
|
|
[ASSOC_NS_TIME] = "ns/time",
|
|
|
|
[ASSOC_NS_TIME4C] = "ns/time_for_children",
|
|
|
|
[ASSOC_NS_USER] = "ns/user",
|
|
|
|
[ASSOC_NS_UTS] = "ns/uts",
|
2021-04-17 07:15:50 +09:00
|
|
|
};
|
2021-09-08 11:22:15 +02:00
|
|
|
collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs));
|
2021-04-17 07:38:48 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 10:49:43 +02:00
|
|
|
static struct nodev *new_nodev(unsigned long minor, const char *filesystem)
|
2021-05-09 10:41:34 +09:00
|
|
|
{
|
2021-09-08 10:49:43 +02:00
|
|
|
struct nodev *nodev = xcalloc(1, sizeof(*nodev));
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&nodev->nodevs);
|
|
|
|
nodev->minor = minor;
|
|
|
|
nodev->filesystem = xstrdup(filesystem);
|
|
|
|
|
|
|
|
return nodev;
|
2021-05-09 10:41:34 +09:00
|
|
|
}
|
|
|
|
|
2021-09-08 10:49:43 +02:00
|
|
|
static void free_nodev(struct nodev *nodev)
|
2021-05-09 10:41:34 +09:00
|
|
|
{
|
2021-09-08 10:49:43 +02:00
|
|
|
free(nodev->filesystem);
|
|
|
|
free(nodev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void initialize_nodevs(void)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < NODEV_TABLE_SIZE; i++)
|
|
|
|
INIT_LIST_HEAD(&nodev_table.tables[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void finalize_nodevs(void)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < NODEV_TABLE_SIZE; i++)
|
|
|
|
list_free(&nodev_table.tables[i], struct nodev, nodevs, free_nodev);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *get_nodev_filesystem(unsigned long minor)
|
|
|
|
{
|
|
|
|
struct list_head *n;
|
|
|
|
int slot = minor % NODEV_TABLE_SIZE;
|
|
|
|
|
|
|
|
list_for_each (n, &nodev_table.tables[slot]) {
|
|
|
|
struct nodev *nodev = list_entry(n, struct nodev, nodevs);
|
|
|
|
if (nodev->minor == minor)
|
|
|
|
return nodev->filesystem;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_nodevs(FILE *mnt)
|
|
|
|
{
|
|
|
|
/* This can be very long. A line in mountinfo can have more than 3
|
|
|
|
* paths. */
|
2021-05-09 10:41:34 +09:00
|
|
|
char line[PATH_MAX * 3 + 256];
|
2021-09-08 10:49:43 +02:00
|
|
|
|
|
|
|
while (fgets(line, sizeof(line), mnt)) {
|
2021-05-09 10:41:34 +09:00
|
|
|
unsigned long major, minor;
|
|
|
|
char filesystem[256];
|
2021-09-08 10:49:43 +02:00
|
|
|
struct nodev *nodev;
|
|
|
|
int slot;
|
|
|
|
|
2021-05-09 10:41:34 +09:00
|
|
|
|
|
|
|
/* 23 61 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw,seclabel */
|
|
|
|
if(sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %s %*[^\n]",
|
|
|
|
&major, &minor, filesystem) != 3)
|
|
|
|
/* 1600 1458 0:55 / / rw,nodev,relatime - overlay overlay rw,context="s... */
|
|
|
|
if (sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s - %s %*[^\n]",
|
|
|
|
&major, &minor, filesystem) != 3)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (major != 0)
|
|
|
|
continue;
|
2021-09-08 10:49:43 +02:00
|
|
|
if (get_nodev_filesystem(minor))
|
|
|
|
continue;
|
2021-05-09 10:41:34 +09:00
|
|
|
|
2021-09-08 10:49:43 +02:00
|
|
|
nodev = new_nodev(minor, filesystem);
|
|
|
|
slot = minor % NODEV_TABLE_SIZE;
|
|
|
|
|
|
|
|
list_add_tail(&nodev->nodevs, &nodev_table.tables[slot]);
|
2021-05-09 10:41:34 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
static void fill_column(struct proc *proc,
|
|
|
|
struct file *file,
|
|
|
|
struct libscols_line *ln,
|
|
|
|
int column_id,
|
|
|
|
size_t column_index)
|
|
|
|
{
|
|
|
|
const struct file_class *class = file->class;
|
|
|
|
|
|
|
|
while (class) {
|
|
|
|
if (class->fill_column
|
|
|
|
&& class->fill_column(proc, file, ln,
|
|
|
|
column_id, column_index))
|
|
|
|
break;
|
|
|
|
class = class->super;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert1(struct proc *proc,
|
|
|
|
struct file *file,
|
|
|
|
struct libscols_line *ln)
|
|
|
|
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < ncolumns; i++)
|
|
|
|
fill_column(proc, file, ln, get_column_id(i), i);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert(struct list_head *procs, struct lsfd_control *ctl)
|
|
|
|
{
|
|
|
|
struct list_head *p;
|
|
|
|
|
|
|
|
list_for_each (p, procs) {
|
|
|
|
struct proc *proc = list_entry(p, struct proc, procs);
|
|
|
|
struct list_head *f;
|
|
|
|
|
|
|
|
list_for_each (f, &proc->files) {
|
|
|
|
struct file *file = list_entry(f, struct file, files);
|
|
|
|
struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL);
|
|
|
|
if (!ln)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate output line"));
|
|
|
|
convert1(proc, file, ln);
|
2021-09-21 02:58:48 +09:00
|
|
|
|
|
|
|
if (!lsfd_filter_apply(ctl->filter, ln))
|
|
|
|
scols_table_remove_line(ctl->tb, ln);
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void delete(struct list_head *procs, struct lsfd_control *ctl)
|
|
|
|
{
|
|
|
|
list_free(procs, struct proc, procs, free_proc);
|
|
|
|
|
|
|
|
scols_unref_table(ctl->tb);
|
2021-09-21 02:58:48 +09:00
|
|
|
lsfd_filter_free(ctl->filter);
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
static void emit(struct lsfd_control *ctl)
|
|
|
|
{
|
|
|
|
scols_print_table(ctl->tb);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-06 23:51:00 +09:00
|
|
|
static void initialize_class(const struct file_class *class)
|
|
|
|
{
|
|
|
|
if (class->initialize_class)
|
|
|
|
class->initialize_class();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void initialize_classes(void)
|
|
|
|
{
|
|
|
|
initialize_class(&file_class);
|
|
|
|
initialize_class(&cdev_class);
|
|
|
|
initialize_class(&bdev_class);
|
|
|
|
initialize_class(&sock_class);
|
|
|
|
initialize_class(&unkn_class);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void finalize_class(const struct file_class *class)
|
|
|
|
{
|
|
|
|
if (class->finalize_class)
|
|
|
|
class->finalize_class();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void finalize_classes(void)
|
|
|
|
{
|
|
|
|
finalize_class(&file_class);
|
|
|
|
finalize_class(&cdev_class);
|
|
|
|
finalize_class(&bdev_class);
|
|
|
|
finalize_class(&sock_class);
|
|
|
|
finalize_class(&unkn_class);
|
|
|
|
}
|
|
|
|
|
2021-09-01 18:12:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
struct name_manager *new_name_manager(void)
|
|
|
|
{
|
|
|
|
struct name_manager *nm = xcalloc(1, sizeof(struct name_manager));
|
|
|
|
|
|
|
|
nm->cache = new_idcache();
|
|
|
|
if (!nm->cache)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate an idcache"));
|
|
|
|
|
|
|
|
nm->next_id = 1; /* 0 is never issued as id. */
|
|
|
|
return nm;
|
|
|
|
}
|
|
|
|
|
|
|
|
void free_name_manager(struct name_manager *nm)
|
|
|
|
{
|
|
|
|
free_idcache(nm->cache);
|
|
|
|
free(nm);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *get_name(struct name_manager *nm, unsigned long id)
|
|
|
|
{
|
|
|
|
struct identry *e;
|
|
|
|
|
|
|
|
e = get_id(nm->cache, id);
|
|
|
|
|
|
|
|
return e? e->name: NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned long add_name(struct name_manager *nm, const char *name)
|
|
|
|
{
|
|
|
|
struct identry *e = NULL, *tmp;
|
|
|
|
|
|
|
|
for (tmp = nm->cache->ent; tmp; tmp = tmp->next) {
|
|
|
|
if (strcmp(tmp->name, name) == 0) {
|
|
|
|
e = tmp;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e)
|
|
|
|
return e->id;
|
|
|
|
|
|
|
|
e = xmalloc(sizeof(struct identry));
|
|
|
|
e->name = xstrdup(name);
|
|
|
|
e->id = nm->next_id++;
|
|
|
|
e->next = nm->cache->ent;
|
|
|
|
nm->cache->ent = e;
|
|
|
|
|
|
|
|
return e->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
DIR *opendirf(const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
char path[PATH_MAX];
|
|
|
|
|
|
|
|
memset(path, 0, sizeof(path));
|
|
|
|
|
|
|
|
va_start(ap, format);
|
|
|
|
vsprintf(path, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return opendir(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE *fopenf(const char *mode, const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
char path[PATH_MAX];
|
|
|
|
|
|
|
|
memset(path, 0, sizeof(path));
|
|
|
|
|
|
|
|
va_start(ap, format);
|
|
|
|
vsprintf(path, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return fopen(path, mode);
|
|
|
|
}
|
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
static void read_process(struct lsfd_control *ctl, struct path_cxt *pc,
|
|
|
|
pid_t pid, struct proc *leader)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
struct proc *proc;
|
2021-09-08 10:49:43 +02:00
|
|
|
FILE *mnt;
|
2021-09-08 10:28:14 +02:00
|
|
|
|
|
|
|
if (procfs_process_init_path(pc, pid) != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
proc = new_process(pid, leader);
|
2021-09-08 11:22:15 +02:00
|
|
|
proc->command = procfs_process_get_cmdname(pc, buf, sizeof(buf)) > 0 ?
|
2021-09-08 10:28:14 +02:00
|
|
|
xstrdup(buf) : xstrdup(_("(unknown)"));
|
|
|
|
|
2021-09-08 14:42:05 +02:00
|
|
|
collect_execve_file(pc, proc);
|
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
if (proc->pid == proc->leader->pid
|
|
|
|
|| kcmp(proc->leader->pid, proc->pid, KCMP_FS, 0, 0) != 0)
|
2021-09-08 14:42:05 +02:00
|
|
|
collect_fs_files(pc, proc);
|
2021-09-08 10:28:14 +02:00
|
|
|
|
2021-09-08 11:22:15 +02:00
|
|
|
collect_namespace_files(pc, proc);
|
2021-09-08 10:28:14 +02:00
|
|
|
|
2021-09-08 10:49:43 +02:00
|
|
|
/* TODO: parse mountinfo only when process uses not-yet-known
|
|
|
|
* mount namespace. Parse mountinfo for each process is
|
|
|
|
* extremly expensive.
|
|
|
|
*/
|
|
|
|
mnt = ul_path_fopen(pc, "r", "mountinfo");
|
|
|
|
if (mnt) {
|
|
|
|
add_nodevs(mnt);
|
|
|
|
fclose(mnt);
|
2021-09-08 10:28:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* If kcmp is not available,
|
|
|
|
* there is no way to no whether threads share resources.
|
|
|
|
* In such cases, we must pay the costs: call collect_mem_files()
|
|
|
|
* and collect_fd_files().
|
|
|
|
*/
|
|
|
|
if (proc->pid == proc->leader->pid
|
|
|
|
|| kcmp(proc->leader->pid, proc->pid, KCMP_VM, 0, 0) != 0)
|
2021-09-20 15:23:58 +02:00
|
|
|
collect_mem_files(pc, proc);
|
2021-09-08 10:28:14 +02:00
|
|
|
|
|
|
|
if (proc->pid == proc->leader->pid
|
|
|
|
|| kcmp(proc->leader->pid, proc->pid, KCMP_FILES, 0, 0) != 0)
|
2021-09-08 14:32:31 +02:00
|
|
|
collect_fd_files(pc, proc);
|
2021-09-08 10:28:14 +02:00
|
|
|
|
|
|
|
list_add_tail(&proc->procs, &ctl->procs);
|
|
|
|
|
|
|
|
/* The tasks collecting overwrites @pc by /proc/<task-pid>/. Keep it as
|
|
|
|
* the last path based operation in read_process()
|
|
|
|
*/
|
|
|
|
if (ctl->threads && leader == NULL) {
|
|
|
|
DIR *sub = NULL;;
|
|
|
|
pid_t tid;
|
|
|
|
|
|
|
|
while (procfs_process_next_tid(pc, &sub, &tid) == 0) {
|
|
|
|
if (tid == pid)
|
|
|
|
continue;
|
|
|
|
read_process(ctl, pc, tid, proc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Let's be careful with number of open files */
|
|
|
|
ul_path_close_dirfd(pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void collect_processes(struct lsfd_control *ctl)
|
|
|
|
{
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *d;
|
|
|
|
struct path_cxt *pc = NULL;
|
|
|
|
|
|
|
|
pc = ul_new_path(NULL);
|
|
|
|
if (!pc)
|
|
|
|
err(EXIT_FAILURE, _("failed to alloc procfs handler"));
|
|
|
|
|
|
|
|
dir = opendir(_PATH_PROC);
|
|
|
|
if (!dir)
|
|
|
|
err(EXIT_FAILURE, _("failed to open /proc"));
|
|
|
|
|
|
|
|
while ((d = readdir(dir))) {
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
if (procfs_dirent_get_pid(d, &pid) != 0)
|
|
|
|
continue;
|
|
|
|
read_process(ctl, pc, pid, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
ul_unref_path(pc);
|
|
|
|
}
|
|
|
|
|
2021-09-01 18:12:09 +02:00
|
|
|
static void __attribute__((__noreturn__)) usage(void)
|
|
|
|
{
|
|
|
|
FILE *out = stdout;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
fputs(USAGE_HEADER, out);
|
|
|
|
fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
|
|
|
|
|
|
|
|
fputs(USAGE_OPTIONS, out);
|
|
|
|
fputs(_(" -l, --threads list in threads level\n"), out);
|
|
|
|
fputs(_(" -J, --json use JSON output format\n"), out);
|
|
|
|
fputs(_(" -n, --noheadings don't print headings\n"), out);
|
|
|
|
fputs(_(" -o, --output <list> output columns\n"), out);
|
|
|
|
fputs(_(" -r, --raw use raw output format\n"), out);
|
|
|
|
fputs(_(" --sysroot <dir> use specified directory as system root\n"), out);
|
|
|
|
fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
|
2021-09-21 02:58:48 +09:00
|
|
|
fputs(_(" -Q, --filter <expr> apply display filter\n"), out);
|
2021-09-25 04:32:27 +09:00
|
|
|
fputs(_(" --source <source> add filter by SOURCE\n"), out);
|
2021-09-01 18:12:09 +02:00
|
|
|
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
|
|
printf(USAGE_HELP_OPTIONS(23));
|
|
|
|
|
|
|
|
fprintf(out, USAGE_COLUMNS);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(infos); i++)
|
2021-09-21 02:58:48 +09:00
|
|
|
fprintf(out, " %11s %-10s%s\n", infos[i].name,
|
|
|
|
infos[i].json_type == SCOLS_JSON_STRING? "<string>":
|
|
|
|
infos[i].json_type == SCOLS_JSON_NUMBER? "<number>":
|
|
|
|
"<boolean>",
|
|
|
|
_(infos[i].help));
|
2021-09-01 18:12:09 +02:00
|
|
|
|
|
|
|
printf(USAGE_MAN_TAIL("lsfd(1)"));
|
|
|
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
static void xstrappend(char **a, const char *b)
|
|
|
|
{
|
|
|
|
if (strappend(a, b) < 0)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate memory for string"));
|
|
|
|
}
|
|
|
|
|
2021-09-25 04:32:27 +09:00
|
|
|
static char * quote_filter_expr(char *expr)
|
|
|
|
{
|
|
|
|
char c[] = {'\0', '\0'};
|
|
|
|
char *r = strdup("");
|
|
|
|
while (*expr) {
|
|
|
|
switch (*expr) {
|
|
|
|
case '\'':
|
|
|
|
xstrappend(&r, "\\'");
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
xstrappend(&r, "\\\"");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
c[0] = *expr;
|
|
|
|
xstrappend(&r, c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
expr++;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
static void append_filter_expr(char **a, const char *b, bool and)
|
|
|
|
{
|
|
|
|
if (*a == NULL) {
|
|
|
|
*a = xstrdup(b);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *tmp = *a;
|
|
|
|
*a = NULL;
|
|
|
|
|
|
|
|
xstrappend(a, "(");
|
|
|
|
xstrappend(a, tmp);
|
|
|
|
xstrappend(a, ")");
|
|
|
|
if (and)
|
|
|
|
xstrappend(a, "and(");
|
|
|
|
else
|
|
|
|
xstrappend(a, "or(");
|
|
|
|
xstrappend(a, b);
|
|
|
|
xstrappend(a, ")");
|
|
|
|
}
|
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int c;
|
2021-09-01 16:35:42 +02:00
|
|
|
size_t i;
|
2021-03-24 03:43:51 +09:00
|
|
|
char *outarg = NULL;
|
|
|
|
struct lsfd_control ctl = {};
|
2021-09-21 02:58:48 +09:00
|
|
|
char *filter_expr = NULL;
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-09-01 17:36:29 +02:00
|
|
|
enum {
|
2021-09-25 04:32:27 +09:00
|
|
|
OPT_SYSROOT = CHAR_MAX + 1,
|
|
|
|
OPT_SOURCE,
|
2021-09-01 17:36:29 +02:00
|
|
|
};
|
2021-03-24 03:43:51 +09:00
|
|
|
static const struct option longopts[] = {
|
|
|
|
{ "noheadings", no_argument, NULL, 'n' },
|
|
|
|
{ "output", required_argument, NULL, 'o' },
|
|
|
|
{ "version", no_argument, NULL, 'V' },
|
|
|
|
{ "help", no_argument, NULL, 'h' },
|
|
|
|
{ "json", no_argument, NULL, 'J' },
|
|
|
|
{ "raw", no_argument, NULL, 'r' },
|
2021-05-05 01:47:49 +09:00
|
|
|
{ "threads", no_argument, NULL, 'l' },
|
2021-09-01 16:50:04 +02:00
|
|
|
{ "notruncate", no_argument, NULL, 'u' },
|
2021-09-01 17:36:29 +02:00
|
|
|
{ "sysroot", required_argument, NULL, OPT_SYSROOT },
|
2021-09-21 02:58:48 +09:00
|
|
|
{ "filter", required_argument, NULL, 'Q' },
|
2021-09-25 04:32:27 +09:00
|
|
|
{ "source", required_argument, NULL, OPT_SOURCE },
|
2021-03-24 03:43:51 +09:00
|
|
|
{ NULL, 0, NULL, 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
|
|
textdomain(PACKAGE);
|
|
|
|
close_stdout_atexit();
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
while ((c = getopt_long(argc, argv, "no:JrVhluQ:", longopts, NULL)) != -1) {
|
2021-03-24 03:43:51 +09:00
|
|
|
switch (c) {
|
|
|
|
case 'n':
|
|
|
|
ctl.noheadings = 1;
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
outarg = optarg;
|
|
|
|
break;
|
|
|
|
case 'J':
|
|
|
|
ctl.json = 1;
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
ctl.raw = 1;
|
|
|
|
break;
|
2021-05-05 01:47:49 +09:00
|
|
|
case 'l':
|
|
|
|
ctl.threads = 1;
|
|
|
|
break;
|
2021-09-01 16:50:04 +02:00
|
|
|
case 'u':
|
|
|
|
ctl.notrunc = 1;
|
|
|
|
break;
|
2021-09-01 17:36:29 +02:00
|
|
|
case OPT_SYSROOT:
|
|
|
|
ctl.sysroot = optarg;
|
|
|
|
break;
|
2021-09-21 02:58:48 +09:00
|
|
|
case 'Q':
|
|
|
|
append_filter_expr(&filter_expr, optarg, true);
|
|
|
|
break;
|
2021-09-25 04:32:27 +09:00
|
|
|
case OPT_SOURCE: {
|
|
|
|
char * quoted_source = quote_filter_expr(optarg);
|
|
|
|
char * source_expr = NULL;
|
|
|
|
xstrappend(&source_expr, "(SOURCE == '");
|
|
|
|
xstrappend(&source_expr, quoted_source);
|
|
|
|
xstrappend(&source_expr, "')");
|
|
|
|
append_filter_expr(&filter_expr, source_expr, true);
|
|
|
|
free(source_expr);
|
|
|
|
free(quoted_source);
|
|
|
|
break;
|
|
|
|
}
|
2021-09-01 17:36:29 +02:00
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
case 'V':
|
|
|
|
print_version(EXIT_SUCCESS);
|
|
|
|
case 'h':
|
|
|
|
usage();
|
|
|
|
default:
|
|
|
|
errtryhelp(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 01:47:49 +09:00
|
|
|
#define INITIALIZE_COLUMNS(COLUMN_SPEC) \
|
2021-09-01 16:35:42 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(COLUMN_SPEC); i++) \
|
2021-05-05 01:47:49 +09:00
|
|
|
columns[ncolumns++] = COLUMN_SPEC[i]
|
|
|
|
if (!ncolumns) {
|
|
|
|
if (ctl.threads)
|
|
|
|
INITIALIZE_COLUMNS(default_threads_columns);
|
|
|
|
else
|
|
|
|
INITIALIZE_COLUMNS(default_columns);
|
|
|
|
}
|
2021-03-24 03:43:51 +09:00
|
|
|
|
|
|
|
if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
|
|
|
|
&ncolumns, column_name_to_id) < 0)
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
|
|
|
scols_init_debug(0);
|
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
INIT_LIST_HEAD(&ctl.procs);
|
|
|
|
|
2021-09-01 17:36:29 +02:00
|
|
|
/* inilialize scols table */
|
2021-09-01 16:35:42 +02:00
|
|
|
ctl.tb = scols_new_table();
|
2021-03-24 03:43:51 +09:00
|
|
|
if (!ctl.tb)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate output table"));
|
|
|
|
|
|
|
|
scols_table_enable_noheadings(ctl.tb, ctl.noheadings);
|
|
|
|
scols_table_enable_raw(ctl.tb, ctl.raw);
|
|
|
|
scols_table_enable_json(ctl.tb, ctl.json);
|
|
|
|
if (ctl.json)
|
|
|
|
scols_table_set_name(ctl.tb, "lsfd");
|
|
|
|
|
2021-09-01 17:36:29 +02:00
|
|
|
/* create output columns */
|
2021-09-01 16:35:42 +02:00
|
|
|
for (i = 0; i < ncolumns; i++) {
|
2021-03-24 03:43:51 +09:00
|
|
|
const struct colinfo *col = get_column_info(i);
|
2021-09-21 02:58:48 +09:00
|
|
|
struct libscols_column *cl = add_column(ctl.tb, col);
|
2021-03-24 03:43:51 +09:00
|
|
|
|
|
|
|
if (!cl)
|
|
|
|
err(EXIT_FAILURE, _("failed to allocate output column"));
|
|
|
|
|
2021-09-21 02:58:48 +09:00
|
|
|
if (ctl.notrunc) {
|
|
|
|
int flags = scols_column_get_flags(cl);
|
|
|
|
flags &= ~SCOLS_FL_TRUNC;
|
|
|
|
scols_column_set_flags(cl, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make fitler */
|
|
|
|
if (filter_expr) {
|
|
|
|
ctl.filter = lsfd_filter_new(filter_expr, ctl.tb,
|
|
|
|
LSFD_N_COLS,
|
|
|
|
column_name_to_id_cb,
|
2021-09-28 21:57:31 +09:00
|
|
|
add_column_by_id_cb, &ctl);
|
2021-09-21 02:58:48 +09:00
|
|
|
const char *errmsg = lsfd_filter_get_errmsg(ctl.filter);
|
|
|
|
if (errmsg)
|
|
|
|
errx(EXIT_FAILURE, "%s", errmsg);
|
|
|
|
free(filter_expr);
|
2021-03-24 03:43:51 +09:00
|
|
|
}
|
|
|
|
|
2021-09-01 17:36:29 +02:00
|
|
|
/* collect data */
|
2021-05-14 05:24:21 +09:00
|
|
|
initialize_nodevs();
|
2021-05-06 23:51:00 +09:00
|
|
|
initialize_classes();
|
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
collect_processes(&ctl);
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-09-08 10:28:14 +02:00
|
|
|
convert(&ctl.procs, &ctl);
|
2021-03-24 03:43:51 +09:00
|
|
|
emit(&ctl);
|
2021-09-01 17:36:29 +02:00
|
|
|
|
2021-09-25 02:14:05 +09:00
|
|
|
/* cleanup */
|
2021-09-08 10:28:14 +02:00
|
|
|
delete(&ctl.procs, &ctl);
|
2021-03-24 03:43:51 +09:00
|
|
|
|
2021-05-06 23:51:00 +09:00
|
|
|
finalize_classes();
|
2021-05-14 05:24:21 +09:00
|
|
|
finalize_nodevs();
|
2021-05-06 23:51:00 +09:00
|
|
|
|
2021-03-24 03:43:51 +09:00
|
|
|
return 0;
|
|
|
|
}
|