X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=344b869c8cb796f66b977fdda25ec549f0833703;hp=81d247ca46d00a7a8981b8138bd87125a270a22e;hb=7862f62de3d5e71a63391e6ce7ecaee080bc6a2d;hpb=8d0e38a2b966799af884e78a54fd6a2dffa44788 diff --git a/src/util.c b/src/util.c index 81d247ca4..344b869c8 100644 --- a/src/util.c +++ b/src/util.c @@ -64,6 +64,9 @@ #include "exit-status.h" #include "hashmap.h" +int saved_argc = 0; +char **saved_argv = NULL; + size_t page_size(void) { static __thread size_t pgsz = 0; long r; @@ -108,6 +111,28 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { return ts; } +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + ts->realtime = u; + + if (u == 0) + ts->monotonic = 0; + else { + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + + ts->monotonic = now(CLOCK_MONOTONIC); + + if ((int64_t) ts->monotonic > delta) + ts->monotonic -= delta; + else + ts->monotonic = 0; + } + + return ts; +} + usec_t timespec_load(const struct timespec *ts) { assert(ts); @@ -465,7 +490,7 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) { assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); char_array_0(fn); - if (!(f = fopen(fn, "r"))) + if (!(f = fopen(fn, "re"))) return -errno; if (!(fgets(line, sizeof(line), f))) { @@ -510,7 +535,7 @@ int get_starttime_of_pid(pid_t pid, unsigned long long *st) { assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); char_array_0(fn); - if (!(f = fopen(fn, "r"))) + if (!(f = fopen(fn, "re"))) return -errno; if (!(fgets(line, sizeof(line), f))) { @@ -567,6 +592,7 @@ int write_one_line_file(const char *fn, const char *line) { if (!(f = fopen(fn, "we"))) return -errno; + errno = 0; if (fputs(line, f) < 0) { r = -errno; goto finish; @@ -590,6 +616,64 @@ finish: return r; } +int fchmod_umask(int fd, mode_t m) { + mode_t u; + int r; + + u = umask(0777); + r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; + umask(u); + + return r; +} + +int write_one_line_file_atomic(const char *fn, const char *line) { + FILE *f; + int r; + char *p; + + assert(fn); + assert(line); + + r = fopen_temporary(fn, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + errno = 0; + if (fputs(line, f) < 0) { + r = -errno; + goto finish; + } + + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else { + if (rename(p, fn) < 0) + r = -errno; + else + r = 0; + } + +finish: + if (r < 0) + unlink(p); + + fclose(f); + free(p); + + return r; +} + int read_one_line_file(const char *fn, char **line) { FILE *f; int r; @@ -621,7 +705,7 @@ finish: return r; } -int read_full_file(const char *fn, char **contents) { +int read_full_file(const char *fn, char **contents, size_t *size) { FILE *f; int r; size_t n, l; @@ -636,6 +720,12 @@ int read_full_file(const char *fn, char **contents) { goto finish; } + /* Safety check */ + if (st.st_size > 4*1024*1024) { + r = -E2BIG; + goto finish; + } + n = st.st_size > 0 ? st.st_size : LINE_MAX; l = 0; @@ -680,6 +770,9 @@ int read_full_file(const char *fn, char **contents) { *contents = buf; buf = NULL; + if (size) + *size = l; + r = 0; finish: @@ -699,7 +792,7 @@ int parse_env_file( assert(fname); assert(separator); - if ((r = read_full_file(fname, &contents)) < 0) + if ((r = read_full_file(fname, &contents, NULL)) < 0) return r; p = contents; @@ -838,15 +931,17 @@ finish: } int write_env_file(const char *fname, char **l) { - - char **i; + char **i, *p; FILE *f; int r; - f = fopen(fname, "we"); - if (!f) - return -errno; + r = fopen_temporary(fname, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + errno = 0; STRV_FOREACH(i, l) { fputs(*i, f); fputc('\n', f); @@ -854,8 +949,23 @@ int write_env_file(const char *fname, char **l) { fflush(f); - r = ferror(f) ? -errno : 0; + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else { + if (rename(p, fname) < 0) + r = -errno; + else + r = 0; + } + + if (r < 0) + unlink(p); + fclose(f); + free(p); return r; } @@ -900,7 +1010,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, char **line) { if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) return -ENOMEM; - f = fopen(p, "r"); + f = fopen(p, "re"); free(p); if (!f) @@ -2546,7 +2656,7 @@ int release_terminal(void) { int r = 0, fd; struct sigaction sa_old, sa_new; - if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY)) < 0) + if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC)) < 0) return -errno; /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed @@ -2837,6 +2947,10 @@ int make_stdio(int fd) { if (r < 0 || s < 0 || t < 0) return -errno; + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + return 0; } @@ -2919,6 +3033,20 @@ void rename_process(const char name[8]) { if (program_invocation_name) strncpy(program_invocation_name, name, strlen(program_invocation_name)); + + if (saved_argc > 0) { + int i; + + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); + + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; + + memset(saved_argv[i], 0, strlen(saved_argv[i])); + } + } } void sigset_add_many(sigset_t *ss, ...) { @@ -3007,23 +3135,28 @@ int getttyname_harder(int fd, char **r) { if (streq(s, "tty")) { free(s); - return get_ctty(r, NULL); + return get_ctty(0, NULL, r); } *r = s; return 0; } -int get_ctty_devnr(dev_t *d) { +int get_ctty_devnr(pid_t pid, dev_t *d) { int k; - char line[LINE_MAX], *p; + char line[LINE_MAX], *p, *fn; unsigned long ttynr; FILE *f; - if (!(f = fopen("/proc/self/stat", "r"))) + if (asprintf(&fn, "/proc/%lu/stat", (unsigned long) (pid <= 0 ? getpid() : pid)) < 0) + return -ENOMEM; + + f = fopen(fn, "re"); + free(fn); + if (!f) return -errno; - if (!(fgets(line, sizeof(line), f))) { + if (!fgets(line, sizeof(line), f)) { k = -errno; fclose(f); return k; @@ -3031,7 +3164,8 @@ int get_ctty_devnr(dev_t *d) { fclose(f); - if (!(p = strrchr(line, ')'))) + p = strrchr(line, ')'); + if (!p) return -EIO; p++; @@ -3049,14 +3183,15 @@ int get_ctty_devnr(dev_t *d) { return 0; } -int get_ctty(char **r, dev_t *_devnr) { +int get_ctty(pid_t pid, dev_t *_devnr, char **r) { int k; char fn[PATH_MAX], *s, *b, *p; dev_t devnr; assert(r); - if ((k = get_ctty_devnr(&devnr)) < 0) + k = get_ctty_devnr(pid, &devnr); + if (k < 0) return k; snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); @@ -3933,8 +4068,31 @@ bool tty_is_vc(const char *tty) { if (startswith(tty, "/dev/")) tty += 5; - return startswith(tty, "tty") && - tty[3] >= '0' && tty[3] <= '9'; + return vtnr_from_tty(tty) >= 0; +} + +int vtnr_from_tty(const char *tty) { + int i, r; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (!startswith(tty, "tty") ) + return -EINVAL; + + if (tty[3] < '0' || tty[3] > '9') + return -EINVAL; + + r = safe_atoi(tty+3, &i); + if (r < 0) + return r; + + if (i < 0 || i > 63) + return -EINVAL; + + return i; } const char *default_term_for_tty(const char *tty) { @@ -4118,7 +4276,7 @@ int detect_container(const char **id) { return 1; } - if ((f = fopen("/proc/self/cgroup", "r"))) { + if ((f = fopen("/proc/self/cgroup", "re"))) { for (;;) { char line[LINE_MAX], *p; @@ -4537,7 +4695,11 @@ int vt_disallocate(const char *name) { if (fd < 0) return fd; - loop_write(fd, "\033[H\033[2J", 7, false); /* clear screen */ + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[2J", /* clear screen */ + 10, false); close_nointr_nofail(fd); return 0; @@ -4573,8 +4735,11 @@ int vt_disallocate(const char *name) { if (fd < 0) return fd; - /* Requires Linux 2.6.40 */ - loop_write(fd, "\033[H\033[3J", 7, false); /* clear screen including scrollback */ + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ + 10, false); close_nointr_nofail(fd); return 0; @@ -4700,40 +4865,50 @@ finish: return r; } -bool hwclock_is_localtime(void) { +int hwclock_is_localtime(void) { FILE *f; - char line[LINE_MAX]; bool local = false; /* * The third line of adjtime is "UTC" or "LOCAL" or nothing. * # /etc/adjtime - * 0.0 0 0.0 + * 0.0 0 0 * 0 * UTC */ f = fopen("/etc/adjtime", "re"); if (f) { - if (fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) ) { - if (!strcmp(line, "LOCAL\n")) - local = true; - } + char line[LINE_MAX]; + bool b; + + b = fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f); + fclose(f); - } + + if (!b) + return -EIO; + + + truncate_nl(line); + local = streq(line, "LOCAL"); + + } else if (errno != -ENOENT) + return -errno; + return local; } int hwclock_apply_localtime_delta(void) { const struct timeval *tv_null = NULL; - struct timeval tv; + struct timespec ts; struct tm *tm; int minuteswest; struct timezone tz; - gettimeofday(&tv, NULL); - tm = localtime(&tv.tv_sec); + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); minuteswest = tm->tm_gmtoff / 60; tz.tz_minuteswest = -minuteswest; @@ -4746,20 +4921,43 @@ int hwclock_apply_localtime_delta(void) { */ if (settimeofday(tv_null, &tz) < 0) return -errno; - else - return minuteswest; + + return minuteswest; +} + +int hwclock_reset_localtime_delta(void) { + const struct timeval *tv_null = NULL; + struct timezone tz; + + tz.tz_minuteswest = 0; + tz.tz_dsttime = 0; /* DST_NONE*/ + + if (settimeofday(tv_null, &tz) < 0) + return -errno; + + return 0; } int hwclock_get_time(struct tm *tm) { int fd; int err = 0; + assert(tm); + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ if (ioctl(fd, RTC_RD_TIME, tm) < 0) err = -errno; - close(fd); + + /* We don't now daylight saving, so we reset this in order not + * to confused mktime(). */ + tm->tm_isdst = -1; + + close_nointr_nofail(fd); return err; } @@ -4768,16 +4966,278 @@ int hwclock_set_time(const struct tm *tm) { int fd; int err = 0; + assert(tm); + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + if (ioctl(fd, RTC_SET_TIME, tm) < 0) err = -errno; - close(fd); + + close_nointr_nofail(fd); return err; } +int copy_file(const char *from, const char *to) { + int r, fdf, fdt; + + assert(from); + assert(to); + + fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fdf < 0) + return -errno; + + fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644); + if (fdt < 0) { + close_nointr_nofail(fdf); + return -errno; + } + + for (;;) { + char buf[PIPE_BUF]; + ssize_t n, k; + + n = read(fdf, buf, sizeof(buf)); + if (n < 0) { + r = -errno; + + close_nointr_nofail(fdf); + close_nointr(fdt); + unlink(to); + + return r; + } + + if (n == 0) + break; + + errno = 0; + k = loop_write(fdt, buf, n, false); + if (n != k) { + r = k < 0 ? k : (errno ? -errno : -EIO); + + close_nointr_nofail(fdf); + close_nointr(fdt); + + unlink(to); + return r; + } + } + + close_nointr_nofail(fdf); + r = close_nointr(fdt); + + if (r < 0) { + unlink(to); + return r; + } + + return 0; +} + +int symlink_or_copy(const char *from, const char *to) { + char *pf = NULL, *pt = NULL; + struct stat a, b; + int r; + + assert(from); + assert(to); + + if (parent_of_path(from, &pf) < 0 || + parent_of_path(to, &pt) < 0) { + r = -ENOMEM; + goto finish; + } + + if (stat(pf, &a) < 0 || + stat(pt, &b) < 0) { + r = -errno; + goto finish; + } + + if (a.st_dev != b.st_dev) { + free(pf); + free(pt); + + return copy_file(from, to); + } + + if (symlink(from, to) < 0) { + r = -errno; + goto finish; + } + + r = 0; + +finish: + free(pf); + free(pt); + + return r; +} + +int symlink_or_copy_atomic(const char *from, const char *to) { + char *t, *x; + const char *fn; + size_t k; + unsigned long long ull; + unsigned i; + int r; + + assert(from); + assert(to); + + t = new(char, strlen(to) + 1 + 16 + 1); + if (!t) + return -ENOMEM; + + fn = file_name_from_path(to); + k = fn-to; + memcpy(t, to, k); + t[k] = '.'; + x = stpcpy(t+k+1, fn); + + ull = random_ull(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(ull & 0xF); + ull >>= 4; + } + + *x = 0; + + r = symlink_or_copy(from, t); + if (r < 0) { + unlink(t); + free(t); + return r; + } + + if (rename(t, to) < 0) { + r = -errno; + unlink(t); + free(t); + return r; + } + + free(t); + return r; +} + +int audit_session_from_pid(pid_t pid, uint32_t *id) { + char *p, *s; + uint32_t u; + int r; + + assert(pid >= 1); + assert(id); + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + if (r < 0) + return r; + + r = safe_atou32(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uint32_t) -1 || u <= 0) + return -ENOENT; + + *id = u; + return 0; +} + +bool display_is_local(const char *display) { + assert(display); + + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} + +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; + + assert(display); + assert(path); + + if (!display_is_local(display)) + return -EINVAL; + + k = strspn(display+1, "0123456789"); + + f = new(char, sizeof("/tmp/.X11-unix/X") + k); + if (!f) + return -ENOMEM; + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; + + *path = f; + + return 0; +} + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) { + struct passwd *p; + unsigned long lu; + + assert(username); + assert(*username); + assert(uid); + assert(gid); + assert(home); + + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; + *uid = 0; + *gid = 0; + *home = "/root"; + return 0; + } + + if (safe_atolu(*username, &lu) >= 0) { + errno = 0; + p = getpwuid((uid_t) lu); + + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); + } + + if (!p) + return errno != 0 ? -errno : -ESRCH; + + *uid = p->pw_uid; + *gid = p->pw_gid; + *home = p->pw_dir; + return 0; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime",