***/
#include <unistd.h>
+#include <sys/sendfile.h>
#include "fileio.h"
#include "util.h"
#include "strv.h"
+#include "utf8.h"
+#include "ctype.h"
-int write_one_line_file(const char *fn, const char *line) {
- _cleanup_fclose_ FILE *f = NULL;
-
- assert(fn);
- assert(line);
-
- f = fopen(fn, "we");
- if (!f)
- return -errno;
-
+int write_string_to_file(FILE *f, const char *line) {
errno = 0;
- if (fputs(line, f) < 0)
- return errno ? -errno : -EIO;
-
+ fputs(line, f);
if (!endswith(line, "\n"))
fputc('\n', f);
return 0;
}
-int write_one_line_file_atomic(const char *fn, const char *line) {
+int write_string_file(const char *fn, const char *line) {
+ _cleanup_fclose_ FILE *f = NULL;
+
+ assert(fn);
+ assert(line);
+
+ f = fopen(fn, "we");
+ if (!f)
+ return -errno;
+
+ return write_string_to_file(f, line);
+}
+
+int write_string_file_atomic(const char *fn, const char *line) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
fchmod_umask(fileno(f), 0644);
errno = 0;
- if (fputs(line, f) < 0) {
- r = -errno;
- goto finish;
- }
-
+ fputs(line, f);
if (!endswith(line, "\n"))
fputc('\n', f);
r = 0;
}
-finish:
if (r < 0)
unlink(p);
return 0;
}
+ssize_t sendfile_full(int out_fd, const char *fn) {
+ _cleanup_fclose_ FILE *f;
+ struct stat st;
+ int r;
+ ssize_t s;
+
+ size_t n, l;
+ _cleanup_free_ char *buf = NULL;
+
+ assert(out_fd > 0);
+ assert(fn);
+
+ f = fopen(fn, "re");
+ if (!f)
+ return -errno;
+
+ r = fstat(fileno(f), &st);
+ if (r < 0)
+ return -errno;
+
+ s = sendfile(out_fd, fileno(f), NULL, st.st_size);
+ if (s < 0)
+ if (errno == EINVAL || errno == ENOSYS) {
+ /* continue below */
+ } else
+ return -errno;
+ else
+ return s;
+
+ /* sendfile() failed, fall back to read/write */
+
+ /* Safety check */
+ if (st.st_size > 4*1024*1024)
+ return -E2BIG;
+
+ n = st.st_size > 0 ? st.st_size : LINE_MAX;
+ l = 0;
+
+ while (true) {
+ char *t;
+ size_t k;
+
+ t = realloc(buf, n);
+ if (!t)
+ return -ENOMEM;
+
+ buf = t;
+ k = fread(buf + l, 1, n - l, f);
+
+ if (k <= 0) {
+ if (ferror(f))
+ return -errno;
+
+ break;
+ }
+
+ l += k;
+ n *= 2;
+
+ /* Safety check */
+ if (n > 4*1024*1024)
+ return -E2BIG;
+ }
+
+ r = write(out_fd, buf, l);
+ if (r < 0)
+ return -errno;
+
+ return (ssize_t) l;
+}
+
int read_full_file(const char *fn, char **contents, size_t *size) {
_cleanup_fclose_ FILE *f = NULL;
size_t n, l;
buf[l] = 0;
*contents = buf;
- buf = NULL;
+ buf = NULL; /* do not free */
if (size)
*size = l;
return 0;
}
-int parse_env_file(
+static int parse_env_file_internal(
const char *fname,
- const char *separator, ...) {
-
- int r = 0;
- char *contents = NULL, *p;
+ const char *newline,
+ int (*push) (const char *filename, unsigned line,
+ const char *key, char *value, void *userdata),
+ void *userdata) {
+
+ _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;
+ unsigned line = 1;
+
+ enum {
+ PRE_KEY,
+ KEY,
+ PRE_VALUE,
+ VALUE,
+ VALUE_ESCAPE,
+ SINGLE_QUOTE_VALUE,
+ SINGLE_QUOTE_VALUE_ESCAPE,
+ DOUBLE_QUOTE_VALUE,
+ DOUBLE_QUOTE_VALUE_ESCAPE,
+ COMMENT,
+ COMMENT_ESCAPE
+ } state = PRE_KEY;
assert(fname);
- assert(separator);
+ assert(newline);
r = read_full_file(fname, &contents, NULL);
if (r < 0)
return r;
- p = contents;
- for (;;) {
- const char *key = NULL;
+ for (p = contents; *p; p++) {
+ char c = *p;
+
+ switch (state) {
+
+ case PRE_KEY:
+ if (strchr(COMMENTS, c))
+ state = COMMENT;
+ else if (!strchr(WHITESPACE, c)) {
+ state = KEY;
+ last_key_whitespace = (size_t) -1;
+
+ if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ key[n_key++] = c;
+ }
+ break;
+
+ case KEY:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+ line ++;
+ n_key = 0;
+ } else if (c == '=') {
+ state = PRE_VALUE;
+ last_value_whitespace = (size_t) -1;
+ } else {
+ if (!strchr(WHITESPACE, c))
+ last_key_whitespace = (size_t) -1;
+ 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;
+ }
+
+ key[n_key++] = c;
+ }
+
+ break;
+
+ case PRE_VALUE:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+ line ++;
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(fname, line, key, value, userdata);
+ if (r < 0)
+ goto fail;
+
+ n_key = 0;
+ value = NULL;
+ value_alloc = n_value = 0;
+
+ } else if (c == '\'')
+ state = SINGLE_QUOTE_VALUE;
+ else if (c == '\"')
+ state = DOUBLE_QUOTE_VALUE;
+ else if (c == '\\')
+ state = VALUE_ESCAPE;
+ else if (!strchr(WHITESPACE, c)) {
+ state = VALUE;
+
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case VALUE:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+ line ++;
+
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ /* Chomp off trailing whitespace from value */
+ if (last_value_whitespace != (size_t) -1)
+ value[last_value_whitespace] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(fname, line, key, value, userdata);
+ if (r < 0)
+ goto fail;
+
+ n_key = 0;
+ value = NULL;
+ value_alloc = n_value = 0;
+
+ } else if (c == '\\') {
+ state = VALUE_ESCAPE;
+ last_value_whitespace = (size_t) -1;
+ } else {
+ if (!strchr(WHITESPACE, c))
+ last_value_whitespace = (size_t) -1;
+ 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;
+ }
- p += strspn(p, separator);
- p += strspn(p, WHITESPACE);
+ value[n_value++] = c;
+ }
- if (!*p)
break;
- if (!strchr(COMMENTS, *p)) {
- va_list ap;
- char **value;
+ case VALUE_ESCAPE:
+ state = VALUE;
+
+ if (!strchr(newline, c)) {
+ /* Escaped newlines we eat up entirely */
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
- va_start(ap, separator);
- while ((key = va_arg(ap, char *))) {
- size_t n;
- char *v;
+ value[n_value++] = c;
+ }
+ break;
- value = va_arg(ap, char **);
+ case SINGLE_QUOTE_VALUE:
+ if (c == '\'')
+ state = PRE_VALUE;
+ else if (c == '\\')
+ state = SINGLE_QUOTE_VALUE_ESCAPE;
+ else {
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
- n = strlen(key);
- if (!strneq(p, key, n) ||
- p[n] != '=')
- continue;
+ value[n_value++] = c;
+ }
- p += n + 1;
- n = strcspn(p, separator);
+ break;
- if (n >= 2 &&
- strchr(QUOTES, p[0]) &&
- p[n-1] == p[0])
- v = strndup(p+1, n-2);
- else
- v = strndup(p, n);
+ case SINGLE_QUOTE_VALUE_ESCAPE:
+ state = SINGLE_QUOTE_VALUE;
- if (!v) {
+ if (!strchr(newline, c)) {
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
r = -ENOMEM;
- va_end(ap);
goto fail;
}
- if (v[0] == '\0') {
- /* return empty value strings as NULL */
- free(v);
- v = NULL;
+ value[n_value++] = c;
+ }
+ break;
+
+ case DOUBLE_QUOTE_VALUE:
+ if (c == '\"')
+ state = PRE_VALUE;
+ else if (c == '\\')
+ state = DOUBLE_QUOTE_VALUE_ESCAPE;
+ else {
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
}
- free(*value);
- *value = v;
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case DOUBLE_QUOTE_VALUE_ESCAPE:
+ state = DOUBLE_QUOTE_VALUE;
+
+ if (!strchr(newline, c)) {
+ if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
- p += n;
+ value[n_value++] = c;
+ }
+ break;
- r ++;
- break;
+ case COMMENT:
+ if (c == '\\')
+ state = COMMENT_ESCAPE;
+ else if (strchr(newline, c)) {
+ state = PRE_KEY;
+ line ++;
}
- va_end(ap);
+ break;
+
+ case COMMENT_ESCAPE:
+ state = COMMENT;
+ break;
}
+ }
+
+ 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 (!key)
- p += strcspn(p, separator);
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ if (state == VALUE)
+ if (last_value_whitespace != (size_t) -1)
+ value[last_value_whitespace] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(fname, line, key, value, userdata);
+ if (r < 0)
+ goto fail;
}
+ return 0;
+
fail:
- free(contents);
+ free(value);
return r;
}
-int load_env_file(const char *fname, char ***rl) {
+static int parse_env_file_push(const char *filename, unsigned line,
+ const char *key, char *value, void *userdata) {
- _cleanup_fclose_ FILE *f;
- _cleanup_strv_free_ char **m = NULL;
- _cleanup_free_ char *c = NULL;
+ const char *k;
+ va_list aq, *ap = userdata;
- assert(fname);
- assert(rl);
+ if (!utf8_is_valid(key)) {
+ _cleanup_free_ char *p = utf8_escape_invalid(key);
- /* This reads an environment file, but will not complain about
- * any invalid assignments, that needs to be done by the
- * caller */
+ log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.",
+ filename, line, p);
+ return -EINVAL;
+ }
- f = fopen(fname, "re");
- if (!f)
- return -errno;
+ if (value && !utf8_is_valid(value)) {
+ _cleanup_free_ char *p = utf8_escape_invalid(value);
- while (!feof(f)) {
- char l[LINE_MAX], *p, *cs, *b;
+ log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
+ filename, line, key, p);
+ return -EINVAL;
+ }
- if (!fgets(l, sizeof(l), f)) {
- if (ferror(f))
- return -errno;
+ va_copy(aq, *ap);
- /* The previous line was a continuation line?
- * Let's process it now, before we leave the
- * loop */
- if (c)
- goto process;
+ while ((k = va_arg(aq, const char *))) {
+ char **v;
- break;
- }
+ v = va_arg(aq, char **);
- /* Is this a continuation line? If so, just append
- * this to c, and go to next line right-away */
- cs = endswith(l, "\\\n");
- if (cs) {
- *cs = '\0';
- b = strappend(c, l);
- if (!b)
- return -ENOMEM;
-
- free(c);
- c = b;
- continue;
+ if (streq(key, k)) {
+ va_end(aq);
+ free(*v);
+ *v = value;
+ return 1;
}
+ }
- /* If the previous line was a continuation line,
- * append the current line to it */
- if (c) {
- b = strappend(c, l);
- if (!b)
- return -ENOMEM;
+ va_end(aq);
+ free(value);
+ return 0;
+}
- free(c);
- c = b;
- }
+int parse_env_file(
+ const char *fname,
+ const char *newline, ...) {
- process:
- p = strstrip(c ? c : l);
+ va_list ap;
+ int r;
- if (*p && !strchr(COMMENTS, *p)) {
- _cleanup_free_ char *u;
- int k;
+ if (!newline)
+ newline = NEWLINE;
- u = normalize_env_assignment(p);
- if (!u)
- return -ENOMEM;
+ va_start(ap, newline);
+ r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
+ va_end(ap);
- k = strv_extend(&m, u);
- if (k < 0)
- return -ENOMEM;
- }
+ return r;
+}
+
+static int load_env_file_push(const char *filename, unsigned line,
+ const char *key, char *value, void *userdata) {
+ char ***m = userdata;
+ char *p;
+ int r;
- free(c);
- c = NULL;
+ if (!utf8_is_valid(key)) {
+ _cleanup_free_ char *t = utf8_escape_invalid(key);
+
+ log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.",
+ filename, line, t);
+ return -EINVAL;
}
- *rl = m;
- m = NULL;
+ if (value && !utf8_is_valid(value)) {
+ _cleanup_free_ char *t = utf8_escape_invalid(value);
+
+ log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
+ filename, line, key, t);
+ return -EINVAL;
+ }
+ p = strjoin(key, "=", strempty(value), NULL);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(m, p);
+ if (r < 0)
+ return r;
+
+ free(value);
+ return 0;
+}
+
+int load_env_file(const char *fname, const char *newline, char ***rl) {
+ char **m = NULL;
+ int r;
+
+ if (!newline)
+ newline = NEWLINE;
+
+ r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
+ if (r < 0) {
+ strv_free(m);
+ return r;
+ }
+
+ *rl = m;
return 0;
}
+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);
+ return;
+ }
+
+ p++;
+ fwrite(v, 1, p-v, f);
+
+ if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
+ fputc('\"', f);
+
+ for (; *p; p++) {
+ if (strchr("\'\"\\`$", *p))
+ fputc('\\', f);
+
+ fputc(*p, f);
+ }
+
+ fputc('\"', f);
+ } else
+ fputs(p, f);
+
+ fputc('\n', f);
+}
+
int write_env_file(const char *fname, char **l) {
char **i;
- char _cleanup_free_ *p = NULL;
- FILE _cleanup_fclose_ *f = NULL;
+ _cleanup_free_ char *p = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
int r;
r = fopen_temporary(fname, &f, &p);
fchmod_umask(fileno(f), 0644);
errno = 0;
- STRV_FOREACH(i, l) {
- fputs(*i, f);
- fputc('\n', f);
- }
+ STRV_FOREACH(i, l)
+ write_env_var(f, *i);
fflush(f);
- if (ferror(f)) {
- if (errno > 0)
- r = -errno;
- else
- r = -EIO;
- } else {
+ if (ferror(f))
+ r = errno ? -errno : -EIO;
+ else {
if (rename(p, fname) < 0)
r = -errno;
else
return r;
}
+
+int executable_is_script(const char *path, char **interpreter) {
+ int r;
+ _cleanup_free_ char *line = NULL;
+ int len;
+ char *ans;
+
+ assert(path);
+
+ r = read_one_line_file(path, &line);
+ if (r < 0)
+ return r;
+
+ if (!startswith(line, "#!"))
+ return 0;
+
+ ans = strstrip(line + 2);
+ len = strcspn(ans, " \t");
+
+ if (len == 0)
+ return 0;
+
+ ans = strndup(ans, len);
+ if (!ans)
+ return -ENOMEM;
+
+ *interpreter = ans;
+ return 1;
+}
+
+/**
+ * Retrieve one field from a file like /proc/self/status. pattern
+ * should start with '\n' and end with a ':'. Whitespace and zeros
+ * after the ':' will be skipped. field must be freed afterwards.
+ */
+int get_status_field(const char *filename, const char *pattern, char **field) {
+ _cleanup_free_ char *status = NULL;
+ char *t;
+ size_t len;
+ int r;
+
+ assert(filename);
+ assert(pattern);
+ assert(field);
+
+ r = read_full_file(filename, &status, NULL);
+ if (r < 0)
+ return r;
+
+ t = strstr(status, pattern);
+ if (!t)
+ return -ENOENT;
+
+ t += strlen(pattern);
+ if (*t) {
+ t += strspn(t, " \t");
+
+ /* Also skip zeros, because when this is used for
+ * capabilities, we don't want the zeros. This way the
+ * same capability set always maps to the same string,
+ * irrespective of the total capability set size. For
+ * other numbers it shouldn't matter. */
+ t += strspn(t, "0");
+ /* Back off one char if there's nothing but whitespace
+ and zeros */
+ if (!*t || isspace(*t))
+ t --;
+ }
+
+ len = strcspn(t, WHITESPACE);
+
+ *field = strndup(t, len);
+ if (!*field)
+ return -ENOMEM;
+
+ return 0;
+}