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

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

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

/* the format for the finderinfo fields (from IM: Toolbox Essentials):
 * field         bytes        subfield    bytes
 * 
 * files:
 * ioFlFndrInfo  16      ->       type    4  type field
 *                             creator    4  creator field
 *                               flags    2  finder flags: 
 *					     alias, bundle, etc.
 *                            location    4  location in window
 *                              folder    2  window that contains file
 * 
 * ioFlXFndrInfo 16      ->     iconID    2  icon id
 *                              unused    6  reserved 
 *                              script    1  script system
 *                              xflags    1  reserved
 *                           commentID    2  comment id
 *                           putawayID    4  home directory id
 */

const u_char ufinderi[] = {
    'T', 'E', 'X', 'T', 'U', 'N', 'I', 'X',
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0
};

getfilparams(vol, bitmap, path, dir, st, buf, buflen )
    struct vol  *vol;
    u_short	bitmap;
    char	*path;
    struct dir	*dir;
    struct stat	*st;
    char	*buf;
    int		*buflen;
{
    struct stat		hst;
    struct adouble	ad;
    struct extmap	*em;
    char		*data, *nameoff = NULL, *upath;
    int			bit = 0, isad = 1, aint;
    u_int16_t		ashort;
    u_char              achar, fdType[4];

    upath = mtoupath(vol, path);
    if ( ad_open( upath, ADFLAGS_HF, O_RDONLY, 0, &ad ) < 0 ) {
	isad = 0;
    } else {
	if ( fstat( ad_hfileno( &ad ), &hst ) < 0 ) {
	    syslog( LOG_ERR, "getfilparams fstat: %m" );
	}
    }

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

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

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

	case FILPBIT_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 FILPBIT_MDATE :
	    if ( isad && (ad_getdate(&ad, AD_DATE_MODIFY, &aint) == 0)) {
		if ((st->st_mtime > aint) && 
		    (hst.st_mtime < st->st_mtime)) {
		    aint = AD_DATE_FROM_UNIX(st->st_mtime);
		}
	    } else {
		aint = AD_DATE_FROM_UNIX(st->st_mtime);
	    }
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case FILPBIT_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 FILPBIT_FINFO :
	    memcpy(data, isad ? (void *) ad_entry(&ad, ADEID_FINDERI) :
		   (void *) ufinderi, 32);

	    if ( !isad ||
		    bcmp( ad_entry( &ad, ADEID_FINDERI ), ufinderi, 8 ) == 0 ) {
	         bcopy(ufinderi, data, 8);
		 if (( em = getextmap( path )) != NULL ) {
		   bcopy( em->em_type, data, sizeof( em->em_type ));
		   bcopy( em->em_creator, data + 4, sizeof( em->em_creator ));
		 } 
	    }
	    data += 32;
	    break;

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

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

	case FILPBIT_FNUM :
#if AD_VERSION > AD_VERSION1
	  /* use the CNID database if we're using AD v2 */
	    if (isad)
	      memcpy(&aint, ad_entry(&ad, ADEID_DID), sizeof(aint));
	    else
	      aint = 0;

	    if (!(aint = cnid_add(vol->v_db, st, dir->d_did, upath, 
				  strlen(upath), aint))) {
#endif
	    /*
	     * What a fucking mess.  First thing:  DID and FNUMs are
	     * in the same space for purposes of enumerate (and several
	     * other wierd places).  While we consider this Apple's bug,
	     * this is the work-around:  In order to maintain constant and
	     * unique DIDs and FNUMs, we monotonically generate the DIDs
	     * during the session, and derive the FNUMs from the filesystem.
	     * Since the DIDs are small, we insure that the FNUMs are fairly
	     * large by setting thier high bits to the device number.
	     *
	     * AFS already does something very similar to this for the
	     * inode number, so we don't repeat the procedure.
	     */
#ifdef AFS
	      aint = st->st_ino;
#else AFS
	      aint = ( st->st_dev << 16 ) | ( st->st_ino & 0x0000ffff );
#endif AFS
#if AD_VERSION > AD_VERSION1
	    }
#endif
	    bcopy( &aint, data, sizeof( aint ));
	    data += sizeof( aint );
	    break;

	case FILPBIT_DFLEN :
	    aint = htonl( st->st_size );
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

	case FILPBIT_RFLEN :
	    if ( isad ) {
		aint = htonl( ad_getentrylen( &ad, ADEID_RFORK ));
	    } else {
		aint = 0;
	    }
	    bcopy( &aint, data, sizeof( int ));
	    data += sizeof( int );
	    break;

 	    /* Current client needs ProDOS info block for this file.
 	       Use simple heuristic and let the Mac "type" string tell
 	       us what the PD file code should be.  Everything gets a
 	       subtype of 0x0000 unless the original value was hashed
 	       to "pXYZ" when we created it.  See IA, Ver 2.
 	       <shirsch@ibm.net> */
 	case FILPBIT_PDINFO :
 	    if ( isad ) {
 	      bcopy( ad_entry( &ad, ADEID_FINDERI ), fdType, 4 );
 	      
 	      if ( bcmp( fdType, "TEXT", 4 ) == 0 ) {
 		achar = '\x04';
 		ashort = 0x0000;
 	      }
 	      else if ( bcmp( fdType, "PSYS", 4 ) == 0 ) {
 		achar = '\xff';
 		ashort = 0x0000;
 	      }
 	      else if ( bcmp( fdType, "PS16", 4 ) == 0 ) {
 		achar = '\xb3';
 		ashort = 0x0000;
 	      }
 	      else if ( bcmp( fdType, "BINA", 4 ) == 0 ) {
 		achar = '\x00';
 		ashort = 0x0000;
 	      }
 	      else if ( fdType[0] == 'p' ) {
 		achar = fdType[1];
 		ashort = (fdType[2] * 256) + fdType[3];
 	      } 
 	      else {
 		achar = '\x00';
 		ashort = 0x0000;
 	      }
 	    } 
 	    else {
 	      achar = '\x00';
 	      ashort = 0x0000;
 	    }
 	    
 	    bcopy( &achar, data, sizeof( u_char ));
 	    data += sizeof( u_char );
 
 	    achar = 0x00;
 	    bcopy( &achar, data, sizeof( u_char ));
 	    data += sizeof( u_char );
 
 	    bcopy( &ashort, data, sizeof( u_short ));
 	    data += sizeof( u_short );
 
 	    ashort = 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( path );
	aint = ( aint > MACFILELEN ) ? MACFILELEN : aint;
	*data++ = aint;
	bcopy( path, data, aint );
	data += aint;
    }
    if ( isad ) {
        ad_close( &ad, ADFLAGS_HF );
    }
    *buflen = data - buf;
    return( AFP_OK );
}

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

    *rbuflen = 0;
    ibuf++;
    creatf = (unsigned char) *ibuf++;

    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 ( creatf) {
        /* on a hard create, fail if file exists and is open */
        if ((stat(upath, &st) == 0) && of_findname(vol, curdir, path))
	  return AFPERR_BUSY;
	openf = O_RDWR|O_CREAT|O_TRUNC;
    } else {
	openf = O_RDWR|O_CREAT|O_EXCL;
    }

    if ( ad_open( upath, vol_noadouble(vol)|ADFLAGS_DF|ADFLAGS_HF,
		  openf, 0666, &ad ) < 0 ) {
      switch ( errno ) {
	case EEXIST :
	    return( AFPERR_EXIST );
	case EACCES :
	    return( AFPERR_ACCESS );
        case ENOENT:
	    /* on noadouble volumes, just creating the data fork is ok */
	    if (vol_noadouble(vol) && (stat(upath, &st) == 0))
	      goto createfile_done;
	    /* fallthrough */
	default :
	    return( AFPERR_PARAM );
	}
    }

    ad_setentrylen( &ad, ADEID_NAME, strlen( path ));
    bcopy( path, ad_entry( &ad, ADEID_NAME ),
	    ad_getentrylen( &ad, ADEID_NAME ));
    ad_flush( &ad, ADFLAGS_DF|ADFLAGS_HF );
    ad_close( &ad, ADFLAGS_DF|ADFLAGS_HF );

