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>
38 #include <libsmartcols.h>
40 #include "pathnames.h"
41 #include "canonicalize.h"
48 #include "closestream.h"
51 #include "column-list-table.h"
52 #include "fileutils.h"
73 const char * const name
; /* header */
74 double whint
; /* width hint (N < 1 is in percent of termwidth) */
75 int flags
; /* SCOLS_FL_* */
79 /* columns descriptions */
80 static struct colinfo infos
[] = {
81 [COL_SRC
] = { "COMMAND",15, 0, N_("command of the process holding the lock") },
82 [COL_PID
] = { "PID", 5, SCOLS_FL_RIGHT
, N_("PID of the process holding the lock") },
83 [COL_TYPE
] = { "TYPE", 5, SCOLS_FL_RIGHT
, N_("kind of lock") },
84 [COL_SIZE
] = { "SIZE", 4, SCOLS_FL_RIGHT
, N_("size of the lock, use <number> if --bytes is given") },
85 [COL_INODE
] = { "INODE", 5, SCOLS_FL_RIGHT
, N_("inode number") },
86 [COL_MAJMIN
] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
87 [COL_MODE
] = { "MODE", 5, 0, N_("lock access mode") },
88 [COL_M
] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")},
89 [COL_START
] = { "START", 10, SCOLS_FL_RIGHT
, N_("relative byte offset of the lock")},
90 [COL_END
] = { "END", 10, SCOLS_FL_RIGHT
, N_("ending offset of the lock")},
91 [COL_PATH
] = { "PATH", 0, SCOLS_FL_TRUNC
, N_("path of the locked file")},
92 [COL_BLOCKER
] = { "BLOCKER", 0, SCOLS_FL_RIGHT
, N_("PID of the process blocking the lock") },
93 [COL_HOLDERS
] = { "HOLDERS", 0, SCOLS_FL_WRAP
, N_("holders of the lock") },
96 static int columns
[ARRAY_SIZE(infos
) * 2];
97 static size_t ncolumns
;
99 static struct libmnt_table
*tab
; /* /proc/self/mountinfo */
101 /* basic output flags */
102 static int no_headings
;
103 static int no_inaccessible
;
109 struct list_head locks
;
131 struct list_head chain
;
134 static int lock_tnode_compare(const void *a
, const void *b
)
136 struct lock_tnode
*anode
= ((struct lock_tnode
*)a
);
137 struct lock_tnode
*bnode
= ((struct lock_tnode
*)b
);
139 if (anode
->dev
> bnode
->dev
)
141 else if (anode
->dev
< bnode
->dev
)
144 if (anode
->inode
> bnode
->inode
)
146 else if (anode
->inode
< bnode
->inode
)
152 static void add_to_tree(void *troot
, struct lock
*l
)
154 struct lock_tnode tmp
= { .dev
= l
->dev
, .inode
= l
->inode
, };
155 struct lock_tnode
**head
= tfind(&tmp
, troot
, lock_tnode_compare
);
156 struct lock_tnode
*new_head
;
159 list_add_tail(&l
->locks
, &(*head
)->chain
);
163 new_head
= xmalloc(sizeof(*new_head
));
164 new_head
->dev
= l
->dev
;
165 new_head
->inode
= l
->inode
;
166 INIT_LIST_HEAD(&new_head
->chain
);
167 if (tsearch(new_head
, troot
, lock_tnode_compare
) == NULL
)
168 errx(EXIT_FAILURE
, _("failed to allocate memory"));
170 list_add_tail(&l
->locks
, &new_head
->chain
);
173 static void rem_lock(struct lock
*lock
)
182 list_del(&lock
->locks
);
186 static void disable_columns_truncate(void)
190 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
191 infos
[i
].flags
&= ~SCOLS_FL_TRUNC
;
195 * Associate the device's mountpoint for a filename
197 static char *get_fallback_filename(dev_t dev
)
199 struct libmnt_fs
*fs
;
203 tab
= mnt_new_table_from_file(_PATH_PROC_MOUNTINFO
);
208 fs
= mnt_table_find_devno(tab
, dev
, MNT_ITER_BACKWARD
);
212 xasprintf(&res
, "%s...", mnt_fs_get_target(fs
));
217 * Return the absolute path of a file from
218 * a given inode number (and its size)
220 static char *get_filename_sz(ino_t inode
, pid_t lock_pid
, size_t *size
)
226 char path
[PATH_MAX
] = { 0 },
227 sym
[PATH_MAX
] = { 0 }, *ret
= NULL
;
232 /* pid could be -1 for OFD locks */
236 * We know the pid so we don't have to
237 * iterate the *entire* filesystem searching
240 snprintf(path
, sizeof(path
), "/proc/%d/fd/", lock_pid
);
241 if (!(dirp
= opendir(path
)))
244 if (strlen(path
) >= (sizeof(path
) - 2))
247 if ((fd
= dirfd(dirp
)) < 0 )
250 while ((dp
= xreaddir(dirp
))) {
255 /* care only for numerical descriptors */
256 if (!strtol(dp
->d_name
, (char **) NULL
, 10) || errno
)
259 if (!fstatat(fd
, dp
->d_name
, &sb
, 0)
260 && inode
!= sb
.st_ino
)
263 if ((len
= readlinkat(fd
, dp
->d_name
, sym
, sizeof(sym
) - 1)) < 1)
278 * Return the inode number from a string
280 static ino_t
get_dev_inode(char *str
, dev_t
*dev
)
282 unsigned int maj
= 0, min
= 0;
285 if (sscanf(str
, "%x:%x:%ju", &maj
, &min
, &inum
) != 3)
286 errx(EXIT_FAILURE
, _("failed to parse '%s'"), str
);
288 *dev
= (dev_t
) makedev(maj
, min
);
292 struct override_info
{
297 static bool is_holder(struct lock
*l
, struct lock
*m
)
299 return (l
->start
== m
->start
&&
301 l
->inode
== m
->inode
&&
303 l
->mandatory
== m
->mandatory
&&
304 l
->blocked
== m
->blocked
&&
305 strcmp(l
->type
, m
->type
) == 0 &&
306 strcmp(l
->mode
, m
->mode
) == 0);
309 static void patch_lock(struct lock
*l
, void *fallback
)
311 struct lock_tnode tmp
= { .dev
= l
->dev
, .inode
= l
->inode
, };
312 struct lock_tnode
**head
= tfind(&tmp
, fallback
, lock_tnode_compare
);
318 list_for_each(p
, &(*head
)->chain
) {
319 struct lock
*m
= list_entry(p
, struct lock
, locks
);
320 if (is_holder(l
, m
)) {
321 /* size and id can be ignored. */
323 l
->cmdname
= xstrdup(m
->cmdname
);
329 static void add_to_list(void *locks
, struct lock
*l
)
331 list_add(&l
->locks
, locks
);
334 static struct lock
*get_lock(char *buf
, struct override_info
*oinfo
, void *fallback
)
339 struct lock
*l
= xcalloc(1, sizeof(*l
));
340 INIT_LIST_HEAD(&l
->locks
);
343 bool cmdname_unknown
= false;
345 for (tok
= strtok(buf
, " "), i
= 0; tok
;
346 tok
= strtok(NULL
, " "), i
++) {
349 * /proc/locks has *exactly* 8 "blocks" of text
350 * separated by ' ' - check <kernel>/fs/locks.c
357 tok
[strlen(tok
) - 1] = '\0';
358 l
->id
= strtos32_or_err(tok
, _("failed to parse ID"));
361 case 1: /* posix, flock, etc */
362 if (strcmp(tok
, "->") == 0) { /* optional field */
366 l
->type
= xstrdup(tok
);
369 case 2: /* is this a mandatory lock? other values are advisory or noinode */
370 l
->mandatory
= *tok
== 'M' ? 1 : 0;
372 case 3: /* lock mode */
373 l
->mode
= xstrdup(tok
);
378 * If user passed a pid we filter it later when adding
379 * to the list, no need to worry now. OFD locks use -1 PID.
383 l
->cmdname
= xstrdup(oinfo
->cmdname
);
385 /* strtopid_or_err() is not suitable here; tok can be -1.*/
386 l
->pid
= strtos32_or_err(tok
, _("invalid PID argument"));
388 l
->cmdname
= pid_get_cmdname(l
->pid
);
390 cmdname_unknown
= true;
396 case 5: /* device major:minor and inode number */
397 l
->inode
= get_dev_inode(tok
, &l
->dev
);
401 l
->start
= !strcmp(tok
, "EOF") ? 0 :
402 strtou64_or_err(tok
, _("failed to parse start"));
406 /* replace '\n' character */
407 tok
[strlen(tok
)-1] = '\0';
408 l
->end
= !strcmp(tok
, "EOF") ? 0 :
409 strtou64_or_err(tok
, _("failed to parse end"));
416 if ((!l
->blocked
) && fallback
&& !l
->cmdname
)
417 patch_lock(l
, fallback
);
420 l
->cmdname
= xstrdup(_("(unknown)"));
422 l
->cmdname
= xstrdup(_("(undefined)"));
424 l
->path
= get_filename_sz(l
->inode
, l
->pid
, &sz
);
426 /* no permissions -- ignore */
427 if (!l
->path
&& no_inaccessible
) {
433 /* probably no permission to peek into l->pid's path */
434 l
->path
= get_fallback_filename(l
->dev
);
442 static int get_pid_lock(void *locks
, void (*add_lock
)(void *, struct lock
*), FILE *fp
,
443 pid_t pid
, const char *cmdname
, int fd
)
446 struct override_info oinfo
= {
451 while (fgets(buf
, sizeof(buf
), fp
)) {
453 if (strncmp(buf
, "lock:\t", 6))
455 l
= get_lock(buf
+ 6, &oinfo
, NULL
);
461 Multiple recode locks can be taken via one fd. */
467 static int get_pid_locks(void *locks
, void (*add_lock
)(void *, struct lock
*), struct path_cxt
*pc
,
468 pid_t pid
, const char *cmdname
)
471 struct dirent
*d
= NULL
;
474 while (ul_path_next_dirent(pc
, &sub
, "fdinfo", &d
) == 0) {
478 if (ul_strtou64(d
->d_name
, &num
, 10) != 0) /* only numbers */
481 fdinfo
= ul_path_fopenf(pc
, "r", "fdinfo/%ju", num
);
485 get_pid_lock(locks
, add_lock
, fdinfo
, pid
, cmdname
, (int)num
);
492 static void get_pids_locks(void *locks
, void (*add_lock
)(void *, struct lock
*))
496 struct path_cxt
*pc
= NULL
;
498 pc
= ul_new_path(NULL
);
500 err(EXIT_FAILURE
, _("failed to alloc procfs handler"));
502 dir
= opendir(_PATH_PROC
);
504 err(EXIT_FAILURE
, _("failed to open /proc"));
506 while ((d
= readdir(dir
))) {
509 const char *cmdname
= NULL
;
511 if (procfs_dirent_get_pid(d
, &pid
) != 0)
514 if (procfs_process_init_path(pc
, pid
) != 0)
517 if (procfs_process_get_cmdname(pc
, buf
, sizeof(buf
)) <= 0)
521 get_pid_locks(locks
, add_lock
, pc
, pid
, cmdname
);
530 static int get_proc_locks(void *locks
, void (*add_lock
)(void *, struct lock
*), void *fallback
)
535 if (!(fp
= fopen(_PATH_PROC_LOCKS
, "r")))
538 while (fgets(buf
, sizeof(buf
), fp
)) {
539 struct lock
*l
= get_lock(buf
, NULL
, fallback
);
548 static int column_name_to_id(const char *name
, size_t namesz
)
554 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
555 const char *cn
= infos
[i
].name
;
557 if (!c_strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
560 warnx(_("unknown column: %s"), name
);
564 static inline int get_column_id(int num
)
567 assert((size_t) num
< ncolumns
);
568 assert(columns
[num
] < (int) ARRAY_SIZE(infos
));
574 static inline const struct colinfo
*get_column_info(unsigned num
)
576 return &infos
[ get_column_id(num
) ];
579 static pid_t
get_blocker(int id
, struct list_head
*locks
)
583 list_for_each(p
, locks
) {
584 struct lock
*l
= list_entry(p
, struct lock
, locks
);
586 if (l
->id
== id
&& !l
->blocked
)
593 static void xstrcoholder(char **str
, struct lock
*l
)
595 xstrfappend(str
, "%d,%s,%d",
596 l
->pid
, l
->cmdname
, l
->fd
);
599 static void add_scols_line(struct libscols_table
*table
, struct lock
*l
, struct list_head
*locks
, void *pid_locks
)
602 struct libscols_line
*line
;
604 * Whenever cmdname or filename is NULL it is most
605 * likely because there's no read permissions
606 * for the specified process.
608 const char *notfnd
= "";
613 line
= scols_table_new_line(table
, NULL
);
615 err(EXIT_FAILURE
, _("failed to allocate output line"));
617 for (i
= 0; i
< ncolumns
; i
++) {
620 switch (get_column_id(i
)) {
622 xasprintf(&str
, "%s", l
->cmdname
? l
->cmdname
: notfnd
);
625 xasprintf(&str
, "%d", l
->pid
);
628 xasprintf(&str
, "%s", l
->type
);
631 xasprintf(&str
, "%ju", (uintmax_t) l
->inode
);
635 xasprintf(&str
, "%u:%u", major(l
->dev
), minor(l
->dev
));
637 xasprintf(&str
, "%3u:%-3u", major(l
->dev
), minor(l
->dev
));
643 xasprintf(&str
, "%ju", l
->size
);
645 str
= size_to_human_string(SIZE_SUFFIX_1LETTER
, l
->size
);
648 xasprintf(&str
, "%s%s", l
->mode
, l
->blocked
? "*" : "");
651 xasprintf(&str
, "%d", l
->mandatory
? 1 : 0);
654 xasprintf(&str
, "%jd", l
->start
);
657 xasprintf(&str
, "%jd", l
->end
);
660 xasprintf(&str
, "%s", l
->path
? l
->path
: notfnd
);
664 pid_t bl
= l
->blocked
&& l
->id
?
665 get_blocker(l
->id
, locks
) : 0;
667 xasprintf(&str
, "%d", (int) bl
);
672 struct lock_tnode tmp
= { .dev
= l
->dev
, .inode
= l
->inode
, };
673 struct lock_tnode
**head
= tfind(&tmp
, pid_locks
, lock_tnode_compare
);
679 list_for_each(p
, &(*head
)->chain
) {
680 struct lock
*m
= list_entry(p
, struct lock
, locks
);
682 if (!is_holder(l
, m
))
686 xstrputc(&str
, '\n');
687 xstrcoholder(&str
, m
);
695 if (str
&& scols_line_refer_data(line
, i
, str
))
696 err(EXIT_FAILURE
, _("failed to add output data"));
700 static void rem_locks(struct list_head
*locks
)
702 struct list_head
*p
, *pnext
;
704 /* destroy the list */
705 list_for_each_safe(p
, pnext
, locks
) {
706 struct lock
*l
= list_entry(p
, struct lock
, locks
);
711 static void rem_tnode(void *node
)
713 struct lock_tnode
*tnode
= node
;
715 rem_locks(&tnode
->chain
);
719 static int get_json_type_for_column(int column_id
, int representing_in_bytes
)
723 if (!representing_in_bytes
)
724 return SCOLS_JSON_STRING
;
731 return SCOLS_JSON_NUMBER
;
733 return SCOLS_JSON_BOOLEAN
;
735 return SCOLS_JSON_ARRAY_STRING
;
737 return SCOLS_JSON_STRING
;
741 static int show_locks(struct list_head
*locks
, pid_t target_pid
, void *pid_locks
)
746 struct libscols_table
*table
;
748 table
= scols_new_table();
750 err(EXIT_FAILURE
, _("failed to allocate output table"));
752 scols_table_enable_raw(table
, raw
);
753 scols_table_enable_json(table
, json
);
754 scols_table_enable_noheadings(table
, no_headings
);
757 scols_table_set_name(table
, "locks");
759 for (i
= 0; i
< ncolumns
; i
++) {
760 struct libscols_column
*cl
;
761 const struct colinfo
*col
= get_column_info(i
);
763 cl
= scols_table_new_column(table
, col
->name
, col
->whint
, col
->flags
);
765 err(EXIT_FAILURE
, _("failed to allocate output column"));
767 if (col
->flags
& SCOLS_FL_WRAP
) {
768 scols_column_set_wrapfunc(cl
,
769 scols_wrapnl_chunksize
,
770 scols_wrapnl_nextchunk
,
772 scols_column_set_safechars(cl
, "\n");
776 int id
= get_column_id(i
);
777 int json_type
= get_json_type_for_column(id
, bytes
);
778 scols_column_set_json_type(cl
, json_type
);
783 /* prepare data for output */
784 list_for_each(p
, locks
) {
785 struct lock
*l
= list_entry(p
, struct lock
, locks
);
787 if (target_pid
&& target_pid
!= l
->pid
)
790 add_scols_line(table
, l
, locks
, pid_locks
);
793 scols_print_table(table
);
794 scols_unref_table(table
);
799 static void __attribute__((__noreturn__
)) usage(void)
803 fputs(USAGE_HEADER
, out
);
806 _(" %s [options]\n"), program_invocation_short_name
);
808 fputs(USAGE_SEPARATOR
, out
);
809 fputs(_("List local system locks.\n"), out
);
811 fputs(USAGE_OPTIONS
, out
);
812 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out
);
813 fputs(_(" -J, --json use JSON output format\n"), out
);
814 fputs(_(" -i, --noinaccessible ignore locks without read permissions\n"), out
);
815 fputs(_(" -n, --noheadings don't print headings\n"), out
);
816 fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out
);
817 fputs(_(" --output-all output all columns\n"), out
);
818 fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out
);
819 fputs(_(" -r, --raw use the raw output format\n"), out
);
820 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out
);
822 fputs(USAGE_SEPARATOR
, out
);
823 fputs(_(" -H, --list-columns list the available columns\n"), out
);
824 fprintf(out
, USAGE_HELP_OPTIONS(24));
825 fprintf(out
, USAGE_MAN_TAIL("lslocks(8)"));
830 static void __attribute__((__noreturn__
)) list_colunms(void)
832 struct libscols_table
*col_tb
= xcolumn_list_table_new(
833 "lslocks-columns", stdout
, raw
, json
);
835 for (size_t i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
837 int json_type
= get_json_type_for_column(i
, bytes
);
838 xcolumn_list_table_append_line(col_tb
, infos
[i
].name
,
842 xcolumn_list_table_append_line(col_tb
, infos
[i
].name
,
843 -1, "<string|number>",
847 scols_print_table(col_tb
);
848 scols_unref_table(col_tb
);
853 int main(int argc
, char *argv
[])
855 int c
, rc
= 0, collist
= 0;
856 struct list_head proc_locks
;
857 void *pid_locks
= NULL
;
860 OPT_OUTPUT_ALL
= CHAR_MAX
+ 1
862 static const struct option long_opts
[] = {
863 { "bytes", no_argument
, NULL
, 'b' },
864 { "json", no_argument
, NULL
, 'J' },
865 { "pid", required_argument
, NULL
, 'p' },
866 { "help", no_argument
, NULL
, 'h' },
867 { "output", required_argument
, NULL
, 'o' },
868 { "output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
869 { "notruncate", no_argument
, NULL
, 'u' },
870 { "version", no_argument
, NULL
, 'V' },
871 { "noheadings", no_argument
, NULL
, 'n' },
872 { "raw", no_argument
, NULL
, 'r' },
873 { "noinaccessible", no_argument
, NULL
, 'i' },
874 { "list-columns", no_argument
, NULL
, 'H' },
878 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
882 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
883 pid_t target_pid
= 0;
885 setlocale(LC_ALL
, "");
886 bindtextdomain(PACKAGE
, LOCALEDIR
);
888 close_stdout_atexit();
890 while ((c
= getopt_long(argc
, argv
,
891 "biJp:o:nruhVH", long_opts
, NULL
)) != -1) {
893 err_exclusive_options(c
, long_opts
, excl
, excl_st
);
906 target_pid
= strtopid_or_err(optarg
, _("invalid PID argument"));
912 for (ncolumns
= 0; ncolumns
< ARRAY_SIZE(infos
); ncolumns
++)
913 columns
[ncolumns
] = ncolumns
;
922 disable_columns_truncate();
929 print_version(EXIT_SUCCESS
);
933 errtryhelp(EXIT_FAILURE
);
938 list_colunms(); /* print end exit */
940 INIT_LIST_HEAD(&proc_locks
);
943 /* default columns */
944 columns
[ncolumns
++] = COL_SRC
;
945 columns
[ncolumns
++] = COL_PID
;
946 columns
[ncolumns
++] = COL_TYPE
;
947 columns
[ncolumns
++] = COL_SIZE
;
948 columns
[ncolumns
++] = COL_MODE
;
949 columns
[ncolumns
++] = COL_M
;
950 columns
[ncolumns
++] = COL_START
;
951 columns
[ncolumns
++] = COL_END
;
952 columns
[ncolumns
++] = COL_PATH
;
955 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
956 &ncolumns
, column_name_to_id
) < 0)
961 /* get_pids_locks() get locks related information from "lock:" fields
962 * of /proc/$pid/fdinfo/$fd as fallback information.
963 * get_proc_locks() used the fallback information if /proc/locks
964 * doesn't provides enough information or provides staled information. */
965 get_pids_locks(&pid_locks
, add_to_tree
);
966 rc
= get_proc_locks(&proc_locks
, add_to_list
, &pid_locks
);
968 if (!rc
&& !list_empty(&proc_locks
))
969 rc
= show_locks(&proc_locks
, target_pid
, &pid_locks
);
971 tdestroy(pid_locks
, rem_tnode
);
972 rem_locks(&proc_locks
);
974 mnt_unref_table(tab
);