When dry running, mbsync should not be altering the filesystem by
creating directories.  However, mkdir() and mkdir_p() were being called
on both dry and non-dry runs, creating directories in both situations.

Ensure mkdir_p() instead just calls mkdir_p_check() on dry runs.
mkdir_p_check() attempts to determine whether mkdir_p() would succeed or
fail and returns appropriately, thereby simulating a call to mkdir_p()
without modifying the filesystem.

Where appropriate, reroute any calls to mkdir() to mkdir_p() so that the
simulated directory creation is also used at those calls.  With
'appropriate' meaning the parent directory of the wanted new directory
is expected to exist prior to the call.
---
 src/drv_maildir.c |   2 +-
 src/util.c        | 102 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/src/drv_maildir.c b/src/drv_maildir.c
index fe0e95140703..7ab3acb3e01f 100644
--- a/src/drv_maildir.c
+++ b/src/drv_maildir.c
@@ -614,7 +614,7 @@ maildir_validate( const char *box, int create, 
maildir_store_t *ctx )
                        /* We always create new/ and tmp/ if they are missing. 
cur/ is the presence indicator. */
                        if (!i && !create)
                                return DRV_BOX_BAD;
-                       if (mkdir( buf, 0700 )) {
+                       if (mkdir_p( buf, bl + 3 )) {
                                sys_error( "Maildir error: cannot create 
directory %s", buf );
                                return DRV_BOX_BAD;
                        }
diff --git a/src/util.c b/src/util.c
index 4d17cae5f5d8..94cd9f88e323 100644
--- a/src/util.c
+++ b/src/util.c
@@ -796,9 +796,111 @@ map_name( const char *arg, int l, char **result, uint 
reserve, const char *in, c
        return 0;
 }
 
+/* See open_last_valid_dir(). */
+static int
+do_open_last_valid_dir( char *path, size_t len )
+{
+       assert( path );
+
+#ifdef __linux__
+       int oflags = O_RDONLY | O_DIRECTORY | O_PATH;
+#else
+       int oflags = O_SEARCH | O_DIRECTORY;
+#endif
+
+       int fd = open( path, oflags );
+       if (fd >= 0)
+               return fd;
+       if (errno != ENOENT)
+               return -1;
+
+       char *last_slash = memrchr( path, '/', len );
+       if (!last_slash)
+               return -1; // open() set errno
+
+       *last_slash = '\0';
+       return do_open_last_valid_dir( path, last_slash - path );
+}
+
+/*
+ * Finds the last component in 'path' that locates an existent file, if any, 
and
+ * attempts to open it as a directory with search permissions.
+ *
+ * For example, suppose 'path' is "/A/B/C", /A and /A/B are searchable
+ * directories, and /A/B/C does not exist.
+ * Then on success, the returned value will be an open file descriptor for 
/A/B.
+ *
+ * If 'path' is NUL-terminated, 'len' may be zero.
+ * Otherwise, 'len' must be positive.
+ *
+ * On success, returns the resultant file descriptor.
+ * Otherwise, returns -1 and preserves the relevant errno value.
+ */
+static int
+open_last_valid_dir( const char *path, size_t len )
+{
+       assert( path );
+
+       // Make sure to also check cwd if path is relative
+       const char *prefix = (path[0] != '/' && (path[0] != '.' || path[1] != 
'/'))
+                       ? "./" : "";
+       size_t prefix_len = strlen( prefix );
+       size_t path_len = len ? len : strlen( path );
+       size_t full_len = prefix_len + path_len;
+
+       // nf*alloc() is overkill here
+       char *full_path = malloc( full_len + 1 );
+       if (!full_path)
+               return -1;
+
+       memcpy( full_path, prefix, prefix_len );
+       memcpy( full_path + prefix_len, path, path_len );
+       full_path[full_len] = '\0';
+
+       int fd = do_open_last_valid_dir( full_path, full_len );
+       free( full_path );
+       return fd;
+}
+
+/*
+ * Checks whether an attempt to recursively create the directory components of
+ * 'path' would be successful, considering the existent components' 
permissions.
+ *
+ * If 'path' is NUL-terminated, 'len' may be zero.
+ * Otherwise, 'len' must be positive.
+ *
+ * On success, returns 0.
+ * Otherwise, returns -1 and preserves the relevant errno value.
+ *
+ * Note that a successful call to this function does NOT guarantee a later call
+ * to mkdir_p() will be successful.
+ * Rather, success indicates that if mkdir_p() was called instead of
+ * mkdir_p_check(), then mkdir_p() would have succeeded.
+ */
+static int
+mkdir_p_check( const char *path, size_t len )
+{
+       assert( path );
+
+       int dir = open_last_valid_dir( path, len );
+       if (dir < 0)
+               return -1;
+
+       // open()ing dir guarantees search permissions on all path components.
+       // So we only need to check for write permission on dir itself.
+       int ret = faccessat( dir, ".", W_OK, AT_EACCESS );
+       int errno_bak = errno;
+       close( dir );
+       errno = errno_bak;
+       return ret;
+}
+
 int
 mkdir_p( char *path, int len )
 {
+       if (DFlags & DRYRUN)
+               return mkdir_p_check( path, len );
+
        if (!mkdir( path, 0700 ) || errno == EEXIST)
                return 0;
        char *p = memrchr( path, '/', (size_t)len );
-- 
2.47.3



_______________________________________________
isync-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to