tmpfiles: add support for creating symlinks, char/block device nodes

This commit is contained in:
Lennart Poettering 2012-01-17 15:04:12 +01:00
parent fc3c1c6e09
commit 468d726bdd
2 changed files with 176 additions and 31 deletions

View file

@ -67,27 +67,31 @@
<title>Configuration Format</title> <title>Configuration Format</title>
<para>Each configuration file is named in the style of <para>Each configuration file is named in the style of
<filename>&lt;program&gt;.conf</filename>. <filename>&lt;program&gt;.conf</filename>. Files in
Files in <filename>/etc/</filename> overwrite <filename>/etc/</filename> override files with the
files with the same name in <filename>/usr/lib/</filename>. same name in <filename>/usr/lib/</filename>. Files in
Files in <filename>/run</filename> overwrite files with <filename>/run</filename> override files with the same
the same name in <filename>/etc/</filename> and name in <filename>/etc/</filename> and
<filename>/usr/lib/</filename>. Packages should install their <filename>/usr/lib/</filename>. Packages should
configuration files in <filename>/usr/lib/</filename>, files install their configuration files in
in <filename>/etc/</filename> are reserved for the local <filename>/usr/lib/</filename>, files in
administration, which possibly decides to overwrite the <filename>/etc/</filename> are reserved for the local
configurations installed from packages. All files are sorted administrator, who may choose to override the
by filename in alphabetical order, regardless in which of the configurations installed from packages. The list of
directories they reside, to ensure that a specific configuration files are sorted by their filename in
configuration file takes precedence over another file with alphabetical order, regardless in which of the
an alphabetically later name.</para> directories they reside, to guarantee that a
configuration file takes precedence over another
configuration file with an alphabetically later
name.</para>
<para>The configuration format is one line per path <para>The configuration format is one line per path
containing action, mode, ownership and age containing action, path, mode, ownership, age and argument
fields:</para> fields:</para>
<programlisting>Type Path Mode UID GID Age <programlisting>Type Path Mode UID GID Age Argument
d /run/user 0755 root root 10d</programlisting> d /run/user 0755 root root 10d -
L /tmp/foobar - - - - /dev/null</programlisting>
<refsect2> <refsect2>
<title>Type</title> <title>Type</title>
@ -117,6 +121,21 @@ d /run/user 0755 root root 10d</programlisting>
<listitem><para>Create a named pipe (FIFO) if it doesn't exist yet</para></listitem> <listitem><para>Create a named pipe (FIFO) if it doesn't exist yet</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>L</varname></term>
<listitem><para>Create a symlink if it doesn't exist yet</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>c</varname></term>
<listitem><para>Create a character device node if it doesn't exist yet</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>b</varname></term>
<listitem><para>Create a block device node if it doesn't exist yet</para></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>x</varname></term> <term><varname>x</varname></term>
<listitem><para>Ignore a path <listitem><para>Ignore a path
@ -185,10 +204,11 @@ d /run/user 0755 root root 10d</programlisting>
<para>The file access mode to use when <para>The file access mode to use when
creating this file or directory. If omitted or creating this file or directory. If omitted or
when set to - the default is used: 0755 for when set to - the default is used: 0755 for
directories, 0644 for files. For z, Z lines directories, 0644 for all other file
if omitted or when set to - the file access mode will objects. For z, Z lines if omitted or when set
not be modified. This parameter is ignored for x, r, R to - the file access mode will not be
lines.</para> modified. This parameter is ignored for x, r,
R, L lines.</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
@ -200,7 +220,7 @@ d /run/user 0755 root root 10d</programlisting>
omitted or when set to - the default 0 (root) omitted or when set to - the default 0 (root)
is used. For z, Z lines when omitted or when set to - is used. For z, Z lines when omitted or when set to -
the file ownership will not be modified. the file ownership will not be modified.
These parameters are ignored for x, r, R lines.</para> These parameters are ignored for x, r, R, L lines.</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
@ -233,6 +253,16 @@ d /run/user 0755 root root 10d</programlisting>
is done.</para> is done.</para>
</refsect2> </refsect2>
<refsect2>
<title>Argument</title>
<para>For L lines determines the destination
path of the symlink. For c, b determines the
major/minor of the device node, with major and
minor formatted as integers, separated by :,
e.g. "1:3". Ignored for all other lines.</para>
</refsect2>
</refsect1> </refsect1>
<refsect1> <refsect1>
@ -241,7 +271,7 @@ d /run/user 0755 root root 10d</programlisting>
<title>/etc/tmpfiles.d/screen.conf example</title> <title>/etc/tmpfiles.d/screen.conf example</title>
<para><command>screen</command> needs two directories created at boot with specific modes and ownership.</para> <para><command>screen</command> needs two directories created at boot with specific modes and ownership.</para>
<programlisting>d /var/run/screens 1777 root root 10d <programlisting>d /var/run/screens 1777 root root 10d
d /var/run/uscreens 0755 root root 10d12h</programlisting> d /var/run/uscreens 0755 root root 10d12h</programlisting>
</example> </example>
</refsect1> </refsect1>

