#include <sys/xattr.h>
#include <libgen.h>
#include <sys/statvfs.h>
+#include <sys/file.h>
#include <linux/fs.h>
#undef basename
int safe_atod(const char *s, double *ret_d) {
char *x = NULL;
double d = 0;
+ locale_t loc;
assert(s);
assert(ret_d);
- RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") {
- errno = 0;
- d = strtod(s, &x);
- }
+ loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+ if (loc == (locale_t) 0)
+ return -errno;
- if (!x || x == s || *x || errno)
+ errno = 0;
+ d = strtod_l(s, &x, loc);
+
+ if (!x || x == s || *x || errno) {
+ freelocale(loc);
return errno ? -errno : -EINVAL;
+ }
+ freelocale(loc);
*ret_d = (double) d;
return 0;
}
memcpy(r, prefix, pl);
for (f = s, t = r + pl; f < s + length; f++) {
+ size_t remaining = s + length - f;
+ assert(remaining > 0);
- if (*f != '\\') {
+ if (*f != '\\') { /* a literal literal */
*(t++) = *f;
continue;
}
+ if (--remaining == 0) { /* copy trailing backslash verbatim */
+ *(t++) = *f;
+ break;
+ }
+
f++;
switch (*f) {
case 'x': {
/* hexadecimal encoding */
- int a, b;
+ int a = -1, b = -1;
- a = unhexchar(f[1]);
- b = unhexchar(f[2]);
+ if (remaining >= 2) {
+ a = unhexchar(f[1]);
+ b = unhexchar(f[2]);
+ }
if (a < 0 || b < 0 || (a == 0 && b == 0)) {
/* Invalid escape code, let's take it literal then */
case '6':
case '7': {
/* octal encoding */
- int a, b, c;
+ int a = -1, b = -1, c = -1;
- a = unoctchar(f[0]);
- b = unoctchar(f[1]);
- c = unoctchar(f[2]);
+ if (remaining >= 3) {
+ a = unoctchar(f[0]);
+ b = unoctchar(f[1]);
+ c = unoctchar(f[2]);
+ }
if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) {
/* Invalid escape code, let's take it literal then */
break;
}
- case 0:
- /* premature end of string. */
- *(t++) = '\\';
- goto finish;
-
default:
/* Invalid escape code, let's take it literal then */
*(t++) = '\\';
}
}
-finish:
*t = 0;
return r;
}
endswith(filename, ".dpkg-old") ||
endswith(filename, ".dpkg-new") ||
endswith(filename, ".dpkg-tmp") ||
+ endswith(filename, ".dpkg-dist") ||
+ endswith(filename, ".dpkg-bak") ||
+ endswith(filename, ".dpkg-backup") ||
+ endswith(filename, ".dpkg-remove") ||
endswith(filename, ".swp");
}
return endswith(de->d_name, suffix);
}
-static int do_execute(const char *directory, usec_t timeout, char *argv[]) {
+static int do_execute(char **directories, usec_t timeout, char *argv[]) {
_cleanup_hashmap_free_free_ Hashmap *pids = NULL;
- _cleanup_closedir_ DIR *d;
- struct dirent *de;
+ _cleanup_set_free_free_ Set *seen = NULL;
+ char **directory;
/* We fork this all off from a child process so that we can
* somewhat cleanly make use of SIGALRM to set a time limit */
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
- d = opendir(directory);
- if (!d) {
- if (errno == ENOENT)
- return 0;
-
- return log_error_errno(errno, "Failed to open directory %s: %m", directory);
- }
-
pids = hashmap_new(NULL);
if (!pids)
return log_oom();
- FOREACH_DIRENT(de, d, break) {
- _cleanup_free_ char *path = NULL;
- pid_t pid;
- int r;
+ seen = set_new(&string_hash_ops);
+ if (!seen)
+ return log_oom();
- if (!dirent_is_file(de))
- continue;
+ STRV_FOREACH(directory, directories) {
+ _cleanup_closedir_ DIR *d;
+ struct dirent *de;
- path = strjoin(directory, "/", de->d_name, NULL);
- if (!path)
- return log_oom();
+ d = opendir(*directory);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
- pid = fork();
- if (pid < 0) {
- log_error_errno(errno, "Failed to fork: %m");
- continue;
- } else if (pid == 0) {
- char *_argv[2];
+ return log_error_errno(errno, "Failed to open directory %s: %m", *directory);
+ }
+
+ FOREACH_DIRENT(de, d, break) {
+ _cleanup_free_ char *path = NULL;
+ pid_t pid;
+ int r;
+
+ if (!dirent_is_file(de))
+ continue;
+
+ if (set_contains(seen, de->d_name)) {
+ log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name);
+ continue;
+ }
+
+ r = set_put_strdup(seen, de->d_name);
+ if (r < 0)
+ return log_oom();
- assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+ path = strjoin(*directory, "/", de->d_name, NULL);
+ if (!path)
+ return log_oom();
- if (!argv) {
- _argv[0] = path;
- _argv[1] = NULL;
- argv = _argv;
+ if (null_or_empty_path(path)) {
+ log_debug("%s is empty (a mask).", path);
+ continue;
} else
- argv[0] = path;
+ log_debug("%s will be executed.", path);
- execv(path, argv);
- return log_error_errno(errno, "Failed to execute %s: %m", path);
- }
+ pid = fork();
+ if (pid < 0) {
+ log_error_errno(errno, "Failed to fork: %m");
+ continue;
+ } else if (pid == 0) {
+ char *_argv[2];
- log_debug("Spawned %s as " PID_FMT ".", path, pid);
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
- r = hashmap_put(pids, UINT_TO_PTR(pid), path);
- if (r < 0)
- return log_oom();
+ if (!argv) {
+ _argv[0] = path;
+ _argv[1] = NULL;
+ argv = _argv;
+ } else
+ argv[0] = path;
- path = NULL;
+ execv(path, argv);
+ return log_error_errno(errno, "Failed to execute %s: %m", path);
+ }
+
+ log_debug("Spawned %s as " PID_FMT ".", path, pid);
+
+ r = hashmap_put(pids, UINT_TO_PTR(pid), path);
+ if (r < 0)
+ return log_oom();
+ path = NULL;
+ }
}
/* Abort execution of this process after the timout. We simply
return 0;
}
-void execute_directory(const char *directory, usec_t timeout, char *argv[]) {
+void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) {
pid_t executor_pid;
int r;
+ char *name;
+ char **dirs = (char**) directories;
- assert(directory);
+ assert(!strv_isempty(dirs));
- /* Executes all binaries in the directory in parallel and waits
- * for them to finish. Optionally a timeout is applied. */
+ name = basename(dirs[0]);
+ assert(!isempty(name));
+
+ /* Executes all binaries in the directories in parallel and waits
+ * for them to finish. Optionally a timeout is applied. If a file
+ * with the same name exists in more than one directory, the
+ * earliest one wins. */
executor_pid = fork();
if (executor_pid < 0) {
return;
} else if (executor_pid == 0) {
- r = do_execute(directory, timeout, argv);
+ r = do_execute(dirs, timeout, argv);
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
- wait_for_terminate_and_warn(directory, executor_pid, true);
+ wait_for_terminate_and_warn(name, executor_pid, true);
}
int kill_and_sigcont(pid_t pid, int sig) {
if (isempty(s))
return false;
+ /* Doesn't accept empty hostnames, hostnames with trailing or
+ * leading dots, and hostnames with multiple dots in a
+ * sequence. Also ensures that the length stays below
+ * HOST_NAME_MAX. */
+
for (p = s, dot = true; *p; p++) {
if (*p == '.') {
if (dot)
return true;
}
-bool image_name_is_valid(const char *s) {
- if (!filename_is_valid(s))
- return false;
-
- if (string_has_cc(s, NULL))
- return false;
-
- if (!utf8_is_valid(s))
- return false;
-
- /* Temporary files for atomically creating new files */
- if (startswith(s, ".#"))
- return false;
-
- return true;
-}
-
int pipe_eof(int fd) {
struct pollfd pollfd = {
.fd = fd,
return r;
}
-bool is_valid_documentation_url(const char *url) {
- assert(url);
+bool http_etag_is_valid(const char *etag) {
+ if (isempty(etag))
+ return false;
- if (startswith(url, "http://") && url[7])
- return true;
+ if (!endswith(etag, "\""))
+ return false;
- if (startswith(url, "https://") && url[8])
- return true;
+ if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
+ return false;
- if (startswith(url, "file:") && url[5])
- return true;
+ return true;
+}
- if (startswith(url, "info:") && url[5])
- return true;
+bool http_url_is_valid(const char *url) {
+ const char *p;
+
+ if (isempty(url))
+ return false;
- if (startswith(url, "man:") && url[4])
+ p = startswith(url, "http://");
+ if (!p)
+ p = startswith(url, "https://");
+ if (!p)
+ return false;
+
+ if (isempty(p))
+ return false;
+
+ return ascii_is_valid(p);
+}
+
+bool documentation_url_is_valid(const char *url) {
+ const char *p;
+
+ if (isempty(url))
+ return false;
+
+ if (http_url_is_valid(url))
return true;
- return false;
+ p = startswith(url, "file:/");
+ if (!p)
+ p = startswith(url, "info:");
+ if (!p)
+ p = startswith(url, "man:");
+
+ if (isempty(p))
+ return false;
+
+ return ascii_is_valid(p);
}
bool in_initrd(void) {
return (uint64_t) mem * (uint64_t) page_size();
}
-char* mount_test_option(const char *haystack, const char *needle) {
-
- struct mntent me = {
- .mnt_opts = (char*) haystack
- };
-
- assert(needle);
-
- /* Like glibc's hasmntopt(), but works on a string, not a
- * struct mntent */
-
- if (!haystack)
- return NULL;
-
- return hasmntopt(&me, needle);
-}
-
void hexdump(FILE *f, const void *p, size_t s) {
const uint8_t *b = p;
unsigned n = 0;
return fa == fb;
}
-int chattr_fd(int fd, bool b, int mask) {
- int old_attr, new_attr;
+int chattr_fd(int fd, bool b, unsigned mask) {
+ unsigned old_attr, new_attr;
assert(fd >= 0);
+ if (mask == 0)
+ return 0;
+
if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0)
return -errno;
return 0;
}
-int chattr_path(const char *p, bool b, int mask) {
+int chattr_path(const char *p, bool b, unsigned mask) {
_cleanup_close_ int fd = -1;
- fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ assert(p);
+
+ if (mask == 0)
+ return 0;
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
if (fd < 0)
return -errno;
return chattr_fd(fd, b, mask);
}
+
+int read_attr_fd(int fd, unsigned *ret) {
+ assert(fd >= 0);
+
+ if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int read_attr_path(const char *p, unsigned *ret) {
+ _cleanup_close_ int fd = -1;
+
+ assert(p);
+ assert(ret);
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ return read_attr_fd(fd, ret);
+}
+
+int make_lock_file(const char *p, int operation, LockFile *ret) {
+ _cleanup_close_ int fd = -1;
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ /*
+ * We use UNPOSIX locks if they are available. They have nice
+ * semantics, and are mostly compatible with NFS. However,
+ * they are only available on new kernels. When we detect we
+ * are running on an older kernel, then we fall back to good
+ * old BSD locks. They also have nice semantics, but are
+ * slightly problematic on NFS, where they are upgraded to
+ * POSIX locks, even though locally they are orthogonal to
+ * POSIX locks.
+ */
+
+ t = strdup(p);
+ if (!t)
+ return -ENOMEM;
+
+ for (;;) {
+ struct flock fl = {
+ .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
+ .l_whence = SEEK_SET,
+ };
+ struct stat st;
+
+ fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
+ if (fd < 0)
+ return -errno;
+
+ r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
+ if (r < 0) {
+
+ /* If the kernel is too old, use good old BSD locks */
+ if (errno == EINVAL)
+ r = flock(fd, operation);
+
+ if (r < 0)
+ return errno == EAGAIN ? -EBUSY : -errno;
+ }
+
+ /* If we acquired the lock, let's check if the file
+ * still exists in the file system. If not, then the
+ * previous exclusive owner removed it and then closed
+ * it. In such a case our acquired lock is worthless,
+ * hence try again. */
+
+ r = fstat(fd, &st);
+ if (r < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ fd = safe_close(fd);
+ }
+
+ ret->path = t;
+ ret->fd = fd;
+ ret->operation = operation;
+
+ fd = -1;
+ t = NULL;
+
+ return r;
+}
+
+int make_lock_file_for(const char *p, int operation, LockFile *ret) {
+ const char *fn;
+ char *t;
+
+ assert(p);
+ assert(ret);
+
+ fn = basename(p);
+ if (!filename_is_valid(fn))
+ return -EINVAL;
+
+ t = newa(char, strlen(p) + 2 + 4 + 1);
+ stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck");
+
+ return make_lock_file(t, operation, ret);
+}
+
+void release_lock_file(LockFile *f) {
+ int r;
+
+ if (!f)
+ return;
+
+ if (f->path) {
+
+ /* If we are the exclusive owner we can safely delete
+ * the lock file itself. If we are not the exclusive
+ * owner, we can try becoming it. */
+
+ if (f->fd >= 0 &&
+ (f->operation & ~LOCK_NB) == LOCK_SH) {
+ static const struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ };
+
+ r = fcntl(f->fd, F_OFD_SETLK, &fl);
+ if (r < 0 && errno == EINVAL)
+ r = flock(f->fd, LOCK_EX|LOCK_NB);
+
+ if (r >= 0)
+ f->operation = LOCK_EX|LOCK_NB;
+ }
+
+ if ((f->operation & ~LOCK_NB) == LOCK_EX)
+ unlink_noerrno(f->path);
+
+ free(f->path);
+ f->path = NULL;
+ }
+
+ f->fd = safe_close(f->fd);
+ f->operation = 0;
+}
+
+static size_t nul_length(const uint8_t *p, size_t sz) {
+ size_t n = 0;
+
+ while (sz > 0) {
+ if (*p != 0)
+ break;
+
+ n++;
+ p++;
+ sz--;
+ }
+
+ return n;
+}
+
+ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
+ const uint8_t *q, *w, *e;
+ ssize_t l;
+
+ q = w = p;
+ e = q + sz;
+ while (q < e) {
+ size_t n;
+
+ n = nul_length(q, e - q);
+
+ /* If there are more than the specified run length of
+ * NUL bytes, or if this is the beginning or the end
+ * of the buffer, then seek instead of write */
+ if ((n > run_length) ||
+ (n > 0 && q == p) ||
+ (n > 0 && q + n >= e)) {
+ if (q > w) {
+ l = write(fd, w, q - w);
+ if (l < 0)
+ return -errno;
+ if (l != q -w)
+ return -EIO;
+ }
+
+ if (lseek(fd, n, SEEK_CUR) == (off_t) -1)
+ return -errno;
+
+ q += n;
+ w = q;
+ } else if (n > 0)
+ q += n;
+ else
+ q ++;
+ }
+
+ if (q > w) {
+ l = write(fd, w, q - w);
+ if (l < 0)
+ return -errno;
+ if (l != q - w)
+ return -EIO;
+ }
+
+ return q - (const uint8_t*) p;
+}
+
+void sigkill_wait(pid_t *pid) {
+ if (!pid)
+ return;
+ if (*pid <= 1)
+ return;
+
+ if (kill(*pid, SIGKILL) > 0)
+ (void) wait_for_terminate(*pid, NULL);
+}
+
+int syslog_parse_priority(const char **p, int *priority, bool with_facility) {
+ int a = 0, b = 0, c = 0;
+ int k;
+
+ assert(p);
+ assert(*p);
+ assert(priority);
+
+ if ((*p)[0] != '<')
+ return 0;
+
+ if (!strchr(*p, '>'))
+ return 0;
+
+ if ((*p)[2] == '>') {
+ c = undecchar((*p)[1]);
+ k = 3;
+ } else if ((*p)[3] == '>') {
+ b = undecchar((*p)[1]);
+ c = undecchar((*p)[2]);
+ k = 4;
+ } else if ((*p)[4] == '>') {
+ a = undecchar((*p)[1]);
+ b = undecchar((*p)[2]);
+ c = undecchar((*p)[3]);
+ k = 5;
+ } else
+ return 0;
+
+ if (a < 0 || b < 0 || c < 0 ||
+ (!with_facility && (a || b || c > 7)))
+ return 0;
+
+ if (with_facility)
+ *priority = a*100 + b*10 + c;
+ else
+ *priority = (*priority & LOG_FACMASK) | c;
+
+ *p += k;
+ return 1;
+}