]>
Commit | Line | Data |
---|---|---|
2a4c734b MB |
1 | /* |
2 | * lsblk(1) - list block devices | |
3 | * | |
4 | * Copyright (C) 2010 Red Hat, Inc. All rights reserved. | |
5 | * Written by Milan Broz <mbroz@redhat.com> | |
6 | * Karel Zak <kzak@redhat.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it would be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software Foundation, | |
20 | * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | */ | |
22 | ||
23 | #include <stdio.h> | |
24 | #include <errno.h> | |
25 | #include <getopt.h> | |
26 | #include <stdio.h> | |
27 | #include <stdlib.h> | |
28 | #include <unistd.h> | |
29 | #include <stdint.h> | |
30 | #include <sys/types.h> | |
31 | #include <sys/stat.h> | |
32 | #include <dirent.h> | |
33 | #include <fcntl.h> | |
34 | #include <string.h> | |
35 | #include <err.h> | |
36 | #include <sys/ioctl.h> | |
37 | #include <inttypes.h> | |
38 | #include <stdarg.h> | |
39 | #include <locale.h> | |
40 | #include <pwd.h> | |
41 | #include <grp.h> | |
42 | ||
43 | #include <blkid.h> | |
44 | ||
45 | #include <assert.h> | |
46 | ||
47 | #include "pathnames.h" | |
48 | #include "blkdev.h" | |
49 | #include "canonicalize.h" | |
50 | #include "ismounted.h" | |
51 | #include "nls.h" | |
52 | #include "tt.h" | |
53 | #include "xalloc.h" | |
54 | #include "strutils.h" | |
55 | ||
56 | /* column IDs */ | |
57 | enum { | |
58 | COL_NAME = 0, | |
59 | COL_KNAME, | |
60 | COL_MAJMIN, | |
61 | COL_FSTYPE, | |
62 | COL_TARGET, | |
63 | COL_LABEL, | |
64 | COL_UUID, | |
65 | COL_RO, | |
66 | COL_RA, | |
67 | COL_MODEL, | |
68 | COL_SIZE, | |
69 | COL_OWNER, | |
70 | COL_GROUP, | |
71 | COL_MODE, | |
72 | COL_ALIOFF, | |
73 | COL_MINIO, | |
74 | COL_OPTIO, | |
75 | COL_PHYSEC, | |
76 | COL_LOGSEC, | |
77 | COL_ROTA, | |
78 | COL_SCHED, | |
79 | ||
80 | __NCOLUMNS | |
81 | }; | |
82 | ||
83 | /* column names */ | |
84 | struct colinfo { | |
85 | const char *name; /* header */ | |
86 | double whint; /* width hint (N < 1 is in percent of termwidth) */ | |
87 | int flags; /* TT_FL_* */ | |
88 | const char *help; | |
89 | }; | |
90 | ||
91 | /* columns descriptions */ | |
92 | static struct colinfo infos[__NCOLUMNS] = { | |
93 | [COL_NAME] = { "NAME", 0.25, TT_FL_TREE, N_("device name") }, | |
94 | [COL_KNAME] = { "KNAME", 0.3, 0, N_("internel kernel device name") }, | |
95 | [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") }, | |
96 | [COL_FSTYPE] = { "FSTYPE", 0.1, TT_FL_TRUNC, N_("filesystem type") }, | |
97 | [COL_TARGET] = { "MOUNTPOINT", 0.10, TT_FL_TRUNC, N_("where the device is mounted") }, | |
98 | [COL_LABEL] = { "LABEL", 0.1, 0, N_("filesystem LABEL") }, | |
99 | [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") }, | |
100 | [COL_RO] = { "RO", 1, TT_FL_RIGHT, N_("read-only device") }, | |
101 | [COL_RA] = { "RA", 1, TT_FL_RIGHT, N_("removable device") }, | |
102 | [COL_ROTA] = { "ROTA", 1, TT_FL_RIGHT, N_("rotational device") }, | |
103 | [COL_MODEL] = { "MODEL", 0.1, TT_FL_TRUNC, N_("device identifier") }, | |
104 | [COL_SIZE] = { "SIZE", 6, TT_FL_RIGHT, N_("size of the device") }, | |
105 | [COL_OWNER] = { "OWNER", 0.1, TT_FL_TRUNC, N_("user name"), }, | |
106 | [COL_GROUP] = { "GROUP", 0.1, TT_FL_TRUNC, N_("group name") }, | |
107 | [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") }, | |
108 | [COL_ALIOFF] = { "ALIGNMENT", 6, TT_FL_RIGHT, N_("alignment offset") }, | |
109 | [COL_MINIO] = { "MIN-IO", 6, TT_FL_RIGHT, N_("minimum I/O size") }, | |
110 | [COL_OPTIO] = { "OPT-IO", 6, TT_FL_RIGHT, N_("optimal I/O size") }, | |
111 | [COL_PHYSEC] = { "PHY-SEC", 7, TT_FL_RIGHT, N_("physical sector size") }, | |
112 | [COL_LOGSEC] = { "LOG-SEC", 7, TT_FL_RIGHT, N_("logical sector size") }, | |
113 | [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") } | |
114 | ||
115 | }; | |
116 | ||
117 | struct lsblk { | |
118 | struct tt *tt; /* output table */ | |
119 | int all_devices:1; /* print all devices */ | |
120 | int bytes:1; /* print SIZE in bytes */ | |
121 | }; | |
122 | ||
123 | struct lsblk *lsblk; /* global handler */ | |
124 | int columns[__NCOLUMNS];/* enabled columns */ | |
125 | int ncolumns; /* number of enabled columns */ | |
126 | ||
127 | unsigned int excludes[256]; | |
128 | int nexcludes; | |
129 | ||
130 | struct blkdev_cxt { | |
131 | struct blkdev_cxt *parent; | |
132 | ||
133 | struct tt_line *tt_line; | |
134 | struct stat st; | |
135 | ||
136 | char *name; /* kernel name in /sys/block */ | |
137 | char *dm_name; /* DM name (dm/block) */ | |
138 | ||
139 | char *filename; /* path to device node */ | |
140 | int sysfs_fd; /* O_RDONLY file desciptor to /sys/block/<dev> */ | |
141 | ||
142 | int partition; /* is partition? TRUE/FALSE */ | |
143 | ||
144 | int probed; /* already probed */ | |
145 | char *fstype; /* detected fs, NULL or "?" if cannot detect */ | |
146 | char *uuid; /* UUID of device / filesystem */ | |
147 | char *label; /* FS label */ | |
148 | ||
149 | int nholders; /* # of devices mapped directly to this device | |
150 | * /sys/block/.../holders + number of partition */ | |
151 | int nslaves; /* # of devices this device maps to */ | |
152 | int maj, min; /* devno */ | |
153 | ||
154 | uint64_t size; /* device size */ | |
155 | }; | |
156 | ||
157 | static int is_maj_excluded(int maj) | |
158 | { | |
159 | int i; | |
160 | ||
161 | assert(ARRAY_SIZE(excludes) > nexcludes); | |
162 | ||
163 | for (i = 0; i < nexcludes; i++) | |
164 | if (excludes[i] == maj) | |
165 | return 1; | |
166 | return 0; | |
167 | } | |
168 | ||
169 | ||
170 | /* array with IDs of enabled columns */ | |
171 | static int get_column_id(int num) | |
172 | { | |
173 | assert(ARRAY_SIZE(columns) == __NCOLUMNS); | |
174 | assert(num < ncolumns); | |
175 | assert(columns[num] < __NCOLUMNS); | |
176 | return columns[num]; | |
177 | } | |
178 | ||
179 | static struct colinfo *get_column_info(int num) | |
180 | { | |
181 | return &infos[ get_column_id(num) ]; | |
182 | } | |
183 | ||
184 | ||
185 | static int column_name_to_id(const char *name, size_t namesz) | |
186 | { | |
187 | int i; | |
188 | ||
189 | for (i = 0; i < __NCOLUMNS; i++) { | |
190 | const char *cn = infos[i].name; | |
191 | ||
192 | if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
193 | return i; | |
194 | } | |
195 | warnx(_("unknown column: %s"), name); | |
196 | return -1; | |
197 | } | |
198 | ||
199 | static void reset_blkdev_cxt(struct blkdev_cxt *cxt) | |
200 | { | |
201 | if (!cxt) | |
202 | return; | |
203 | free(cxt->name); | |
204 | free(cxt->dm_name); | |
205 | free(cxt->filename); | |
206 | free(cxt->fstype); | |
207 | free(cxt->uuid); | |
208 | free(cxt->label); | |
209 | ||
210 | if (cxt->sysfs_fd >= 0) | |
211 | close(cxt->sysfs_fd); | |
212 | ||
213 | memset(cxt, 0, sizeof(*cxt)); | |
214 | } | |
215 | ||
216 | static int is_dm(const char *name) | |
217 | { | |
218 | return strncmp(name, "dm-", 3) ? 0 : 1; | |
219 | } | |
220 | ||
221 | static struct dirent *xreaddir(DIR *dp) | |
222 | { | |
223 | struct dirent *d; | |
224 | ||
225 | assert(dp); | |
226 | ||
227 | while ((d = readdir(dp))) { | |
228 | if (!strcmp(d->d_name, ".") || | |
229 | !strcmp(d->d_name, "..")) | |
230 | continue; | |
231 | ||
232 | /* blacklist here? */ | |
233 | break; | |
234 | } | |
235 | return d; | |
236 | } | |
237 | ||
238 | /* | |
239 | * returns exponent (2^x=n) in range KiB..PiB | |
240 | */ | |
241 | static int get_exp(uint64_t n) | |
242 | { | |
243 | int shft; | |
244 | ||
245 | for (shft = 10; shft <= 60; shft += 10) { | |
246 | if (n < (1ULL << shft)) | |
247 | break; | |
248 | } | |
249 | return shft - 10; | |
250 | } | |
251 | ||
252 | static char *size_to_human_string(uint64_t bytes) | |
253 | { | |
254 | char buf[32]; | |
255 | int dec, frac, exp; | |
256 | const char *letters = "BKMGTP"; | |
257 | char c; | |
258 | ||
259 | exp = get_exp(bytes); | |
260 | c = *(letters + (exp ? exp / 10 : 0)); | |
261 | dec = exp ? bytes / (1ULL << exp) : bytes; | |
262 | frac = exp ? bytes % (1ULL << exp) : 0; | |
263 | ||
264 | if (frac) { | |
265 | /* round */ | |
266 | frac = (frac / (1ULL << (exp - 10)) + 50) / 100; | |
267 | if (frac == 10) | |
268 | dec++, frac = 0; | |
269 | } | |
270 | ||
271 | if (frac) { | |
272 | struct lconv const *l = localeconv(); | |
273 | char *dp = l ? l->decimal_point : NULL; | |
274 | ||
275 | if (!dp || !*dp) | |
276 | dp = "."; | |
277 | snprintf(buf, sizeof(buf), "%d%s%d%c", dec, dp, frac, c); | |
278 | } else | |
279 | snprintf(buf, sizeof(buf), "%d%c", dec, c); | |
280 | ||
281 | return xstrdup(buf); | |
282 | } | |
283 | ||
284 | ||
285 | static int is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) | |
286 | { | |
287 | char path[256]; | |
288 | ||
289 | assert(dir); | |
290 | assert(d); | |
291 | ||
292 | #ifdef _DIRENT_HAVE_D_TYPE | |
293 | if (d->d_type != DT_DIR) | |
294 | return 0; | |
295 | #endif | |
296 | if (strncmp(parent_name, d->d_name, strlen(parent_name))) | |
297 | return 0; | |
298 | ||
299 | /* Cannot use /partition file, not supported on old sysfs */ | |
300 | snprintf(path, sizeof(path), "%s/start", d->d_name); | |
301 | ||
302 | return faccessat(dirfd(dir), path, R_OK, 0) == 0; | |
303 | } | |
304 | ||
305 | static char *get_device_path(struct blkdev_cxt *cxt) | |
306 | { | |
307 | char path[PATH_MAX]; | |
308 | ||
309 | assert(cxt); | |
310 | assert(cxt->name); | |
311 | ||
312 | if (is_dm(cxt->name)) | |
313 | return canonicalize_dm_name(cxt->name); | |
314 | ||
315 | snprintf(path, sizeof(path), "/dev/%s", cxt->name); | |
316 | return xstrdup(path); | |
317 | } | |
318 | ||
319 | static char *get_sysfs_path(struct blkdev_cxt *cxt) | |
320 | { | |
321 | char path[PATH_MAX]; | |
322 | ||
323 | assert(cxt); | |
324 | assert(cxt->name); | |
325 | ||
326 | if (cxt->partition && cxt->parent) | |
327 | snprintf(path, sizeof(path), _PATH_SYS_BLOCK "/%s/%s", | |
328 | cxt->parent->name, cxt->name); | |
329 | else | |
330 | snprintf(path, sizeof(path), _PATH_SYS_BLOCK "/%s", cxt->name); | |
331 | ||
332 | return xstrdup(path); | |
333 | } | |
334 | ||
335 | static int sysfs_open(struct blkdev_cxt *cxt, const char *attr) | |
336 | { | |
337 | int fd; | |
338 | ||
339 | assert(cxt); | |
340 | assert(cxt->sysfs_fd >= 0); | |
341 | ||
342 | fd = openat(cxt->sysfs_fd, attr, O_RDONLY); | |
343 | if (fd == -1 && errno == ENOENT && !strncmp(attr, "queue/", 6) && cxt->parent) { | |
344 | fd = openat(cxt->parent->sysfs_fd, attr, O_RDONLY); | |
345 | } | |
346 | return fd; | |
347 | } | |
348 | ||
349 | static FILE *sysfs_fopen(struct blkdev_cxt *cxt, const char *attr) | |
350 | { | |
351 | int fd = sysfs_open(cxt, attr); | |
352 | ||
353 | return fd < 0 ? NULL : fdopen(fd, "r"); | |
354 | } | |
355 | ||
356 | static DIR *sysfs_opendir(struct blkdev_cxt *cxt, const char *attr) | |
357 | { | |
358 | DIR *dir; | |
359 | int fd; | |
360 | ||
361 | if (attr) | |
362 | fd = sysfs_open(cxt, attr); | |
363 | else { | |
364 | /* request to open root of device in sysfs (/sys/block/<dev>) | |
365 | * -- we cannot use cxt->sysfs_fd directly, because closedir() | |
366 | * will close this our persistent file descriptor. | |
367 | */ | |
368 | assert(cxt); | |
369 | assert(cxt->sysfs_fd >= 0); | |
370 | ||
371 | fd = dup(cxt->sysfs_fd); | |
372 | } | |
373 | ||
374 | if (fd < 0) | |
375 | return NULL; | |
376 | dir = fdopendir(fd); | |
377 | if (!dir) { | |
378 | close(fd); | |
379 | return NULL; | |
380 | } | |
381 | if (!attr) | |
382 | rewinddir(dir); | |
383 | return dir; | |
384 | } | |
385 | ||
386 | static __attribute__ ((format (scanf, 3, 4))) | |
387 | int sysfs_scanf(struct blkdev_cxt *cxt, const char *attr, const char *fmt, ...) | |
388 | { | |
389 | FILE *f = sysfs_fopen(cxt, attr); | |
390 | va_list ap; | |
391 | int rc; | |
392 | ||
393 | if (!f) | |
394 | return -EINVAL; | |
395 | va_start(ap, fmt); | |
396 | rc = vfscanf(f, fmt, ap); | |
397 | va_end(ap); | |
398 | ||
399 | fclose(f); | |
400 | return rc; | |
401 | } | |
402 | ||
403 | static uint64_t sysfs_read_u64(struct blkdev_cxt *cxt, const char *attr) | |
404 | { | |
405 | uint64_t x; | |
406 | return sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1 ? x : 0; | |
407 | } | |
408 | ||
409 | static char *sysfs_strdup(struct blkdev_cxt *cxt, const char *attr) | |
410 | { | |
411 | char buf[1024]; | |
412 | return sysfs_scanf(cxt, attr, "%1024[^\n]", buf) == 1 ? | |
413 | xstrdup(buf) : NULL; | |
414 | } | |
415 | ||
416 | static int sysfs_count_dirents(struct blkdev_cxt *cxt, const char *attr) | |
417 | { | |
418 | DIR *dir; | |
419 | int r = 0; | |
420 | ||
421 | if (!(dir = sysfs_opendir(cxt, attr))) | |
422 | return 0; | |
423 | ||
424 | while (xreaddir(dir)) r++; | |
425 | ||
426 | closedir(dir); | |
427 | return r; | |
428 | } | |
429 | ||
430 | static int sysfs_count_partitions(struct blkdev_cxt *cxt) | |
431 | { | |
432 | DIR *dir; | |
433 | struct dirent *d; | |
434 | int r = 0; | |
435 | ||
436 | if (!(dir = sysfs_opendir(cxt, NULL))) | |
437 | return 0; | |
438 | ||
439 | while ((d = xreaddir(dir))) { | |
440 | if (is_partition_dirent(dir, d, cxt->name)) | |
441 | r++; | |
442 | } | |
443 | ||
444 | closedir(dir); | |
445 | return r; | |
446 | } | |
447 | ||
448 | static char *get_device_mountpoint(struct blkdev_cxt *cxt) | |
449 | { | |
450 | int fl = 0; | |
451 | char mnt[PATH_MAX]; | |
452 | ||
453 | *mnt = '\0'; | |
454 | ||
455 | /* | |
456 | * TODO: use libmount and parse /proc/mountinfo only once | |
457 | */ | |
458 | if (check_mount_point(cxt->filename, &fl, mnt, sizeof(mnt)) == 0 && | |
459 | (fl & MF_MOUNTED)) { | |
460 | if (fl & MF_SWAP) | |
461 | strcpy(mnt, "[SWAP]"); | |
462 | } | |
463 | return strlen(mnt) ? xstrdup(mnt) : NULL; | |
464 | } | |
465 | ||
466 | /* TODO: read info from udev db (if possible) for non-root users | |
467 | */ | |
468 | static void probe_device(struct blkdev_cxt *cxt) | |
469 | { | |
470 | char *path = NULL; | |
471 | blkid_probe pr = NULL; | |
472 | ||
473 | if (cxt->probed) | |
474 | return; | |
475 | cxt->probed = 1; | |
476 | ||
477 | if (!cxt->size) | |
478 | return; | |
479 | ||
480 | pr = blkid_new_probe_from_filename(cxt->filename); | |
481 | if (!pr) | |
482 | return; | |
483 | ||
484 | /* TODO: we have to enable partitions probing to avoid conflicts | |
485 | * between raids and PT -- see blkid(8) code for more details | |
486 | */ | |
487 | blkid_probe_enable_superblocks(pr, 1); | |
488 | blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_LABEL | | |
489 | BLKID_SUBLKS_UUID | | |
490 | BLKID_SUBLKS_TYPE); | |
491 | if (!blkid_do_safeprobe(pr)) { | |
492 | const char *data = NULL; | |
493 | ||
494 | if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) | |
495 | cxt->fstype = xstrdup(data); | |
496 | if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL)) | |
497 | cxt->uuid = xstrdup(data); | |
498 | if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) | |
499 | cxt->label = xstrdup(data); | |
500 | } | |
501 | ||
502 | free(path); | |
503 | blkid_free_probe(pr); | |
504 | return; | |
505 | } | |
506 | ||
507 | static int is_readonly_device(struct blkdev_cxt *cxt) | |
508 | { | |
509 | int fd, ro = 0; | |
510 | ||
511 | if (sysfs_scanf(cxt, "ro", "%d", &ro) == 0) | |
512 | return ro; | |
513 | ||
514 | /* fallback if "ro" attribute does not exist */ | |
515 | fd = open(cxt->filename, O_RDONLY); | |
516 | if (fd != -1) { | |
517 | ioctl(fd, BLKROGET, &ro); | |
518 | close(fd); | |
519 | } | |
520 | return ro; | |
521 | } | |
522 | ||
523 | static char *get_scheduler(struct blkdev_cxt *cxt) | |
524 | { | |
525 | char *str = sysfs_strdup(cxt, "queue/scheduler"); | |
526 | char *p, *res = NULL; | |
527 | ||
528 | if (!str) | |
529 | return NULL; | |
530 | p = strchr(str, '['); | |
531 | if (p) { | |
532 | res = p + 1; | |
533 | p = strchr(res, ']'); | |
534 | if (p) { | |
535 | *p = '\0'; | |
536 | res = xstrdup(res); | |
537 | } else | |
538 | res = NULL; | |
539 | } | |
540 | free(str); | |
541 | return res; | |
542 | } | |
543 | ||
544 | static void set_tt_data(struct blkdev_cxt *cxt, int col, int id, struct tt_line *ln) | |
545 | { | |
546 | char buf[1024]; | |
547 | char *p; | |
548 | ||
549 | if (!cxt->st.st_rdev && (id == COL_OWNER || id == COL_GROUP || | |
550 | id == COL_MODE)) | |
551 | stat(cxt->filename, &cxt->st); | |
552 | ||
553 | switch(id) { | |
554 | case COL_NAME: | |
555 | if (cxt->dm_name) { | |
556 | snprintf(buf, sizeof(buf), "%s (%s)", | |
557 | cxt->dm_name, cxt->name); | |
558 | tt_line_set_data(ln, col, xstrdup(buf)); | |
559 | break; | |
560 | } | |
561 | case COL_KNAME: | |
562 | tt_line_set_data(ln, col, xstrdup(cxt->name)); | |
563 | break; | |
564 | case COL_OWNER: | |
565 | { | |
566 | struct passwd *pw = getpwuid(cxt->st.st_uid); | |
567 | if (pw) | |
568 | tt_line_set_data(ln, col, xstrdup(pw->pw_name)); | |
569 | break; | |
570 | } | |
571 | case COL_GROUP: | |
572 | { | |
573 | struct group *gr = getgrgid(cxt->st.st_gid); | |
574 | if (gr) | |
575 | tt_line_set_data(ln, col, xstrdup(gr->gr_name)); | |
576 | break; | |
577 | } | |
578 | case COL_MODE: | |
579 | { | |
580 | char md[11]; | |
581 | strmode(cxt->st.st_mode, md); | |
582 | tt_line_set_data(ln, col, xstrdup(md)); | |
583 | break; | |
584 | } | |
585 | case COL_MAJMIN: | |
586 | if (lsblk->tt->flags & TT_FL_RAW) | |
587 | snprintf(buf, sizeof(buf), "%u:%u", cxt->maj, cxt->min); | |
588 | else | |
589 | snprintf(buf, sizeof(buf), "%3u:%-3u", cxt->maj, cxt->min); | |
590 | tt_line_set_data(ln, col, xstrdup(buf)); | |
591 | break; | |
592 | case COL_FSTYPE: | |
593 | probe_device(cxt); | |
594 | if (cxt->fstype) | |
595 | tt_line_set_data(ln, col, xstrdup(cxt->fstype)); | |
596 | break; | |
597 | case COL_TARGET: | |
598 | if (!cxt->nholders) { | |
599 | p = get_device_mountpoint(cxt); | |
600 | if (p) | |
601 | tt_line_set_data(ln, col, p); | |
602 | } | |
603 | break; | |
604 | case COL_LABEL: | |
605 | probe_device(cxt); | |
606 | if (cxt->label) | |
607 | tt_line_set_data(ln, col, xstrdup(cxt->label)); | |
608 | break; | |
609 | case COL_UUID: | |
610 | probe_device(cxt); | |
611 | if (cxt->uuid) | |
612 | tt_line_set_data(ln, col, xstrdup(cxt->uuid)); | |
613 | break; | |
614 | case COL_RO: | |
615 | tt_line_set_data(ln, col, is_readonly_device(cxt) ? | |
616 | xstrdup("1") : xstrdup("0")); | |
617 | break; | |
618 | case COL_RA: | |
619 | p = sysfs_strdup(cxt, "removable"); | |
620 | if (!p && cxt->parent) | |
621 | p = sysfs_strdup(cxt->parent, "removable"); | |
622 | if (p) | |
623 | tt_line_set_data(ln, col, p); | |
624 | break; | |
625 | case COL_ROTA: | |
626 | p = sysfs_strdup(cxt, "queue/rotational"); | |
627 | if (p) | |
628 | tt_line_set_data(ln, col, p); | |
629 | break; | |
630 | case COL_MODEL: | |
631 | if (!cxt->partition && cxt->nslaves == 0) { | |
632 | p = sysfs_strdup(cxt, "device/model"); | |
633 | if (p) | |
634 | tt_line_set_data(ln, col, p); | |
635 | } | |
636 | break; | |
637 | case COL_SIZE: | |
638 | if (cxt->size) { | |
639 | if (lsblk->bytes) { | |
640 | if (asprintf(&p, "%jd", cxt->size) < 0) | |
641 | p = NULL; | |
642 | } else | |
643 | p = size_to_human_string(cxt->size); | |
644 | if (p) | |
645 | tt_line_set_data(ln, col, p); | |
646 | } | |
647 | break; | |
648 | case COL_ALIOFF: | |
649 | p = sysfs_strdup(cxt, "alignment_offset"); | |
650 | if (p) | |
651 | tt_line_set_data(ln, col, p); | |
652 | break; | |
653 | case COL_MINIO: | |
654 | p = sysfs_strdup(cxt, "queue/minimum_io_size"); | |
655 | if (p) | |
656 | tt_line_set_data(ln, col, p); | |
657 | break; | |
658 | case COL_OPTIO: | |
659 | p = sysfs_strdup(cxt, "queue/optimal_io_size"); | |
660 | if (p) | |
661 | tt_line_set_data(ln, col, p); | |
662 | break; | |
663 | case COL_PHYSEC: | |
664 | p = sysfs_strdup(cxt, "queue/physical_block_size"); | |
665 | if (p) | |
666 | tt_line_set_data(ln, col, p); | |
667 | break; | |
668 | case COL_LOGSEC: | |
669 | p = sysfs_strdup(cxt, "queue/logical_block_size"); | |
670 | if (p) | |
671 | tt_line_set_data(ln, col, p); | |
672 | break; | |
673 | case COL_SCHED: | |
674 | p = get_scheduler(cxt); | |
675 | if (p) | |
676 | tt_line_set_data(ln, col, p); | |
677 | break; | |
678 | }; | |
679 | } | |
680 | ||
681 | static void print_device(struct blkdev_cxt *cxt, struct tt_line *tt_parent) | |
682 | { | |
683 | int i; | |
684 | ||
685 | cxt->tt_line = tt_add_line(lsblk->tt, tt_parent); | |
686 | ||
687 | for (i = 0; i < ncolumns; i++) | |
688 | set_tt_data(cxt, i, get_column_id(i), cxt->tt_line); | |
689 | } | |
690 | ||
691 | static int set_cxt(struct blkdev_cxt *cxt, | |
692 | struct blkdev_cxt *parent, | |
693 | const char *name, | |
694 | int partition) | |
695 | { | |
696 | char *p; | |
697 | ||
698 | cxt->parent = parent; | |
699 | cxt->name = xstrdup(name); | |
700 | cxt->partition = partition; | |
701 | ||
702 | cxt->filename = get_device_path(cxt); | |
703 | ||
704 | /* open /sys/block/<name> */ | |
705 | p = get_sysfs_path(cxt); | |
706 | cxt->sysfs_fd = open(p, O_RDONLY); | |
707 | if (cxt->sysfs_fd < 0) | |
708 | err(EXIT_FAILURE, _("%s: open failed"), p); | |
709 | free(p); | |
710 | ||
711 | if (sysfs_scanf(cxt, "dev", "%u:%u", &cxt->maj, &cxt->min) != 2) | |
712 | return -1; | |
713 | ||
714 | cxt->size = sysfs_read_u64(cxt, "size") << 9; | |
715 | ||
716 | /* Ignore devices of zero size */ | |
717 | if (!lsblk->all_devices && cxt->size == 0) | |
718 | return -1; | |
719 | ||
720 | if (is_dm(name)) | |
721 | cxt->dm_name = sysfs_strdup(cxt, "dm/name"); | |
722 | ||
723 | cxt->nholders = sysfs_count_dirents(cxt, "holders") + | |
724 | sysfs_count_partitions(cxt); | |
725 | cxt->nslaves = sysfs_count_dirents(cxt, "slaves"); | |
726 | ||
727 | return 0; | |
728 | } | |
729 | ||
730 | /* | |
731 | * List devices (holders) mapped to device | |
732 | */ | |
733 | static int list_holders(struct blkdev_cxt *cxt) | |
734 | { | |
735 | DIR *dir; | |
736 | struct dirent *d; | |
737 | struct blkdev_cxt holder = {}; | |
738 | ||
739 | assert(cxt); | |
740 | assert(cxt->sysfs_fd >= 0); | |
741 | ||
742 | if (!cxt->nholders) | |
743 | return 0; | |
744 | ||
745 | /* Partitions */ | |
746 | dir = sysfs_opendir(cxt, NULL); | |
747 | if (!dir) | |
748 | err(EXIT_FAILURE, _("failed to open device directory in sysfs")); | |
749 | ||
750 | while ((d = xreaddir(dir))) { | |
751 | if (!is_partition_dirent(dir, d, cxt->name)) | |
752 | continue; | |
753 | ||
754 | set_cxt(&holder, cxt, d->d_name, 1); | |
755 | print_device(&holder, cxt->tt_line); | |
756 | list_holders(&holder); | |
757 | reset_blkdev_cxt(&holder); | |
758 | } | |
759 | closedir(dir); | |
760 | ||
761 | /* Holders */ | |
762 | dir = sysfs_opendir(cxt, "holders"); | |
763 | if (!dir) | |
764 | return 0; | |
765 | ||
766 | while ((d = xreaddir(dir))) { | |
767 | set_cxt(&holder, cxt, d->d_name, 0); | |
768 | print_device(&holder, cxt->tt_line); | |
769 | list_holders(&holder); | |
770 | reset_blkdev_cxt(&holder); | |
771 | } | |
772 | closedir(dir); | |
773 | ||
774 | return 0; | |
775 | } | |
776 | ||
777 | /* Iterate top-level devices in sysfs */ | |
778 | static int iterate_block_devices(void) | |
779 | { | |
780 | DIR *dir; | |
781 | struct dirent *d; | |
782 | struct blkdev_cxt cxt = {}; | |
783 | ||
784 | if (!(dir = opendir(_PATH_SYS_BLOCK))) | |
785 | return EXIT_FAILURE; | |
786 | ||
787 | while ((d = xreaddir(dir))) { | |
788 | ||
789 | if (set_cxt(&cxt, NULL, d->d_name, 0)) | |
790 | goto next; | |
791 | ||
792 | /* Skip devices in the middle of dependence tree */ | |
793 | if (cxt.nslaves > 0) | |
794 | goto next; | |
795 | ||
796 | if (!lsblk->all_devices && is_maj_excluded(cxt.maj)) | |
797 | goto next; | |
798 | ||
799 | print_device(&cxt, NULL); | |
800 | list_holders(&cxt); | |
801 | next: | |
802 | reset_blkdev_cxt(&cxt); | |
803 | } | |
804 | ||
805 | closedir(dir); | |
806 | ||
807 | return EXIT_SUCCESS; | |
808 | } | |
809 | ||
810 | static int process_one_device(char *devname) | |
811 | { | |
812 | struct blkdev_cxt parent = {}, cxt = {}; | |
813 | struct stat st; | |
814 | char buf[PATH_MAX]; | |
815 | dev_t disk = 0; | |
816 | ||
817 | if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { | |
818 | warnx(_("%s: not a block device"), devname); | |
819 | return EXIT_FAILURE; | |
820 | } | |
821 | if (blkid_devno_to_wholedisk(st.st_rdev, buf, sizeof(buf), &disk)) { | |
822 | warn(_("%s: failed to get whole-list devno"), devname); | |
823 | return EXIT_FAILURE; | |
824 | } | |
825 | if (st.st_rdev == disk) | |
826 | /* | |
827 | * unpartitioned device | |
828 | */ | |
829 | set_cxt(&cxt, NULL, buf, 0); | |
830 | else { | |
831 | /* | |
832 | * Parititioned, read sysfs name of the device | |
833 | */ | |
834 | size_t len; | |
835 | char path[PATH_MAX], *diskname, *name; | |
836 | ||
837 | snprintf(path, sizeof(path), "/sys/dev/block/%d:%d", | |
838 | major(st.st_rdev), minor(st.st_rdev)); | |
839 | diskname = xstrdup(buf); | |
840 | ||
841 | len = readlink(path, buf, sizeof(buf)); | |
842 | if (len < 0) { | |
843 | warn(_("%s: failed to read link"), path); | |
844 | return EXIT_FAILURE; | |
845 | } | |
846 | buf[len] = '\0'; | |
847 | ||
848 | /* sysfs device name */ | |
849 | name = strrchr(buf, '/') + 1; | |
850 | ||
851 | set_cxt(&parent, NULL, diskname, 0); | |
852 | set_cxt(&cxt, &parent, name, 1); | |
853 | ||
854 | free(diskname); | |
855 | } | |
856 | ||
857 | print_device(&cxt, NULL); | |
858 | list_holders(&cxt); | |
859 | reset_blkdev_cxt(&cxt); | |
860 | ||
861 | if (st.st_rdev != disk) | |
862 | reset_blkdev_cxt(&parent); | |
863 | ||
864 | return EXIT_SUCCESS; | |
865 | } | |
866 | ||
867 | static void parse_excludes(const char *str) | |
868 | { | |
869 | nexcludes = 0; | |
870 | ||
871 | while (str && *str) { | |
872 | char *end = NULL; | |
873 | unsigned int n; | |
874 | ||
875 | errno = 0; | |
876 | n = strtoul(str, &end, 10); | |
877 | ||
878 | if (end == str || (errno != 0 && (n == ULONG_MAX || n == 0))) | |
879 | err(EXIT_FAILURE, _("failed to parse list '%s'"), str); | |
880 | excludes[nexcludes++] = n; | |
881 | ||
882 | if (nexcludes == ARRAY_SIZE(excludes)) | |
883 | errx(EXIT_FAILURE, _("the list of excluded devices is " | |
884 | "too large (limit is %d devices)"), | |
885 | (int)ARRAY_SIZE(excludes)); | |
886 | str = end && *end ? end + 1 : NULL; | |
887 | } | |
888 | } | |
889 | ||
890 | static int __attribute__((__noreturn__)) help(FILE *out) | |
891 | { | |
892 | int i; | |
893 | ||
894 | fprintf(out, _( | |
895 | "\nUsage:\n" | |
896 | " %s [options] [<device> ...]\n"), program_invocation_short_name); | |
897 | ||
898 | fprintf(out, _( | |
899 | "\nOptions:\n" | |
900 | " -a, --all print all devices\n" | |
901 | " -b, --bytes print SIZE in bytes rather than in human readable format\n" | |
902 | " -e, --exclude <list> exclude devices by major number (default: RAM disks)\n" | |
903 | " -f, --fs output info about filesystems\n" | |
904 | " -h, --help usage information (this)\n" | |
905 | " -i, --ascii use ascii characters only\n" | |
906 | " -m, --perms output info about permissions\n" | |
907 | " -l, --list use list format ouput\n" | |
908 | " -n, --noheadings don't print headings\n" | |
909 | " -o, --output <list> output columns\n" | |
910 | " -r, --raw use raw format output\n" | |
911 | " -t, --topology output info about topology\n")); | |
912 | ||
913 | fprintf(out, _("\nAvailable columns:\n")); | |
914 | ||
915 | for (i = 0; i < __NCOLUMNS; i++) | |
916 | fprintf(out, " %10s %s\n", infos[i].name, gettext(infos[i].help)); | |
917 | ||
918 | fprintf(out, _("\nFor more information see lsblk(1).\n")); | |
919 | ||
920 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); | |
921 | } | |
922 | ||
923 | static int __attribute__((__noreturn__)) | |
924 | errx_mutually_exclusive(const char *opts) | |
925 | { | |
926 | errx(EXIT_FAILURE, "%s %s", opts, _("options are mutually exclusive")); | |
927 | } | |
928 | ||
929 | int main(int argc, char *argv[]) | |
930 | { | |
931 | struct lsblk _ls; | |
932 | int tt_flags = TT_FL_TREE; | |
933 | int i, c, status = EXIT_FAILURE; | |
934 | ||
935 | struct option longopts[] = { | |
936 | { "all", 0, 0, 'a' }, | |
937 | { "help", 0, 0, 'h' }, | |
938 | { "output", 1, 0, 'o' }, | |
939 | { "perms", 0, 0, 'm' }, | |
940 | { "noheadings", 0, 0, 'n' }, | |
941 | { "list", 0, 0, 'l' }, | |
942 | { "ascii", 0, 0, 'i' }, | |
943 | { "raw", 0, 0, 'r' }, | |
944 | { "fs", 0, 0, 'f' }, | |
945 | { "exclude", 1, 0, 'e' }, | |
946 | { "topology", 0, 0, 't' }, | |
947 | { NULL, 0, 0, 0 }, | |
948 | }; | |
949 | ||
950 | setlocale(LC_ALL, ""); | |
951 | bindtextdomain(PACKAGE, LOCALEDIR); | |
952 | textdomain(PACKAGE); | |
953 | ||
954 | lsblk = &_ls; | |
955 | memset(lsblk, 0, sizeof(*lsblk)); | |
956 | ||
957 | while((c = getopt_long(argc, argv, "abe:fhlnmo:irt", longopts, NULL)) != -1) { | |
958 | switch(c) { | |
959 | case 'a': | |
960 | lsblk->all_devices = 1; | |
961 | break; | |
962 | case 'b': | |
963 | lsblk->bytes = 1; | |
964 | break; | |
965 | case 'e': | |
966 | parse_excludes(optarg); | |
967 | break; | |
968 | case 'h': | |
969 | help(stdout); | |
970 | break; | |
971 | case 'l': | |
972 | if (tt_flags & TT_FL_RAW) | |
973 | errx_mutually_exclusive("--{raw,list}"); | |
974 | ||
975 | tt_flags &= ~TT_FL_TREE; /* disable the default */ | |
976 | break; | |
977 | case 'n': | |
978 | tt_flags |= TT_FL_NOHEADINGS; | |
979 | break; | |
980 | case 'o': | |
981 | if (tt_parse_columns_list(optarg, columns, &ncolumns, | |
982 | column_name_to_id)) | |
983 | return EXIT_FAILURE; | |
984 | break; | |
985 | case 'i': | |
986 | tt_flags = TT_FL_ASCII; | |
987 | break; | |
988 | case 'r': | |
989 | tt_flags &= ~TT_FL_TREE; /* disable the default */ | |
990 | tt_flags |= TT_FL_RAW; /* enable raw */ | |
991 | break; | |
992 | case 'f': | |
993 | columns[ncolumns++] = COL_NAME; | |
994 | columns[ncolumns++] = COL_FSTYPE; | |
995 | columns[ncolumns++] = COL_LABEL; | |
996 | columns[ncolumns++] = COL_TARGET; | |
997 | break; | |
998 | case 'm': | |
999 | columns[ncolumns++] = COL_NAME; | |
1000 | columns[ncolumns++] = COL_SIZE; | |
1001 | columns[ncolumns++] = COL_OWNER; | |
1002 | columns[ncolumns++] = COL_GROUP; | |
1003 | columns[ncolumns++] = COL_MODE; | |
1004 | break; | |
1005 | case 't': | |
1006 | columns[ncolumns++] = COL_NAME; | |
1007 | columns[ncolumns++] = COL_ALIOFF; | |
1008 | columns[ncolumns++] = COL_MINIO; | |
1009 | columns[ncolumns++] = COL_OPTIO; | |
1010 | columns[ncolumns++] = COL_PHYSEC; | |
1011 | columns[ncolumns++] = COL_LOGSEC; | |
1012 | columns[ncolumns++] = COL_ROTA; | |
1013 | columns[ncolumns++] = COL_SCHED; | |
1014 | break; | |
1015 | default: | |
1016 | help(stderr); | |
1017 | } | |
1018 | } | |
1019 | ||
1020 | if (!ncolumns) { | |
1021 | columns[ncolumns++] = COL_NAME; | |
1022 | columns[ncolumns++] = COL_MAJMIN; | |
1023 | columns[ncolumns++] = COL_RA; | |
1024 | columns[ncolumns++] = COL_SIZE; | |
1025 | columns[ncolumns++] = COL_RO; | |
1026 | columns[ncolumns++] = COL_TARGET; | |
1027 | } | |
1028 | ||
1029 | if (nexcludes && lsblk->all_devices) | |
1030 | errx_mutually_exclusive("--{all,exclude}"); | |
1031 | else if (!nexcludes) | |
1032 | excludes[nexcludes++] = 1; /* default: ignore RAM disks */ | |
1033 | /* | |
1034 | * initialize output columns | |
1035 | */ | |
1036 | if (!(lsblk->tt = tt_new_table(tt_flags))) | |
1037 | errx(EXIT_FAILURE, _("failed to initialize output table")); | |
1038 | ||
1039 | for (i = 0; i < ncolumns; i++) { | |
1040 | struct colinfo *ci = get_column_info(i); | |
1041 | int fl = ci->flags; | |
1042 | ||
1043 | if (!(tt_flags & TT_FL_TREE) && get_column_id(i) == COL_NAME) | |
1044 | fl &= ~TT_FL_TREE; | |
1045 | ||
1046 | if (!tt_define_column(lsblk->tt, ci->name, ci->whint, fl)) { | |
1047 | warn(_("failed to initialize output column")); | |
1048 | goto leave; | |
1049 | } | |
1050 | } | |
1051 | ||
1052 | if (optind == argc) | |
1053 | status = iterate_block_devices(); | |
1054 | else while (optind < argc) | |
1055 | status = process_one_device(argv[optind++]); | |
1056 | ||
1057 | tt_print_table(lsblk->tt); | |
1058 | ||
1059 | leave: | |
1060 | tt_free_table(lsblk->tt); | |
1061 | return status; | |
1062 | } |