summaryrefslogtreecommitdiff
path: root/lib/mkancesdirs.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2006-07-17 03:05:23 +0000
committerPaul Eggert <eggert@cs.ucla.edu>2006-07-17 03:05:23 +0000
commitf25ddb12c65aae1d74ca1fc5c2a3a9d84a119759 (patch)
tree384d89e7b155f14d0fee0f69a5aa15828ec25fdf /lib/mkancesdirs.c
parent999eeed90f8e614cad8f52657d5fc4b3a611213e (diff)
downloadcoreutils-f25ddb12c65aae1d74ca1fc5c2a3a9d84a119759.tar.xz
Initial version.
Diffstat (limited to 'lib/mkancesdirs.c')
-rw-r--r--lib/mkancesdirs.c132
1 files changed, 132 insertions, 0 deletions
diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c
new file mode 100644
index 000000000..473774236
--- /dev/null
+++ b/lib/mkancesdirs.c
@@ -0,0 +1,132 @@
+/* Make a file's ancestor directories.
+
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+ 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
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Written by Paul Eggert. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "mkancesdirs.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "dirname.h"
+#include "stat-macros.h"
+
+/* Return 0 if FILE is a directory, otherwise -1 (setting errno). */
+
+static int
+test_dir (char const *file)
+{
+ struct stat st;
+ if (stat (file, &st) == 0)
+ {
+ if (S_ISDIR (st.st_mode))
+ return 0;
+ errno = ENOTDIR;
+ }
+ return -1;
+}
+
+/* Ensure that the ancestor directories of FILE exist, using an
+ algorithm that should work even if two processes execute this
+ function in parallel. Temporarily modify FILE by storing '\0'
+ bytes into it, to access the ancestor directories.
+
+ Create any ancestor directories that don't already exist, by
+ invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG). This function should
+ return zero if successful, -1 (setting errno) otherwise.
+
+ If successful, return 0 with FILE set back to its original value;
+ otherwise, return -1 (setting errno), storing a '\0' into *FILE so
+ that it names the ancestor directory that had problems. */
+
+int
+mkancesdirs (char *file,
+ int (*make_dir) (char const *, void *),
+ void *make_dir_arg)
+{
+ /* This algorithm is O(N**2) but in typical practice the fancier
+ O(N) algorithms are slower. */
+
+ /* Address of the previous directory separator that follows an
+ ordinary byte in a file name in the left-to-right scan, or NULL
+ if no such separator precedes the current location P. */
+ char *sep = NULL;
+
+ char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file);
+ char *p;
+ char c;
+
+ /* Search backward through FILE using mkdir to create the
+ furthest-away ancestor that is needed. This loop isn't needed
+ for correctness, but typically ancestors already exist so this
+ loop speeds things up a bit.
+
+ This loop runs a bit faster if errno initially contains an error
+ number corresponding to a failed access to FILE. However, things
+ work correctly regardless of errno's initial value. */
+
+ for (p = last_component (file); prefix_end < p; p--)
+ if (ISSLASH (*p) && ! ISSLASH (p[-1]))
+ {
+ *p = '\0';
+
+ if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
+ {
+ *p = '/';
+ break;
+ }
+
+ if (errno != ENOENT)
+ {
+ if (test_dir (file) == 0)
+ {
+ *p = '/';
+ break;
+ }
+ if (errno != ENOENT)
+ return -1;
+ }
+
+ *p = '/';
+ }
+
+ /* Scan forward through FILE, creating directories along the way.
+ Try mkdir before stat, so that the procedure works even when two
+ or more processes are executing it in parallel. */
+
+ while ((c = *p++))
+ if (ISSLASH (*p))
+ {
+ if (! ISSLASH (c))
+ sep = p;
+ }
+ else if (ISSLASH (c) && *p && sep)
+ {
+ *sep = '\0';
+ if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
+ return -1;
+ *sep = '/';
+ }
+
+
+ return 0;
+}