]> arthur.barton.de Git - netatalk.git/blobdiff - etc/afpd/directory.c
Merge master
[netatalk.git] / etc / afpd / directory.c
index db531cf78bee798f3813054e4e6c1da7654fa548..488f900a0a553e7ee278962e8a2f786087557d72 100644 (file)
@@ -1,6 +1,4 @@
 /*
- * $Id: directory.c,v 1.131.2.4 2010-02-01 16:13:52 franklahm Exp $
- *
  * Copyright (c) 1990,1993 Regents of The University of Michigan.
  * All Rights Reserved.  See COPYRIGHT.
  */
@@ -30,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"
 #include "mangle.h"
 #include "hash.h"
 
-#ifdef HAVE_NFSv4_ACLS
-extern void addir_inherit_acl(const struct vol *vol);
-#endif
-
 /*
  * FIXMEs, loose ends after the dircache rewrite:
- * o case-insensitivity is gone
- * o setdirparams doesn't change parent mdate anymore
- * o catsearch doesn't work, see FIXMEs in catsearch.c
- * o curdir per volume caching is gone
- * o directory offspring count calculation probably broken
+ * o merge dircache_search_by_name and dir_add ??
+ * o case-insensitivity is gone from cname
  */
 
 
@@ -62,9 +54,10 @@ extern void addir_inherit_acl(const struct vol *vol);
  ******************************************************************************************/
 
 int         afp_errno;
+/* As long as directory.c hasn't got its own init call, this get initialized in dircache_init */
 struct dir rootParent  = {
     NULL, NULL, NULL, NULL,          /* path, d_m_name, d_u_name, d_m_name_ucs2 */
-    NULL, NULL, 0, 0,                /* qidx_node, d_ofork, ctime, d_flags */
+    NULL, 0, 0,                      /* qidx_node, ctime, d_flags */
     0, 0, 0, 0                       /* pdid, did, offcnt, d_vid */
 };
 struct dir  *curdir = &rootParent;
@@ -73,10 +66,23 @@ struct path Cur_Path = {
     "",  /* mac name */
     ".", /* unix name */
     0,   /* id */
-    NULL,/* struct dir */
+    NULL,/* struct dir * */
     0,   /* stat is not set */
+    0,   /* errno */
+    {0} /* struct stat */
 };
 
+/*
+ * dir_remove queues struct dirs to be freed here. We can't just delete them immeidately
+ * eg in dircache_search_by_id, because a caller somewhere up the stack might be
+ * referencing it.
+ * So instead:
+ * - we mark it as invalid by setting d_did to CNID_INVALID (ie 0)
+ * - queue it in "invalid_dircache_entries" queue
+ * - which is finally freed at the end of every AFP func in afp_dsi.c.
+ */
+q_t *invalid_dircache_entries;
+
 
 /*******************************************************************************************
  * Locals
@@ -86,9 +92,24 @@ struct path Cur_Path = {
 /* -------------------------
    appledouble mkdir afp error code.
 */