createfile_done:
    setvoltime(obj, vol );
    return AFP_OK;
}

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

    *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 ((u_long)ibuf & 1 ) {
	ibuf++;
    }

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

    return( rc );
}


setfilparams(vol, path, bitmap, buf )
    struct vol  *vol;
    char	*path, *buf;
    u_short	bitmap;
{
    struct adouble	ad;
    struct extmap	*em;
    int			bit = 0, isad = 1, err = AFP_OK;
    char                *upath;
    u_char              achar, *fdType, xyy[4];
    u_int16_t		ashort, bshort;
    u_int32_t		aint;
    struct utimbuf	ut;

    upath = mtoupath(vol, path);
    if (ad_open( upath, vol_noadouble(vol) | ADFLAGS_HF, 
		 O_RDWR|O_CREAT, 0666, &ad ) < 0) {
      /* for some things, we don't need an adouble header */
      if (bitmap & ~(1<<FILPBIT_MDATE)) {
	return vol_noadouble(vol) ? AFP_OK : AFPERR_ACCESS;
      }
      isad = 0;
    } else if ((ad_getoflags( &ad, ADFLAGS_HF ) & O_CREAT) ) {
	ad_setentrylen( &ad, ADEID_NAME, strlen( path ));
	bcopy( path, ad_entry( &ad, ADEID_NAME ),
		ad_getentrylen( &ad, ADEID_NAME ));
    }

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

	switch(  bit ) {
	case FILPBIT_ATTR :
	    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 FILPBIT_CDATE :
	    memcpy(&aint, buf, sizeof(aint));
	    ad_setdate(&ad, AD_DATE_CREATE, aint);
	    buf += sizeof( aint );
	    break;

	case FILPBIT_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 FILPBIT_BDATE :
	    memcpy(&aint, buf, sizeof(aint));
	    ad_setdate(&ad, AD_DATE_BACKUP, aint);
	    buf += sizeof( aint );
	    break;

	case FILPBIT_FINFO :
	    if (( ad_getoflags( &ad, ADFLAGS_HF ) & O_CREAT ) ||
		bcmp( ad_entry( &ad, ADEID_FINDERI ), ufinderi, 8 ) == 0 ) {
	      if (( em = getextmap( path )) != NULL ) {
		if ( bcmp( buf, em->em_type, sizeof( em->em_type )) == 0 &&
		     bcmp( buf + 4, em->em_creator,
			   sizeof( em->em_creator )) == 0 ) {
		  bcopy( ufinderi, buf, 8 );
		}
	      }
	    }
	    memcpy(ad_entry( &ad, ADEID_FINDERI ), buf, 32 );
	    buf += 32;
	    break;

 	    /* Client needs to set the ProDOS file info for this file.
 	       Use defined strings for the simple cases, and convert
 	       all else into pXYY per Inside Appletalk.  Always set
 	       the creator as "pdos". <shirsch@ibm.net> */
 	case FILPBIT_PDINFO :
 	    bcopy( buf, &achar, sizeof( u_char ));
 	    buf += 2 * sizeof( u_char );
 	  
 	    bcopy( buf, &ashort, sizeof( u_short ));
 	    ashort = ntohs( ashort );
 	    buf += 2 * sizeof( u_char );
 
 	    switch ( (unsigned int) achar )
 	      {
 	      case 0x04 :
 		fdType = ( u_char *) "TEXT";
 		break;
 		
 	      case 0xff :
 		fdType = ( u_char *) "PSYS";
 		break;
 
 	      case 0xb3 :
 		fdType = ( u_char *) "PS16";
 		break;
 
 	      case 0x00 :
 		fdType = ( u_char *) "BINA";
 		break;
 
 	      default :
 		xyy[0] = ( u_char ) 'p';
 		xyy[1] = achar;
 		xyy[2] = ( u_char ) ( ashort >> 8 ) & 0xff;
 		xyy[3] = ( u_char ) ashort & 0xff;
 		fdType = xyy;
 		break;
 	      }
 	    
 	    bcopy( fdType, ad_entry( &ad, ADEID_FINDERI ), 4 );
 	    bcopy( "pdos", ad_entry( &ad, ADEID_FINDERI ) + 4, 4 );
 	    break;
 

	default :
	    err = AFPERR_BITMAP;
	    goto setfilparam_done;
	}

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

setfilparam_done:
    if (isad) {
      ad_flush( &ad, ADFLAGS_HF );
      ad_close( &ad, ADFLAGS_HF );
    }
    return err;
}

