diff options
Diffstat (limited to '')
-rw-r--r-- | misc-utils/lslocks.c | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/misc-utils/lslocks.c b/misc-utils/lslocks.c new file mode 100644 index 0000000..caca13f --- /dev/null +++ b/misc-utils/lslocks.c @@ -0,0 +1,679 @@ +/* + * lslocks(8) - list local system locks + * + * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org> + * + * Very generally based on lslk(8) by Victor A. Abell <abe@purdue.edu> + * Since it stopped being maintained over a decade ago, this + * program should be considered its replacement. + * + * 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 <string.h> +#include <getopt.h> +#include <stdlib.h> +#include <assert.h> +#include <dirent.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <libmount.h> +#include <libsmartcols.h> + +#include "pathnames.h" +#include "canonicalize.h" +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "c.h" +#include "list.h" +#include "closestream.h" +#include "optutils.h" +#include "procfs.h" + +/* column IDs */ +enum { + COL_SRC = 0, + COL_PID, + COL_TYPE, + COL_SIZE, + COL_INODE, + COL_MAJMIN, + COL_MODE, + COL_M, + COL_START, + COL_END, + COL_PATH, + COL_BLOCKER +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + const char *help; +}; + +/* columns descriptions */ +static struct colinfo infos[] = { + [COL_SRC] = { "COMMAND",15, 0, N_("command of the process holding the lock") }, + [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("PID of the process holding the lock") }, + [COL_TYPE] = { "TYPE", 5, SCOLS_FL_RIGHT, N_("kind of lock") }, + [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, N_("size of the lock") }, + [COL_INODE] = { "INODE", 5, SCOLS_FL_RIGHT, N_("inode number") }, + [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") }, + [COL_MODE] = { "MODE", 5, 0, N_("lock access mode") }, + [COL_M] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")}, + [COL_START] = { "START", 10, SCOLS_FL_RIGHT, N_("relative byte offset of the lock")}, + [COL_END] = { "END", 10, SCOLS_FL_RIGHT, N_("ending offset of the lock")}, + [COL_PATH] = { "PATH", 0, SCOLS_FL_TRUNC, N_("path of the locked file")}, + [COL_BLOCKER] = { "BLOCKER", 0, SCOLS_FL_RIGHT, N_("PID of the process blocking the lock") } +}; + +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +static pid_t pid = 0; + +static struct libmnt_table *tab; /* /proc/self/mountinfo */ + +/* basic output flags */ +static int no_headings; +static int no_inaccessible; +static int raw; +static int json; +static int bytes; + +struct lock { + struct list_head locks; + + char *cmdname; + pid_t pid; + char *path; + char *type; + char *mode; + off_t start; + off_t end; + ino_t inode; + dev_t dev; + unsigned int mandatory :1, + blocked :1; + uint64_t size; + int id; +}; + +static void rem_lock(struct lock *lock) +{ + if (!lock) + return; + + free(lock->path); + free(lock->mode); + free(lock->cmdname); + free(lock->type); + list_del(&lock->locks); + free(lock); +} + +static void disable_columns_truncate(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) + infos[i].flags &= ~SCOLS_FL_TRUNC; +} + +/* + * Associate the device's mountpoint for a filename + */ +static char *get_fallback_filename(dev_t dev) +{ + struct libmnt_fs *fs; + char *res = NULL; + + if (!tab) { + tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); + if (!tab) + return NULL; + } + + fs = mnt_table_find_devno(tab, dev, MNT_ITER_BACKWARD); + if (!fs) + return NULL; + + xasprintf(&res, "%s...", mnt_fs_get_target(fs)); + return res; +} + +/* + * Return the absolute path of a file from + * a given inode number (and its size) + */ +static char *get_filename_sz(ino_t inode, pid_t lock_pid, size_t *size) +{ + struct stat sb; + struct dirent *dp; + DIR *dirp; + size_t len; + int fd; + char path[PATH_MAX], sym[PATH_MAX], *ret = NULL; + + *size = 0; + memset(path, 0, sizeof(path)); + memset(sym, 0, sizeof(sym)); + + /* + * We know the pid so we don't have to + * iterate the *entire* filesystem searching + * for the damn file. + */ + snprintf(path, sizeof(path), "/proc/%d/fd/", lock_pid); + if (!(dirp = opendir(path))) + return NULL; + + if ((len = strlen(path)) >= (sizeof(path) - 2)) + goto out; + + if ((fd = dirfd(dirp)) < 0 ) + goto out; + + while ((dp = readdir(dirp))) { + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..")) + continue; + + errno = 0; + + /* care only for numerical descriptors */ + if (!strtol(dp->d_name, (char **) NULL, 10) || errno) + continue; + + if (!fstatat(fd, dp->d_name, &sb, 0) + && inode != sb.st_ino) + continue; + + if ((len = readlinkat(fd, dp->d_name, sym, sizeof(sym) - 1)) < 1) + goto out; + + *size = sb.st_size; + sym[len] = '\0'; + + ret = xstrdup(sym); + break; + } +out: + closedir(dirp); + return ret; +} + +/* + * Return the inode number from a string + */ +static ino_t get_dev_inode(char *str, dev_t *dev) +{ + unsigned int maj = 0, min = 0; + ino_t inum = 0; + + if (sscanf(str, "%x:%x:%ju", &maj, &min, &inum) != 3) + errx(EXIT_FAILURE, _("failed to parse '%s'"), str); + + *dev = (dev_t) makedev(maj, min); + return inum; +} + +static int get_local_locks(struct list_head *locks) +{ + int i; + FILE *fp; + char buf[PATH_MAX], *tok = NULL; + size_t sz; + struct lock *l; + + if (!(fp = fopen(_PATH_PROC_LOCKS, "r"))) + return -1; + + while (fgets(buf, sizeof(buf), fp)) { + + l = xcalloc(1, sizeof(*l)); + INIT_LIST_HEAD(&l->locks); + + for (tok = strtok(buf, " "), i = 0; tok; + tok = strtok(NULL, " "), i++) { + + /* + * /proc/locks has *exactly* 8 "blocks" of text + * separated by ' ' - check <kernel>/fs/locks.c + */ + switch (i) { + case 0: /* ID: */ + tok[strlen(tok) - 1] = '\0'; + l->id = strtos32_or_err(tok, _("failed to parse ID")); + break; + case 1: /* posix, flock, etc */ + if (strcmp(tok, "->") == 0) { /* optional field */ + l->blocked = 1; + i--; + } else + l->type = xstrdup(tok); + break; + + case 2: /* is this a mandatory lock? other values are advisory or noinode */ + l->mandatory = *tok == 'M' ? 1 : 0; + break; + case 3: /* lock mode */ + l->mode = xstrdup(tok); + break; + + case 4: /* PID */ + /* + * If user passed a pid we filter it later when adding + * to the list, no need to worry now. OFD locks use -1 PID. + */ + l->pid = strtos32_or_err(tok, _("failed to parse pid")); + if (l->pid > 0) { + l->cmdname = pid_get_cmdname(l->pid); + if (!l->cmdname) + l->cmdname = xstrdup(_("(unknown)")); + } else + l->cmdname = xstrdup(_("(undefined)")); + break; + + case 5: /* device major:minor and inode number */ + l->inode = get_dev_inode(tok, &l->dev); + break; + + case 6: /* start */ + l->start = !strcmp(tok, "EOF") ? 0 : + strtou64_or_err(tok, _("failed to parse start")); + break; + + case 7: /* end */ + /* replace '\n' character */ + tok[strlen(tok)-1] = '\0'; + l->end = !strcmp(tok, "EOF") ? 0 : + strtou64_or_err(tok, _("failed to parse end")); + break; + default: + break; + } + } + + l->path = get_filename_sz(l->inode, l->pid, &sz); + + /* no permissions -- ignore */ + if (!l->path && no_inaccessible) { + rem_lock(l); + continue; + } + + if (!l->path) { + /* probably no permission to peek into l->pid's path */ + l->path = get_fallback_filename(l->dev); + l->size = 0; + } else + l->size = sz; + + list_add(&l->locks, locks); + } + + fclose(fp); + return 0; +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + + 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; +} + +static inline 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 inline struct colinfo *get_column_info(unsigned num) +{ + return &infos[ get_column_id(num) ]; +} + +static pid_t get_blocker(int id, struct list_head *locks) +{ + struct list_head *p; + + list_for_each(p, locks) { + struct lock *l = list_entry(p, struct lock, locks); + + if (l->id == id && !l->blocked) + return l->pid; + } + + return 0; +} + +static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks) +{ + size_t i; + struct libscols_line *line; + /* + * Whenever cmdname or filename is NULL it is most + * likely because there's no read permissions + * for the specified process. + */ + const char *notfnd = ""; + + assert(l); + assert(table); + + line = scols_table_new_line(table, NULL); + if (!line) + err(EXIT_FAILURE, _("failed to allocate output line")); + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(i)) { + case COL_SRC: + xasprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd); + break; + case COL_PID: + xasprintf(&str, "%d", l->pid); + break; + case COL_TYPE: + xasprintf(&str, "%s", l->type); + break; + case COL_INODE: + xasprintf(&str, "%ju", (uintmax_t) l->inode); + break; + case COL_MAJMIN: + if (json || raw) + xasprintf(&str, "%u:%u", major(l->dev), minor(l->dev)); + else + xasprintf(&str, "%3u:%-3u", major(l->dev), minor(l->dev)); + break; + case COL_SIZE: + if (!l->size) + break; + if (bytes) + xasprintf(&str, "%ju", l->size); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, l->size); + break; + case COL_MODE: + xasprintf(&str, "%s%s", l->mode, l->blocked ? "*" : ""); + break; + case COL_M: + xasprintf(&str, "%d", l->mandatory ? 1 : 0); + break; + case COL_START: + xasprintf(&str, "%jd", l->start); + break; + case COL_END: + xasprintf(&str, "%jd", l->end); + break; + case COL_PATH: + xasprintf(&str, "%s", l->path ? l->path : notfnd); + break; + case COL_BLOCKER: + { + pid_t bl = l->blocked && l->id ? + get_blocker(l->id, locks) : 0; + if (bl) + xasprintf(&str, "%d", (int) bl); + } + default: + break; + } + + if (str && scols_line_refer_data(line, i, str)) + err(EXIT_FAILURE, _("failed to add output data")); + } +} + +static int show_locks(struct list_head *locks) +{ + int rc = 0; + size_t i; + struct list_head *p, *pnext; + struct libscols_table *table; + + table = scols_new_table(); + if (!table) + err(EXIT_FAILURE, _("failed to allocate output table")); + + scols_table_enable_raw(table, raw); + scols_table_enable_json(table, json); + scols_table_enable_noheadings(table, no_headings); + + if (json) + scols_table_set_name(table, "locks"); + + for (i = 0; i < ncolumns; i++) { + struct libscols_column *cl; + struct colinfo *col = get_column_info(i); + + cl = scols_table_new_column(table, col->name, col->whint, col->flags); + if (!cl) + err(EXIT_FAILURE, _("failed to allocate output column")); + + if (json) { + int id = get_column_id(i); + + switch (id) { + case COL_SIZE: + if (!bytes) + break; + /* fallthrough */ + case COL_PID: + case COL_START: + case COL_END: + case COL_BLOCKER: + case COL_INODE: + scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + break; + case COL_M: + scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); + break; + default: + scols_column_set_json_type(cl, SCOLS_JSON_STRING); + break; + } + } + + } + + /* prepare data for output */ + list_for_each(p, locks) { + struct lock *l = list_entry(p, struct lock, locks); + + if (pid && pid != l->pid) + continue; + + add_scols_line(table, l, locks); + } + + /* destroy the list */ + list_for_each_safe(p, pnext, locks) { + struct lock *l = list_entry(p, struct lock, locks); + rem_lock(l); + } + + scols_print_table(table); + scols_unref_table(table); + return rc; +} + + +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_SEPARATOR, out); + fputs(_("List local system locks.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -i, --noinaccessible ignore locks without read permissions\n"), out); + fputs(_(" -n, --noheadings don't print headings\n"), out); + fputs(_(" -o, --output <list> define which output columns to use\n"), out); + fputs(_(" --output-all output all columns\n"), out); + fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out); + fputs(_(" -r, --raw use the raw output format\n"), out); + fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(24)); + + fputs(USAGE_COLUMNS, out); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("lslocks(8)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c, rc = 0; + struct list_head locks; + char *outarg = NULL; + enum { + OPT_OUTPUT_ALL = CHAR_MAX + 1 + }; + static const struct option long_opts[] = { + { "bytes", no_argument, NULL, 'b' }, + { "json", no_argument, NULL, 'J' }, + { "pid", required_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, + { "notruncate", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "noheadings", no_argument, NULL, 'n' }, + { "raw", no_argument, NULL, 'r' }, + { "noinaccessible", no_argument, NULL, 'i' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'J','r' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, + "biJp:o:nruhV", long_opts, NULL)) != -1) { + + err_exclusive_options(c, long_opts, excl, excl_st); + + switch(c) { + case 'b': + bytes = 1; + break; + case 'i': + no_inaccessible = 1; + break; + case 'J': + json = 1; + break; + case 'p': + pid = strtos32_or_err(optarg, _("invalid PID argument")); + break; + case 'o': + outarg = optarg; + break; + case OPT_OUTPUT_ALL: + for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'n': + no_headings = 1; + break; + case 'r': + raw = 1; + break; + case 'u': + disable_columns_truncate(); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + INIT_LIST_HEAD(&locks); + + if (!ncolumns) { + /* default columns */ + columns[ncolumns++] = COL_SRC; + columns[ncolumns++] = COL_PID; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_MODE; + columns[ncolumns++] = COL_M; + columns[ncolumns++] = COL_START; + columns[ncolumns++] = COL_END; + columns[ncolumns++] = COL_PATH; + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + scols_init_debug(0); + + rc = get_local_locks(&locks); + + if (!rc && !list_empty(&locks)) + rc = show_locks(&locks); + + mnt_unref_table(tab); + return rc; +} |