]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/lslocks.c
lslocks: (preparation) add a fd number to the lock struct when loading lock info...
[thirdparty/util-linux.git] / misc-utils / lslocks.c
CommitLineData
3dc02ef4
DB
1/*
2 * lslocks(8) - list local system locks
3 *
4 * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
5 *
6 * Very generally based on lslk(8) by Victor A. Abell <abe@purdue.edu>
9e930041 7 * Since it stopped being maintained over a decade ago, this
3dc02ef4
DB
8 * program should be considered its replacement.
9 *
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.
14 *
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.
19 *
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
23 */
24
25#include <stdio.h>
26#include <string.h>
27#include <getopt.h>
28#include <stdlib.h>
29#include <assert.h>
30#include <dirent.h>
31#include <unistd.h>
32#include <sys/stat.h>
33#include <sys/types.h>
6c2c1b11 34#include <stdbool.h>
751ca46e 35#include <search.h>
3dc02ef4 36
07c916cf 37#include <libmount.h>
ba1bf716 38#include <libsmartcols.h>
07c916cf 39
3dc02ef4
DB
40#include "pathnames.h"
41#include "canonicalize.h"
42#include "nls.h"
3dc02ef4 43#include "xalloc.h"
3dc02ef4
DB
44#include "strutils.h"
45#include "c.h"
ba1bf716 46#include "list.h"
c05a80ca 47#include "closestream.h"
b3a2e889 48#include "optutils.h"
0692c3ae 49#include "procfs.h"
3dc02ef4
DB
50
51/* column IDs */
52enum {
53 COL_SRC = 0,
54 COL_PID,
74fa8244 55 COL_TYPE,
3dc02ef4 56 COL_SIZE,
c340ff05
KZ
57 COL_INODE,
58 COL_MAJMIN,
74fa8244 59 COL_MODE,
3dc02ef4
DB
60 COL_M,
61 COL_START,
62 COL_END,
63 COL_PATH,
7badb909 64 COL_BLOCKER
3dc02ef4
DB
65};
66
67/* column names */
68struct colinfo {
37b2b3fa
TW
69 const char * const name; /* header */
70 double whint; /* width hint (N < 1 is in percent of termwidth) */
71 int flags; /* SCOLS_FL_* */
72 const char *help;
3dc02ef4
DB
73};
74
75/* columns descriptions */
c4137d39 76static struct colinfo infos[] = {
74fa8244 77 [COL_SRC] = { "COMMAND",15, 0, N_("command of the process holding the lock") },
ba1bf716 78 [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("PID of the process holding the lock") },
4707bc83 79 [COL_TYPE] = { "TYPE", 5, SCOLS_FL_RIGHT, N_("kind of lock") },
ba1bf716 80 [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, N_("size of the lock") },
c340ff05
KZ
81 [COL_INODE] = { "INODE", 5, SCOLS_FL_RIGHT, N_("inode number") },
82 [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
74fa8244
DB
83 [COL_MODE] = { "MODE", 5, 0, N_("lock access mode") },
84 [COL_M] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")},
ba1bf716
OO
85 [COL_START] = { "START", 10, SCOLS_FL_RIGHT, N_("relative byte offset of the lock")},
86 [COL_END] = { "END", 10, SCOLS_FL_RIGHT, N_("ending offset of the lock")},
87 [COL_PATH] = { "PATH", 0, SCOLS_FL_TRUNC, N_("path of the locked file")},
88 [COL_BLOCKER] = { "BLOCKER", 0, SCOLS_FL_RIGHT, N_("PID of the process blocking the lock") }
3dc02ef4 89};
21abf83d
KZ
90
91static int columns[ARRAY_SIZE(infos) * 2];
40b17508 92static size_t ncolumns;
21abf83d 93
07c916cf
KZ
94static struct libmnt_table *tab; /* /proc/self/mountinfo */
95
ba1bf716
OO
96/* basic output flags */
97static int no_headings;
f29bc6e1 98static int no_inaccessible;
ba1bf716 99static int raw;
b3a2e889 100static int json;
51b0bcf0 101static int bytes;
ba1bf716 102
3dc02ef4
DB
103struct lock {
104 struct list_head locks;
105
106 char *cmdname;
107 pid_t pid;
108 char *path;
109 char *type;
74fa8244 110 char *mode;
3dc02ef4
DB
111 off_t start;
112 off_t end;
c340ff05
KZ
113 ino_t inode;
114 dev_t dev;
55c0d16b
KZ
115 unsigned int mandatory :1,
116 blocked :1;
51b0bcf0 117 uint64_t size;
b28481cd 118 int fd;
7badb909 119 int id;
3dc02ef4
DB
120};
121
751ca46e
MY
122struct lock_tnode {
123 dev_t dev;
124 ino_t inode;
125
126 struct list_head chain;
127};
128
129static int lock_tnode_compare(const void *a, const void *b)
130{
131 struct lock_tnode *anode = ((struct lock_tnode *)a);
132 struct lock_tnode *bnode = ((struct lock_tnode *)b);
133
134 if (anode->dev > bnode->dev)
135 return 1;
136 else if (anode->dev < bnode->dev)
137 return -1;
138
139 if (anode->inode > bnode->inode)
140 return 1;
141 else if (anode->inode < bnode->inode)
142 return -1;
143
144 return 0;
145}
146
147static void add_to_tree(void *troot, struct lock *l)
148{
149 struct lock_tnode tmp = { .dev = l->dev, .inode = l->inode, };
150 struct lock_tnode **head = tfind(&tmp, troot, lock_tnode_compare);
151 struct lock_tnode *new_head;
152
153 if (head) {
154 list_add(&l->locks, &(*head)->chain);
155 return;
156 }
157
b28481cd 158 new_head = xmalloc(sizeof(*new_head));
751ca46e
MY
159 new_head->dev = l->dev;
160 new_head->inode = l->inode;
161 INIT_LIST_HEAD(&new_head->chain);
162 if (tsearch(new_head, troot, lock_tnode_compare) == NULL)
163 errx(EXIT_FAILURE, _("failed to allocate memory"));
164
165 list_add(&l->locks, &new_head->chain);
166}
167
f29bc6e1
KZ
168static void rem_lock(struct lock *lock)
169{
170 if (!lock)
171 return;
172
173 free(lock->path);
f29bc6e1
KZ
174 free(lock->mode);
175 free(lock->cmdname);
176 free(lock->type);
177 list_del(&lock->locks);
178 free(lock);
179}
180
c4137d39
KZ
181static void disable_columns_truncate(void)
182{
183 size_t i;
184
21abf83d 185 for (i = 0; i < ARRAY_SIZE(infos); i++)
ba1bf716 186 infos[i].flags &= ~SCOLS_FL_TRUNC;
c4137d39
KZ
187}
188
3dc02ef4
DB
189/*
190 * Associate the device's mountpoint for a filename
191 */
192static char *get_fallback_filename(dev_t dev)
193{
07c916cf 194 struct libmnt_fs *fs;
f29bc6e1 195 char *res = NULL;
3dc02ef4 196
07c916cf
KZ
197 if (!tab) {
198 tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
199 if (!tab)
200 return NULL;
201 }
3dc02ef4 202
07c916cf
KZ
203 fs = mnt_table_find_devno(tab, dev, MNT_ITER_BACKWARD);
204 if (!fs)
205 return NULL;
3dc02ef4 206
f29bc6e1
KZ
207 xasprintf(&res, "%s...", mnt_fs_get_target(fs));
208 return res;
3dc02ef4
DB
209}
210
211/*
212 * Return the absolute path of a file from
213 * a given inode number (and its size)
214 */
7ee26cbf 215static char *get_filename_sz(ino_t inode, pid_t lock_pid, size_t *size)
3dc02ef4
DB
216{
217 struct stat sb;
218 struct dirent *dp;
219 DIR *dirp;
220 size_t len;
221 int fd;
222 char path[PATH_MAX], sym[PATH_MAX], *ret = NULL;
223
224 *size = 0;
225 memset(path, 0, sizeof(path));
226 memset(sym, 0, sizeof(sym));
227
89295e10
JW
228 if (lock_pid < 0)
229 /* pid could be -1 for OFD locks */
230 return NULL;
231
3dc02ef4
DB
232 /*
233 * We know the pid so we don't have to
234 * iterate the *entire* filesystem searching
235 * for the damn file.
236 */
09eee8b4 237 snprintf(path, sizeof(path), "/proc/%d/fd/", lock_pid);
3dc02ef4
DB
238 if (!(dirp = opendir(path)))
239 return NULL;
240
241 if ((len = strlen(path)) >= (sizeof(path) - 2))
242 goto out;
243
244 if ((fd = dirfd(dirp)) < 0 )
245 goto out;
246
247 while ((dp = readdir(dirp))) {
248 if (!strcmp(dp->d_name, ".") ||
249 !strcmp(dp->d_name, ".."))
250 continue;
251
93d01a62
KZ
252 errno = 0;
253
3dc02ef4 254 /* care only for numerical descriptors */
93d01a62 255 if (!strtol(dp->d_name, (char **) NULL, 10) || errno)
3dc02ef4
DB
256 continue;
257
2208b3cc 258 if (!fstatat(fd, dp->d_name, &sb, 0)
3dc02ef4
DB
259 && inode != sb.st_ino)
260 continue;
261
2208b3cc 262 if ((len = readlinkat(fd, dp->d_name, sym, sizeof(sym) - 1)) < 1)
3dc02ef4
DB
263 goto out;
264
265 *size = sb.st_size;
266 sym[len] = '\0';
267
268 ret = xstrdup(sym);
269 break;
fbf9034e 270 }
3dc02ef4
DB
271out:
272 closedir(dirp);
273 return ret;
274}
275
276/*
277 * Return the inode number from a string
278 */
279static ino_t get_dev_inode(char *str, dev_t *dev)
280{
64a265bf 281 unsigned int maj = 0, min = 0;
3dc02ef4
DB
282 ino_t inum = 0;
283
9b725af7 284 if (sscanf(str, "%x:%x:%ju", &maj, &min, &inum) != 3)
27f68508 285 errx(EXIT_FAILURE, _("failed to parse '%s'"), str);
3dc02ef4
DB
286
287 *dev = (dev_t) makedev(maj, min);
288 return inum;
289}
290
6c2c1b11
MY
291struct override_info {
292 pid_t pid;
293 const char *cmdname;
294};
295
751ca46e 296static void patch_lock(struct lock *l, void *fallback)
6c2c1b11 297{
751ca46e
MY
298 struct lock_tnode tmp = { .dev = l->dev, .inode = l->inode, };
299 struct lock_tnode **head = tfind(&tmp, fallback, lock_tnode_compare);
6c2c1b11
MY
300 struct list_head *p;
301
751ca46e
MY
302 if (!head)
303 return;
304
305 list_for_each(p, &(*head)->chain) {
6c2c1b11
MY
306 struct lock *m = list_entry(p, struct lock, locks);
307 if (l->start == m->start &&
308 l->end == m->end &&
309 l->inode == m->inode &&
310 l->dev == m->dev &&
311 l->mandatory == m->mandatory &&
312 l->blocked == m->blocked &&
313 strcmp(l->type, m->type) == 0 &&
314 strcmp(l->mode, m->mode) == 0) {
315 /* size and id can be ignored. */
316 l->pid = m->pid;
317 l->cmdname = xstrdup(m->cmdname);
318 break;
319 }
320 }
321}
322
ec1d8306
MY
323static void add_to_list(void *locks, struct lock *l)
324{
325 list_add(&l->locks, locks);
326}
327
751ca46e 328static struct lock *get_lock(char *buf, struct override_info *oinfo, void *fallback)
3dc02ef4
DB
329{
330 int i;
5697f991 331 char *tok = NULL;
3dc02ef4 332 size_t sz;
5697f991
MY
333 struct lock *l = xcalloc(1, sizeof(*l));
334 INIT_LIST_HEAD(&l->locks);
b28481cd 335 l->fd = -1;
5697f991 336
6c2c1b11
MY
337 bool cmdname_unknown = false;
338
5697f991
MY
339 for (tok = strtok(buf, " "), i = 0; tok;
340 tok = strtok(NULL, " "), i++) {
341
342 /*
343 * /proc/locks has *exactly* 8 "blocks" of text
344 * separated by ' ' - check <kernel>/fs/locks.c
345 */
346 switch (i) {
347 case 0: /* ID: */
6c2c1b11
MY
348 if (oinfo)
349 l->id = -1;
350 else {
351 tok[strlen(tok) - 1] = '\0';
352 l->id = strtos32_or_err(tok, _("failed to parse ID"));
353 }
5697f991
MY
354 break;
355 case 1: /* posix, flock, etc */
356 if (strcmp(tok, "->") == 0) { /* optional field */
357 l->blocked = 1;
358 i--;
359 } else
360 l->type = xstrdup(tok);
361 break;
362
363 case 2: /* is this a mandatory lock? other values are advisory or noinode */
364 l->mandatory = *tok == 'M' ? 1 : 0;
365 break;
366 case 3: /* lock mode */
367 l->mode = xstrdup(tok);
368 break;
369
370 case 4: /* PID */
3dc02ef4 371 /*
5697f991
MY
372 * If user passed a pid we filter it later when adding
373 * to the list, no need to worry now. OFD locks use -1 PID.
3dc02ef4 374 */
6c2c1b11
MY
375 if (oinfo) {
376 l->pid = oinfo->pid;
377 l->cmdname = xstrdup(oinfo->cmdname);
378 } else {
379 l->pid = strtos32_or_err(tok, _("failed to parse pid"));
380 if (l->pid > 0) {
381 l->cmdname = pid_get_cmdname(l->pid);
382 if (!l->cmdname) {
383 l->cmdname = NULL;
384 cmdname_unknown = true;
385 }
386 } else
387 l->cmdname = NULL;
388 }
5697f991
MY
389 break;
390
391 case 5: /* device major:minor and inode number */
392 l->inode = get_dev_inode(tok, &l->dev);
393 break;
394
395 case 6: /* start */
396 l->start = !strcmp(tok, "EOF") ? 0 :
397 strtou64_or_err(tok, _("failed to parse start"));
398 break;
399
400 case 7: /* end */
401 /* replace '\n' character */
402 tok[strlen(tok)-1] = '\0';
403 l->end = !strcmp(tok, "EOF") ? 0 :
404 strtou64_or_err(tok, _("failed to parse end"));
405 break;
406 default:
407 break;
f29bc6e1 408 }
5697f991 409 }
3dc02ef4 410
6c2c1b11
MY
411 if ((!l->blocked) && fallback && !l->cmdname)
412 patch_lock(l, fallback);
413 if (!l->cmdname) {
414 if (cmdname_unknown)
415 l->cmdname = xstrdup(_("(unknown)"));
416 else
417 l->cmdname = xstrdup(_("(undefined)"));
418 }
5697f991 419 l->path = get_filename_sz(l->inode, l->pid, &sz);
3dc02ef4 420
5697f991
MY
421 /* no permissions -- ignore */
422 if (!l->path && no_inaccessible) {
423 rem_lock(l);
424 return NULL;
425 }
426
427 if (!l->path) {
428 /* probably no permission to peek into l->pid's path */
429 l->path = get_fallback_filename(l->dev);
430 l->size = 0;
431 } else
432 l->size = sz;
433
434 return l;
435}
436
ec1d8306 437static int get_pid_lock(void *locks, void (*add_lock)(void *, struct lock *), FILE *fp,
b28481cd 438 pid_t pid, const char *cmdname, int fd)
6c2c1b11
MY
439{
440 char buf[PATH_MAX];
441 struct override_info oinfo = {
442 .pid = pid,
443 .cmdname = cmdname,
444 };
445
446 while (fgets(buf, sizeof(buf), fp)) {
447 struct lock *l;
448 if (strncmp(buf, "lock:\t", 6))
449 continue;
450 l = get_lock(buf + 6, &oinfo, NULL);
b28481cd 451 if (l) {
ec1d8306 452 add_lock(locks, l);
b28481cd
MY
453 l->fd = fd;
454 }
6c2c1b11
MY
455 /* no break here.
456 Multiple recode locks can be taken via one fd. */
457 }
458
459 return 0;
460}
461
ec1d8306 462static int get_pid_locks(void *locks, void (*add_lock)(void *, struct lock *), struct path_cxt *pc,
6c2c1b11
MY
463 pid_t pid, const char *cmdname)
464{
465 DIR *sub = NULL;
466 struct dirent *d = NULL;
467 int rc = 0;
468
469 while (ul_path_next_dirent(pc, &sub, "fdinfo", &d) == 0) {
470 uint64_t num;
471 FILE *fdinfo;
472
473 if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */
474 continue;
475
476 fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%ju", num);
477 if (fdinfo == NULL)
478 continue;
479
b28481cd 480 get_pid_lock(locks, add_lock, fdinfo, pid, cmdname, (int)num);
6c2c1b11
MY
481 fclose(fdinfo);
482 }
483
484 return rc;
485}
486
ec1d8306 487static int get_pids_locks(void *locks, void (*add_lock)(void *, struct lock *))
6c2c1b11
MY
488{
489 DIR *dir;
490 struct dirent *d;
491 struct path_cxt *pc = NULL;
492 int rc = 0;
493
494 pc = ul_new_path(NULL);
495 if (!pc)
496 err(EXIT_FAILURE, _("failed to alloc procfs handler"));
497
498 dir = opendir(_PATH_PROC);
499 if (!dir)
500 err(EXIT_FAILURE, _("failed to open /proc"));
501
502 while ((d = readdir(dir))) {
503 pid_t pid;
504 char buf[BUFSIZ];
505 const char *cmdname = NULL;
506
507 if (procfs_dirent_get_pid(d, &pid) != 0)
508 continue;
509
510 if (procfs_process_init_path(pc, pid) != 0) {
511 rc = -1;
512 break;
513 }
514
515 if (procfs_process_get_cmdname(pc, buf, sizeof(buf)) <= 0)
516 continue;
517 cmdname = buf;
518
ec1d8306 519 get_pid_locks(locks, add_lock, pc, pid, cmdname);
6c2c1b11
MY
520 }
521
522 closedir(dir);
523 ul_unref_path(pc);
524
525 return rc;
526}
527
751ca46e 528static int get_proc_locks(void *locks, void (*add_lock)(void *, struct lock *), void *fallback)
5697f991
MY
529{
530 FILE *fp;
531 char buf[PATH_MAX];
3dc02ef4 532
5697f991
MY
533 if (!(fp = fopen(_PATH_PROC_LOCKS, "r")))
534 return -1;
f29bc6e1 535
5697f991 536 while (fgets(buf, sizeof(buf), fp)) {
6c2c1b11 537 struct lock *l = get_lock(buf, NULL, fallback);
5697f991 538 if (l)
ec1d8306 539 add_lock(locks, l);
3dc02ef4
DB
540 }
541
542 fclose(fp);
543 return 0;
544}
545
546static int column_name_to_id(const char *name, size_t namesz)
547{
548 size_t i;
549
550 assert(name);
551
21abf83d 552 for (i = 0; i < ARRAY_SIZE(infos); i++) {
3dc02ef4
DB
553 const char *cn = infos[i].name;
554
555 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
556 return i;
557 }
558 warnx(_("unknown column: %s"), name);
559 return -1;
560}
561
562static inline int get_column_id(int num)
563{
40b17508
KZ
564 assert(num >= 0);
565 assert((size_t) num < ncolumns);
21abf83d 566 assert(columns[num] < (int) ARRAY_SIZE(infos));
3dc02ef4
DB
567
568 return columns[num];
569}
570
571
37b2b3fa 572static inline const struct colinfo *get_column_info(unsigned num)
3dc02ef4
DB
573{
574 return &infos[ get_column_id(num) ];
575}
576
7badb909
KZ
577static pid_t get_blocker(int id, struct list_head *locks)
578{
579 struct list_head *p;
580
581 list_for_each(p, locks) {
582 struct lock *l = list_entry(p, struct lock, locks);
583
584 if (l->id == id && !l->blocked)
585 return l->pid;
586 }
587
588 return 0;
589}
590
ba1bf716 591static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks)
3dc02ef4 592{
40b17508 593 size_t i;
ba1bf716 594 struct libscols_line *line;
3dc02ef4
DB
595 /*
596 * Whenever cmdname or filename is NULL it is most
597 * likely because there's no read permissions
598 * for the specified process.
599 */
600 const char *notfnd = "";
601
602 assert(l);
ba1bf716 603 assert(table);
3dc02ef4 604
ba1bf716 605 line = scols_table_new_line(table, NULL);
780ce22c
KZ
606 if (!line)
607 err(EXIT_FAILURE, _("failed to allocate output line"));
3dc02ef4
DB
608
609 for (i = 0; i < ncolumns; i++) {
610 char *str = NULL;
3dc02ef4
DB
611
612 switch (get_column_id(i)) {
613 case COL_SRC:
0d7ebfc4 614 xasprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd);
3dc02ef4
DB
615 break;
616 case COL_PID:
0d7ebfc4 617 xasprintf(&str, "%d", l->pid);
3dc02ef4 618 break;
74fa8244 619 case COL_TYPE:
0d7ebfc4 620 xasprintf(&str, "%s", l->type);
74fa8244 621 break;
c340ff05
KZ
622 case COL_INODE:
623 xasprintf(&str, "%ju", (uintmax_t) l->inode);
624 break;
625 case COL_MAJMIN:
626 if (json || raw)
627 xasprintf(&str, "%u:%u", major(l->dev), minor(l->dev));
628 else
629 xasprintf(&str, "%3u:%-3u", major(l->dev), minor(l->dev));
630 break;
3dc02ef4 631 case COL_SIZE:
51b0bcf0
KZ
632 if (!l->size)
633 break;
634 if (bytes)
635 xasprintf(&str, "%ju", l->size);
636 else
637 str = size_to_human_string(SIZE_SUFFIX_1LETTER, l->size);
3dc02ef4 638 break;
74fa8244 639 case COL_MODE:
55c0d16b 640 xasprintf(&str, "%s%s", l->mode, l->blocked ? "*" : "");
3dc02ef4
DB
641 break;
642 case COL_M:
55c0d16b 643 xasprintf(&str, "%d", l->mandatory ? 1 : 0);
3dc02ef4
DB
644 break;
645 case COL_START:
46d4ce56 646 xasprintf(&str, "%jd", l->start);
3dc02ef4
DB
647 break;
648 case COL_END:
46d4ce56 649 xasprintf(&str, "%jd", l->end);
3dc02ef4
DB
650 break;
651 case COL_PATH:
0d7ebfc4 652 xasprintf(&str, "%s", l->path ? l->path : notfnd);
3dc02ef4 653 break;
7badb909
KZ
654 case COL_BLOCKER:
655 {
656 pid_t bl = l->blocked && l->id ?
657 get_blocker(l->id, locks) : 0;
658 if (bl)
659 xasprintf(&str, "%d", (int) bl);
660 }
3dc02ef4
DB
661 default:
662 break;
663 }
664
6ceb17c8 665 if (str && scols_line_refer_data(line, i, str))
780ce22c 666 err(EXIT_FAILURE, _("failed to add output data"));
3dc02ef4
DB
667 }
668}
669
53ce6e70
MY
670static void rem_locks(struct list_head *locks)
671{
672 struct list_head *p, *pnext;
673
674 /* destroy the list */
675 list_for_each_safe(p, pnext, locks) {
676 struct lock *l = list_entry(p, struct lock, locks);
677 rem_lock(l);
678 }
679}
680
751ca46e
MY
681static void rem_tnode(void *node)
682{
683 struct lock_tnode *tnode = node;
684
685 rem_locks(&tnode->chain);
686 free(node);
687}
688
8199879e 689static int show_locks(struct list_head *locks, pid_t target_pid)
3dc02ef4 690{
40b17508
KZ
691 int rc = 0;
692 size_t i;
53ce6e70 693 struct list_head *p;
ba1bf716 694 struct libscols_table *table;
3dc02ef4 695
0925a9dd 696 table = scols_new_table();
780ce22c
KZ
697 if (!table)
698 err(EXIT_FAILURE, _("failed to allocate output table"));
699
0925a9dd 700 scols_table_enable_raw(table, raw);
b3a2e889 701 scols_table_enable_json(table, json);
0925a9dd 702 scols_table_enable_noheadings(table, no_headings);
3dc02ef4 703
b3a2e889
KZ
704 if (json)
705 scols_table_set_name(table, "locks");
706
3dc02ef4 707 for (i = 0; i < ncolumns; i++) {
fffdff1e 708 struct libscols_column *cl;
37b2b3fa 709 const struct colinfo *col = get_column_info(i);
3dc02ef4 710
fffdff1e
KZ
711 cl = scols_table_new_column(table, col->name, col->whint, col->flags);
712 if (!cl)
780ce22c 713 err(EXIT_FAILURE, _("failed to allocate output column"));
fffdff1e
KZ
714
715 if (json) {
716 int id = get_column_id(i);
717
718 switch (id) {
719 case COL_SIZE:
720 if (!bytes)
721 break;
722 /* fallthrough */
723 case COL_PID:
724 case COL_START:
725 case COL_END:
726 case COL_BLOCKER:
c340ff05 727 case COL_INODE:
fffdff1e
KZ
728 scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
729 break;
730 case COL_M:
731 scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN);
732 break;
733 default:
734 scols_column_set_json_type(cl, SCOLS_JSON_STRING);
735 break;
736 }
737 }
738
3dc02ef4
DB
739 }
740
7badb909
KZ
741 /* prepare data for output */
742 list_for_each(p, locks) {
743 struct lock *l = list_entry(p, struct lock, locks);
744
8199879e 745 if (target_pid && target_pid != l->pid)
7badb909
KZ
746 continue;
747
ba1bf716 748 add_scols_line(table, l, locks);
7badb909
KZ
749 }
750
ba1bf716 751 scols_print_table(table);
ba1bf716 752 scols_unref_table(table);
3dc02ef4
DB
753 return rc;
754}
755
756
86be6a32 757static void __attribute__((__noreturn__)) usage(void)
3dc02ef4 758{
86be6a32 759 FILE *out = stdout;
3dc02ef4
DB
760 size_t i;
761
762 fputs(USAGE_HEADER, out);
763
764 fprintf(out,
765 _(" %s [options]\n"), program_invocation_short_name);
766
451dbcfa
BS
767 fputs(USAGE_SEPARATOR, out);
768 fputs(_("List local system locks.\n"), out);
769
e9b46759 770 fputs(USAGE_OPTIONS, out);
51b0bcf0 771 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out);
315be52f 772 fputs(_(" -J, --json use JSON output format\n"), out);
f29bc6e1 773 fputs(_(" -i, --noinaccessible ignore locks without read permissions\n"), out);
315be52f
BS
774 fputs(_(" -n, --noheadings don't print headings\n"), out);
775 fputs(_(" -o, --output <list> define which output columns to use\n"), out);
25d3c405 776 fputs(_(" --output-all output all columns\n"), out);
315be52f
BS
777 fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out);
778 fputs(_(" -r, --raw use the raw output format\n"), out);
779 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
780
781 fputs(USAGE_SEPARATOR, out);
bad4c729 782 fprintf(out, USAGE_HELP_OPTIONS(24));
3dc02ef4 783
c3a4cfc5 784 fputs(USAGE_COLUMNS, out);
3dc02ef4 785
21abf83d 786 for (i = 0; i < ARRAY_SIZE(infos); i++)
3dc02ef4
DB
787 fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
788
bad4c729 789 fprintf(out, USAGE_MAN_TAIL("lslocks(8)"));
3dc02ef4 790
86be6a32 791 exit(EXIT_SUCCESS);
3dc02ef4
DB
792}
793
794int main(int argc, char *argv[])
795{
ba1bf716 796 int c, rc = 0;
751ca46e
MY
797 struct list_head proc_locks;
798 void *pid_locks = NULL;
e68948e1 799 char *outarg = NULL;
25d3c405
SK
800 enum {
801 OPT_OUTPUT_ALL = CHAR_MAX + 1
802 };
3dc02ef4 803 static const struct option long_opts[] = {
51b0bcf0 804 { "bytes", no_argument, NULL, 'b' },
b3a2e889 805 { "json", no_argument, NULL, 'J' },
3dc02ef4
DB
806 { "pid", required_argument, NULL, 'p' },
807 { "help", no_argument, NULL, 'h' },
808 { "output", required_argument, NULL, 'o' },
25d3c405 809 { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
c4137d39 810 { "notruncate", no_argument, NULL, 'u' },
3dc02ef4
DB
811 { "version", no_argument, NULL, 'V' },
812 { "noheadings", no_argument, NULL, 'n' },
813 { "raw", no_argument, NULL, 'r' },
f29bc6e1 814 { "noinaccessible", no_argument, NULL, 'i' },
3dc02ef4
DB
815 { NULL, 0, NULL, 0 }
816 };
817
b3a2e889
KZ
818 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
819 { 'J','r' },
820 { 0 }
821 };
822 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
8199879e
MY
823 pid_t target_pid = 0;
824
3dc02ef4
DB
825 setlocale(LC_ALL, "");
826 bindtextdomain(PACKAGE, LOCALEDIR);
827 textdomain(PACKAGE);
2c308875 828 close_stdout_atexit();
3dc02ef4
DB
829
830 while ((c = getopt_long(argc, argv,
51b0bcf0 831 "biJp:o:nruhV", long_opts, NULL)) != -1) {
b3a2e889
KZ
832
833 err_exclusive_options(c, long_opts, excl, excl_st);
3dc02ef4
DB
834
835 switch(c) {
51b0bcf0
KZ
836 case 'b':
837 bytes = 1;
838 break;
f29bc6e1
KZ
839 case 'i':
840 no_inaccessible = 1;
841 break;
b3a2e889
KZ
842 case 'J':
843 json = 1;
844 break;
3dc02ef4 845 case 'p':
8199879e 846 target_pid = strtos32_or_err(optarg, _("invalid PID argument"));
3dc02ef4
DB
847 break;
848 case 'o':
e68948e1 849 outarg = optarg;
3dc02ef4 850 break;
25d3c405
SK
851 case OPT_OUTPUT_ALL:
852 for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
853 columns[ncolumns] = ncolumns;
854 break;
3dc02ef4 855 case 'n':
ba1bf716 856 no_headings = 1;
3dc02ef4
DB
857 break;
858 case 'r':
ba1bf716 859 raw = 1;
3dc02ef4 860 break;
c4137d39
KZ
861 case 'u':
862 disable_columns_truncate();
863 break;
2c308875
KZ
864
865 case 'V':
866 print_version(EXIT_SUCCESS);
867 case 'h':
868 usage();
3dc02ef4 869 default:
677ec86c 870 errtryhelp(EXIT_FAILURE);
3dc02ef4
DB
871 }
872 }
873
7b26824c 874 INIT_LIST_HEAD(&proc_locks);
3dc02ef4
DB
875
876 if (!ncolumns) {
877 /* default columns */
878 columns[ncolumns++] = COL_SRC;
879 columns[ncolumns++] = COL_PID;
74fa8244 880 columns[ncolumns++] = COL_TYPE;
3dc02ef4 881 columns[ncolumns++] = COL_SIZE;
74fa8244 882 columns[ncolumns++] = COL_MODE;
3dc02ef4
DB
883 columns[ncolumns++] = COL_M;
884 columns[ncolumns++] = COL_START;
885 columns[ncolumns++] = COL_END;
886 columns[ncolumns++] = COL_PATH;
887 }
888
e68948e1
KZ
889 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
890 &ncolumns, column_name_to_id) < 0)
891 return EXIT_FAILURE;
892
710ed55d
KZ
893 scols_init_debug(0);
894
6c2c1b11
MY
895 /* get_pids_locks() get locks related information from "lock:" fields
896 * of /proc/$pid/fdinfo/$fd as fallback information.
897 * get_proc_locks() used the fallback information if /proc/locks
898 * doesn't provides enough information or provides staled information. */
751ca46e 899 get_pids_locks(&pid_locks, add_to_tree);
ec1d8306 900 rc = get_proc_locks(&proc_locks, add_to_list, &pid_locks);
3dc02ef4 901
7b26824c
MY
902 if (!rc && !list_empty(&proc_locks))
903 rc = show_locks(&proc_locks, target_pid);
3dc02ef4 904
751ca46e 905 tdestroy(pid_locks, rem_tnode);
53ce6e70
MY
906 rem_locks(&proc_locks);
907
50fccba1 908 mnt_unref_table(tab);
3dc02ef4
DB
909 return rc;
910}