1052 lines
24 KiB
C
1052 lines
24 KiB
C
/*
|
|
* lsfd(1) - list file descriptors
|
|
*
|
|
* Copyright (C) 2021 Red Hat, Inc. All rights reserved.
|
|
* Written by Masatake YAMATO <yamato@redhat.com>
|
|
*
|
|
* 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 <unistd.h>
|
|
|
|
#ifdef HAVE_LINUX_NSFS_H
|
|
# include <linux/nsfs.h>
|
|
# if defined(NS_GET_NSTYPE)
|
|
# define USE_NS_GET_API 1
|
|
# include <sys/ioctl.h>
|
|
# endif
|
|
#endif
|
|
#include <linux/sched.h>
|
|
#include <sys/shm.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <mqueue.h> /* mq_open */
|
|
|
|
#include "buffer.h"
|
|
#include "idcache.h"
|
|
#include "strutils.h"
|
|
|
|
#include "procfs.h"
|
|
|
|
#include "lsfd.h"
|
|
#include "pidfd.h"
|
|
#include "pidfd-utils.h"
|
|
#include "decode-file-flags.h"
|
|
|
|
static size_t pagesize;
|
|
|
|
|
|
/*
|
|
* Abstract file class
|
|
*
|
|
* This class is for filling columns that don't need "sb" member, the
|
|
* result of stat(2).
|
|
*/
|
|
#define does_file_has_fdinfo_alike(file) \
|
|
((file)->association >= 0 \
|
|
|| (file)->association == -ASSOC_SHM \
|
|
|| (file)->association == -ASSOC_MEM)
|
|
|
|
static struct idcache *username_cache;
|
|
|
|
static const char *assocstr[N_ASSOCS] = {
|
|
[ASSOC_CWD] = "cwd",
|
|
[ASSOC_EXE] = "exe",
|
|
/* "root" appears as user names, too.
|
|
* So we use "rtd" here instead of "root". */
|
|
[ASSOC_ROOT] = "rtd",
|
|
[ASSOC_NS_CGROUP] = "cgroup",
|
|
[ASSOC_NS_IPC] = "ipc",
|
|
[ASSOC_NS_MNT] = "mnt",
|
|
[ASSOC_NS_NET] = "net",
|
|
[ASSOC_NS_PID] = "pid",
|
|
[ASSOC_NS_PID4C] = "pid4c",
|
|
[ASSOC_NS_TIME] = "time",
|
|
[ASSOC_NS_TIME4C] = "time4c",
|
|
[ASSOC_NS_USER] = "user",
|
|
[ASSOC_NS_UTS] = "uts",
|
|
[ASSOC_MEM] = "mem",
|
|
[ASSOC_SHM] = "shm",
|
|
};
|
|
|
|
static void file_fill_flags_buf(struct ul_buffer *buf, int flags)
|
|
{
|
|
lsfd_decode_file_flags(buf, flags);
|
|
}
|
|
|
|
static uint64_t get_map_length(struct file *file)
|
|
{
|
|
uint64_t res = 0;
|
|
|
|
if (is_association(file, SHM) || is_association(file, MEM))
|
|
res = (file->map_end - file->map_start) / pagesize;
|
|
|
|
return res;
|
|
}
|
|
|
|
static void abst_class_initialize(void)
|
|
{
|
|
username_cache = new_idcache();
|
|
if (!username_cache)
|
|
err(EXIT_FAILURE, _("failed to allocate UID cache"));
|
|
}
|
|
|
|
static void abst_class_finalize(void)
|
|
{
|
|
free_idcache(username_cache);
|
|
}
|
|
|
|
static bool abst_fill_column(struct proc *proc,
|
|
struct file *file,
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
char *str = NULL;
|
|
|
|
switch(column_id) {
|
|
case COL_COMMAND:
|
|
if (proc->command
|
|
&& scols_line_set_data(ln, column_index, proc->command))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_NAME:
|
|
case COL_KNAME:
|
|
if (file->name
|
|
&& scols_line_set_data(ln, column_index, file->name))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_USER:
|
|
add_uid(username_cache, (int)proc->uid);
|
|
if (scols_line_set_data(ln, column_index,
|
|
get_id(username_cache,
|
|
(int)proc->uid)->name))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_DEVTYPE:
|
|
if (scols_line_set_data(ln, column_index,
|
|
"nodev"))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_FD:
|
|
if (!is_opened_file(file))
|
|
return false;
|
|
/* FALL THROUGH */
|
|
case COL_ASSOC:
|
|
if (is_opened_file(file))
|
|
xasprintf(&str, "%d", file->association);
|
|
else {
|
|
int assoc = file->association * -1;
|
|
if (assoc >= N_ASSOCS)
|
|
return false; /* INTERNAL ERROR */
|
|
str = xstrdup(assocstr[assoc]);
|
|
}
|
|
break;
|
|
case COL_PID:
|
|
xasprintf(&str, "%d", (int)proc->leader->pid);
|
|
break;
|
|
case COL_TID:
|
|
xasprintf(&str, "%d", (int)proc->pid);
|
|
break;
|
|
case COL_UID:
|
|
xasprintf(&str, "%d", (int)proc->uid);
|
|
break;
|
|
case COL_KTHREAD:
|
|
xasprintf(&str, "%u", proc->kthread);
|
|
break;
|
|
case COL_MODE:
|
|
xasprintf(&str, "???");
|
|
break;
|
|
case COL_XMODE: {
|
|
char r, w, x;
|
|
char D = '?';
|
|
char L = file->locked_write? 'L'
|
|
:file->locked_read? 'l'
|
|
: '-';
|
|
char m = file->multiplexed? 'm': '-';
|
|
r = w = x = '?';
|
|
xasprintf(&str, "%c%c%c%c%c%c", r, w, x, D, L, m);
|
|
break;
|
|
}
|
|
case COL_POS:
|
|
xasprintf(&str, "%" PRIu64,
|
|
(does_file_has_fdinfo_alike(file))? file->pos: 0);
|
|
break;
|
|
case COL_FLAGS: {
|
|
struct ul_buffer buf = UL_INIT_BUFFER;
|
|
|
|
if (!is_opened_file(file))
|
|
return true;
|
|
|
|
if (file->sys_flags == 0)
|
|
return true;
|
|
|
|
file_fill_flags_buf(&buf, file->sys_flags);
|
|
if (ul_buffer_is_empty(&buf))
|
|
return true;
|
|
str = ul_buffer_get_data(&buf, NULL, NULL);
|
|
break;
|
|
}
|
|
case COL_MAPLEN:
|
|
if (!is_mapped_file(file))
|
|
return true;
|
|
xasprintf(&str, "%ju", (uintmax_t)get_map_length(file));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (!str)
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
if (scols_line_refer_data(ln, column_index, str))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
}
|
|
|
|
const struct file_class abst_class = {
|
|
.super = NULL,
|
|
.size = sizeof(struct file),
|
|
.initialize_class = abst_class_initialize,
|
|
.finalize_class = abst_class_finalize,
|
|
.fill_column = abst_fill_column,
|
|
};
|
|
|
|
/*
|
|
* Error classes
|
|
*/
|
|
|
|
/* get_errno_name() --- the private replacement of strerrorname_np(3).
|
|
* Some platforms don't have strerrorname_np.
|
|
*
|
|
* Mainly copied from misc-utils/enosys.c.
|
|
*/
|
|
struct errno_s {
|
|
const char *const name;
|
|
long number;
|
|
};
|
|
|
|
static const struct errno_s errnos[] = {
|
|
#define UL_ERRNO(name, nr) { name, nr },
|
|
#include "errnos.h"
|
|
#undef UL_ERRNO
|
|
};
|
|
|
|
static const char *get_errno_name(int ern)
|
|
{
|
|
for (size_t i = 0; i < ARRAY_SIZE(errnos); i ++) {
|
|
if (errnos[i].number == ern)
|
|
return errnos[i].name;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool error_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file __attribute__((__unused__)),
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
char *str = NULL;
|
|
const char *ename;
|
|
|
|
switch(column_id) {
|
|
case COL_TYPE:
|
|
if (scols_line_set_data(ln, column_index, "ERROR"))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_SOURCE:
|
|
ename = get_errno_name(file->error.number);
|
|
if (ename)
|
|
xasprintf(&str, "%s:%s",
|
|
file->error.syscall, ename);
|
|
else
|
|
xasprintf(&str, "%s:unknown(%d)",
|
|
file->error.syscall, file->error.number);
|
|
if (scols_line_refer_data(ln, column_index, str))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct file_class error_class = {
|
|
.super = &abst_class,
|
|
.size = sizeof(struct file),
|
|
.fill_column = error_fill_column,
|
|
};
|
|
|
|
static void init_error_content(struct file *file)
|
|
{
|
|
file->is_error = 1;
|
|
}
|
|
|
|
static bool readlink_error_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file __attribute__((__unused__)),
|
|
struct libscols_line *ln __attribute__((__unused__)),
|
|
int column_id,
|
|
size_t column_index __attribute__((__unused__)),
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
switch(column_id) {
|
|
case COL_NAME:
|
|
case COL_KNAME:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const struct file_class readlink_error_class = {
|
|
.super = &error_class,
|
|
.size = sizeof(struct file),
|
|
.initialize_content = init_error_content,
|
|
.fill_column = readlink_error_fill_column,
|
|
};
|
|
|
|
const struct file_class stat_error_class = {
|
|
.super = &error_class,
|
|
.size = sizeof(struct file),
|
|
.initialize_content = init_error_content,
|
|
};
|
|
|
|
/*
|
|
* Concrete file class
|
|
*/
|
|
static const char *strftype(mode_t ftype)
|
|
{
|
|
switch (ftype) {
|
|
case S_IFBLK:
|
|
return "BLK";
|
|
case S_IFCHR:
|
|
return "CHR";
|
|
case S_IFDIR:
|
|
return "DIR";
|
|
case S_IFIFO:
|
|
return "FIFO";
|
|
case S_IFLNK:
|
|
return "LINK";
|
|
case S_IFREG:
|
|
return "REG";
|
|
case S_IFSOCK:
|
|
return "SOCK";
|
|
default:
|
|
return "UNKN";
|
|
}
|
|
}
|
|
|
|
void decode_source(char *buf, size_t bufsize,
|
|
unsigned int dev_major, unsigned int dev_minor,
|
|
enum decode_source_level level)
|
|
{
|
|
if (bufsize == 0)
|
|
return;
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (level & DECODE_SOURCE_FILESYS_BIT) {
|
|
if (dev_major == 0) {
|
|
const char *filesystem = get_nodev_filesystem(dev_minor);
|
|
if (filesystem) {
|
|
xstrncpy(buf, filesystem, bufsize);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (level & DECODE_SOURCE_PARTITION_BIT) {
|
|
dev_t dev = makedev(dev_major, dev_minor);
|
|
const char *partition = get_partition(dev);
|
|
if (partition) {
|
|
xstrncpy(buf, partition, bufsize);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (level & DECODE_SOURCE_MAJMIN_BIT)
|
|
snprintf(buf, bufsize, "%u:%u",
|
|
dev_major,
|
|
dev_minor);
|
|
}
|
|
|
|
static char *strnrstr(const char *haystack, const char *needle, size_t needle_len)
|
|
{
|
|
char *last = strstr(haystack, needle);
|
|
if (last == NULL)
|
|
return NULL;
|
|
|
|
do {
|
|
char *current = strstr(last + needle_len, needle);
|
|
if (current == NULL)
|
|
return last;
|
|
last = current;
|
|
} while (1);
|
|
}
|
|
|
|
static bool file_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file,
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
char *str = NULL;
|
|
mode_t ftype;
|
|
char buf[BUFSIZ];
|
|
|
|
switch(column_id) {
|
|
case COL_NAME:
|
|
if (file->name && file->stat.st_nlink == 0) {
|
|
char *d = strnrstr(file->name, "(deleted)",
|
|
sizeof("(deleted)") - 1);
|
|
if (d) {
|
|
int r;
|
|
*d = '\0';
|
|
r = scols_line_set_data(ln, column_index, file->name);
|
|
*d = '(';
|
|
if (r)
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
if (uri) {
|
|
struct libscols_cell *ce = scols_line_get_cell(ln, column_index);
|
|
if (ce)
|
|
scols_cell_disable_uri(ce, 1);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
/* FALL THROUGH */
|
|
case COL_KNAME:
|
|
if (file->name
|
|
&& scols_line_set_data(ln, column_index, file->name))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
|
|
ftype = file->stat.st_mode & S_IFMT;
|
|
if (uri && (!file->name || *file->name != '/'
|
|
|| (ftype != S_IFREG && ftype != S_IFDIR)
|
|
|| file->stat.st_nlink == 0)) {
|
|
struct libscols_cell *ce = scols_line_get_cell(ln, column_index);
|
|
if (ce)
|
|
scols_cell_disable_uri(ce, 1);
|
|
}
|
|
return true;
|
|
case COL_STTYPE:
|
|
case COL_TYPE:
|
|
ftype = file->stat.st_mode & S_IFMT;
|
|
if (scols_line_set_data(ln, column_index, strftype(ftype)))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_INODE:
|
|
xasprintf(&str, "%llu", (unsigned long long)file->stat.st_ino);
|
|
break;
|
|
case COL_SOURCE:
|
|
decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev),
|
|
DECODE_SOURCE_FILESYS);
|
|
str = xstrdup(buf);
|
|
break;
|
|
case COL_PARTITION:
|
|
decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev),
|
|
DECODE_SOURCE_PARTITION);
|
|
str = xstrdup(buf);
|
|
break;
|
|
case COL_DEV:
|
|
case COL_MAJMIN:
|
|
decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev),
|
|
DECODE_SOURCE_MAJMIN);
|
|
str = xstrdup(buf);
|
|
break;
|
|
case COL_RDEV:
|
|
xasprintf(&str, "%u:%u",
|
|
major(file->stat.st_rdev),
|
|
minor(file->stat.st_rdev));
|
|
break;
|
|
case COL_FUID:
|
|
xasprintf(&str, "%d", (int)file->stat.st_uid);
|
|
break;
|
|
case COL_SIZE:
|
|
xasprintf(&str, "%jd", (intmax_t)file->stat.st_size);
|
|
break;
|
|
case COL_NLINK:
|
|
xasprintf(&str, "%ju", (uintmax_t)file->stat.st_nlink);
|
|
break;
|
|
case COL_DELETED:
|
|
xasprintf(&str, "%d", file->stat.st_nlink == 0);
|
|
break;
|
|
case COL_MNT_ID:
|
|
xasprintf(&str, "%d", is_opened_file(file)? file->mnt_id: 0);
|
|
break;
|
|
case COL_MODE:
|
|
if (does_file_has_fdinfo_alike(file))
|
|
xasprintf(&str, "%c%c%c",
|
|
file->mode & S_IRUSR? 'r': '-',
|
|
file->mode & S_IWUSR? 'w': '-',
|
|
(is_mapped_file(file)
|
|
&& file->mode & S_IXUSR)? 'x': '-');
|
|
else
|
|
xasprintf(&str, "---");
|
|
break;
|
|
case COL_XMODE: {
|
|
char r, w, x;
|
|
char D = file->stat.st_nlink == 0? 'D': '-';
|
|
char L = file->locked_write? 'L'
|
|
:file->locked_read? 'l'
|
|
: '-';
|
|
char m = file->multiplexed? 'm': '-';
|
|
|
|
if (does_file_has_fdinfo_alike(file)) {
|
|
r = file->mode & S_IRUSR? 'r': '-';
|
|
w = file->mode & S_IWUSR? 'w': '-';
|
|
x = (is_mapped_file(file)
|
|
&& file->mode & S_IXUSR)? 'x': '-';
|
|
} else
|
|
r = w = x = '-';
|
|
xasprintf(&str, "%c%c%c%c%c%c", r, w, x, D, L, m);
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (!str)
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
if (scols_line_refer_data(ln, column_index, str))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
}
|
|
|
|
enum lock_mode {
|
|
LOCK_NONE,
|
|
READ_LOCK,
|
|
WRITE_LOCK,
|
|
};
|
|
|
|
static unsigned int parse_lock_line(const char *line)
|
|
{
|
|
char mode[6] = {0};
|
|
|
|
/* Examples of lines:
|
|
----------------------------------------------------
|
|
1: FLOCK ADVISORY READ 2283292 fd:03:26219728 0 EOF
|
|
1: FLOCK ADVISORY WRITE 2283321 fd:03:26219728 0 EOF
|
|
1: POSIX ADVISORY READ 2283190 fd:03:26219728 0 0
|
|
1: POSIX ADVISORY WRITE 2283225 fd:03:26219728 0 0
|
|
1: OFDLCK ADVISORY READ -1 fd:03:26219728 0 0
|
|
1: OFDLCK ADVISORY WRITE -1 fd:03:26219728 0 0
|
|
1: LEASE ACTIVE WRITE 2328907 fd:03:26219472 0 EOF
|
|
1: LEASE ACTIVE READ 2326777 fd:03:26219472 0 EOF
|
|
---------------------------------------------------- */
|
|
|
|
if (sscanf(line, "%*d: %*s %*s %5s %*s", mode) != 1)
|
|
return LOCK_NONE;
|
|
|
|
if (strcmp(mode, "READ") == 0)
|
|
return READ_LOCK;
|
|
|
|
if (strcmp(mode, "WRITE") == 0)
|
|
return WRITE_LOCK;
|
|
|
|
return LOCK_NONE;
|
|
}
|
|
|
|
static int file_handle_fdinfo(struct file *file, const char *key, const char* value)
|
|
{
|
|
int rc;
|
|
|
|
if (strcmp(key, "pos") == 0) {
|
|
rc = ul_strtou64(value, &file->pos, 10);
|
|
|
|
} else if (strcmp(key, "flags") == 0) {
|
|
rc = ul_strtou32(value, &file->sys_flags, 8);
|
|
|
|
} else if (strcmp(key, "mnt_id") == 0) {
|
|
rc = ul_strtou32(value, &file->mnt_id, 10);
|
|
|
|
} else if (strcmp(key, "lock") == 0) {
|
|
switch (parse_lock_line(value)) {
|
|
case READ_LOCK:
|
|
file->locked_read = 1;
|
|
break;
|
|
case WRITE_LOCK:
|
|
file->locked_write = 1;
|
|
break;
|
|
}
|
|
rc = 1;
|
|
} else
|
|
return 0; /* ignore -- unknown item */
|
|
|
|
if (rc < 0)
|
|
return 0; /* ignore -- parse failed */
|
|
|
|
return 1; /* success */
|
|
}
|
|
|
|
static void file_free_content(struct file *file)
|
|
{
|
|
free(file->name);
|
|
}
|
|
|
|
static unsigned long get_minor_for_sysvipc(void)
|
|
{
|
|
int id;
|
|
void *start;
|
|
|
|
pid_t self = getpid();
|
|
struct path_cxt *pc = NULL;
|
|
|
|
struct stat sb;
|
|
unsigned long m = 0;
|
|
|
|
id = shmget(IPC_PRIVATE, pagesize, IPC_CREAT | 0600);
|
|
if (id == -1)
|
|
return 0;
|
|
|
|
start = shmat(id, NULL, SHM_RDONLY);
|
|
if (start == (void *) -1) {
|
|
shmctl(id, IPC_RMID, NULL);
|
|
return 0;
|
|
}
|
|
|
|
pc = ul_new_path(NULL);
|
|
if (!pc)
|
|
goto out;
|
|
|
|
if (procfs_process_init_path(pc, self) != 0)
|
|
goto out;
|
|
|
|
if (ul_path_statf(pc, &sb, 0, "map_files/%lx-%lx",
|
|
(long)start, (long)start + pagesize) < 0)
|
|
goto out;
|
|
|
|
m = minor(sb.st_dev);
|
|
out:
|
|
if (pc)
|
|
ul_unref_path(pc);
|
|
shmdt(start);
|
|
shmctl(id, IPC_RMID, NULL);
|
|
return m;
|
|
}
|
|
|
|
static unsigned long get_minor_for_mqueue(void)
|
|
{
|
|
mqd_t mq;
|
|
char mq_name[BUFSIZ];
|
|
struct mq_attr attr = {
|
|
.mq_maxmsg = 1,
|
|
.mq_msgsize = 1,
|
|
};
|
|
|
|
pid_t self = getpid();
|
|
struct stat sb;
|
|
|
|
snprintf(mq_name, sizeof(mq_name), "/.lsfd-mqueue-nodev-test:%d", self);
|
|
mq = mq_open(mq_name, O_CREAT|O_EXCL | O_RDONLY, S_IRUSR | S_IWUSR, &attr);
|
|
if (mq < 0)
|
|
return 0;
|
|
|
|
if (fstat((int)mq, &sb) < 0) {
|
|
mq_close(mq);
|
|
mq_unlink(mq_name);
|
|
return 0;
|
|
}
|
|
|
|
mq_close(mq);
|
|
mq_unlink(mq_name);
|
|
return minor(sb.st_dev);
|
|
}
|
|
|
|
static unsigned long get_minor_for_pidfs(void)
|
|
{
|
|
int fd = pidfd_open(getpid(), 0);
|
|
struct stat sb;
|
|
unsigned long ret = 0;
|
|
|
|
if (fd < 0)
|
|
return 0;
|
|
|
|
if (fstat(fd, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFREG)
|
|
ret = minor(sb.st_dev);
|
|
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
static void file_class_initialize(void)
|
|
{
|
|
unsigned long m;
|
|
|
|
if (!pagesize)
|
|
pagesize = getpagesize();
|
|
|
|
m = get_minor_for_sysvipc();
|
|
if (m)
|
|
add_nodev(m, "tmpfs");
|
|
|
|
m = get_minor_for_mqueue();
|
|
if (m)
|
|
add_nodev(m, "mqueue");
|
|
|
|
m = get_minor_for_pidfs();
|
|
if (m)
|
|
add_nodev(m, "pidfs");
|
|
}
|
|
|
|
const struct file_class file_class = {
|
|
.super = &abst_class,
|
|
.size = sizeof(struct file),
|
|
.initialize_class = file_class_initialize,
|
|
.finalize_class = NULL,
|
|
.fill_column = file_fill_column,
|
|
.handle_fdinfo = file_handle_fdinfo,
|
|
.free_content = file_free_content,
|
|
};
|
|
|
|
/*
|
|
* Regular files on NSFS
|
|
*/
|
|
|
|
struct nsfs_file {
|
|
struct file file;
|
|
int clone_type;
|
|
};
|
|
|
|
static const char *get_ns_type_name(int clone_type)
|
|
{
|
|
switch (clone_type) {
|
|
#ifdef USE_NS_GET_API
|
|
case CLONE_NEWNS:
|
|
return "mnt";
|
|
case CLONE_NEWCGROUP:
|
|
return "cgroup";
|
|
case CLONE_NEWUTS:
|
|
return "uts";
|
|
case CLONE_NEWIPC:
|
|
return "ipc";
|
|
case CLONE_NEWUSER:
|
|
return "user";
|
|
case CLONE_NEWPID:
|
|
return "pid";
|
|
case CLONE_NEWNET:
|
|
return "net";
|
|
#ifdef CLONE_NEWTIME
|
|
case CLONE_NEWTIME:
|
|
return "time";
|
|
#endif /* CLONE_NEWTIME */
|
|
#endif /* USE_NS_GET_API */
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void init_nsfs_file_content(struct file *file)
|
|
{
|
|
struct nsfs_file *nsfs_file = (struct nsfs_file *)file;
|
|
nsfs_file->clone_type = -1;
|
|
|
|
#ifdef USE_NS_GET_API
|
|
char *proc_fname = NULL;
|
|
int ns_fd;
|
|
int ns_type;
|
|
|
|
if (is_association(file, NS_CGROUP))
|
|
nsfs_file->clone_type = CLONE_NEWCGROUP;
|
|
else if (is_association(file, NS_IPC))
|
|
nsfs_file->clone_type = CLONE_NEWIPC;
|
|
else if (is_association(file, NS_MNT))
|
|
nsfs_file->clone_type = CLONE_NEWNS;
|
|
else if (is_association(file, NS_NET))
|
|
nsfs_file->clone_type = CLONE_NEWNET;
|
|
else if (is_association(file, NS_PID)
|
|
|| is_association(file, NS_PID4C))
|
|
nsfs_file->clone_type = CLONE_NEWPID;
|
|
#ifdef CLONE_NEWTIME
|
|
else if (is_association(file, NS_TIME)
|
|
|| is_association(file, NS_TIME4C))
|
|
nsfs_file->clone_type = CLONE_NEWTIME;
|
|
#endif
|
|
else if (is_association(file, NS_USER))
|
|
nsfs_file->clone_type = CLONE_NEWUSER;
|
|
else if (is_association(file, NS_UTS))
|
|
nsfs_file->clone_type = CLONE_NEWUTS;
|
|
|
|
if (nsfs_file->clone_type != -1)
|
|
return;
|
|
|
|
if (!is_opened_file(file))
|
|
return;
|
|
|
|
if (!file->name)
|
|
return;
|
|
|
|
xasprintf(&proc_fname, "/proc/%d/fd/%d",
|
|
file->proc->pid, file->association);
|
|
ns_fd = open(proc_fname, O_RDONLY);
|
|
free(proc_fname);
|
|
if (ns_fd < 0)
|
|
return;
|
|
|
|
ns_type = ioctl(ns_fd, NS_GET_NSTYPE);
|
|
close(ns_fd);
|
|
if (ns_type < 0)
|
|
return;
|
|
|
|
nsfs_file->clone_type = ns_type;
|
|
#endif /* USE_NS_GET_API */
|
|
}
|
|
|
|
|
|
static bool nsfs_file_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file,
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
struct nsfs_file *nsfs_file = (struct nsfs_file *)file;
|
|
char *name = NULL;
|
|
|
|
if (nsfs_file->clone_type == -1)
|
|
return false;
|
|
|
|
switch (column_id) {
|
|
case COL_NS_NAME:
|
|
xasprintf(&name, "%s:[%llu]",
|
|
get_ns_type_name(nsfs_file->clone_type),
|
|
(unsigned long long)file->stat.st_ino);
|
|
break;
|
|
case COL_NS_TYPE:
|
|
if (scols_line_set_data(ln, column_index,
|
|
get_ns_type_name(nsfs_file->clone_type)))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (name && scols_line_refer_data(ln, column_index, name))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
|
|
return true;
|
|
}
|
|
|
|
const struct file_class nsfs_file_class = {
|
|
.super = &file_class,
|
|
.size = sizeof(struct nsfs_file),
|
|
.initialize_class = NULL,
|
|
.finalize_class = NULL,
|
|
.initialize_content = init_nsfs_file_content,
|
|
.free_content = NULL,
|
|
.fill_column = nsfs_file_fill_column,
|
|
.handle_fdinfo = NULL,
|
|
};
|
|
|
|
/*
|
|
* POSIX Mqueue
|
|
*/
|
|
struct mqueue_file {
|
|
struct file file;
|
|
struct ipc_endpoint endpoint;
|
|
};
|
|
|
|
struct mqueue_file_ipc {
|
|
struct ipc ipc;
|
|
ino_t ino;
|
|
};
|
|
|
|
bool is_mqueue_dev(dev_t dev)
|
|
{
|
|
const char *fs = get_nodev_filesystem(minor(dev));
|
|
|
|
if (fs && (strcmp (fs, "mqueue") == 0))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline char *mqueue_file_xstrendpoint(struct file *file)
|
|
{
|
|
char *str = NULL;
|
|
xasprintf(&str, "%d,%s,%d%c%c",
|
|
file->proc->pid, file->proc->command, file->association,
|
|
(file->mode & S_IRUSR)? 'r': '-',
|
|
(file->mode & S_IWUSR)? 'w': '-');
|
|
return str;
|
|
}
|
|
|
|
static bool mqueue_file_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file __attribute__((__unused__)),
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
switch (column_id) {
|
|
case COL_TYPE:
|
|
if (scols_line_set_data(ln, column_index, "mqueue"))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_ENDPOINTS: {
|
|
char *str = NULL;
|
|
struct mqueue_file *this = (struct mqueue_file *)file;
|
|
struct list_head *e;
|
|
foreach_endpoint(e, this->endpoint) {
|
|
char *estr;
|
|
struct mqueue_file *other = list_entry(e, struct mqueue_file,
|
|
endpoint.endpoints);
|
|
if (this == other)
|
|
continue;
|
|
if (str)
|
|
xstrputc(&str, '\n');
|
|
estr = mqueue_file_xstrendpoint(&other->file);
|
|
xstrappend(&str, estr);
|
|
free(estr);
|
|
}
|
|
if (!str)
|
|
return false;
|
|
if (scols_line_refer_data(ln, column_index, str))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static unsigned int mqueue_file_get_hash(struct file *file)
|
|
{
|
|
return (unsigned int)(file->stat.st_ino % UINT_MAX);
|
|
}
|
|
|
|
static bool mqueue_file_is_suitable_ipc(struct ipc *ipc, struct file *file)
|
|
{
|
|
return ((struct mqueue_file_ipc *)ipc)->ino == file->stat.st_ino;
|
|
}
|
|
|
|
static const struct ipc_class *mqueue_file_get_ipc_class(struct file *file __attribute__((__unused__)))
|
|
{
|
|
static const struct ipc_class mqueue_file_ipc_class = {
|
|
.size = sizeof(struct mqueue_file_ipc),
|
|
.get_hash = mqueue_file_get_hash,
|
|
.is_suitable_ipc = mqueue_file_is_suitable_ipc,
|
|
};
|
|
return &mqueue_file_ipc_class;
|
|
}
|
|
|
|
static void init_mqueue_file_content(struct file *file)
|
|
{
|
|
struct mqueue_file *mqueue_file = (struct mqueue_file *)file;
|
|
struct ipc *ipc;
|
|
unsigned int hash;
|
|
|
|
init_endpoint(&mqueue_file->endpoint);
|
|
ipc = get_ipc(file);
|
|
if (ipc)
|
|
goto link;
|
|
|
|
ipc = new_ipc(mqueue_file_get_ipc_class(file));
|
|
((struct mqueue_file_ipc *)ipc)->ino = file->stat.st_ino;
|
|
|
|
hash = mqueue_file_get_hash(file);
|
|
add_ipc(ipc, hash);
|
|
link:
|
|
add_endpoint(&mqueue_file->endpoint, ipc);
|
|
}
|
|
|
|
const struct file_class mqueue_file_class = {
|
|
.super = &file_class,
|
|
.size = sizeof(struct mqueue_file),
|
|
.initialize_content = init_mqueue_file_content,
|
|
.fill_column = mqueue_file_fill_column,
|
|
.get_ipc_class = mqueue_file_get_ipc_class,
|
|
};
|
|
|
|
struct pidfs_file {
|
|
struct file file;
|
|
struct pidfd_data data;
|
|
};
|
|
|
|
static void init_pidfs_file_content(struct file *file)
|
|
{
|
|
struct pidfs_file *pidfs_file = (struct pidfs_file *)file;
|
|
|
|
memset(&pidfs_file->data, 0, sizeof(pidfs_file->data));
|
|
}
|
|
|
|
static int pidfs_file_handle_fdinfo(struct file *file, const char *key, const char *value)
|
|
{
|
|
struct pidfs_file *pidfs_file = (struct pidfs_file *)file;
|
|
|
|
return pidfd_handle_fdinfo(&pidfs_file->data, key, value);
|
|
}
|
|
|
|
static void pidfs_file_free_content(struct file *file)
|
|
{
|
|
struct pidfs_file *pidfs_file = (struct pidfs_file *)file;
|
|
|
|
pidfd_free(&pidfs_file->data);
|
|
}
|
|
|
|
static bool pidfs_file_fill_column(struct proc *proc __attribute__((__unused__)),
|
|
struct file *file,
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
struct pidfs_file *pidfs_file = (struct pidfs_file *)file;
|
|
char *buf = NULL;
|
|
|
|
switch(column_id) {
|
|
case COL_TYPE:
|
|
if (scols_line_set_data(ln, column_index, "pidfd"))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_NAME:
|
|
buf = pidfd_get_name(&pidfs_file->data);
|
|
break;
|
|
default:
|
|
if (!pidfd_fill_column(&pidfs_file->data, column_id, &buf))
|
|
return false;
|
|
}
|
|
|
|
if (buf &&
|
|
scols_line_refer_data(ln, column_index, buf))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
|
|
return true;
|
|
}
|
|
|
|
const struct file_class pidfs_file_class = {
|
|
.super = &file_class,
|
|
.size = sizeof(struct pidfs_file),
|
|
.initialize_content = init_pidfs_file_content,
|
|
.handle_fdinfo = pidfs_file_handle_fdinfo,
|
|
.fill_column = pidfs_file_fill_column,
|
|
.free_content = pidfs_file_free_content,
|
|
};
|
|
|
|
bool is_pidfs_dev(dev_t dev)
|
|
{
|
|
const char *fs = get_nodev_filesystem(minor(dev));
|
|
|
|
if (fs && (strcmp (fs, "pidfs") == 0))
|
|
return true;
|
|
|
|
return false;
|
|
}
|