View file

@ -57,6 +57,9 @@ typedef enum ItemType {
CREATE_DIRECTORY = 'd', CREATE_DIRECTORY = 'd',
TRUNCATE_DIRECTORY = 'D', TRUNCATE_DIRECTORY = 'D',
CREATE_FIFO = 'p', CREATE_FIFO = 'p',
CREATE_SYMLINK = 'L',
CREATE_CHAR_DEVICE = 'c',
CREATE_BLOCK_DEVICE = 'b',
/* These ones take globs */ /* These ones take globs */
IGNORE_PATH = 'x', IGNORE_PATH = 'x',
@ -70,11 +73,14 @@ typedef struct Item {
ItemType type; ItemType type;
char *path; char *path;
char *argument;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
mode_t mode; mode_t mode;
usec_t age; usec_t age;
dev_t major_minor;
bool uid_set:1; bool uid_set:1;
bool gid_set:1; bool gid_set:1;
bool mode_set:1; bool mode_set:1;
@ -656,6 +662,60 @@ static int create_item(Item *i) {
break; break;
case CREATE_SYMLINK: {
char *x;
r = symlink(i->argument, i->path);
if (r < 0 && errno != EEXIST) {
log_error("symlink(%s, %s) failed: %m", i->argument, i->path);
return -errno;
}
r = readlink_malloc(i->path, &x);
if (r < 0) {
log_error("readlink(%s) failed: %s", i->path, strerror(-r));
return -errno;
}
if (!streq(i->argument, x)) {
free(x);
log_error("%s is not the right symlinks.", i->path);
return -EEXIST;
}
free(x);
break;
}
case CREATE_BLOCK_DEVICE:
case CREATE_CHAR_DEVICE: {
u = umask(0);
r = mknod(i->path, i->mode | (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR), i->major_minor);
umask(u);
if (r < 0 && errno != EEXIST) {
log_error("Failed to create device node %s: %m", i->path);
return -errno;
}
if (stat(i->path, &st) < 0) {
log_error("stat(%s) failed: %m", i->path);
return -errno;
}
if (i->type == CREATE_BLOCK_DEVICE ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) {
log_error("%s is not a device node.", i->path);
return -EEXIST;
}
r = item_set_perms(i, i->path);
if (r < 0)
return r;
break;
}
case RELABEL_PATH: case RELABEL_PATH:
r = glob_item(i, item_set_perms); r = glob_item(i, item_set_perms);
@ -686,6 +746,9 @@ static int remove_item_instance(Item *i, const char *instance) {
case TRUNCATE_FILE: case TRUNCATE_FILE:
case CREATE_DIRECTORY: case CREATE_DIRECTORY:
case CREATE_FIFO: case CREATE_FIFO:
case CREATE_SYMLINK:
case CREATE_BLOCK_DEVICE:
case CREATE_CHAR_DEVICE:
case IGNORE_PATH: case IGNORE_PATH:
case RELABEL_PATH: case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH: case RECURSIVE_RELABEL_PATH:
@ -701,8 +764,8 @@ static int remove_item_instance(Item *i, const char *instance) {
case TRUNCATE_DIRECTORY: case TRUNCATE_DIRECTORY:
case RECURSIVE_REMOVE_PATH: case RECURSIVE_REMOVE_PATH:
if ((r = rm_rf(instance, false, i->type == RECURSIVE_REMOVE_PATH, false)) < 0 && r = rm_rf(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
r != -ENOENT) { if (r < 0 && r != -ENOENT) {
log_error("rm_rf(%s): %s", instance, strerror(-r)); log_error("rm_rf(%s): %s", instance, strerror(-r));
return r; return r;
} }
@ -724,6 +787,9 @@ static int remove_item(Item *i) {
case TRUNCATE_FILE: case TRUNCATE_FILE:
case CREATE_DIRECTORY: case CREATE_DIRECTORY:
case CREATE_FIFO: case CREATE_FIFO:
case CREATE_SYMLINK:
case CREATE_CHAR_DEVICE:
case CREATE_BLOCK_DEVICE:
case IGNORE_PATH: case IGNORE_PATH:
case RELABEL_PATH: case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH: case RECURSIVE_RELABEL_PATH:
@ -761,6 +827,7 @@ static void item_free(Item *i) {
assert(i); assert(i);
free(i->path); free(i->path);
free(i->argument);
free(i); free(i);
} }
@ -790,6 +857,15 @@ static bool item_equal(Item *a, Item *b) {
(a->age_set && a->age != b->age)) (a->age_set && a->age != b->age))
return false; return false;
if (a->type == CREATE_SYMLINK &&
!streq(a->argument, b->argument))
return false;
if ((a->type == CREATE_CHAR_DEVICE ||
a->type == CREATE_BLOCK_DEVICE) &&
a->major_minor != b->major_minor)
return false;
return true; return true;
} }
@ -804,7 +880,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
assert(line >= 1); assert(line >= 1);
assert(buffer); assert(buffer);
if (!(i = new0(Item, 1))) { i = new0(Item, 1);
if (!i) {
log_error("Out of memory"); log_error("Out of memory");
return -ENOMEM; return -ENOMEM;
} }
@ -815,19 +892,22 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
"%ms " "%ms "
"%ms " "%ms "
"%ms " "%ms "
"%ms "
"%ms", "%ms",
&type, &type,
&i->path, &i->path,
&mode, &mode,
&user, &user,
&group, &group,
&age) < 2) { &age,
&i->argument) < 2) {
log_error("[%s:%u] Syntax error.", fname, line); log_error("[%s:%u] Syntax error.", fname, line);
r = -EIO; r = -EIO;
goto finish; goto finish;
} }
switch(type) { switch(type) {
case CREATE_FILE: case CREATE_FILE:
case TRUNCATE_FILE: case TRUNCATE_FILE:
case CREATE_DIRECTORY: case CREATE_DIRECTORY:
@ -839,11 +919,41 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case RELABEL_PATH: case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH: case RECURSIVE_RELABEL_PATH:
break; break;
case CREATE_SYMLINK:
if (!i->argument) {
log_error("[%s:%u] Symlink file requires argument.", fname, line);
r = -EBADMSG;
goto finish;
}
break;
case CREATE_CHAR_DEVICE:
case CREATE_BLOCK_DEVICE: {
unsigned major, minor;
if (!i->argument) {
log_error("[%s:%u] Device file requires argument.", fname, line);
r = -EBADMSG;
goto finish;
}
if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
r = -EBADMSG;
goto finish;
}
i->major_minor = makedev(major, minor);
break;
}
default: default:
log_error("[%s:%u] Unknown file type '%c'.", fname, line, type); log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
r = -EBADMSG; r = -EBADMSG;
goto finish; goto finish;
} }
i->type = type; i->type = type;
if (!path_is_absolute(i->path)) { if (!path_is_absolute(i->path)) {
@ -895,7 +1005,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i->mode = m; i->mode = m;
i->mode_set = true; i->mode_set = true;
} else } else
i->mode = i->type == CREATE_DIRECTORY ? 0755 : 0644; i->mode =
i->type == CREATE_DIRECTORY ||
i->type == TRUNCATE_DIRECTORY ? 0755 : 0644;
if (age && !streq(age, "-")) { if (age && !streq(age, "-")) {
if (parse_usec(age, &i->age) < 0) { if (parse_usec(age, &i->age) < 0) {
@ -909,7 +1021,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
h = needs_glob(i->type) ? globs : items; h = needs_glob(i->type) ? globs : items;
if ((existing = hashmap_get(h, i->path))) { existing = hashmap_get(h, i->path);
if (existing) {
/* Two identical items are fine */ /* Two identical items are fine */
if (!item_equal(existing, i)) if (!item_equal(existing, i))
@ -919,7 +1032,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
goto finish; goto finish;
} }
if ((r = hashmap_put(h, i->path, i)) < 0) { r = hashmap_put(h, i->path, i);
if (r < 0) {
log_error("Failed to insert item %s: %s", i->path, strerror(-r)); log_error("Failed to insert item %s: %s", i->path, strerror(-r));
goto finish; goto finish;
} }
@ -1024,7 +1138,8 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
assert(fn); assert(fn);
if (!(f = fopen(fn, "re"))) { f = fopen(fn, "re");
if (!f) {
if (ignore_enoent && errno == ENOENT) if (ignore_enoent && errno == ENOENT)
return 0; return 0;