1 /*
2  * No copyright is claimed.  This code is in the public domain; do with
3  * it what you wish.
4  *
5  * Written by Karel Zak <kzak@redhat.com>
6  */
7 #include <ctype.h>
8 #include <libgen.h>
9 #include <fcntl.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12 
13 #include "c.h"
14 #include "pathnames.h"
15 #include "sysfs.h"
16 #include "fileutils.h"
17 #include "all-io.h"
18 #include "debug.h"
19 #include "strutils.h"
20 
21 static void sysfs_blkdev_deinit_path(struct path_cxt *pc);
22 static int  sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd);
23 
24 /*
25  * Debug stuff (based on include/debug.h)
26  */
27 static UL_DEBUG_DEFINE_MASK(ulsysfs);
28 UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES;
29 
30 #define ULSYSFS_DEBUG_INIT	(1 << 1)
31 #define ULSYSFS_DEBUG_CXT	(1 << 2)
32 
33 #define DBG(m, x)       __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x)
34 #define ON_DBG(m, x)    __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x)
35 
36 #define UL_DEBUG_CURRENT_MASK	UL_DEBUG_MASK(ulsysfs)
37 #include "debugobj.h"
38 
ul_sysfs_init_debug(void)39 void ul_sysfs_init_debug(void)
40 {
41 	if (ulsysfs_debug_mask)
42 		return;
43 	__UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG);
44 }
45 
ul_new_sysfs_path(dev_t devno,struct path_cxt * parent,const char * prefix)46 struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix)
47 {
48 	struct path_cxt *pc = ul_new_path(NULL);
49 
50 	if (!pc)
51 		return NULL;
52 	if (prefix)
53 		ul_path_set_prefix(pc, prefix);
54 
55 	if (sysfs_blkdev_init_path(pc, devno, parent) != 0) {
56 		ul_unref_path(pc);
57 		return NULL;
58 	}
59 
60 	DBG(CXT, ul_debugobj(pc, "alloc"));
61 	return pc;
62 }
63 
64 /*
65  * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices.
66  *
67  * The function is possible to call in loop and without sysfs_blkdev_deinit_path().
68  * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path().
69  *
70  */
sysfs_blkdev_init_path(struct path_cxt * pc,dev_t devno,struct path_cxt * parent)71 int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent)
72 {
73 	struct sysfs_blkdev *blk;
74 	int rc;
75 	char buf[sizeof(_PATH_SYS_DEVBLOCK)
76 		 + sizeof(stringify_value(UINT32_MAX)) * 2
77 		 + 3];
78 
79 	/* define path to devno stuff */
80 	snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno));
81 	rc = ul_path_set_dir(pc, buf);
82 	if (rc)
83 		return rc;
84 
85 	/* make sure path exists */
86 	rc = ul_path_get_dirfd(pc);
87 	if (rc < 0)
88 		return rc;
89 
90 	/* initialize sysfs blkdev specific stuff */
91 	blk = ul_path_get_dialect(pc);
92 	if (!blk) {
93 		DBG(CXT, ul_debugobj(pc, "alloc new sysfs handler"));
94 		blk = calloc(1, sizeof(struct sysfs_blkdev));
95 		if (!blk)
96 			return -ENOMEM;
97 
98 		ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path);
99 		ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect);
100 	}
101 
102 	DBG(CXT, ul_debugobj(pc, "init sysfs stuff"));
103 
104 	blk->devno = devno;
105 	sysfs_blkdev_set_parent(pc, parent);
106 
107 	return 0;
108 }
109 
sysfs_blkdev_deinit_path(struct path_cxt * pc)110 static void sysfs_blkdev_deinit_path(struct path_cxt *pc)
111 {
112 	struct sysfs_blkdev *blk;
113 
114 	if (!pc)
115 		return;
116 
117 	DBG(CXT, ul_debugobj(pc, "deinit"));
118 
119 	blk = ul_path_get_dialect(pc);
120 	if (!blk)
121 		return;
122 
123 	ul_unref_path(blk->parent);
124 	free(blk);
125 
126 	ul_path_set_dialect(pc, NULL, NULL);
127 }
128 
sysfs_blkdev_set_parent(struct path_cxt * pc,struct path_cxt * parent)129 int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent)
130 {
131 	struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
132 
133 	if (!pc || !blk)
134 		return -EINVAL;
135 
136 	if (blk->parent) {
137 		ul_unref_path(blk->parent);
138 		blk->parent = NULL;
139 	}
140 
141 	if (parent) {
142 		ul_ref_path(parent);
143 		blk->parent = parent;
144 	} else
145 		blk->parent = NULL;
146 
147 	DBG(CXT, ul_debugobj(pc, "new parent"));
148 	return 0;
149 }
150 
sysfs_blkdev_get_parent(struct path_cxt * pc)151 struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc)
152 {
153 	struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
154 	return blk ? blk->parent : NULL;
155 }
156 
157 /*
158  * Redirects ENOENT errors to the parent, if the path is to the queue/
159  * sysfs directory. For example
160  *
161  *	/sys/dev/block/8:1/queue/logical_block_size redirects to
162  *	/sys/dev/block/8:0/queue/logical_block_size
163  */
sysfs_blkdev_enoent_redirect(struct path_cxt * pc,const char * path,int * dirfd)164 static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd)
165 {
166 	struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
167 
168 	if (blk && blk->parent && strncmp(path, "queue/", 6) == 0) {
169 		*dirfd = ul_path_get_dirfd(blk->parent);
170 		if (*dirfd >= 0) {
171 			DBG(CXT, ul_debugobj(pc, "%s redirected to parent", path));
172 			return 0;
173 		}
174 	}
175 	return 1;	/* no redirect */
176 }
177 
sysfs_blkdev_get_name(struct path_cxt * pc,char * buf,size_t bufsiz)178 char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz)
179 {
180 	char link[PATH_MAX];
181 	char *name;
182 	ssize_t	sz;
183 
184         /* read /sys/dev/block/<maj:min> link */
185 	sz = ul_path_readlink(pc, link, sizeof(link) - 1, NULL);
186 	if (sz < 0)
187 		return NULL;
188 	link[sz] = '\0';
189 
190 	name = strrchr(link, '/');
191 	if (!name)
192 		return NULL;
193 
194 	name++;
195 	sz = strlen(name);
196 	if ((size_t) sz + 1 > bufsiz)
197 		return NULL;
198 
199 	memcpy(buf, name, sz + 1);
200 	sysfs_devname_sys_to_dev(buf);
201 	return buf;
202 }
203 
sysfs_blkdev_is_partition_dirent(DIR * dir,struct dirent * d,const char * parent_name)204 int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
205 {
206 	char path[NAME_MAX + 6 + 1];
207 
208 #ifdef _DIRENT_HAVE_D_TYPE
209 	if (d->d_type != DT_DIR &&
210 	    d->d_type != DT_LNK &&
211 	    d->d_type != DT_UNKNOWN)
212 		return 0;
213 #endif
214 	if (parent_name) {
215 		const char *p = parent_name;
216 		size_t len;
217 
218 		/* /dev/sda --> "sda" */
219 		if (*parent_name == '/') {
220 			p = strrchr(parent_name, '/');
221 			if (!p)
222 				return 0;
223 			p++;
224 		}
225 
226 		len = strlen(p);
227 		if (strlen(d->d_name) <= len)
228 			return 0;
229 
230 		/* partitions subdir name is
231 		 *	"<parent>[:digit:]" or "<parent>p[:digit:]"
232 		 */
233 		return strncmp(p, d->d_name, len) == 0 &&
234 		       ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
235 			|| isdigit(*(d->d_name + len)));
236 	}
237 
238 	/* Cannot use /partition file, not supported on old sysfs */
239 	snprintf(path, sizeof(path), "%s/start", d->d_name);
240 
241 	return faccessat(dirfd(dir), path, R_OK, 0) == 0;
242 }
243 
sysfs_blkdev_count_partitions(struct path_cxt * pc,const char * devname)244 int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname)
245 {
246 	DIR *dir;
247 	struct dirent *d;
248 	int r = 0;
249 
250 	dir = ul_path_opendir(pc, NULL);
251 	if (!dir)
252 		return 0;
253 
254 	while ((d = xreaddir(dir))) {
255 		if (sysfs_blkdev_is_partition_dirent(dir, d, devname))
256 			r++;
257 	}
258 
259 	closedir(dir);
260 	return r;
261 }
262 
263 /*
264  * Converts @partno (partition number) to devno of the partition.
265  * The @pc handles wholedisk device.
266  *
267  * Note that this code does not expect any special format of the
268  * partitions devnames.
269  */
sysfs_blkdev_partno_to_devno(struct path_cxt * pc,int partno)270 dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno)
271 {
272 	DIR *dir;
273 	struct dirent *d;
274 	dev_t devno = 0;
275 
276 	dir = ul_path_opendir(pc, NULL);
277 	if (!dir)
278 		return 0;
279 
280 	while ((d = xreaddir(dir))) {
281 		int n;
282 
283 		if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL))
284 			continue;
285 
286 		if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name))
287 			continue;
288 
289 		if (n == partno) {
290 			if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0)
291 				break;
292 		}
293 	}
294 
295 	closedir(dir);
296 	DBG(CXT, ul_debugobj(pc, "partno (%d) -> devno (%d)", (int) partno, (int) devno));
297 	return devno;
298 }
299 
300 
301 /*
302  * Returns slave name if there is only one slave, otherwise returns NULL.
303  * The result should be deallocated by free().
304  */
sysfs_blkdev_get_slave(struct path_cxt * pc)305 char *sysfs_blkdev_get_slave(struct path_cxt *pc)
306 {
307 	DIR *dir;
308 	struct dirent *d;
309 	char *name = NULL;
310 
311 	dir = ul_path_opendir(pc, "slaves");
312 	if (!dir)
313 		return NULL;
314 
315 	while ((d = xreaddir(dir))) {
316 		if (name)
317 			goto err;	/* more slaves */
318 		name = strdup(d->d_name);
319 	}
320 
321 	closedir(dir);
322 	return name;
323 err:
324 	free(name);
325 	closedir(dir);
326 	return NULL;
327 }
328 
329 
330 #define SUBSYSTEM_LINKNAME	"/subsystem"
331 
332 /*
333  * For example:
334  *
335  * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
336  *                           1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
337  *
338  * The function check if <chain>/subsystem symlink exists, if yes then returns
339  * basename of the readlink result, and remove the last subdirectory from the
340  * <chain> path.
341  */
get_subsystem(char * chain,char * buf,size_t bufsz)342 static char *get_subsystem(char *chain, char *buf, size_t bufsz)
343 {
344 	size_t len;
345 	char *p;
346 
347 	if (!chain || !*chain)
348 		return NULL;
349 
350 	len = strlen(chain);
351 	if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX)
352 		return NULL;
353 
354 	do {
355 		ssize_t sz;
356 
357 		/* append "/subsystem" to the path */
358 		memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME));
359 
360 		/* try if subsystem symlink exists */
361 		sz = readlink(chain, buf, bufsz - 1);
362 
363 		/* remove last subsystem from chain */
364 		chain[len] = '\0';
365 		p = strrchr(chain, '/');
366 		if (p) {
367 			*p = '\0';
368 			len = p - chain;
369 		}
370 
371 		if (sz > 0) {
372 			/* we found symlink to subsystem, return basename */
373 			buf[sz] = '\0';
374 			return basename(buf);
375 		}
376 
377 	} while (p);
378 
379 	return NULL;
380 }
381 
382 /*
383  * Returns complete path to the device, the patch contains all subsystems
384  * used for the device.
385  */
sysfs_blkdev_get_devchain(struct path_cxt * pc,char * buf,size_t bufsz)386 char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz)
387 {
388 	/* read /sys/dev/block/<maj>:<min> symlink */
389 	ssize_t sz = ul_path_readlink(pc, buf, bufsz, NULL);
390 	const char *prefix;
391 	size_t psz = 0;
392 
393 	if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz)
394 		return NULL;
395 
396 	buf[sz++] = '\0';
397 	prefix = ul_path_get_prefix(pc);
398 	if (prefix)
399 		psz = strlen(prefix);
400 
401 	/* create absolute patch from the link */
402 	memmove(buf + psz + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz);
403 	if (prefix)
404 		memcpy(buf, prefix, psz);
405 
406 	memcpy(buf + psz, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1);
407 	return buf;
408 }
409 
410 /*
411  * The @subsys returns the next subsystem in the chain. Function modifies
412  * @devchain string.
413  *
414  * Returns: 0 in success, <0 on error, 1 on end of chain
415  */
sysfs_blkdev_next_subsystem(struct path_cxt * pc,char * devchain,char ** subsys)416 int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)),
417 			 char *devchain, char **subsys)
418 {
419 	char subbuf[PATH_MAX];
420 	char *sub;
421 
422 	if (!subsys || !devchain)
423 		return -EINVAL;
424 
425 	*subsys = NULL;
426 
427 	while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) {
428 		*subsys = strdup(sub);
429 		if (!*subsys)
430 			return -ENOMEM;
431 		return 0;
432 	}
433 
434 	return 1;
435 }
436 
437 
is_hotpluggable_subsystem(const char * name)438 static int is_hotpluggable_subsystem(const char *name)
439 {
440 	static const char * const hotplug_subsystems[] = {
441 		"usb",
442 		"ieee1394",
443 		"pcmcia",
444 		"mmc",
445 		"ccw"
446 	};
447 	size_t i;
448 
449 	for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++)
450 		if (strcmp(name, hotplug_subsystems[i]) == 0)
451 			return 1;
452 
453 	return 0;
454 }
455 
sysfs_blkdev_is_hotpluggable(struct path_cxt * pc)456 int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc)
457 {
458 	char buf[PATH_MAX], *chain, *sub;
459 	int rc = 0;
460 
461 
462 	/* check /sys/dev/block/<maj>:<min>/removable attribute */
463 	if (ul_path_read_s32(pc, &rc, "removable") == 0 && rc == 1)
464 		return 1;
465 
466 	chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf));
467 
468 	while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
469 		rc = is_hotpluggable_subsystem(sub);
470 		if (rc) {
471 			free(sub);
472 			break;
473 		}
474 		free(sub);
475 	}
476 
477 	return rc;
478 }
479 
get_dm_wholedisk(struct path_cxt * pc,char * diskname,size_t len,dev_t * diskdevno)480 static int get_dm_wholedisk(struct path_cxt *pc, char *diskname,
481                 size_t len, dev_t *diskdevno)
482 {
483     int rc = 0;
484     char *name;
485 
486     /* Note, sysfs_blkdev_get_slave() returns the first slave only,
487      * if there is more slaves, then return NULL
488      */
489     name = sysfs_blkdev_get_slave(pc);
490     if (!name)
491         return -1;
492 
493     if (diskname && len)
494         xstrncpy(diskname, name, len);
495 
496     if (diskdevno) {
497         *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
498         if (!*diskdevno)
499             rc = -1;
500     }
501 
502     free(name);
503     return rc;
504 }
505 
506 /*
507  * Returns by @diskdevno whole disk device devno and (optionally) by
508  * @diskname the whole disk device name.
509  */
sysfs_blkdev_get_wholedisk(struct path_cxt * pc,char * diskname,size_t len,dev_t * diskdevno)510 int sysfs_blkdev_get_wholedisk(	struct path_cxt *pc,
511 				char *diskname,
512 				size_t len,
513 				dev_t *diskdevno)
514 {
515     int is_part = 0;
516 
517     if (!pc)
518         return -1;
519 
520     is_part = ul_path_access(pc, F_OK, "partition") == 0;
521     if (!is_part) {
522         /*
523          * Extra case for partitions mapped by device-mapper.
524          *
525          * All regular partitions (added by BLKPG ioctl or kernel PT
526          * parser) have the /sys/.../partition file. The partitions
527          * mapped by DM don't have such file, but they have "part"
528          * prefix in DM UUID.
529          */
530         char *uuid = NULL, *tmp, *prefix;
531 
532 	ul_path_read_string(pc, &uuid, "dm/uuid");
533 	tmp = uuid;
534 	prefix = uuid ? strsep(&tmp, "-") : NULL;
535 
536         if (prefix && strncasecmp(prefix, "part", 4) == 0)
537             is_part = 1;
538         free(uuid);
539 
540         if (is_part &&
541             get_dm_wholedisk(pc, diskname, len, diskdevno) == 0)
542             /*
543              * partitioned device, mapped by DM
544              */
545             goto done;
546 
547         is_part = 0;
548     }
549 
550     if (!is_part) {
551         /*
552          * unpartitioned device
553          */
554         if (diskname && !sysfs_blkdev_get_name(pc, diskname, len))
555             goto err;
556         if (diskdevno)
557             *diskdevno = sysfs_blkdev_get_devno(pc);
558 
559     } else {
560         /*
561          * partitioned device
562          *  - readlink /sys/dev/block/8:1   = ../../block/sda/sda1
563          *  - dirname  ../../block/sda/sda1 = ../../block/sda
564          *  - basename ../../block/sda      = sda
565          */
566         char linkpath[PATH_MAX];
567         char *name;
568 	ssize_t	linklen;
569 
570 	linklen = ul_path_readlink(pc, linkpath, sizeof(linkpath) - 1, NULL);
571         if (linklen < 0)
572             goto err;
573         linkpath[linklen] = '\0';
574 
575         stripoff_last_component(linkpath);      /* dirname */
576         name = stripoff_last_component(linkpath);   /* basename */
577         if (!name)
578             goto err;
579 
580 	sysfs_devname_sys_to_dev(name);
581         if (diskname && len)
582             xstrncpy(diskname, name, len);
583 
584         if (diskdevno) {
585             *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
586             if (!*diskdevno)
587                 goto err;
588         }
589     }
590 
591 done:
592     return 0;
593 err:
594     return -1;
595 }
596 
sysfs_devno_to_wholedisk(dev_t devno,char * diskname,size_t len,dev_t * diskdevno)597 int sysfs_devno_to_wholedisk(dev_t devno, char *diskname,
598 		             size_t len, dev_t *diskdevno)
599 {
600 	struct path_cxt *pc;
601 	int rc = 0;
602 
603 	if (!devno)
604 		return -EINVAL;
605 	pc = ul_new_sysfs_path(devno, NULL, NULL);
606 	if (!pc)
607 		return -ENOMEM;
608 
609 	rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno);
610 	ul_unref_path(pc);
611 	return rc;
612 }
613 
614 /*
615  * Returns 1 if the device is private device mapper device. The @uuid
616  * (if not NULL) returns DM device UUID, use free() to deallocate.
617  */
sysfs_devno_is_dm_private(dev_t devno,char ** uuid)618 int sysfs_devno_is_dm_private(dev_t devno, char **uuid)
619 {
620 	struct path_cxt *pc = NULL;
621 	char *id = NULL;
622 	int rc = 0;
623 
624 	pc = ul_new_sysfs_path(devno, NULL, NULL);
625 	if (!pc)
626 		goto done;
627 	if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
628 		goto done;
629 
630 	/* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
631 	 * is the "LVM" prefix and "-<name>" postfix).
632 	 */
633 	if (strncmp(id, "LVM-", 4) == 0) {
634 		char *p = strrchr(id + 4, '-');
635 
636 		if (p && *(p + 1))
637 			rc = 1;
638 
639 	/* Private Stratis devices prefix the UUID with "stratis-1-private"
640 	 */
641 	} else if (strncmp(id, "stratis-1-private", 17) == 0) {
642 		rc = 1;
643 	}
644 done:
645 	ul_unref_path(pc);
646 	if (uuid)
647 		*uuid = id;
648 	else
649 		free(id);
650 	return rc;
651 }
652 
653 /*
654  * Return 0 or 1, or < 0 in case of error
655  */
sysfs_devno_is_wholedisk(dev_t devno)656 int sysfs_devno_is_wholedisk(dev_t devno)
657 {
658 	dev_t disk;
659 
660 	if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
661 		return -1;
662 
663 	return devno == disk;
664 }
665 
666 
sysfs_blkdev_scsi_get_hctl(struct path_cxt * pc,int * h,int * c,int * t,int * l)667 int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l)
668 {
669 	char buf[PATH_MAX], *hctl;
670 	struct sysfs_blkdev *blk;
671 	ssize_t len;
672 
673 	blk = ul_path_get_dialect(pc);
674 
675 	if (!blk || blk->hctl_error)
676 		return -EINVAL;
677 	if (blk->has_hctl)
678 		goto done;
679 
680 	blk->hctl_error = 1;
681 	len = ul_path_readlink(pc, buf, sizeof(buf) - 1, "device");
682 	if (len < 0)
683 		return len;
684 
685 	buf[len] = '\0';
686 	hctl = strrchr(buf, '/');
687 	if (!hctl)
688 		return -1;
689 	hctl++;
690 
691 	if (sscanf(hctl, "%u:%u:%u:%u",	&blk->scsi_host, &blk->scsi_channel,
692 				&blk->scsi_target, &blk->scsi_lun) != 4)
693 		return -1;
694 
695 	blk->has_hctl = 1;
696 done:
697 	if (h)
698 		*h = blk->scsi_host;
699 	if (c)
700 		*c = blk->scsi_channel;
701 	if (t)
702 		*t = blk->scsi_target;
703 	if (l)
704 		*l = blk->scsi_lun;
705 
706 	blk->hctl_error = 0;
707 	return 0;
708 }
709 
710 
scsi_host_attribute_path(struct path_cxt * pc,const char * type,char * buf,size_t bufsz,const char * attr)711 static char *scsi_host_attribute_path(
712 			struct path_cxt *pc,
713 			const char *type,
714 			char *buf,
715 			size_t bufsz,
716 			const char *attr)
717 {
718 	int len;
719 	int host;
720 	const char *prefix;
721 
722 	if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL))
723 		return NULL;
724 
725 	prefix = ul_path_get_prefix(pc);
726 	if (!prefix)
727 		prefix = "";
728 
729 	if (attr)
730 		len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s",
731 				prefix, _PATH_SYS_CLASS, type, host, attr);
732 	else
733 		len = snprintf(buf, bufsz, "%s%s/%s_host/host%d",
734 				prefix, _PATH_SYS_CLASS, type, host);
735 
736 	return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
737 }
738 
sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt * pc,const char * type,const char * attr)739 char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc,
740 			const char *type, const char *attr)
741 {
742 	char buf[1024];
743 	int rc;
744 	FILE *f;
745 
746 	if (!attr || !type ||
747 	    !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr))
748 		return NULL;
749 
750 	if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
751                 return NULL;
752 
753 	rc = fscanf(f, "%1023[^\n]", buf);
754 	fclose(f);
755 
756 	return rc == 1 ? strdup(buf) : NULL;
757 }
758 
sysfs_blkdev_scsi_host_is(struct path_cxt * pc,const char * type)759 int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type)
760 {
761 	char buf[PATH_MAX];
762 	struct stat st;
763 
764 	if (!type || !scsi_host_attribute_path(pc, type,
765 				buf, sizeof(buf), NULL))
766 		return 0;
767 
768 	return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
769 }
770 
scsi_attribute_path(struct path_cxt * pc,char * buf,size_t bufsz,const char * attr)771 static char *scsi_attribute_path(struct path_cxt *pc,
772 		char *buf, size_t bufsz, const char *attr)
773 {
774 	int len, h, c, t, l;
775 	const char *prefix;
776 
777 	if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0)
778 		return NULL;
779 
780 	prefix = ul_path_get_prefix(pc);
781 	if (!prefix)
782 		prefix = "";
783 
784 	if (attr)
785 		len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s",
786 				prefix, _PATH_SYS_SCSI,
787 				h,c,t,l, attr);
788 	else
789 		len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d",
790 				prefix, _PATH_SYS_SCSI,
791 				h,c,t,l);
792 	return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
793 }
794 
sysfs_blkdev_scsi_has_attribute(struct path_cxt * pc,const char * attr)795 int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr)
796 {
797 	char path[PATH_MAX];
798 	struct stat st;
799 
800 	if (!scsi_attribute_path(pc, path, sizeof(path), attr))
801 		return 0;
802 
803 	return stat(path, &st) == 0;
804 }
805 
sysfs_blkdev_scsi_path_contains(struct path_cxt * pc,const char * pattern)806 int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern)
807 {
808 	char path[PATH_MAX], linkc[PATH_MAX];
809 	struct stat st;
810 	ssize_t len;
811 
812 	if (!scsi_attribute_path(pc, path, sizeof(path), NULL))
813 		return 0;
814 
815 	if (stat(path, &st) != 0)
816 		return 0;
817 
818 	len = readlink(path, linkc, sizeof(linkc) - 1);
819 	if (len < 0)
820 		return 0;
821 
822 	linkc[len] = '\0';
823 	return strstr(linkc, pattern) != NULL;
824 }
825 
read_devno(const char * path)826 static dev_t read_devno(const char *path)
827 {
828 	FILE *f;
829 	int maj = 0, min = 0;
830 	dev_t dev = 0;
831 
832 	f = fopen(path, "r" UL_CLOEXECSTR);
833 	if (!f)
834 		return 0;
835 
836 	if (fscanf(f, "%d:%d", &maj, &min) == 2)
837 		dev = makedev(maj, min);
838 	fclose(f);
839 	return dev;
840 }
841 
sysfs_devname_is_hidden(const char * prefix,const char * name)842 int sysfs_devname_is_hidden(const char *prefix, const char *name)
843 {
844 	char buf[PATH_MAX];
845 	int rc = 0, hidden = 0, len;
846 	FILE *f;
847 
848 	if (strncmp("/dev/", name, 5) == 0)
849 		return 0;
850 
851 	if (!prefix)
852 		prefix = "";
853 	/*
854 	 * Create path to /sys/block/<name>/hidden
855 	 */
856 	len = snprintf(buf, sizeof(buf),
857 			"%s" _PATH_SYS_BLOCK "/%s/hidden",
858 			prefix, name);
859 
860 	if (len < 0 || (size_t) len + 1 > sizeof(buf))
861 		return 0;
862 
863 	f = fopen(buf, "r" UL_CLOEXECSTR);
864 	if (!f)
865 		return 0;
866 
867 	rc = fscanf(f, "%d", &hidden);
868 	fclose(f);
869 
870 	return rc == 1 ? hidden : 0;
871 }
872 
873 
__sysfs_devname_to_devno(const char * prefix,const char * name,const char * parent)874 dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent)
875 {
876 	char buf[PATH_MAX];
877 	char *_name = NULL;	/* name as encoded in sysfs */
878 	dev_t dev = 0;
879 	int len;
880 
881 	if (!prefix)
882 		prefix = "";
883 
884 	assert(name);
885 
886 	if (strncmp("/dev/", name, 5) == 0) {
887 		/*
888 		 * Read from /dev
889 		 */
890 		struct stat st;
891 
892 		if (stat(name, &st) == 0) {
893 			dev = st.st_rdev;
894 			goto done;
895 		}
896 		name += 5;	/* unaccessible, or not node in /dev */
897 	}
898 
899 	_name = strdup(name);
900 	if (!_name)
901 		goto done;
902 	sysfs_devname_dev_to_sys(_name);
903 
904 	if (parent && strncmp("dm-", name, 3) != 0) {
905 		/*
906 		 * Create path to /sys/block/<parent>/<name>/dev
907 		 */
908 		char *_parent = strdup(parent);
909 
910 		if (!_parent) {
911 			free(_parent);
912 			goto done;
913 		}
914 		sysfs_devname_dev_to_sys(_parent);
915 		len = snprintf(buf, sizeof(buf),
916 				"%s" _PATH_SYS_BLOCK "/%s/%s/dev",
917 				prefix,	_parent, _name);
918 		free(_parent);
919 		if (len < 0 || (size_t) len >= sizeof(buf))
920 			goto done;
921 
922 		/* don't try anything else for dm-* */
923 		dev = read_devno(buf);
924 		goto done;
925 	}
926 
927 	/*
928 	 * Read from /sys/block/<sysname>/dev
929 	 */
930 	len = snprintf(buf, sizeof(buf),
931 			"%s" _PATH_SYS_BLOCK "/%s/dev",
932 			prefix, _name);
933 	if (len < 0 || (size_t) len >= sizeof(buf))
934 		goto done;
935 	dev = read_devno(buf);
936 
937 	if (!dev) {
938 		/*
939 		 * Read from /sys/block/<sysname>/device/dev
940 		 */
941 		len = snprintf(buf, sizeof(buf),
942 				"%s" _PATH_SYS_BLOCK "/%s/device/dev",
943 				prefix, _name);
944 		if (len < 0 || (size_t) len >= sizeof(buf))
945 			goto done;
946 		dev = read_devno(buf);
947 	}
948 done:
949 	free(_name);
950 	return dev;
951 }
952 
sysfs_devname_to_devno(const char * name)953 dev_t sysfs_devname_to_devno(const char *name)
954 {
955 	return __sysfs_devname_to_devno(NULL, name, NULL);
956 }
957 
sysfs_blkdev_get_path(struct path_cxt * pc,char * buf,size_t bufsiz)958 char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz)
959 {
960 	const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz);
961 	char *res = NULL;
962 	size_t sz;
963 	struct stat st;
964 
965 	if (!name)
966 		goto done;
967 
968 	sz = strlen(name);
969 	if (sz + sizeof("/dev/") > bufsiz)
970 		goto done;
971 
972 	/* create the final "/dev/<name>" string */
973 	memmove(buf + 5, name, sz + 1);
974 	memcpy(buf, "/dev/", 5);
975 
976 	if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc))
977 		res = buf;
978 done:
979 	return res;
980 }
981 
sysfs_blkdev_get_devno(struct path_cxt * pc)982 dev_t sysfs_blkdev_get_devno(struct path_cxt *pc)
983 {
984 	return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno;
985 }
986 
987 /*
988  * Returns devname (e.g. "/dev/sda1") for the given devno.
989  *
990  * Please, use more robust blkid_devno_to_devname() in your applications.
991  */
sysfs_devno_to_devpath(dev_t devno,char * buf,size_t bufsiz)992 char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
993 {
994 	struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
995 	char *res = NULL;
996 
997 	if (pc) {
998 		res = sysfs_blkdev_get_path(pc, buf, bufsiz);
999 		ul_unref_path(pc);
1000 	}
1001 	return res;
1002 }
1003 
sysfs_devno_to_devname(dev_t devno,char * buf,size_t bufsiz)1004 char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
1005 {
1006 	struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1007 	char *res = NULL;
1008 
1009 	if (pc) {
1010 		res = sysfs_blkdev_get_name(pc, buf, bufsiz);
1011 		ul_unref_path(pc);
1012 	}
1013 	return res;
1014 }
1015 
sysfs_devno_count_partitions(dev_t devno)1016 int sysfs_devno_count_partitions(dev_t devno)
1017 {
1018 	struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
1019 	int n = 0;
1020 
1021 	if (pc) {
1022 		char buf[PATH_MAX + 1];
1023 		char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf));
1024 
1025 		n = sysfs_blkdev_count_partitions(pc, name);
1026 		ul_unref_path(pc);
1027 	}
1028 	return n;
1029 }
1030 
1031 
1032 #ifdef TEST_PROGRAM_SYSFS
1033 #include <errno.h>
1034 #include <err.h>
1035 #include <stdlib.h>
1036 
main(int argc,char * argv[])1037 int main(int argc, char *argv[])
1038 {
1039 	struct path_cxt *pc;
1040 	char *devname;
1041 	dev_t devno, disk_devno;
1042 	char path[PATH_MAX], *sub, *chain;
1043 	char diskname[32];
1044 	int i, is_part, rc = EXIT_SUCCESS;
1045 	uint64_t u64;
1046 
1047 	if (argc != 2)
1048 		errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
1049 
1050 	ul_sysfs_init_debug();
1051 
1052 	devname = argv[1];
1053 	devno = sysfs_devname_to_devno(devname);
1054 
1055 	if (!devno)
1056 		err(EXIT_FAILURE, "failed to read devno");
1057 
1058 	printf("non-context:\n");
1059 	printf(" DEVNO:   %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
1060 	printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path)));
1061 	printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
1062 
1063 	sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1064 	printf(" WHOLEDISK-DEVNO:   %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1065 	printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1066 
1067 	pc = ul_new_sysfs_path(devno, NULL, NULL);
1068 	if (!pc)
1069 		goto done;
1070 
1071 	printf("context based:\n");
1072 	devno = sysfs_blkdev_get_devno(pc);
1073 	printf(" DEVNO:   %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
1074 	printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path)));
1075 	printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path)));
1076 
1077 	sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
1078 	printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
1079 	printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
1080 
1081 	is_part = ul_path_access(pc, F_OK, "partition") == 0;
1082 	printf(" PARTITION: %s\n", is_part ? "YES" : "NOT");
1083 
1084 	if (is_part && disk_devno) {
1085 		struct path_cxt *disk_pc =  ul_new_sysfs_path(disk_devno, NULL, NULL);
1086 		sysfs_blkdev_set_parent(pc, disk_pc);
1087 
1088 		ul_unref_path(disk_pc);
1089 	}
1090 
1091 	printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no");
1092 	printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves"));
1093 
1094 	if (!is_part) {
1095 		printf("First 5 partitions:\n");
1096 		for (i = 1; i <= 5; i++) {
1097 			dev_t dev = sysfs_blkdev_partno_to_devno(pc, i);
1098 			if (dev)
1099 				printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
1100 		}
1101 	}
1102 
1103 	if (ul_path_read_u64(pc, &u64, "size") != 0)
1104 		printf(" (!) read SIZE failed\n");
1105 	else
1106 		printf(" SIZE: %jd\n", u64);
1107 
1108 	if (ul_path_read_s32(pc, &i, "queue/hw_sector_size"))
1109 		printf(" (!) read SECTOR failed\n");
1110 	else
1111 		printf(" SECTOR: %d\n", i);
1112 
1113 
1114 	chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path));
1115 	printf(" SUBSUSTEMS:\n");
1116 
1117 	while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
1118 		printf("\t%s\n", sub);
1119 		free(sub);
1120 	}
1121 
1122 	rc = EXIT_SUCCESS;
1123 done:
1124 	ul_unref_path(pc);
1125 	return rc;
1126 }
1127 #endif /* TEST_PROGRAM_SYSFS */
1128