/*
 * renamefile and copyfile take the old and new unix pathnames
 * and the new mac name.
 */
renamefile(src, dst, newname, noadouble )
    char	*src, *dst, *newname;
    const int         noadouble;
{
    struct adouble	ad;
    char		adsrc[ MAXPATHLEN + 1];
    int			len, rc;

    /*
     * Note that this is only checking the existance of the data file,
     * not the header file.  The thinking is that if the data file doesn't
     * exist, but the header file does, the right thing to do is remove
     * the data file silently.
     */

    /* existence check moved to afp_moveandrename */

    if ( rename( src, dst ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case EACCES :
	    return( AFPERR_ACCESS );
	case EXDEV :			/* Cross device move -- try copy */
	    if (( rc = copyfile(src, dst, newname, noadouble )) != AFP_OK ) {
		deletefile( dst );
		return( rc );
	    }
	    rc = deletefile( src );
	    return( rc );
	default :
	    return( AFPERR_PARAM );
	}
    }

    strcpy( adsrc, ad_path( src, 0 ));
    rc = 0;
rename_retry:
    if (rename( adsrc, ad_path( dst, 0 )) < 0 ) {
        struct stat st;

	switch ( errno ) {
	case ENOENT :
	  /* check for a source appledouble header. if it exists, make
	   * a dest appledouble directory and do the rename again. */
	  if (rc || stat(adsrc, &st) ||
	      (ad_open(dst, ADFLAGS_HF, O_RDWR | O_CREAT, 0666, &ad) < 0))
	    return AFP_OK;
	  rc++;
	  ad_close(&ad, ADFLAGS_HF);
	  goto rename_retry;
	case EACCES :
	    return( AFPERR_ACCESS );
	default :
	    return( AFPERR_PARAM );
	}
    }

    if ( ad_open( dst, ADFLAGS_HF, O_RDWR, 0666, &ad ) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    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 );

    return( AFP_OK );
}

