summaryrefslogtreecommitdiff
path: root/src/libnm-systemd-shared/src/basic/fs-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-systemd-shared/src/basic/fs-util.c')
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.c203
1 files changed, 150 insertions, 53 deletions
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c
index 27b4c2b661..32fd849d34 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.c
+++ b/src/libnm-systemd-shared/src/basic/fs-util.c
@@ -5,6 +5,7 @@
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
+#include <sys/file.h>
#include <linux/falloc.h>
#include <linux/magic.h>
#include <unistd.h>
@@ -15,6 +16,8 @@
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
+#include "label.h"
+#include "lock-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fcntl.h"
@@ -35,11 +38,6 @@
#include "umask-util.h"
#include "user-util.h"
-int unlink_noerrno(const char *path) {
- PROTECT_ERRNO;
- return RET_NERRNO(unlink(path));
-}
-
#if 0 /* NM_IGNORED */
int rmdir_parents(const char *path, const char *stop) {
char *p;
@@ -311,7 +309,7 @@ int fchmod_opath(int fd, mode_t m) {
}
int futimens_opath(int fd, const struct timespec ts[2]) {
- /* Similar to fchmod_path() but for futimens() */
+ /* Similar to fchmod_opath() but for futimens() */
if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) {
if (errno != ENOENT)
@@ -683,7 +681,7 @@ void unlink_tempfilep(char (*p)[]) {
* successfully created. We ignore both the rare case where the
* original suffix is used and unlink failures. */
if (!endswith(*p, ".XXXXXX"))
- (void) unlink_noerrno(*p);
+ (void) unlink(*p);
}
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
@@ -781,7 +779,7 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
* punch-hole/truncate this to release the disk space. */
bs = MAX(st.st_blksize, 512);
- l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */
+ l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
return 0; /* Successfully punched a hole! 😊 */
@@ -795,12 +793,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
return 0;
}
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
_cleanup_free_ char *parent = NULL;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
r = path_extract_directory(path, &parent);
- if (r < 0)
+ if (r == -EDESTADDRREQ) {
+ parent = strdup(".");
+ if (!parent)
+ return -ENOMEM;
+ } else if (r == -EADDRNOTAVAIL) {
+ parent = strdup(path);
+ if (!parent)
+ return -ENOMEM;
+ } else if (r < 0)
return r;
/* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
@@ -811,7 +820,7 @@ int open_parent(const char *path, int flags, mode_t mode) {
else if (!FLAGS_SET(flags, O_TMPFILE))
flags |= O_DIRECTORY|O_RDONLY;
- return RET_NERRNO(open(parent, flags, mode));
+ return RET_NERRNO(openat(dir_fd, parent, flags, mode));
}
#endif /* NM_IGNORED */
@@ -822,11 +831,11 @@ int conservative_renameat(
_cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
struct stat old_stat, new_stat;
- /* Renames the old path to thew new path, much like renameat() — except if both are regular files and
+ /* Renames the old path to the new path, much like renameat() — except if both are regular files and
* have the exact same contents and basic file attributes already. In that case remove the new file
* instead. This call is useful for reducing inotify wakeups on files that are updated but don't
* actually change. This function is written in a style that we rather rename too often than suppress
- * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+ * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
* events is an optimization only, not more. */
old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
@@ -1002,8 +1011,7 @@ int parse_cifs_service(
int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
_cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
- _cleanup_free_ char *fname = NULL;
- bool made;
+ _cleanup_free_ char *fname = NULL, *parent = NULL;
int r;
/* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1018,19 +1026,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
/* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
* flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
- if (isempty(path))
- return -EINVAL;
-
- if (!filename_is_valid(path)) {
- _cleanup_free_ char *parent = NULL;
-
- /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
- * that we can pin it, and operate below it. */
-
- r = path_extract_directory(path, &parent);
- if (r < 0)
+ /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+ * that we can pin it, and operate below it. */
+ r = path_extract_directory(path, &parent);
+ if (r < 0) {
+ if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
return r;
-
+ } else {
r = path_extract_filename(path, &fname);
if (r < 0)
return r;
@@ -1043,33 +1045,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
path = fname;
}
- r = RET_NERRNO(mkdirat(dirfd, path, mode));
- if (r == -EEXIST) {
- if (FLAGS_SET(flags, O_EXCL))
- return -EEXIST;
-
- made = false;
- } else if (r < 0)
- return r;
- else
- made = true;
-
- fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW));
- if (fd < 0) {
- if (fd == -ENOENT) /* 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. */
- return -ENOENT;
- if (fd == -ELOOP) /* is a symlink? exists already → created by someone else, don't unlink */
- return -EEXIST;
- if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */
- return -EEXIST;
-
- if (made)
- (void) unlinkat(dirfd, path, AT_REMOVEDIR);
-
+ fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
+ if (IN_SET(fd, -ELOOP, -ENOTDIR))
+ return -EEXIST;
+ if (fd < 0)
return fd;
- }
return TAKE_FD(fd);
}
@@ -1120,3 +1100,120 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
return -EEXIST;
}
}
+
+int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
+ _cleanup_close_ int fd = -EBADF;
+ bool made = false;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+ if (isempty(path)) {
+ assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
+ return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
+ }
+
+ 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;
+ }
+
+ if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
+ r = RET_NERRNO(mkdirat(dir_fd, path, mode));
+ if (r == -EEXIST) {
+ if (FLAGS_SET(open_flags, O_EXCL))
+ return -EEXIST;
+
+ made = false;
+ } else if (r < 0)
+ return r;
+ else
+ made = 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(dir_fd, path, open_flags, mode));
+ 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)
+ (void) unlinkat(dir_fd, path, AT_REMOVEDIR);
+
+ return fd;
+ }
+
+ if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
+ r = label_ops_post(dir_fd, path);
+ if (r < 0)
+ return r;
+ }
+
+ return TAKE_FD(fd);
+}
+
+#if 0 /* NM_IGNORED */
+int xopenat_lock(
+ int dir_fd,
+ const char *path,
+ int open_flags,
+ XOpenFlags xopen_flags,
+ mode_t mode,
+ LockType locktype,
+ int operation) {
+
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
+
+ /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
+ * the same error here). */
+ if (FLAGS_SET(open_flags, O_DIRECTORY) && locktype != LOCK_BSD)
+ return -EBADF;
+
+ for (;;) {
+ struct stat st;
+
+ fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode);
+ if (fd < 0)
+ return fd;
+
+ r = lock_generic(fd, locktype, operation);
+ if (r < 0)
+ return r;
+
+ /* If we acquired the lock, let's check if the file/directory still exists in the file
+ * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+ * case our acquired lock is worthless, hence try again. */
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ fd = safe_close(fd);
+ }
+
+ return TAKE_FD(fd);
+}
+#endif /* NM_IGNORED */