summaryrefslogtreecommitdiff
path: root/src/libs/file/dirs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/file/dirs.c')
-rw-r--r--src/libs/file/dirs.c830
1 files changed, 830 insertions, 0 deletions
diff --git a/src/libs/file/dirs.c b/src/libs/file/dirs.c
new file mode 100644
index 0000000..39a44f5
--- /dev/null
+++ b/src/libs/file/dirs.c
@@ -0,0 +1,830 @@
+/*
+ * 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 of the License, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+// Contains code handling directories
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "port.h"
+#include "config.h"
+#include "filintrn.h"
+#include "libs/compiler.h"
+#include "libs/memlib.h"
+#include "libs/misc.h"
+#include "libs/log.h"
+
+#ifdef HAVE_DRIVE_LETTERS
+# include <ctype.h>
+ // For tolower()
+#endif /* HAVE_DRIVE_LETTERS */
+#ifdef WIN32
+# include <direct.h>
+ // For _getdcwd()
+#else
+# include <pwd.h>
+ // For getpwuid()
+#endif
+
+/* Try to find a suitable value for %APPDATA% if it isn't defined on
+ * Windows.
+ */
+#define APPDATA_FALLBACK
+
+
+static char *expandPathAbsolute (char *dest, size_t destLen, const char *src,
+ size_t *skipSrc, int what);
+static char *strrchr2(const char *start, int c, const char *end);
+
+
+int
+createDirectory(const char *dir, int mode)
+{
+ return MKDIR(dir, mode);
+}
+
+// make all components of the path if they don't exist already
+// returns 0 on success, -1 on failure.
+// on failure, some parts may still have been created.
+int
+mkdirhier (const char *path)
+{
+ char *buf; // buffer
+ char *ptr; // end of the string in buf
+ const char *pathstart; // start of a component of path
+ const char *pathend; // first char past the end of a component of path
+ size_t len;
+ struct stat statbuf;
+
+ len = strlen (path);
+ buf = HMalloc (len + 2); // one extra for possibly added '/'
+
+ ptr = buf;
+ pathstart = path;
+
+#ifdef HAVE_DRIVE_LETTERS
+ if (isDriveLetter(pathstart[0]) && pathstart[1] == ':')
+ {
+ // Driveletter + semicolon on Windows.
+ // Copy as is; don't try to create directories for it.
+ *(ptr++) = *(pathstart++);
+ *(ptr++) = *(pathstart++);
+
+ ptr[0] = '/';
+ ptr[1] = '\0';
+ if (stat (buf, &statbuf) == -1)
+ {
+ log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno));
+ goto err;
+ }
+ }
+ else
+#endif /* HAVE_DRIVE_LETTERS */
+#ifdef HAVE_UNC_PATHS
+ if (pathstart[0] == '\\' && pathstart[1] == '\\')
+ {
+ // Universal Naming Convention path. (\\server\share\...)
+ // Copy the server part as is; don't try to create directories for
+ // it, or stat it. Don't create a dir for the share either.
+ *(ptr++) = *(pathstart++);
+ *(ptr++) = *(pathstart++);
+
+ // Copy the server part
+ while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/')
+ *(ptr++) = *(pathstart++);
+
+ if (*pathstart == '\0')
+ {
+ log_add (log_Error, "Incomplete UNC path \"%s\"", pathstart);
+ goto err;
+ }
+
+ // Copy the path seperator.
+ *(ptr++) = *(pathstart++);
+
+ // Copy the share part
+ while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/')
+ *(ptr++) = *(pathstart++);
+
+ ptr[0] = '/';
+ ptr[1] = '\0';
+ if (stat (buf, &statbuf) == -1)
+ {
+ log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno));
+ goto err;
+ }
+ }
+#else
+ {
+ // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is
+ // defined.
+ }
+#endif /* HAVE_UNC_PATHS */
+
+ if (*pathstart == '/')
+ *(ptr++) = *(pathstart++);
+
+ if (*pathstart == '\0') {
+ // path exists completely, nothing more to do
+ goto success;
+ }
+
+ // walk through the path as long as the components exist
+ while (1)
+ {
+ pathend = strchr (pathstart, '/');
+ if (pathend == NULL)
+ pathend = path + len;
+ memcpy(ptr, pathstart, pathend - pathstart);
+ ptr += pathend - pathstart;
+ *ptr = '\0';
+
+ if (stat (buf, &statbuf) == -1)
+ {
+ if (errno == ENOENT)
+ break;
+#ifdef __SYMBIAN32__
+ // XXX: HACK: If we don't have access to a directory, we can
+ // still have access to the underlying entries. We don't
+ // actually know whether the entry is a directory, but I know of
+ // no way to find out. We just pretend that it is; if we were
+ // wrong, an error will occur when we try to do something with
+ // the directory. That /should/ not be a problem, as any such
+ // action should have its own error checking.
+ if (errno != EACCES)
+#endif
+ {
+ log_add (log_Error, "Can't stat \"%s\": %s", buf,
+ strerror (errno));
+ goto err;
+ }
+ }
+
+ if (*pathend == '\0')
+ goto success;
+
+ *ptr = '/';
+ ptr++;
+ pathstart = pathend + 1;
+ while (*pathstart == '/')
+ pathstart++;
+ // pathstart is the next non-slash character
+
+ if (*pathstart == '\0')
+ goto success;
+ }
+
+ // create all components left
+ while (1)
+ {
+ if (createDirectory (buf, 0777) == -1)
+ {
+ log_add (log_Error, "Error: Can't create %s: %s", buf,
+ strerror (errno));
+ goto err;
+ }
+
+ if (*pathend == '\0')
+ break;
+
+ *ptr = '/';
+ ptr++;
+ pathstart = pathend + 1;
+ while (*pathstart == '/')
+ pathstart++;
+ // pathstart is the next non-slash character
+
+ if (*pathstart == '\0')
+ break;
+
+ pathend = strchr (pathstart, '/');
+ if (pathend == NULL)
+ pathend = path + len;
+
+ memcpy (ptr, pathstart, pathend - pathstart);
+ ptr += pathend - pathstart;
+ *ptr = '\0';
+ }
+
+success:
+ HFree (buf);
+ return 0;
+
+err:
+ {
+ int savedErrno = errno;
+ HFree (buf);
+ errno = savedErrno;
+ }
+ return -1;
+}
+
+// Get the user's home dir
+// returns a pointer to a static buffer from either getenv() or getpwuid().
+const char *
+getHomeDir (void)
+{
+#ifdef WIN32
+ return getenv ("HOME");
+#else
+ const char *home;
+ struct passwd *pw;
+
+ home = getenv ("HOME");
+ if (home != NULL)
+ return home;
+
+ pw = getpwuid (getuid ());
+ if (pw == NULL)
+ return NULL;
+ // NB: pw points to a static buffer.
+
+ return pw->pw_dir;
+#endif
+}
+
+// Performs various types of string expansions on a path.
+// 'what' is an OR'd compination of the folowing flags, which
+// specify what type of exmansions will be performed.
+// EP_HOME - Expand '~' for home dirs.
+// EP_ABSOLUTE - Make relative paths absolute
+// EP_ENVVARS - Expand environment variables
+// EP_DOTS - Process ".." and "."
+// EP_SLASHES - Consider backslashes as path component separators.
+// They will be replaced by slashes.
+// EP_SINGLESEP - Replace multiple consecutive path seperators (which POSIX
+// considers equivalent to a single one) by a single one.
+// Additionally, there's EP_ALL, which indicates all of the above,
+// and EP_ALL_SYSTEM, which does the same as EP_ALL, with the exception
+// of EP_SLASHES, which will only be included if the operating system
+// accepts backslashes as path terminators.
+// Returns 0 on success.
+// Returns -1 on failure, setting errno.
+int
+expandPath (char *dest, size_t len, const char *src, int what)
+{
+ char *destptr, *destend;
+ char *buf = NULL;
+ char *bufptr, *bufend;
+ const char *srcend;
+
+#define CHECKLEN(bufname, n) \
+ if (bufname##ptr + (n) >= bufname##end) \
+ { \
+ errno = ENAMETOOLONG; \
+ goto err; \
+ } \
+ else \
+ (void) 0
+
+ destptr = dest;
+ destend = dest + len;
+
+ if (what & EP_ENVVARS)
+ {
+ buf = HMalloc (len);
+ bufptr = buf;
+ bufend = buf + len;
+ while (*src != '\0')
+ {
+ switch (*src)
+ {
+#ifdef WIN32
+ case '%':
+ {
+ /* Environment variable substitution in Windows */
+ const char *end; // end of env var name in src
+ const char *envVar;
+ char *envName;
+ size_t envNameLen, envVarLen;
+
+ src++;
+ end = strchr (src, '%');
+ if (end == NULL)
+ {
+ errno = EINVAL;
+ goto err;
+ }
+
+ envNameLen = end - src;
+ envName = HMalloc (envNameLen + 1);
+ memcpy (envName, src, envNameLen + 1);
+ envName[envNameLen] = '\0';
+ envVar = getenv (envName);
+ HFree (envName);
+
+ if (envVar == NULL)
+ {
+#ifdef APPDATA_FALLBACK
+ if (strncmp (src, "APPDATA", envNameLen) != 0)
+ {
+ // Substitute an empty string
+ src = end + 1;
+ break;
+ }
+
+ // fallback for when the APPDATA env var is not set
+ // Using SHGetFolderPath or SHGetSpecialFolderPath
+ // is problematic (not everywhere available).
+ log_add (log_Warning, "Warning: %%APPDATA%% is not set. "
+ "Falling back to \"%%USERPROFILE%%\\Application "
+ "Data\"");
+ envVar = getenv ("USERPROFILE");
+ if (envVar != NULL)
+ {
+#define APPDATA_STRING "\\Application Data"
+ envVarLen = strlen (envVar);
+ CHECKLEN (buf,
+ envVarLen + sizeof (APPDATA_STRING) - 1);
+ strcpy (bufptr, envVar);
+ bufptr += envVarLen;
+ strcpy (bufptr, APPDATA_STRING);
+ bufptr += sizeof (APPDATA_STRING) - 1;
+ src = end + 1;
+ break;
+ }
+
+ // fallback to "./userdata"
+#define APPDATA_FALLBACK_STRING ".\\userdata"
+ log_add (log_Warning,
+ "Warning: %%USERPROFILE%% is not set. "
+ "Falling back to \"%s\" for %%APPDATA%%",
+ APPDATA_FALLBACK_STRING);
+ CHECKLEN (buf, sizeof (APPDATA_FALLBACK_STRING) - 1);
+ strcpy (bufptr, APPDATA_FALLBACK_STRING);
+ bufptr += sizeof (APPDATA_FALLBACK_STRING) - 1;
+ src = end + 1;
+ break;
+
+#else /* !defined (APPDATA_FALLBACK) */
+ // Substitute an empty string
+ src = end + 1;
+ break;
+#endif /* APPDATA_FALLBACK */
+ }
+
+ envVarLen = strlen (envVar);
+ CHECKLEN (buf, envVarLen);
+ strcpy (bufptr, envVar);
+ bufptr += envVarLen;
+ src = end + 1;
+ break;
+ }
+#endif
+#ifndef WIN32
+ case '$':
+ {
+ const char *end;
+ char *envName;
+ size_t envNameLen;
+ const char *envVar;
+ size_t envVarLen;
+
+ src++;
+ if (*src == '{')
+ {
+ src++;
+ end = strchr(src, '}');
+ if (end == NULL)
+ {
+ errno = EINVAL;
+ goto err;
+ }
+ envNameLen = end - src;
+ end++; // Skip the '}'
+ }
+ else
+ {
+ end = src;
+ while ((*end >= 'A' && *end <= 'Z') ||
+ (*end >= 'a' && *end <= 'z') ||
+ (*end >= '0' && *end <= '9') ||
+ *end == '_')
+ end++;
+ envNameLen = end - src;
+ }
+
+ envName = HMalloc (envNameLen + 1);
+ memcpy (envName, src, envNameLen + 1);
+ envName[envNameLen] = '\0';
+ envVar = getenv (envName);
+ HFree (envName);
+
+ if (envVar != NULL)
+ {
+ envVarLen = strlen (envVar);
+ CHECKLEN (buf, envVarLen);
+ memcpy (bufptr, envVar, envVarLen);
+ bufptr += envVarLen;
+ }
+
+ src = end;
+ break;
+ }
+#endif
+ default:
+ CHECKLEN(buf, 1);
+ *(bufptr++) = *(src++);
+ break;
+ } // switch
+ } // while
+ *bufptr = '\0';
+ src = buf;
+ srcend = bufptr;
+ } // if (what & EP_ENVVARS)
+ else
+ srcend = src + strlen (src);
+
+ if (what & EP_HOME)
+ {
+ if (src[0] == '~')
+ {
+ const char *home;
+ size_t homelen;
+
+ if (src[1] != '/')
+ {
+ errno = EINVAL;
+ goto err;
+ }
+
+ home = getHomeDir ();
+ if (home == NULL)
+ {
+ errno = ENOENT;
+ goto err;
+ }
+ homelen = strlen (home);
+
+ if (what & EP_ABSOLUTE) {
+ size_t skip;
+ destptr = expandPathAbsolute (dest, destend - dest,
+ home, &skip, what);
+ if (destptr == NULL)
+ {
+ // errno is set
+ goto err;
+ }
+ home += skip;
+ what &= ~EP_ABSOLUTE;
+ // The part after the '~' should not be seen
+ // as absolute.
+ }
+
+ CHECKLEN (dest, homelen);
+ memcpy (destptr, home, homelen);
+ destptr += homelen;
+ src++; /* skip the ~ */
+ }
+ }
+
+ if (what & EP_ABSOLUTE)
+ {
+ size_t skip;
+ destptr = expandPathAbsolute (destptr, destend - destptr, src,
+ &skip, what);
+ if (destptr == NULL)
+ {
+ // errno is set
+ goto err;
+ }
+ src += skip;
+ }
+
+ CHECKLEN (dest, srcend - src);
+ memcpy (destptr, src, srcend - src + 1);
+ // The +1 is for the '\0'. It is already taken into account by
+ // CHECKLEN.
+
+ if (what & EP_SLASHES)
+ {
+ /* Replacing backslashes in path by slashes. */
+ destptr = dest;
+#ifdef HAVE_UNC_PATHS
+ {
+ // A UNC path should always start with two backslashes
+ // and have a backslash in between the server and share part.
+ size_t skip = skipUNCServerShare (destptr);
+ if (skip != 0)
+ {
+ char *slash = (char *) memchr (destptr + 2, '/', skip - 2);
+ if (slash)
+ *slash = '\\';
+ destptr += skip;
+ }
+ }
+#endif /* HAVE_UNC_PATHS */
+ while (*destptr != '\0')
+ {
+ if (*destptr == '\\')
+ *destptr = '/';
+ destptr++;
+ }
+ }
+
+ if (what & EP_DOTS) {
+ // At this point backslashes are already replaced by slashes if they
+ // are specified to be path seperators.
+ // Note that the path can only get smaller, so no size checks
+ // need to be done.
+ char *pathStart;
+ // Start of the first path component, after any
+ // leading slashes or drive letters.
+ char *startPart;
+ char *endPart;
+
+ pathStart = dest;
+#ifdef HAVE_DRIVE_LETTERS
+ if (isDriveLetter(pathStart[0]) && (pathStart[1] == ':'))
+ {
+ pathStart += 2;
+ }
+ else
+#endif /* HAVE_DRIVE_LETTERS */
+#ifdef HAVE_UNC_PATHS
+ {
+ // Test for a Universal Naming Convention path.
+ pathStart += skipUNCServerShare(pathStart);
+ }
+#else
+ {
+ // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is
+ // defined.
+ }
+#endif /* HAVE_UNC_PATHS */
+ if (pathStart[0] == '/')
+ pathStart++;
+
+ startPart = pathStart;
+ destptr = pathStart;
+ for (;;)
+ {
+ endPart = strchr(startPart, '/');
+ if (endPart == NULL)
+ endPart = startPart + strlen(startPart);
+
+ if (endPart - startPart == 1 && startPart[0] == '.')
+ {
+ // Found "." as path component. Ignore this component.
+ }
+ else if (endPart - startPart == 2 &&
+ startPart[0] == '.' && startPart[1] == '.')
+ {
+ // Found ".." as path component. Remove the previous
+ // component, and ignore this one.
+ char *lastSlash;
+ lastSlash = strrchr2(pathStart, '/', destptr - 1);
+ if (lastSlash == NULL)
+ {
+ if (destptr == pathStart)
+ {
+ // We ran out of path components to back out of.
+ errno = EINVAL;
+ goto err;
+ }
+ destptr = pathStart;
+ }
+ else
+ {
+ destptr = lastSlash;
+ if (*endPart == '/')
+ destptr++;
+ }
+ }
+ else
+ {
+ // A normal path component; copy it.
+ // Using memmove as source and destination may overlap.
+ memmove(destptr, startPart, endPart - startPart);
+ destptr += (endPart - startPart);
+ if (*endPart == '/')
+ {
+ *destptr = '/';
+ destptr++;
+ }
+ }
+ if (*endPart == '\0')
+ break;
+ startPart = endPart + 1;
+ }
+ *destptr = '\0';
+ }
+
+ if (what & EP_SINGLESEP)
+ {
+ char *srcptr;
+ srcptr = dest;
+ destptr = dest;
+ while (*srcptr != '\0')
+ {
+ char ch = *srcptr;
+ *(destptr++) = *(srcptr++);
+ if (ch == '/')
+ {
+ while (*srcptr == '/')
+ srcptr++;
+ }
+ }
+ *destptr = '\0';
+ }
+
+ HFree (buf);
+ return 0;
+
+err:
+ if (buf != NULL) {
+ int savedErrno = errno;
+ HFree (buf);
+ errno = savedErrno;
+ }
+ return -1;
+}
+
+#if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE)
+ // This code is only needed if we have a current working directory
+ // per drive.
+// letter is 0 based: 0 = A, 1 = B, ...
+static bool
+driveLetterExists(int letter)
+{
+ unsigned long drives;
+
+ drives = _getdrives ();
+
+ return ((drives >> letter) & 1) != 0;
+}
+#endif /* if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE) */
+
+// helper for expandPath, expanding an absolute path
+// returns a pointer to the end of the filled in part of dest.
+static char *
+expandPathAbsolute (char *dest, size_t destLen, const char *src,
+ size_t *skipSrc, int what)
+{
+ const char *orgSrc;
+
+ if (src[0] == '/' || ((what & EP_SLASHES) && src[0] == '\\'))
+ {
+ // Path is already absolute; nothing to do
+ *skipSrc = 0;
+ return dest;
+ }
+
+ orgSrc = src;
+#ifdef HAVE_DRIVE_LETTERS
+ if (isDriveLetter(src[0]) && (src[1] == ':'))
+ {
+ int letter;
+
+ if (src[2] == '/' || src[2] == '\\')
+ {
+ // Path is already absolute (of the form "d:/"); nothing to do
+ *skipSrc = 0;
+ return dest;
+ }
+
+ // Path is of the form "d:path", without a (back)slash after the
+ // semicolon.
+
+#ifdef REJECT_DRIVE_PATH_WITHOUT_SLASH
+ // We reject paths of the form "d:foo/bar".
+ errno = EINVAL;
+ return NULL;
+#elif defined(HAVE_CWD_PER_DRIVE)
+ // Paths of the form "d:foo/bar" are treated as "foo/bar" relative
+ // to the working directory of d:.
+ letter = tolower(src[0]) - 'a';
+
+ // _getdcwd() should only be called on drives that exist.
+ // This is weird though, because it means a race condition
+ // in between the existance check and the call to _getdcwd()
+ // cannot be avoided, unless a drive still exists for Windows
+ // when the physical drive is removed.
+ if (!driveLetterExists (letter))
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ // Get the working directory for a specific drive.
+ if (_getdcwd (letter + 1, dest, destLen) == NULL)
+ {
+ // errno is set
+ return NULL;
+ }
+
+ src += 2;
+#else /* if !defined(HAVE_CWD_PER_DRIVE) */
+ // We treat paths of the form "d:foo/bar" as "d:/foo/bar".
+ if (destLen < 3) {
+ errno = ERANGE;
+ return NULL;
+ }
+ dest[0] = src[0];
+ dest[1] = ':';
+ dest[2] = '/';
+ *skipSrc = 2;
+ dest += 3;
+ return dest;
+#endif /* HAVE_CWD_PER_DRIVE */
+ }
+ else
+#endif /* HAVE_DRIVE_LETTERS */
+ {
+ // Relative dir
+ if (getcwd (dest, destLen) == NULL)
+ {
+ // errno is set
+ return NULL;
+ }
+ }
+
+ {
+ size_t tempLen;
+ tempLen = strlen (dest);
+ if (tempLen == 0)
+ {
+ // getcwd() or _getdcwd() returned a 0-length string.
+ errno = ENOENT;
+ return NULL;
+ }
+ dest += tempLen;
+ destLen -= tempLen;
+ }
+ if (dest[-1] != '/'
+#ifdef BACKSLASH_IS_PATH_SEPARATOR
+ && dest[-1] != '\\'
+#endif /* BACKSLASH_IS_PATH_SEPARATOR */
+ )
+ {
+ // Need to add a slash.
+ // There's always space, as we overwrite the '\0' that getcwd()
+ // always returns.
+ dest[0] = '/';
+ dest++;
+ destLen--;
+ }
+
+ *skipSrc = (size_t) (src - orgSrc);
+ return dest;
+}
+
+// As strrchr, but starts searching from the indicated end of the string.
+static char *
+strrchr2(const char *start, int c, const char *end) {
+ for (;;) {
+ end--;
+ if (end < start)
+ return (char *) NULL;
+ if (*end == c)
+ return (char *) unconst(end);
+ }
+}
+
+#ifdef HAVE_UNC_PATHS
+// returns 0 if the path is not a valid UNC path.
+// Does not skip trailing slashes.
+size_t
+skipUNCServerShare(const char *inPath) {
+ const char *path = inPath;
+
+ // Skip the initial two backslashes.
+ if (path[0] != '\\' || path[1] != '\\')
+ return (size_t) 0;
+ path += 2;
+
+ // Skip the server part.
+ while (*path != '\\' && *path != '/') {
+ if (*path == '\0')
+ return (size_t) 0;
+ path++;
+ }
+
+ // Skip the seperator.
+ path++;
+
+ // Skip the share part.
+ while (*path != '\0' && *path != '\\' && *path != '/')
+ path++;
+
+ return (size_t) (path - inPath);
+}
+#endif /* HAVE_UNC_PATHS */
+
+