summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2014-12-27 17:44:04 +0100
committerLennart Poettering <lennart@poettering.net>2014-12-28 02:08:40 +0100
commitebd93cb684806ac0f352139e69ac8f53eb49f5e4 (patch)
tree537df721db4f8a9a05ff8a3093481f5ea5571a92 /src
parent086821244b5113f00a0ef993b78dc56aae2a8f6c (diff)
machinectl/machined: implement "rename", "clone", "read-only" verbs for machine images
Diffstat (limited to 'src')
-rw-r--r--src/machine/image-dbus.c92
-rw-r--r--src/machine/machinectl.c90
-rw-r--r--src/machine/machined-dbus.c87
-rw-r--r--src/shared/copy.c46
-rw-r--r--src/shared/copy.h1
-rw-r--r--src/shared/machine-image.c150
-rw-r--r--src/shared/machine-image.h3
-rw-r--r--src/shared/missing.h10
-rw-r--r--src/shared/util.c14
9 files changed, 470 insertions, 23 deletions
diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c
index be2369953c..1030cd314f 100644
--- a/src/machine/image-dbus.c
+++ b/src/machine/image-dbus.c
@@ -236,6 +236,95 @@ static int method_remove(
return sd_bus_reply_method_return(message, NULL);
}
+static int method_rename(
+ sd_bus *bus,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_(image_unrefp) Image *image = NULL;
+ const char *new_name;
+ int r;
+
+ assert(bus);
+ assert(message);
+
+ r = image_find_by_bus_path_with_error(sd_bus_message_get_path(message), &image, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "s", &new_name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = image_rename(image, new_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_clone(
+ sd_bus *bus,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_(image_unrefp) Image *image = NULL;
+ const char *new_name;
+ int r, read_only;
+
+ assert(bus);
+ assert(message);
+
+ r = image_find_by_bus_path_with_error(sd_bus_message_get_path(message), &image, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "sb", &new_name, &read_only);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = image_clone(image, new_name, read_only);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_mark_read_only(
+ sd_bus *bus,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_(image_unrefp) Image *image = NULL;
+ int r, read_only;
+
+ assert(bus);
+ assert(message);
+
+ r = image_find_by_bus_path_with_error(sd_bus_message_get_path(message), &image, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "b", &read_only);
+ if (r < 0)
+ return r;
+
+ r = image_read_only(image, read_only);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
const sd_bus_vtable image_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Name", "s", property_get_name, 0, 0),
@@ -245,6 +334,9 @@ const sd_bus_vtable image_vtable[] = {
SD_BUS_PROPERTY("CreationTimestamp", "t", property_get_crtime, 0, 0),
SD_BUS_PROPERTY("ModificationTimestamp", "t", property_get_mtime, 0, 0),
SD_BUS_METHOD("Remove", NULL, NULL, method_remove, 0),
+ SD_BUS_METHOD("Rename", "s", NULL, method_rename, 0),
+ SD_BUS_METHOD("Clone", "sb", NULL, method_clone, 0),
+ SD_BUS_METHOD("MarkeReadOnly", "b", NULL, method_mark_read_only, 0),
SD_BUS_VTABLE_END
};
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 500e5b721a..893b429342 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1286,13 +1286,11 @@ static int login_machine(int argc, char *argv[], void *userdata) {
static int remove_image(int argc, char *argv[], void *userdata) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus = userdata;
- int i;
+ int r, i;
assert(bus);
for (i = 1; i < argc; i++) {
- int r;
-
r = sd_bus_call_method(
bus,
"org.freedesktop.machine1",
@@ -1311,6 +1309,80 @@ static int remove_image(int argc, char *argv[], void *userdata) {
return 0;
}
+static int rename_image(int argc, char *argv[], void *userdata) {
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = userdata;
+ int r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "RenameImage",
+ &error,
+ NULL,
+ "ss", argv[1], argv[2]);
+ if (r < 0) {
+ log_error("Could not rename image: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ return 0;
+}
+
+static int clone_image(int argc, char *argv[], void *userdata) {
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = userdata;
+ int r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "CloneImage",
+ &error,
+ NULL,
+ "ssb", argv[1], argv[2], arg_read_only);
+ if (r < 0) {
+ log_error("Could not clone image: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ return 0;
+}
+
+static int read_only_image(int argc, char *argv[], void *userdata) {
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = userdata;
+ int b = true, r;
+
+ if (argc > 2) {
+ b = parse_boolean(argv[2]);
+ if (b < 0) {
+ log_error("Failed to parse boolean argument: %s", argv[2]);
+ return -EINVAL;
+ }
+ }
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "MarkImageReadOnly",
+ &error,
+ NULL,
+ "sb", argv[1], b);
+ if (r < 0) {
+ log_error("Could not mark image read-only: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ return 0;
+}
+
static int help(int argc, char *argv[], void *userdata) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
@@ -1336,15 +1408,18 @@ static int help(int argc, char *argv[], void *userdata) {
" login NAME Get a login prompt on a container\n"
" poweroff NAME... Power off one or more containers\n"
" reboot NAME... Reboot one or more containers\n"
- " kill NAME... Send signal to processes of a VM/container\n"
" terminate NAME... Terminate one or more VMs/containers\n"
- " bind NAME PATH [PATH] Bind mount a path from the host into a container\n"
+ " kill NAME... Send signal to processes of a VM/container\n"
" copy-to NAME PATH [PATH] Copy files from the host to a container\n"
- " copy-from NAME PATH [PATH] Copy files from a container to the host\n\n"
+ " copy-from NAME PATH [PATH] Copy files from a container to the host\n"
+ " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n"
"Image Commands:\n"
" list-images Show available images\n"
" image-status NAME... Show image details\n"
" show-image NAME... Show properties of image\n"
+ " clone NAME NAME Clone an image\n"
+ " rename NAME NAME Rename an image\n"
+ " read-only NAME [BOOL] Mark or unmark image read-only\n"
" remove NAME... Remove an image\n",
program_invocation_short_name);
@@ -1482,6 +1557,9 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "copy-to", 3, 4, 0, copy_files },
{ "copy-from", 3, 4, 0, copy_files },
{ "remove", 2, VERB_ANY, 0, remove_image },
+ { "rename", 3, 3, 0, rename_image },
+ { "clone", 3, 3, 0, clone_image },
+ { "read-only", 2, 3, 0, read_only_image },
{}
};
diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c
index 9296377c6f..18b772d42d 100644
--- a/src/machine/machined-dbus.c
+++ b/src/machine/machined-dbus.c
@@ -588,6 +588,90 @@ static int method_remove_image(sd_bus *bus, sd_bus_message *message, void *userd
return sd_bus_reply_method_return(message, NULL);
}
+static int method_rename_image(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image* i = NULL;
+ const char *old_name, *new_name;
+ int r;
+
+ assert(bus);
+ assert(message);
+
+ r = sd_bus_message_read(message, "ss", &old_name, &new_name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(old_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = image_find(old_name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
+
+ r = image_rename(i, new_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_clone_image(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image *i = NULL;
+ const char *old_name, *new_name;
+ int read_only, r;
+
+ assert(bus);
+ r = sd_bus_message_read(message, "ssb", &old_name, &new_name, &read_only);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(old_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = image_find(old_name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
+
+ r = image_clone(i, new_name, read_only);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_mark_image_read_only(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image *i = NULL;
+ const char *name;
+ int read_only, r;
+
+ assert(bus);
+ r = sd_bus_message_read(message, "sb", &name, &read_only);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
+
+ r = image_find(name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
+
+ r = image_read_only(i, read_only);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED),
@@ -606,6 +690,9 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0),
SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, 0),
+ SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, 0),
+ SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, 0),
+ SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, 0),
SD_BUS_SIGNAL("MachineNew", "so", 0),
SD_BUS_SIGNAL("MachineRemoved", "so", 0),
SD_BUS_VTABLE_END
diff --git a/src/shared/copy.c b/src/shared/copy.c
index 92f6e1e114..3df636704c 100644
--- a/src/shared/copy.c
+++ b/src/shared/copy.c
@@ -120,6 +120,7 @@ static int fd_copy_symlink(int df, const char *from, const struct stat *st, int
static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) {
_cleanup_close_ int fdf = -1, fdt = -1;
+ struct timespec ts[2];
int r, q;
assert(from);
@@ -146,7 +147,10 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int
if (fchmod(fdt, st->st_mode & 07777) < 0)
r = -errno;
- (void) copy_times(fdf, fdt);
+ ts[0] = st->st_atim;
+ ts[1] = st->st_mtim;
+ (void) futimens(fdt, ts);
+
(void) copy_xattr(fdf, fdt);
q = close(fdt);
@@ -243,14 +247,19 @@ static int fd_copy_directory(
r = 0;
if (created) {
+ struct timespec ut[2] = {
+ st->st_atim,
+ st->st_mtim
+ };
+
if (fchown(fdt, st->st_uid, st->st_gid) < 0)
r = -errno;
if (fchmod(fdt, st->st_mode & 07777) < 0)
r = -errno;
- (void) copy_times(fdf, fdt);
- (void) copy_xattr(fdf, fdt);
+ (void) futimens(fdt, ut);
+ (void) copy_xattr(dirfd(d), fdt);
}
FOREACH_DIRENT(de, d, return -errno) {
@@ -356,9 +365,11 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode) {
assert(from);
assert(to);
- fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
- if (fdt < 0)
- return -errno;
+ RUN_WITH_UMASK(0000) {
+ fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
+ if (fdt < 0)
+ return -errno;
+ }
r = copy_file_fd(from, fdt, true);
if (r < 0) {
@@ -375,6 +386,29 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode) {
return 0;
}
+int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace) {
+ _cleanup_free_ char *t;
+ int r;
+
+ assert(from);
+ assert(to);
+
+ r = tempfn_random(to, &t);
+ if (r < 0)
+ return r;
+
+ r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode);
+ if (r < 0)
+ return r;
+
+ if (renameat2(AT_FDCWD, t, AT_FDCWD, to, replace ? 0 : RENAME_NOREPLACE) < 0) {
+ unlink_noerrno(t);
+ return -errno;
+ }
+
+ return 0;
+}
+
int copy_times(int fdf, int fdt) {
struct timespec ut[2];
struct stat st;
diff --git a/src/shared/copy.h b/src/shared/copy.h
index 6d725ef26d..58159a02cc 100644
--- a/src/shared/copy.h
+++ b/src/shared/copy.h
@@ -26,6 +26,7 @@
int copy_file_fd(const char *from, int to, bool try_reflink);
int copy_file(const char *from, const char *to, int flags, mode_t mode);
+int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace);
int copy_tree(const char *from, const char *to, bool merge);
int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge);
int copy_directory_fd(int dirfd, const char *to, bool merge);
diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c
index fa06a0dc59..7c041fab76 100644
--- a/src/shared/machine-image.c
+++ b/src/shared/machine-image.c
@@ -20,11 +20,13 @@
***/
#include <sys/statfs.h>
+#include <fcntl.h>
#include "strv.h"
#include "utf8.h"
#include "btrfs-util.h"
#include "path-util.h"
+#include "copy.h"
#include "machine-image.h"
static const char image_search_path[] =
@@ -317,18 +319,158 @@ void image_hashmap_free(Hashmap *map) {
}
int image_remove(Image *i) {
- int r;
-
assert(i);
if (path_equal(i->path, "/") ||
path_startswith(i->path, "/usr"))
return -EROFS;
- if (i->type == IMAGE_SUBVOLUME)
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
return btrfs_subvol_remove(i->path);
- else
+
+ case IMAGE_DIRECTORY:
+ case IMAGE_GPT:
return rm_rf_dangerous(i->path, false, true, false);
+
+ default:
+ return -ENOTSUP;
+ }
+}
+
+int image_rename(Image *i, const char *new_name) {
+ _cleanup_free_ char *new_path = NULL, *nn = NULL;
+ int r;
+
+ assert(i);
+
+ if (!image_name_is_valid(new_name))
+ return -EINVAL;
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ r = image_find(new_name, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return -EEXIST;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+ case IMAGE_DIRECTORY:
+ new_path = file_in_same_dir(i->path, new_name);
+ break;
+
+ case IMAGE_GPT: {
+ const char *fn;
+
+ fn = strappenda(new_name, ".gpt");
+ new_path = file_in_same_dir(i->path, fn);
+ break;
+ }
+
+ default:
+ return -ENOTSUP;
+ }
+
+ if (!new_path)
+ return -ENOMEM;
+
+ nn = strdup(new_name);
+ if (!nn)
+ return -ENOMEM;
+
+ if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0)
+ return -errno;
+
+ free(i->path);
+ i->path = new_path;
+ new_path = NULL;
+
+ free(i->name);
+ i->name = nn;
+ nn = NULL;
+
+ return 0;
+}
+
+int image_clone(Image *i, const char *new_name, bool read_only) {
+ const char *new_path;
+ int r;
+
+ assert(i);
+
+ if (!image_name_is_valid(new_name))
+ return -EINVAL;
+
+ r = image_find(new_name, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return -EEXIST;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+ case IMAGE_DIRECTORY:
+ new_path = strappenda("/var/lib/container/", new_name);
+
+ r = btrfs_subvol_snapshot(i->path, new_path, read_only, true);
+ break;
+
+ case IMAGE_GPT:
+ new_path = strappenda("/var/lib/container/", new_name, ".gpt");
+
+ r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false);
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int image_read_only(Image *i, bool b) {
+ int r;
+ assert(i);
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+ r = btrfs_subvol_set_read_only(i->path, b);
+ if (r < 0)
+ return r;
+ break;
+
+ case IMAGE_GPT: {
+ struct stat st;
+
+ if (stat(i->path, &st) < 0)
+ return -errno;
+
+ if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0)
+ return -errno;
+ break;
+ }
+
+ case IMAGE_DIRECTORY:
+ default:
+ return -ENOTSUP;
+ }
+
+ return 0;
}
static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
diff --git a/src/shared/machine-image.h b/src/shared/machine-image.h
index e17e32f4a5..9e0f6aedba 100644
--- a/src/shared/machine-image.h
+++ b/src/shared/machine-image.h
@@ -52,6 +52,9 @@ int image_find(const char *name, Image **ret);
int image_discover(Hashmap *map);
int image_remove(Image *i);
+int image_rename(Image *i, const char *new_name);
+int image_clone(Image *i, const char *new_name, bool read_only);
+int image_read_only(Image *i, bool b);
const char* image_type_to_string(ImageType t) _const_;
ImageType image_type_from_string(const char *s) _pure_;
diff --git a/src/shared/missing.h b/src/shared/missing.h
index dd7bef4d9d..08cf83647d 100644
--- a/src/shared/missing.h
+++ b/src/shared/missing.h
@@ -657,3 +657,13 @@ static inline int raw_clone(unsigned long flags, void *child_stack) {
static inline pid_t raw_getpid(void) {
return (pid_t) syscall(__NR_getpid);
}
+
+#if !HAVE_DECL_RENAMEAT2
+static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) {
+ return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags);
+}
+#endif
+
+#ifndef RENAME_NOREPLACE
+#define RENAME_NOREPLACE (1 << 0)
+#endif
diff --git a/src/shared/util.c b/src/shared/util.c
index d04d73872e..92b4d498fe 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -1152,7 +1152,7 @@ char *delete_chars(char *s, const char *bad) {
}
char *file_in_same_dir(const char *path, const char *filename) {
- char *e, *r;
+ char *e, *ret;
size_t k;
assert(path);
@@ -1165,17 +1165,17 @@ char *file_in_same_dir(const char *path, const char *filename) {
if (path_is_absolute(filename))
return strdup(filename);
- if (!(e = strrchr(path, '/')))
+ e = strrchr(path, '/');
+ if (!e)
return strdup(filename);
k = strlen(filename);
- if (!(r = new(char, e-path+1+k+1)))
+ ret = new(char, (e + 1 - path) + k + 1);
+ if (!ret)
return NULL;
- memcpy(r, path, e-path+1);
- memcpy(r+(e-path)+1, filename, k+1);
-
- return r;
+ memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
+ return ret;
}
int rmdir_parents(const char *path, const char *stop) {