]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/lslocks.c
a47c1d0addc161dafb943e1b1b9dce6da2abeaaf
[thirdparty/util-linux.git] / misc-utils / lslocks.c
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>
7 * Since it stopped being maingained over a decade ago, this
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>
34
35 #include <libmount.h>
36 #include <libsmartcols.h>
37
38 #include "pathnames.h"
39 #include "canonicalize.h"
40 #include "nls.h"
41 #include "xalloc.h"
42 #include "strutils.h"
43 #include "c.h"
44 #include "list.h"
45 #include "closestream.h"
46 #include "optutils.h"
47 #include "procutils.h"
48
49 /* column IDs */
50 enum {
51 COL_SRC = 0,
52 COL_PID,
53 COL_TYPE,
54 COL_SIZE,
55 COL_MODE,
56 COL_M,
57 COL_START,
58 COL_END,
59 COL_PATH,
60 COL_BLOCKER
61 };
62
63 /* column names */
64 struct colinfo {
65 const char *name; /* header */
66 double whint; /* width hint (N < 1 is in percent of termwidth) */
67 int flags; /* SCOLS_FL_* */
68 const char *help;
69 };
70
71 /* columns descriptions */
72 static struct colinfo infos[] = {
73 [COL_SRC] = { "COMMAND",15, 0, N_("command of the process holding the lock") },
74 [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("PID of the process holding the lock") },
75 [COL_TYPE] = { "TYPE", 5, SCOLS_FL_RIGHT, N_("kind of lock: FL_FLOCK or FL_POSIX.") },
76 [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, N_("size of the lock") },
77 [COL_MODE] = { "MODE", 5, 0, N_("lock access mode") },
78 [COL_M] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")},
79 [COL_START] = { "START", 10, SCOLS_FL_RIGHT, N_("relative byte offset of the lock")},
80 [COL_END] = { "END", 10, SCOLS_FL_RIGHT, N_("ending offset of the lock")},
81 [COL_PATH] = { "PATH", 0, SCOLS_FL_TRUNC, N_("path of the locked file")},
82 [COL_BLOCKER] = { "BLOCKER", 0, SCOLS_FL_RIGHT, N_("PID of the process blocking the lock") }
83 };
84
85 static int columns[ARRAY_SIZE(infos) * 2];
86 static size_t ncolumns;
87
88 static pid_t pid = 0;
89
90 static struct libmnt_table *tab; /* /proc/self/mountinfo */
91
92 /* basic output flags */
93 static int no_headings;
94 static int raw;
95 static int json;
96
97 struct lock {
98 struct list_head locks;
99
100 char *cmdname;
101 pid_t pid;
102 char *path;
103 char *type;
104 char *mode;
105 off_t start;
106 off_t end;
107 unsigned int mandatory :1,
108 blocked :1;
109 char *size;
110 int id;
111 };
112
113 static void disable_columns_truncate(void)
114 {
115 size_t i;
116
117 for (i = 0; i < ARRAY_SIZE(infos); i++)
118 infos[i].flags &= ~SCOLS_FL_TRUNC;
119 }
120
121 /*
122 * Associate the device's mountpoint for a filename
123 */
124 static char *get_fallback_filename(dev_t dev)
125 {
126 struct libmnt_fs *fs;
127
128 if (!tab) {
129 tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
130 if (!tab)
131 return NULL;
132 }
133
134 fs = mnt_table_find_devno(tab, dev, MNT_ITER_BACKWARD);
135 if (!fs)
136 return NULL;
137
138 return xstrdup(mnt_fs_get_target(fs));
139 }
140
141 /*
142 * Return the absolute path of a file from
143 * a given inode number (and its size)
144 */
145 static char *get_filename_sz(ino_t inode, pid_t lock_pid, size_t *size)
146 {
147 struct stat sb;
148 struct dirent *dp;
149 DIR *dirp;
150 size_t len;
151 int fd;
152 char path[PATH_MAX], sym[PATH_MAX], *ret = NULL;
153
154 *size = 0;
155 memset(path, 0, sizeof(path));
156 memset(sym, 0, sizeof(sym));
157
158 /*
159 * We know the pid so we don't have to
160 * iterate the *entire* filesystem searching
161 * for the damn file.
162 */
163 sprintf(path, "/proc/%d/fd/", lock_pid);
164 if (!(dirp = opendir(path)))
165 return NULL;
166
167 if ((len = strlen(path)) >= (sizeof(path) - 2))
168 goto out;
169
170 if ((fd = dirfd(dirp)) < 0 )
171 goto out;
172
173 while ((dp = readdir(dirp))) {
174 if (!strcmp(dp->d_name, ".") ||
175 !strcmp(dp->d_name, ".."))
176 continue;
177
178 /* care only for numerical descriptors */
179 if (!strtol(dp->d_name, (char **) NULL, 10))
180 continue;
181
182 if (!fstatat(fd, dp->d_name, &sb, 0)
183 && inode != sb.st_ino)
184 continue;
185
186 if ((len = readlinkat(fd, dp->d_name, sym, sizeof(sym) - 1)) < 1)
187 goto out;
188
189 *size = sb.st_size;
190 sym[len] = '\0';
191
192 ret = xstrdup(sym);
193 break;
194 }
195 out:
196 closedir(dirp);
197 return ret;
198 }
199
200 /*
201 * Return the inode number from a string
202 */
203 static ino_t get_dev_inode(char *str, dev_t *dev)
204 {
205 unsigned int maj = 0, min = 0;
206 ino_t inum = 0;
207
208 sscanf(str, "%02x:%02x:%ju", &maj, &min, &inum);
209
210 *dev = (dev_t) makedev(maj, min);
211 return inum;
212 }
213
214 static int get_local_locks(struct list_head *locks)
215 {
216 int i;
217 ino_t inode = 0;
218 FILE *fp;
219 char buf[PATH_MAX], *szstr = NULL, *tok = NULL;
220 size_t sz;
221 struct lock *l;
222 dev_t dev = 0;
223
224 if (!(fp = fopen(_PATH_PROC_LOCKS, "r")))
225 return -1;
226
227 while (fgets(buf, sizeof(buf), fp)) {
228
229 l = xcalloc(1, sizeof(*l));
230 INIT_LIST_HEAD(&l->locks);
231
232 for (tok = strtok(buf, " "), i = 0; tok;
233 tok = strtok(NULL, " "), i++) {
234
235 /*
236 * /proc/locks has *exactly* 8 "blocks" of text
237 * separated by ' ' - check <kernel>/fs/locks.c
238 */
239 switch (i) {
240 case 0: /* ID: */
241 tok[strlen(tok) - 1] = '\0';
242 l->id = strtos32_or_err(tok, _("failed to parse ID"));
243 break;
244 case 1: /* posix, flock, etc */
245 if (strcmp(tok, "->") == 0) { /* optional field */
246 l->blocked = 1;
247 i--;
248 } else
249 l->type = xstrdup(tok);
250 break;
251
252 case 2: /* is this a mandatory lock? other values are advisory or noinode */
253 l->mandatory = *tok == 'M' ? 1 : 0;
254 break;
255 case 3: /* lock mode */
256 l->mode = xstrdup(tok);
257 break;
258
259 case 4: /* PID */
260 /*
261 * If user passed a pid we filter it later when adding
262 * to the list, no need to worry now.
263 */
264 l->pid = strtos32_or_err(tok, _("failed to parse pid"));
265 l->cmdname = proc_get_command_name(l->pid);
266 if (!l->cmdname)
267 l->cmdname = xstrdup(_("(unknown)"));
268 break;
269
270 case 5: /* device major:minor and inode number */
271 inode = get_dev_inode(tok, &dev);
272 break;
273
274 case 6: /* start */
275 l->start = !strcmp(tok, "EOF") ? 0 :
276 strtou64_or_err(tok, _("failed to parse start"));
277 break;
278
279 case 7: /* end */
280 /* replace '\n' character */
281 tok[strlen(tok)-1] = '\0';
282 l->end = !strcmp(tok, "EOF") ? 0 :
283 strtou64_or_err(tok, _("failed to parse end"));
284 break;
285 default:
286 break;
287 }
288
289 l->path = get_filename_sz(inode, l->pid, &sz);
290 if (!l->path)
291 /* probably no permission to peek into l->pid's path */
292 l->path = get_fallback_filename(dev);
293
294 /* avoid leaking */
295 szstr = size_to_human_string(SIZE_SUFFIX_1LETTER, sz);
296 l->size = xstrdup(szstr);
297 free(szstr);
298 }
299
300 list_add(&l->locks, locks);
301 }
302
303 fclose(fp);
304 return 0;
305 }
306
307 static int column_name_to_id(const char *name, size_t namesz)
308 {
309 size_t i;
310
311 assert(name);
312
313 for (i = 0; i < ARRAY_SIZE(infos); i++) {
314 const char *cn = infos[i].name;
315
316 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
317 return i;
318 }
319 warnx(_("unknown column: %s"), name);
320 return -1;
321 }
322
323 static inline int get_column_id(int num)
324 {
325 assert(num >= 0);
326 assert((size_t) num < ncolumns);
327 assert(columns[num] < (int) ARRAY_SIZE(infos));
328
329 return columns[num];
330 }
331
332
333 static inline struct colinfo *get_column_info(unsigned num)
334 {
335 return &infos[ get_column_id(num) ];
336 }
337
338 static void rem_lock(struct lock *lock)
339 {
340 if (!lock)
341 return;
342
343 free(lock->path);
344 free(lock->size);
345 free(lock->mode);
346 free(lock->cmdname);
347 free(lock->type);
348 list_del(&lock->locks);
349 free(lock);
350 }
351
352 static pid_t get_blocker(int id, struct list_head *locks)
353 {
354 struct list_head *p;
355
356 list_for_each(p, locks) {
357 struct lock *l = list_entry(p, struct lock, locks);
358
359 if (l->id == id && !l->blocked)
360 return l->pid;
361 }
362
363 return 0;
364 }
365
366 static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks)
367 {
368 size_t i;
369 struct libscols_line *line;
370 /*
371 * Whenever cmdname or filename is NULL it is most
372 * likely because there's no read permissions
373 * for the specified process.
374 */
375 const char *notfnd = "";
376
377 assert(l);
378 assert(table);
379
380 line = scols_table_new_line(table, NULL);
381 if (!line) {
382 warn(_("failed to add line to output"));
383 return;
384 }
385
386 for (i = 0; i < ncolumns; i++) {
387 char *str = NULL;
388
389 switch (get_column_id(i)) {
390 case COL_SRC:
391 xasprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd);
392 break;
393 case COL_PID:
394 xasprintf(&str, "%d", l->pid);
395 break;
396 case COL_TYPE:
397 xasprintf(&str, "%s", l->type);
398 break;
399 case COL_SIZE:
400 xasprintf(&str, "%s", l->size);
401 break;
402 case COL_MODE:
403 xasprintf(&str, "%s%s", l->mode, l->blocked ? "*" : "");
404 break;
405 case COL_M:
406 xasprintf(&str, "%d", l->mandatory ? 1 : 0);
407 break;
408 case COL_START:
409 xasprintf(&str, "%jd", l->start);
410 break;
411 case COL_END:
412 xasprintf(&str, "%jd", l->end);
413 break;
414 case COL_PATH:
415 xasprintf(&str, "%s", l->path ? l->path : notfnd);
416 break;
417 case COL_BLOCKER:
418 {
419 pid_t bl = l->blocked && l->id ?
420 get_blocker(l->id, locks) : 0;
421 if (bl)
422 xasprintf(&str, "%d", (int) bl);
423 }
424 default:
425 break;
426 }
427
428 if (str)
429 scols_line_set_data(line, i, str);
430 }
431 }
432
433 static int show_locks(struct list_head *locks)
434 {
435 int rc = 0;
436 size_t i;
437 struct list_head *p, *pnext;
438 struct libscols_table *table;
439
440 table = scols_new_table();
441 if (!table) {
442 warn(_("failed to initialize output table"));
443 return -1;
444 }
445 scols_table_enable_raw(table, raw);
446 scols_table_enable_json(table, json);
447 scols_table_enable_noheadings(table, no_headings);
448
449 if (json)
450 scols_table_set_name(table, "locks");
451
452 for (i = 0; i < ncolumns; i++) {
453 struct colinfo *col = get_column_info(i);
454
455 if (!scols_table_new_column(table, col->name, col->whint, col->flags)) {
456 warnx(_("failed to initialize output column"));
457 rc = -1;
458 goto done;
459 }
460 }
461
462 /* prepare data for output */
463 list_for_each(p, locks) {
464 struct lock *l = list_entry(p, struct lock, locks);
465
466 if (pid && pid != l->pid)
467 continue;
468
469 add_scols_line(table, l, locks);
470 }
471
472 /* destroy the list */
473 list_for_each_safe(p, pnext, locks) {
474 struct lock *l = list_entry(p, struct lock, locks);
475 rem_lock(l);
476 }
477
478 scols_print_table(table);
479 done:
480 scols_unref_table(table);
481 return rc;
482 }
483
484
485 static void __attribute__ ((__noreturn__)) usage(FILE * out)
486 {
487 size_t i;
488
489 fputs(USAGE_HEADER, out);
490
491 fprintf(out,
492 _(" %s [options]\n"), program_invocation_short_name);
493
494 fputs(USAGE_SEPARATOR, out);
495 fputs(_("List local system locks.\n"), out);
496
497 fputs(USAGE_OPTIONS, out);
498 fputs(_(" -J, --json use JSON output format\n"), out);
499 fputs(_(" -n, --noheadings don't print headings\n"), out);
500 fputs(_(" -o, --output <list> define which output columns to use\n"), out);
501 fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out);
502 fputs(_(" -r, --raw use the raw output format\n"), out);
503 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
504
505 fputs(USAGE_SEPARATOR, out);
506 fputs(USAGE_HELP, out);
507 fputs(USAGE_VERSION, out);
508
509 fputs(_("\nAvailable columns (for --output):\n"), out);
510
511 for (i = 0; i < ARRAY_SIZE(infos); i++)
512 fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
513
514 fprintf(out, USAGE_MAN_TAIL("lslocks(8)"));
515
516 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
517 }
518
519 int main(int argc, char *argv[])
520 {
521 int c, rc = 0;
522 struct list_head locks;
523 char *outarg = NULL;
524 static const struct option long_opts[] = {
525 { "json", no_argument, NULL, 'J' },
526 { "pid", required_argument, NULL, 'p' },
527 { "help", no_argument, NULL, 'h' },
528 { "output", required_argument, NULL, 'o' },
529 { "notruncate", no_argument, NULL, 'u' },
530 { "version", no_argument, NULL, 'V' },
531 { "noheadings", no_argument, NULL, 'n' },
532 { "raw", no_argument, NULL, 'r' },
533 { NULL, 0, NULL, 0 }
534 };
535
536 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
537 { 'J','r' },
538 { 0 }
539 };
540 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
541 setlocale(LC_ALL, "");
542 bindtextdomain(PACKAGE, LOCALEDIR);
543 textdomain(PACKAGE);
544 atexit(close_stdout);
545
546 while ((c = getopt_long(argc, argv,
547 "Jp:o:nruhV", long_opts, NULL)) != -1) {
548
549 err_exclusive_options(c, long_opts, excl, excl_st);
550
551 switch(c) {
552 case 'J':
553 json = 1;
554 break;
555 case 'p':
556 pid = strtos32_or_err(optarg, _("invalid PID argument"));
557 break;
558 case 'o':
559 outarg = optarg;
560 break;
561 case 'V':
562 printf(UTIL_LINUX_VERSION);
563 return EXIT_SUCCESS;
564 case 'h':
565 usage(stdout);
566 case 'n':
567 no_headings = 1;
568 break;
569 case 'r':
570 raw = 1;
571 break;
572 case 'u':
573 disable_columns_truncate();
574 break;
575 case '?':
576 default:
577 usage(stderr);
578 }
579 }
580
581 INIT_LIST_HEAD(&locks);
582
583 if (!ncolumns) {
584 /* default columns */
585 columns[ncolumns++] = COL_SRC;
586 columns[ncolumns++] = COL_PID;
587 columns[ncolumns++] = COL_TYPE;
588 columns[ncolumns++] = COL_SIZE;
589 columns[ncolumns++] = COL_MODE;
590 columns[ncolumns++] = COL_M;
591 columns[ncolumns++] = COL_START;
592 columns[ncolumns++] = COL_END;
593 columns[ncolumns++] = COL_PATH;
594 }
595
596 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
597 &ncolumns, column_name_to_id) < 0)
598 return EXIT_FAILURE;
599
600 scols_init_debug(0);
601
602 rc = get_local_locks(&locks);
603
604 if (!rc && !list_empty(&locks))
605 rc = show_locks(&locks);
606
607 mnt_unref_table(tab);
608 return rc;
609 }