+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
- 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 Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
+#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "alloc-util.h"
#include "ctype.h"
+#include "def.h"
#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "hexdecoct.h"
//#include "log.h"
//#include "macro.h"
+#include "missing.h"
#include "parse-util.h"
#include "path-util.h"
+#include "process-util.h"
#include "random-util.h"
#include "stdio-util.h"
#include "string-util.h"
#define READ_FULL_BYTES_MAX (4U*1024U*1024U)
-int write_string_stream(FILE *f, const char *line, bool enforce_newline) {
+int write_string_stream_ts(
+ FILE *f,
+ const char *line,
+ WriteStringFileFlags flags,
+ struct timespec *ts) {
+
+ bool needs_nl;
+ int r;
assert(f);
assert(line);
- fputs(line, f);
- if (enforce_newline && !endswith(line, "\n"))
- fputc('\n', f);
+ if (ferror(f))
+ return -EIO;
+
+ needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n");
+
+ if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) {
+ /* If STDIO buffering was disabled, then let's append the newline character to the string itself, so
+ * that the write goes out in one go, instead of two */
+
+ line = strjoina(line, "\n");
+ needs_nl = false;
+ }
+
+ if (fputs(line, f) == EOF)
+ return -errno;
+
+ if (needs_nl)
+ if (fputc('\n', f) == EOF)
+ return -errno;
+
+ if (flags & WRITE_STRING_FILE_SYNC)
+ r = fflush_sync_and_check(f);
+ else
+ r = fflush_and_check(f);
+ if (r < 0)
+ return r;
- return fflush_and_check(f);
+ if (ts) {
+ struct timespec twice[2] = {*ts, *ts};
+
+ if (futimens(fileno(f), twice) < 0)
+ return -errno;
+ }
+
+ return 0;
}
-static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) {
+static int write_string_file_atomic(
+ const char *fn,
+ const char *line,
+ WriteStringFileFlags flags,
+ struct timespec *ts) {
+
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
if (r < 0)
return r;
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
(void) fchmod_umask(fileno(f), 0644);
- r = write_string_stream(f, line, enforce_newline);
- if (r >= 0) {
- if (rename(p, fn) < 0)
- r = -errno;
+ r = write_string_stream_ts(f, line, flags, ts);
+ if (r < 0)
+ goto fail;
+
+ if (rename(p, fn) < 0) {
+ r = -errno;
+ goto fail;
}
- if (r < 0)
- (void) unlink(p);
+ return 0;
+fail:
+ (void) unlink(p);
return r;
}
-int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
+int write_string_file_ts(
+ const char *fn,
+ const char *line,
+ WriteStringFileFlags flags,
+ struct timespec *ts) {
+
_cleanup_fclose_ FILE *f = NULL;
int q, r;
assert(fn);
assert(line);
+ /* We don't know how to verify whether the file contents was already on-disk. */
+ assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
+
if (flags & WRITE_STRING_FILE_ATOMIC) {
assert(flags & WRITE_STRING_FILE_CREATE);
- r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+ r = write_string_file_atomic(fn, line, flags, ts);
if (r < 0)
goto fail;
return r;
- }
+ } else
+ assert(!ts);
if (flags & WRITE_STRING_FILE_CREATE) {
f = fopen(fn, "we");
}
}
- r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+ if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
+ setvbuf(f, NULL, _IONBF, 0);
+
+ r = write_string_stream_ts(f, line, flags, ts);
if (r < 0)
goto fail;
return 0;
}
+int write_string_filef(
+ const char *fn,
+ WriteStringFileFlags flags,
+ const char *format, ...) {
+
+ _cleanup_free_ char *p = NULL;
+ va_list ap;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return write_string_file(fn, p, flags);
+}
+
int read_one_line_file(const char *fn, char **line) {
_cleanup_fclose_ FILE *f = NULL;
- char t[LINE_MAX], *c;
+ int r;
assert(fn);
assert(line);
if (!f)
return -errno;
- if (!fgets(t, sizeof(t), f)) {
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
- if (ferror(f))
- return errno > 0 ? -errno : -EIO;
-
- t[0] = 0;
- }
-
- c = strdup(t);
- if (!c)
- return -ENOMEM;
- truncate_nl(c);
-
- *line = c;
- return 0;
+ r = read_line(f, LONG_LINE_MAX, line);
+ return r < 0 ? r : 0;
}
int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
if (!f)
return -errno;
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
/* We try to read one byte more than we need, so that we know whether we hit eof */
errno = 0;
k = fread(buf, 1, l + accept_extra_nl + 1, f);
}
int read_full_stream(FILE *f, char **contents, size_t *size) {
- size_t n, l;
_cleanup_free_ char *buf = NULL;
struct stat st;
+ size_t n, l;
+ int fd;
assert(f);
assert(contents);
- if (fstat(fileno(f), &st) < 0)
- return -errno;
-
n = LINE_MAX;
- if (S_ISREG(st.st_mode)) {
+ fd = fileno(f);
+ if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
+ * optimize our buffering) */
- /* Safety check */
- if (st.st_size > READ_FULL_BYTES_MAX)
- return -E2BIG;
+ if (fstat(fileno(f), &st) < 0)
+ return -errno;
+
+ if (S_ISREG(st.st_mode)) {
- /* Start with the right file size, but be prepared for
- * files from /proc which generally report a file size
- * of 0 */
- if (st.st_size > 0)
- n = st.st_size;
+ /* Safety check */
+ if (st.st_size > READ_FULL_BYTES_MAX)
+ return -E2BIG;
+
+ /* Start with the right file size, but be prepared for files from /proc which generally report a file
+ * size of 0. Note that we increase the size to read here by one, so that the first read attempt
+ * already makes us notice the EOF. */
+ if (st.st_size > 0)
+ n = st.st_size + 1;
+ }
}
l = 0;
return -ENOMEM;
buf = t;
+ errno = 0;
k = fread(buf + l, 1, n - l, f);
if (k > 0)
l += k;
if (ferror(f))
- return -errno;
+ return errno > 0 ? -errno : -EIO;
if (feof(f))
break;
}
buf[l] = 0;
- *contents = buf;
- buf = NULL; /* do not free */
+ *contents = TAKE_PTR(buf);
if (size)
*size = l;
if (!f)
return -errno;
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
return read_full_stream(f, contents, size);
}
void *userdata,
int *n_pushed) {
- _cleanup_free_ char *contents = NULL, *key = NULL;
size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1;
- char *p, *value = NULL;
- int r;
+ _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
unsigned line = 1;
+ char *p;
+ int r;
enum {
PRE_KEY,
state = KEY;
last_key_whitespace = (size_t) -1;
- if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+ return -ENOMEM;
key[n_key++] = c;
}
else if (last_key_whitespace == (size_t) -1)
last_key_whitespace = n_key;
- if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+ return -ENOMEM;
key[n_key++] = c;
}
r = push(fname, line, key, value, userdata, n_pushed);
if (r < 0)
- goto fail;
+ return r;
n_key = 0;
value = NULL;
else if (!strchr(WHITESPACE, c)) {
state = VALUE;
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
r = push(fname, line, key, value, userdata, n_pushed);
if (r < 0)
- goto fail;
+ return r;
n_key = 0;
value = NULL;
else if (last_value_whitespace == (size_t) -1)
last_value_whitespace = n_value;
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
if (!strchr(newline, c)) {
/* Escaped newlines we eat up entirely */
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
else if (c == '\\')
state = SINGLE_QUOTE_VALUE_ESCAPE;
else {
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
state = SINGLE_QUOTE_VALUE;
if (!strchr(newline, c)) {
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
else if (c == '\\')
state = DOUBLE_QUOTE_VALUE_ESCAPE;
else {
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
state = DOUBLE_QUOTE_VALUE;
if (!strchr(newline, c)) {
- if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+ return -ENOMEM;
value[n_value++] = c;
}
}
}
- if (state == PRE_VALUE ||
- state == VALUE ||
- state == VALUE_ESCAPE ||
- state == SINGLE_QUOTE_VALUE ||
- state == SINGLE_QUOTE_VALUE_ESCAPE ||
- state == DOUBLE_QUOTE_VALUE ||
- state == DOUBLE_QUOTE_VALUE_ESCAPE) {
+ if (IN_SET(state,
+ PRE_VALUE,
+ VALUE,
+ VALUE_ESCAPE,
+ SINGLE_QUOTE_VALUE,
+ SINGLE_QUOTE_VALUE_ESCAPE,
+ DOUBLE_QUOTE_VALUE,
+ DOUBLE_QUOTE_VALUE_ESCAPE)) {
key[n_key] = 0;
r = push(fname, line, key, value, userdata, n_pushed);
if (r < 0)
- goto fail;
+ return r;
+
+ value = NULL;
}
return 0;
-
-fail:
- free(value);
- return r;
}
static int check_utf8ness_and_warn(
return 0;
}
-int parse_env_file(
+int parse_env_filev(
+ FILE *f,
const char *fname,
- const char *newline, ...) {
+ const char *newline,
+ va_list ap) {
- va_list ap;
int r, n_pushed = 0;
+ va_list aq;
if (!newline)
newline = NEWLINE;
+ va_copy(aq, ap);
+ r = parse_env_file_internal(f, fname, newline, parse_env_file_push, &aq, &n_pushed);
+ va_end(aq);
+ if (r < 0)
+ return r;
+
+ return n_pushed;
+}
+
+int parse_env_file(
+ FILE *f,
+ const char *fname,
+ const char *newline,
+ ...) {
+
+ va_list ap;
+ int r;
+
va_start(ap, newline);
- r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
+ r = parse_env_filev(f, fname, newline, ap);
va_end(ap);
- return r < 0 ? r : n_pushed;
+ return r;
}
#if 0 /// UNNEEDED by elogind
*rl = m;
return 0;
}
+#endif // 0
static int load_env_file_push_pairs(
const char *filename, unsigned line,
*rl = m;
return 0;
}
+#if 0 /// UNNEEDED by elogind
+
+static int merge_env_file_push(
+ const char *filename, unsigned line,
+ const char *key, char *value,
+ void *userdata,
+ int *n_pushed) {
+
+ char ***env = userdata;
+ char *expanded_value;
+
+ assert(env);
+
+ if (!value) {
+ log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
+ return 0;
+ }
+
+ if (!env_name_is_valid(key)) {
+ log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
+ free(value);
+ return 0;
+ }
+
+ expanded_value = replace_env(value, *env,
+ REPLACE_ENV_USE_ENVIRONMENT|
+ REPLACE_ENV_ALLOW_BRACELESS|
+ REPLACE_ENV_ALLOW_EXTENDED);
+ if (!expanded_value)
+ return -ENOMEM;
+
+ free_and_replace(value, expanded_value);
+
+ return load_env_file_push(filename, line, key, value, env, n_pushed);
+}
+
+int merge_env_file(
+ char ***env,
+ FILE *f,
+ const char *fname) {
+
+ /* NOTE: this function supports braceful and braceless variable expansions,
+ * plus "extended" substitutions, unlike other exported parsing functions.
+ */
+
+ return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);
+}
static void write_env_var(FILE *f, const char *v) {
const char *p;
p = strchr(v, '=');
if (!p) {
/* Fallback */
- fputs(v, f);
- fputc('\n', f);
+ fputs_unlocked(v, f);
+ fputc_unlocked('\n', f);
return;
}
p++;
- fwrite(v, 1, p-v, f);
+ fwrite_unlocked(v, 1, p-v, f);
if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
- fputc('\"', f);
+ fputc_unlocked('\"', f);
for (; *p; p++) {
if (strchr(SHELL_NEED_ESCAPE, *p))
- fputc('\\', f);
+ fputc_unlocked('\\', f);
- fputc(*p, f);
+ fputc_unlocked(*p, f);
}
- fputc('\"', f);
+ fputc_unlocked('\"', f);
} else
- fputs(p, f);
+ fputs_unlocked(p, f);
- fputc('\n', f);
+ fputc_unlocked('\n', f);
}
int write_env_file(const char *fname, char **l) {
if (r < 0)
return r;
- fchmod_umask(fileno(f), 0644);
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ (void) fchmod_umask(fileno(f), 0644);
STRV_FOREACH(i, l)
write_env_var(f, *i);
}
int executable_is_script(const char *path, char **interpreter) {
- int r;
_cleanup_free_ char *line = NULL;
- int len;
+ size_t len;
char *ans;
+ int r;
assert(path);
r = read_one_line_file(path, &line);
+ if (r == -ENOBUFS) /* First line overly long? if so, then it's not a script */
+ return 0;
if (r < 0)
return r;
return 0;
}
+int fflush_sync_and_check(FILE *f) {
+ int r;
+
+ assert(f);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return r;
+
+ if (fsync(fileno(f)) < 0)
+ return -errno;
+
+ r = fsync_directory_of_file(fileno(f));
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
/* This is much like mkostemp() but is subject to umask(). */
int mkostemp_safe(char *pattern) {
_cleanup_umask_ mode_t u = 0;
if (!filename_is_valid(fn))
return -EINVAL;
- if (extra == NULL)
- extra = "";
+ extra = strempty(extra);
t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
if (!t)
strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
- *ret = path_kill_slashes(t);
+ *ret = path_simplify(t, false);
return 0;
}
if (!filename_is_valid(fn))
return -EINVAL;
- if (!extra)
- extra = "";
+ extra = strempty(extra);
t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
if (!t)
*x = 0;
- *ret = path_kill_slashes(t);
+ *ret = path_simplify(t, false);
return 0;
}
return r;
}
- if (!extra)
- extra = "";
+ extra = strempty(extra);
t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
if (!t)
*x = 0;
- *ret = path_kill_slashes(t);
+ *ret = path_simplify(t, false);
return 0;
}
return fputs(s, f);
}
+#endif // 0
int open_tmpfile_unlinkable(const char *directory, int flags) {
char *p;
return fd;
}
+#if 0 /// UNNEEDED by elogind
int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
_cleanup_free_ char *tmp = NULL;
int r, fd;
if (fd < 0)
return -errno;
- *ret_path = tmp;
- tmp = NULL;
+ *ret_path = TAKE_PTR(tmp);
+
+ return fd;
+}
+#endif // 0
+
+int open_serialization_fd(const char *ident) {
+ int fd = -1;
+
+ fd = memfd_create(ident, MFD_CLOEXEC);
+ if (fd < 0) {
+ const char *path;
+
+ path = getpid_cached() == 1 ? "/run/systemd" : "/tmp";
+ fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ log_debug("Serializing %s to %s.", ident, path);
+ } else
+ log_debug("Serializing %s to memfd.", ident);
return fd;
}
+#if 0 /// UNNEEDED by elogind
int link_tmpfile(int fd, const char *path, const char *target) {
assert(fd >= 0);
if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0)
return -errno;
} else {
- char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+ char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
return 0;
}
-#endif // 0
int read_nul_string(FILE *f, char **ret) {
_cleanup_free_ char *x = NULL;
return -ENOMEM;
}
- *ret = x;
- x = NULL;
+ *ret = TAKE_PTR(x);
return 0;
}
*ret = p;
return 0;
}
+#endif // 0
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
+
+int read_line(FILE *f, size_t limit, char **ret) {
+ _cleanup_free_ char *buffer = NULL;
+ size_t n = 0, allocated = 0, count = 0;
+
+ assert(f);
+
+ /* Something like a bounded version of getline().
+ *
+ * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string
+ * returned.
+ *
+ * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
+ * the number of characters in the returned string). When EOF is hit, 0 is returned.
+ *
+ * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
+ * delimiters. If the limit is hit we fail and return -ENOBUFS.
+ *
+ * If a line shall be skipped ret may be initialized as NULL. */
+
+ if (ret) {
+ if (!GREEDY_REALLOC(buffer, allocated, 1))
+ return -ENOMEM;
+ }
+
+ {
+ _unused_ _cleanup_(funlockfilep) FILE *flocked = f;
+ flockfile(f);
+
+ for (;;) {
+ int c;
+
+ if (n >= limit)
+ return -ENOBUFS;
+
+ errno = 0;
+ c = fgetc_unlocked(f);
+ if (c == EOF) {
+ /* if we read an error, and have no data to return, then propagate the error */
+ if (ferror_unlocked(f) && n == 0)
+ return errno > 0 ? -errno : -EIO;
+
+ break;
+ }
+
+ count++;
+
+ if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
+ break;
+
+ if (ret) {
+ if (!GREEDY_REALLOC(buffer, allocated, n + 2))
+ return -ENOMEM;
+
+ buffer[n] = (char) c;
+ }
+
+ n++;
+ }
+ }
+
+ if (ret) {
+ buffer[n] = 0;
+
+ *ret = TAKE_PTR(buffer);
+ }
+
+ return (int) count;
+}