/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

#include <sys/syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/param.h>
#include <netatalk/endian.h>
#include <atalk/adouble.h>
#include <atalk/afp.h>
#include <atalk/util.h>
#include <atalk/cnid.h>
#include <utime.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>

#include "directory.h"
#include "desktop.h"
#include "volume.h"
#include "file.h"
#include "globals.h"

struct dir	*curdir;

static struct dir	rootpar = { NULL, NULL, NULL, NULL, NULL,
				    0, 1, 0, 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
 */
#define FINDERINFO_FRVIEWOFF  14 
#define FINDERINFO_CLOSEDVIEW 0x100   


/*
 * These tree management routines need to be modified to support
 * AVL trees -- notice that the DIDs for any give volume are
 * monotonically increasing, and they are assigned as the directories
 * are inserted.
 */
    struct dir *
dirsearch( vol, did )
    struct vol	*vol;
    int		did;
{
    struct dir	*dir;

    if ( ntohl( did ) == DIRDID_ROOT_PARENT ) {
	rootpar.d_child = vol->v_dir;
	return( &rootpar );
    }

    dir = vol->v_dir;
    while ( dir != NULL ) {
	if ( dir->d_did == did ) {
	    break;
	}
	if ( dir->d_did > did ) {
	    dir = dir->d_left;
	} else {
	    dir = dir->d_right;
	}
    }
    return( dir );
}

dirfree( vol, dir )
    struct vol	*vol;
    struct dir	*dir;
{
#if AD_VERSION > AD_VERSION1
    cnid_delete(vol->v_db, dir->d_did);
#endif
}

dirinsert( vol, dir )
    struct vol	*vol;
    struct dir	*dir;
{
    struct dir	*pdir;

    pdir = vol->v_dir;
    for (;;) {
	if ( pdir->d_did == dir->d_did ) {
	    syslog( LOG_ERR, "panic: dirinsert: DID problem!" );
	    return( -1 );
	}
	if ( pdir->d_did > dir->d_did ) {
	    if ( pdir->d_left == NULL ) {
		pdir->d_left = dir;
		return( 0 );
	    }
	    pdir = pdir->d_left;
	} else {
	    if ( pdir->d_right == NULL ) {
		pdir->d_right = dir;
		return( 0 );
	    }
	    pdir = pdir->d_right;
	}
    }
}

/*
 * attempt to extend the current dir. tree to include path
 * as a side-effect, movecwd to that point and return the new dir
 */
    struct dir *
extenddir( vol, dir, path )
    struct vol	*vol;
    struct dir	*dir;
    char	*path;
{
    char	*p;
    struct stat	st;

    p = mtoupath(vol, path );
    if ( stat( p, &st ) != 0 ) {
	return( NULL );
    }
    if (!S_ISDIR(st.st_mode)) {
	return( NULL );
    }

    if (( dir = adddir( vol, dir, path, strlen( path ), p, strlen(p),
			&st)) == NULL ) {
	return( NULL );
    }

    if ( movecwd( vol, dir ) < 0 ) {
	return( NULL );
    }

    return( dir );
}

/* XXX: this needs to be changed to handle path types */
    char *
cname( vol, dir, cpath )
    struct vol	*vol;
    struct dir	*dir;
    char	**cpath;
{
    struct dir		*cdir;
    static char		path[ MAXPATHLEN + 1];
    char		*data, *p;
    int			extend = 0;
    int			len;

    data = *cpath;
    if ( *data++ != 2 ) {			/* path type */
	return( NULL );
    }
    len = (unsigned char) *data++;
    *cpath += len + 2;
    *path = '\0';

    for ( ;; ) {
	if ( len == 0 ) {
	    if ( !extend && movecwd( vol, dir ) < 0 ) {
		return( NULL );
	    }
	    return( path );
	}

	if ( *data == '\0' ) {
	    data++;
	    len--;
	}

	while ( *data == '\0' && len > 0 ) {
	    if ( dir->d_parent == NULL ) {
		return( NULL );
	    }
	    dir = dir->d_parent;
	    data++;
	    len--;
	}

	/* would this be faster with strlen + strncpy? */
	p = path;
	while ( *data != '\0' && len > 0 ) {
	    *p++ = *data++;
	    len--;
	}

	/* short cut bits by chopping off a trailing \0. this also
           makes the traversal happy w/ filenames at the end of the
           cname. */
	if (len == 1) 
	  len--;

#ifdef notdef
	/*
	 * Dung Nguyen <ntd@adb.fr>
	 *
	 * AFPD cannot handle paths with "::" if the "::" notation is
	 * not at the beginning of the path. The following path will not
	 * be interpreted correctly:
	 *
	 * :a:b:::c: (directory c at the same level as directory a) */
	if ( len > 0 ) {
	    data++;
	    len--;
	}
#endif notdef
	*p = '\0';

	if ( p != path ) { /* we got something */
	    if ( !extend ) {
		for ( cdir = dir->d_child; cdir; cdir = cdir->d_next ) {
		    if ( strcasecmp( cdir->d_name, path ) == 0 ) {
			break;
		    }
		}
		if ( cdir == NULL ) {
		    ++extend;
		    if ( movecwd( vol, dir ) < 0 ) {
			return( NULL );
		    }
		    cdir = extenddir( vol, dir, path );
		}

	    } else {
		cdir = extenddir( vol, dir, path );
	    }

	    if ( cdir == NULL ) {
		if ( len > 0 ) {
		    return( NULL );
		}

	    } else {
		dir = cdir;
		*path = '\0';
	    }
	}
    }
}

/*
 * Move curdir to dir, with a possible chdir()
 */
int movecwd( vol, dir)
    const struct vol	*vol;
    struct dir	*dir;
{
    char path[MAXPATHLEN + 1];
    struct dir	*d;
    char	*p, *u;
    int		n;

