2 * lslocks(8) - list local system locks
4 * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
6 * Very generally based on lslk(8) by Victor A. Abell <abe@purdue.edu>
7 * Since it stopped being maintained over a decade ago, this
8 * program should be considered its replacement.
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it would be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include <sys/types.h>
36 #include <libsmartcols.h>
38 #include "pathnames.h"
39 #include "canonicalize.h"
45 #include "closestream.h"
67 const char * const name
; /* header */
68 double whint
; /* width hint (N < 1 is in percent of termwidth) */
69 int flags
; /* SCOLS_FL_* */
73 /* columns descriptions */
74 static struct colinfo infos
[] = {
75 [COL_SRC
] = { "COMMAND",15, 0, N_("command of the process holding the lock") },
76 [COL_PID
] = { "PID", 5, SCOLS_FL_RIGHT
, N_("PID of the process holding the lock") },
77 [COL_TYPE
] = { "TYPE", 5, SCOLS_FL_RIGHT
, N_("kind of lock") },
78 [COL_SIZE
] = { "SIZE", 4, SCOLS_FL_RIGHT
, N_("size of the lock") },
79 [COL_INODE
] = { "INODE", 5, SCOLS_FL_RIGHT
, N_("inode number") },
80 [COL_MAJMIN
] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
81 [COL_MODE
] = { "MODE", 5, 0, N_("lock access mode") },
82 [COL_M
] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")},
83 [COL_START
] = { "START", 10, SCOLS_FL_RIGHT
, N_("relative byte offset of the lock")},
84 [COL_END
] = { "END", 10, SCOLS_FL_RIGHT
, N_("ending offset of the lock")},
85 [COL_PATH
] = { "PATH", 0, SCOLS_FL_TRUNC
, N_("path of the locked file")},
86 [COL_BLOCKER
] = { "BLOCKER", 0, SCOLS_FL_RIGHT
, N_("PID of the process blocking the lock") }
89 static int columns
[ARRAY_SIZE(infos
) * 2];
90 static size_t ncolumns
;
94 static struct libmnt_table
*tab
; /* /proc/self/mountinfo */
96 /* basic output flags */
97 static int no_headings
;
98 static int no_inaccessible
;
104 struct list_head locks
;
115 unsigned int mandatory
:1,
121 static void rem_lock(struct lock
*lock
)
130 list_del(&lock
->locks
);
134 static void disable_columns_truncate(void)
138 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
139 infos
[i
].flags
&= ~SCOLS_FL_TRUNC
;
143 * Associate the device's mountpoint for a filename
145 static char *get_fallback_filename(dev_t dev
)
147 struct libmnt_fs
*fs
;
151 tab
= mnt_new_table_from_file(_PATH_PROC_MOUNTINFO
);
156 fs
= mnt_table_find_devno(tab
, dev
, MNT_ITER_BACKWARD
);
160 xasprintf(&res
, "%s...", mnt_fs_get_target(fs
));
165 * Return the absolute path of a file from
166 * a given inode number (and its size)
168 static char *get_filename_sz(ino_t inode
, pid_t lock_pid
, size_t *size
)
175 char path
[PATH_MAX
], sym
[PATH_MAX
], *ret
= NULL
;
178 memset(path
, 0, sizeof(path
));
179 memset(sym
, 0, sizeof(sym
));
182 /* pid could be -1 for OFD locks */
186 * We know the pid so we don't have to
187 * iterate the *entire* filesystem searching
190 snprintf(path
, sizeof(path
), "/proc/%d/fd/", lock_pid
);
191 if (!(dirp
= opendir(path
)))
194 if ((len
= strlen(path
)) >= (sizeof(path
) - 2))
197 if ((fd
= dirfd(dirp
)) < 0 )
200 while ((dp
= readdir(dirp
))) {
201 if (!strcmp(dp
->d_name
, ".") ||
202 !strcmp(dp
->d_name
, ".."))
207 /* care only for numerical descriptors */
208 if (!strtol(dp
->d_name
, (char **) NULL
, 10) || errno
)
211 if (!fstatat(fd
, dp
->d_name
, &sb
, 0)
212 && inode
!= sb
.st_ino
)
215 if ((len
= readlinkat(fd
, dp
->d_name
, sym
, sizeof(sym
) - 1)) < 1)
230 * Return the inode number from a string
232 static ino_t
get_dev_inode(char *str
, dev_t
*dev
)
234 unsigned int maj
= 0, min
= 0;
237 if (sscanf(str
, "%x:%x:%ju", &maj
, &min
, &inum
) != 3)
238 errx(EXIT_FAILURE
, _("failed to parse '%s'"), str
);
240 *dev
= (dev_t
) makedev(maj
, min
);
244 static int get_local_locks(struct list_head
*locks
)
248 char buf
[PATH_MAX
], *tok
= NULL
;
252 if (!(fp
= fopen(_PATH_PROC_LOCKS
, "r")))
255 while (fgets(buf
, sizeof(buf
), fp
)) {
257 l
= xcalloc(1, sizeof(*l
));
258 INIT_LIST_HEAD(&l
->locks
);
260 for (tok
= strtok(buf
, " "), i
= 0; tok
;
261 tok
= strtok(NULL
, " "), i
++) {
264 * /proc/locks has *exactly* 8 "blocks" of text
265 * separated by ' ' - check <kernel>/fs/locks.c
269 tok
[strlen(tok
) - 1] = '\0';
270 l
->id
= strtos32_or_err(tok
, _("failed to parse ID"));
272 case 1: /* posix, flock, etc */
273 if (strcmp(tok
, "->") == 0) { /* optional field */
277 l
->type
= xstrdup(tok
);
280 case 2: /* is this a mandatory lock? other values are advisory or noinode */
281 l
->mandatory
= *tok
== 'M' ? 1 : 0;
283 case 3: /* lock mode */
284 l
->mode
= xstrdup(tok
);
289 * If user passed a pid we filter it later when adding
290 * to the list, no need to worry now. OFD locks use -1 PID.
292 l
->pid
= strtos32_or_err(tok
, _("failed to parse pid"));
294 l
->cmdname
= pid_get_cmdname(l
->pid
);
296 l
->cmdname
= xstrdup(_("(unknown)"));
298 l
->cmdname
= xstrdup(_("(undefined)"));
301 case 5: /* device major:minor and inode number */
302 l
->inode
= get_dev_inode(tok
, &l
->dev
);
306 l
->start
= !strcmp(tok
, "EOF") ? 0 :
307 strtou64_or_err(tok
, _("failed to parse start"));
311 /* replace '\n' character */
312 tok
[strlen(tok
)-1] = '\0';
313 l
->end
= !strcmp(tok
, "EOF") ? 0 :
314 strtou64_or_err(tok
, _("failed to parse end"));
321 l
->path
= get_filename_sz(l
->inode
, l
->pid
, &sz
);
323 /* no permissions -- ignore */
324 if (!l
->path
&& no_inaccessible
) {
330 /* probably no permission to peek into l->pid's path */
331 l
->path
= get_fallback_filename(l
->dev
);
336 list_add(&l
->locks
, locks
);
343 static int column_name_to_id(const char *name
, size_t namesz
)
349 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
350 const char *cn
= infos
[i
].name
;
352 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
355 warnx(_("unknown column: %s"), name
);
359 static inline int get_column_id(int num
)
362 assert((size_t) num
< ncolumns
);
363 assert(columns
[num
] < (int) ARRAY_SIZE(infos
));
369 static inline const struct colinfo
*get_column_info(unsigned num
)
371 return &infos
[ get_column_id(num
) ];
374 static pid_t
get_blocker(int id
, struct list_head
*locks
)
378 list_for_each(p
, locks
) {
379 struct lock
*l
= list_entry(p
, struct lock
, locks
);
381 if (l
->id
== id
&& !l
->blocked
)
388 static void add_scols_line(struct libscols_table
*table
, struct lock
*l
, struct list_head
*locks
)
391 struct libscols_line
*line
;
393 * Whenever cmdname or filename is NULL it is most
394 * likely because there's no read permissions
395 * for the specified process.
397 const char *notfnd
= "";
402 line
= scols_table_new_line(table
, NULL
);
404 err(EXIT_FAILURE
, _("failed to allocate output line"));
406 for (i
= 0; i
< ncolumns
; i
++) {
409 switch (get_column_id(i
)) {
411 xasprintf(&str
, "%s", l
->cmdname
? l
->cmdname
: notfnd
);
414 xasprintf(&str
, "%d", l
->pid
);
417 xasprintf(&str
, "%s", l
->type
);
420 xasprintf(&str
, "%ju", (uintmax_t) l
->inode
);
424 xasprintf(&str
, "%u:%u", major(l
->dev
), minor(l
->dev
));
426 xasprintf(&str
, "%3u:%-3u", major(l
->dev
), minor(l
->dev
));
432 xasprintf(&str
, "%ju", l
->size
);
434 str
= size_to_human_string(SIZE_SUFFIX_1LETTER
, l
->size
);
437 xasprintf(&str
, "%s%s", l
->mode
, l
->blocked
? "*" : "");
440 xasprintf(&str
, "%d", l
->mandatory
? 1 : 0);
443 xasprintf(&str
, "%jd", l
->start
);
446 xasprintf(&str
, "%jd", l
->end
);
449 xasprintf(&str
, "%s", l
->path
? l
->path
: notfnd
);
453 pid_t bl
= l
->blocked
&& l
->id
?
454 get_blocker(l
->id
, locks
) : 0;
456 xasprintf(&str
, "%d", (int) bl
);
462 if (str
&& scols_line_refer_data(line
, i
, str
))
463 err(EXIT_FAILURE
, _("failed to add output data"));
467 static int show_locks(struct list_head
*locks
)
471 struct list_head
*p
, *pnext
;
472 struct libscols_table
*table
;
474 table
= scols_new_table();
476 err(EXIT_FAILURE
, _("failed to allocate output table"));
478 scols_table_enable_raw(table
, raw
);
479 scols_table_enable_json(table
, json
);
480 scols_table_enable_noheadings(table
, no_headings
);
483 scols_table_set_name(table
, "locks");
485 for (i
= 0; i
< ncolumns
; i
++) {
486 struct libscols_column
*cl
;
487 const struct colinfo
*col
= get_column_info(i
);
489 cl
= scols_table_new_column(table
, col
->name
, col
->whint
, col
->flags
);
491 err(EXIT_FAILURE
, _("failed to allocate output column"));
494 int id
= get_column_id(i
);
506 scols_column_set_json_type(cl
, SCOLS_JSON_NUMBER
);
509 scols_column_set_json_type(cl
, SCOLS_JSON_BOOLEAN
);
512 scols_column_set_json_type(cl
, SCOLS_JSON_STRING
);
519 /* prepare data for output */
520 list_for_each(p
, locks
) {
521 struct lock
*l
= list_entry(p
, struct lock
, locks
);
523 if (pid
&& pid
!= l
->pid
)
526 add_scols_line(table
, l
, locks
);
529 /* destroy the list */
530 list_for_each_safe(p
, pnext
, locks
) {
531 struct lock
*l
= list_entry(p
, struct lock
, locks
);
535 scols_print_table(table
);
536 scols_unref_table(table
);
541 static void __attribute__((__noreturn__
)) usage(void)
546 fputs(USAGE_HEADER
, out
);
549 _(" %s [options]\n"), program_invocation_short_name
);
551 fputs(USAGE_SEPARATOR
, out
);
552 fputs(_("List local system locks.\n"), out
);
554 fputs(USAGE_OPTIONS
, out
);
555 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out
);
556 fputs(_(" -J, --json use JSON output format\n"), out
);
557 fputs(_(" -i, --noinaccessible ignore locks without read permissions\n"), out
);
558 fputs(_(" -n, --noheadings don't print headings\n"), out
);
559 fputs(_(" -o, --output <list> define which output columns to use\n"), out
);
560 fputs(_(" --output-all output all columns\n"), out
);
561 fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out
);
562 fputs(_(" -r, --raw use the raw output format\n"), out
);
563 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out
);
565 fputs(USAGE_SEPARATOR
, out
);
566 printf(USAGE_HELP_OPTIONS(24));
568 fputs(USAGE_COLUMNS
, out
);
570 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
571 fprintf(out
, " %11s %s\n", infos
[i
].name
, _(infos
[i
].help
));
573 printf(USAGE_MAN_TAIL("lslocks(8)"));
578 int main(int argc
, char *argv
[])
581 struct list_head locks
;
584 OPT_OUTPUT_ALL
= CHAR_MAX
+ 1
586 static const struct option long_opts
[] = {
587 { "bytes", no_argument
, NULL
, 'b' },
588 { "json", no_argument
, NULL
, 'J' },
589 { "pid", required_argument
, NULL
, 'p' },
590 { "help", no_argument
, NULL
, 'h' },
591 { "output", required_argument
, NULL
, 'o' },
592 { "output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
593 { "notruncate", no_argument
, NULL
, 'u' },
594 { "version", no_argument
, NULL
, 'V' },
595 { "noheadings", no_argument
, NULL
, 'n' },
596 { "raw", no_argument
, NULL
, 'r' },
597 { "noinaccessible", no_argument
, NULL
, 'i' },
601 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
605 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
606 setlocale(LC_ALL
, "");
607 bindtextdomain(PACKAGE
, LOCALEDIR
);
609 close_stdout_atexit();
611 while ((c
= getopt_long(argc
, argv
,
612 "biJp:o:nruhV", long_opts
, NULL
)) != -1) {
614 err_exclusive_options(c
, long_opts
, excl
, excl_st
);
627 pid
= strtos32_or_err(optarg
, _("invalid PID argument"));
633 for (ncolumns
= 0; ncolumns
< ARRAY_SIZE(infos
); ncolumns
++)
634 columns
[ncolumns
] = ncolumns
;
643 disable_columns_truncate();
647 print_version(EXIT_SUCCESS
);
651 errtryhelp(EXIT_FAILURE
);
655 INIT_LIST_HEAD(&locks
);
658 /* default columns */
659 columns
[ncolumns
++] = COL_SRC
;
660 columns
[ncolumns
++] = COL_PID
;
661 columns
[ncolumns
++] = COL_TYPE
;
662 columns
[ncolumns
++] = COL_SIZE
;
663 columns
[ncolumns
++] = COL_MODE
;
664 columns
[ncolumns
++] = COL_M
;
665 columns
[ncolumns
++] = COL_START
;
666 columns
[ncolumns
++] = COL_END
;
667 columns
[ncolumns
++] = COL_PATH
;
670 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
671 &ncolumns
, column_name_to_id
) < 0)
676 rc
= get_local_locks(&locks
);
678 if (!rc
&& !list_empty(&locks
))
679 rc
= show_locks(&locks
);
681 mnt_unref_table(tab
);