afp_copyfile(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol	*vol;
    struct dir	*dir;
    char	*newname, *path, *p;
    int		sdid, ddid;
    int		plen;
    short	svid, dvid;

    *rbuflen = 0;
    ibuf += 2;

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

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

    bcopy( ibuf, &dvid, sizeof( short ));
    ibuf += sizeof( short );
    bcopy( ibuf, &ddid, sizeof( int ));
    ibuf += sizeof( int );

    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }
    if ( *path == '\0' ) {
	return( AFPERR_BADTYPE );
    }

    newname = obj->newtmp;
    strcpy( newname, path );

    p = ctoupath( vol, curdir, newname );

    if (( vol = getvolbyvid( dvid )) == NULL ) {
	return( AFPERR_PARAM );
    }
    if (( dir = dirsearch( vol, ddid )) == NULL ) {
	return( AFPERR_PARAM );
    }

    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }
    if ( *path != '\0' ) {
	return( AFPERR_BADTYPE );
    }

    /* one of the handful of places that knows about the path type */
    if ( *ibuf++ != 2 ) {
	return( AFPERR_PARAM );
    }
    if (( plen = (unsigned char)*ibuf++ ) != 0 ) {
	strncpy( newname, ibuf, plen );
	newname[ plen ] = '\0';
    }
    if ( copyfile(p, mtoupath(vol, newname ), newname, 
		  vol_noadouble(vol)) < 0 ) {
	return( AFPERR_ACCESS );
    }

    setvoltime(obj, vol );
    return( AFP_OK );
}


