summaryrefslogtreecommitdiffstats
path: root/src/basic
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic')
-rw-r--r--src/basic/fileio.c94
-rw-r--r--src/basic/fileio.h6
-rw-r--r--src/basic/fs-util.c88
-rw-r--r--src/basic/gunicode.c3
-rw-r--r--src/basic/label.c15
-rw-r--r--src/basic/label.h7
6 files changed, 118 insertions, 95 deletions
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index bf1603de0e..aa1657c118 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -18,6 +18,7 @@
#include "fileio.h"
#include "fs-util.h"
#include "hexdecoct.h"
+#include "label.h"
#include "log.h"
#include "macro.h"
#include "mkdir.h"
@@ -230,22 +231,40 @@ static int write_string_file_atomic_at(
/* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement
* semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */
+ mode_t mode = write_string_file_flags_to_mode(flags);
+
+ bool call_label_ops_post = false;
+ if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL)) {
+ r = label_ops_pre(dir_fd, fn, mode);
+ if (r < 0)
+ return r;
+
+ call_label_ops_post = true;
+ }
+
r = fopen_temporary_at(dir_fd, fn, &f, &p);
if (r < 0)
- return r;
+ goto fail;
+
+ if (call_label_ops_post) {
+ call_label_ops_post = false;
+
+ r = label_ops_post(fileno(f), /* path= */ NULL, /* created= */ true);
+ if (r < 0)
+ goto fail;
+ }
r = write_string_stream_full(f, line, flags, ts);
if (r < 0)
goto fail;
- r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags));
+ r = fchmod_umask(fileno(f), mode);
if (r < 0)
goto fail;
- if (renameat(dir_fd, p, dir_fd, fn) < 0) {
- r = -errno;
+ r = RET_NERRNO(renameat(dir_fd, p, dir_fd, fn));
+ if (r < 0)
goto fail;
- }
if (FLAGS_SET(flags, WRITE_STRING_FILE_SYNC)) {
/* Sync the rename, too */
@@ -257,7 +276,11 @@ static int write_string_file_atomic_at(
return 0;
fail:
- (void) unlinkat(dir_fd, p, 0);
+ if (call_label_ops_post)
+ (void) label_ops_post(f ? fileno(f) : dir_fd, f ? NULL : fn, /* created= */ !!f);
+
+ if (f)
+ (void) unlinkat(dir_fd, p, 0);
return r;
}
@@ -268,9 +291,10 @@ int write_string_file_full(
WriteStringFileFlags flags,
const struct timespec *ts) {
+ bool call_label_ops_post = false, made_file = false;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_close_ int fd = -EBADF;
- int q, r;
+ int r;
assert(fn);
assert(line);
@@ -292,21 +316,40 @@ int write_string_file_full(
goto fail;
return r;
- } else
- assert(!ts);
+ }
+
+ mode_t mode = write_string_file_flags_to_mode(flags);
+
+ if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL|WRITE_STRING_FILE_CREATE)) {
+ r = label_ops_pre(dir_fd, fn, mode);
+ if (r < 0)
+ goto fail;
+
+ call_label_ops_post = true;
+ }
/* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */
- fd = openat(dir_fd, fn, O_CLOEXEC|O_NOCTTY |
- (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
- (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
- (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
- (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
- write_string_file_flags_to_mode(flags));
+ fd = openat_report_new(
+ dir_fd, fn, O_CLOEXEC | O_NOCTTY |
+ (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
+ (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
+ (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
+ (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
+ mode,
+ &made_file);
if (fd < 0) {
- r = -errno;
+ r = fd;
goto fail;
}
+ if (call_label_ops_post) {
+ call_label_ops_post = false;
+
+ r = label_ops_post(fd, /* path= */ NULL, made_file);
+ if (r < 0)
+ goto fail;
+ }
+
r = take_fdopen_unlocked(&fd, "w", &f);
if (r < 0)
goto fail;
@@ -321,19 +364,24 @@ int write_string_file_full(
return 0;
fail:
+ if (call_label_ops_post)
+ (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : fn, made_file);
+
+ if (made_file)
+ (void) unlinkat(dir_fd, fn, 0);
+
if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE))
return r;
f = safe_fclose(f);
+ fd = safe_close(fd);
- /* OK, the operation failed, but let's see if the right
- * contents in place already. If so, eat up the error. */
-
- q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE));
- if (q <= 0)
- return r;
+ /* OK, the operation failed, but let's see if the right contents in place already. If so, eat up the
+ * error. */
+ if (verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)) > 0)
+ return 0;
- return 0;
+ return r;
}
int write_string_filef(
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index dc514e3ccc..8f54491c12 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -28,11 +28,7 @@ typedef enum {
WRITE_STRING_FILE_MODE_0600 = 1 << 10,
WRITE_STRING_FILE_MODE_0444 = 1 << 11,
WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12,
-
- /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
- more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
- and friends. */
-
+ WRITE_STRING_FILE_LABEL = 1 << 13,
} WriteStringFileFlags;
typedef enum {
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 16250e1e94..db0a316e6d 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -1066,65 +1066,50 @@ int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_
}
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
- unsigned attempts = 7;
int fd;
/* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if
* it already existed before. This is only relevant if O_CREAT is set without O_EXCL, and thus will
- * shortcut to openat() otherwise */
-
- if (!ret_newly_created)
- return RET_NERRNO(openat(dirfd, pathname, flags, mode));
+ * shortcut to openat() otherwise.
+ *
+ * Note that this routine is a bit more strict with symlinks than regular openat() is. If O_NOFOLLOW
+ * is not specified, then we'll follow the symlink when opening an existing file but we will *not*
+ * follow it when creating a new one (because that's a terrible UNIX misfeature and generally a
+ * security hole). */
if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
fd = openat(dirfd, pathname, flags, mode);
if (fd < 0)
return -errno;
- *ret_newly_created = FLAGS_SET(flags, O_CREAT);
+ if (ret_newly_created)
+ *ret_newly_created = FLAGS_SET(flags, O_CREAT);
return fd;
}
- for (;;) {
+ for (unsigned attempts = 7;;) {
/* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
if (fd >= 0) {
- *ret_newly_created = false;
+ if (ret_newly_created)
+ *ret_newly_created = false;
return fd;
}
if (errno != ENOENT)
return -errno;
- /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL. */
- fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL, mode);
+ /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
+ fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
if (fd >= 0) {
- *ret_newly_created = true;
+ if (ret_newly_created)
+ *ret_newly_created = true;
return fd;
}
if (errno != EEXIST)
return -errno;
- /* Hmm, so now we got EEXIST? This can indicate two things. First, if the path points to a
- * dangling symlink, the first openat() will fail with ENOENT because the symlink is resolved
- * and the second openat() will fail with EEXIST because symlinks are not followed when
- * O_CREAT|O_EXCL is specified. Let's check for this explicitly and fall back to opening with
- * just O_CREAT and assume we're the ones that created the file. */
-
- struct stat st;
- if (fstatat(dirfd, pathname, &st, AT_SYMLINK_NOFOLLOW) < 0)
- return -errno;
-
- if (S_ISLNK(st.st_mode)) {
- fd = openat(dirfd, pathname, flags | O_CREAT, mode);
- if (fd < 0)
- return -errno;
-
- *ret_newly_created = true;
- return fd;
- }
-
- /* If we're not operating on a symlink, someone might have created the file between the first
- * and second call to openat(). Let's try again but with a limit so we don't spin forever. */
+ /* Hmm, so now we got EEXIST? Then someone might have created the file between the first and
+ * second call to openat(). Let's try again but with a limit so we don't spin forever. */
if (--attempts == 0) /* Give up eventually, somebody is playing with us */
return -EEXIST;
@@ -1153,10 +1138,14 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
}
+ bool call_label_ops_post = false;
+
if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
if (r < 0)
return r;
+
+ call_label_ops_post = true;
}
if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
@@ -1172,37 +1161,19 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
else
made_dir = true;
- if (FLAGS_SET(xopen_flags, XO_LABEL)) {
- r = label_ops_post(dir_fd, path);
- if (r < 0)
- return r;
- }
-
open_flags &= ~(O_EXCL|O_CREAT);
- xopen_flags &= ~XO_LABEL;
}
- fd = RET_NERRNO(openat_report_new(dir_fd, path, open_flags, mode, &made_file));
+ fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
if (fd < 0) {
- if (IN_SET(fd,
- /* We got ENOENT? then someone else immediately removed it after we
- * created it. In that case let's return immediately without unlinking
- * anything, because there simply isn't anything to unlink anymore. */
- -ENOENT,
- /* is a symlink? exists already → created by someone else, don't unlink */
- -ELOOP,
- /* not a directory? exists already → created by someone else, don't unlink */
- -ENOTDIR))
- return fd;
-
- if (made_dir)
- (void) unlinkat(dir_fd, path, AT_REMOVEDIR);
-
- return fd;
+ r = fd;
+ goto error;
}
- if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
- r = label_ops_post(dir_fd, path);
+ if (call_label_ops_post) {
+ call_label_ops_post = false;
+
+ r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
if (r < 0)
goto error;
}
@@ -1216,6 +1187,9 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
return TAKE_FD(fd);
error:
+ if (call_label_ops_post)
+ (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
+
if (made_dir || made_file)
(void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c
index 36beb957e1..d7496f3e21 100644
--- a/src/basic/gunicode.c
+++ b/src/basic/gunicode.c
@@ -6,6 +6,7 @@
*/
#include "gunicode.h"
+#include "macro.h"
#define unichar uint32_t
@@ -92,7 +93,7 @@ unichar_iswide (unichar c)
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
};
- if (bsearch ((void *)(uintptr_t)c, wide, (sizeof (wide) / sizeof ((wide)[0])), sizeof wide[0],
+ if (bsearch ((void *)(uintptr_t)c, wide, ELEMENTSOF(wide), sizeof wide[0],
interval_compare))
return true;
diff --git a/src/basic/label.c b/src/basic/label.c
index 8b084a7c05..6bae653188 100644
--- a/src/basic/label.c
+++ b/src/basic/label.c
@@ -4,10 +4,13 @@
#include <stddef.h>
#include "label.h"
+#include "macro.h"
static const LabelOps *label_ops = NULL;
int label_ops_set(const LabelOps *ops) {
+ assert(ops);
+
if (label_ops)
return -EBUSY;
@@ -15,6 +18,10 @@ int label_ops_set(const LabelOps *ops) {
return 0;
}
+void label_ops_reset(void) {
+ label_ops = NULL;
+}
+
int label_ops_pre(int dir_fd, const char *path, mode_t mode) {
if (!label_ops || !label_ops->pre)
return 0;
@@ -22,13 +29,9 @@ int label_ops_pre(int dir_fd, const char *path, mode_t mode) {
return label_ops->pre(dir_fd, path, mode);
}
-int label_ops_post(int dir_fd, const char *path) {
+int label_ops_post(int dir_fd, const char *path, bool created) {
if (!label_ops || !label_ops->post)
return 0;
- return label_ops->post(dir_fd, path);
-}
-
-void label_ops_reset(void) {
- label_ops = NULL;
+ return label_ops->post(dir_fd, path, created);
}
diff --git a/src/basic/label.h b/src/basic/label.h
index a070bf28cc..d001307a4f 100644
--- a/src/basic/label.h
+++ b/src/basic/label.h
@@ -1,15 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <stdbool.h>
#include <sys/types.h>
typedef struct LabelOps {
int (*pre)(int dir_fd, const char *path, mode_t mode);
- int (*post)(int dir_fd, const char *path);
+ int (*post)(int dir_fd, const char *path, bool created);
} LabelOps;
int label_ops_set(const LabelOps *label_ops);
+void label_ops_reset(void);
int label_ops_pre(int dir_fd, const char *path, mode_t mode);
-int label_ops_post(int dir_fd, const char *path);
-void label_ops_reset(void);
+int label_ops_post(int dir_fd, const char *path, bool created);