]> arthur.barton.de Git - netatalk.git/commitdiff
Merge from branch-2-1: umask on upriv volumes
authorFrank Lahm <franklahm@googlemail.com>
Wed, 9 Jun 2010 13:59:30 +0000 (15:59 +0200)
committerFrank Lahm <franklahm@googlemail.com>
Wed, 9 Jun 2010 13:59:30 +0000 (15:59 +0200)
1  2 
etc/afpd/afp_dsi.c
etc/afpd/directory.c
etc/afpd/volume.c

Simple merge
index 467bc62d89e936c2cf69988db42f66b27ea3fe36,0e13a162805ed81f3c3fbb0c55f94aedf16cf38f..03bb0146b123ba5ee3b26361c4bdc18679abb144
  extern void addir_inherit_acl(const struct vol *vol);
  #endif
  
 -/* 
 - * Directory caches
 - * ================
 - *
 - * There are currently two cache structures where afpd caches directory information
 - * a) a DID/dirname cache in a hashtable 
 - * b) a (red-black) tree with CNIDs as key
 - *
 - * a) is for searching by DID/dirname
 - * b) is for searching by CNID
 - *
 - * Through additional parent, child, previous and next pointers, b) is also used to
 - * represent the on-disk layout of the filesystem. parent and child point to parent
 - * and child directory respectively, linking 2 or more subdirectories in one
 - * directory with previous and next pointers.
 - *
 - * Usage examples, highlighting the main functions:
 - * 
 - * a) is eg used in enumerate():
 - * if IS_DIR
 - *     dir = dirsearch_byname() // search in cache
 - *     if (dir == NULL)         // not found
 - *         dir = adddir()       // add to cache
 - *      getdirparams()
 - *
 - * b) is eg used in afp_getfildirparams()
 - * dirlookup()             // wrapper for cache and db search
 - *   => dir = dirsearch()  // search in cache
 - *      if (dir)           // found
 - *          return
 - *      else               // not found,
 - *          cnid_resolve() // resolve with CNID database
 - *      cname()            // add to cache
 +/*
 + * FIXMEs, loose ends after the dircache rewrite:
 + * o merge dircache_search_by_name and dir_add ??
 + * o case-insensitivity is gone from cname
   */
  
 -struct dir  *curdir;
 +
 +/*******************************************************************************************
 + * Globals
 + ******************************************************************************************/
 +
  int         afp_errno;
 +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 */
 +    0, 0, 0, 0                       /* pdid, did, offcnt, d_vid */
 +};
 +struct dir  *curdir = &rootParent;
 +struct path Cur_Path = {
 +    0,
 +    "",  /* mac name */
 +    ".", /* unix name */
 +    0,   /* id */
 +    NULL,/* struct dir * */
 +    0,   /* stat is not set */
 +    0,   /* errno */
 +    {0} /* struct stat */
 +};
  
 -#define SENTINEL (&sentinel)
 -static struct dir sentinel = { SENTINEL, SENTINEL, NULL, /* left, right, back */
 -                               DIRTREE_COLOR_BLACK,      /* color */
 -                               NULL, NULL,               /* parent, child */
 -                               NULL, NULL,               /* previous, next */
 -                               NULL, 0, 0,               /* oforks, did, flags */
 -                               0, 0,                     /* ctime, offcnt */
 -                               NULL, NULL, NULL};        /* mname, uname, ucs2-name */
 -static struct dir rootpar  = { SENTINEL, SENTINEL, NULL,
 -                               0,
 -                               NULL, NULL,
 -                               NULL, NULL,
 -                               NULL, 0, 0,
 -                               0, 0,
 -                               NULL, NULL, NULL};
 -
 -/* (from IM: Toolbox Essentials)
 - * dirFinderInfo (DInfo) fields:
 - * field        bytes
 - * frRect       8    folder's window rectangle
 - * frFlags      2    flags
 - * frLocation   4    folder's location in window
 - * frView       2    folder's view (default == closedView (256))
 - *
 - * extended dirFinderInfo (DXInfo) fields:
 - * frScroll     4    scroll position
 - * frOpenChain: 4    directory ID chain of open folders
 - * frScript:    1    script flag and code
 - * frXFlags:    1    reserved
 - * frComment:   2    comment ID
 - * frPutAway:   4    home directory ID
 - */
  
 -static struct dir *
 -vol_tree_root(const struct vol *vol, u_int32_t did)
 -{
 -    struct dir *dir;
 +/*******************************************************************************************
 + * Locals
 + ******************************************************************************************/
  
 -    if (vol->v_curdir && vol->v_curdir->d_did == did) {
 -        dir = vol->v_curdir;
 -    }
 -    else {
 -        dir = vol->v_root;
 -    }
 -    return dir;
 -}
  
 -/*
 - * redid did assignment for directories. now we use red-black trees.
 - * how exciting.
 - */
 -struct dir *
 -dirsearch(const struct vol *vol, u_int32_t did)
 +/* -------------------------
 +   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) {
 -    struct dir  *dir;
++    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);
 -    /* check for 0 did */
 -    if (!did) {
 -        afp_errno = AFPERR_PARAM;
 -        return NULL;
 -    }
 -    if ( did == DIRDID_ROOT_PARENT ) {
 -        if (!rootpar.d_did)
 -            rootpar.d_did = DIRDID_ROOT_PARENT;
 -        rootpar.d_child = vol->v_dir;
 -        return( &rootpar );
++        ret = mkdir(name, mode);
++    } else {
++        ret = ad_mkdir(name, DIRBITS | 0777);
+     }
 -    dir = vol_tree_root(vol, did);
 -
 -    afp_errno = AFPERR_NOOBJ;
 -    while ( dir != SENTINEL ) {
 -        if (dir->d_did == did)
 -            return dir->d_m_name ? dir : NULL;
 -        dir = (dir->d_did > did) ? dir->d_left : dir->d_right;
++    if (ret < 0) {
 +        switch ( errno ) {
 +        case ENOENT :
 +            return( AFPERR_NOOBJ );
 +        case EROFS :
 +            return( AFPERR_VLOCK );
 +        case EPERM:
 +        case EACCES :
 +            return( AFPERR_ACCESS );
 +        case EEXIST :
 +            return( AFPERR_EXIST );
 +        case ENOSPC :
 +        case EDQUOT :
 +            return( AFPERR_DFULL );
 +        default :
 +            return( AFPERR_PARAM );
 +        }
      }
 -    return NULL;
 +    return AFP_OK;
  }
  
  /* ------------------- */
 -int get_afp_errno(const int param)
 +static int deletedir(int dirfd, char *dir)
  {
 -    if (afp_errno != AFPERR_DID1)
 -        return afp_errno;
 -    return param;
 -}
 +    char path[MAXPATHLEN + 1];
 +    DIR *dp;
 +    struct dirent   *de;
 +    struct stat st;
 +    size_t len;
 +    int err = AFP_OK;
 +    size_t remain;
  
 -/* ------------------- */
 -struct dir *
 -dirsearch_byname( const struct vol *vol, struct dir *cdir, char *name)
 -{
 -    struct dir *dir = NULL;
 +    if ((len = strlen(dir)) +2 > sizeof(path))
 +        return AFPERR_PARAM;
 +
 +    /* already gone */
 +    if ((dp = opendirat(dirfd, dir)) == NULL)
 +        return AFP_OK;
  
 -    if ((cdir->d_did != DIRDID_ROOT_PARENT) && (cdir->d_child)) {
 -        struct dir key;
 -        hnode_t *hn;
 +    strcpy(path, dir);
 +    strcat(path, "/");
 +    len++;
 +    remain = sizeof(path) -len -1;
 +    while ((de = readdir(dp)) && err == AFP_OK) {
 +        /* skip this and previous directory */
 +        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
 +            continue;
  
 -        key.d_parent = cdir;
 -        key.d_u_name = name;
 -        hn = hash_lookup(vol->v_hash, &key);
 -        if (hn) {
 -            dir = hnode_get(hn);
 +        if (strlen(de->d_name) > remain) {
 +            err = AFPERR_PARAM;
 +            break;
 +        }
 +        strcpy(path + len, de->d_name);
 +        if (lstatat(dirfd, path, &st)) {
 +            continue;
 +        }
 +        if (S_ISDIR(st.st_mode)) {
 +            err = deletedir(dirfd, path);
 +        } else {
 +            err = netatalk_unlinkat(dirfd, path);
          }
      }
 -    return dir;
 +    closedir(dp);
 +
 +    /* okay. the directory is empty. delete it. note: we already got rid
 +       of .AppleDouble.  */
 +    if (err == AFP_OK) {
 +        err = netatalk_rmdir(dirfd, dir);
 +    }
 +    return err;
  }
  
 -/* -----------------------------------------
 - * if did is not in the cache resolve it with cnid
 - *
 - * FIXME
 - * OSX call it with bogus id, ie file ID not folder ID,
 - * and we are really bad in this case.
 - */
 -struct dir *
 -dirlookup( struct vol *vol, u_int32_t did)
 +/* do a recursive copy. */
 +static int copydir(const struct vol *vol, int dirfd, char *src, char *dst)
  {
 -    struct dir   *ret;
 -    char     *upath;
 -    cnid_t       id, cnid;
 -    static char  path[MAXPATHLEN + 1];
 -    size_t len,  pathlen;
 -    char         *ptr;
 -    static char  buffer[12 + MAXPATHLEN + 1];
 -    int          buflen = 12 + MAXPATHLEN + 1;
 -    char         *mpath;
 -    int          utf8;
 -    size_t       maxpath;
 +    char spath[MAXPATHLEN + 1], dpath[MAXPATHLEN + 1];
 +    DIR *dp;
 +    struct dirent   *de;
 +    struct stat st;
 +    struct utimbuf      ut;
 +    size_t slen, dlen;
 +    size_t srem, drem;
 +    int err;
  
 -    ret = dirsearch(vol, did);
 -    if (ret != NULL || afp_errno == AFPERR_PARAM)
 -        return ret;
 +    /* doesn't exist or the path is too long. */
 +    if (((slen = strlen(src)) > sizeof(spath) - 2) ||
 +        ((dlen = strlen(dst)) > sizeof(dpath) - 2) ||
 +        ((dp = opendirat(dirfd, src)) == NULL))
 +        return AFPERR_PARAM;
  
 -    utf8 = utf8_encoding();
 -    maxpath = (utf8)?MAXPATHLEN -7:255;
 -    id = did;
 -    if (NULL == (upath = cnid_resolve(vol->v_cdb, &id, buffer, buflen)) ) {
 -        afp_errno = AFPERR_NOOBJ;
 -        return NULL;
 -    }
 -    ptr = path + MAXPATHLEN;
 -    if (NULL == ( mpath = utompath(vol, upath, did, utf8) ) ) {
 -        afp_errno = AFPERR_NOOBJ;
 -        return NULL;
 +    /* 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;
      }
 -    len = strlen(mpath);
 -    pathlen = len;          /* no 0 in the last part */
 -    len++;
 -    strcpy(ptr - len, mpath);
 -    ptr -= len;
 -    while (1) {
 -        ret = dirsearch(vol,id);
 -        if (ret != NULL) {
 -            break;
 -        }
 -        cnid = id;
 -        if ( NULL == (upath = cnid_resolve(vol->v_cdb, &id, buffer, buflen))
 -             ||
 -             NULL == (mpath = utompath(vol, upath, cnid, utf8))
 -            ) {
 -            afp_errno = AFPERR_NOOBJ;
 -            return NULL;
 -        }
  
 -        len = strlen(mpath) + 1;
 -        pathlen += len;
 -        if (pathlen > maxpath) {
 -            afp_errno = AFPERR_PARAM;
 -            return NULL;
 +    /* set things up to copy */
 +    strcpy(spath, src);
 +    strcat(spath, "/");
 +    slen++;
 +    srem = sizeof(spath) - slen -1;
 +
 +    strcpy(dpath, dst);
 +    strcat(dpath, "/");
 +    dlen++;
 +    drem = sizeof(dpath) - dlen -1;
 +
 +    err = AFP_OK;
 +    while ((de = readdir(dp))) {
 +        /* skip this and previous directory */
 +        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
 +            continue;
 +
 +        if (strlen(de->d_name) > srem) {
 +            err = AFPERR_PARAM;
 +            break;
          }
 -        strcpy(ptr - len, mpath);
 -        ptr -= len;
 -    }
 +        strcpy(spath + slen, de->d_name);
  
 -    /* fill the cache, another place where we know about the path type */
 -    if (utf8) {
 -        u_int16_t temp16;
 -        u_int32_t temp;
 +        if (lstatat(dirfd, spath, &st) == 0) {
 +            if (strlen(de->d_name) > drem) {
 +                err = AFPERR_PARAM;
 +                break;
 +            }
 +            strcpy(dpath + dlen, de->d_name);
  
 -        ptr -= 2;
 -        temp16 = htons(pathlen);
 -        memcpy(ptr, &temp16, sizeof(temp16));
 +            if (S_ISDIR(st.st_mode)) {
 +                if (AFP_OK != (err = copydir(vol, dirfd, spath, dpath)))
 +                    goto copydir_done;
 +            } else if (AFP_OK != (err = copyfile(vol, vol, dirfd, spath, dpath, NULL, NULL))) {
 +                goto copydir_done;
  
 -        temp = htonl(kTextEncodingUTF8);
 -        ptr -= 4;
 -        memcpy(ptr, &temp, sizeof(temp));
 -        ptr--;
 -        *ptr = 3;
 +            } else {
 +                /* keep the same time stamp. */
 +                ut.actime = ut.modtime = st.st_mtime;
 +                utime(dpath, &ut);
 +            }
 +        }
      }
 -    else {
 -        ptr--;
 -        *ptr = (unsigned char)pathlen;
 -        ptr--;
 -        *ptr = 2;
 +
 +    /* keep the same time stamp. */
 +    if (lstatat(dirfd, src, &st) == 0) {
 +        ut.actime = ut.modtime = st.st_mtime;
 +        utime(dst, &ut);
      }
 -    /* cname is not efficient */
 -    if (cname( vol, ret, &ptr ) == NULL )
 -        return NULL;
  
 -    return dirsearch(vol, did);
 +copydir_done:
 +    closedir(dp);
 +    return err;
  }
  
 -/* child addition/removal */
 -static void dirchildadd(const struct vol *vol, struct dir *a, struct dir *b)
 +/* ---------------------
 + * is our cached offspring count valid?
 + */
 +static int diroffcnt(struct dir *dir, struct stat *st)
  {
 -    if (!a->d_child)
 -        a->d_child = b;
 -    else {
 -        b->d_next = a->d_child;
 -        b->d_prev = b->d_next->d_prev;
 -        b->d_next->d_prev = b;
 -        b->d_prev->d_next = b;
 -    }
 -    if (!hash_alloc_insert(vol->v_hash, b, b)) {
 -        LOG(log_error, logtype_afpd, "dirchildadd: can't hash %s", b->d_u_name);
 -    }
 +    return st->st_ctime == dir->ctime;
  }
  
 -static void dirchildremove(struct dir *a,struct dir *b)
 +/* --------------------- */
 +static int invisible_dots(const struct vol *vol, const char *name)
  {
 -    if (a->d_child == b)
 -        a->d_child = (b == b->d_next) ? NULL : b->d_next;
 -    b->d_next->d_prev = b->d_prev;
 -    b->d_prev->d_next = b->d_next;
 -    b->d_next = b->d_prev = b;
 +    return vol_inv_dots(vol) && *name  == '.' && strcmp(name, ".") && strcmp(name, "..");
  }
  
 -/* --------------------------- */
 -/* rotate the tree to the left */
 -static void dir_leftrotate(struct vol *vol, struct dir *dir)
 +/* ------------------ */
 +static int set_dir_errors(struct path *path, const char *where, int err)
  {
 -    struct dir *right = dir->d_right;
 -
 -    /* whee. move the right's left tree into dir's right tree */
 -    dir->d_right = right->d_left;
 -    if (right->d_left != SENTINEL)
 -        right->d_left->d_back = dir;
 -
 -    if (right != SENTINEL) {
 -        right->d_back = dir->d_back;
 -        right->d_left = dir;
 +    switch ( err ) {
 +    case EPERM :
 +    case EACCES :
 +        return AFPERR_ACCESS;
 +    case EROFS :
 +        return AFPERR_VLOCK;
      }
 +    LOG(log_error, logtype_afpd, "setdirparam(%s): %s: %s", fullpathname(path->u_name), where, strerror(err) );
 +    return AFPERR_PARAM;
 +}
  
 -    if (!dir->d_back) /* no parent. move the right tree to the top. */
 -        vol->v_root = right;
 -    else if (dir == dir->d_back->d_left) /* we were on the left */
 -        dir->d_back->d_left = right;
 -    else
 -        dir->d_back->d_right = right; /* we were on the right */
 +/*!
 + * @brief Convert name in client encoding to server encoding
 + *
 + * Convert ret->m_name to ret->u_name from client encoding to server encoding.
 + * This only gets called from cname().
 + *
 + * @returns 0 on success, -1 on error
 + *
 + * @note If the passed ret->m_name is mangled, we'll demangle it
 + */
 +static int cname_mtouname(const struct vol *vol, const struct dir *dir, struct path *ret, int toUTF8)
 +{
 +    static char temp[ MAXPATHLEN + 1];
 +    char *t;
 +    cnid_t fileid;
 +
 +    if (afp_version >= 30) {
 +        if (toUTF8) {
 +            if (dir->d_did == DIRDID_ROOT_PARENT) {
 +                /*
 +                 * With uft8 volume name is utf8-mac, but requested path may be a mangled longname. See #2611981.
 +                 * So we compare it with the longname from the current volume and if they match
 +                 * we overwrite the requested path with the utf8 volume name so that the following
 +                 * strcmp can match.
 +                 */
 +                ucs2_to_charset(vol->v_maccharset, vol->v_macname, temp, AFPVOL_MACNAMELEN + 1);
 +                if (strcasecmp(ret->m_name, temp) == 0)
 +                    ucs2_to_charset(CH_UTF8_MAC, vol->v_u8mname, ret->m_name, AFPVOL_U8MNAMELEN);
 +            } else {
 +                /* toUTF8 */
 +                if (mtoUTF8(vol, ret->m_name, strlen(ret->m_name), temp, MAXPATHLEN) == (size_t)-1) {
 +                    afp_errno = AFPERR_PARAM;
 +                    return -1;
 +                }
 +                strcpy(ret->m_name, temp);
 +            }
 +        }
  
 -    /* re-insert dir on the left tree */
 -    if (dir != SENTINEL)
 -        dir->d_back = right;
 -}
 +        /* check for OS X mangled filename :( */
 +        t = demangle_osx(vol, ret->m_name, dir->d_did, &fileid);
 +        LOG(log_maxdebug, logtype_afpd, "cname_mtouname('%s',did:%u) {demangled:'%s', fileid:%u}",
 +            ret->m_name, ntohl(dir->d_did), t, ntohl(fileid));
 +
 +        if (t != ret->m_name) {
 +            ret->u_name = t;
 +            /* duplicate work but we can't reuse all convert_char we did in demangle_osx
 +             * flags weren't the same
 +             */
 +            if ( (t = utompath(vol, ret->u_name, fileid, utf8_encoding())) ) {
 +                /* at last got our view of mac name */
 +                strcpy(ret->m_name, t);
 +            }
 +        }
 +    } /* afp_version >= 30 */
  
 +    /* If we haven't got it by now, get it */
 +    if (ret->u_name == NULL) {
 +        if ((ret->u_name = mtoupath(vol, ret->m_name, dir->d_did, utf8_encoding())) == NULL) {
 +            afp_errno = AFPERR_PARAM;
 +            return -1;
 +        }
 +    }
  
 +    return 0;
 +}
  
 -/* rotate the tree to the right */
 -static void dir_rightrotate(struct vol *vol, struct dir *dir)
 +/*!
 + * @brief Build struct path from struct dir
 + *
 + * 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)
  {
 -    struct dir *left = dir->d_left;
 +    if (dir->d_did == DIRDID_ROOT_PARENT || dir->d_did == DIRDID_ROOT)
 +        return NULL;
  
 -    /* whee. move the left's right tree into dir's left tree */
 -    dir->d_left = left->d_right;
 -    if (left->d_right != SENTINEL)
 -        left->d_right->d_back = dir;
 +    switch (afp_errno) {
  
 -    if (left != SENTINEL) {
 -        left->d_back = dir->d_back;
 -        left->d_right = dir;
 -    }
 +    case AFPERR_ACCESS:
 +        if (movecwd( vol, dirlookup(vol, dir->d_pdid)) < 0 ) /* 1 */
 +            return NULL;
  
 -    if (!dir->d_back) /* no parent. move the left tree to the top. */
 -        vol->v_root = left;
 -    else if (dir == dir->d_back->d_right) /* we were on the right */
 -        dir->d_back->d_right = left;
 -    else
 -        dir->d_back->d_left = left; /* we were on the left */
 +        memcpy(ret->m_name, cfrombstring(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);
 +        }
  
 -    /* re-insert dir on the right tree */
 -    if (dir != SENTINEL)
 -        dir->d_back = left;
 -}
 +        ret->d_dir = dir;
  
 -#if 0
 -/* recolor after a removal */
 -static struct dir *dir_rmrecolor(struct vol *vol, struct dir *dir)
 -{
 -    struct dir *leaf;
 -
 -    while ((dir != vol->v_root) && (dir->d_color == DIRTREE_COLOR_BLACK)) {
 -        /* are we on the left tree? */
 -        if (dir == dir->d_back->d_left) {
 -            leaf = dir->d_back->d_right; /* get right side */
 -            if (leaf->d_color == DIRTREE_COLOR_RED) {
 -                /* we're red. we need to change to black. */
 -                leaf->d_color = DIRTREE_COLOR_BLACK;
 -                dir->d_back->d_color = DIRTREE_COLOR_RED;
 -                dir_leftrotate(vol, dir->d_back);
 -                leaf = dir->d_back->d_right;
 -            }
 +        LOG(log_debug, logtype_afpd, "cname('%s') {path-from-dir: AFPERR_ACCESS. curdir:'%s', path:'%s'}",
 +            cfrombstring(dir->d_fullpath),
 +            cfrombstring(curdir->d_fullpath),
 +            ret->u_name);
  
 -            /* right leaf has black end nodes */
 -            if ((leaf->d_left->d_color == DIRTREE_COLOR_BLACK) &&
 -                (leaf->d_right->d_color = DIRTREE_COLOR_BLACK)) {
 -                leaf->d_color = DIRTREE_COLOR_RED; /* recolor leaf as red */
 -                dir = dir->d_back; /* ascend */
 -            } else {
 -                if (leaf->d_right->d_color == DIRTREE_COLOR_BLACK) {
 -                    leaf->d_left->d_color = DIRTREE_COLOR_BLACK;
 -                    leaf->d_color = DIRTREE_COLOR_RED;
 -                    dir_rightrotate(vol, leaf);
 -                    leaf = dir->d_back->d_right;
 -                }
 -                leaf->d_color = dir->d_back->d_color;
 -                dir->d_back->d_color = DIRTREE_COLOR_BLACK;
 -                leaf->d_right->d_color = DIRTREE_COLOR_BLACK;
 -                dir_leftrotate(vol, dir->d_back);
 -                dir = vol->v_root;
 -            }
 -        } else { /* right tree */
 -            leaf = dir->d_back->d_left; /* left tree */
 -            if (leaf->d_color == DIRTREE_COLOR_RED) {
 -                leaf->d_color = DIRTREE_COLOR_BLACK;
 -                dir->d_back->d_color = DIRTREE_COLOR_RED;
 -                dir_rightrotate(vol, dir->d_back);
 -                leaf = dir->d_back->d_left;
 -            }
 +        return ret;
  
 -            /* left leaf has black end nodes */
 -            if ((leaf->d_right->d_color == DIRTREE_COLOR_BLACK) &&
 -                (leaf->d_left->d_color = DIRTREE_COLOR_BLACK)) {
 -                leaf->d_color = DIRTREE_COLOR_RED; /* recolor leaf as red */
 -                dir = dir->d_back; /* ascend */
 -            } else {
 -                if (leaf->d_left->d_color == DIRTREE_COLOR_BLACK) {
 -                    leaf->d_right->d_color = DIRTREE_COLOR_BLACK;
 -                    leaf->d_color = DIRTREE_COLOR_RED;
 -                    dir_leftrotate(vol, leaf);
 -                    leaf = dir->d_back->d_left;
 -                }
 -                leaf->d_color = dir->d_back->d_color;
 -                dir->d_back->d_color = DIRTREE_COLOR_BLACK;
 -                leaf->d_left->d_color = DIRTREE_COLOR_BLACK;
 -                dir_rightrotate(vol, dir->d_back);
 -                dir = vol->v_root;
 -            }
 +    case AFPERR_NOOBJ:
 +        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);
 +        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);
          }
 +
 +        ret->d_dir = NULL;      /* 4 */
 +        dir_remove(vol, dir);   /* 5 */
 +        return ret;
 +
 +    default:
 +        return NULL;
      }
 -    dir->d_color = DIRTREE_COLOR_BLACK;
  
 -    return dir;
 +    /* DEADC0DE: never get here */
 +    return NULL;
  }
 -#endif /* 0 */
  
 -/* --------------------- */
 -static void dir_hash_del(const struct vol *vol, struct dir *dir)
 -{
 -    hnode_t *hn;
  
 -    hn = hash_lookup(vol->v_hash, dir);
 -    if (!hn) {
 -        LOG(log_error, logtype_afpd, "dir_hash_del: %s not hashed", dir->d_u_name);
 -    }
 -    else {
 -        hash_delete(vol->v_hash, hn);
 -    }
 -}
 +/*********************************************************************************************
 + * Interface
 + ********************************************************************************************/
  
 -/* remove the node from the tree. this is just like insertion, but
 - * different. actually, it has to worry about a bunch of things that
 - * insertion doesn't care about. */
 +int get_afp_errno(const int param)
 +{
 +    if (afp_errno != AFPERR_DID1)
 +        return afp_errno;
 +    return param;
 +}
  
 -static void dir_remove( struct vol *vol, struct dir *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.
 + * 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.
 + * 6. Create the struct dir and populate it.
 + * 7. Add it to the cache.
 + *
 + * @param vol   (r) pointer to struct vol
 + * @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)
  {
 -#ifdef REMOVE_NODES
 -    struct ofork *of, *last;
 -    struct dir *node, *leaf;
 -#endif /* REMOVE_NODES */
 -
 -    if (!dir || (dir == SENTINEL))
 -        return;
 -
 -    /* i'm not sure if it really helps to delete stuff. */
 -    dir_hash_del(vol, dir);
 -    vol->v_curdir = NULL;
 -#ifndef REMOVE_NODES
 -    dirfreename(dir);
 -    dir->d_m_name = NULL;
 -    dir->d_u_name = NULL;
 -    dir->d_m_name_ucs2 = NULL;
 -#else /* ! REMOVE_NODES */
 -
 -    /* go searching for a node with at most one child */
 -    if ((dir->d_left == SENTINEL) || (dir->d_right == SENTINEL)) {
 -        node = dir;
 -    } else {
 -        node = dir->d_right;
 -        while (node->d_left != SENTINEL)
 -            node = node->d_left;
 +    static char  buffer[12 + MAXPATHLEN + 1];
 +    struct stat  st;
 +    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));
 +
 +    /* check for did 0, 1 and 2 */
 +    if (did == 0 || vol == NULL) { /* 1 */
 +        afp_errno = AFPERR_PARAM;
 +        return NULL;
 +    } else if (did == DIRDID_ROOT_PARENT) {
 +        rootParent.d_vid = vol->v_vid;
 +        return (&rootParent);
 +    } else if (did == DIRDID_ROOT) {
 +        return vol->v_root;
 +    }
 +
 +    /* Search the cache */
 +    if ((ret = dircache_search_by_did(vol, did)) != NULL) { /* 2 */
 +        if (lstat(cfrombstring(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;
 +                return NULL;
 +            default:
 +                return ret;
 +            }
 +            /* DEADC0DE */
 +            return NULL;
 +        }
 +        return ret;
      }
  
 -    /* get that child */
 -    leaf = (node->d_left != SENTINEL) ? node->d_left : node->d_right;
 +    utf8 = utf8_encoding();
 +    maxpath = (utf8) ? MAXPATHLEN - 7 : 255;
  
 -    /* detach node */
 -    leaf->d_back = node->d_back;
 -    if (!node->d_back) {
 -        vol->v_root = leaf;
 -    } else if (node == node->d_back->d_left) { /* left tree */
 -        node->d_back->d_left = leaf;
 -    } else {
 -        node->d_back->d_right = leaf;
 +    /* Get it from the database */
 +    cnid = did;
 +    if ( (upath = cnid_resolve(vol->v_cdb, &cnid, buffer, buflen)) == NULL 
 +         || (upath = strdup(upath)) == NULL) { /* 3 */
 +        afp_errno = AFPERR_NOOBJ;
 +        err = 1;
 +        goto exit;
      }
 +    pdid = cnid;
  
 -    /* we want to free node, but we also want to free the data in dir.
 -     * currently, that's d_name and the directory traversal bits.
 -     * we just copy the necessary bits and then fix up all the
 -     * various pointers to the directory. needless to say, there are
 -     * a bunch of places that store the directory struct. */
 -    if (node != dir) {
 -        struct dir save, *tmp;
 -
 -        memcpy(&save, dir, sizeof(save));
 -        memcpy(dir, node, sizeof(struct dir));
 +    /*
 +     * Recurse up the tree, terminates in dirlookup when either
 +     * - DIRDID_ROOT is hit
 +     * - a cached entry is found
 +     */
 +    if ((pdir = dirlookup(vol, pdid)) == NULL) {
 +        err = 1;
 +        goto exit;
 +    }
  
 -        /* restore the red-black bits */
 -        dir->d_left = save.d_left;
 -        dir->d_right = save.d_right;
 -        dir->d_back = save.d_back;
 -        dir->d_color = save.d_color;
 +    /* build the fullpath */
 +    if ((fullpath = bstrcpy(pdir->d_fullpath)) == NULL
 +        || bconchar(fullpath, '/') != BSTR_OK
 +        || bcatcstr(fullpath, upath) != BSTR_OK) {
 +        err = 1;
 +        goto exit;
 +    }
  
 -        if (node == vol->v_dir) {/* we may need to fix up this pointer */
 -            vol->v_dir = dir;
 -            rootpar.d_child = vol->v_dir;
 -        } else {
 -            /* if we aren't the root directory, we have parents and
 -             * siblings to worry about */
 -            if (dir->d_parent->d_child == node)
 -                dir->d_parent->d_child = dir;
 -            dir->d_next->d_prev = dir;
 -            dir->d_prev->d_next = dir;
 -        }
 +    /* stat it and check if it's a dir */
 +    LOG(log_debug, logtype_afpd, "dirlookup: {stating %s}", cfrombstring(fullpath));
  
 -        /* fix up children. */
 -        tmp = dir->d_child;
 -        while (tmp) {
 -            tmp->d_parent = dir;
 -            tmp = (tmp == dir->d_child->d_prev) ? NULL : tmp->d_next;
 +    if (stat(cfrombstring(fullpath), &st) != 0) { /* 5a */
 +        switch (errno) {
 +        case ENOENT:
 +            afp_errno = AFPERR_NOOBJ;
 +            err = 1;
 +            goto exit;
 +        case EPERM:
 +            afp_errno = AFPERR_ACCESS;
 +            err = 1;
 +            goto exit;
 +        default:
 +            afp_errno = AFPERR_MISC;
 +            err = 1;
 +            goto exit;
          }
 -
 -        if (node == curdir) /* another pointer to fixup */
 -            curdir = dir;
 -
 -        /* we also need to fix up oforks. bleah */
 -        if ((of = dir->d_ofork)) {
 -            last = of->of_d_prev;
 -            while (of) {
 -                of->of_dir = dir;
 -                of = (last == of) ? NULL : of->of_d_next;
 -            }
 +    } else {
 +        if ( ! S_ISDIR(st.st_mode)) { /* 5b */
 +            afp_errno = AFPERR_BADTYPE;
 +            err = 1;
 +            goto exit;
          }
 -
 -        /* set the node's d_name */
 -        node->d_m_name = save.d_m_name;
 -        node->d_u_name = save.d_u_name;
 -        node->d_m_name_ucs2 = save.d_m_name_ucs2;
      }
  
 -    if (node->d_color == DIRTREE_COLOR_BLACK)
 -        dir_rmrecolor(vol, leaf);
 +    /* Get macname from unix name */
 +    if ( (mpath = utompath(vol, upath, did, utf8)) == NULL ) {
 +        afp_errno = AFPERR_NOOBJ;
 +        err = 1;
 +        goto exit;
 +    }
  
 -    if (node->d_m_name_ucs2)
 -        free(node->d_u_name_ucs2);
 -    if (node->d_u_name != node->d_m_name) {
 -        free(node->d_u_name);
 +    /* Create struct dir */
 +    if ((ret = dir_new(mpath, upath, vol, pdid, did, fullpath)) == NULL) { /* 6 */
 +        LOG(log_error, logtype_afpd, "dirlookup(did: %u) {%s, %s}: %s", ntohl(did), mpath, upath, strerror(errno));
 +        err = 1;
 +        goto exit;
      }
 -    free(node->d_m_name);
 -    free(node);
 -#endif /* ! REMOVE_NODES */
 -}
  
 -/* ---------------------------------------
 - * remove the node and its childs from the tree
 - *
 - * FIXME what about opened forks with refs to it?
 - * it's an afp specs violation because you can't delete
 - * an opened forks. Now afpd doesn't care about forks opened by other
 - * process. It's fixable within afpd if fnctl_lock, doable with smb and
 - * next to impossible for nfs and local filesystem access.
 - */
 -static void dir_invalidate( struct vol *vol, struct dir *dir)
 -{
 -    if (curdir == dir) {
 -        /* v_root can't be deleted */
 -        if (movecwd(vol, vol->v_root) < 0) {
 -            LOG(log_error, logtype_afpd, "cname can't chdir to : %s", vol->v_root);
 -        }
 +    /* Add it to the cache only if it's a dir */
 +    if (dircache_add(ret) != 0) { /* 7 */
 +        err = 1;
 +        goto exit;
      }
 -    /* FIXME */
 -    dirchildremove(dir->d_parent, dir);
 -    dir_remove( vol, dir );
 -}
  
 -/* ------------------------------------ */
 -static struct dir *dir_insert(const struct vol *vol, struct dir *dir)
 -{
 -    struct dir  *pdir;
 -
 -    pdir = vol_tree_root(vol, dir->d_did);
 -    while (pdir->d_did != dir->d_did ) {
 -        if ( pdir->d_did > dir->d_did ) {
 -            if ( pdir->d_left == SENTINEL ) {
 -                pdir->d_left = dir;
 -                dir->d_back = pdir;
 -                return NULL;
 -            }
 -            pdir = pdir->d_left;
 -        } else {
 -            if ( pdir->d_right == SENTINEL ) {
 -                pdir->d_right = dir;
 -                dir->d_back = pdir;
 -                return NULL;
 -            }
 -            pdir = pdir->d_right;
 +    LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {end: did:%u, path:'%s'}",
 +        ntohl(did), ntohl(pdid), cfrombstring(ret->d_fullpath));
 +
 +exit:
 +    if (err) {
 +        LOG(log_debug, logtype_afpd, "dirlookup(did: %u) {exit_error: %s}",
 +            ntohl(did), AfpErr2name(afp_errno));
 +        free(upath);
 +        if (fullpath)
 +            bdestroy(fullpath);
 +        if (ret) {
 +            dir_free(ret);
 +            ret = NULL;
          }
      }
 -    return pdir;
 +    return ret;
  }
  
  #define ENUMVETO "./../Network Trash Folder/TheVolumeSettingsFolder/TheFindByContentFolder/:2eDS_Store/Contents/Desktop Folder/Trash/Benutzer/"
Simple merge