static __inline__ int copy_all(const int dfd, const void *buf,
			       size_t buflen)
{
  ssize_t cc;

  while (buflen > 0) {
    if ((cc = write(dfd, buf, buflen)) < 0) {
      switch (errno) {
      case EINTR:
	continue;
      case EDQUOT:
      case EFBIG:
      case ENOSPC:
	return AFPERR_DFULL;
      default:
	return AFPERR_PARAM;
      }
    }
    buflen -= cc;
  }

  return 0;
}


copyfile(src, dst, newname, noadouble )
    char	*src, *dst, *newname;
    const int   noadouble;
{
    struct adouble	ad;
    struct stat         st;
    char		filebuf[8192];
    int			sfd, dfd, len, err = AFP_OK;
    ssize_t             cc;


    if (( sfd = open( ad_path( src, ADFLAGS_HF ), O_RDONLY, 0 )) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    break; /* just copy the data fork */
	case EACCES :
	    return( AFPERR_ACCESS );
	default :
	    return( AFPERR_PARAM );
	}
    } else {
	if (( dfd = open( ad_path( dst, ADFLAGS_HF ), O_WRONLY|O_CREAT,
		ad_mode( ad_path( dst, ADFLAGS_HF ), 0666 ))) < 0 ) {
	    close( sfd );
	    switch ( errno ) {
	    case ENOENT :
		return( AFPERR_NOOBJ );
	    case EACCES :
		return( AFPERR_ACCESS );
	    default :
		return( AFPERR_PARAM );
	    }
	}

	/* copy the file */
#ifdef SENDFILE_FLAVOR_LINUX
	if (fstat(sfd, &st) == 0) {
	  if ((cc = sendfile(dfd, sfd, NULL, st.st_size)) < 0) {
	    switch (errno) {
	    case EDQUOT:
	    case EFBIG:
	    case ENOSPC:
	      err = AFPERR_DFULL;
	      break;
	    default:
	      err = AFPERR_PARAM;
	    }
	  }
	  goto copyheader_done;
	}
#endif
	while (1) {
	  if ((cc = read(sfd, filebuf, sizeof(filebuf))) < 0) {
	    if (errno == EINTR) 
	      continue;

	    err = AFPERR_PARAM;
	    break;
	  }

	  if (!cc || ((err = copy_all(dfd, filebuf, cc)) < 0))
	    break;
	}

copyheader_done:
	close(sfd);
	close(dfd);
	if (err < 0) {
	  unlink(ad_path(dst, ADFLAGS_HF));
	  return err;
	}
    }

    /* data fork copying */
    if (( sfd = open( src, O_RDONLY, 0 )) < 0 ) {
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case EACCES :
	    return( AFPERR_ACCESS );
	default :
	    return( AFPERR_PARAM );
	}
    }

    if (( dfd = open( dst, O_WRONLY|O_CREAT, ad_mode( dst, 0666 ))) < 0 ) {
	close( sfd );
	switch ( errno ) {
	case ENOENT :
	    return( AFPERR_NOOBJ );
	case EACCES :
	    return( AFPERR_ACCESS );
	default :
	    return( AFPERR_PARAM );
	}
    }

#ifdef SENDFILE_FLAVOR_LINUX
    if (fstat(sfd, &st) == 0) {
      if ((cc = sendfile(dfd, sfd, NULL, st.st_size)) < 0) {
	switch (errno) {
	case EDQUOT:
	case EFBIG:
	case ENOSPC:
	  err = AFPERR_DFULL;
	  break;
	default:
	  err = AFPERR_PARAM;
	}
      }
      goto copydata_done;
    }
#endif

    while (1) {
      if ((cc = read( sfd, filebuf, sizeof( filebuf ))) < 0) {
	if (errno == EINTR)
	  continue;
	
	err = AFPERR_PARAM;
	break;
      }
      
      if (!cc || ((err = copy_all(dfd, filebuf, cc)) < 0)) {
	break;
      }
    }
    
