summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/tmpfiles.d.xml16
-rw-r--r--src/tmpfiles/tmpfiles.c37
2 files changed, 46 insertions, 7 deletions
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index 5d8c2b5b3..0081a6762 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -368,6 +368,22 @@ r! /tmp/.X[0-9]*-lock</programlisting>
ignored for <varname>x</varname>,
<varname>r</varname>, <varname>R</varname>,
<varname>L</varname> lines.</para>
+
+ <para>Optionally, if prefixed with
+ <literal>~</literal> the access mode is masked
+ based on the already set access bits for
+ existing file or directories: if the existing
+ file has all executable bits unset then all
+ executable bits are removed from the new
+ access mode, too. Similar, if all read bits
+ are removed from the old access mode they will
+ be removed from the new access mode too, and
+ if all write bits are removed, they will be
+ removed from the new access mode too. In
+ addition the sticky/suid/gid bit is removed unless
+ applied to a directory. This
+ functionality is particularly useful in
+ conjunction with <varname>Z</varname>.</para>
</refsect2>
<refsect2>
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index b80731d47..bb12dd0b5 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -99,6 +99,7 @@ typedef struct Item {
bool gid_set:1;
bool mode_set:1;
bool age_set:1;
+ bool mask_perms:1;
bool keep_first_level:1;
@@ -454,11 +455,29 @@ static int item_set_perms(Item *i, const char *path) {
assert(path);
/* not using i->path directly because it may be a glob */
- if (i->mode_set)
- if (chmod(path, i->mode) < 0) {
+ if (i->mode_set) {
+ mode_t m = i->mode;
+
+ if (i->mask_perms) {
+ struct stat st;
+
+ if (stat(path, &st) >= 0) {
+ if (!(st.st_mode & 0111))
+ m &= ~0111;
+ if (!(st.st_mode & 0222))
+ m &= ~0222;
+ if (!(st.st_mode & 0444))
+ m &= ~0444;
+ if (!S_ISDIR(st.st_mode))
+ m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
+ }
+ }
+
+ if (chmod(path, m) < 0) {
log_error("chmod(%s) failed: %m", path);
return -errno;
}
+ }
if (i->uid_set || i->gid_set)
if (chown(path,
@@ -1056,15 +1075,13 @@ static bool item_equal(Item *a, Item *b) {
static bool should_include_path(const char *path) {
char **prefix;
- STRV_FOREACH(prefix, arg_exclude_prefixes) {
+ STRV_FOREACH(prefix, arg_exclude_prefixes)
if (path_startswith(path, *prefix))
return false;
- }
- STRV_FOREACH(prefix, arg_include_prefixes) {
+ STRV_FOREACH(prefix, arg_include_prefixes)
if (path_startswith(path, *prefix))
return true;
- }
/* no matches, so we should include this path only if we
* have no whitelist at all */
@@ -1249,9 +1266,15 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
if (mode && !streq(mode, "-")) {
+ const char *mm = mode;
unsigned m;
- if (sscanf(mode, "%o", &m) != 1) {
+ if (*mm == '~') {
+ i->mask_perms = true;
+ mm++;
+ }
+
+ if (sscanf(mm, "%o", &m) != 1) {
log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
return -ENOENT;
}