#include <linux/tiocl.h>
#include <termios.h>
#include <stdarg.h>
-#include <sys/inotify.h>
#include <sys/poll.h>
#include <ctype.h>
#include <sys/prctl.h>
#include <langinfo.h>
#include <locale.h>
#include <sys/personality.h>
+#include <sys/xattr.h>
#include <libgen.h>
#undef basename
#include "gunicode.h"
#include "virt.h"
#include "def.h"
+#include "sparse-endian.h"
int saved_argc = 0;
char **saved_argv = NULL;
static size_t strcspn_escaped(const char *s, const char *reject) {
bool escaped = false;
- size_t n;
+ int n;
for (n=0; s[n]; n++) {
if (escaped)
else if (strchr(reject, s[n]))
break;
}
+
/* if s ends in \, return index of previous char */
return n - escaped;
}
*l = strcspn_escaped(current + 1, quotechars);
if (current[*l + 1] == '\0' ||
(current[*l + 2] && !strchr(separator, current[*l + 2]))) {
- /* right quote missing or garbage at the end*/
+ /* right quote missing or garbage at the end */
*state = current;
return NULL;
}
*state = current++ + *l + 2;
} else if (quoted) {
*l = strcspn_escaped(current, separator);
+ if (current[*l] && !strchr(separator, current[*l])) {
+ /* unfinished escape */
+ *state = current;
+ return NULL;
+ }
*state = current + *l;
} else {
*l = strcspn(current, separator);
}
case 0:
- /* premature end of string.*/
+ /* premature end of string. */
*(t++) = '\\';
goto finish;
return t;
}
-_pure_ static bool ignore_file_allow_backup(const char *filename) {
+_pure_ static bool hidden_file_allow_backup(const char *filename) {
assert(filename);
return
endswith(filename, ".swp");
}
-bool ignore_file(const char *filename) {
+bool hidden_file(const char *filename) {
assert(filename);
if (endswith(filename, "~"))
return true;
- return ignore_file_allow_backup(filename);
+ return hidden_file_allow_backup(filename);
}
int fd_nonblock(int fd, bool nonblock) {
while ((de = readdir(d))) {
int fd = -1;
- if (ignore_file(de->d_name))
+ if (hidden_file(de->d_name))
continue;
if (safe_atoi(de->d_name, &fd) < 0)
assert(notify >= 0);
for (;;) {
- uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
- ssize_t l;
+ union inotify_event_buffer buffer;
struct inotify_event *e;
+ ssize_t l;
if (timeout != USEC_INFINITY) {
usec_t n;
}
}
- l = read(notify, inotify_buffer, sizeof(inotify_buffer));
+ l = read(notify, &buffer, sizeof(buffer));
if (l < 0) {
-
if (errno == EINTR || errno == EAGAIN)
continue;
goto fail;
}
- e = (struct inotify_event*) inotify_buffer;
-
- while (l > 0) {
- size_t step;
-
+ FOREACH_INOTIFY_EVENT(e, buffer, l) {
if (e->wd != wd || !(e->mask & IN_CLOSE)) {
r = -EIO;
goto fail;
}
-
- step = sizeof(struct inotify_event) + e->len;
- assert(step <= (size_t) l);
-
- e = (struct inotify_event*) ((uint8_t*) e + step);
- l -= step;
}
break;
ssize_t k;
k = read(fd, p, nbytes);
- if (k < 0 && errno == EINTR)
- continue;
+ if (k < 0) {
+ if (errno == EINTR)
+ continue;
- if (k < 0 && errno == EAGAIN && do_poll) {
+ if (errno == EAGAIN && do_poll) {
- /* We knowingly ignore any return value here,
- * and expect that any error/EOF is reported
- * via read() */
+ /* We knowingly ignore any return value here,
+ * and expect that any error/EOF is reported
+ * via read() */
- fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
- continue;
+ fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
+ continue;
+ }
+
+ return n > 0 ? n : -errno;
}
- if (k <= 0)
- return n > 0 ? n : (k < 0 ? -errno : 0);
+ if (k == 0)
+ return n;
p += k;
nbytes -= k;
return n;
}
-ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
+int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
const uint8_t *p = buf;
- ssize_t n = 0;
assert(fd >= 0);
assert(buf);
+ errno = 0;
+
while (nbytes > 0) {
ssize_t k;
k = write(fd, p, nbytes);
- if (k < 0 && errno == EINTR)
- continue;
+ if (k < 0) {
+ if (errno == EINTR)
+ continue;
- if (k < 0 && errno == EAGAIN && do_poll) {
+ if (errno == EAGAIN && do_poll) {
+ /* We knowingly ignore any return value here,
+ * and expect that any error/EOF is reported
+ * via write() */
- /* We knowingly ignore any return value here,
- * and expect that any error/EOF is reported
- * via write() */
+ fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
+ continue;
+ }
- fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
- continue;
+ return -errno;
}
- if (k <= 0)
- return n > 0 ? n : (k < 0 ? -errno : 0);
+ if (k == 0) /* Can't really happen */
+ return -EIO;
p += k;
nbytes -= k;
- n += k;
}
- return n;
+ return 0;
}
int parse_size(const char *t, off_t base, off_t *size) {
assert(fd >= 0);
- r = dup3(fd, STDIN_FILENO, 0);
- s = dup3(fd, STDOUT_FILENO, 0);
- t = dup3(fd, STDERR_FILENO, 0);
+ r = dup2(fd, STDIN_FILENO);
+ s = dup2(fd, STDOUT_FILENO);
+ t = dup2(fd, STDERR_FILENO);
if (fd >= 3)
safe_close(fd);
if (r < 0 || s < 0 || t < 0)
return -errno;
- /* We rely here that the new fd has O_CLOEXEC not set */
+ /* Explicitly unset O_CLOEXEC, since if fd was < 3, then
+ * dup2() was a NOP and the bit hence possibly set. */
+ fd_cloexec(STDIN_FILENO, false);
+ fd_cloexec(STDOUT_FILENO, false);
+ fd_cloexec(STDERR_FILENO, false);
return 0;
}
if (!de)
return 1;
- if (!ignore_file(de->d_name))
+ if (!hidden_file(de->d_name))
return 0;
}
}
return lookup_uid(getuid());
}
-int getttyname_malloc(int fd, char **r) {
- char path[PATH_MAX], *c;
- int k;
+int getttyname_malloc(int fd, char **ret) {
+ size_t l = 100;
+ int r;
- assert(r);
+ assert(fd >= 0);
+ assert(ret);
+
+ for (;;) {
+ char path[l];
- k = ttyname_r(fd, path, sizeof(path));
- if (k > 0)
- return -k;
+ r = ttyname_r(fd, path, sizeof(path));
+ if (r == 0) {
+ const char *p;
+ char *c;
- char_array_0(path);
+ p = startswith(path, "/dev/");
+ c = strdup(p ?: path);
+ if (!c)
+ return -ENOMEM;
- c = strdup(startswith(path, "/dev/") ? path + 5 : path);
- if (!c)
- return -ENOMEM;
+ *ret = c;
+ return 0;
+ }
+
+ if (r != ERANGE)
+ return -r;
+
+ l *= 2;
+ }
- *r = c;
return 0;
}
fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
if (fd < 0) {
- if (errno != ENOTDIR)
+ if (errno != ENOTDIR && errno != ELOOP)
return -errno;
if (!dangerous) {
/* This is rather stupid, simply removes the heading and
* trailing quotes if there is one. Doesn't care about
* escaping or anything. We should make this smarter one
- * day...*/
+ * day... */
l = strlen(s);
if (l < 2)
bool dirent_is_file(const struct dirent *de) {
assert(de);
- if (ignore_file(de->d_name))
+ if (hidden_file(de->d_name))
return false;
if (de->d_type != DT_REG &&
de->d_type != DT_UNKNOWN)
return false;
- if (ignore_file_allow_backup(de->d_name))
+ if (hidden_file_allow_backup(de->d_name))
return false;
return endswith(de->d_name, suffix);
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,
int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
FILE *f;
char *t;
- int fd;
+ int r, fd;
assert(path);
assert(_f);
assert(_temp_path);
- t = tempfn_xxxxxx(path);
- if (!t)
- return -ENOMEM;
+ r = tempfn_xxxxxx(path, &t);
+ if (r < 0)
+ return r;
fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC);
if (fd < 0) {
int symlink_atomic(const char *from, const char *to) {
_cleanup_free_ char *t = NULL;
+ int r;
assert(from);
assert(to);
- t = tempfn_random(to);
- if (!t)
- return -ENOMEM;
+ r = tempfn_random(to, &t);
+ if (r < 0)
+ return r;
if (symlink(from, t) < 0)
return -errno;
int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
_cleanup_free_ char *t = NULL;
+ int r;
assert(path);
- t = tempfn_random(path);
- if (!t)
- return -ENOMEM;
+ r = tempfn_random(path, &t);
+ if (r < 0)
+ return r;
if (mknod(t, mode, dev) < 0)
return -errno;
int mkfifo_atomic(const char *path, mode_t mode) {
_cleanup_free_ char *t = NULL;
+ int r;
assert(path);
- t = tempfn_random(path);
- if (!t)
- return -ENOMEM;
+ r = tempfn_random(path, &t);
+ if (r < 0)
+ return r;
if (mkfifo(t, mode) < 0)
return -errno;
return 0;
}
-bool filename_is_safe(const char *p) {
+bool filename_is_valid(const char *p) {
if (isempty(p))
return false;
if (!de)
break;
- if (ignore_file(de->d_name))
+ if (hidden_file(de->d_name))
continue;
device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
return 0;
}
-char *tempfn_xxxxxx(const char *p) {
+int tempfn_xxxxxx(const char *p, char **ret) {
const char *fn;
char *t;
- size_t k;
assert(p);
+ assert(ret);
- t = new(char, strlen(p) + 1 + 6 + 1);
- if (!t)
- return NULL;
+ /*
+ * Turns this:
+ * /foo/bar/waldo
+ *
+ * Into this:
+ * /foo/bar/.#waldoXXXXXX
+ */
fn = basename(p);
- k = fn - p;
+ if (!filename_is_valid(fn))
+ return -EINVAL;
- strcpy(stpcpy(stpcpy(mempcpy(t, p, k), "."), fn), "XXXXXX");
+ t = new(char, strlen(p) + 2 + 6 + 1);
+ if (!t)
+ return -ENOMEM;
- return t;
+ strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX");
+
+ *ret = path_kill_slashes(t);
+ return 0;
}
-char *tempfn_random(const char *p) {
+int tempfn_random(const char *p, char **ret) {
const char *fn;
char *t, *x;
uint64_t u;
- size_t k;
unsigned i;
assert(p);
+ assert(ret);
- t = new(char, strlen(p) + 1 + 16 + 1);
- if (!t)
- return NULL;
+ /*
+ * Turns this:
+ * /foo/bar/waldo
+ *
+ * Into this:
+ * /foo/bar/.#waldobaa2a261115984a9
+ */
fn = basename(p);
- k = fn - p;
+ if (!filename_is_valid(fn))
+ return -EINVAL;
+
+ t = new(char, strlen(p) + 2 + 16 + 1);
+ if (!t)
+ return -ENOMEM;
- x = stpcpy(stpcpy(mempcpy(t, p, k), "."), fn);
+ x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn);
u = random_u64();
for (i = 0; i < 16; i++) {
*x = 0;
- return t;
+ *ret = path_kill_slashes(t);
+ return 0;
+}
+
+int tempfn_random_child(const char *p, char **ret) {
+ char *t, *x;
+ uint64_t u;
+ unsigned i;
+
+ assert(p);
+ assert(ret);
+
+ /* Turns this:
+ * /foo/bar/waldo
+ * Into this:
+ * /foo/bar/waldo/.#3c2b6219aa75d7d0
+ */
+
+ t = new(char, strlen(p) + 3 + 16 + 1);
+ if (!t)
+ return -ENOMEM;
+
+ x = stpcpy(stpcpy(t, p), "/.#");
+
+ u = random_u64();
+ for (i = 0; i < 16; i++) {
+ *(x++) = hexchar(u & 0xF);
+ u >>= 4;
+ }
+
+ *x = 0;
+
+ *ret = path_kill_slashes(t);
+ return 0;
}
/* make sure the hostname is not "localhost" */
return 1;
}
+
+int ptsname_malloc(int fd, char **ret) {
+ size_t l = 100;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ for (;;) {
+ char *c;
+
+ c = new(char, l);
+ if (!c)
+ return -ENOMEM;
+
+ if (ptsname_r(fd, c, l) == 0) {
+ *ret = c;
+ return 0;
+ }
+ if (errno != ERANGE) {
+ free(c);
+ return -errno;
+ }
+
+ free(c);
+ l *= 2;
+ }
+}
+
+int openpt_in_namespace(pid_t pid, int flags) {
+ _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ siginfo_t si;
+ pid_t child;
+ int r;
+
+ assert(pid > 0);
+
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return -errno;
+
+ if (child == 0) {
+ int master;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ master = posix_openpt(flags);
+ if (master < 0)
+ _exit(EXIT_FAILURE);
+
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
+
+ mh.msg_controllen = cmsg->cmsg_len;
+
+ if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0)
+ _exit(EXIT_FAILURE);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0)
+ return r;
+ if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return -EIO;
+
+ if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
+ return -errno;
+
+ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ int *fds;
+ unsigned n_fds;
+
+ fds = (int*) CMSG_DATA(cmsg);
+ n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+
+ if (n_fds != 1) {
+ close_many(fds, n_fds);
+ return -EIO;
+ }
+
+ return fds[0];
+ }
+
+ return -EIO;
+}
+
+ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) {
+ _cleanup_close_ int fd = -1;
+ ssize_t l;
+
+ /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
+
+ fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOATIME|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
+ if (fd < 0)
+ return -errno;
+
+ l = fgetxattr(fd, attribute, value, size);
+ if (l < 0)
+ return -errno;
+
+ return l;
+}
+
+static int parse_crtime(le64_t le, usec_t *usec) {
+ uint64_t u;
+
+ assert(usec);
+
+ u = le64toh(le);
+ if (u == 0 || u == (uint64_t) -1)
+ return -EIO;
+
+ *usec = (usec_t) u;
+ return 0;
+}
+
+int fd_getcrtime(int fd, usec_t *usec) {
+ le64_t le;
+ ssize_t n;
+
+ assert(fd >= 0);
+ assert(usec);
+
+ /* Until Linux gets a real concept of birthtime/creation time,
+ * let's fake one with xattrs */
+
+ n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le));
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) {
+ le64_t le;
+ ssize_t n;
+
+ n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags);
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int path_getcrtime(const char *p, usec_t *usec) {
+ le64_t le;
+ ssize_t n;
+
+ assert(p);
+ assert(usec);
+
+ n = getxattr(p, "user.crtime_usec", &le, sizeof(le));
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int fd_setcrtime(int fd, usec_t usec) {
+ le64_t le;
+
+ assert(fd >= 0);
+
+ le = htole64((uint64_t) usec);
+ if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
+ return -errno;
+
+ return 0;
+}