copydata_done:
    close(sfd);
    close(dfd);
    if (err < 0) {
      unlink(ad_path(dst, ADFLAGS_HF));
      unlink(dst);
      return err;
    }
    
    if ( ad_open( dst, noadouble | ADFLAGS_HF, O_RDWR|O_CREAT,
		    0666, &ad ) < 0 ) {
      switch ( errno ) {
      case ENOENT :
	return noadouble ? AFP_OK : 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 );
    
    return( AFP_OK );
}


deletefile( file )
    char		*file;
{
    struct adouble	ad;
    int			adflags, err = AFP_OK;

    /* try to open both at once */
    adflags = ADFLAGS_DF|ADFLAGS_HF;
    if ( ad_open( file, adflags, O_RDWR, 0, &ad ) < 0 ) {
        /* that failed. now try to open just the data fork */
	if ( errno == ENOENT ) {
	    adflags = ADFLAGS_DF;
	    if ( ad_open( file, adflags, O_RDWR, 0, &ad ) < 0 ) 
		  return AFPERR_NOOBJ;
	} else {
	    if ( errno == EACCES ) {
		return( AFPERR_ACCESS );
	    } else {
		return( AFPERR_PARAM );
	    }
	}
    }

    if ((adflags & ADFLAGS_HF) &&
	(ad_tmplock(&ad, ADEID_RFORK, ADLOCK_WR, 0, 0, 0) < 0 )) {
      ad_close( &ad, adflags );
      return( AFPERR_BUSY );
    }

    if (ad_tmplock( &ad, ADEID_DFORK, ADLOCK_WR, 0, 0, 0 ) < 0) {
      err = AFPERR_BUSY;
      goto delete_unlock;
    }

    if ( unlink( ad_path( file, ADFLAGS_HF )) < 0 ) {
	switch ( errno ) {
	case EACCES :
	    err = AFPERR_ACCESS;
	    goto delete_unlock;
	case ENOENT :
	    break;
	default :
	    err = AFPERR_PARAM;
            goto delete_unlock;
	}
    }

    if ( unlink( file ) < 0 ) {
	switch ( errno ) {
	case EACCES :
	    err = AFPERR_ACCESS;
	    break;
	case ENOENT :
	    break;
	default :
	    err = AFPERR_PARAM;
	    break;
	}
    }

delete_unlock:
    if (adflags & ADFLAGS_HF)
      ad_tmplock(&ad, ADEID_RFORK, ADLOCK_CLR, 0, 0, 0);
    ad_tmplock(&ad, ADEID_DFORK, ADLOCK_CLR, 0, 0, 0);
    ad_close( &ad, adflags );
    return err;
}


#if AD_VERSION > AD_VERSION1
/* return a file id */
afp_createid(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat         st;
    struct adouble	ad;
    struct vol		*vol;
    struct dir		*dir;
    char		*path, *upath;
    int                 len;
    cnid_t		did, id;
    u_short		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 (( dir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_PARAM );
    }

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

    if ( *path == '\0' ) {
	return( AFPERR_BADTYPE );
    }

    upath = mtoupath(vol, path);
    if (stat(upath, &st) < 0) {
      switch (errno) {
      case EPERM:
      case EACCES:
	return AFPERR_ACCESS;
      case ENOENT:
	return AFPERR_NOOBJ;
      default:
	return AFPERR_PARAM;
      }
    }

    if (id = cnid_lookup(vol->v_db, &st, did, upath, len = strlen(upath))) {
      memcpy(rbuf, &id, sizeof(id));
      *rbuflen = sizeof(id);
      return AFPERR_EXISTID;
    }

    if (ad_open( upath, ADFLAGS_HF, O_RDONLY, 0, &ad ) < 0)
      id = 0;
    else {
      memcpy(&id, ad_entry(&ad, ADEID_DID), sizeof(id));
      ad_close(upath, ADFLAGS_HF);
    }

    if (id = cnid_add(vol->v_db, &st, did, upath, len, id)) {
      memcpy(rbuf, &id, sizeof(id));
      *rbuflen = sizeof(id);
      return AFP_OK;
    }

    switch (errno) {
    case EROFS:
      return AFPERR_VLOCK;
      break;
    case EPERM:
    case EACCES:
      return AFPERR_ACCESS;
      break;
    default:
      syslog(LOG_ERR, "afp_createid: cnid_add: %m");
      return AFPERR_PARAM;
    }
}

