hxtools/sadmin/ofl.c

333 lines
7.3 KiB
C

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2008-2010 Jan Engelhardt
/*
* Show processes using directories/files/mountpoints
*
* (While it says mountpoint in the source, any directory is acceptable,
* as are files.)
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libHX.h>
/**
* @sb: just space
* @pid: pid for current process
* @signal: signal to send
* @check: check for symlink
* @found: found something (used for exit value)
*/
struct ofl_compound {
struct stat sb;
pid_t pid;
unsigned char signal;
bool check, found;
};
static bool pids_only;
static const char *ofl_comm(pid_t pid, char *buf, size_t size)
{
char src[64], dst[512];
const char *p;
ssize_t ret;
snprintf(src, sizeof(src), COMPAT_PROC "/proc/%u/exe", (unsigned int)pid);
ret = readlink(src, dst, sizeof(dst) - 1);
if (ret < 0) {
*buf = '\0';
return buf;
}
dst[ret] = '\0';
p = HX_basename(dst);
strncpy(buf, p, size);
return buf;
}
/**
* ofl_file - check if file is within directory
* @mnt: mountpoint
* @file: file that is supposed to be within @mnt
*
* Returns true if that seems so.
* We do not check for the existence of @file using lstat() or so - it is
* assumed this exists if it is found through procfs. In fact,
* /proc/<pid>/task/<tid>/fd/<n> might point to the ominous
* "/foo/bar (deleted)" which almost never exists, but it shows us anyway that
* the file is still in use.
*/
static bool ofl_file(const char *mnt, const char *file, const char *ll_entry,
struct ofl_compound *data)
{
ssize_t mnt_len;
const char *p;
/* Strip extra slashes at the end */
mnt_len = strlen(mnt);
for (p = mnt + mnt_len - 1; p >= mnt && *p == '/'; --p)
--mnt_len;
if (strncmp(file, mnt, mnt_len) != 0)
return false;
if (file[mnt_len] != '\0' && file[mnt_len] != '/')
return false;
data->found = true;
if (pids_only) {
printf("%u ", data->pid);
} else if (data->signal == 0) {
char buf[24];
printf("%u(%s): %s -> %s\n", data->pid,
ofl_comm(data->pid, buf, sizeof(buf)), ll_entry, file);
return false; /* so that more FDs will be inspected */
}
if (kill(data->pid, data->signal) < 0) {
if (errno == ESRCH)
return true;
return false;
}
return true;
}
/**
* ofl_pmap - read process mappings
* @mnt: mountpoint
* @map_file: /proc/<pid>/maps
*/
static bool ofl_pmap(const char *mnt, const char *map_file,
struct ofl_compound *data)
{
hxmc_t *line = NULL;
bool ret = false;
unsigned int i;
const char *p;
FILE *fp;
if ((fp = fopen(map_file, "r")) == NULL)
return false;
while (HX_getl(&line, fp) != NULL) {
HX_chomp(line);
p = line;
for (i = 0; i < 5; ++i) {
while (!HX_isspace(*p) && *p != '\0')
++p;
while (HX_isspace(*p))
++p;
}
if (*p == '\0')
continue;
ret = ofl_file(mnt, p, map_file, data);
if (ret)
break;
}
HXmc_free(line);
fclose(fp);
return ret;
}
/**
* ofl_one - check a symlink
* @mnt: Mountpoint that is to be removed.
* @entry: Path to a symlink.
*
* Returns true if the process does not exist anymore or has been signalled.
*/
static bool ofl_one(const char *mnt, const char *entry,
struct ofl_compound *data)
{
ssize_t lnk_len;
char tmp[512];
if (data->check)
if (lstat(entry, &data->sb) < 0 || !S_ISLNK(data->sb.st_mode))
return false;
lnk_len = readlink(entry, tmp, sizeof(tmp) - 1);
if (lnk_len < 0)
return false;
tmp[lnk_len] = '\0';
return ofl_file(mnt, tmp, entry, data);
}
/**
* ofl_taskfd - iterate through /proc/<pid>/task/<tid>/fd/
*/
static bool ofl_taskfd(const char *mnt, const char *path,
struct ofl_compound *data)
{
const char *de;
char tmp[256];
struct HXdir *dir;
bool ret = false;
dir = HXdir_open(path);
if (dir == NULL)
return false;
while ((de = HXdir_read(dir)) != NULL) {
if (*de == '.')
continue;
snprintf(tmp, sizeof(tmp), "%s/%s", path, de);
if (lstat(tmp, &data->sb) < 0 || !S_ISLNK(data->sb.st_mode))
continue;
ret = ofl_one(mnt, tmp, data);
if (ret)
break;
}
HXdir_close(dir);
return ret;
}
/**
* ofl_task - iterate through /proc/<pid>/task/
*/
static void ofl_task(const char *mnt, const char *path,
struct ofl_compound *data)
{
const char *de;
char tmp[256];
struct HXdir *dir;
dir = HXdir_open(path);
if (dir == NULL)
return;
while ((de = HXdir_read(dir)) != NULL) {
if (*de == '.')
continue;
snprintf(tmp, sizeof(tmp), "%s/%s/fd", path, de);
if (lstat(tmp, &data->sb) < 0 || !S_ISDIR(data->sb.st_mode))
continue;
ofl_taskfd(mnt, tmp, data);
}
HXdir_close(dir);
}
/**
* ofl - filesystem use checker
* @mnt: mountpoint to search for
* @action: action to take
*/
static bool ofl(const char *mnt, unsigned int signum)
{
struct ofl_compound data = {.signal = signum};
const char *de;
char tmp[256];
struct HXdir *dir;
dir = HXdir_open(COMPAT_PROC "/proc");
if (dir == NULL)
return false;
while ((de = HXdir_read(dir)) != NULL) {
if (*de == '.')
continue;
data.pid = strtoul(de, NULL, 0);
if (data.pid == 0)
continue;
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s", de);
if (lstat(tmp, &data.sb) < 0 || !S_ISDIR(data.sb.st_mode))
continue;
/* Program map */
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s/maps", de);
if (ofl_pmap(mnt, tmp, &data))
continue;
/* Basic links */
data.check = true;
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s/root", de);
if (ofl_one(mnt, tmp, &data))
continue;
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s/cwd", de);
if (ofl_one(mnt, tmp, &data))
continue;
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s/exe", de);
if (ofl_one(mnt, tmp, &data))
continue;
/* All file descriptors */
data.check = false;
snprintf(tmp, sizeof(tmp), COMPAT_PROC "/proc/%s/task", de);
ofl_task(mnt, tmp, &data);
}
return data.found;
}
static unsigned int parse_signal(const char *str)
{
static const char *signames[] = {
[SIGHUP] = "HUP", [SIGINT] = "INT",
[SIGQUIT] = "QUIT", [SIGKILL] = "KILL",
[SIGTERM] = "TERM", [SIGALRM] = "ALRM",
[SIGPIPE] = "PIPE",
};
unsigned int ret;
char *end;
if (HX_isdigit(*str)) {
ret = strtoul(str, &end, 10);
if (*end == '\0')
return ret;
}
for (ret = 0; ret < ARRAY_SIZE(signames); ++ret)
if (signames[ret] != NULL && strcmp(str, signames[ret]) == 0)
return ret;
return 0;
}
int main(int argc, const char **argv)
{
unsigned int signum = 0;
char *signum_str = NULL;
struct HXoption options_table[] = {
{.sh = 'P', .type = HXTYPE_NONE, .ptr = &pids_only,
.help = "Show only PIDs"},
{.sh = 'k', .type = HXTYPE_STRING, .ptr = &signum_str,
.help = "Signal to send (if any)", .htyp = "NUM/NAME"},
HXOPT_AUTOHELP,
HXOPT_TABLEEND,
};
int ret;
if ((ret = HX_init()) <= 0) {
fprintf(stderr, "HX_init: %s\n", strerror(-ret));
abort();
}
if (HX_getopt(options_table, &argc, &argv, HXOPT_USAGEONERR) !=
HXOPT_ERR_SUCCESS)
goto out;
if (argc == 1) {
fprintf(stderr, "You need to supply at least a path\n");
goto out;
}
if (signum_str != NULL)
signum = parse_signal(signum_str);
ret = false;
while (*++argv != NULL)
ret |= ofl(*argv, signum);
if (pids_only)
printf("\n");
HX_exit();
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
out:
HX_exit();
return EXIT_FAILURE + 1;
}