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 maingained 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>
35 #include "pathnames.h"
36 #include "canonicalize.h"
43 #include "closestream.h"
60 const char *name
; /* header */
61 double whint
; /* width hint (N < 1 is in percent of termwidth) */
62 int flags
; /* TT_FL_* */
66 /* columns descriptions */
67 static struct colinfo infos
[] = {
68 [COL_SRC
] = { "COMMAND",15, 0, N_("command of the process holding the lock") },
69 [COL_PID
] = { "PID", 5, TT_FL_RIGHT
, N_("PID of the process holding the lock") },
70 [COL_TYPE
] = { "TYPE", 5, TT_FL_RIGHT
, N_("kind of lock: FL_FLOCK or FL_POSIX.") },
71 [COL_SIZE
] = { "SIZE", 4, TT_FL_RIGHT
, N_("size of the lock") },
72 [COL_MODE
] = { "MODE", 5, 0, N_("lock access mode") },
73 [COL_M
] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")},
74 [COL_START
] = { "START", 10, TT_FL_RIGHT
, N_("relative byte offset of the lock")},
75 [COL_END
] = { "END", 10, TT_FL_RIGHT
, N_("ending offset of the lock")},
76 [COL_PATH
] = { "PATH", 0, TT_FL_TRUNC
, N_("path of the locked file")},
78 #define NCOLS ARRAY_SIZE(infos)
79 static int columns
[NCOLS
], ncolumns
;
83 struct list_head locks
;
96 static void disable_columns_truncate(void)
100 for (i
= 0; i
< NCOLS
; i
++)
101 infos
[i
].flags
&= ~TT_FL_TRUNC
;
105 * Return a PID's command name
107 static char *get_cmdname(pid_t id
)
110 char path
[PATH_MAX
], *ret
= NULL
;
112 sprintf(path
, "/proc/%d/comm", id
);
113 if (!(fp
= fopen(path
, "r")))
116 if (!fgets(path
, sizeof(path
), fp
))
119 path
[strlen(path
) - 1] = '\0';
127 * Associate the device's mountpoint for a filename
129 static char *get_fallback_filename(dev_t dev
)
131 char buf
[BUFSIZ
], target
[PATH_MAX
], *ret
= NULL
;
135 if (!(fp
= fopen(_PATH_PROC_MOUNTINFO
, "r")))
138 while (fgets(buf
, sizeof(buf
), fp
)) {
139 sscanf(buf
, "%*u %*u %u:%u %*s %s",
142 if (dev
== makedev(maj
, min
)) {
143 ret
= xstrdup(target
);
154 * Return the absolute path of a file from
155 * a given inode number (and its size)
157 static char *get_filename_sz(ino_t inode
, pid_t pid
, size_t *size
)
164 char path
[PATH_MAX
], sym
[PATH_MAX
], *ret
= NULL
;
167 memset(path
, 0, sizeof(path
));
168 memset(sym
, 0, sizeof(sym
));
171 * We know the pid so we don't have to
172 * iterate the *entire* filesystem searching
175 sprintf(path
, "/proc/%d/fd/", pid
);
176 if (!(dirp
= opendir(path
)))
179 if ((len
= strlen(path
)) >= (sizeof(path
) - 2))
182 if ((fd
= dirfd(dirp
)) < 0 )
185 while ((dp
= readdir(dirp
))) {
186 if (!strcmp(dp
->d_name
, ".") ||
187 !strcmp(dp
->d_name
, ".."))
190 /* care only for numerical descriptors */
191 if (!strtol(dp
->d_name
, (char **) NULL
, 10))
194 if (!fstat_at(fd
, path
, dp
->d_name
, &sb
, 0)
195 && inode
!= sb
.st_ino
)
198 if ((len
= readlink_at(fd
, path
, dp
->d_name
,
199 sym
, sizeof(sym
) - 1)) < 1)
214 * Return the inode number from a string
216 static ino_t
get_dev_inode(char *str
, dev_t
*dev
)
218 int maj
= 0, min
= 0;
221 sscanf(str
, "%02x:%02x:%ju", &maj
, &min
, &inum
);
223 *dev
= (dev_t
) makedev(maj
, min
);
227 static int get_local_locks(struct list_head
*locks
)
232 char buf
[PATH_MAX
], *szstr
= NULL
, *tok
= NULL
;
237 if (!(fp
= fopen(_PATH_PROC_LOCKS
, "r")))
240 while (fgets(buf
, sizeof(buf
), fp
)) {
242 l
= xcalloc(1, sizeof(*l
));
243 INIT_LIST_HEAD(&l
->locks
);
245 for (tok
= strtok(buf
, " "), i
= 0; tok
;
246 tok
= strtok(NULL
, " "), i
++) {
249 * /proc/locks has *exactly* 8 "blocks" of text
250 * separated by ' ' - check <kernel>/fs/locks.c
255 case 1: /* posix, flock, etc */
256 l
->type
= xstrdup(tok
);
259 case 2: /* is this a mandatory lock? other values are advisory or noinode */
260 l
->mandatory
= *tok
== 'M' ? TRUE
: FALSE
;
262 case 3: /* lock mode */
263 l
->mode
= xstrdup(tok
);
268 * If user passed a pid we filter it later when adding
269 * to the list, no need to worry now.
271 l
->pid
= strtos32_or_err(tok
, _("failed to parse pid"));
272 l
->cmdname
= get_cmdname(l
->pid
);
274 l
->cmdname
= xstrdup(_("(unknown)"));
277 case 5: /* device major:minor and inode number */
278 inode
= get_dev_inode(tok
, &dev
);
282 l
->start
= !strcmp(tok
, "EOF") ? 0 :
283 strtou64_or_err(tok
, _("failed to parse start"));
287 /* replace '\n' character */
288 tok
[strlen(tok
)-1] = '\0';
289 l
->end
= !strcmp(tok
, "EOF") ? 0 :
290 strtou64_or_err(tok
, _("failed to parse end"));
296 l
->path
= get_filename_sz(inode
, l
->pid
, &sz
);
298 /* probably no permission to peek into l->pid's path */
299 l
->path
= get_fallback_filename(dev
);
302 szstr
= size_to_human_string(SIZE_SUFFIX_1LETTER
, sz
);
303 l
->size
= xstrdup(szstr
);
307 if (pid
&& pid
!= l
->pid
) {
309 * It's easier to just parse the file then decide if
310 * it should be added to the list - otherwise just
311 * get rid of stored data
323 list_add(&l
->locks
, locks
);
330 static int column_name_to_id(const char *name
, size_t namesz
)
336 for (i
= 0; i
< NCOLS
; i
++) {
337 const char *cn
= infos
[i
].name
;
339 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
342 warnx(_("unknown column: %s"), name
);
346 static inline int get_column_id(int num
)
348 assert(ARRAY_SIZE(columns
) == NCOLS
);
349 assert(num
< ncolumns
);
350 assert(columns
[num
] < (int) NCOLS
);
356 static inline struct colinfo
*get_column_info(unsigned num
)
358 return &infos
[ get_column_id(num
) ];
361 static void rem_lock(struct lock
*lock
)
371 list_del(&lock
->locks
);
375 static void add_tt_line(struct tt
*tt
, struct lock
*l
)
378 struct tt_line
*line
;
380 * Whenever cmdname or filename is NULL it is most
381 * likely because there's no read permissions
382 * for the specified process.
384 const char *notfnd
= "";
389 line
= tt_add_line(tt
, NULL
);
391 warn(_("failed to add line to output"));
395 for (i
= 0; i
< ncolumns
; i
++) {
398 switch (get_column_id(i
)) {
400 xasprintf(&str
, "%s", l
->cmdname
? l
->cmdname
: notfnd
);
403 xasprintf(&str
, "%d", l
->pid
);
406 xasprintf(&str
, "%s", l
->type
);
409 xasprintf(&str
, "%s", l
->size
);
412 xasprintf(&str
, "%s", l
->mode
);
415 xasprintf(&str
, "%d", l
->mandatory
);
418 xasprintf(&str
, "%jd", l
->start
);
421 xasprintf(&str
, "%jd", l
->end
);
424 xasprintf(&str
, "%s", l
->path
? l
->path
: notfnd
);
431 tt_line_set_data(line
, i
, str
);
435 static int show_locks(struct list_head
*locks
, int tt_flags
)
438 struct list_head
*p
, *pnext
;
441 tt
= tt_new_table(tt_flags
);
443 warn(_("failed to initialize output table"));
447 for (i
= 0; i
< ncolumns
; i
++) {
448 struct colinfo
*col
= get_column_info(i
);
450 if (!tt_define_column(tt
, col
->name
, col
->whint
, col
->flags
)) {
451 warnx(_("failed to initialize output column"));
457 list_for_each_safe(p
, pnext
, locks
) {
458 struct lock
*lock
= list_entry(p
, struct lock
, locks
);
459 add_tt_line(tt
, lock
);
470 static void __attribute__ ((__noreturn__
)) usage(FILE * out
)
474 fputs(USAGE_HEADER
, out
);
477 _(" %s [options]\n"), program_invocation_short_name
);
479 fputs(USAGE_OPTIONS
, out
);
480 fputs(_(" -p, --pid <pid> process id\n"
481 " -o, --output <list> define which output columns to use\n"
482 " -n, --noheadings don't print headings\n"
483 " -r, --raw use the raw output format\n"
484 " -u, --notruncate don't truncate text in columns\n"
485 " -h, --help display this help and exit\n"
486 " -V, --version output version information and exit\n"), out
);
488 fputs(_("\nAvailable columns (for --output):\n"), out
);
490 for (i
= 0; i
< NCOLS
; i
++)
491 fprintf(out
, " %11s %s\n", infos
[i
].name
, _(infos
[i
].help
));
493 fprintf(out
, USAGE_MAN_TAIL("lslocks(8)"));
495 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
498 int main(int argc
, char *argv
[])
500 int c
, tt_flags
= 0, rc
= 0;
501 struct list_head locks
;
502 static const struct option long_opts
[] = {
503 { "pid", required_argument
, NULL
, 'p' },
504 { "help", no_argument
, NULL
, 'h' },
505 { "output", required_argument
, NULL
, 'o' },
506 { "notruncate", no_argument
, NULL
, 'u' },
507 { "version", no_argument
, NULL
, 'V' },
508 { "noheadings", no_argument
, NULL
, 'n' },
509 { "raw", no_argument
, NULL
, 'r' },
513 setlocale(LC_ALL
, "");
514 bindtextdomain(PACKAGE
, LOCALEDIR
);
516 atexit(close_stdout
);
518 while ((c
= getopt_long(argc
, argv
,
519 "p:o:nruhV", long_opts
, NULL
)) != -1) {
523 pid
= strtos32_or_err(optarg
, _("invalid PID argument"));
526 ncolumns
= string_to_idarray(optarg
,
527 columns
, ARRAY_SIZE(columns
),
533 printf(UTIL_LINUX_VERSION
);
538 tt_flags
|= TT_FL_NOHEADINGS
;
541 tt_flags
|= TT_FL_RAW
;
544 disable_columns_truncate();
552 INIT_LIST_HEAD(&locks
);
555 /* default columns */
556 columns
[ncolumns
++] = COL_SRC
;
557 columns
[ncolumns
++] = COL_PID
;
558 columns
[ncolumns
++] = COL_TYPE
;
559 columns
[ncolumns
++] = COL_SIZE
;
560 columns
[ncolumns
++] = COL_MODE
;
561 columns
[ncolumns
++] = COL_M
;
562 columns
[ncolumns
++] = COL_START
;
563 columns
[ncolumns
++] = COL_END
;
564 columns
[ncolumns
++] = COL_PATH
;
567 rc
= get_local_locks(&locks
);
569 if (!rc
&& !list_empty(&locks
))
570 rc
= show_locks(&locks
, tt_flags
);