/* resolve a file id */
afp_resolveid(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat         st;
    struct vol		*vol;
    struct dir		*dir;
    char		*upath;
    int                 err, buflen;
    cnid_t		id;
    u_short		vid, bitmap;
    

    *rbuflen = 0;
    ibuf += 2;

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

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

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

    if ((upath = cnid_resolve(vol->v_db, &id)) == NULL) {
      return AFPERR_BADID;
    }

    if (( dir = dirsearch( vol, id )) == NULL ) {
      return( AFPERR_PARAM );
    }

    if ((movecwd(vol, dir) < 0) || (stat(upath, &st) < 0)) {
      switch (errno) {
      case EACCES:
      case EPERM:
	return AFPERR_ACCESS;
      case ENOENT:
	return AFPERR_NOID;
      default:
	return AFPERR_PARAM;
      }
    }

    /* directories are bad */
    if (S_ISDIR(st.st_mode))
      return AFPERR_BADTYPE;

    memcpy(&bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs( bitmap );

    buflen = *rbuflen - sizeof(bitmap);
    if ((err = getfilparams(vol, bitmap, utompath(vol, upath), curdir, &st,
			   rbuf + sizeof(bitmap), &buflen)) != AFP_OK)
      return err;

    *rbuflen = buflen + sizeof(bitmap);
    memcpy(rbuf, ibuf, sizeof(bitmap));
    return AFP_OK;
}

int afp_deleteid(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat         st;
    struct vol		*vol;
    struct dir		*dir;
    char                *upath;
    int                 err;
    cnid_t		id;
    u_short		vid;
    

    *rbuflen = 0;
    ibuf += 2;

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

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

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

    if ((upath = cnid_resolve(vol->v_db, &id)) == NULL) {
      return AFPERR_NOID;
    }

    if (( dir = dirsearch( vol, id )) == NULL ) {
      return( AFPERR_PARAM );
    }

    err = AFP_OK;
    if ((movecwd(vol, dir) < 0) || (stat(upath, &st) < 0)) {
      switch (errno) {
      case EACCES:
      case EPERM:
	return AFPERR_ACCESS;
      case ENOENT:
	/* still try to delete the id */
	err = AFPERR_NOOBJ;
	break;
      default:
	return AFPERR_PARAM;
      }
    }

    /* directories are bad */
    if (S_ISDIR(st.st_mode))
      return AFPERR_BADTYPE;

    if (cnid_delete(vol->v_db, id)) {
      switch (errno) {
      case EROFS:
	return AFPERR_VLOCK;
      case EPERM:
      case EACCES:
	return AFPERR_ACCESS;
      default:
	return AFPERR_PARAM;
      }
    }

    return err;
}
#endif

#define APPLETEMP ".AppleTempXXXXXX"
int afp_exchangefiles(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat         srcst, destst;
    struct vol		*vol;
    struct dir		*dir, *sdir;
    char		*spath, temp[17], *path, *p;
    char                *supath, *upath;
    int                 err, slen, dlen;
    cnid_t		sid, 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);
    }

    /* source and destination dids */
    memcpy(&sid, ibuf, sizeof(sid));
    ibuf += sizeof(sid);
    memcpy(&did, ibuf, sizeof(did));
    ibuf += sizeof(did);

    /* source file */
    if ((dir = dirsearch( vol, sid )) == NULL ) {
	return( AFPERR_PARAM );
    }

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

    if ( *path == '\0' ) {
	return( AFPERR_BADTYPE );
    }

    upath = mtoupath(vol, path);
    if (stat(upath, &srcst) < 0) {
      switch (errno) {
      case ENOENT:
	return AFPERR_NOID;
      case EPERM:
      case EACCES:
	return AFPERR_ACCESS;
      default:
	return AFPERR_PARAM;
      }
    }

    /* save some stuff */
    sdir = curdir;
    spath = obj->oldtmp;
    supath = obj->newtmp;
    strcpy(spath, path);
    strcpy(supath, upath); /* this is for the cnid changing */
    p = ctoupath( vol, sdir, spath);

    /* look for the source cnid. if it doesn't exist, don't worry about
     * it. */
