]> arthur.barton.de Git - netatalk.git/commitdiff
remove dir locking, therfor convert catsearch to use paths instead of struct dir...
authorFrank Lahm <franklahm@googlemail.com>
Tue, 12 Apr 2011 09:28:05 +0000 (11:28 +0200)
committerFrank Lahm <franklahm@googlemail.com>
Tue, 12 Apr 2011 09:28:05 +0000 (11:28 +0200)
bin/ad/ad_util.c
etc/afpd/catsearch.c
etc/afpd/dircache.c
etc/afpd/directory.c
etc/afpd/directory.h
include/atalk/util.h
libatalk/util/Makefile.am
libatalk/util/cnid.c [new file with mode: 0644]

index 5a9b0973c7a2173f81a74850a594a07f7facb085..8aafd667c61f947bd96f23fc2a995d02712281ed 100644 (file)
@@ -190,79 +190,6 @@ char *utompath(const struct volinfo *volinfo, const char *upath)
     return(m);
 }
 
-/*!
- * Build path relativ to volume root
- *
- * path might be:
- * (a) relative:
- *     "dir/subdir" with cwd: "/afp_volume/topdir"
- * (b) absolute:
- *     "/afp_volume/dir/subdir"
- *
- * @param path     (r) path relative to cwd() or absolute
- * @param volpath  (r) volume path that path is a subdir of (has been computed in volinfo funcs)
- *
- * @returns relative path in new bstring, caller must bdestroy it
- */
-static bstring rel_path_in_vol(const char *path, const char *volpath)
-{
-    EC_INIT;
-    int cwd = -1;
-    bstring fpath = NULL;
-    char *dname = NULL;
-    struct stat st;
-
-    if (path == NULL || volpath == NULL)
-        return NULL;
-
-    EC_NEG1_LOG(cwd = open(".", O_RDONLY));
-
-    EC_ZERO_LOGSTR(lstat(path, &st), "lstat(%s): %s", path, strerror(errno));
-    switch (S_IFMT & st.st_mode) {
-
-    case S_IFREG:
-    case S_IFLNK:
-        EC_NULL_LOG(dname = strdup(path));
-        EC_ZERO_LOGSTR(chdir(dirname(dname)), "chdir(%s): %s", dirname, strerror(errno));
-        free(dname);
-        dname = NULL;
-        EC_NULL(fpath = bfromcstr(getcwdpath()));
-        BSTRING_STRIP_SLASH(fpath);
-        EC_ZERO(bcatcstr(fpath, "/"));
-        EC_NULL_LOG(dname = strdup(path));
-        EC_ZERO(bcatcstr(fpath, basename(dname)));
-        break;
-
-    case S_IFDIR:
-        EC_ZERO_LOGSTR(chdir(path), "chdir(%s): %s", path, strerror(errno));
-        EC_NULL(fpath = bfromcstr(getcwdpath()));
-        break;
-
-    default:
-        SLOG("special: %s", path);
-        EC_FAIL;
-    }
-
-    BSTRING_STRIP_SLASH(fpath);
-
-    /*
-     * Now we have eg:
-     *   fpath:   /Volume/netatalk/dir/bla
-     *   volpath: /Volume/netatalk/
-     * we want: "dir/bla"
-     */
-    EC_ZERO(bdelete(fpath, 0, strlen(volpath)));
-
-EC_CLEANUP:
-    if (dname) free(dname);
-    if (cwd != -1) {
-        fchdir(cwd);
-        close(cwd);
-    }
-    if (ret != 0)
-        return NULL;
-    return fpath;
-}
 
 /*!
  * Convert dot encoding of basename _in place_
index 5404118c753d33ac0ba1d096ba9b54ce7c12c29f..6a9161e2e8720743ca3cbbf4100b0980de9d34b0 100644 (file)
@@ -108,7 +108,8 @@ struct scrit {
  *
  */
 struct dsitem {
-       struct dir *dir; /* Structure describing this directory */
+//     struct dir *dir; /* Structure describing this directory */
+//  cnid_t did;      /* CNID of this directory           */
        int pidx;        /* Parent's dsitem structure index. */
        int checked;     /* Have we checked this directory ? */
        int path_len;
@@ -141,8 +142,7 @@ static int addstack(char *uname, struct dir *dir, int pidx)
 
        /* Put new element. Allocate and copy lname and path. */
        ds = dstack + dsidx++;
-       ds->dir = dir;
-    dir->d_flags |= DIRF_CACHELOCK;
+//     ds->did = dir->d_did;
        ds->pidx = pidx;
        ds->checked = 0;
        if (pidx >= 0) {
@@ -175,7 +175,6 @@ static int reducestack(void)
        while (dsidx > 0) {
                if (dstack[dsidx-1].checked) {
                        dsidx--;
-            dstack[dsidx].dir->d_flags &= ~DIRF_CACHELOCK;
                        free(dstack[dsidx].path);
                } else
                        return dsidx - 1;
@@ -189,7 +188,6 @@ static void clearstack(void)
        save_cidx = -1;
        while (dsidx > 0) {
                dsidx--;
-        dstack[dsidx].dir->d_flags &= ~DIRF_CACHELOCK;
                free(dstack[dsidx].path);
        }
 } 
@@ -506,6 +504,7 @@ static int catsearch(struct vol *vol,
 {
     static u_int32_t cur_pos;    /* Saved position index (ID) - used to remember "position" across FPCatSearch calls */
     static DIR *dirpos;                 /* UNIX structure describing currently opened directory. */
+    struct dir *curdir;          /* struct dir of current directory */
        int cidx, r;
        struct dirent *entry;
        int result = AFP_OK;
@@ -576,8 +575,13 @@ static int catsearch(struct vol *vol,
                        } /* switch (errno) */
                        goto catsearch_end;
                }
+
+        if ((curdir = dirlookup_bypath(vol, dstack[cidx].path)) == NULL) {
+            result = AFPERR_MISC;
+            goto catsearch_end;
+        }
                
-               while ((entry=readdir(dirpos)) != NULL) {
+               while ((entry = readdir(dirpos)) != NULL) {
                        (*pos)++;
 
                        if (!check_dirent(vol, entry->d_name))
@@ -606,10 +610,17 @@ static int catsearch(struct vol *vol,
                                   ALL dirsearch_byname will fail.
                                */
                 int unlen = strlen(path.u_name);
-                path.d_dir = dircache_search_by_name(vol, dstack[cidx].dir, path.u_name, unlen, path.st.st_ctime);
+                path.d_dir = dircache_search_by_name(vol,
+                                                     curdir,
+                                                     path.u_name,
+                                                     unlen,
+                                                     path.st.st_ctime);
                if (path.d_dir == NULL) {
                        /* path.m_name is set by adddir */
-                   if (NULL == (path.d_dir = dir_add( vol, dstack[cidx].dir, &path, unlen) ) ) {
+                   if ((path.d_dir = dir_add(vol,
+                                              curdir,
+                                              &path,
+                                              unlen)) == NULL) {
                                                result = AFPERR_MISC;
                                                goto catsearch_end;
                                        }
@@ -620,11 +631,10 @@ static int catsearch(struct vol *vol,
                                        result = AFPERR_MISC;
                                        goto catsearch_end;
                                } 
+            } else {
+               path.d_dir = curdir;
             }
-            else {
-               /* yes it sucks for directory d_dir is the directory, for file it's the parent directory*/
-               path.d_dir = dstack[cidx].dir;
-            }
+
                        ccr = crit_check(vol, &path);
 
                        /* bit 0 means that criteria has been met */
index 8a03de7e91687496f88549105543e5b626f1aa48..9907c5dd35bddabc02bbcef02d94dc07503d2975 100644 (file)
@@ -96,8 +96,6 @@
  * We have/need two indexes:
  * - a DID/name index on the main dircache, another hashtable
  * - a queue index on the dircache, for evicting the oldest entries
- * The cache supports locking of struct dir elements through the DIRF_CACHELOCK flag. A dir
- * locked this way wont ever be removed from the cache, so be careful.
  *
  * Debugging
  * =========
@@ -241,7 +239,7 @@ static unsigned long queue_count;
  * The default is to remove the 256 oldest entries from the cache.
  * 1. Get the oldest entry
  * 2. If it's in use ie open forks reference it or it's curdir requeue it,
- *    or it's locked (from catsearch) dont remove it
+ *    dont remove it
  * 3. Remove the dir from the main cache and the didname index
  * 4. Free the struct dir structure and all its members
  */
@@ -259,8 +257,7 @@ static void dircache_evict(void)
         }
         queue_count--;
 
-        if (curdir == dir
-            || (dir->d_flags & DIRF_CACHELOCK)) {     /* 2 */
+        if (curdir == dir) {                          /* 2 */
             if ((dir->qidx_node = enqueue(index_queue, dir)) == NULL) {
                 dircache_dump();
                 AFP_PANIC("dircache_evict");
@@ -504,11 +501,8 @@ void dircache_remove(const struct vol *vol _U_, struct dir *dir, int flags)
 {
     hnode_t *hn;
 
-   AFP_ASSERT(dir);
-   AFP_ASSERT((flags & ~(QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE)) == 0);
-
-    if (dir->d_flags & DIRF_CACHELOCK)
-        return;
+    AFP_ASSERT(dir);
+    AFP_ASSERT((flags & ~(QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE)) == 0);
 
     if (flags & QUEUE_INDEX) {
         /* remove it from the queue index */
@@ -644,13 +638,12 @@ void dircache_dump(void)
     i = 1;
     while ((hn = hash_scan_next(&hs))) {
         dir = hnode_get(hn);
-        fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
+        fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
                 i++,
                 ntohs(dir->d_vid),
                 ntohl(dir->d_pdid),
                 ntohl(dir->d_did),
                 dir->d_fullpath ? "d" : "f",
-                (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
                 cfrombstr(dir->d_u_name));
     }
 
@@ -661,13 +654,12 @@ void dircache_dump(void)
     i = 1;
     while ((hn = hash_scan_next(&hs))) {
         dir = hnode_get(hn);
-        fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
+        fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
                 i++,
                 ntohs(dir->d_vid),
                 ntohl(dir->d_pdid),
                 ntohl(dir->d_did),
                 dir->d_fullpath ? "d" : "f",
-                (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
                 cfrombstr(dir->d_u_name));
     }
 
@@ -679,13 +671,12 @@ void dircache_dump(void)
         if (n == index_queue)
             break;
         dir = (struct dir *)n->data;
-        fprintf(dump, "%05u: %3u  %6u  %6u  %s%s  %s\n",
+        fprintf(dump, "%05u: %3u  %6u  %6u %s    %s\n",
                 i,
                 ntohs(dir->d_vid),
                 ntohl(dir->d_pdid),
                 ntohl(dir->d_did),
                 dir->d_fullpath ? "d" : "f",
-                (dir->d_flags & DIRF_CACHELOCK) ? "l" : "-",
                 cfrombstr(dir->d_u_name));
         n = n->next;
     }
index 3b0e29482017a28b0ea740de4d72f0de9162d792..031bf2a7b9aa3c3407e9eff2ac16f4cafbcc5fbe 100644 (file)
@@ -28,6 +28,7 @@
 #include <atalk/unix.h>
 #include <atalk/bstrlib.h>
 #include <atalk/bstradd.h>
+#include <atalk/errchk.h>
 
 #include "directory.h"
 #include "dircache.h"
@@ -429,6 +430,84 @@ int get_afp_errno(const int param)
     return param;
 }
 
+/*!
+ * Resolve struct dir for an absolute path
+ *
+ * Given a path like "/Volumes/volume/dir/subdir" in a volume "/Volumes/volume" return
+ * a pointer to struct dir of "subdir".
+ * 1. Remove volue path from absolute path
+ * 2. start path
+ * 3. loop through all elements of the remaining path from 1.
+ * 4. we only allow dirs
+ * 5. search dircache
+ * 6. if not found in the dircache query the CNID database for the DID
+ * 7. and use dirlookup to resolve the DID to a it's struct dir *
+ *
+ * @param vol   (r) volume the path is in, must be known
+ * @param path  (r) absoule path
+ *
+ * @returns pointer to struct dir or NULL on error
+ */
+struct dir *dirlookup_bypath(const struct vol *vol, const char *path)
+{
+    EC_INIT;
+
+    struct dir *dir = NULL;
+    cnid_t cnid, did;
+    bstring rpath = NULL;
+    bstring statpath = NULL;
+    struct bstrList *l = NULL;
+    struct stat st;
+
+    cnid = htonl(2);
+    dir = vol->v_root;
+
+    EC_NULL(rpath = rel_path_in_vol(path, vol->v_path)); /* 1. */
+    EC_NULL(statpath = bfromcstr(vol->v_path));          /* 2. */
+
+    l = bsplit(rpath, '/');
+    for (int i = 0; i < l->qty ; i++) {                  /* 3. */
+        did = cnid;
+        EC_ZERO(bconcat(statpath, l->entry[i]));
+        EC_ZERO_LOGSTR(lstat(cfrombstr(statpath), &st),
+                       "lstat(rpath: %s, elem: %s): %s: %s",
+                       cfrombstr(rpath), cfrombstr(l->entry[i]),
+                       cfrombstr(statpath), strerror(errno));
+
+        if (!(S_ISDIR(st.st_mode)))                      /* 4. */
+            EC_FAIL;
+
+        if ((dir = dircache_search_by_name(vol,          /* 5. */
+                                           dir,
+                                           cfrombstr(l->entry[i]),
+                                           blength(l->entry[i]),
+                                           st.st_ctime)) == NULL) {
+            if ((cnid = cnid_add(vol->v_cdb,             /* 6. */
+                                 &st,
+                                 did,
+                                 cfrombstr(l->entry[i]),
+                                 blength(l->entry[i]),
+                                 0)) == CNID_INVALID) {
+                EC_FAIL;
+            }
+
+            if ((dir = dirlookup(vol, cnid)) == NULL) /* 7. */
+                EC_FAIL;
+        }
+
+        EC_ZERO(bcatcstr(statpath, "/"));
+    }
+
+EC_CLEANUP:
+    bdestroy(rpath);
+    bstrListDestroy(l);
+    bdestroy(statpath);
+    if (ret != 0)
+        return NULL;
+
+    return dir;
+}
+
 /*!
  * @brief Resolve a DID
  *
@@ -446,9 +525,6 @@ int get_afp_errno(const int param)
  * @param did   (r) DID to resolve
  *
  * @returns pointer to struct dir
- *
- * @note FIXME: OSX calls it with bogus id, ie file ID not folder ID,
- *       and we are really bad in this case.
  */
 struct dir *dirlookup(const struct vol *vol, cnid_t did)
 {
@@ -514,8 +590,12 @@ struct dir *dirlookup(const struct vol *vol, cnid_t did)
 
     /* Get it from the database */
     cnid = did;
-    if ( (upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL 
-         || (upath = strdup(upath)) == NULL) { /* 3 */
+    if ((upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL) {
+        afp_errno = AFPERR_NOOBJ;
+        err = 1;
+        goto exit;
+    }
+    if ((upath = strdup(upath)) == NULL) { /* 3 */
         afp_errno = AFPERR_NOOBJ;
         err = 1;
         goto exit;
@@ -527,6 +607,7 @@ struct dir *dirlookup(const struct vol *vol, cnid_t did)
      * - DIRDID_ROOT is hit
      * - a cached entry is found
      */
+    LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {recursion for did: %u}", ntohl(pdid));
     if ((pdir = dirlookup(vol, pdid)) == NULL) {
         err = 1;
         goto exit;
@@ -826,6 +907,7 @@ struct dir *dir_add(struct vol *vol, const struct dir *dir, struct path *path, i
     /* Get macname from unixname */
     if (path->m_name == NULL) {
         if ((path->m_name = utompath(vol, path->u_name, id, utf8_encoding())) == NULL) {
+            LOG(log_error, logtype_afpd, "dir_add(\"%s\"): can't assign macname", path->u_name);
             err = 2;
             goto exit;
         }
@@ -905,12 +987,6 @@ int dir_remove(const struct vol *vol, struct dir *dir)
     if (dir->d_did == DIRDID_ROOT_PARENT || dir->d_did == DIRDID_ROOT)
         return 0;
 
-    if (dir->d_flags & DIRF_CACHELOCK) { /* 1 */
-        LOG(log_warning, logtype_afpd, "dir_remove(did:%u,'%s'): dir is locked",
-            ntohl(dir->d_did), cfrombstr(dir->d_u_name));
-        return 0;
-    }
-
     LOG(log_debug, logtype_afpd, "dir_remove(did:%u,'%s'): {removing}",
         ntohl(dir->d_did), cfrombstr(dir->d_u_name));
 
@@ -1271,8 +1347,6 @@ int movecwd(const struct vol *vol, struct dir *dir)
     LOG(log_maxdebug, logtype_afpd, "movecwd: from: curdir:\"%s\", cwd:\"%s\"",
         cfrombstr(curdir->d_fullpath), getcwdpath());
 
-    if ( dir == curdir)
-        return( 0 );
     if (dir->d_did == DIRDID_ROOT_PARENT) {
         curdir = &rootParent;
         return 0;
index 76267e73b9dac151a088c175af46d8348468dae3..79c4a0407fe3b6c1d61bb3d2208e1cc22f319d9f 100644 (file)
@@ -48,7 +48,6 @@
 
 #define DIRF_OFFCNT    (1<<4) /* offsprings count is valid */
 #define DIRF_CNID         (1<<5) /* renumerate id */
-#define DIRF_CACHELOCK (1<<6) /* lock in cache, don't remove in dircache_eviction, for catsearch */
 
 #define AFPDIR_READ    (1<<0)
 
@@ -117,6 +116,8 @@ extern int         dir_modify(const struct vol *vol, struct dir *dir, cnid_t pdi
                               const char *new_mname, const char *new_uname, bstring pdir_fullpath);
 extern int         dir_remove(const struct vol *vol, struct dir *dir);
 extern struct dir  *dirlookup (const struct vol *, cnid_t);
+extern struct dir *dirlookup_bypath(const struct vol *vol, const char *path);
+
 extern int         movecwd (const struct vol *, struct dir *);
 extern struct path *cname (struct vol *, struct dir *, char **);
 
index 457b0c8a97a9dda25f01750705a06b6f50c417b1..999f14daac86349e508be888df10e0a40a987123 100644 (file)
@@ -18,7 +18,9 @@
 #endif /* HAVE_UNISTD_H */
 #include <poll.h>
 #include <netatalk/at.h>
+
 #include <atalk/unicode.h>
+#include <atalk/bstrlib.h>
 
 /* exit error codes */
 #define EXITERR_CLNT 1  /* client related error */
@@ -167,3 +169,9 @@ extern char *stripped_slashes_basename(char *p);
 extern int lchdir(const char *dir);
 extern void randombytes(void *buf, int n);
 #endif  /* _ATALK_UTIL_H */
+
+/******************************************************************
+ * cnid.c
+ *****************************************************************/
+
+extern bstring rel_path_in_vol(const char *path, const char *volpath);
index 4869bfb45ee0b78cd0694ff84bd55f513d3707e0..af5d3c9ff6b10515d0aaebc26b6350ea3531c022 100644 (file)
@@ -7,6 +7,7 @@ AM_CFLAGS = -I$(top_srcdir)/sys
 libutil_la_SOURCES = \
        atalk_addr.c    \
        bprint.c        \
+       cnid.c          \
        fault.c         \
        getiface.c      \
        locking.c   \
diff --git a/libatalk/util/cnid.c b/libatalk/util/cnid.c
new file mode 100644 (file)
index 0000000..8870224
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+#include <libgen.h>
+
+#include <atalk/util.h>
+#include <atalk/cnid.h>
+#include <atalk/volinfo.h>
+#include <atalk/bstrlib.h>
+#include <atalk/bstradd.h>
+#include <atalk/logger.h>
+#include <atalk/errchk.h>
+#include <atalk/unicode.h>
+
+/*!
+ * Build path relativ to volume root
+ *
+ * path might be:
+ * (a) relative:
+ *     "dir/subdir" with cwd: "/afp_volume/topdir"
+ * (b) absolute:
+ *     "/afp_volume/dir/subdir"
+ *
+ * @param path     (r) path relative to cwd() or absolute
+ * @param volpath  (r) volume path that path is a subdir of (has been computed in volinfo funcs)
+ *
+ * @returns relative path in new bstring, caller must bdestroy it
+ */
+bstring rel_path_in_vol(const char *path, const char *volpath)
+{
+    EC_INIT;
+    int cwd = -1;
+    bstring fpath = NULL;
+    char *dname = NULL;
+    struct stat st;
+
+    if (path == NULL || volpath == NULL)
+        return NULL;
+
+    EC_NEG1_LOG(cwd = open(".", O_RDONLY));
+
+    EC_ZERO_LOGSTR(lstat(path, &st), "lstat(%s): %s", path, strerror(errno));
+
+    if (path[0] == '/') {
+        EC_NULL(fpath = bfromcstr(path));
+    } else {
+        switch (S_IFMT & st.st_mode) {
+        case S_IFREG:
+        case S_IFLNK:
+            EC_NULL_LOG(dname = strdup(path));
+            EC_ZERO_LOGSTR(chdir(dirname(dname)), "chdir(%s): %s", dirname, strerror(errno));
+            free(dname);
+            dname = NULL;
+            EC_NULL(fpath = bfromcstr(getcwdpath()));
+            BSTRING_STRIP_SLASH(fpath);
+            EC_ZERO(bcatcstr(fpath, "/"));
+            EC_NULL_LOG(dname = strdup(path));
+            EC_ZERO(bcatcstr(fpath, basename(dname)));
+            break;
+
+        case S_IFDIR:
+            EC_ZERO_LOGSTR(chdir(path), "chdir(%s): %s", path, strerror(errno));
+            EC_NULL(fpath = bfromcstr(getcwdpath()));
+            break;
+
+        default:
+            EC_FAIL;
+        }
+    }
+
+    BSTRING_STRIP_SLASH(fpath);
+
+    /*
+     * Now we have eg:
+     *   fpath:   /Volume/netatalk/dir/bla
+     *   volpath: /Volume/netatalk/
+     * we want: "dir/bla"
+     */
+    EC_ZERO(bdelete(fpath, 0, strlen(volpath)));
+
+EC_CLEANUP:
+    if (dname) free(dname);
+    if (cwd != -1) {
+        fchdir(cwd);
+        close(cwd);
+    }
+    if (ret != 0)
+        return NULL;
+    return fpath;
+}