]>
Commit | Line | Data |
---|---|---|
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 */ | |
48 | enum { | |
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 */ | |
61 | struct 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 | 69 | static 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) | |
81 | static int columns[NCOLS], ncolumns; | |
82 | static pid_t pid = 0; | |
3dc02ef4 | 83 | |
07c916cf KZ |
84 | static struct libmnt_table *tab; /* /proc/self/mountinfo */ |
85 | ||
3dc02ef4 DB |
86 | struct 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 |
100 | static 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 | 111 | static 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); | |
125 | out: | |
126 | fclose(fp); | |
127 | return ret; | |
128 | } | |
129 | ||
130 | /* | |
131 | * Associate the device's mountpoint for a filename | |
132 | */ | |
133 | static 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 | */ | |
154 | static 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 |
205 | out: |
206 | closedir(dirp); | |
207 | return ret; | |
208 | } | |
209 | ||
210 | /* | |
211 | * Return the inode number from a string | |
212 | */ | |
213 | static 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 | ||
224 | static 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 | ||
327 | static 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 | ||
343 | static 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 | ||
353 | static inline struct colinfo *get_column_info(unsigned num) | |
354 | { | |
355 | return &infos[ get_column_id(num) ]; | |
356 | } | |
357 | ||
358 | static 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 | ||
372 | static 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 | ||
432 | static 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); | |
461 | done: | |
462 | tt_free_table(tt); | |
463 | return rc; | |
464 | } | |
465 | ||
466 | ||
467 | static 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 | ||
495 | int 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 | } |