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