-static int netatalk_mkdir(const char *name)
+static int netatalk_mkdir(const struct vol *vol, const char *name)
 {
-    if (ad_mkdir(name, DIRBITS | 0777) < 0) {
+    int ret;
+    struct stat st;
+
+    if (vol->v_flags & AFPVOL_UNIX_PRIV) {
+        if (lstat(".", &st) < 0)
+            return AFPERR_MISC;
+        int mode = (DIRBITS & (~S_ISGID & st.st_mode)) | (0777 & ~vol->v_umask);
+        LOG(log_maxdebug, logtype_afpd, "netatalk_mkdir(\"%s\") {parent mode: %04o, vol umask: %04o}",
+            name, st.st_mode, vol->v_umask);
+
+        ret = mkdir(name, mode);
+    } else {
+        ret = ad_mkdir(name, DIRBITS | 0777);
+    }
+
+    if (ret < 0) {
         switch ( errno ) {
         case ENOENT :
             return( AFPERR_NOOBJ );
@@ -110,7 +131,7 @@ static int netatalk_mkdir(const char *name)
 }
 
 /* ------------------- */
-static int deletedir(char *dir)
+static int deletedir(int dirfd, char *dir)
 {
     char path[MAXPATHLEN + 1];
     DIR *dp;
@@ -124,7 +145,7 @@ static int deletedir(char *dir)
         return AFPERR_PARAM;
 
     /* already gone */
-    if ((dp = opendir(dir)) == NULL)
+    if ((dp = opendirat(dirfd, dir)) == NULL)
         return AFP_OK;
 
     strcpy(path, dir);
@@ -141,13 +162,13 @@ static int deletedir(char *dir)
             break;
         }
         strcpy(path + len, de->d_name);
-        if (stat(path, &st)) {
+        if (lstatat(dirfd, path, &st)) {
             continue;
         }
         if (S_ISDIR(st.st_mode)) {
-            err = deletedir(path);
+            err = deletedir(dirfd, path);
         } else {
-            err = netatalk_unlink(path);
+            err = netatalk_unlinkat(dirfd, path);
         }
     }
     closedir(dp);
@@ -155,13 +176,13 @@ static int deletedir(char *dir)
     /* okay. the directory is empty. delete it. note: we already got rid
        of .AppleDouble.  */
     if (err == AFP_OK) {
-        err = netatalk_rmdir(dir);
+        err = netatalk_rmdir(dirfd, dir);
     }
     return err;
 }
 
 /* do a recursive copy. */
-static int copydir(const struct vol *vol, char *src, char *dst)
+static int copydir(const struct vol *vol, int dirfd, char *src, char *dst)
 {
     char spath[MAXPATHLEN + 1], dpath[MAXPATHLEN + 1];
     DIR *dp;
@@ -175,11 +196,11 @@ static int copydir(const struct vol *vol, char *src, char *dst)
     /* doesn't exist or the path is too long. */
     if (((slen = strlen(src)) > sizeof(spath) - 2) ||
         ((dlen = strlen(dst)) > sizeof(dpath) - 2) ||
-        ((dp = opendir(src)) == NULL))
+        ((dp = opendirat(dirfd, src)) == NULL))
         return AFPERR_PARAM;
 
     /* try to create the destination directory */
-    if (AFP_OK != (err = netatalk_mkdir(dst)) ) {
+    if (AFP_OK != (err = netatalk_mkdir(vol, dst)) ) {
         closedir(dp);
         return err;
     }
@@ -207,7 +228,7 @@ static int copydir(const struct vol *vol, char *src, char *dst)
         }
         strcpy(spath + slen, de->d_name);
 
-        if (stat(spath, &st) == 0) {
+        if (lstatat(dirfd, spath, &st) == 0) {
             if (strlen(de->d_name) > drem) {
                 err = AFPERR_PARAM;
                 break;
@@ -215,9 +236,9 @@ static int copydir(const struct vol *vol, char *src, char *dst)
             strcpy(dpath + dlen, de->d_name);
 
             if (S_ISDIR(st.st_mode)) {
-                if (AFP_OK != (err = copydir(vol, spath, dpath)))
+                if (AFP_OK != (err = copydir(vol, dirfd, spath, dpath)))
                     goto copydir_done;
-            } else if (AFP_OK != (err = copyfile(vol, vol, spath, dpath, NULL, NULL))) {
+            } else if (AFP_OK != (err = copyfile(vol, vol, dirfd, spath, dpath, NULL, NULL))) {
                 goto copydir_done;
 
             } else {
@@ -229,7 +250,7 @@ static int copydir(const struct vol *vol, char *src, char *dst)
     }
 
     /* keep the same time stamp. */
-    if (stat(src, &st) == 0) {
+    if (lstatat(dirfd, src, &st) == 0) {
         ut.actime = ut.modtime = st.st_mtime;
         utime(dst, &ut);
     }
@@ -281,7 +302,7 @@ static int cname_mtouname(const struct vol *vol, const struct dir *dir, struct p
 {
     static char temp[ MAXPATHLEN + 1];
     char *t;
-    cnid_t fileid;
+    cnid_t fileid = 0;
 
     if (afp_version >= 30) {
         if (toUTF8) {
@@ -338,61 +359,55 @@ static int cname_mtouname(const struct vol *vol, const struct dir *dir, struct p
  *
  * The final movecwd in cname failed, possibly with EPERM or ENOENT. We:
  * 1. move cwd into parent dir (we're often already there, but not always)
+ * 2. set struct path to the dirname
+ * 3. in case of
+ *    AFPERR_ACCESS: the dir is there, we just cant chdir into it
+ *    AFPERR_NOOBJ: the dir was there when we stated it in cname, so we have a race
+ *                  4. indicate there's no dir for this path
+ *                  5. remove the dir
  */
 static struct path *path_from_dir(struct vol *vol, struct dir *dir, struct path *ret)
 {
-    /*
-     * it's tricky: movecwd failed some of dir path are not there anymore.
-     * FIXME: Is it true with other errors?
-     */
     if (dir->d_did == DIRDID_ROOT_PARENT || dir->d_did == DIRDID_ROOT)
         return NULL;
 
     switch (afp_errno) {
 
     case AFPERR_ACCESS:
-        if (movecwd( vol, dirlookup(vol, dir->d_pdid)) < 0 )
+        if (movecwd( vol, dirlookup(vol, dir->d_pdid)) < 0 ) /* 1 */
             return NULL;
 
-        memcpy(ret->m_name, cfrombstring(dir->d_m_name), blength(dir->d_m_name) + 1);
+        memcpy(ret->m_name, cfrombstr(dir->d_m_name), blength(dir->d_m_name) + 1); /* 3 */
         if (dir->d_m_name == dir->d_u_name) {
             ret->u_name = ret->m_name;
         } else {
             ret->u_name =  ret->m_name + blength(dir->d_m_name) + 1;
-            memcpy(ret->u_name, cfrombstring(dir->d_u_name), blength(dir->d_u_name) + 1);
+            memcpy(ret->u_name, cfrombstr(dir->d_u_name), blength(dir->d_u_name) + 1);
         }
 
         ret->d_dir = dir;
-#if 0
-        ret->st_valid = 1;
-        ret->st_errno = EACCES;
-#endif
 
-        LOG(log_debug, logtype_afpd, "cname(AFPERR_ACCESS:'%s') {path-from-dir: curdir:'%s', path:'%s'}",
-            cfrombstring(dir->d_fullpath),
-            cfrombstring(curdir->d_fullpath),
+        LOG(log_debug, logtype_afpd, "cname('%s') {path-from-dir: AFPERR_ACCESS. curdir:'%s', path:'%s'}",
+            cfrombstr(dir->d_fullpath),
+            cfrombstr(curdir->d_fullpath),
             ret->u_name);
 
         return ret;
 
     case AFPERR_NOOBJ:
-        if (movecwd(vol, dirlookup(vol, dir->d_pdid)) < 0 )
+        if (movecwd(vol, dirlookup(vol, dir->d_pdid)) < 0 ) /* 1 */
             return NULL;
 
-        memcpy(ret->m_name, cfrombstring(dir->d_m_name), blength(dir->d_m_name) + 1);
+        memcpy(ret->m_name, cfrombstr(dir->d_m_name), blength(dir->d_m_name) + 1);
         if (dir->d_m_name == dir->d_u_name) {
             ret->u_name = ret->m_name;
         } else {
             ret->u_name =  ret->m_name + blength(dir->d_m_name) + 1;
-            memcpy(ret->u_name, cfrombstring(dir->d_u_name), blength(dir->d_u_name) + 1);
+            memcpy(ret->u_name, cfrombstr(dir->d_u_name), blength(dir->d_u_name) + 1);
         }
 
-#if 0
-        ret->st_valid = 1;
-        ret->st_errno = ENOENT;
-#endif
-        ret->d_dir = NULL;
-        dir_remove(vol, dir);
+        ret->d_dir = NULL;      /* 4 */
+        dir_remove(vol, dir);   /* 5 */
         return ret;
 
     default:
@@ -415,12 +430,91 @@ 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
  *
  * Resolve a DID, allocate a struct dir for it
  * 1. Check for special CNIDs 0 (invalid), 1 and 2.
- * 2. Check if the DID is in the cache.
+ * 2a. Check if the DID is in the cache.
+ * 2b. Check if it's really a dir (d_fullpath != NULL) because we cache files too.
  * 3. If it's not in the cache resolve it via the database.
  * 4. Build complete server-side path to the dir.
  * 5. Check if it exists and is a directory.
@@ -431,105 +525,106 @@ 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)
 {
     static char  buffer[12 + MAXPATHLEN + 1];
-    struct bstrList *pathlist = NULL;
-    bstring      fullpath = NULL;
     struct stat  st;
-    struct dir   *ret = NULL;
-    char         *upath, *mpath;
+    struct dir   *ret = NULL, *pdir;
+    bstring      fullpath = NULL;
+    char         *upath = NULL, *mpath;
     cnid_t       cnid, pdid;
     size_t       maxpath;
     int          buflen = 12 + MAXPATHLEN + 1;
     int          utf8;
     int          err = 0;
 
-    LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {start}", ntohl(did));
+    LOG(log_debug, logtype_afpd, "dirlookup(did: %u)", ntohl(did));
 
     /* check for did 0, 1 and 2 */
     if (did == 0 || vol == NULL) { /* 1 */
         afp_errno = AFPERR_PARAM;
-        return NULL;
+        ret = NULL;
+        goto exit;
     } else if (did == DIRDID_ROOT_PARENT) {
         rootParent.d_vid = vol->v_vid;
-        return (&rootParent);
+        ret = &rootParent;
+        goto exit;
     } else if (did == DIRDID_ROOT) {
-        return vol->v_root;
+        ret = vol->v_root;
+        goto exit;
     }
 
     /* Search the cache */
-    if ((ret = dircache_search_by_did(vol, did)) != NULL) { /* 2 */
-        return ret;
+    if ((ret = dircache_search_by_did(vol, did)) != NULL) { /* 2a */
+        if (ret->d_fullpath == NULL) {                      /* 2b */
+            afp_errno = AFPERR_BADTYPE;
+            ret = NULL;
+            goto exit;
+        }
+        if (lstat(cfrombstr(ret->d_fullpath), &st) != 0) {
+            LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {lstat: %s}", ntohl(did), strerror(errno));
+            switch (errno) {
+            case ENOENT:
+            case ENOTDIR:
+                /* It's not there anymore, so remove it */
+                LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {calling dir_remove()}", ntohl(did));
+                dir_remove(vol, ret);
+                afp_errno = AFPERR_NOOBJ;
+                ret = NULL;
+                goto exit;
+            default:
+                ret = ret;
+                goto exit;
+            }
+            /* DEADC0DE */
+            ret = NULL;
+            goto exit;            
+        }
+        ret = ret;
+        goto exit;
     }
 
     utf8 = utf8_encoding();
     maxpath = (utf8) ? MAXPATHLEN - 7 : 255;
 
-    /* Create list for path elements, request 16 list elements for now*/
-    if ((pathlist = bstListCreateMin(16)) == NULL) { /* 4 */
-        LOG(log_error, logtype_afpd, "dirlookup(did: %u): OOM: %s", ntohl(did), strerror(errno));
-        return NULL;
-    }
-
     /* Get it from the database */
     cnid = did;
-    if ( (upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL ) { /* 3 */
+    if ((upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL) {
         afp_errno = AFPERR_NOOBJ;
         err = 1;
         goto exit;
     }
-    pdid = cnid;
-
-    /* construct path, copy already found uname to path element list*/
-    if ((bstrListPush(pathlist, bfromcstr(upath))) != BSTR_OK) { /* 4 */
-        afp_errno = AFPERR_MISC;
+    if ((upath = strdup(upath)) == NULL) { /* 3 */
+        afp_errno = AFPERR_NOOBJ;
         err = 1;
         goto exit;
     }
+    pdid = cnid;
 
-    LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {%u, %s}", ntohl(did), ntohl(pdid), upath);
-
-    /* The stuff that follows is for building the full path to the directory */
-
-    /* work upwards until we reach volume root */
-    while (cnid != DIRDID_ROOT) {
-        /* construct path, copy already found uname to path element list*/
-        if ((bstrListPush(pathlist, bfromcstr(upath))) != BSTR_OK) { /* 4 */
-            afp_errno = AFPERR_MISC;
-            err = 1;
-            goto exit;
-        }
-
-        /* next part */
-        if ((upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL ) { /* 3 */
-            afp_errno = AFPERR_NOOBJ;
-            err = 1;
-            goto exit;
-        }
-    }
-
-    if ((bstrListPush(pathlist, bfromcstr(vol->v_path))) != BSTR_OK) { /* 4 */
-        afp_errno = AFPERR_MISC;
+    /*
+     * Recurse up the tree, terminates in dirlookup when either
+     * - 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;
     }
 
-    if ((fullpath = bjoinInv(pathlist, bfromcstr("/"))) == NULL) { /* 4 */
-        afp_errno = AFPERR_MISC;
+    /* build the fullpath */
+    if ((fullpath = bstrcpy(pdir->d_fullpath)) == NULL
+        || bconchar(fullpath, '/') != BSTR_OK
+        || bcatcstr(fullpath, upath) != BSTR_OK) {
         err = 1;
         goto exit;
     }
-    /* Finished building the fullpath */
 
     /* stat it and check if it's a dir */
-    LOG(log_debug, logtype_afpd, "dirlookup: {stating %s}", cfrombstring(fullpath));
+    LOG(log_debug, logtype_afpd, "dirlookup: {stating %s}", cfrombstr(fullpath));
 
-    if (stat(cfrombstring(fullpath), &st) != 0) { /* 5a */
+    if (stat(cfrombstr(fullpath), &st) != 0) { /* 5a */
         switch (errno) {
         case ENOENT:
             afp_errno = AFPERR_NOOBJ;
@@ -560,25 +655,20 @@ struct dir *dirlookup(const struct vol *vol, cnid_t did)
     }
 
     /* Create struct dir */
-    if ((ret = dir_new(mpath, upath, vol, pdid, did, fullpath)) == NULL) { /* 6 */
+    if ((ret = dir_new(mpath, upath, vol, pdid, did, fullpath, st.st_ctime)) == NULL) { /* 6 */
         LOG(log_error, logtype_afpd, "dirlookup(did: %u) {%s, %s}: %s", ntohl(did), mpath, upath, strerror(errno));
         err = 1;
         goto exit;
     }
-
+    
     /* Add it to the cache only if it's a dir */
-    if (dircache_add(ret) != 0) { /* 7 */
+    if (dircache_add(vol, ret) != 0) { /* 7 */
         err = 1;
         goto exit;
     }
 
-    LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {end: did:%u, path:'%s'}",
-        ntohl(did), ntohl(pdid), cfrombstring(ret->d_fullpath));
-
 exit:
-    if (pathlist)
-        bstrListDestroy(pathlist);
-
+    if (upath) free(upath);
     if (err) {
         LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {exit_error: %s}",
             ntohl(did), AfpErr2name(afp_errno));
@@ -589,6 +679,10 @@ exit:
             ret = NULL;
         }
     }
+    if (ret)
+        LOG(log_debug, logtype_afpd, "dirlookup(did: %u): pdid: %u, \"%s\"",
+            ntohl(ret->d_did), ntohl(ret->d_pdid), cfrombstr(ret->d_fullpath));
+
     return ret;
 }
 
@@ -684,7 +778,8 @@ int caseenumerate(const struct vol *vol, struct path *path, struct dir *dir)
  * @param vol      (r) pointer to struct vol
  * @param pdid     (r) Parent CNID
  * @param did      (r) CNID
- * @param fullpath (r) Full unix path to dir
+ * @param path     (r) Full unix path to dir or NULL for files
+ * @param ctime    (r) st_ctime from stat
  *
  * @returns pointer to new struct dir or NULL on error
  *
@@ -695,7 +790,8 @@ struct dir *dir_new(const char *m_name,
                     const struct vol *vol,
                     cnid_t pdid,
                     cnid_t did,
-                    bstring path)
+                    bstring path,
+                    time_t ctime)
 {
     struct dir *dir;
 
@@ -729,6 +825,7 @@ struct dir *dir_new(const char *m_name,
     dir->d_pdid = pdid;
     dir->d_vid = vol->v_vid;
     dir->d_fullpath = path;
+    dir->ctime_dircache = ctime;
     return dir;
 }
 
@@ -753,14 +850,13 @@ void dir_free(struct dir *dir)
  * @brief Create struct dir from struct path
  *
  * Create a new struct dir from struct path. Then add it to the cache.
- * The caller must have assured that the dir is not already in the cache,
- * cf the assertion.
+ *
  * 1. Open adouble file, get CNID from it.
  * 2. Search the database, hinting with the CNID from (1).
  * 3. Build fullpath and create struct dir.
  * 4. Add it to the cache.
  *
- * @param vol   (r) pointer to struct vol
+ * @param vol   (r) pointer to struct vol, possibly modified in callee
  * @param dir   (r) pointer to parrent directory
  * @param path  (rw) pointer to struct path with valid path->u_name
  * @param len   (r) strlen of path->u_name
@@ -769,7 +865,7 @@ void dir_free(struct dir *dir)
  *
  * @note Function also assigns path->m_name from path->u_name.
  */
-struct dir *dir_add(const struct vol *vol, const struct dir *dir, struct path *path, int len)
+struct dir *dir_add(struct vol *vol, const struct dir *dir, struct path *path, int len)
 {
     int err = 0;
     struct dir  *cdir = NULL;
@@ -778,15 +874,25 @@ struct dir *dir_add(const struct vol *vol, const struct dir *dir, struct path *p
     struct adouble *adp = NULL;
     bstring fullpath;
 
-    assert(vol);
-    assert(dir);
-    assert(path);
-    assert(len > 0);
-    assert(dircache_search_by_name(vol, dir->d_did, path->u_name, strlen(path->u_name)) == NULL);
+    AFP_ASSERT(vol);
+    AFP_ASSERT(dir);
+    AFP_ASSERT(path);
+    AFP_ASSERT(len > 0);
+
+    if ((cdir = dircache_search_by_name(vol, dir, path->u_name, strlen(path->u_name), path->st.st_ctime)) != NULL) {
+        /* there's a stray entry in the dircache */
+        LOG(log_debug, logtype_afpd, "dir_add(did:%u,'%s/%s'): {stray cache entry: did:%u,'%s', removing}",
+            ntohl(dir->d_did), cfrombstr(dir->d_fullpath), path->u_name,
+            ntohl(cdir->d_did), cfrombstr(dir->d_fullpath));
+        if (dir_remove(vol, cdir) != 0) {
+            dircache_dump();
+            AFP_PANIC("dir_add");
+        }
+    }
 
     /* get_id needs adp for reading CNID from adouble file */
     ad_init(&ad, vol->v_adouble, vol->v_ad_options);
-    if ((ad_open_metadata(path->u_name, ADFLAGS_DIR, 0, &ad)) == 0) /* 1 */
+    if ((ad_open(&ad, path->u_name, ADFLAGS_HF | ADFLAGS_DIR)) == 0) /* 1 */
         adp = &ad;
 
     /* Get CNID */
@@ -801,7 +907,8 @@ struct dir *dir_add(const struct vol *vol, const struct dir *dir, struct path *p
     /* Get macname from unixname */
     if (path->m_name == NULL) {
         if ((path->m_name = utompath(vol, path->u_name, id, utf8_encoding())) == NULL) {
-            err = 1;
+            LOG(log_error, logtype_afpd, "dir_add(\"%s\"): can't assign macname", path->u_name);
+            err = 2;
             goto exit;
         }
     }
@@ -811,23 +918,26 @@ struct dir *dir_add(const struct vol *vol, const struct dir *dir, struct path *p
          || (bconchar(fullpath, '/') != BSTR_OK)
          || (bcatcstr(fullpath, path->u_name)) != BSTR_OK) {
         LOG(log_error, logtype_afpd, "dir_add: fullpath: %s", strerror(errno) );
-        err = 1;
+        err = 3;
         goto exit;
     }
 
     /* Allocate and initialize struct dir */
-    if ((cdir = dir_new( path->m_name, path->u_name, vol, dir->d_did, id, fullpath)) == NULL) { /* 3 */
-        err = 1;
+    if ((cdir = dir_new( path->m_name, path->u_name, vol, dir->d_did, id, fullpath, path->st.st_ctime)) == NULL) { /* 3 */
+        err = 4;
         goto exit;
     }
 
-    if ((dircache_add(cdir)) != 0) { /* 4 */
-        LOG(log_error, logtype_afpd, "dir_add: fatal dircache error: %s", cfrombstring(fullpath));
+    if ((dircache_add(vol, cdir)) != 0) { /* 4 */
+        LOG(log_error, logtype_afpd, "dir_add: fatal dircache error: %s", cfrombstr(fullpath));
         exit(EXITERR_SYS);
     }
 
 exit:
     if (err != 0) {
+        LOG(log_debug, logtype_afpd, "dir_add('%s/%s'): error: %u",
+            cfrombstr(dir->d_u_name), path->u_name, err);
+
         if (adp)
             ad_close_metadata(adp);
         if (!cdir && fullpath)
@@ -835,39 +945,65 @@ exit:
         if (cdir)
             dir_free(cdir);
         cdir = NULL;
+    } else {
+        /* no error */
+        LOG(log_debug, logtype_afpd, "dir_add(did:%u,'%s/%s'): {cached: %u,'%s'}",
+            ntohl(dir->d_did), cfrombstr(dir->d_fullpath), path->u_name,
+            ntohl(cdir->d_did), cfrombstr(cdir->d_fullpath));
     }
 
     return(cdir);
 }
 
 /*!
- * @brief Remove a dir from a cache and free it and any ressources with it
+ * Free the queue with invalid struct dirs
+ *
+ * This gets called at the end of every AFP func.
+ */
+void dir_free_invalid_q(void)
+{
+    struct dir *dir;
+    while (dir = (struct dir *)dequeue(invalid_dircache_entries))
+        dir_free(dir);
+}
+
+/*!
+ * @brief Remove a dir from a cache and queue it for freeing
+ *
+ * 1. Check if the dir is locked or has opened forks
+ * 2. Remove it from the cache
+ * 3. Queue it for removal
+ * 4. If it's a request to remove curdir, mark curdir as invalid
+ * 5. Mark it as invalid
  *
  * @param (r) pointer to struct vol
  * @param (rw) pointer to struct dir
  */
 int dir_remove(const struct vol *vol, struct dir *dir)
 {
-    assert(vol);
-    assert(dir);
+    AFP_ASSERT(vol);
+    AFP_ASSERT(dir);
 
     if (dir->d_did == DIRDID_ROOT_PARENT || dir->d_did == DIRDID_ROOT)
         return 0;
 
-    if (curdir == dir) {
-        if (movecwd(vol, vol->v_root) < 0) {
-            LOG(log_error, logtype_afpd, "dir_remove: can't chdir to : %s", vol->v_root);
-        }
-    }
+    LOG(log_debug, logtype_afpd, "dir_remove(did:%u,'%s'): {removing}",
+        ntohl(dir->d_did), cfrombstr(dir->d_u_name));
 
-    dircache_remove(vol, dir, DIRCACHE | DIDNAME_INDEX | QUEUE_INDEX);
-    dir_free(dir);
+    dircache_remove(vol, dir, DIRCACHE | DIDNAME_INDEX | QUEUE_INDEX); /* 2 */
+    enqueue(invalid_dircache_entries, dir); /* 3 */
+
+    if (curdir == dir)                      /* 4 */
+        curdir = NULL;
+
+    dir->d_did = CNID_INVALID;              /* 5 */
 
     return 0;
 }
 
+#if 0 /* unused */
 /*!
- * @brief Modify a struct dir, adust cache
+ * @brief Modify a struct dir, adjust cache
  *
  * Any value that is 0 or NULL is not changed. If new_uname is NULL it is set to new_mname.
  * If given new_uname == new_mname, new_uname will point to new_mname.
@@ -878,6 +1014,7 @@ int dir_remove(const struct vol *vol, struct dir *dir)
  * @param did       (r) new DID
  * @param new_mname (r) new mac-name
  * @param new_uname (r) new unix-name
+ * @param pdir_fullpath (r) new fullpath of parent dir
  */
 int dir_modify(const struct vol *vol,
                struct dir *dir,
@@ -915,7 +1052,7 @@ int dir_modify(const struct vol *vol,
             dir->d_u_name = dir->d_m_name;
         } else {
             if ((dir->d_u_name = bfromcstr(new_uname)) == NULL) {
-                LOG(log_error, logtype_afpd, "renamedir: bassigncstr: %s", strerror(errno) );
+                LOG(log_error, logtype_afpd, "dir_modify: bassigncstr: %s", strerror(errno) );
                 return -1;
             }
         }
@@ -936,50 +1073,41 @@ int dir_modify(const struct vol *vol,
         dir->d_m_name_ucs2 = NULL;
 
     /* Re-add it to the cache */
-    if ((dircache_add(dir)) != 0) {
+    if ((dircache_add(vol, dir)) != 0) {
         dircache_dump();
-        exit(EXITERR_SYS);
+        AFP_PANIC("dir_modify");
     }
 
     return ret;
 }
+#endif
 
 /*!
  * @brief Resolve a catalog node name path
  *
- * If it's a filename:
- * 1. compute unix name
- * 2. stat the file, storing struct stat or errno in struct path
- * 3. cwd (and curdir) is filename parent directory
- * 4. return path with with filename
- *
- * If it's a dirname:
- * 5. search the dircache
- * 6. if not in the cache:
- * 7.     compute unix name
- * 8.     stat the dir, storing struct stat or errno in struct path
- * 9.     chdir dirname
- * 10.    if chdir failed, return path with dirname
- *                         cwd is dir parent directory
- *        if chdir succeeded, add to dircache
- *                            return path with "" and "."
- *                            cwd is dirname
- * 11. else in the cache:
- * 12.     stat the dir, storing struct stat or errno in struct path
- * 13.     chdir dirname
- * 14.     if chdir failed
- *             if ENOENT
- *                 remove from cache
- *             if not last path part, return NULL
- *             else 
-   return
-   if chdir error
-   dirname
-   curdir: dir parent directory
-   else
-   dirname: ""
-   curdir: dir
-*/
+ * 1. Evaluate path type
+ * 2. Move to start dir, if we cant, it might eg because of EACCES, build
+ *    path from dirname, so eg getdirparams has sth it can chew on. curdir
+ *    is dir parent then. All this is done in path_from_dir().
+ * 3. Parse next cnode name in path, cases:
+ * 4.   single "\0" -> do nothing
+ * 5.   two or more consecutive "\0" -> chdir("..") one or more times
+ * 6.   cnode name -> copy it to path.m_name
+ * 7. Get unix name from mac name
+ * 8. Special handling of request with did 1
+ * 9. stat the cnode name
+ * 10. If it's not there, it's probably an afp_createfile|dir,
+ *     return with curdir = dir parent, struct path = dirname
+ * 11. If it's there and it's a file, it must should be the last element of the requested
+ *     path. Return with curdir = cnode name parent dir, struct path = filename
+ * 12. Treat symlinks like files, dont follow them
+ * 13. If it's a dir:
+ * 14. Search the dircache for it
+ * 15. If it's not in the cache, create a struct dir for it and add it to the cache
+ * 16. chdir into the dir and
+ * 17. set m_name to the mac equivalent of "."
+ * 18. goto 3
+ */
 struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
 {
     static char        path[ MAXPATHLEN + 1];
@@ -993,13 +1121,13 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
     int         size = 0;
     int         toUTF8 = 0;
 
-    LOG(log_debug, logtype_afpd, "came('%s'): {start}", cfrombstring(dir->d_fullpath));
+    LOG(log_maxdebug, logtype_afpd, "came('%s'): {start}", cfrombstr(dir->d_fullpath));
 
     data = *cpath;
     afp_errno = AFPERR_NOOBJ;
     memset(&ret, 0, sizeof(ret));
 
-    switch (ret.m_type = *data) { /* path type */
+    switch (ret.m_type = *data) { /* 1 */
     case 2:
         data++;
         len = (unsigned char) *data++;
@@ -1034,26 +1162,18 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
 
     if (movecwd(vol, dir) < 0 ) {
         LOG(log_debug, logtype_afpd, "cname(did:%u): failed to chdir to '%s'",
-            ntohl(dir->d_did), cfrombstring(dir->d_fullpath));
+            ntohl(dir->d_did), cfrombstr(dir->d_fullpath));
         if (len == 0)
             return path_from_dir(vol, dir, &ret);
         else
             return NULL;
     }
 
-    while (len) {
-        /*
-         * Three cases:
-         * 1. single 0 -> delimiter
-         * 2. additional 0 -> chdir(..)
-         * 3. a name
-         *    a) name is a file, build struct path from it etc., exit while loop
-         *    b) name is a dir, add it to the dircache, chdir to it, continue
-         */
-        if (*data == 0) { /* case 1 or 2 */
+    while (len) {         /* 3 */
+        if (*data == 0) { /* 4 or 5 */
             data++;
             len--;
-            while (len > 0 && *data == 0) { /* case 2 */
+            while (len > 0 && *data == 0) { /* 5 */
                 /* chdir to parrent dir */
                 if ((dir = dirlookup(vol, dir->d_pdid)) == NULL)
                     return NULL;
@@ -1067,10 +1187,10 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
             continue;
         }
 
-        /* case 3: copy name from packet buffer to ret.m_name and process it */
+        /* 6*/
         for ( p = path; *data != 0 && len > 0; len-- ) {
             *p++ = *data++;
-            if (p > &path[ MAXPATHLEN]) {
+            if (p > &path[UTF8FILELEN_EARLY]) {   /* FIXME safeguard, limit of early Mac OS X */
                 afp_errno = AFPERR_PARAM;
                 return NULL;
             }
@@ -1078,13 +1198,12 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
         *p = 0;            /* Terminate string */
         ret.u_name = NULL;
 
-        /* Get u_name from m_name */
-        if (cname_mtouname(vol, dir, &ret, toUTF8) != 0) {
+        if (cname_mtouname(vol, dir, &ret, toUTF8) != 0) { /* 7 */
             LOG(log_error, logtype_afpd, "cname('%s'): error from cname_mtouname", path);
             return NULL;
         }
 
-        LOG(log_maxdebug, logtype_afpd, "came('%s'): {node: '%s}", cfrombstring(dir->d_fullpath), ret.u_name);
+        LOG(log_maxdebug, logtype_afpd, "came('%s'): {node: '%s}", cfrombstr(dir->d_fullpath), ret.u_name);
 
         /* Prevent access to our special folders like .AppleDouble */
         if (check_name(vol, ret.u_name)) {
@@ -1094,13 +1213,13 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
             return NULL;
         }
 
-        if (dir->d_did == DIRDID_ROOT_PARENT) {
+        if (dir->d_did == DIRDID_ROOT_PARENT) { /* 8 */
             /*
              * Special case: CNID 1
              * root parent (did 1) has one child: the volume. Requests for did=1 with
              * some <name> must check against the volume name.
              */
-            if ((strcmp(cfrombstring(vol->v_root->d_m_name), ret.m_name)) == 0)
+            if ((strcmp(cfrombstr(vol->v_root->d_m_name), ret.m_name)) == 0)
                 cdir = vol->v_root;
             else
                 return NULL;
@@ -1113,7 +1232,7 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
              *   and thus call continue which should terminate the while loop because
              *   len = 0. Ok?
              */
-            if (of_stat(&ret) != 0) {
+            if (of_stat(&ret) != 0) { /* 9 */
                 /*
                  * ret.u_name doesn't exist, might be afp_createfile|dir
                  * that means it should have been the last part
@@ -1127,28 +1246,32 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
                  * this will terminate clean in while (1) because len == 0,
                  * probably afp_createfile|dir
                  */
-                LOG(log_maxdebug, logtype_afpd, "came('%s'): {leave-cnode ENOENT (possile create request): '%s'}", cfrombstring(dir->d_fullpath), ret.u_name);
-                continue;
+                LOG(log_maxdebug, logtype_afpd, "came('%s'): {leave-cnode ENOENT (possile create request): '%s'}",
+                    cfrombstr(dir->d_fullpath), ret.u_name);
+                continue; /* 10 */
             }
 
             switch (ret.st.st_mode & S_IFMT) {
-            case S_IFREG:
-                LOG(log_debug, logtype_afpd, "came('%s'): {file: '%s'}", cfrombstring(dir->d_fullpath), ret.u_name);
+            case S_IFREG: /* 11 */
+                LOG(log_debug, logtype_afpd, "came('%s'): {file: '%s'}",
+                    cfrombstr(dir->d_fullpath), ret.u_name);
                 if (len > 0) {
                     /* it wasn't the last part, so we have a bogus path request */
                     afp_errno = AFPERR_PARAM;
                     return NULL;
                 }
                 continue; /* continues while loop */
-            case S_IFLNK:
-                LOG(log_debug, logtype_afpd, "came('%s'): {link: '%s'}", cfrombstring(dir->d_fullpath), ret.u_name);
+            case S_IFLNK: /* 12 */
+                LOG(log_debug, logtype_afpd, "came('%s'): {link: '%s'}",
+                    cfrombstr(dir->d_fullpath), ret.u_name);
                 if (len > 0) {
-                    LOG(log_warning, logtype_afpd, "came('%s'): {symlinked dir: '%s'}", cfrombstring(dir->d_fullpath), ret.u_name);
+                    LOG(log_warning, logtype_afpd, "came('%s'): {symlinked dir: '%s'}",
+                        cfrombstr(dir->d_fullpath), ret.u_name);
                     afp_errno = AFPERR_PARAM;
                     return NULL;
                 }
                 continue; /* continues while loop */
-            case S_IFDIR:
+            case S_IFDIR: /* 13 */
                 break;
             default:
                 LOG(log_info, logtype_afpd, "cname: special file: '%s'", ret.u_name);
@@ -1158,10 +1281,10 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
 
             /* Search the cache */
             int unamelen = strlen(ret.u_name);
-            cdir = dircache_search_by_name(vol, dir->d_did, ret.u_name, unamelen);
+            cdir = dircache_search_by_name(vol, dir, ret.u_name, unamelen, ret.st.st_ctime); /* 14 */
             if (cdir == NULL) {
                 /* Not in cache, create one */
-                if ((cdir = dir_add(vol, dir, &ret, unamelen)) == NULL) {
+                if ((cdir = dir_add(vol, dir, &ret, unamelen)) == NULL) { /* 15 */
                     LOG(log_error, logtype_afpd, "cname(did:%u, name:'%s', cwd:'%s'): failed to add dir",
                         ntohl(dir->d_did), ret.u_name, getcwdpath());
                     return NULL;
@@ -1170,16 +1293,16 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
         } /* if/else cnid==1 */
 
         /* Now chdir to the evaluated dir */
-        if (movecwd( vol, cdir ) < 0 ) {
+        if (movecwd( vol, cdir ) < 0 ) { /* 16 */
             LOG(log_debug, logtype_afpd, "cname(cwd:'%s'): failed to chdir to new subdir '%s': %s",
-                cfrombstring(curdir->d_fullpath), cfrombstring(cdir->d_fullpath), strerror(errno));
+                cfrombstr(curdir->d_fullpath), cfrombstr(cdir->d_fullpath), strerror(errno));
             if (len == 0)
                 return path_from_dir(vol, cdir, &ret);
             else
                 return NULL;
         }
         dir = cdir;
-        ret.m_name[0] = 0;      /* so we later know last token was a dir */
+        ret.m_name[0] = 0;      /* 17, so we later know last token was a dir */
     } /* while (len) */
 
     if (curdir->d_did == DIRDID_ROOT_PARENT) {
@@ -1194,8 +1317,8 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
     }
 
     LOG(log_debug, logtype_afpd, "came('%s') {end: curdir:'%s', path:'%s'}",
-        cfrombstring(dir->d_fullpath),
-        cfrombstring(curdir->d_fullpath),
+        cfrombstr(dir->d_fullpath),
+        cfrombstr(curdir->d_fullpath),
         ret.u_name);
 
     return &ret;
@@ -1211,25 +1334,37 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath)
  */
 int movecwd(const struct vol *vol, struct dir *dir)
 {
-    assert(vol);
+    int ret;
 
-    if (dir == NULL)
-        return -1;
+    AFP_ASSERT(vol);
+    AFP_ASSERT(dir);
 
-    LOG(log_maxdebug, logtype_afpd, "movecwd(curdir:'%s', cwd:'%s')", 
-        cfrombstring(curdir->d_fullpath), getcwdpath());
+    LOG(log_maxdebug, logtype_afpd, "movecwd: from: curdir:\"%s\", cwd:\"%s\"",
+        curdir ? cfrombstr(curdir->d_fullpath) : "INVALID", getcwdpath());
 
-    if ( dir == curdir)
-        return( 0 );
     if (dir->d_did == DIRDID_ROOT_PARENT) {
         curdir = &rootParent;
         return 0;
     }
 
-    LOG(log_debug, logtype_afpd, "movecwd(did:%u, '%s')", ntohl(dir->d_did), cfrombstring(dir->d_fullpath));
+    LOG(log_debug, logtype_afpd, "movecwd(to: did: %u, \"%s\")",
+        ntohl(dir->d_did), cfrombstr(dir->d_fullpath));
+
+    if ((ret = lchdir(cfrombstr(dir->d_fullpath))) != 0 ) {
+        LOG(log_debug, logtype_afpd, "movecwd(\"%s\"): ret: %u, %s",
+            cfrombstr(dir->d_fullpath), ret, strerror(errno));
+        if (ret == 1) {
+            /* p is a symlink or getcwd failed */
+            afp_errno = AFPERR_BADTYPE;
+
+            if (chdir(vol->v_path ) < 0) {
+                LOG(log_error, logtype_afpd, "can't chdir back'%s': %s", vol->v_path, strerror(errno));
+                /* XXX what do we do here? */
+            }
+            curdir = vol->v_root;
+            return -1;
+        }
 
-    if ( chdir(cfrombstring(dir->d_fullpath)) < 0 ) {
-        LOG(log_debug, logtype_afpd, "movecwd('%s'): %s", cfrombstring(dir->d_fullpath), strerror(errno));
         switch (errno) {
         case EACCES:
         case EPERM:
@@ -1277,10 +1412,18 @@ int file_access(struct path *path, int mode)
     struct maccess ma;
 
     accessmode(path->u_name, &ma, curdir, &path->st);
-    if ((mode & OPENACC_WR) && !(ma.ma_user & AR_UWRITE))
+
+    LOG(log_debug, logtype_afpd, "file_access(\"%s\"): mapped user mode: 0x%02x",
+        path->u_name, ma.ma_user);
+
+    if ((mode & OPENACC_WR) && !(ma.ma_user & AR_UWRITE)) {
+        LOG(log_debug, logtype_afpd, "file_access(\"%s\"): write access denied", path->u_name);
         return -1;
-    if ((mode & OPENACC_RD) && !(ma.ma_user & AR_UREAD))
+    }
+    if ((mode & OPENACC_RD) && !(ma.ma_user & AR_UREAD)) {
+        LOG(log_debug, logtype_afpd, "file_access(\"%s\"): read access denied", path->u_name);
         return -1;
+    }
     return 0;
 
 }
@@ -1331,20 +1474,8 @@ int getdirparams(const struct vol *vol,
                    (1 << DIRPBIT_FINFO)))) {
 
         ad_init(&ad, vol->v_adouble, vol->v_ad_options);
-        if ( !ad_metadata( upath, ADFLAGS_CREATE|ADFLAGS_DIR, &ad) ) {
+        if ( !ad_metadata( upath, ADFLAGS_DIR, &ad) )
             isad = 1;
-            if (ad.ad_md->adf_flags & O_CREAT) {
-                /* We just created it */
-                ad_setname(&ad, s_path->m_name);
-                ad_setid( &ad,
-                          s_path->st.st_dev,
-                          s_path->st.st_ino,
-                          dir->d_did,
-                          dir->d_pdid,
-                          vol->v_stamp);
-                ad_flush( &ad);
-            }
-        }
     }
 
     pdid = dir->d_pdid;
@@ -1360,7 +1491,7 @@ int getdirparams(const struct vol *vol,
         case DIRPBIT_ATTR :
             if ( isad ) {
                 ad_getattr(&ad, &ashort);
-            } else if (invisible_dots(vol, cfrombstring(dir->d_u_name))) {
+            } else if (invisible_dots(vol, cfrombstr(dir->d_u_name))) {
                 ashort = htons(ATTRBIT_INVISIBLE);
             } else
                 ashort = 0;
@@ -1372,6 +1503,8 @@ int getdirparams(const struct vol *vol,
         case DIRPBIT_PDID :
             memcpy( data, &pdid, sizeof( pdid ));
             data += sizeof( pdid );
+            LOG(log_debug, logtype_afpd, "metadata('%s'):     Parent DID: %u",
+                s_path->u_name, ntohl(pdid));
             break;
 
         case DIRPBIT_CDATE :
@@ -1404,7 +1537,7 @@ int getdirparams(const struct vol *vol,
                 memcpy(data + FINDERINFO_FRVIEWOFF, &ashort, sizeof(ashort));
 
                 /* dot files are by default visible */
-                if (invisible_dots(vol, cfrombstring(dir->d_u_name))) {
+                if (invisible_dots(vol, cfrombstr(dir->d_u_name))) {
                     ashort = htons(FINDERINFO_INVISIBLE);
                     memcpy(data + FINDERINFO_FRFLAGOFF, &ashort, sizeof(ashort));
                 }
@@ -1428,6 +1561,8 @@ int getdirparams(const struct vol *vol,
         case DIRPBIT_DID :
             memcpy( data, &dir->d_did, sizeof( aint ));
             data += sizeof( aint );
+            LOG(log_debug, logtype_afpd, "metadata('%s'):            DID: %u",
+                s_path->u_name, ntohl(dir->d_did));
             break;
 
         case DIRPBIT_OFFCNT :
@@ -1525,12 +1660,12 @@ int getdirparams(const struct vol *vol,
     if ( l_nameoff ) {
         ashort = htons( data - buf );
         memcpy( l_nameoff, &ashort, sizeof( ashort ));
-        data = set_name(vol, data, pdid, cfrombstring(dir->d_m_name), dir->d_did, 0);
+        data = set_name(vol, data, pdid, cfrombstr(dir->d_m_name), dir->d_did, 0);
     }
     if ( utf_nameoff ) {
         ashort = htons( data - buf );
         memcpy( utf_nameoff, &ashort, sizeof( ashort ));
-        data = set_name(vol, data, pdid, cfrombstring(dir->d_m_name), dir->d_did, utf8);
+        data = set_name(vol, data, pdid, cfrombstr(dir->d_m_name), dir->d_did, utf8);
     }
     if ( isad ) {
         ad_close_metadata( &ad );
@@ -1744,7 +1879,7 @@ int setdirparams(struct vol *vol, struct path *path, u_int16_t d_bitmap, char *b
     }
     ad_init(&ad, vol->v_adouble, vol->v_ad_options);
 
-    if (ad_open_metadata( upath, ADFLAGS_DIR, O_CREAT, &ad) < 0) {
+    if (ad_open(&ad, upath, ADFLAGS_HF | ADFLAGS_DIR, O_CREAT, 0777) != 0) {
         /*
          * Check to see what we're trying to set.  If it's anything
          * but ACCESS, UID, or GID, give an error.  If it's any of those
@@ -1766,8 +1901,8 @@ int setdirparams(struct vol *vol, struct path *path, u_int16_t d_bitmap, char *b
          * Check to see if a create was necessary. If it was, we'll want
          * to set our name, etc.
          */
-        if ( (ad_get_HF_flags( &ad ) & O_CREAT)) {
-            ad_setname(&ad, cfrombstring(curdir->d_m_name));
+        if ( (ad_get_MD_flags( &ad ) & O_CREAT)) {
+            ad_setname(&ad, cfrombstr(curdir->d_m_name));
         }
     }
 
@@ -1932,10 +2067,9 @@ setdirparam_done:
         ad_close_metadata( &ad);
     }
 
-#if 0
     if (change_parent_mdate && dir->d_did != DIRDID_ROOT
         && gettimeofday(&tv, NULL) == 0) {
-        if (!movecwd(vol, dir->d_parent)) {
+        if (movecwd(vol, dirlookup(vol, dir->d_pdid)) == 0) {
             newdate = AD_DATE_FROM_UNIX(tv.tv_sec);
             /* be careful with bitmap because now dir is null */
             bitmap = 1<<DIRPBIT_MDATE;
@@ -1943,7 +2077,7 @@ setdirparam_done:
             /* should we reset curdir ?*/
         }
     }
-#endif
+
     return err;
 }
 
@@ -2030,7 +2164,7 @@ int afp_syncdir(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_,
 
         if ( fsync(dfd) < 0 )
             LOG(log_error, logtype_afpd, "afp_syncdir(%s): %s",
-                vol->ad_path(cfrombstring(dir->d_u_name), ADFLAGS_DIR), strerror(errno) );
+                vol->ad_path(cfrombstr(dir->d_u_name), ADFLAGS_DIR), strerror(errno) );
         close(dfd);
     }
 
@@ -2079,7 +2213,7 @@ int afp_createdir(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_
 
     upath = s_path->u_name;
 
-    if (AFP_OK != (err = netatalk_mkdir( upath))) {
+    if (AFP_OK != (err = netatalk_mkdir(vol, upath))) {
         return err;
     }
 
@@ -2098,7 +2232,7 @@ int afp_createdir(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_
     }
 
     ad_init(&ad, vol->v_adouble, vol->v_ad_options);
-    if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad ) < 0)  {
+    if (ad_open(&ad, ".", ADFLAGS_HF | ADFLAGS_DIR, O_CREAT, 0777) < 0)  {
         if (vol_noadouble(vol))
             goto createdir_done;
         return( AFPERR_ACCESS );
@@ -2110,11 +2244,6 @@ int afp_createdir(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_
     ad_close_metadata( &ad);
 
 createdir_done:
-#ifdef HAVE_NFSv4_ACLS
-    /* FIXME: are we really inside the created dir? */
-    addir_inherit_acl(vol);
-#endif
-
     memcpy( rbuf, &dir->d_did, sizeof( u_int32_t ));
     *rbuflen = sizeof( u_int32_t );
     setvoltime(obj, vol );
@@ -2125,8 +2254,12 @@ createdir_done:
  * dst       new unix filename (not a pathname)
  * newname   new mac name
  * newparent curdir
+ * dirfd     -1 means ignore dirfd (or use AT_FDCWD), otherwise src is relative to dirfd
  */
-int renamedir(const struct vol *vol, char *src, char *dst,
+int renamedir(const struct vol *vol,
+              int dirfd,
+              char *src,
+              char *dst,
               struct dir *dir,
               struct dir *newparent,
               char *newname)
@@ -2135,7 +2268,7 @@ int renamedir(const struct vol *vol, char *src, char *dst,
     int             err;
 
     /* existence check moved to afp_moveandrename */
-    if ( unix_rename( src, dst ) < 0 ) {
+    if ( unix_rename(dirfd, src, -1, dst ) < 0 ) {
         switch ( errno ) {
         case ENOENT :
             return( AFPERR_NOOBJ );
@@ -2149,11 +2282,11 @@ int renamedir(const struct vol *vol, char *src, char *dst,
         case EXDEV:
             /* this needs to copy and delete. bleah. that means we have
              * to deal with entire directory hierarchies. */
-            if ((err = copydir(vol, src, dst)) < 0) {
-                deletedir(dst);
+            if ((err = copydir(vol, dirfd, src, dst)) < 0) {
+                deletedir(-1, dst);
                 return err;
             }
-            if ((err = deletedir(src)) < 0)
+            if ((err = deletedir(dirfd, src)) < 0)
                 return err;
             break;
         default :
@@ -2161,21 +2294,16 @@ int renamedir(const struct vol *vol, char *src, char *dst,
         }
     }
 
-    vol->vfs->vfs_renamedir(vol, src, dst);
+    vol->vfs->vfs_renamedir(vol, dirfd, src, dst);
 
     ad_init(&ad, vol->v_adouble, vol->v_ad_options);
 
-    if (!ad_open_metadata( dst, ADFLAGS_DIR, 0, &ad)) {
+    if (ad_open(&ad, dst, ADFLAGS_HF | ADFLAGS_DIR) == 0) {
         ad_setname(&ad, newname);
         ad_flush( &ad);
         ad_close_metadata( &ad);
     }
 
-    if (dir_modify(vol, dir, curdir->d_did, 0, newname, dst, curdir->d_fullpath) != 0) {
-        LOG(log_error, logtype_afpd, "renamedir: fatal error from dir_modify: %s -> %s", src, dst);
-        return AFPERR_MISC;
-    }
-
     return( AFP_OK );
 }
 
@@ -2184,13 +2312,13 @@ int deletecurdir(struct vol *vol)
 {
     struct dirent *de;
     struct stat st;
-    struct dir  *fdir;
+    struct dir  *fdir, *pdir;
     DIR *dp;
     struct adouble  ad;
     u_int16_t       ashort;
     int err;
 
-    if ( dirlookup(vol, curdir->d_pdid) == NULL ) {
+    if ((pdir = dirlookup(vol, curdir->d_pdid)) == NULL) {
         return( AFPERR_ACCESS );
     }
 
@@ -2201,13 +2329,15 @@ int deletecurdir(struct vol *vol)
     if ( ad_metadata( ".", ADFLAGS_DIR, &ad) == 0 ) {
 
         ad_getattr(&ad, &ashort);
-        ad_close( &ad, ADFLAGS_HF );
+        ad_close_metadata(&ad);
         if ((ashort & htons(ATTRBIT_NODELETE))) {
             return  AFPERR_OLOCK;
         }
     }
     err = vol->vfs->vfs_deletecurdir(vol);
     if (err) {
+        LOG(log_error, logtype_afpd, "deletecurdir: error deleting .AppleDouble in \"%s\"",
+            curdir->d_fullpath);
         return err;
     }
 
@@ -2220,6 +2350,8 @@ int deletecurdir(struct vol *vol)
 
             /* bail if it's not a symlink */
             if ((lstat(de->d_name, &st) == 0) && !S_ISLNK(st.st_mode)) {
+                LOG(log_error, logtype_afpd, "deletecurdir(\"%s\"): not empty",
+                    curdir->d_fullpath);
                 closedir(dp);
                 return AFPERR_DIRNEMPT;
             }
@@ -2231,16 +2363,23 @@ int deletecurdir(struct vol *vol)
         }
     }
 
-    if ( movecwd(vol, dirlookup(vol, curdir->d_pdid)) < 0 ) {
+    if (movecwd(vol, pdir) < 0) {
         err = afp_errno;
         goto delete_done;
     }
 
-    err = netatalk_rmdir_all_errors(cfrombstring(fdir->d_u_name));
+    LOG(log_debug, logtype_afpd, "deletecurdir: moved to \"%s\"",
+        cfrombstr(curdir->d_fullpath));
+
+    err = netatalk_rmdir_all_errors(-1, cfrombstr(fdir->d_u_name));
     if ( err ==  AFP_OK || err == AFPERR_NOOBJ) {
         cnid_delete(vol->v_cdb, fdir->d_did);
         dir_remove( vol, fdir );
+    } else {
+        LOG(log_error, logtype_afpd, "deletecurdir(\"%s\"): netatalk_rmdir_all_errors error",
+            curdir->d_fullpath);
     }
+
 delete_done:
     if (dp) {
         /* inode is used as key for cnid.
@@ -2265,7 +2404,6 @@ int afp_mapid(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *r
     sfunc = (unsigned char) *ibuf++;
     *rbuflen = 0;
 
-
     if (sfunc >= 3 && sfunc <= 6) {
         if (afp_version < 30) {
             return( AFPERR_PARAM );
@@ -2304,17 +2442,18 @@ int afp_mapid(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *r
             name = NULL;
         }
         break;
-#ifdef HAVE_NFSv4_ACLS
+
     case 5 : /* UUID -> username */
     case 6 : /* UUID -> groupname */
         if ((afp_version < 32) || !(obj->options.flags & OPTION_UUID ))
             return AFPERR_PARAM;
         LOG(log_debug, logtype_afpd, "afp_mapid: valid UUID request");
         uuidtype_t type;
-        len = getnamefromuuid( ibuf, &name, &type);
+        len = getnamefromuuid((unsigned char*) ibuf, &name, &type);
         if (len != 0)       /* its a error code, not len */
             return AFPERR_NOITEM;
-        if (type == UUID_USER) {
+        switch (type) {
+        case UUID_USER:
             if (( pw = getpwnam( name )) == NULL )
                 return( AFPERR_NOITEM );
             LOG(log_debug, logtype_afpd, "afp_mapid: name:%s -> uid:%d", name, pw->pw_uid);
@@ -2325,7 +2464,8 @@ int afp_mapid(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *r
             memcpy( rbuf, &id, sizeof( id ));
             rbuf += sizeof( id );
             *rbuflen = 2 * sizeof( id );
-        } else {        /* type == UUID_GROUP */
+            break;
+        case UUID_GROUP:
             if (( gr = getgrnam( name )) == NULL )
                 return( AFPERR_NOITEM );
             LOG(log_debug, logtype_afpd, "afp_mapid: group:%s -> gid:%d", name, gr->gr_gid);
@@ -2336,9 +2476,15 @@ int afp_mapid(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *r
             memcpy( rbuf, &id, sizeof( id ));
             rbuf += sizeof( id );
             *rbuflen = 2 * sizeof( id );
+            break;
+        case UUID_LOCAL:
+            free(name);
+            return (AFPERR_NOITEM);
+        default:
+            return AFPERR_MISC;
         }
         break;
-#endif
+
     default :
         return( AFPERR_PARAM );
     }
@@ -2392,7 +2538,6 @@ int afp_mapname(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, siz
     case 4 :
         len = (unsigned char) *ibuf++;
         break;
-#ifdef HAVE_NFSv4_ACLS
     case 5 : /* username -> UUID  */
     case 6 : /* groupname -> UUID */
         if ((afp_version < 32) || !(obj->options.flags & OPTION_UUID ))
@@ -2401,7 +2546,6 @@ int afp_mapname(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, siz
         len = ntohs(ulen);
         ibuf += 2;
         break;
-#endif
     default :
         return( AFPERR_PARAM );
     }
@@ -2435,20 +2579,18 @@ int afp_mapname(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, siz
             memcpy( rbuf, &id, sizeof( id ));
             *rbuflen = sizeof( id );
             break;
-#ifdef HAVE_NFSv4_ACLS
         case 5 :        /* username -> UUID */
             LOG(log_debug, logtype_afpd, "afp_mapname: name: %s",ibuf);
-            if (0 != getuuidfromname(ibuf, UUID_USER, rbuf))
+            if (0 != getuuidfromname(ibuf, UUID_USER, (unsigned char *)rbuf))
                 return AFPERR_NOITEM;
             *rbuflen = UUID_BINSIZE;
             break;
         case 6 :        /* groupname -> UUID */
             LOG(log_debug, logtype_afpd, "afp_mapname: name: %s",ibuf);
-            if (0 != getuuidfromname(ibuf, UUID_GROUP, rbuf))
+            if (0 != getuuidfromname(ibuf, UUID_GROUP, (unsigned char *)rbuf))
                 return AFPERR_NOITEM;
             *rbuflen = UUID_BINSIZE;
             break;
-#endif
         }
     }
     return( AFP_OK );