/*
- * Copyright (C) 2012 J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2012, 2014 J. Ali Harlow <ali@juiblex.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
+#include <unistd.h>
#include "razor-internal.h"
/*
free(atomic);
}
+#ifndef MSWIN_API
+static char *absolute_path(const char *path)
+{
+ int len;
+ char *result, *subpath, *p, *s, *t;
+
+ result = realpath(path, NULL);
+
+ if (!result && errno == ENOENT) {
+ p = strdup(path);
+ s = strrchr(p, '/');
+
+ while (s) {
+ if (s == p) {
+ result = strdup("/");
+ break;
+ }
+
+ *s = '\0';
+ subpath = realpath(p, NULL);
+
+ if (subpath) {
+ *s = '/';
+ len = strlen(subpath);
+ result = malloc(len + strlen(s) + 1);
+ memcpy(result, subpath, len);
+ strcpy(result + len, s);
+ break;
+ } else if (errno != ENOENT)
+ break;
+
+ t = strrchr(p, '/');
+ *s = '/';
+ s = t;
+ }
+
+ if (!s)
+ result = realpath(".", NULL);
+
+ free(p);
+ }
+
+ return result;
+}
+#endif
+
/*
* We need a toplevel directory in which to hold temporary files
* before they are committed. Since we can generally assume that
- * we have write permissions anywhere on the disk in question,
- * the best location is in the relevant root directory. The most
- * common case where this assumption fails is when testing, when
- * the current directory is a good choice.
- * It might be even better to find a mount point above path instead
- * but this is hard to do, and probably not worth the effort.
+ * we have write permissions anywhere on the filesystem in
+ * question, the best location is at the relevant mount point.
+ * The most common case where this assumption fails is when
+ * testing, when the current directory is a good choice.
*/
static int
razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic,
const char *path)
{
+#ifndef MSWIN_API
+ dev_t filesystem;
+ struct stat buf;
+#endif
+
if (razor_atomic_in_error_state(atomic))
return -1;
free(buf);
}
#else
- atomic->toplevel = strdup("/.atomic-XXXXXX");
+ {
+ /*
+ * Find the mount point (assuming we can write to the
+ * whole filesystem). Otherwise stop at the first
+ * unwritable directory and take one step back.
+ */
+ char *s, *abspath, saved;
+ int len;
+
+ abspath = absolute_path(path);
+ if (!abspath) {
+ atomic->error = razor_error_new_str(path,
+ strerror(errno));
+ return -1;
+ }
+
+ if (stat(abspath, &buf) < 0) {
+ atomic->error = razor_error_new_str(abspath,
+ strerror(errno));
+ free(abspath);
+ return -1;
+ }
+ filesystem = buf.st_dev;
+
+ len = strlen(abspath);
+ while(len > 1 && (s = strrchr(abspath, '/'))) {
+ if (s == abspath) {
+ saved = s[1];
+ s[1] = '\0';
+ len = s + 1 - abspath;
+ } else {
+ s[0] = '\0';
+ len = s - abspath;
+ }
+
+ if (stat(abspath, &buf) < 0) {
+ atomic->error =
+ razor_error_new_str(abspath, strerror(errno));
+ free(abspath);
+ return -1;
+ }
+
+ if (buf.st_dev != filesystem || access(abspath, W_OK)) {
+ if (s == abspath)
+ s[1] = saved;
+ else
+ s[0] = '/';
+ len = strlen(abspath);
+ break;
+ }
+ }
+
+ if (len == 1)
+ len = 0; /* Avoid an unslightly double slash. */
+ atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1);
+ memcpy(atomic->toplevel, abspath, len);
+ strcpy(atomic->toplevel + len, "/.atomic-XXXXXX");
+
+ free(abspath);
+ }
#endif
if (!mkdtemp(atomic->toplevel)) {
if (err == EACCES) {
char *s = strdup("atomic-XXXXXX");
+#ifndef MSWIN_API
+ if (stat(".", &buf) < 0) {
+ atomic->error =
+ razor_error_new_str(".", strerror(errno));
+ free(s);
+ free(atomic->toplevel);
+ atomic->toplevel = NULL;
+ return -1;
+ }
+ if (buf.st_dev != filesystem)
+ /*
+ * Don't use a different filesystem. It will
+ * only fail later on (in rename) and cause
+ * an unhelpful error message (EXDEV).
+ */
+ free(s);
+ else
+#endif
if (mkdtemp(s)) {
free(atomic->toplevel);
atomic->toplevel = s;