    if ( dir == curdir ) {
	return( 0 );
    }
    if ( dir->d_did == DIRDID_ROOT_PARENT ) {
	return( -1 );
    }

    p = path + sizeof(path) - 1;
    *p-- = '\0';
    *p = '.';
    for ( d = dir; d->d_parent != NULL && d != curdir; d = d->d_parent ) {
	*--p = '/';
	u = mtoupath(vol, d->d_name );
	n = strlen( u );
	p -= n;
	strncpy( p, u, n );
    }
    if ( d != curdir ) {
	*--p = '/';
	n = strlen( vol->v_path );
	p -= n;
	strncpy( p, vol->v_path, n );
    }
    if ( chdir( p ) < 0 ) {
	return( -1 );
    }
    curdir = dir;
    return( 0 );
}

getdirparams(vol, bitmap, upath, dir, st, buf, buflen )
    struct vol          *vol;
    u_short		bitmap;
    char		*upath;
    struct dir		*dir;
    struct stat		*st;
    char		*buf;
    int			*buflen;
{
    struct maccess	ma;
    struct adouble	ad;
    char		*data, *nameoff = NULL;
    DIR			*dp;
    struct dirent	*de;
    int			bit = 0, isad = 1;
    u_int32_t           aint;
    u_int16_t		ashort;
    u_int8_t            achar;

    if ( ad_open( upath, ADFLAGS_HF|ADFLAGS_DIR, O_RDONLY, 
		  DIRBITS | 0777, &ad ) < 0 ) {
	isad = 0;
    }

    data = buf;
    while ( bitmap != 0 ) {
	while (( bitmap & 1 ) == 0 ) {
	    bitmap = bitmap>>1;
	    bit++;
	}

	switch ( bit ) {
	case DIRPBIT_ATTR :
	    if ( isad ) {
		ad_getattr(&ad, &ashort);
	    } else {
		ashort = 0;
	    }
	    bcopy( &ashort, data, sizeof( u_short ));
	    data += sizeof( ashort );
	    break;

	case DIRPBIT_PDID :
	    if ( dir->d_parent == NULL ) {
		aint = htonl( DIRDID_ROOT_PARENT );
	    } else {
		aint = dir->d_parent->d_did;
	    }
	    bcopy( &aint, data, sizeof( aint ));
	    data += sizeof( aint );
	    break;

	case DIRPBIT_CDATE :
	    if (!isad || (ad_getdate(&ad, AD_DATE_CREATE, &aint) < 0)) 
		aint = AD_DATE_FROM_UNIX(st->st_mtime);
	    bcopy( &aint, data, sizeof( aint ));
	    data += sizeof( aint );
	    break;

	case DIRPBIT_MDATE :
	    aint = AD_DATE_FROM_UNIX(st->st_mtime);
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case DIRPBIT_BDATE :
	    if (!isad || (ad_getdate(&ad, AD_DATE_BACKUP, &aint) < 0))
		aint = AD_DATE_START;
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case DIRPBIT_FINFO :
	    if ( isad ) {
		bcopy( ad_entry( &ad, ADEID_FINDERI ), data, 32 );
	    } else { /* no appledouble */
		bzero( data, 32 );
		/* set default view -- this also gets done in ad_open() */
	        ashort = htons(FINDERINFO_CLOSEDVIEW);
		bcopy(&ashort, data + FINDERINFO_FRVIEWOFF, sizeof(ashort));
	    }
	    data += 32;
	    break;

	case DIRPBIT_LNAME :
	    nameoff = data;
	    data += sizeof( u_short );
	    break;

	case DIRPBIT_SNAME :
	    ashort = 0;
	    bcopy( &ashort, data, sizeof( u_short ));
	    data += sizeof( u_short );
	    break;

	case DIRPBIT_DID :
	    bcopy( &dir->d_did, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case DIRPBIT_OFFCNT :
	    ashort = 0;
	    if ((dp = opendir( upath ))) {
		while (( de = readdir( dp )) != NULL ) {
		  if (validupath(vol, de->d_name))
			ashort++;
		}
		closedir( dp );
	    }

	    ashort = htons( ashort );
	    bcopy( &ashort, data, sizeof( u_short ));
	    data += sizeof( u_short );
	    break;

	case DIRPBIT_UID :
	    aint = st->st_uid;
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case DIRPBIT_GID :
	    aint = st->st_gid;
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case DIRPBIT_ACCESS :
	    utommode( st, &ma );
#ifdef AFS
	    afsmode( upath, &ma, dir );
#endif AFS
	    bcopy( &ma, data, sizeof( int ));
	    data += sizeof( int );
	    break;

 	    /* Client has requested the ProDOS information block.
 	       Just pass back the same basic block for all
 	       directories. <shirsch@ibm.net> */
 	case DIRPBIT_PDINFO :			  /* ProDOS Info Block */
 	    achar = 0x0f;
 	    bcopy( &achar, data, sizeof( u_char ));
 	    data += sizeof( u_char );
 
 	    achar = 0x0;
 	    bcopy( &achar, data, sizeof( u_char ));
 	    data += sizeof( u_char );
 
 	    ashort = htons( 0x0200 );
 	    bcopy( &ashort, data, sizeof( u_short ));
 	    data += sizeof( u_short );
 
 	    ashort = htons( 0x0000 );
 	    bcopy( &ashort, data, sizeof( u_short ));
 	    data += sizeof( u_short );
 	    break;

	default :
	    if ( isad ) {
	      ad_close( &ad, ADFLAGS_HF );
	    }
	    return( AFPERR_BITMAP );
	}
	bitmap = bitmap>>1;
	bit++;
    }
    if ( nameoff ) {
	ashort = htons( data - buf );
	bcopy( &ashort, nameoff, sizeof( u_short ));

	aint = strlen( dir->d_name );
	aint = ( aint > MACFILELEN ) ? MACFILELEN : aint;

	*data++ = aint;
	bcopy( dir->d_name, data, aint );
	data += aint;
    }
    if ( isad ) {
        ad_close( &ad, ADFLAGS_HF );
    }
    *buflen = data - buf;
    return( AFP_OK );
}

afp_setdirparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol	*vol;
    struct dir	*dir;
    char	*path;
    u_short	vid, bitmap;
    int		did, rc;

    *rbuflen = 0;
    ibuf += 2;
    bcopy( ibuf, &vid, sizeof( u_short ));
    ibuf += sizeof( u_short );

    if (( vol = getvolbyvid( vid )) == NULL ) {
	return( AFPERR_PARAM );
    }

    bcopy( ibuf, &did, sizeof( int ));
    ibuf += sizeof( int );

    if (( dir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    bcopy( ibuf, &bitmap, sizeof( u_short ));
    bitmap = ntohs( bitmap );
    ibuf += sizeof( u_short );

    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    /*
     * If ibuf is odd, make it even.
     */
    if ((u_long)ibuf & 1 ) {
	ibuf++;
    }

    if (( rc = setdirparams(vol, path, bitmap, ibuf )) == AFP_OK ) {
	setvoltime(obj, vol );
    }
    return( rc );
}

setdirparams(vol, path, bitmap, buf )
    struct vol          *vol;
    char		*path, *buf;
    short		bitmap;
{
    struct maccess	ma;
    struct adouble	ad;
    struct utimbuf      ut;
    char                *upath;
    int			bit = 0, aint, isad = 1;
    u_int16_t		ashort, bshort;
    int                 err = AFP_OK;

    upath = mtoupath(vol, path);
    if (ad_open( upath, vol_noadouble(vol)|ADFLAGS_HF|ADFLAGS_DIR, 
		 O_RDWR|O_CREAT, 0666, &ad ) < 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
	 * three, we don't need the ad to be open, so just continue.
	 *
	 * note: we also don't need to worry about mdate. also, be quiet
	 *       if we're using the noadouble option.
	 */
	if (!vol_noadouble(vol) && (bitmap &
		~((1<<DIRPBIT_ACCESS)|(1<<DIRPBIT_UID)|(1<<DIRPBIT_GID)|
		  (1<<DIRPBIT_MDATE)|(1<<DIRPBIT_PDINFO))))
	  return AFPERR_ACCESS;

	isad = 0;
    } else {
	/*
	 * Check to see if a create was necessary. If it was, we'll want
	 * to set our name, etc.
	 */
	if ( ad_getoflags( &ad, ADFLAGS_HF ) & O_CREAT ) {
	    ad_setentrylen( &ad, ADEID_NAME, strlen( curdir->d_name ));
	    bcopy( curdir->d_name, ad_entry( &ad, ADEID_NAME ),
		    ad_getentrylen( &ad, ADEID_NAME ));
	}
    }

    while ( bitmap != 0 ) {
	while (( bitmap & 1 ) == 0 ) {
	    bitmap = bitmap>>1;
	    bit++;
	}

	switch( bit ) {
	case DIRPBIT_ATTR :
	    if (isad) {
	    bcopy( buf, &ashort, sizeof( u_short ));
	    ad_getattr(&ad, &bshort);
	    if ( ntohs( ashort ) & ATTRBIT_SETCLR ) {
	      bshort |= htons( ntohs( ashort ) & ~ATTRBIT_SETCLR );
	    } else {
	      bshort &= ~ashort;
	    }
	    ad_setattr(&ad, bshort);
	    }
	    buf += sizeof( ashort );
	    break;

	case DIRPBIT_CDATE :
	    if (isad) {
	    memcpy(&aint, buf, sizeof(aint));
	    ad_setdate(&ad, AD_DATE_CREATE, aint);
	    }
	    buf += sizeof( aint );
	    break;

	case DIRPBIT_MDATE :
	    memcpy(&aint, buf, sizeof(aint));
	    if (isad)
	      ad_setdate(&ad, AD_DATE_MODIFY, aint);
	    ut.actime = ut.modtime = AD_DATE_TO_UNIX(aint);
	    utime(upath, &ut);
	    buf += sizeof( aint );
	    break;

	case DIRPBIT_BDATE :
	  if (isad) {
	    memcpy(&aint, buf, sizeof(aint));
	    ad_setdate(&ad, AD_DATE_BACKUP, aint);
	  }
	    buf += sizeof( aint );
	    break;

	case DIRPBIT_FINFO :
	    /*
	     * Alright, we admit it, this is *really* sick!
	     * The 4 bytes that we don't copy, when we're dealing
	     * with the root of a volume, are the directory's
	     * location information. This eliminates that annoying
	     * behavior one sees when mounting above another mount
	     * point.
	     */
	  if (isad) {
	      if ( ntohl( curdir->d_did ) == DIRDID_ROOT ) {
		bcopy( buf, ad_entry( &ad, ADEID_FINDERI ), 10 );
		bcopy( buf + 14, ad_entry( &ad, ADEID_FINDERI ) + 14, 18 );
	      } else {
		bcopy( buf, ad_entry( &ad, ADEID_FINDERI ), 32 );
	      }
	  }
	    buf += 32;
	    break;

	case DIRPBIT_UID :	/* What kind of loser mounts as root? */
	    buf += sizeof( int );
	    break;

	case DIRPBIT_GID :
	    bcopy( buf, &aint, sizeof( int ));
	    buf += sizeof( int );
	    if ( (ntohl( curdir->d_did ) == DIRDID_ROOT) &&
		 (setdeskowner( -1, aint ) < 0)) {
		switch ( errno ) {
		case EPERM :
		case EACCES :
		    err = AFPERR_ACCESS;
		    goto setdirparam_done;
		    break;
		case EROFS :
		    err = AFPERR_VLOCK;
		    goto setdirparam_done;
		    break;
		default :
		    syslog( LOG_ERR, "setdirparam: setdeskowner: %m" );
		    if (!isad) {
		    err = AFPERR_PARAM;
		    goto setdirparam_done;
		    }
		    break;
		}
	    }
	    if ( setdirowner( -1, aint, vol_noadouble(vol) ) < 0 ) {
		switch ( errno ) {
		case EPERM :
		case EACCES :
		    err = AFPERR_ACCESS;
		    goto setdirparam_done;
		    break;
		case EROFS :
		    err = AFPERR_VLOCK;
		    goto setdirparam_done;
		    break;
		default :
		    syslog( LOG_ERR, "setdirparam: setdirowner: %m" );
		    break;
		}
	    }
	    break;

	case DIRPBIT_ACCESS :
	    bcopy( buf, &ma, sizeof( struct maccess ));
	    buf += sizeof( int );
	    if ( (ntohl( curdir->d_did ) == DIRDID_ROOT) &&
		 (setdeskmode( mtoumode( &ma )) < 0)) {
		switch ( errno ) {
		case EPERM :
		case EACCES :
		    err = AFPERR_ACCESS;
		    goto setdirparam_done;
		case EROFS :
		    err = AFPERR_VLOCK;
		    goto setdirparam_done;
		default :
		    syslog( LOG_ERR, "setdirparam: setdeskmode: %m" );
		    break;
		    err = AFPERR_PARAM;
		    goto setdirparam_done;
		}
	    }
	    if ( setdirmode( mtoumode( &ma ), vol_noadouble(vol)) < 0 ) {
		switch ( errno ) {
		case EPERM :
		case EACCES :
		    err = AFPERR_ACCESS;
		    goto setdirparam_done;
		case EROFS :
		    err = AFPERR_VLOCK;
		    goto setdirparam_done;
		default :
		    syslog( LOG_ERR, "setdirparam: setdirmode: %m" );
		    err = AFPERR_PARAM;
		    goto setdirparam_done;
		}
	    }
	    break;
 	    
 	    /* Ignore what the client thinks we should do to the
 	       ProDOS information block.  Skip over the data and
 	       report nothing amiss. <shirsch@ibm.net> */
 	case DIRPBIT_PDINFO :
 	    buf += 6;
  	    break;

	default :
	    err = AFPERR_BITMAP;
	    goto setdirparam_done;
	    break;
	}

	bitmap = bitmap>>1;
	bit++;
    }


setdirparam_done:
    if ( isad ) {
	ad_flush( &ad, ADFLAGS_HF );
	ad_close( &ad, ADFLAGS_HF );
    }

    return err;
}

afp_createdir(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct adouble	ad;
    struct stat         st;
    struct vol		*vol;
    struct dir		*dir;
    char		*path, *upath;
    int			did;
    u_short		vid;

    *rbuflen = 0;
    ibuf += 2;

    bcopy( ibuf, &vid, sizeof( u_short ));
    ibuf += sizeof( u_short );
    if (( vol = getvolbyvid( vid )) == NULL ) {
	return( AFPERR_PARAM );
    }

    bcopy( ibuf, &did, sizeof( int ));
    ibuf += sizeof( int );
    if (( dir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    upath = mtoupath(vol, path);
    if ( ad_mkdir( upath, DIRBITS | 0777 ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case EROFS :
	    return( AFPERR_VLOCK );
	case EACCES :
	    return( AFPERR_ACCESS );
	case EEXIST :
	    return( AFPERR_EXIST );
	case ENOSPC :
	case EDQUOT :
	    return( AFPERR_DFULL );
	default :
	    return( AFPERR_PARAM );
	}
    }

    if (stat(upath, &st) < 0)
      return AFPERR_MISC;

    if ((dir = adddir( vol, curdir, path, strlen( path ), upath,
		       strlen(upath), &st)) == NULL)
      return AFPERR_MISC;

    if ( movecwd( vol, dir ) < 0 ) {
	return( AFPERR_PARAM );
    }

    if (ad_open( "", vol_noadouble(vol)|ADFLAGS_HF|ADFLAGS_DIR,
		 O_RDWR|O_CREAT, 0666, &ad ) < 0)  {
      if (vol_noadouble(vol))
	  goto createdir_done;
      return( AFPERR_ACCESS );
    }
    
    ad_setentrylen( &ad, ADEID_NAME, strlen( path ));
    bcopy( path, ad_entry( &ad, ADEID_NAME ),
	   ad_getentrylen( &ad, ADEID_NAME ));
    ad_flush( &ad, ADFLAGS_HF );
    ad_close( &ad, ADFLAGS_HF );

createdir_done:
    bcopy( &dir->d_did, rbuf, sizeof( u_int32_t ));
    *rbuflen = sizeof( u_int32_t );
    setvoltime(obj, vol );
    return( AFP_OK );
}

renamedir(src, dst, dir, newparent, newname, noadouble)
    char	*src, *dst, *newname;
    struct dir	*dir, *newparent;
    const int noadouble;
{
    struct adouble	ad;
    struct dir		*parent, *d;
    char                *buf;
    int			len;

    /* existence check moved to afp_moveandrename */

    if ( rename( src, dst ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case EACCES :
	    return( AFPERR_ACCESS );
	default : 
	    return( AFPERR_PARAM );
	}
    }

    if ( ad_open( dst, ADFLAGS_HF|ADFLAGS_DIR, O_RDWR, 0, &ad ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    if (noadouble) {
	      len = strlen(newname);
	      goto renamedir_done;
	    }
	    return( AFPERR_NOOBJ );
	case EACCES :
	    return( AFPERR_ACCESS );
	default : 
	    return( AFPERR_PARAM );
	}
    }
    len = strlen( newname );
    ad_setentrylen( &ad, ADEID_NAME, len );
    bcopy( newname, ad_entry( &ad, ADEID_NAME ), len );
    ad_flush( &ad, ADFLAGS_HF );
    ad_close( &ad, ADFLAGS_HF );

renamedir_done:
    if ((buf = (char *) realloc( dir->d_name, len + 1 )) == NULL ) {
	syslog( LOG_ERR, "renamedir: realloc: %m" );
	return AFPERR_MISC;
    }
    dir->d_name = buf;
    strcpy( dir->d_name, newname );

    if (( parent = dir->d_parent ) == NULL ) {
	return( AFP_OK );
    }
    if ( parent == newparent ) {
	return( AFP_OK );
    }
    if ( parent->d_child == dir ) {
	parent->d_child = dir->d_next;
    } else {
	for ( d = parent->d_child; d && d->d_next; d = d->d_next ) {
	    if ( d->d_next == dir ) {
		break;
	    }
	}
	if ( d->d_next != dir ) {
	    return( AFPERR_PARAM );
	}
	d->d_next = dir->d_next;
    }

    dir->d_parent = newparent;
    dir->d_next = newparent->d_child;
    newparent->d_child = dir;

    return( AFP_OK );
}

/* delete an empty directory */
deletecurdir( vol, path, pathlen )
    const struct vol	*vol;
    char *path;
    int pathlen;
{
    struct dir	*fdir, *dir;
    DIR *dp;

    if ( curdir->d_parent == NULL ) {
	return( AFPERR_ACCESS );
    }

    if ( curdir->d_child != NULL ) {
	return( AFPERR_DIRNEMPT );
    }

    fdir = curdir;

    /* delete stray .AppleDouble files. this happens to get .Parent files
       as well. */
    if (dp = opendir(".AppleDouble")) {
      struct dirent *de;
      struct stat st;
    
      strcpy(path, ".AppleDouble/");
      while (de = readdir(dp)) {
	/* skip this and previous directory */
	if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
	  continue;

	/* bail if the file exists in the current directory */
	if (stat(de->d_name, &st) == 0)
	  return AFPERR_DIRNEMPT;

	strcpy(path + 13, de->d_name);
	if (unlink(path) < 0) {
	  switch (errno) {
	  case EACCES :
	    return( AFPERR_ACCESS );
	  case ENOENT :
	    continue;
	  default :
	    return( AFPERR_PARAM );
	  }
	}
      }
      closedir(dp);
    }
    
    if ( rmdir( ".AppleDouble" ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    break;
	case ENOTEMPTY :
	    return( AFPERR_DIRNEMPT );
	case EACCES :
	    return( AFPERR_ACCESS );
	default :
	    return( AFPERR_PARAM );
	}
    }
    if ( movecwd( vol, curdir->d_parent ) < 0 ) {
	return( AFPERR_NOOBJ );
    }

    if ( rmdir(mtoupath(vol, fdir->d_name)) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case ENOTEMPTY :
	    return( AFPERR_DIRNEMPT );
	case EACCES :
	    return( AFPERR_ACCESS );
	default : 
	    return( AFPERR_PARAM );
	}
    }

    if ( curdir->d_child == fdir ) {
	curdir->d_child = fdir->d_next;
    } else {
	for ( dir = curdir->d_child; dir && dir->d_next;
		dir = dir->d_next ) {
	    if ( fdir == dir->d_next ) {
		break;
	    }
	}
	if ( fdir != dir->d_next ) {
	    return( AFPERR_PARAM );
	}
	dir->d_next = fdir->d_next;
    }
    dirfree( vol, fdir );

    return( AFP_OK );
}

afp_mapid(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct passwd	*pw;
    struct group	*gr;
    char		*name;
    int			id, len, sfunc;

    ibuf++;
    sfunc = (unsigned char) *ibuf++;
    bcopy( ibuf, &id, sizeof( int ));

    if ( id != 0 ) {
	switch ( sfunc ) {
	case 1 :
	    if (( pw = getpwuid( id )) == NULL ) {
		*rbuflen = 0;
		return( AFPERR_NOITEM );
	    }
	    name = pw->pw_name;
	    break;

	case 2 :
	    if (( gr = (struct group *)getgrgid( id )) == NULL ) {
		*rbuflen = 0;
		return( AFPERR_NOITEM );
	    }
	    name = gr->gr_name;
	    break;

	default :
	    *rbuflen = 0;
	    return( AFPERR_PARAM );
	}

	len = strlen( name );

    } else {
	len = 0;
	name = NULL;
    }

    *rbuf++ = len;
    if ( len > 0 ) {
	bcopy( name, rbuf, len );
    }
    *rbuflen = len + 1;
    return( AFP_OK );
}

afp_mapname(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct passwd	*pw;
    struct group	*gr;
    int			len, sfunc, id;

    ibuf++;
    sfunc = (unsigned char) *ibuf++;
    len = (unsigned char) *ibuf++;
    ibuf[ len ] = '\0';

    if ( len != 0 ) {
	switch ( sfunc ) {
	case 3 :
	  if (len == 0) /* null user name */
	    if (( pw = (struct passwd *)getpwnam( ibuf )) == NULL ) {
		*rbuflen = 0;
		return( AFPERR_NOITEM );
	    }
	    id = pw->pw_uid;
	    break;

	case 4 :
	    if (( gr = (struct group *)getgrnam( ibuf )) == NULL ) {
		*rbuflen = 0;
		return( AFPERR_NOITEM );
	    }
	    id = gr->gr_gid;
	    break;
	default :
	    *rbuflen = 0;
	    return( AFPERR_PARAM );
	}
    } else {
	id = 0;
    }

    bcopy( &id, rbuf, sizeof( int ));
    *rbuflen = sizeof( int );
    return( AFP_OK );
}

/* variable DID support */
afp_closedir(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
#if 0
    struct vol   *vol;
    struct dir   *dir;
    u_int16_t    vid;
    u_int32_t    did;
#endif

   *rbuflen = 0;

  /* do nothing as dids are static for the life of the process. */
#if 0
    ibuf += 2;

    memcpy(&vid,  ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (( vol = getvolbyvid( vid )) == NULL ) {
	return( AFPERR_PARAM );
    }

    memcpy( &did, ibuf, sizeof( did ));
    ibuf += sizeof( did );
    if (( dir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_PARAM );
    }

    /* dirfree -- deletedid */
#endif

    return AFP_OK;
}

/* did creation gets done automatically */
afp_opendir(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol		*vol;
    struct dir		*dir, *parentdir;
    struct stat         st;
    char		*path, *upath;
    u_int32_t		did;
    u_int16_t		vid;

    *rbuflen = 0;
    ibuf += 2;

    memcpy(&vid, ibuf, sizeof(vid));
    ibuf += sizeof( vid );

    if (( vol = getvolbyvid( vid )) == NULL ) {
	return( AFPERR_PARAM );
    }

    memcpy(&did, ibuf, sizeof(did));
    ibuf += sizeof(did);

    if (( parentdir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    if (( path = cname( vol, parentdir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    /* see if we already have the directory.
     * XXX: this can be sped up a bit. */
    upath = mtoupath(vol, path);
    if ( stat( upath, &st ) < 0 ) {
	return( AFPERR_NOOBJ );
    }

    for (dir = parentdir->d_child; dir && dir->d_next; dir = dir->d_next)
      if (strdiacasecmp(dir->d_name, path) == 0) {
	memcpy(rbuf, &dir->d_did, sizeof(dir->d_did));
	*rbuflen = sizeof(dir->d_did);
	return AFP_OK;
      }    

    /* we don't already have a did. add one in. */

    if ((dir = adddir(vol, parentdir, path, strlen(path), 
		      upath, strlen(upath), &st)) == NULL) 
      return AFPERR_MISC;

    memcpy(rbuf, &dir->d_did, sizeof(dir->d_did));
    *rbuflen = sizeof(dir->d_did);
    return AFP_OK;
}