#if AD_VERSION > AD_VERSION1
    sid = cnid_lookup(vol->v_db, &srcst, sdir->d_did, supath, 
		      slen = strlen(supath));
#endif

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

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

    if ( *path == '\0' ) {
	return( AFPERR_BADTYPE );
    }

    upath = mtoupath(vol, path);
    if (stat(upath, &destst) < 0) {
      switch (errno) {
      case ENOENT:
	return AFPERR_NOID;
      case EPERM:
      case EACCES:
	return AFPERR_ACCESS;
      default:
	return AFPERR_PARAM;
      }
    }

#if AD_VERSION > AD_VERSION1
    /* look for destination id. */
    did = cnid_lookup(vol->v_db, &destst, curdir->d_did, upath, 
		      dlen = strlen(upath));
#endif

    /* construct a temp name. 
     * NOTE: the temp file will be in the dest file's directory. it
     * will also be inaccessible from AFP. */
    memcpy(temp, APPLETEMP, sizeof(APPLETEMP));
    if (!mktemp(temp))
      return AFPERR_MISC;

    /* now, quickly rename the file. we error if we can't. */
    if ((err = renamefile(p, temp, temp, vol_noadouble(vol))) < 0)
      goto err_exchangefile;
    of_rename(vol, sdir, spath, curdir, temp);

    /* rename destination to source */
    if ((err = renamefile(path, p, spath, vol_noadouble(vol))) < 0) 
      goto err_src_to_tmp;
    of_rename(vol, curdir, path, sdir, spath);

    /* rename temp to destination */
    if ((err = renamefile(temp, upath, path, vol_noadouble(vol))) < 0) 
      goto err_dest_to_src;
    of_rename(vol, curdir, temp, curdir, path);
    
#if AD_VERSION > AD_VERSION1
    /* id's need switching. src -> dest and dest -> src. */
    if (sid && (cnid_update(vol->v_db, sid, &destst, curdir->d_did, 
			    upath, dlen) < 0)) {
      switch (errno) {
      case EPERM:
      case EACCES:
	err = AFPERR_ACCESS;
      default:
	err = AFPERR_PARAM;
      }
      goto err_temp_to_dest;
    }

    if (did && (cnid_update(vol->v_db, did, &srcst, sdir->d_did,
			    supath, slen) < 0)) {
      switch (errno) {
      case EPERM:
      case EACCES:
	err = AFPERR_ACCESS;
      default:
	err = AFPERR_PARAM;
      }

      if (sid)
	cnid_update(vol->v_db, sid, &srcst, sdir->d_did, supath, slen);
      goto err_temp_to_dest;
    }
#endif
    return AFP_OK;


    /* all this stuff is so that we can unwind a failed operation 
     * properly. */
err_temp_to_dest:
    /* rename dest to temp */
    renamefile(upath, temp, temp, vol_noadouble(vol));
    of_rename(vol, curdir, upath, curdir, temp);

err_dest_to_src:
    /* rename source back to dest */
    renamefile(p, upath, path, vol_noadouble(vol));
    of_rename(vol, sdir, spath, curdir, path);

err_src_to_tmp:
    /* rename temp back to source */
    renamefile(temp, p, spath, vol_noadouble(vol));
    of_rename(vol, curdir, temp, sdir, spath);

err_exchangefile:
    return err;
}
