1 /*
2  * Simple functions to access files. Paths can be globally prefixed to read
3  * data from an alternative source (e.g. a /proc dump for regression tests).
4  *
5  * The paths is possible to format by printf-like way for functions with "f"
6  * postfix in the name (e.g. readf, openf, ... ul_path_readf_u64()).
7  *
8  * The ul_path_read_* API is possible to use without path_cxt handler. In this
9  * case is not possible to use global prefix and printf-like formatting.
10  *
11  * No copyright is claimed.  This code is in the public domain; do with
12  * it what you wish.
13  *
14  * Written by Karel Zak <kzak@redhat.com> [February 2018]
15  */
16 #include <stdarg.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <inttypes.h>
21 #include <errno.h>
22 
23 #include "c.h"
24 #include "fileutils.h"
25 #include "all-io.h"
26 #include "path.h"
27 #include "debug.h"
28 #include "strutils.h"
29 
30 /*
31  * Debug stuff (based on include/debug.h)
32  */
33 static UL_DEBUG_DEFINE_MASK(ulpath);
34 UL_DEBUG_DEFINE_MASKNAMES(ulpath) = UL_DEBUG_EMPTY_MASKNAMES;
35 
36 #define ULPATH_DEBUG_INIT	(1 << 1)
37 #define ULPATH_DEBUG_CXT	(1 << 2)
38 
39 #define DBG(m, x)       __UL_DBG(ulpath, ULPATH_DEBUG_, m, x)
40 #define ON_DBG(m, x)    __UL_DBG_CALL(ulpath, ULPATH_DEBUG_, m, x)
41 
42 #define UL_DEBUG_CURRENT_MASK	UL_DEBUG_MASK(ulpath)
43 #include "debugobj.h"
44 
ul_path_init_debug(void)45 void ul_path_init_debug(void)
46 {
47 	if (ulpath_debug_mask)
48 		return;
49 	__UL_INIT_DEBUG_FROM_ENV(ulpath, ULPATH_DEBUG_, 0, ULPATH_DEBUG);
50 }
51 
ul_new_path(const char * dir,...)52 struct path_cxt *ul_new_path(const char *dir, ...)
53 {
54 	struct path_cxt *pc = calloc(1, sizeof(*pc));
55 
56 	if (!pc)
57 		return NULL;
58 
59 	DBG(CXT, ul_debugobj(pc, "alloc"));
60 
61 	pc->refcount = 1;
62 	pc->dir_fd = -1;
63 
64 	if (dir) {
65 		int rc;
66 		va_list ap;
67 
68 		va_start(ap, dir);
69 		rc = vasprintf(&pc->dir_path, dir, ap);
70 		va_end(ap);
71 
72 		if (rc < 0 || !pc->dir_path)
73 			goto fail;
74 	}
75 	return pc;
76 fail:
77 	ul_unref_path(pc);
78 	return NULL;
79 }
80 
ul_ref_path(struct path_cxt * pc)81 void ul_ref_path(struct path_cxt *pc)
82 {
83 	if (pc)
84 		pc->refcount++;
85 }
86 
ul_unref_path(struct path_cxt * pc)87 void ul_unref_path(struct path_cxt *pc)
88 {
89 	if (!pc)
90 		return;
91 
92 	pc->refcount--;
93 
94 	if (pc->refcount <= 0) {
95 		DBG(CXT, ul_debugobj(pc, "dealloc"));
96 		if (pc->dialect)
97 			pc->free_dialect(pc);
98 		ul_path_close_dirfd(pc);
99 		free(pc->dir_path);
100 		free(pc->prefix);
101 		free(pc);
102 	}
103 }
104 
ul_path_set_prefix(struct path_cxt * pc,const char * prefix)105 int ul_path_set_prefix(struct path_cxt *pc, const char *prefix)
106 {
107 	char *p = NULL;
108 
109 	assert(pc->dir_fd < 0);
110 
111 	if (prefix) {
112 		p = strdup(prefix);
113 		if (!p)
114 			return -ENOMEM;
115 	}
116 
117 	free(pc->prefix);
118 	pc->prefix = p;
119 	DBG(CXT, ul_debugobj(pc, "new prefix: '%s'", p));
120 	return 0;
121 }
122 
ul_path_get_prefix(struct path_cxt * pc)123 const char *ul_path_get_prefix(struct path_cxt *pc)
124 {
125 	return pc ? pc->prefix : NULL;
126 }
127 
ul_path_set_dir(struct path_cxt * pc,const char * dir)128 int ul_path_set_dir(struct path_cxt *pc, const char *dir)
129 {
130 	char *p = NULL;
131 
132 	if (dir) {
133 		p = strdup(dir);
134 		if (!p)
135 			return -ENOMEM;
136 	}
137 
138 	if (pc->dir_fd >= 0) {
139 		close(pc->dir_fd);
140 		pc->dir_fd = -1;
141 	}
142 
143 	free(pc->dir_path);
144 	pc->dir_path = p;
145 	DBG(CXT, ul_debugobj(pc, "new dir: '%s'", p));
146 	return 0;
147 }
148 
ul_path_get_dir(struct path_cxt * pc)149 const char *ul_path_get_dir(struct path_cxt *pc)
150 {
151 	return pc ? pc->dir_path : NULL;
152 }
153 
ul_path_set_dialect(struct path_cxt * pc,void * data,void free_data (struct path_cxt *))154 int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *))
155 {
156 	pc->dialect = data;
157 	pc->free_dialect = free_data;
158 	DBG(CXT, ul_debugobj(pc, "(re)set dialect"));
159 	return 0;
160 }
161 
ul_path_get_dialect(struct path_cxt * pc)162 void *ul_path_get_dialect(struct path_cxt *pc)
163 {
164 	return pc ? pc->dialect : NULL;
165 }
166 
ul_path_set_enoent_redirect(struct path_cxt * pc,int (* func)(struct path_cxt *,const char *,int *))167 int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *))
168 {
169 	pc->redirect_on_enoent = func;
170 	return 0;
171 }
172 
get_absdir(struct path_cxt * pc)173 static const char *get_absdir(struct path_cxt *pc)
174 {
175 	int rc;
176 	const char *dirpath;
177 
178 	if (!pc->prefix)
179 		return pc->dir_path;
180 
181 	dirpath = pc->dir_path;
182 	if (!dirpath)
183 		return pc->prefix;
184 	if (*dirpath == '/')
185 		dirpath++;
186 
187 	rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, dirpath);
188 	if (rc < 0)
189 		return NULL;
190 	if ((size_t)rc >= sizeof(pc->path_buffer)) {
191 		errno = ENAMETOOLONG;
192 		return NULL;
193 	}
194 
195 	return pc->path_buffer;
196 }
197 
ul_path_is_accessible(struct path_cxt * pc)198 int ul_path_is_accessible(struct path_cxt *pc)
199 {
200 	const char *path;
201 	assert(pc);
202 
203 	if (pc->dir_fd >= 0)
204 		return 1;
205 
206 	path = get_absdir(pc);
207 	if (!path)
208 		return 0;
209 	return access(path, F_OK) == 0;
210 }
211 
ul_path_get_dirfd(struct path_cxt * pc)212 int ul_path_get_dirfd(struct path_cxt *pc)
213 {
214 	assert(pc);
215 	assert(pc->dir_path);
216 
217 	if (pc->dir_fd < 0) {
218 		const char *path = get_absdir(pc);
219 		if (!path)
220 			return -errno;
221 
222 		DBG(CXT, ul_debugobj(pc, "opening dir: '%s'", path));
223 		pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC);
224 	}
225 
226 	return pc->dir_fd;
227 }
228 
229 /* Note that next ul_path_get_dirfd() will reopen the directory */
ul_path_close_dirfd(struct path_cxt * pc)230 void ul_path_close_dirfd(struct path_cxt *pc)
231 {
232 	assert(pc);
233 
234 	if (pc->dir_fd >= 0) {
235 		DBG(CXT, ul_debugobj(pc, "closing dir"));
236 		close(pc->dir_fd);
237 		pc->dir_fd = -1;
238 	}
239 }
240 
ul_path_isopen_dirfd(struct path_cxt * pc)241 int ul_path_isopen_dirfd(struct path_cxt *pc)
242 {
243 	return pc && pc->dir_fd >= 0;
244 }
245 
ul_path_mkpath(struct path_cxt * pc,const char * path,va_list ap)246 static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap)
247 {
248 	int rc;
249 
250 	errno = 0;
251 
252 	rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap);
253 	if (rc < 0) {
254 		if (!errno)
255 			errno = EINVAL;
256 		return NULL;
257 	}
258 
259 	if ((size_t)rc >= sizeof(pc->path_buffer)) {
260 		errno = ENAMETOOLONG;
261 		return NULL;
262 	}
263 
264 	return pc->path_buffer;
265 }
266 
ul_path_get_abspath(struct path_cxt * pc,char * buf,size_t bufsz,const char * path,...)267 char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
268 {
269 	if (path) {
270 		int rc;
271 		va_list ap;
272 		const char *tail = NULL, *dirpath = pc->dir_path;
273 
274 		va_start(ap, path);
275 		tail = ul_path_mkpath(pc, path, ap);
276 		va_end(ap);
277 
278 		if (dirpath && *dirpath == '/')
279 			dirpath++;
280 		if (tail && *tail == '/')
281 			tail++;
282 
283 		rc = snprintf(buf, bufsz, "%s/%s/%s",
284 				pc->prefix ? pc->prefix : "",
285 				dirpath ? dirpath : "",
286 				tail ? tail : "");
287 
288 		if ((size_t)rc >= bufsz) {
289 			errno = ENAMETOOLONG;
290 			return NULL;
291 		}
292 	} else {
293 		const char *tmp = get_absdir(pc);
294 
295 		if (!tmp)
296 			return NULL;
297 		xstrncpy(buf, tmp, bufsz);
298 	}
299 
300 	return buf;
301 }
302 
303 
ul_path_access(struct path_cxt * pc,int mode,const char * path)304 int ul_path_access(struct path_cxt *pc, int mode, const char *path)
305 {
306 	int rc;
307 
308 	if (!pc) {
309 		rc = access(path, mode);
310 		DBG(CXT, ul_debug("access '%s' [no context, rc=%d]", path, rc));
311 	} else {
312 		int dir = ul_path_get_dirfd(pc);
313 		if (dir < 0)
314 			return dir;
315 		if (*path == '/')
316 			path++;
317 
318 		rc = faccessat(dir, path, mode, 0);
319 
320 		if (rc && errno == ENOENT
321 		    && pc->redirect_on_enoent
322 		    && pc->redirect_on_enoent(pc, path, &dir) == 0)
323 			rc = faccessat(dir, path, mode, 0);
324 
325 		DBG(CXT, ul_debugobj(pc, "access: '%s' [rc=%d]", path, rc));
326 	}
327 	return rc;
328 }
329 
ul_path_accessf(struct path_cxt * pc,int mode,const char * path,...)330 int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
331 {
332 	va_list ap;
333 	const char *p;
334 
335 	va_start(ap, path);
336 	p = ul_path_mkpath(pc, path, ap);
337 	va_end(ap);
338 
339 	return !p ? -errno : ul_path_access(pc, mode, p);
340 }
341 
ul_path_stat(struct path_cxt * pc,struct stat * sb,const char * path)342 int ul_path_stat(struct path_cxt *pc, struct stat *sb, const char *path)
343 {
344 	int rc;
345 
346 	if (!pc) {
347 		rc = stat(path, sb);
348 		DBG(CXT, ul_debug("stat '%s' [no context, rc=%d]", path, rc));
349 	} else {
350 		int dir = ul_path_get_dirfd(pc);
351 		if (dir < 0)
352 			return dir;
353 		if (*path == '/')
354 			path++;
355 
356 		rc = fstatat(dir, path, sb, 0);
357 
358 		if (rc && errno == ENOENT
359 		    && pc->redirect_on_enoent
360 		    && pc->redirect_on_enoent(pc, path, &dir) == 0)
361 			rc = fstatat(dir, path, sb, 0);
362 
363 		DBG(CXT, ul_debugobj(pc, "stat '%s' [rc=%d]", path, rc));
364 	}
365 	return rc;
366 }
367 
ul_path_open(struct path_cxt * pc,int flags,const char * path)368 int ul_path_open(struct path_cxt *pc, int flags, const char *path)
369 {
370 	int fd;
371 
372 	if (!pc) {
373 		fd = open(path, flags);
374 		DBG(CXT, ul_debug("opening '%s' [no context]", path));
375 	} else {
376 		int fdx;
377 		int dir = ul_path_get_dirfd(pc);
378 		if (dir < 0)
379 			return dir;
380 
381 		if (*path == '/')
382 			path++;
383 
384 		fdx = fd = openat(dir, path, flags);
385 
386 		if (fd < 0 && errno == ENOENT
387 		    && pc->redirect_on_enoent
388 		    && pc->redirect_on_enoent(pc, path, &dir) == 0)
389 			fd = openat(dir, path, flags);
390 
391 		DBG(CXT, ul_debugobj(pc, "opening '%s'%s", path, fdx != fd ? " [redirected]" : ""));
392 	}
393 	return fd;
394 }
395 
ul_path_vopenf(struct path_cxt * pc,int flags,const char * path,va_list ap)396 int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap)
397 {
398 	const char *p = ul_path_mkpath(pc, path, ap);
399 
400 	return !p ? -errno : ul_path_open(pc, flags, p);
401 }
402 
ul_path_openf(struct path_cxt * pc,int flags,const char * path,...)403 int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
404 {
405 	va_list ap;
406 	int rc;
407 
408 	va_start(ap, path);
409 	rc = ul_path_vopenf(pc, flags, path, ap);
410 	va_end(ap);
411 
412 	return rc;
413 }
414 
415 /*
416  * Maybe stupid, but good enough ;-)
417  */
mode2flags(const char * mode)418 static int mode2flags(const char *mode)
419 {
420 	int flags = 0;
421 	const char *p;
422 
423 	for (p = mode; p && *p; p++) {
424 		if (*p == 'r' && *(p + 1) == '+')
425 			flags |= O_RDWR;
426 		else if (*p == 'r')
427 			flags |= O_RDONLY;
428 
429 		else if (*p == 'w' && *(p + 1) == '+')
430 			flags |= O_RDWR | O_TRUNC;
431 		else if (*p == 'w')
432 			flags |= O_WRONLY | O_TRUNC;
433 
434 		else if (*p == 'a' && *(p + 1) == '+')
435 			flags |= O_RDWR | O_APPEND;
436 		else if (*p == 'a')
437 			flags |= O_WRONLY | O_APPEND;
438 #ifdef O_CLOEXEC
439 		else if (*p == *UL_CLOEXECSTR)
440 			flags |= O_CLOEXEC;
441 #endif
442 	}
443 
444 	return flags;
445 }
446 
ul_path_fopen(struct path_cxt * pc,const char * mode,const char * path)447 FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path)
448 {
449 	int flags = mode2flags(mode);
450 	int fd = ul_path_open(pc, flags, path);
451 
452 	if (fd < 0)
453 		return NULL;
454 
455 	return fdopen(fd, mode);
456 }
457 
458 
ul_path_vfopenf(struct path_cxt * pc,const char * mode,const char * path,va_list ap)459 FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap)
460 {
461 	const char *p = ul_path_mkpath(pc, path, ap);
462 
463 	return !p ? NULL : ul_path_fopen(pc, mode, p);
464 }
465 
ul_path_fopenf(struct path_cxt * pc,const char * mode,const char * path,...)466 FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
467 {
468 	FILE *f;
469 	va_list ap;
470 
471 	va_start(ap, path);
472 	f = ul_path_vfopenf(pc, mode, path, ap);
473 	va_end(ap);
474 
475 	return f;
476 }
477 
478 /*
479  * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
480  * to the directory addressed by @pc.
481  */
ul_path_opendir(struct path_cxt * pc,const char * path)482 DIR *ul_path_opendir(struct path_cxt *pc, const char *path)
483 {
484 	DIR *dir;
485 	int fd = -1;
486 
487 	if (path)
488 		fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
489 	else if (pc->dir_path) {
490 		int dirfd;
491 
492 		DBG(CXT, ul_debugobj(pc, "duplicate dir path"));
493 		dirfd = ul_path_get_dirfd(pc);
494 		if (dirfd >= 0)
495 			fd = dup_fd_cloexec(dirfd, STDERR_FILENO + 1);
496 	}
497 
498 	if (fd < 0)
499 		return NULL;
500 
501 	dir = fdopendir(fd);
502 	if (!dir) {
503 		close(fd);
504 		return NULL;
505 	}
506 	if (!path)
507 		 rewinddir(dir);
508 	return dir;
509 }
510 
511 
512 /*
513  * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
514  * to the directory addressed by @pc.
515  */
ul_path_vopendirf(struct path_cxt * pc,const char * path,va_list ap)516 DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap)
517 {
518 	const char *p = ul_path_mkpath(pc, path, ap);
519 
520 	return !p ? NULL : ul_path_opendir(pc, p);
521 }
522 
523 /*
524  * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
525  * to the directory addressed by @pc.
526  */
ul_path_opendirf(struct path_cxt * pc,const char * path,...)527 DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
528 {
529 	va_list ap;
530 	DIR *dir;
531 
532 	va_start(ap, path);
533 	dir = ul_path_vopendirf(pc, path, ap);
534 	va_end(ap);
535 
536 	return dir;
537 }
538 
539 /*
540  * If @path is NULL then readlink is called on @pc directory.
541  */
ul_path_readlink(struct path_cxt * pc,char * buf,size_t bufsiz,const char * path)542 ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path)
543 {
544 	int dirfd;
545 
546 	if (!path) {
547 		const char *p = get_absdir(pc);
548 		if (!p)
549 			return -errno;
550 		return readlink(p, buf, bufsiz);
551 	}
552 
553 	dirfd = ul_path_get_dirfd(pc);
554 	if (dirfd < 0)
555 		return dirfd;
556 
557 	if (*path == '/')
558 		path++;
559 
560 	return readlinkat(dirfd, path, buf, bufsiz);
561 }
562 
563 /*
564  * If @path is NULL then readlink is called on @pc directory.
565  */
ul_path_readlinkf(struct path_cxt * pc,char * buf,size_t bufsiz,const char * path,...)566 ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
567 {
568 	const char *p;
569 	va_list ap;
570 
571 	va_start(ap, path);
572 	p = ul_path_mkpath(pc, path, ap);
573 	va_end(ap);
574 
575 	return !p ? -errno : ul_path_readlink(pc, buf, bufsiz, p);
576 }
577 
ul_path_read(struct path_cxt * pc,char * buf,size_t len,const char * path)578 int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path)
579 {
580 	int rc, errsv;
581 	int fd;
582 
583 	fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
584 	if (fd < 0)
585 		return -errno;
586 
587 	DBG(CXT, ul_debug(" reading '%s'", path));
588 	rc = read_all(fd, buf, len);
589 
590 	errsv = errno;
591 	close(fd);
592 	errno = errsv;
593 	return rc;
594 }
595 
ul_path_vreadf(struct path_cxt * pc,char * buf,size_t len,const char * path,va_list ap)596 int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap)
597 {
598 	const char *p = ul_path_mkpath(pc, path, ap);
599 
600 	return !p ? -errno : ul_path_read(pc, buf, len, p);
601 }
602 
ul_path_readf(struct path_cxt * pc,char * buf,size_t len,const char * path,...)603 int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
604 {
605 	va_list ap;
606 	int rc;
607 
608 	va_start(ap, path);
609 	rc = ul_path_vreadf(pc, buf, len, path, ap);
610 	va_end(ap);
611 
612 	return rc;
613 }
614 
615 
616 /*
617  * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ
618  * (send patch if you need something bigger;-)
619  *
620  * Returns size of the string!
621  */
ul_path_read_string(struct path_cxt * pc,char ** str,const char * path)622 int ul_path_read_string(struct path_cxt *pc, char **str, const char *path)
623 {
624 	char buf[BUFSIZ];
625 	int rc;
626 
627 	if (!str)
628 		return -EINVAL;
629 
630 	*str = NULL;
631 	rc = ul_path_read(pc, buf, sizeof(buf) - 1, path);
632 	if (rc < 0)
633 		return rc;
634 
635 	/* Remove tailing newline (usual in sysfs) */
636 	if (rc > 0 && *(buf + rc - 1) == '\n')
637 		--rc;
638 
639 	buf[rc] = '\0';
640 	*str = strdup(buf);
641 	if (!*str)
642 		rc = -ENOMEM;
643 
644 	return rc;
645 }
646 
ul_path_readf_string(struct path_cxt * pc,char ** str,const char * path,...)647 int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
648 {
649 	const char *p;
650 	va_list ap;
651 
652 	va_start(ap, path);
653 	p = ul_path_mkpath(pc, path, ap);
654 	va_end(ap);
655 
656 	return !p ? -errno : ul_path_read_string(pc, str, p);
657 }
658 
ul_path_read_buffer(struct path_cxt * pc,char * buf,size_t bufsz,const char * path)659 int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path)
660 {
661 	int rc = ul_path_read(pc, buf, bufsz - 1, path);
662 	if (rc < 0)
663 		return rc;
664 
665 	/* Remove tailing newline (usual in sysfs) */
666 	if (rc > 0 && *(buf + rc - 1) == '\n')
667 		buf[--rc] = '\0';
668 	else
669 		buf[rc - 1] = '\0';
670 
671 	return rc;
672 }
673 
ul_path_readf_buffer(struct path_cxt * pc,char * buf,size_t bufsz,const char * path,...)674 int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
675 {
676 	const char *p;
677 	va_list ap;
678 
679 	va_start(ap, path);
680 	p = ul_path_mkpath(pc, path, ap);
681 	va_end(ap);
682 
683 	return !p ? -errno : ul_path_read_buffer(pc, buf, bufsz, p);
684 }
685 
ul_path_scanf(struct path_cxt * pc,const char * path,const char * fmt,...)686 int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...)
687 {
688 	FILE *f;
689 	va_list fmt_ap;
690 	int rc;
691 
692 	f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path);
693 	if (!f)
694 		return -EINVAL;
695 
696 	DBG(CXT, ul_debug(" fscanf [%s] '%s'", fmt, path));
697 
698 	va_start(fmt_ap, fmt);
699 	rc = vfscanf(f, fmt, fmt_ap);
700 	va_end(fmt_ap);
701 
702 	fclose(f);
703 	return rc;
704 }
705 
ul_path_scanff(struct path_cxt * pc,const char * path,va_list ap,const char * fmt,...)706 int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
707 {
708 	FILE *f;
709 	va_list fmt_ap;
710 	int rc;
711 
712 	f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
713 	if (!f)
714 		return -EINVAL;
715 
716 	va_start(fmt_ap, fmt);
717 	rc = vfscanf(f, fmt, fmt_ap);
718 	va_end(fmt_ap);
719 
720 	fclose(f);
721 	return rc;
722 }
723 
724 
ul_path_read_s64(struct path_cxt * pc,int64_t * res,const char * path)725 int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path)
726 {
727 	int64_t x = 0;
728 	int rc;
729 
730 	rc = ul_path_scanf(pc, path, "%"SCNd64, &x);
731 	if (rc != 1)
732 		return -1;
733 	if (res)
734 		*res = x;
735 	return 0;
736 }
737 
ul_path_readf_s64(struct path_cxt * pc,int64_t * res,const char * path,...)738 int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
739 {
740 	const char *p;
741 	va_list ap;
742 
743 	va_start(ap, path);
744 	p = ul_path_mkpath(pc, path, ap);
745 	va_end(ap);
746 
747 	return !p ? -errno : ul_path_read_s64(pc, res, p);
748 }
749 
ul_path_read_u64(struct path_cxt * pc,uint64_t * res,const char * path)750 int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path)
751 {
752 	uint64_t x = 0;
753 	int rc;
754 
755 	rc = ul_path_scanf(pc, path, "%"SCNu64, &x);
756 	if (rc != 1)
757 		return -1;
758 	if (res)
759 		*res = x;
760 	return 0;
761 }
762 
ul_path_readf_u64(struct path_cxt * pc,uint64_t * res,const char * path,...)763 int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
764 {
765 	const char *p;
766 	va_list ap;
767 
768 	va_start(ap, path);
769 	p = ul_path_mkpath(pc, path, ap);
770 	va_end(ap);
771 
772 	return !p ? -errno : ul_path_read_u64(pc, res, p);
773 }
774 
ul_path_read_s32(struct path_cxt * pc,int * res,const char * path)775 int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path)
776 {
777 	int rc, x = 0;
778 
779 	rc = ul_path_scanf(pc, path, "%d", &x);
780 	if (rc != 1)
781 		return -1;
782 	if (res)
783 		*res = x;
784 	return 0;
785 }
786 
ul_path_readf_s32(struct path_cxt * pc,int * res,const char * path,...)787 int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...)
788 {
789 	const char *p;
790 	va_list ap;
791 
792 	va_start(ap, path);
793 	p = ul_path_mkpath(pc, path, ap);
794 	va_end(ap);
795 
796 	return !p ? -errno : ul_path_read_s32(pc, res, p);
797 }
798 
ul_path_read_u32(struct path_cxt * pc,unsigned int * res,const char * path)799 int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path)
800 {
801 	int rc;
802 	unsigned int x;
803 
804 	rc = ul_path_scanf(pc, path, "%u", &x);
805 	if (rc != 1)
806 		return -1;
807 	if (res)
808 		*res = x;
809 	return 0;
810 }
811 
ul_path_readf_u32(struct path_cxt * pc,unsigned int * res,const char * path,...)812 int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...)
813 {
814 	const char *p;
815 	va_list ap;
816 
817 	va_start(ap, path);
818 	p = ul_path_mkpath(pc, path, ap);
819 	va_end(ap);
820 
821 	return !p ? -errno : ul_path_read_u32(pc, res, p);
822 }
823 
ul_path_read_majmin(struct path_cxt * pc,dev_t * res,const char * path)824 int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path)
825 {
826 	int rc, maj, min;
827 
828 	rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min);
829 	if (rc != 2)
830 		return -1;
831 	if (res)
832 		*res = makedev(maj, min);
833 	return 0;
834 }
835 
ul_path_readf_majmin(struct path_cxt * pc,dev_t * res,const char * path,...)836 int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
837 {
838 	const char *p;
839 	va_list ap;
840 
841 	va_start(ap, path);
842 	p = ul_path_mkpath(pc, path, ap);
843 	va_end(ap);
844 
845 	return !p ? -errno : ul_path_read_majmin(pc, res, p);
846 }
847 
ul_path_write_string(struct path_cxt * pc,const char * str,const char * path)848 int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path)
849 {
850 	int rc, errsv;
851 	int fd;
852 
853 	fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
854 	if (fd < 0)
855 		return -errno;
856 
857 	rc = write_all(fd, str, strlen(str));
858 
859 	errsv = errno;
860 	close(fd);
861 	errno = errsv;
862 	return rc;
863 }
864 
ul_path_writef_string(struct path_cxt * pc,const char * str,const char * path,...)865 int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
866 {
867 	const char *p;
868 	va_list ap;
869 
870 	va_start(ap, path);
871 	p = ul_path_mkpath(pc, path, ap);
872 	va_end(ap);
873 
874 	return !p ? -errno : ul_path_write_string(pc, str, p);
875 }
876 
ul_path_write_s64(struct path_cxt * pc,int64_t num,const char * path)877 int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path)
878 {
879 	char buf[sizeof(stringify_value(LLONG_MAX))];
880 	int rc, errsv;
881 	int fd, len;
882 
883 	fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
884 	if (fd < 0)
885 		return -errno;
886 
887 	len = snprintf(buf, sizeof(buf), "%" PRId64, num);
888 	if (len < 0 || (size_t) len >= sizeof(buf))
889 		rc = len < 0 ? -errno : -E2BIG;
890 	else
891 		rc = write_all(fd, buf, len);
892 
893 	errsv = errno;
894 	close(fd);
895 	errno = errsv;
896 	return rc;
897 }
898 
ul_path_write_u64(struct path_cxt * pc,uint64_t num,const char * path)899 int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path)
900 {
901 	char buf[sizeof(stringify_value(ULLONG_MAX))];
902 	int rc, errsv;
903 	int fd, len;
904 
905 	fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
906 	if (fd < 0)
907 		return -errno;
908 
909 	len = snprintf(buf, sizeof(buf), "%" PRIu64, num);
910 	if (len < 0 || (size_t) len >= sizeof(buf))
911 		rc = len < 0 ? -errno : -E2BIG;
912 	else
913 		rc = write_all(fd, buf, len);
914 
915 	errsv = errno;
916 	close(fd);
917 	errno = errsv;
918 	return rc;
919 }
920 
ul_path_writef_u64(struct path_cxt * pc,uint64_t num,const char * path,...)921 int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
922 {
923 	const char *p;
924 	va_list ap;
925 
926 	va_start(ap, path);
927 	p = ul_path_mkpath(pc, path, ap);
928 	va_end(ap);
929 
930 	return !p ? -errno : ul_path_write_u64(pc, num, p);
931 
932 }
933 
ul_path_count_dirents(struct path_cxt * pc,const char * path)934 int ul_path_count_dirents(struct path_cxt *pc, const char *path)
935 {
936 	DIR *dir;
937 	int r = 0;
938 
939 	dir = ul_path_opendir(pc, path);
940 	if (!dir)
941 		return 0;
942 
943 	while (xreaddir(dir)) r++;
944 
945 	closedir(dir);
946 	return r;
947 }
948 
ul_path_countf_dirents(struct path_cxt * pc,const char * path,...)949 int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
950 {
951 	const char *p;
952 	va_list ap;
953 
954 	va_start(ap, path);
955 	p = ul_path_mkpath(pc, path, ap);
956 	va_end(ap);
957 
958 	return !p ? -errno : ul_path_count_dirents(pc, p);
959 }
960 
961 /*
962  * Like fopen() but, @path is always prefixed by @prefix. This function is
963  * useful in case when ul_path_* API is overkill.
964  */
ul_prefix_fopen(const char * prefix,const char * path,const char * mode)965 FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode)
966 {
967 	char buf[PATH_MAX];
968 
969 	if (!path)
970 		return NULL;
971 	if (!prefix)
972 		return fopen(path, mode);
973 	if (*path == '/')
974 		path++;
975 
976 	snprintf(buf, sizeof(buf), "%s/%s", prefix, path);
977 	return fopen(buf, mode);
978 }
979 
980 #ifdef HAVE_CPU_SET_T
ul_path_cpuparse(struct path_cxt * pc,cpu_set_t ** set,int maxcpus,int islist,const char * path,va_list ap)981 static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap)
982 {
983 	FILE *f;
984 	size_t setsize, len = maxcpus * 7;
985 	char buf[len];
986 	int rc;
987 
988 	*set = NULL;
989 
990 	f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
991 	if (!f)
992 		return -errno;
993 
994 	rc = fgets(buf, len, f) == NULL ? -errno : 0;
995 	fclose(f);
996 
997 	if (rc)
998 		return rc;
999 
1000 	len = strlen(buf);
1001 	if (buf[len - 1] == '\n')
1002 		buf[len - 1] = '\0';
1003 
1004 	*set = cpuset_alloc(maxcpus, &setsize, NULL);
1005 	if (!*set)
1006 		return -ENOMEM;
1007 
1008 	if (islist) {
1009 		if (cpulist_parse(buf, *set, setsize, 0)) {
1010 			cpuset_free(*set);
1011 			return -EINVAL;
1012 		}
1013 	} else {
1014 		if (cpumask_parse(buf, *set, setsize)) {
1015 			cpuset_free(*set);
1016 			return -EINVAL;
1017 		}
1018 	}
1019 	return 0;
1020 }
1021 
ul_path_readf_cpuset(struct path_cxt * pc,cpu_set_t ** set,int maxcpus,const char * path,...)1022 int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
1023 {
1024 	va_list ap;
1025 	int rc = 0;
1026 
1027 	va_start(ap, path);
1028 	rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap);
1029 	va_end(ap);
1030 
1031 	return rc;
1032 }
1033 
ul_path_readf_cpulist(struct path_cxt * pc,cpu_set_t ** set,int maxcpus,const char * path,...)1034 int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
1035 {
1036 	va_list ap;
1037 	int rc = 0;
1038 
1039 	va_start(ap, path);
1040 	rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap);
1041 	va_end(ap);
1042 
1043 	return rc;
1044 }
1045 
1046 #endif /* HAVE_CPU_SET_T */
1047 
1048 
1049 #ifdef TEST_PROGRAM_PATH
1050 #include <getopt.h>
1051 
usage(void)1052 static void __attribute__((__noreturn__)) usage(void)
1053 {
1054 	fprintf(stdout, " %s [options] <dir> <command>\n\n", program_invocation_short_name);
1055 	fputs(" -p, --prefix <dir>      redirect hardcoded paths to <dir>\n", stdout);
1056 
1057 	fputs(" Commands:\n", stdout);
1058 	fputs(" read-u64 <file>            read uint64_t from file\n", stdout);
1059 	fputs(" read-s64 <file>            read  int64_t from file\n", stdout);
1060 	fputs(" read-u32 <file>            read uint32_t from file\n", stdout);
1061 	fputs(" read-s32 <file>            read  int32_t from file\n", stdout);
1062 	fputs(" read-string <file>         read string  from file\n", stdout);
1063 	fputs(" read-majmin <file>         read devno from file\n", stdout);
1064 	fputs(" read-link <file>           read symlink\n", stdout);
1065 	fputs(" write-string <file> <str>  write string from file\n", stdout);
1066 	fputs(" write-u64 <file> <str>     write uint64_t from file\n", stdout);
1067 
1068 	exit(EXIT_SUCCESS);
1069 }
1070 
main(int argc,char * argv[])1071 int main(int argc, char *argv[])
1072 {
1073 	int c;
1074 	const char *prefix = NULL, *dir, *file, *command;
1075 	struct path_cxt *pc = NULL;
1076 
1077 	static const struct option longopts[] = {
1078 		{ "prefix",	1, NULL, 'p' },
1079 		{ "help",       0, NULL, 'h' },
1080 		{ NULL, 0, NULL, 0 },
1081 	};
1082 
1083 	while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) {
1084 		switch(c) {
1085 		case 'p':
1086 			prefix = optarg;
1087 			break;
1088 		case 'h':
1089 			usage();
1090 			break;
1091 		default:
1092 			err(EXIT_FAILURE, "try --help");
1093 		}
1094 	}
1095 
1096 	if (optind == argc)
1097 		errx(EXIT_FAILURE, "<dir> not defined");
1098 	dir = argv[optind++];
1099 
1100 	ul_path_init_debug();
1101 
1102 	pc = ul_new_path(dir);
1103 	if (!pc)
1104 		err(EXIT_FAILURE, "failed to initialize path context");
1105 	if (prefix)
1106 		ul_path_set_prefix(pc, prefix);
1107 
1108 	if (optind == argc)
1109 		errx(EXIT_FAILURE, "<command> not defined");
1110 	command = argv[optind++];
1111 
1112 	if (strcmp(command, "read-u32") == 0) {
1113 		uint32_t res;
1114 
1115 		if (optind == argc)
1116 			errx(EXIT_FAILURE, "<file> not defined");
1117 		file = argv[optind++];
1118 
1119 		if (ul_path_read_u32(pc, &res, file) != 0)
1120 			err(EXIT_FAILURE, "read u64 failed");
1121 		printf("read:  %s: %u\n", file, res);
1122 
1123 		if (ul_path_readf_u32(pc, &res, "%s", file) != 0)
1124 			err(EXIT_FAILURE, "readf u64 failed");
1125 		printf("readf: %s: %u\n", file, res);
1126 
1127 	} else if (strcmp(command, "read-s32") == 0) {
1128 		int32_t res;
1129 
1130 		if (optind == argc)
1131 			errx(EXIT_FAILURE, "<file> not defined");
1132 		file = argv[optind++];
1133 
1134 		if (ul_path_read_s32(pc, &res, file) != 0)
1135 			err(EXIT_FAILURE, "read u64 failed");
1136 		printf("read:  %s: %d\n", file, res);
1137 
1138 		if (ul_path_readf_s32(pc, &res, "%s", file) != 0)
1139 			err(EXIT_FAILURE, "readf u64 failed");
1140 		printf("readf: %s: %d\n", file, res);
1141 
1142 	} else if (strcmp(command, "read-u64") == 0) {
1143 		uint64_t res;
1144 
1145 		if (optind == argc)
1146 			errx(EXIT_FAILURE, "<file> not defined");
1147 		file = argv[optind++];
1148 
1149 		if (ul_path_read_u64(pc, &res, file) != 0)
1150 			err(EXIT_FAILURE, "read u64 failed");
1151 		printf("read:  %s: %" PRIu64 "\n", file, res);
1152 
1153 		if (ul_path_readf_u64(pc, &res, "%s", file) != 0)
1154 			err(EXIT_FAILURE, "readf u64 failed");
1155 		printf("readf: %s: %" PRIu64 "\n", file, res);
1156 
1157 	} else if (strcmp(command, "read-s64") == 0) {
1158 		int64_t res;
1159 
1160 		if (optind == argc)
1161 			errx(EXIT_FAILURE, "<file> not defined");
1162 		file = argv[optind++];
1163 
1164 		if (ul_path_read_s64(pc, &res, file) != 0)
1165 			err(EXIT_FAILURE, "read u64 failed");
1166 		printf("read:  %s: %" PRIu64 "\n", file, res);
1167 
1168 		if (ul_path_readf_s64(pc, &res, "%s", file) != 0)
1169 			err(EXIT_FAILURE, "readf u64 failed");
1170 		printf("readf: %s: %" PRIu64 "\n", file, res);
1171 
1172 	} else if (strcmp(command, "read-majmin") == 0) {
1173 		dev_t res;
1174 
1175 		if (optind == argc)
1176 			errx(EXIT_FAILURE, "<file> not defined");
1177 		file = argv[optind++];
1178 
1179 		if (ul_path_read_majmin(pc, &res, file) != 0)
1180 			err(EXIT_FAILURE, "read maj:min failed");
1181 		printf("read:  %s: %d\n", file, (int) res);
1182 
1183 		if (ul_path_readf_majmin(pc, &res, "%s", file) != 0)
1184 			err(EXIT_FAILURE, "readf maj:min failed");
1185 		printf("readf: %s: %d\n", file, (int) res);
1186 
1187 	} else if (strcmp(command, "read-string") == 0) {
1188 		char *res;
1189 
1190 		if (optind == argc)
1191 			errx(EXIT_FAILURE, "<file> not defined");
1192 		file = argv[optind++];
1193 
1194 		if (ul_path_read_string(pc, &res, file) < 0)
1195 			err(EXIT_FAILURE, "read string failed");
1196 		printf("read:  %s: %s\n", file, res);
1197 
1198 		if (ul_path_readf_string(pc, &res, "%s", file) < 0)
1199 			err(EXIT_FAILURE, "readf string failed");
1200 		printf("readf: %s: %s\n", file, res);
1201 
1202 	} else if (strcmp(command, "read-link") == 0) {
1203 		char res[PATH_MAX];
1204 
1205 		if (optind == argc)
1206 			errx(EXIT_FAILURE, "<file> not defined");
1207 		file = argv[optind++];
1208 
1209 		if (ul_path_readlink(pc, res, sizeof(res), file) < 0)
1210 			err(EXIT_FAILURE, "read symlink failed");
1211 		printf("read:  %s: %s\n", file, res);
1212 
1213 		if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0)
1214 			err(EXIT_FAILURE, "readf symlink failed");
1215 		printf("readf: %s: %s\n", file, res);
1216 
1217 	} else if (strcmp(command, "write-string") == 0) {
1218 		char *str;
1219 
1220 		if (optind + 1 == argc)
1221 			errx(EXIT_FAILURE, "<file> <string> not defined");
1222 		file = argv[optind++];
1223 		str = argv[optind++];
1224 
1225 		if (ul_path_write_string(pc, str, file) != 0)
1226 			err(EXIT_FAILURE, "write string failed");
1227 		if (ul_path_writef_string(pc, str, "%s", file) != 0)
1228 			err(EXIT_FAILURE, "writef string failed");
1229 
1230 	} else if (strcmp(command, "write-u64") == 0) {
1231 		uint64_t num;
1232 
1233 		if (optind + 1 == argc)
1234 			errx(EXIT_FAILURE, "<file> <num> not defined");
1235 		file = argv[optind++];
1236 		num = strtoumax(argv[optind++], NULL, 0);
1237 
1238 		if (ul_path_write_u64(pc, num, file) != 0)
1239 			err(EXIT_FAILURE, "write u64 failed");
1240 		if (ul_path_writef_u64(pc, num, "%s", file) != 0)
1241 			err(EXIT_FAILURE, "writef u64 failed");
1242 	}
1243 
1244 	ul_unref_path(pc);
1245 	return EXIT_SUCCESS;
1246 }
1247 #endif /* TEST_PROGRAM_PATH */
1248 
1249