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