Merge pull request #24010 from poettering/tmpfiles-base64-data
tmpfiles: optionally allow configuring file contents for f/w lines to be encoded in base64
This commit is contained in:
+11
-3
@@ -160,8 +160,9 @@ L /tmp/foobar - - - - /dev/null</programlisting>
|
||||
<refsect2>
|
||||
<title>Type</title>
|
||||
|
||||
<para>The type consists of a single letter and optionally an exclamation mark (<literal>!</literal>)
|
||||
minus sign (<literal>-</literal>), and/or equals sign (<literal>=</literal>).</para>
|
||||
<para>The type consists of a single letter and optionally a plus sign (<literal>+</literal>),
|
||||
exclamation mark (<literal>!</literal>), minus sign (<literal>-</literal>), equals sign
|
||||
(<literal>=</literal>) and/or tilde character (<literal>~</literal>).</para>
|
||||
|
||||
<para>The following line types are understood:</para>
|
||||
|
||||
@@ -330,7 +331,7 @@ L /tmp/foobar - - - - /dev/null</programlisting>
|
||||
exists and is not empty. Instead, the entire copy operation is
|
||||
skipped. If the argument is omitted, files from the source directory
|
||||
<filename>/usr/share/factory/</filename> with the same name
|
||||
are copied. Does not follow symlinks. Contents of the directories
|
||||
are copied. Does not follow symlinks. Contents of the directories
|
||||
are subject to time based cleanup if the age argument is specified.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
@@ -489,6 +490,13 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
|
||||
be either directories or directory symlinks). For example, if there is a FIFO in place of one of the parent path
|
||||
components it will be replaced with a directory.</para>
|
||||
|
||||
<para>If the tilde character (<literal>~</literal>) is used, the argument (i.e. 6th) column is <ulink
|
||||
url="https://www.rfc-editor.org/rfc/rfc4648.html">Base64 decoded</ulink> before use. This modifier is
|
||||
only supported on line types that can write file contents, i.e. <varname>f</varname>,
|
||||
<varname>f+</varname>, <varname>w</varname>. This is useful for writing arbitrary binary data
|
||||
(including newlines and NUL bytes) to files. Note that if this switch is used, the argument is not
|
||||
subject to specifier expansion, neither before nor after Base64 decoding.</para>
|
||||
|
||||
<para>Note that for all line types that result in creation of any kind of file node
|
||||
(i.e. <varname>f</varname>/<varname>F</varname>,
|
||||
<varname>d</varname>/<varname>D</varname>/<varname>v</varname>/<varname>q</varname>/<varname>Q</varname>,
|
||||
|
||||
+93
-33
@@ -36,6 +36,7 @@
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "glob-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "io-util.h"
|
||||
#include "label.h"
|
||||
#include "log.h"
|
||||
@@ -127,6 +128,8 @@ typedef struct Item {
|
||||
|
||||
char *path;
|
||||
char *argument;
|
||||
void *binary_argument; /* set if binary data, in which case it takes precedence over 'argument' */
|
||||
size_t binary_argument_size;
|
||||
char **xattrs;
|
||||
#if HAVE_ACL
|
||||
acl_t acl_access;
|
||||
@@ -461,6 +464,17 @@ static bool unix_socket_alive(const char *fn) {
|
||||
return set_contains(unix_sockets, fn);
|
||||
}
|
||||
|
||||
/* Accessors for the argument in binary format */
|
||||
static const void* item_binary_argument(const Item *i) {
|
||||
assert(i);
|
||||
return i->binary_argument ?: i->argument;
|
||||
}
|
||||
|
||||
static size_t item_binary_argument_size(const Item *i) {
|
||||
assert(i);
|
||||
return i->binary_argument ? i->binary_argument_size : strlen_ptr(i->argument);
|
||||
}
|
||||
|
||||
static DIR* xopendirat_nomod(int dirfd, const char *path) {
|
||||
DIR *dir;
|
||||
|
||||
@@ -985,10 +999,8 @@ static int parse_xattrs_from_arg(Item *i) {
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(i->argument);
|
||||
|
||||
p = i->argument;
|
||||
|
||||
assert_se(p = i->argument);
|
||||
for (;;) {
|
||||
_cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL;
|
||||
|
||||
@@ -1329,6 +1341,27 @@ static int path_set_attribute(Item *item, const char *path) {
|
||||
return fd_set_attribute(item, fd, path, NULL);
|
||||
}
|
||||
|
||||
static int write_argument_data(Item *i, int fd, const char *path) {
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(fd >= 0);
|
||||
assert(path);
|
||||
|
||||
if (item_binary_argument_size(i) == 0)
|
||||
return 0;
|
||||
|
||||
assert(item_binary_argument(i));
|
||||
|
||||
log_debug("Writing to \"%s\".", path);
|
||||
|
||||
r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i), /* do_poll= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write file \"%s\": %m", path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_one_file(Item *i, const char *path) {
|
||||
_cleanup_close_ int fd = -1, dir_fd = -1;
|
||||
_cleanup_free_ char *bn = NULL;
|
||||
@@ -1336,7 +1369,6 @@ static int write_one_file(Item *i, const char *path) {
|
||||
|
||||
assert(i);
|
||||
assert(path);
|
||||
assert(i->argument);
|
||||
assert(i->type == WRITE_FILE);
|
||||
|
||||
r = path_extract_filename(path, &bn);
|
||||
@@ -1368,11 +1400,10 @@ static int write_one_file(Item *i, const char *path) {
|
||||
}
|
||||
|
||||
/* 'w' is allowed to write into any kind of files. */
|
||||
log_debug("Writing to \"%s\".", path);
|
||||
|
||||
r = loop_write(fd, i->argument, strlen(i->argument), false);
|
||||
r = write_argument_data(i, fd, path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write file \"%s\": %m", path);
|
||||
return r;
|
||||
|
||||
return fd_set_perms(i, fd, path, NULL);
|
||||
}
|
||||
@@ -1432,17 +1463,10 @@ static int create_file(Item *i, const char *path) {
|
||||
path);
|
||||
|
||||
st = &stbuf;
|
||||
} else {
|
||||
|
||||
log_debug("\"%s\" has been created.", path);
|
||||
|
||||
if (i->argument) {
|
||||
log_debug("Writing to \"%s\".", path);
|
||||
|
||||
r = loop_write(fd, i->argument, strlen(i->argument), false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write file \"%s\": %m", path);
|
||||
}
|
||||
} else if (item_binary_argument(i)) {
|
||||
r = write_argument_data(i, fd, path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return fd_set_perms(i, fd, path, st);
|
||||
@@ -1522,12 +1546,10 @@ static int truncate_file(Item *i, const char *path) {
|
||||
|
||||
log_debug("\"%s\" has been created.", path);
|
||||
|
||||
if (i->argument) {
|
||||
log_debug("Writing to \"%s\".", path);
|
||||
|
||||
r = loop_write(fd, i->argument, strlen(i->argument), false);
|
||||
if (item_binary_argument(i)) {
|
||||
r = write_argument_data(i, fd, path);
|
||||
if (r < 0)
|
||||
return log_error_errno(erofs ? -EROFS : r, "Failed to write file %s: %m", path);
|
||||
return r;
|
||||
}
|
||||
|
||||
return fd_set_perms(i, fd, path, st);
|
||||
@@ -2643,6 +2665,7 @@ static void item_free_contents(Item *i) {
|
||||
assert(i);
|
||||
free(i->path);
|
||||
free(i->argument);
|
||||
free(i->binary_argument);
|
||||
strv_free(i->xattrs);
|
||||
|
||||
#if HAVE_ACL
|
||||
@@ -2684,7 +2707,8 @@ static bool item_compatible(const Item *a, const Item *b) {
|
||||
|
||||
if (takes_ownership(a->type) && takes_ownership(b->type))
|
||||
/* check if the items are the same */
|
||||
return streq_ptr(a->argument, b->argument) &&
|
||||
return memcmp_nn(item_binary_argument(a), item_binary_argument_size(a),
|
||||
item_binary_argument(b), item_binary_argument_size(b)) == 0 &&
|
||||
|
||||
a->uid_set == b->uid_set &&
|
||||
a->uid == b->uid &&
|
||||
@@ -2953,7 +2977,7 @@ static int parse_line(
|
||||
ItemArray *existing;
|
||||
OrderedHashmap *h;
|
||||
int r, pos;
|
||||
bool append_or_force = false, boot = false, allow_failure = false, try_replace = false;
|
||||
bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false;
|
||||
|
||||
assert(fname);
|
||||
assert(line >= 1);
|
||||
@@ -3024,6 +3048,8 @@ static int parse_line(
|
||||
allow_failure = true;
|
||||
else if (action[pos] == '=' && !try_replace)
|
||||
try_replace = true;
|
||||
else if (action[pos] == '~' && !unbase64)
|
||||
unbase64 = true;
|
||||
else {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action);
|
||||
@@ -3079,6 +3105,11 @@ static int parse_line(
|
||||
break;
|
||||
|
||||
case CREATE_SYMLINK:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for symlink targets.");
|
||||
}
|
||||
|
||||
if (!i.argument) {
|
||||
i.argument = path_join("/usr/share/factory", i.path);
|
||||
if (!i.argument)
|
||||
@@ -3094,11 +3125,15 @@ static int parse_line(
|
||||
break;
|
||||
|
||||
case COPY_FILES:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for copy sources.");
|
||||
}
|
||||
|
||||
if (!i.argument) {
|
||||
i.argument = path_join("/usr/share/factory", i.path);
|
||||
if (!i.argument)
|
||||
return log_oom();
|
||||
|
||||
} else if (!path_is_absolute(i.argument)) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Source path '%s' is not absolute.", i.argument);
|
||||
@@ -3119,6 +3154,11 @@ static int parse_line(
|
||||
|
||||
case CREATE_CHAR_DEVICE:
|
||||
case CREATE_BLOCK_DEVICE:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for device node creation.");
|
||||
}
|
||||
|
||||
if (!i.argument) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Device file requires argument.");
|
||||
@@ -3134,6 +3174,10 @@ static int parse_line(
|
||||
|
||||
case SET_XATTR:
|
||||
case RECURSIVE_SET_XATTR:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for extended attributes.");
|
||||
}
|
||||
if (!i.argument) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
|
||||
@@ -3146,6 +3190,10 @@ static int parse_line(
|
||||
|
||||
case SET_ACL:
|
||||
case RECURSIVE_SET_ACL:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for ACLs.");
|
||||
}
|
||||
if (!i.argument) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
|
||||
@@ -3158,6 +3206,10 @@ static int parse_line(
|
||||
|
||||
case SET_ATTRIBUTE:
|
||||
case RECURSIVE_SET_ATTRIBUTE:
|
||||
if (unbase64) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for file attributes.");
|
||||
}
|
||||
if (!i.argument) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
|
||||
@@ -3187,13 +3239,21 @@ static int parse_line(
|
||||
if (!should_include_path(i.path))
|
||||
return 0;
|
||||
|
||||
r = specifier_expansion_from_arg(specifier_table, &i);
|
||||
if (r == -ENXIO)
|
||||
return log_unresolvable_specifier(fname, line);
|
||||
if (r < 0) {
|
||||
if (IN_SET(r, -EINVAL, -EBADSLT))
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m");
|
||||
if (unbase64) {
|
||||
if (i.argument) {
|
||||
r = unbase64mem(i.argument, SIZE_MAX, &i.binary_argument, &i.binary_argument_size);
|
||||
if (r < 0)
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
|
||||
}
|
||||
} else {
|
||||
r = specifier_expansion_from_arg(specifier_table, &i);
|
||||
if (r == -ENXIO)
|
||||
return log_unresolvable_specifier(fname, line);
|
||||
if (r < 0) {
|
||||
if (IN_SET(r, -EINVAL, -EBADSLT))
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty_or_root(arg_root)) {
|
||||
|
||||
@@ -188,6 +188,14 @@ def test_hard_cleanup(*, user):
|
||||
label = 'valid_symlink-deep'
|
||||
test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink)
|
||||
|
||||
def test_base64():
|
||||
test_line('f~ /tmp/base64-test - - - - UGlmZgpQYWZmClB1ZmYgCg==', user=False, returncode=0)
|
||||
|
||||
with open("/tmp/base64-test", mode='r') as f:
|
||||
d = f.read()
|
||||
|
||||
assert d == "Piff\nPaff\nPuff \n"
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_invalids(user=False)
|
||||
test_invalids(user=True)
|
||||
@@ -198,3 +206,5 @@ if __name__ == '__main__':
|
||||
|
||||
test_hard_cleanup(user=False)
|
||||
test_hard_cleanup(user=True)
|
||||
|
||||
test_base64()
|
||||
|
||||
Reference in New Issue
Block a user