diff options
-rw-r--r-- | man/tmpfiles.d.xml | 14 | ||||
-rw-r--r-- | src/tmpfiles/tmpfiles.c | 122 | ||||
-rwxr-xr-x | test/test-systemd-tmpfiles.py | 10 |
3 files changed, 113 insertions, 33 deletions
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 3267454f3b..79d0ff6bdd 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -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>, diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 0069f444e9..06d11f34b9 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -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; @@ -1329,6 +1343,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 +1371,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 +1402,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 +1465,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 +1548,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 +2667,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 +2709,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 +2979,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 +3050,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 +3107,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 +3127,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 +3156,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 +3176,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 +3192,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 +3208,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 +3241,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)) { diff --git a/test/test-systemd-tmpfiles.py b/test/test-systemd-tmpfiles.py index ba42b3fa37..4b3bdf3364 100755 --- a/test/test-systemd-tmpfiles.py +++ b/test/test-systemd-tmpfiles.py @@ -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() |