/*
- * $Id: directory.c,v 1.131.2.8 2010-02-02 14:39:48 franklahm Exp $
+ * $Id: directory.c,v 1.140 2010/03/12 15:16:49 franklahm Exp $
*
* Copyright (c) 1990,1993 Regents of The University of Michigan.
* All Rights Reserved. See COPYRIGHT.
/*
* FIXMEs, loose ends after the dircache rewrite:
- * o case-insensitivity is gone
- * 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 doesn't work with CNID backend last and the like.
- * CNID backend must support persistent CNIDs.
+ * o merge dircache_search_by_name and dir_add ??
+ * o case-insensitivity is gone from cname
*/
"", /* mac name */
".", /* unix name */
0, /* id */
- NULL,/* struct dir */
+ NULL,/* struct dir * */
0, /* stat is not set */
+ 0, /* errno */
+ {0} /* struct stat */
};
}
/* ------------------- */
-static int deletedir(char *dir)
+static int deletedir(int dirfd, char *dir)
{
char path[MAXPATHLEN + 1];
DIR *dp;
return AFPERR_PARAM;
/* already gone */
- if ((dp = opendir(dir)) == NULL)
+ if ((dp = opendirat(dirfd, dir)) == NULL)
return AFP_OK;
strcpy(path, 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);
/* 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;
/* 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 */
}
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;
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 {
}
/* 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);
}
*
* 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, 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->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'}",
+ 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);
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->u_name, cfrombstring(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:
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;
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
+ || (upath = strdup(upath)) == NULL) { /* 3 */
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;
- err = 1;
- goto exit;
- }
-
- 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
+ */
+ 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));
ntohl(did), ntohl(pdid), cfrombstring(ret->d_fullpath));
exit:
- if (pathlist)
- bstrListDestroy(pathlist);
-
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) {
*
* 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.
+ * cf theAFP_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.
struct adouble *adp = NULL;
bstring fullpath;
- assert(vol);
- assert(dir);
- assert(path);
- assert(len > 0);
+ AFP_ASSERT(vol);
+ AFP_ASSERT(dir);
+ AFP_ASSERT(path);
+ AFP_ASSERT(len > 0);
- if ((cdir = dircache_search_by_name(vol, dir->d_did, path->u_name, strlen(path->u_name))) != NULL) {
+ if ((cdir = dircache_search_by_name(vol, dir, path->u_name, strlen(path->u_name))) != 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), cfrombstring(dir->d_fullpath), path->u_name,
/*!
* @brief Remove a dir from a cache and free it and any ressources with it
*
+ * 1. Check if the dir is locked or has opened forks
+ * 2. If it's a request to remove curdir, just chdir to volume root
+ * 3. Remove it from the cache
+ * 4. Remove the dir plus any allocated resources it references
+ *
* @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 (dir->d_flags & DIRF_CACHELOCK || dir->d_ofork) { /* 1 */
+ LOG(log_warning, logtype_afpd, "dir_remove(did:%u,'%s'): dir is locked or has opened forks",
+ ntohl(dir->d_did), cfrombstring(dir->d_fullpath));
+ return 0;
+ }
+
+ if (curdir == dir) { /* 2 */
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), cfrombstring(dir->d_fullpath));
- dircache_remove(vol, dir, DIRCACHE | DIDNAME_INDEX | QUEUE_INDEX);
- dir_free(dir);
+ dircache_remove(vol, dir, DIRCACHE | DIDNAME_INDEX | QUEUE_INDEX); /* 3 */
+ dir_free(dir); /* 4 */
return 0;
}
/*!
* @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];
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}", cfrombstring(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++;
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;
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]) {
*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;
}
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
* 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
* 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;
+ continue; /* 10 */
}
switch (ret.st.st_mode & S_IFMT) {
- case S_IFREG:
+ case S_IFREG: /* 11 */
LOG(log_debug, logtype_afpd, "came('%s'): {file: '%s'}", cfrombstring(dir->d_fullpath), ret.u_name);
if (len > 0) {
/* it wasn't the last part, so we have a bogus path request */
return NULL;
}
continue; /* continues while loop */
- case S_IFLNK:
+ case S_IFLNK: /* 12 */
LOG(log_debug, logtype_afpd, "came('%s'): {link: '%s'}", cfrombstring(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);
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);
/* 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); /* 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;
} /* 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));
if (len == 0)
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) {
*/
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')",
+ LOG(log_maxdebug, logtype_afpd, "movecwd(curdir:'%s', cwd:'%s')",
cfrombstring(curdir->d_fullpath), getcwdpath());
if ( dir == curdir)
LOG(log_debug, logtype_afpd, "movecwd(did:%u, '%s')", ntohl(dir->d_did), cfrombstring(dir->d_fullpath));
- if ( chdir(cfrombstring(dir->d_fullpath)) < 0 ) {
- LOG(log_debug, logtype_afpd, "movecwd('%s'): %s", cfrombstring(dir->d_fullpath), strerror(errno));
+ if ((ret = lchdir(cfrombstring(dir->d_fullpath))) != 0 ) {
+ LOG(log_debug, logtype_afpd, "movecwd('%s'): ret: %u, %s",
+ cfrombstring(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;
+ }
+
switch (errno) {
case EACCES:
case EPERM:
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 :
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 :
* 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)
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 );
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 :
}
}
- 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_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;
}
goto delete_done;
}
- err = netatalk_rmdir_all_errors(cfrombstring(fdir->d_u_name));
+ err = netatalk_rmdir_all_errors(-1, cfrombstring(fdir->d_u_name));
if ( err == AFP_OK || err == AFPERR_NOOBJ) {
cnid_delete(vol->v_cdb, fdir->d_did);
dir_remove( vol, fdir );