#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>
+#include <sys/statvfs.h>
+#include <linux/fs.h>
#undef basename
#ifdef HAVE_SYS_AUXV_H
#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;
}
*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);
}
char *file_in_same_dir(const char *path, const char *filename) {
- char *e, *r;
+ char *e, *ret;
size_t k;
assert(path);
if (path_is_absolute(filename))
return strdup(filename);
- if (!(e = strrchr(path, '/')))
+ e = strrchr(path, '/');
+ if (!e)
return strdup(filename);
k = strlen(filename);
- if (!(r = new(char, e-path+1+k+1)))
+ ret = new(char, (e + 1 - path) + k + 1);
+ if (!ret)
return NULL;
- memcpy(r, path, e-path+1);
- memcpy(r+(e-path)+1, filename, k+1);
-
- return r;
+ memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
+ return ret;
}
int rmdir_parents(const char *path, const char *stop) {
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 buffer[INOTIFY_EVENT_MAX] _alignas_(struct inotify_event);
+ union inotify_event_buffer buffer;
struct inotify_event *e;
ssize_t l;
}
}
- l = read(notify, buffer, sizeof(buffer));
+ l = read(notify, &buffer, sizeof(buffer));
if (l < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
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;
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);
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)
- /* We were not done yet, and a write error occured. */
- return errno ? -errno : -EIO;
+ if (k == 0) /* Can't really happen */
+ return -EIO;
p += k;
nbytes -= k;
- n += k;
}
return 0;
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);
- k = ttyname_r(fd, path, sizeof(path));
- if (k > 0)
- return -k;
+ for (;;) {
+ char path[l];
- char_array_0(path);
+ r = ttyname_r(fd, path, sizeof(path));
+ if (r == 0) {
+ const char *p;
+ char *c;
- c = strdup(startswith(path, "/dev/") ? path + 5 : path);
- if (!c)
- return -ENOMEM;
+ p = startswith(path, "/dev/");
+ c = strdup(p ?: 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) {
c = 80;
cached_columns = c;
- return c;
+ return cached_columns;
}
int fd_lines(int fd) {
unsigned lines(void) {
const char *e;
- unsigned l;
+ int l;
if (_likely_(cached_lines > 0))
return cached_lines;
l = 0;
e = getenv("LINES");
if (e)
- (void) safe_atou(e, &l);
+ (void) safe_atoi(e, &l);
if (l <= 0)
l = fd_lines(STDOUT_FILENO);
const char *default_term_for_tty(const char *tty) {
assert(tty);
- return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102";
+ return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
}
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,
{ "K", 1024ULL },
};
+ if (t == (off_t) -1)
+ return NULL;
+
for (i = 0; i < ELEMENTSOF(table); i++) {
if (t >= table[i].factor) {
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 r ? r : n;
}
+static int get_mount_flags(const char *path, unsigned long *flags) {
+ struct statvfs buf;
+
+ if (statvfs(path, &buf) < 0)
+ return -errno;
+ *flags = buf.f_flag;
+ return 0;
+}
+
int bind_remount_recursive(const char *prefix, bool ro) {
_cleanup_set_free_free_ Set *done = NULL;
_cleanup_free_ char *cleaned = NULL;
_cleanup_set_free_free_ Set *todo = NULL;
bool top_autofs = false;
char *x;
+ unsigned long orig_flags;
todo = set_new(&string_hash_ops);
if (!todo)
if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0)
return -errno;
- if (mount(NULL, prefix, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
+ orig_flags = 0;
+ (void) get_mount_flags(cleaned, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
return -errno;
x = strdup(cleaned);
if (r < 0)
return r;
- if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
+ /* Try to reuse the original flag set, but
+ * don't care for errors, in case of
+ * obstructed mounts */
+ orig_flags = 0;
+ (void) get_mount_flags(x, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
/* Deal with mount points that are
* obstructed by a later mount */
assert(p);
assert(ret);
+ /*
+ * Turns this:
+ * /foo/bar/waldo
+ *
+ * Into this:
+ * /foo/bar/.#waldoXXXXXX
+ */
+
fn = basename(p);
if (!filename_is_valid(fn))
return -EINVAL;
- t = new(char, strlen(p) + 1 + 6 + 1);
+ t = new(char, strlen(p) + 2 + 6 + 1);
if (!t)
return -ENOMEM;
- strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), "."), fn), "XXXXXX");
+ strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX");
- *ret = t;
+ *ret = path_kill_slashes(t);
return 0;
}
assert(p);
assert(ret);
+ /*
+ * Turns this:
+ * /foo/bar/waldo
+ *
+ * Into this:
+ * /foo/bar/.#waldobaa2a261115984a9
+ */
+
fn = basename(p);
if (!filename_is_valid(fn))
return -EINVAL;
- t = new(char, strlen(p) + 1 + 16 + 1);
+ t = new(char, strlen(p) + 2 + 16 + 1);
if (!t)
return -ENOMEM;
- x = stpcpy(stpcpy(mempcpy(t, p, fn - p), "."), fn);
+ x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn);
u = random_u64();
for (i = 0; i < 16; i++) {
*x = 0;
- *ret = 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;
}
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);
+
+ if (usec <= 0)
+ usec = now(CLOCK_REALTIME);
+
+ le = htole64((uint64_t) usec);
+ if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int same_fd(int a, int b) {
+ struct stat sta, stb;
+ pid_t pid;
+ int r, fa, fb;
+
+ assert(a >= 0);
+ assert(b >= 0);
+
+ /* Compares two file descriptors. Note that semantics are
+ * quite different depending on whether we have kcmp() or we
+ * don't. If we have kcmp() this will only return true for
+ * dup()ed file descriptors, but not otherwise. If we don't
+ * have kcmp() this will also return true for two fds of the same
+ * file, created by separate open() calls. Since we use this
+ * call mostly for filtering out duplicates in the fd store
+ * this difference hopefully doesn't matter too much. */
+
+ if (a == b)
+ return true;
+
+ /* Try to use kcmp() if we have it. */
+ pid = getpid();
+ r = kcmp(pid, pid, KCMP_FILE, a, b);
+ if (r == 0)
+ return true;
+ if (r > 0)
+ return false;
+ if (errno != ENOSYS)
+ return -errno;
+
+ /* We don't have kcmp(), use fstat() instead. */
+ if (fstat(a, &sta) < 0)
+ return -errno;
+
+ if (fstat(b, &stb) < 0)
+ return -errno;
+
+ if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT))
+ return false;
+
+ /* We consider all device fds different, since two device fds
+ * might refer to quite different device contexts even though
+ * they share the same inode and backing dev_t. */
+
+ if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode))
+ return false;
+
+ if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino)
+ return false;
+
+ /* The fds refer to the same inode on disk, let's also check
+ * if they have the same fd flags. This is useful to
+ * distuingish the read and write side of a pipe created with
+ * pipe(). */
+ fa = fcntl(a, F_GETFL);
+ if (fa < 0)
+ return -errno;
+
+ fb = fcntl(b, F_GETFL);
+ if (fb < 0)
+ return -errno;
+
+ return fa == fb;
+}
+
+int chattr_fd(int fd, bool b, int mask) {
+ int old_attr, new_attr;
+
+ assert(fd >= 0);
+
+ if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0)
+ return -errno;
+
+ if (b)
+ new_attr = old_attr | mask;
+ else
+ new_attr = old_attr & ~mask;
+
+ if (new_attr == old_attr)
+ return 0;
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int chattr_path(const char *p, bool b, int mask) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ return chattr_fd(fd, b, mask);
+}