diff options
Diffstat (limited to 'libudev')
-rw-r--r-- | libudev/libudev-device-db-write.c | 3 | ||||
-rw-r--r-- | libudev/libudev-private.h | 31 | ||||
-rw-r--r-- | libudev/libudev-queue-export.c | 7 | ||||
-rw-r--r-- | libudev/libudev-selinux-private.c | 83 | ||||
-rw-r--r-- | libudev/libudev-util-private.c | 446 |
5 files changed, 563 insertions, 7 deletions
diff --git a/libudev/libudev-device-db-write.c b/libudev/libudev-device-db-write.c index a8e66f7884..68dc0a5b98 100644 --- a/libudev/libudev-device-db-write.c +++ b/libudev/libudev-device-db-write.c @@ -18,7 +18,8 @@ #include <string.h> #include <sys/stat.h> -#include "udev.h" +#include "libudev.h" +#include "libudev-private.h" static size_t devpath_to_db_path(struct udev *udev, const char *devpath, char *filename, size_t len) { diff --git a/libudev/libudev-private.h b/libudev/libudev-private.h index 21eb626dc1..9cda7bcb1e 100644 --- a/libudev/libudev-private.h +++ b/libudev/libudev-private.h @@ -16,6 +16,9 @@ #include "libudev.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define UDEV_MAX(a,b) ((a) > (b) ? (a) : (b)) +#define READ_END 0 +#define WRITE_END 1 static inline void __attribute__((always_inline, format(printf, 2, 3))) udev_log_null(struct udev *udev, const char *format, ...) {} @@ -182,7 +185,7 @@ int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); int udev_queue_export_device_failed(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); -/* libudev-utils.c */ +/* libudev-util.c */ #define UTIL_PATH_SIZE 1024 #define UTIL_LINE_SIZE 2048 #define UTIL_NAME_SIZE 512 @@ -203,4 +206,30 @@ int udev_util_replace_chars(char *str, const char *white); int udev_util_encode_string(const char *str, char *str_enc, size_t len); void util_set_fd_cloexec(int fd); unsigned int util_string_hash32(const char *str); + +/* libudev-util-private.c */ +int util_create_path(struct udev *udev, const char *path); +int util_delete_path(struct udev *udev, const char *path); +int util_unlink_secure(struct udev *udev, const char *filename); +uid_t util_lookup_user(struct udev *udev, const char *user); +gid_t util_lookup_group(struct udev *udev, const char *group); +int util_run_program(struct udev *udev, const char *command, char **envp, + char *result, size_t ressize, size_t *reslen); +int util_resolve_subsys_kernel(struct udev *udev, const char *string, + char *result, size_t maxsize, int read_value); + +/* libudev-selinux-private.c */ +#ifndef USE_SELINUX +static inline void udev_selinux_init(struct udev *udev) {} +static inline void udev_selinux_exit(struct udev *udev) {} +static inline void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode) {} +static inline void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode) {} +static inline void udev_selinux_resetfscreatecon(struct udev *udev) {} +#else +void udev_selinux_init(struct udev *udev); +void udev_selinux_exit(struct udev *udev); +void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode); +void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode); +void udev_selinux_resetfscreatecon(struct udev *udev); +#endif #endif diff --git a/libudev/libudev-queue-export.c b/libudev/libudev-queue-export.c index a36ff5150a..9ae680c386 100644 --- a/libudev/libudev-queue-export.c +++ b/libudev/libudev-queue-export.c @@ -14,7 +14,6 @@ * DISCLAIMER - The file format mentioned here is private to udev/libudev, * and may be changed without notice. * - * * The udev event queue is exported as a binary log file. * Each log record consists of a sequence number followed by the device path. * @@ -31,7 +30,6 @@ * The queue does not grow indefinitely. It is periodically re-created * to remove finished events. Atomic rename() makes this transparent to readers. * - * * The queue file starts with a single sequence number which specifies the * minimum sequence number in the log that follows. Any events prior to this * sequence number have already finished. @@ -48,7 +46,8 @@ #include <sys/types.h> #include <assert.h> -#include "udev.h" +#include "libudev.h" +#include "libudev-private.h" static int rebuild_queue_file(struct udev_queue_export *udev_queue_export); @@ -108,7 +107,6 @@ void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export) unlink(filename); } - static int skip_to(FILE *file, long offset) { long old_offset; @@ -320,7 +318,6 @@ write_error: return -1; } - enum device_state { DEVICE_QUEUED, DEVICE_FINISHED, diff --git a/libudev/libudev-selinux-private.c b/libudev/libudev-selinux-private.c new file mode 100644 index 0000000000..84f8b6a63f --- /dev/null +++ b/libudev/libudev-selinux-private.c @@ -0,0 +1,83 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <unistd.h> +#include <selinux/selinux.h> + +#include "libudev.h" +#include "libudev-private.h" + +static int selinux_enabled; +security_context_t selinux_prev_scontext; + +void udev_selinux_init(struct udev *udev) +{ + /* record the present security context */ + selinux_enabled = (is_selinux_enabled() > 0); + info(udev, "selinux=%i\n", selinux_enabled); + if (!selinux_enabled) + return; + matchpathcon_init_prefix(NULL, udev_get_dev_path(udev)); + if (getfscreatecon(&selinux_prev_scontext) < 0) { + err(udev, "getfscreatecon failed\n"); + selinux_prev_scontext = NULL; + } +} + +void udev_selinux_exit(struct udev *udev) +{ + if (!selinux_enabled) + return; + freecon(selinux_prev_scontext); + selinux_prev_scontext = NULL; +} + +void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode) +{ + security_context_t scontext = NULL; + + if (!selinux_enabled) + return; + if (matchpathcon(file, mode, &scontext) < 0) { + err(udev, "matchpathcon(%s) failed\n", file); + return; + } + if (lsetfilecon(file, scontext) < 0) + err(udev, "setfilecon %s failed: %m\n", file); + freecon(scontext); +} + +void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode) +{ + security_context_t scontext = NULL; + + if (!selinux_enabled) + return; + if (matchpathcon(file, mode, &scontext) < 0) { + err(udev, "matchpathcon(%s) failed\n", file); + return; + } + if (setfscreatecon(scontext) < 0) + err(udev, "setfscreatecon %s failed: %m\n", file); + freecon(scontext); +} + +void udev_selinux_resetfscreatecon(struct udev *udev) +{ + if (!selinux_enabled) + return; + if (setfscreatecon(selinux_prev_scontext) < 0) + err(udev, "setfscreatecon failed: %m\n"); +} diff --git a/libudev/libudev-util-private.c b/libudev/libudev-util-private.c new file mode 100644 index 0000000000..f22c04184b --- /dev/null +++ b/libudev/libudev-util-private.c @@ -0,0 +1,446 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2004-2009 Kay Sievers <kay.sievers@vrfy.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <sys/wait.h> + +#include "libudev.h" +#include "libudev-private.h" + +int util_create_path(struct udev *udev, const char *path) +{ + char p[UTIL_PATH_SIZE]; + char *pos; + struct stat stats; + int ret; + + util_strscpy(p, sizeof(p), path); + pos = strrchr(p, '/'); + if (pos == p || pos == NULL) + return 0; + + while (pos[-1] == '/') + pos--; + pos[0] = '\0'; + + dbg(udev, "stat '%s'\n", p); + if (stat(p, &stats) == 0 && (stats.st_mode & S_IFMT) == S_IFDIR) + return 0; + + if (util_create_path(udev, p) != 0) + return -1; + + dbg(udev, "mkdir '%s'\n", p); + udev_selinux_setfscreatecon(udev, p, S_IFDIR|0755); + ret = mkdir(p, 0755); + udev_selinux_resetfscreatecon(udev); + if (ret == 0) + return 0; + + if (errno == EEXIST) + if (stat(p, &stats) == 0 && (stats.st_mode & S_IFMT) == S_IFDIR) + return 0; + return -1; +} + +int util_delete_path(struct udev *udev, const char *path) +{ + char p[UTIL_PATH_SIZE]; + char *pos; + int retval; + + strcpy (p, path); + pos = strrchr(p, '/'); + if (pos == p || pos == NULL) + return 0; + + while (1) { + *pos = '\0'; + pos = strrchr(p, '/'); + + /* don't remove the last one */ + if ((pos == p) || (pos == NULL)) + break; + + /* remove if empty */ + retval = rmdir(p); + if (errno == ENOENT) + retval = 0; + if (retval) { + if (errno == ENOTEMPTY) + return 0; + err(udev, "rmdir(%s) failed: %m\n", p); + break; + } + dbg(udev, "removed '%s'\n", p); + } + return 0; +} + +/* Reset permissions on the device node, before unlinking it to make sure, + * that permisions of possible hard links will be removed too. + */ +int util_unlink_secure(struct udev *udev, const char *filename) +{ + int retval; + + retval = chown(filename, 0, 0); + if (retval) + err(udev, "chown(%s, 0, 0) failed: %m\n", filename); + + retval = chmod(filename, 0000); + if (retval) + err(udev, "chmod(%s, 0000) failed: %m\n", filename); + + retval = unlink(filename); + if (errno == ENOENT) + retval = 0; + + if (retval) + err(udev, "unlink(%s) failed: %m\n", filename); + + return retval; +} + +uid_t util_lookup_user(struct udev *udev, const char *user) +{ + char *endptr; + int buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + char buf[buflen]; + struct passwd pwbuf; + struct passwd *pw; + uid_t uid; + + if (strcmp(user, "root") == 0) + return 0; + uid = strtoul(user, &endptr, 10); + if (endptr[0] == '\0') + return uid; + + errno = 0; + getpwnam_r(user, &pwbuf, buf, buflen, &pw); + if (pw != NULL) + return pw->pw_uid; + if (errno == 0 || errno == ENOENT || errno == ESRCH) + err(udev, "specified user '%s' unknown\n", user); + else + err(udev, "error resolving user '%s': %m\n", user); + return 0; +} + +gid_t util_lookup_group(struct udev *udev, const char *group) +{ + char *endptr; + int buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + char buf[buflen]; + struct group grbuf; + struct group *gr; + gid_t gid = 0; + + if (strcmp(group, "root") == 0) + return 0; + gid = strtoul(group, &endptr, 10); + if (endptr[0] == '\0') + return gid; + + errno = 0; + getgrnam_r(group, &grbuf, buf, buflen, &gr); + if (gr != NULL) + return gr->gr_gid; + if (errno == 0 || errno == ENOENT || errno == ESRCH) + err(udev, "specified group '%s' unknown\n", group); + else + err(udev, "error resolving group '%s': %m\n", group); + return 0; +} + +/* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */ +int util_resolve_subsys_kernel(struct udev *udev, const char *string, + char *result, size_t maxsize, int read_value) +{ + char temp[UTIL_PATH_SIZE]; + char *subsys; + char *sysname; + struct udev_device *dev; + char *attr; + + if (string[0] != '[') + return -1; + + util_strscpy(temp, sizeof(temp), string); + + subsys = &temp[1]; + + sysname = strchr(subsys, '/'); + if (sysname == NULL) + return -1; + sysname[0] = '\0'; + sysname = &sysname[1]; + + attr = strchr(sysname, ']'); + if (attr == NULL) + return -1; + attr[0] = '\0'; + attr = &attr[1]; + if (attr[0] == '/') + attr = &attr[1]; + if (attr[0] == '\0') + attr = NULL; + + if (read_value && attr == NULL) + return -1; + + dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname); + if (dev == NULL) + return -1; + + if (read_value) { + const char *val; + + val = udev_device_get_sysattr_value(dev, attr); + if (val != NULL) + util_strscpy(result, maxsize, val); + else + result[0] = '\0'; + info(udev, "value '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result); + } else { + size_t l; + char *s; + + s = result; + l = util_strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); + if (attr != NULL) + util_strpcpyl(&s, l, "/", attr, NULL); + info(udev, "path '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result); + } + udev_device_unref(dev); + return 0; +} + +int util_run_program(struct udev *udev, const char *command, char **envp, + char *result, size_t ressize, size_t *reslen) +{ + int status; + int outpipe[2] = {-1, -1}; + int errpipe[2] = {-1, -1}; + pid_t pid; + char arg[UTIL_PATH_SIZE]; + char program[UTIL_PATH_SIZE]; + char *argv[(sizeof(arg) / 2) + 1]; + int devnull; + int i; + int err = 0; + + /* build argv from command */ + util_strscpy(arg, sizeof(arg), command); + i = 0; + if (strchr(arg, ' ') != NULL) { + char *pos = arg; + + while (pos != NULL && pos[0] != '\0') { + if (pos[0] == '\'') { + /* do not separate quotes */ + pos++; + argv[i] = strsep(&pos, "\'"); + while (pos != NULL && pos[0] == ' ') + pos++; + } else { + argv[i] = strsep(&pos, " "); + } + dbg(udev, "arg[%i] '%s'\n", i, argv[i]); + i++; + } + argv[i] = NULL; + } else { + argv[0] = arg; + argv[1] = NULL; + } + info(udev, "'%s'\n", command); + + /* prepare pipes from child to parent */ + if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe(outpipe) != 0) { + err(udev, "pipe failed: %m\n"); + return -1; + } + } + if (udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe(errpipe) != 0) { + err(udev, "pipe failed: %m\n"); + return -1; + } + } + + /* allow programs in /lib/udev/ to be called without the path */ + if (argv[0][0] != '/') { + util_strscpyl(program, sizeof(program), UDEV_PREFIX "/lib/udev/", argv[0], NULL); + argv[0] = program; + } + + pid = fork(); + switch(pid) { + case 0: + /* child closes parent ends of pipes */ + if (outpipe[READ_END] > 0) + close(outpipe[READ_END]); + if (errpipe[READ_END] > 0) + close(errpipe[READ_END]); + + /* discard child output or connect to pipe */ + devnull = open("/dev/null", O_RDWR); + if (devnull > 0) { + dup2(devnull, STDIN_FILENO); + if (outpipe[WRITE_END] < 0) + dup2(devnull, STDOUT_FILENO); + if (errpipe[WRITE_END] < 0) + dup2(devnull, STDERR_FILENO); + close(devnull); + } else + err(udev, "open /dev/null failed: %m\n"); + if (outpipe[WRITE_END] > 0) { + dup2(outpipe[WRITE_END], STDOUT_FILENO); + close(outpipe[WRITE_END]); + } + if (errpipe[WRITE_END] > 0) { + dup2(errpipe[WRITE_END], STDERR_FILENO); + close(errpipe[WRITE_END]); + } + execve(argv[0], argv, envp); + if (errno == ENOENT || errno == ENOTDIR) { + /* may be on a filesytem which is not mounted right now */ + info(udev, "program '%s' not found\n", argv[0]); + } else { + /* other problems */ + err(udev, "exec of program '%s' failed\n", argv[0]); + } + _exit(1); + case -1: + err(udev, "fork of '%s' failed: %m\n", argv[0]); + return -1; + default: + /* read from child if requested */ + if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) { + ssize_t count; + size_t respos = 0; + + /* parent closes child ends of pipes */ + if (outpipe[WRITE_END] > 0) + close(outpipe[WRITE_END]); + if (errpipe[WRITE_END] > 0) + close(errpipe[WRITE_END]); + + /* read child output */ + while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) { + int fdcount; + fd_set readfds; + + FD_ZERO(&readfds); + if (outpipe[READ_END] > 0) + FD_SET(outpipe[READ_END], &readfds); + if (errpipe[READ_END] > 0) + FD_SET(errpipe[READ_END], &readfds); + fdcount = select(UDEV_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -1; + break; + } + + /* get stdout */ + if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) { + char inbuf[1024]; + char *pos; + char *line; + + count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1); + if (count <= 0) { + close(outpipe[READ_END]); + outpipe[READ_END] = -1; + if (count < 0) { + err(udev, "stdin read failed: %m\n"); + err = -1; + } + continue; + } + inbuf[count] = '\0'; + + /* store result for rule processing */ + if (result) { + if (respos + count < ressize) { + memcpy(&result[respos], inbuf, count); + respos += count; + } else { + err(udev, "ressize %ld too short\n", (long)ressize); + err = -1; + } + } + pos = inbuf; + while ((line = strsep(&pos, "\n"))) + if (pos || line[0] != '\0') + info(udev, "'%s' (stdout) '%s'\n", argv[0], line); + } + + /* get stderr */ + if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) { + char errbuf[1024]; + char *pos; + char *line; + + count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1); + if (count <= 0) { + close(errpipe[READ_END]); + errpipe[READ_END] = -1; + if (count < 0) + err(udev, "stderr read failed: %m\n"); + continue; + } + errbuf[count] = '\0'; + pos = errbuf; + while ((line = strsep(&pos, "\n"))) + if (pos || line[0] != '\0') + info(udev, "'%s' (stderr) '%s'\n", argv[0], line); + } + } + if (outpipe[READ_END] > 0) + close(outpipe[READ_END]); + if (errpipe[READ_END] > 0) + close(errpipe[READ_END]); + + /* return the childs stdout string */ + if (result) { + result[respos] = '\0'; + dbg(udev, "result='%s'\n", result); + if (reslen) + *reslen = respos; + } + } + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + info(udev, "'%s' returned with status %i\n", argv[0], WEXITSTATUS(status)); + if (WEXITSTATUS(status) != 0) + err = -1; + } else { + err(udev, "'%s' abnormal exit\n", command); + err = -1; + } + } + return err; +} |