From: Davidlohr Bueso <dave@xxxxxxx> The new lslocks(8) program is meant to replace the deprecated lslk(8). It is designed for simplicity and removes unnecessary Unix legacy outputs and options: - Don't output inode number, whence and maj:min device numbers. - Don't provide nonblocking syscall options stat(2) and readlink(2) - Remove lslk's alternate default kernel name list file path (-k) The option to use nonblocking calls was previously intended for NFS partitions; however this should be transparent to utility programs considering that timeouts can occur generically (fuse - sshfs, NFS, netdevs, etc). Signed-off-by: Davidlohr Bueso <dave@xxxxxxx> --- misc-utils/lslocks.c | 569 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 569 insertions(+), 0 deletions(-) create mode 100644 misc-utils/lslocks.c diff --git a/misc-utils/lslocks.c b/misc-utils/lslocks.c new file mode 100644 index 0000000..2db72ef --- /dev/null +++ b/misc-utils/lslocks.c @@ -0,0 +1,569 @@ +/* + * lslocks(8) - list local system locks + * + * Copyright (C) 2012 Davidlohr Bueso <dave@xxxxxxx> + * + * Very generally based on lslk(8) by Victor A. Abell <abe@xxxxxxxxxx> + * Since it stopped being maingained 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 "pathnames.h" +#include "canonicalize.h" +#include "nls.h" +#include "tt.h" +#include "xalloc.h" +#include "strutils.h" +#include "c.h" + +/* column IDs */ +enum { + COL_SRC = 0, + COL_PID, + COL_SIZE, + COL_ACCESS, + COL_M, + COL_START, + COL_END, + COL_PATH, +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* TT_FL_* */ + const char *help; +}; + +/* columns descriptions */ +struct colinfo infos[] = { + [COL_SRC] = { "COMMAND", 0.30, TT_FL_TRUNC, N_("command of the process holding the lock") }, + [COL_PID] = { "PID", 0.30, TT_FL_TRUNC, N_("PID of the process holding the lock") }, + [COL_SIZE] = { "SIZE", 0.30, TT_FL_TRUNC, N_("size of the lock") }, + [COL_ACCESS] = { "ACCESS", 0.30, TT_FL_TRUNC, N_("lock access type") }, + [COL_M] = { "M", 0.30, TT_FL_TRUNC, N_("mandatory state of the lock: 0 (none), 1 (set)")}, + [COL_START] = { "START", 0.30, TT_FL_TRUNC, N_("relative byte offset of the lock")}, + [COL_END] = { "END", 0.30, TT_FL_TRUNC, N_("ending offset of the lock")}, + [COL_PATH] = { "PATH", 0.30, TT_FL_TRUNC, N_("path of the locked file")}, +}; +#define NCOLS ARRAY_SIZE(infos) +static int columns[NCOLS], ncolumns; +static pid_t pid = 0; +/* array with IDs of enabled columns */ +static int columns[NCOLS], ncolumns; + +struct lock { + struct list_head locks; + + char *cmdname; + pid_t pid; + char *path; + char *type; + long start; + long end; + int mandatory; + char *size; +}; + +/* + * Return a PID's command name + */ +static char *get_cmdname(pid_t pid) +{ + FILE *fp; + char path[PATH_MAX], *cmd = NULL; + + sprintf(path, "/proc/%d/status", pid); + if (!(fp = fopen(path, "r"))) + return NULL; + + /* just need the first line */ + if (!fgets(path, sizeof(path), fp)) + goto out; + + (void)strtok_r(path, "\t", &cmd); + cmd[strlen(cmd) - 1] = '\0'; +out: + fclose(fp); + return xstrdup(cmd); +} + +/* + * Associate the device's mountpoint for a filename + */ +static char *get_fallback_filename(size_t dev) +{ + char buf[PATH_MAX], *ret = NULL; + char *key = NULL, *tmp = NULL, *tok = NULL; + int i; + struct stat sb; + FILE *fp; + + if (!(fp = fopen(_PATH_PROC_MOUNTS, "r"))) + return NULL; + + while (fgets(buf, sizeof(buf), fp)) { + for (i = 0, tmp = buf; ; tmp = NULL, i++) { + if (!(tok = strtok_r(tmp, " ", &key))) + break; + + if (i == 1) + if (!stat(tok, &sb) && dev == sb.st_dev) { + ret = xstrdup(tok); + goto out; + } + } + } + +out: + fclose(fp); + return ret; +} + +/* + * Return the absolute path of a file from + * a given inode number (and its size) + */ +static char *get_filename_sz(long inode, pid_t pid, size_t *size) +{ + struct stat sb; + struct dirent *dp; + DIR *dirp; + size_t len; + 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. + */ + sprintf(path, "/proc/%d/fd/", pid); + if (!(dirp = opendir(path))) + return NULL; + + if ((len = strlen(path)) >= (sizeof(path) - 2)) + goto out; + + while ((dp = readdir(dirp))) { + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..")) + continue; + + /* care only for numerical descriptors */ + if (!strtol(dp->d_name, (char **) NULL, 10)) + continue; + + (void) strcpy(&path[len], dp->d_name); + if (!stat(path, &sb) && inode != sb.st_ino) + continue; + + if ((len = readlink(path, sym, sizeof(path))) < 1) + goto out; + + *size = sb.st_size; + ret = xstrdup(sym); + break; + } +out: + closedir(dirp); + return ret; +} + +/* + * Return the inode number from a string + */ +static long get_dev_inode(char *str, dev_t *dev) +{ + char *tmp = NULL, *tok = NULL, *key = NULL; + int i, maj = 0, min = 0; + long inum = 0; + + for (i = 0, tmp = str; ; tmp = NULL, i++) { + if (!(tok = strtok_r(tmp, ":", &key))) + break; + + /* + * traditional format '<major>:<minor>:<inode>' - note that this + * might change and the kernel would export '<name>:<inode>'. + */ + switch (i) { + case 0: + maj = strtol(tok, (char **) NULL, 16); + break; + case 1: + min = strtol(tok, (char **) NULL, 16); + break; + case 2: + inum = strtol_or_err(tok, _("failed to parse inode number")); + default: + break; + } + } + + *dev = (dev_t) makedev(maj, min); + return inum; +} + +static int get_local_locks(struct list_head *locks) +{ + int i; + long inode; + FILE *fp; + char buf[PATH_MAX], *key = NULL, *tmp = NULL, *tok = NULL; + size_t sz; + struct lock *l; + dev_t dev; + pid_t p; + + if (!(fp = fopen(_PATH_PROC_LOCKS, "r"))) + return 0; + + while (fgets(buf, sizeof(buf), fp)) { + l = xcalloc(1, sizeof(*l)); + INIT_LIST_HEAD(&l->locks); + + for (i = 0, tmp = buf; ; tmp = NULL, i++) { + if (!(tok = strtok_r(tmp, " ", &key))) + break; + + /* + * /proc/locks as *exactly* 8 "blocks" of text + * separated by ' ' - check <kernel>/fs/locks.c + */ + switch (i) { + case 0: /* not intereted! */ + case 1: /* not intereted! */ + break; + + case 2: /* is this a mandatory lock? other values are advisory or noinode */ + l->mandatory = *tok == 'M' ? TRUE : FALSE; + break; + case 3: /* lock type */ + l->type = 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. + */ + l->pid = strtol_or_err(tok, _("failed to parse pid")); + l->cmdname = get_cmdname(l->pid); + break; + + case 5: /* device major:minor and inode number */ + inode = get_dev_inode(tok, &dev); + break; + + case 6: /* start */ + l->start = !strcmp(tok, "EOF") ? 0 : + strtol_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 : + strtol_or_err(tok, _("failed to parse end")); + break; + default: + break; + } + + l->path = get_filename_sz(inode, l->pid, &sz); + /* probably no permission to peek into l->pid's path */ + if (!l->path) + l->path = get_fallback_filename(dev); + + /* avoid leaking */ + l->size = xstrdup(size_to_human_string(SIZE_SUFFIX_1LETTER, sz)); + } + + if (pid && pid != l->pid) { + /* + * It's easier to just parse the file then decide if + * it should be added to the list - otherwise just + * get rid of stored data + */ + free(l->path); + free(l->size); + free(l->type); + free(l->cmdname); + free(l); + + continue; + } + + 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 < NCOLS; 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(ARRAY_SIZE(columns) == NCOLS); + assert(num < ncolumns); + assert(columns[num] < (int) NCOLS); + + return columns[num]; +} + + +static inline struct colinfo *get_column_info(unsigned num) +{ + return &infos[ get_column_id(num) ]; +} + +static void rem_lock(struct lock *lock) +{ + if (!lock) + return; + + free(lock->path); + free(lock->size); + free(lock->type); + free(lock->cmdname); + list_del(&lock->locks); + free(lock); +} + +static void add_tt_line(struct tt *tt, struct lock *l) +{ + int i; + struct tt_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(tt); + + line = tt_add_line(tt, NULL); + if (!line) { + warn(_("failed to add line to output")); + return; + } + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + int rc = 0; + + switch (get_column_id(i)) { + case COL_SRC: + rc = asprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd); + break; + case COL_PID: + rc = asprintf(&str, "%d", l->pid); + break; + case COL_SIZE: + rc = asprintf(&str, "%s", l->size); + break; + case COL_ACCESS: + rc = asprintf(&str, "%s", l->type); + break; + case COL_M: + rc = asprintf(&str, "%d", l->mandatory); + break; + case COL_START: + rc = asprintf(&str, "%ld", l->start); + break; + case COL_END: + rc = asprintf(&str, "%ld", l->end); + break; + case COL_PATH: + rc = asprintf(&str, "%s", l->path ? l->path : notfnd); + break; + default: + break; + } + + if (rc || str) + tt_line_set_data(line, i, str); + } +} + +static int show_locks(struct list_head *locks, int tt_flags) +{ + int i; + struct list_head *p, *pnext; + struct tt *tt; + + tt = tt_new_table(tt_flags); + if (!tt) { + warn(_("failed to initialize output table")); + return -1; + } + + for (i = 0; i < ncolumns; i++) { + struct colinfo *col = get_column_info(i); + + if (!tt_define_column(tt, col->name, col->whint, col->flags)) { + warnx(_("failed to initialize output column")); + goto done; + } + } + + list_for_each_safe(p, pnext, locks) { + struct lock *lock = list_entry(p, struct lock, locks); + + add_tt_line(tt, lock); + rem_lock(lock); + } + + tt_print_table(tt); +done: + tt_free_table(tt); + return 0; +} + + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + size_t i; + + fputs(USAGE_HEADER, out); + + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(_("\nGeneral Options:\n"), out); + fputs(_(" -p, --pid <pid> process id\n" + " -o, --output <list> define which output columns to use\n" + " --noheadings don't print headings\n" + " --raw use the raw output format\n" + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n"), out); + + fputs(_("\nAvailable columns (for --output):\n"), out); + + for (i = 0; i < NCOLS; i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("lslocks(8)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c, tt_flags = 0; + struct list_head locks; + enum { + RAW_OPTION = CHAR_MAX + 1, + NOHEADINGS_OPTION + }; + static const struct option long_opts[] = { + { "pid", required_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "noheadings", no_argument, NULL, NOHEADINGS_OPTION }, + { "raw", no_argument, NULL, RAW_OPTION }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long(argc, argv, + "p:o:hV", long_opts, NULL)) != -1) { + + switch(c) { + case 'p': + pid = strtol_or_err(optarg, _("cannot parse PID")); + break; + case 'o': + ncolumns = string_to_idarray(optarg, + columns, ARRAY_SIZE(columns), + column_name_to_id); + if (ncolumns < 0) + return EXIT_FAILURE; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + case NOHEADINGS_OPTION: + tt_flags |= TT_FL_NOHEADINGS; + break; + case RAW_OPTION: + tt_flags |= TT_FL_RAW; + break; + case '?': + default: + usage(stderr); + } + } + + INIT_LIST_HEAD(&locks); + + if (!ncolumns) { + /* default columns */ + columns[ncolumns++] = COL_SRC; + columns[ncolumns++] = COL_PID; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_ACCESS; + columns[ncolumns++] = COL_M; + columns[ncolumns++] = COL_START; + columns[ncolumns++] = COL_END; + columns[ncolumns++] = COL_PATH; + } + + get_local_locks(&locks); + + if (!list_empty(&locks)) + show_locks(&locks, tt_flags); + + return EXIT_SUCCESS; +} -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe util-linux" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html