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