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