/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see .
***/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "log.h"
#include "util.h"
#include "strv.h"
#include "label.h"
/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
* them in the file system. This is intended to be used to create
* properly owned directories beneath /tmp, /var/tmp, /var/run and
* /var/lock which are volatile and hence need to be recreated on
* bootup. */
static int process_line(const char *fname, unsigned line, const char *buffer, const char *prefix) {
char type;
char *path = NULL;
unsigned mode;
char *user = NULL, *group = NULL;
uid_t uid;
gid_t gid;
bool uid_set = false, gid_set = false;
int n, fd = -1, r;
assert(fname);
assert(line >= 1);
assert(buffer);
if ((n = sscanf(buffer,
"%c "
"%ms "
"%o "
"%ms "
"%ms ",
&type,
&path,
&mode,
&user,
&group)) < 2) {
log_error("[%s:%u] Syntax error.", fname, line);
r = -EIO;
goto finish;
}
if (type != 'f' && type != 'd') {
log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
r = -EBADMSG;
goto finish;
}
if (prefix && !path_startswith(path, prefix)) {
r = 0;
goto finish;
}
if (user && !streq(user, "-")) {
unsigned long lu;
struct passwd *p;
if (streq(user, "root") || streq(user, "0"))
uid = 0;
else if (safe_atolu(user, &lu) >= 0)
uid = (uid_t) lu;
else if ((p = getpwnam(user)))
uid = p->pw_uid;
else {
log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
r = -ENOENT;
goto finish;
}
uid_set = true;
}
if (group && !streq(group, "-")) {
unsigned long lu;
struct group *g;
if (streq(group, "root") || streq(group, "0"))
gid = 0;
else if (safe_atolu(group, &lu) >= 0)
gid = (gid_t) lu;
else if ((g = getgrnam(group)))
gid = g->gr_gid;
else {
log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
r = -ENOENT;
goto finish;
}
gid_set = true;
}
if (n < 3)
mode = type == 'f' ? 0644 : 0755;
if (type == 'f') {
mode_t u;
struct stat st;
u = umask(0);
fd = open(path, O_CREAT|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, mode);
umask(u);
if (fd < 0) {
log_error("Failed to create file %s: %m", path);
r = -errno;
goto finish;
}
if (fstat(fd, &st) < 0) {
log_error("stat(%s) failed: %m", path);
r = -errno;
goto finish;
}
if (!S_ISREG(st.st_mode)) {
log_error("%s is not a file.", path);
r = -EEXIST;
goto finish;
}
if (fchmod(fd, mode) < 0) {
log_error("chmod(%s) failed: %m", path);
r = -errno;
goto finish;
}
if (uid_set || gid_set) {
if (fchown(fd,
uid_set ? uid : (uid_t) -1,
gid_set ? gid : (gid_t) -1) < 0) {
log_error("chown(%s) failed: %m", path);
r = -errno;
goto finish;
}
}
} else if (type == 'd') {
mode_t u;
struct stat st;
u = umask(0);
r = mkdir(path, mode);
umask(u);
if (r < 0 && errno != EEXIST) {
log_error("Failed to create directory %s: %m", path);
r = -errno;
goto finish;
}
if (stat(path, &st) < 0) {
log_error("stat(%s) failed: %m", path);
r = -errno;
goto finish;
}
if (!S_ISDIR(st.st_mode)) {
log_error("%s is not a directory.", path);
r = -EEXIST;
goto finish;
}
if (chmod(path, mode) < 0) {
log_error("chmod(%s) failed: %m", path);
r = -errno;
goto finish;
}
if (uid_set || gid_set) {
if (chown(path,
uid_set ? uid : (uid_t) -1,
gid_set ? gid : (gid_t) -1) < 0) {
log_error("chown(%s) failed: %m", path);
r = -errno;
goto finish;
}
}
}
if ((r = label_fix(path)) < 0)
goto finish;
log_debug("%s created successfully.", path);
r = 0;
finish:
free(path);
free(user);
free(group);
if (fd >= 0)
close_nointr_nofail(fd);
return r;
}
static int scandir_filter(const struct dirent *d) {
assert(d);
if (ignore_file(d->d_name))
return 0;
if (d->d_type != DT_REG &&
d->d_type != DT_LNK)
return 0;
return endswith(d->d_name, ".conf");
}
int main(int argc, char *argv[]) {
struct dirent **de = NULL;
int r = EXIT_FAILURE, n, i;
const char *prefix = NULL;
if (argc > 2) {
log_error("This program takes no more than one argument.");
return EXIT_FAILURE;
} else if (argc > 1)
prefix = argv[1];
else
prefix = "/";
log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
log_parse_environment();
log_open();
label_init();
if ((n = scandir("/etc/tmpfiles.d/", &de, scandir_filter, alphasort)) < 0) {
if (errno == ENOENT)
r = EXIT_SUCCESS;
else
log_error("Failed to enumerate /etc/tmpfiles.d/ files: %m");
goto finish;
}
r = EXIT_SUCCESS;
for (i = 0; i < n; i++) {
int k;
char *fn;
FILE *f;
unsigned j;
k = asprintf(&fn, "/etc/tmpfiles.d/%s", de[i]->d_name);
free(de[i]);
if (k < 0) {
log_error("Failed to allocate file name.");
r = EXIT_FAILURE;
continue;
}
if (!(f = fopen(fn, "re"))) {
log_error("Failed to open %s: %m", fn);
free(fn);
r = EXIT_FAILURE;
continue;
}
j = 0;
for (;;) {
char line[LINE_MAX], *l;
if (!(fgets(line, sizeof(line), f)))
break;
j++;
l = strstrip(line);
if (*l == '#' || *l == 0)
continue;
if (process_line(fn, j, l, prefix) < 0)
r = EXIT_FAILURE;
}
if (ferror(f)) {
r = EXIT_FAILURE;
log_error("Failed to read from file %s: %m", fn);
}
free(fn);
fclose(f);
}
free(de);
finish:
return r;
}