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

#include <sys/syslog.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netatalk/endian.h>
#include <netatalk/at.h>
#include <atalk/dsi.h>
#include <atalk/atp.h>
#include <atalk/asp.h>
#include <atalk/afp.h>
#include <atalk/adouble.h>
#include <atalk/cnid.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

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

struct ofork		*writtenfork;

afp_openfork(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol		*vol;
    struct dir		*dir;
    struct ofork	*ofork, *opened;
    int			did, buflen, oflags, ret, adflags, eid;
    int			lockop = ADLOCK_CLR;
    u_int16_t		vid, bitmap, access, ofrefnum, attrbits = 0;
    char		fork, *path, *upath; 


    ibuf++;
    fork = *ibuf++;
    bcopy( ibuf, &vid, sizeof( u_short ));
    ibuf += sizeof( u_short );

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

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

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

    bcopy( ibuf, &bitmap, sizeof( u_short ));
    bitmap = ntohs( bitmap );
    ibuf += sizeof( u_short );
    bcopy( ibuf, &access, sizeof( u_short ));
    access = ntohs( access );
    ibuf += sizeof( u_short );
    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	*rbuflen = 0;
	return( AFPERR_NOOBJ );
    }

    /* *** if we're using fcntl-style locks, we have to check
     *     against any existing open files first (using something like
     *     of_findname) */
#ifdef USE_FCNTL_LOCKS
#endif

    if ( fork == OPENFORK_DATA ) {
	eid = ADEID_DFORK;
	adflags = ADFLAGS_DF|ADFLAGS_HF;
    } else {
        eid = ADEID_RFORK;
	adflags = ADFLAGS_HF;
    }
	
    /* XXX: this probably isn't the best way to do this. the already
       open bits should really be set if the fork is opened by any
       program, not just this one. however, that's problematic to do
       if we can't write lock files somewhere. */
    if (opened = of_findname(vol, curdir, path)) {
      attrbits = ((opened->of_flags & AFPFORK_RSRC) ? ATTRBIT_ROPEN : 0) | 
	((opened->of_flags & AFPFORK_DATA) ? ATTRBIT_DOPEN : 0);
    }

    if (( ofork = of_alloc(vol, curdir, path, &ofrefnum, eid )) == NULL ) {
	*rbuflen = 0;
	return( AFPERR_NFILE );
    }

    if ( access & OPENACC_WR ) {
	oflags = O_RDWR;
    } else {
	oflags = O_RDONLY;
    }

    upath = mtoupath(vol, path);
    ret = AFPERR_NOOBJ;
    if ( ad_open(upath,  adflags, oflags, 0, &ofork->of_ad ) < 0 ) {
        if (errno == ENOENT) { 
	  struct stat st;

	  if (adflags != ADFLAGS_HF) {
	    /* client asked for the data fork */
	    if ( ad_open(upath, ADFLAGS_DF, oflags, 0, &ofork->of_ad) < 0)
	      goto openfork_err;
	    adflags = ADFLAGS_DF;

	  } else if (vol_noadouble(vol) && !stat(upath, &st)) {
	    /* if the client specifically asks for the resource fork,
	       create it on noadouble volumes if the data fork exists. */
	    if (ad_open(upath, adflags, oflags | O_CREAT, 0666, 
			&ofork->of_ad) < 0) 
	      goto openfork_err;
	  }
	} else {
	    switch ( errno ) {
	    case EMFILE :
	    case ENFILE :
	        ret = AFPERR_NFILE;
		break;
	    case EISDIR :
		ret = AFPERR_BADTYPE;
		break;
	    case EACCES :
		ret = (access & OPENACC_WR) ? AFPERR_LOCK : AFPERR_ACCESS;
	    default :
		syslog( LOG_ERR, "afp_openfork: ad_open: %m" );
		ret = AFPERR_PARAM;
	    }
	    goto openfork_err;
	}
    }

    if ((adflags & ADFLAGS_HF) &&
	(ad_getoflags( &ofork->of_ad, ADFLAGS_HF ) & O_CREAT))
	ad_flush( &ofork->of_ad, adflags );

    buflen = *rbuflen - 2 * sizeof( u_int16_t );
    if (( ret = getforkparams(ofork, bitmap, rbuf + 2 * sizeof( u_int16_t ),
			      &buflen, attrbits )) != AFP_OK ) {
	ad_close( &ofork->of_ad, adflags );
	goto openfork_err;
    }

    *rbuflen = buflen + 2 * sizeof( u_int16_t );
    bitmap = htons( bitmap );
    bcopy( &bitmap, rbuf, sizeof( u_int16_t ));
    rbuf += sizeof( u_int16_t );

#if 0
    /*
     * Perform synchronization locks.  There are a couple of ways of
     * doing this.  The method we're using here, translates W & !R
     * to EX, and R & !W to SH.  This will allow multiple readers
     * and one writer.  An alternate method translates R & W to SH,
     * and !R & !W to EX.  This allows multiple writers, but limits
     * the reads to one, which doesn't make much sense, since most
     * apps will want to read *and* write.
     *
     * Note:  By doing this, there's pretty much no point in doing
     * byte range locking.  If you open for writing, you have a lock
     * no one else can break.
     *
     * One last thing:  We do the locking late, so we can still return
     * data on a "Deny Conflict" error.
     *
     *
     */
    if (access & (OPENACC_WR | OPENACC_DRD)) {	/* exclusive lock */
	lockop = ADLOCK_WR;
    } else if (access & (OPENACC_RD | OPENACC_DWR)) { /* shared lock */
#ifdef AFS
	lockop = ADLOCK_CLR;
#else
	lockop = ADLOCK_RD;
#endif
    }
#else
    /* when we aren't using AFS, we have byte locks. we can use those
     * to establish locks which are much closer to what AFP wants. actually, 
     * we attempt to use whole file flocks if fcntl locks don't work.
     * here's the ritual:
     *  1) attempt a test lock to see if we have read or write
     *     privileges.
     *  2) if that succeeds, set a real lock to correspond to the
     *     deny mode requested.
     *  3) whenever a file is read/written, locks get set which
     *     prevent conflicts.
     */

    /* test access */
    if (access & OPENACC_WR) {
      lockop = ADLOCK_WR;
    } else if (access & OPENACC_RD) {
      lockop = ADLOCK_RD;
    }

    /* can we access the fork? */
    if ( (lockop != ADLOCK_CLR) && 
	 ad_lock(&ofork->of_ad, eid, lockop, 0, 0, 0) < 0 ) {
      ad_close( &ofork->of_ad, adflags );
      of_dealloc( ofork );
      ofrefnum = 0;
      bcopy( &ofrefnum, rbuf, sizeof( u_short ));
      return (AFPERR_DENYCONF);
    }
      
    /* now, set the lock to the appropriate deny lock. */
    lockop = ADLOCK_CLR;
    if (access & OPENACC_DRD) {
      lockop = ADLOCK_WR;
    } else if (access & OPENACC_DWR) {
      lockop = ADLOCK_RD;
    }
      
#endif

    /* lock the file if requested */
    if (ad_lock(&ofork->of_ad, eid, lockop, 0, 0, 0) < 0 ) {
      ret = errno;
      ad_close( &ofork->of_ad, adflags );
      of_dealloc( ofork );
      switch (ret) {
      case EAGAIN: /* return data anyway */
      case EACCES:
	ofrefnum = 0;
	bcopy( &ofrefnum, rbuf, sizeof( u_short ));
	return( AFPERR_DENYCONF );
	break;
      default:
	*rbuflen = 0;
	syslog( LOG_ERR, "afp_openfork: ad_lock: %m" );
	return( AFPERR_PARAM );
      }
    }

    bcopy( &ofrefnum, rbuf, sizeof( u_short ));
    return( AFP_OK );

openfork_err:
    *rbuflen = 0;
    of_dealloc( ofork );
    return ret;
}

afp_setforkparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    int32_t		size;
    u_int16_t		ofrefnum, bitmap;
    int                 err;

    ibuf += 2;
    bcopy( ibuf, &ofrefnum, sizeof( u_short ));
    ibuf += sizeof( ofrefnum );
    memcpy(&bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs(bitmap);
    ibuf += sizeof( bitmap );
    bcopy( ibuf, &size, sizeof( int ));
    size = ntohl( size );

    *rbuflen = 0;
    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_setforkparams: of_find: %m" );
	return( AFPERR_PARAM );
    }

    if ((bitmap == (1<<FILPBIT_DFLEN)) && (ofork->of_flags & AFPFORK_DATA)) {
	err = ad_dtruncate( &ofork->of_ad, size );
	if (err < 0)
	  goto afp_setfork_err;
    } else if ((bitmap == (1<<FILPBIT_RFLEN)) &&
	       (ofork->of_flags & AFPFORK_RSRC)) {
	ad_refresh( &ofork->of_ad );
	err = ad_rtruncate(&ofork->of_ad, size);
	if (err < 0)
	  goto afp_setfork_err;
	
	if (ad_flush( &ofork->of_ad, ADFLAGS_HF ) < 0) {
	    syslog( LOG_ERR, "afp_setforkparams: ad_flush: %m" );
	    return( AFPERR_PARAM );
	}
    } else
      return AFPERR_BITMAP;

#ifdef AFS
    if ( flushfork( ofork ) < 0 ) {
	syslog( LOG_ERR, "afp_setforkparams: flushfork: %m" );
    }
#endif AFS

    return( AFP_OK );

afp_setfork_err:
    if (err == -2) 
      return AFPERR_LOCK;
    else {
      switch (errno) {
      case EROFS:
	return AFPERR_VLOCK;
      case EPERM:
      case EACCES:
	return AFPERR_ACCESS;
      case EDQUOT:
      case EFBIG:
      case ENOSPC:
	return AFPERR_DFULL;
      default:
	return AFPERR_PARAM;
      }
    }
}

/* for this to work correctly, we need to check for locks before each
 * read and write. that's most easily handled by always doing an
 * appropriate check before each ad_read/ad_write. other things
 * that can change files like truncate are handled internally to those
 * functions. 
 *
 * NOTE: i haven't implemented AFPERR_RANGEOVR (range overlap) as
 *       fcntl() doesn't complain about that and adding it in would
 *       cause problems with the synchronization locks in
 *       afp_openfork. in addition, unlocking a non-existent lock
 *       won't result in an error (AFPERR_NORANGE). 
 */
#define ENDBIT(a)  ((a) & 0x80)
#define LOCKBIT(a) ((a) & 0x01)
afp_bytelock(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    int32_t             offset, length;
    int                 eid;
    u_short		ofrefnum;
    u_int8_t            flags;

    /* figure out parameters */
    ibuf++;
    flags = *ibuf; /* first bit = endflag, lastbit = lockflag */
    ibuf++;
    bcopy( ibuf, &ofrefnum, sizeof( u_short ));
    ibuf += sizeof(ofrefnum);

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_bytelock: of_find: %m" );
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }

    if ( ad_dfileno( &ofork->of_ad ) != -1 ) {
	eid = ADEID_DFORK;
    } else if (ad_hfileno( &ofork->of_ad ) != -1 ) {
	eid = ADEID_RFORK;
    } else {
      *rbuflen = 0;
      return AFPERR_EOF;
    }

    bcopy( ibuf, &offset, sizeof( offset ));
    offset = ntohl(offset);
    ibuf += sizeof(offset);

    bcopy( ibuf, &length, sizeof( length ));
    length = ntohl(length);
    if (length == 0xFFFFFFFF)
      length = 0;
    else if (length <= 0) {
      *rbuflen = 0;
      return AFPERR_PARAM;
    }
    
    if ((offset = ad_lock(&ofork->of_ad, eid, LOCKBIT(flags) ?
			  ADLOCK_WR : ADLOCK_CLR, offset,
			  ENDBIT(flags), length)) < 0) {
      *rbuflen = 0;
      switch (errno) {
      case EACCES:
      case EAGAIN:
	return LOCKBIT(flags) ? AFPERR_LOCK : AFPERR_NORANGE;
	break;
      case ENOLCK:
	return AFPERR_NLOCK;
	break;
      case EBADF:
      case EINVAL:
      default: 
	return AFPERR_PARAM;
	break;
      }
    }

    offset = htonl(offset);
    bcopy( &offset, rbuf, sizeof( offset ));
    *rbuflen = sizeof( offset );
    return( AFP_OK );
}
#undef ENDBIT
#undef LOCKBIT


static __inline__ int crlf( of )
    struct ofork	*of;
{
    struct extmap	*em;

    if ( ad_hfileno( &of->of_ad ) == -1 ||
	 memcmp( ufinderi, ad_entry( &of->of_ad, ADEID_FINDERI ), 
		 8) == 0 ) {
	if (( em = getextmap( of->of_name )) == NULL ||
		bcmp( "TEXT", em->em_type, sizeof( em->em_type )) == 0 ) {
	    return( 1 );
	} else {
	    return( 0 );
	}
    } else {
      if ( memcmp( ufinderi, 
		   ad_entry( &of->of_ad, ADEID_FINDERI ), 4 ) == 0 ) {
	    return( 1 );
	} else {
	    return( 0 );
	}
    }
}


static __inline__ ssize_t read_file(struct ofork *ofork, int eid,
				    int offset, u_char nlmask, 
				    u_char nlchar, char *rbuf, 
				    int *rbuflen, const int xlate)
{ 
    ssize_t cc;
    int eof = 0;
    char *p, *q;

    cc = ad_read(&ofork->of_ad, eid, offset, rbuf, *rbuflen);
    if ( cc < 0 ) {
	syslog( LOG_ERR, "afp_read: ad_read: %m" );
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }
    if ( cc < *rbuflen ) {
        eof = 1;
    }

    /*
     * Do Newline check.
     */
    if ( nlmask != 0 ) {
	for ( p = rbuf, q = p + cc; p < q; ) {
	    if (( *p++ & nlmask ) == nlchar ) {
		break;
	    }
	}
	if ( p != q ) {
	    cc = p - rbuf;
	    eof = 0;
	}
    }

    /*
     * If this file is of type TEXT, then swap \012 to \015.
     */
    if (xlate) {
	for ( p = rbuf, q = p + cc; p < q; p++ ) {
	    if ( *p == '\012' ) {
		*p = '\015';
	    } else if ( *p == '\015' ) {
	        *p = '\012';
	    }
	      
	}
    }

    *rbuflen = cc;
    if ( eof ) {
	return( AFPERR_EOF );
    }
    return AFP_OK;
}

afp_read(obj, ibuf, ibuflen, rbuf, rbuflen)
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    off_t 		size;
    int32_t		offset, reqcount;
    int			cc, err, eid, xlate = 0;
    u_short		ofrefnum;
    u_char		nlmask, nlchar;

    ibuf += 2;
    bcopy( ibuf, &ofrefnum, sizeof( u_short ));
    ibuf += sizeof( u_short );

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_read: of_find: %m" );
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }

    bcopy( ibuf, &offset, sizeof( offset ));
    offset = ntohl( offset );
    ibuf += sizeof( offset );
    bcopy( ibuf, &reqcount, sizeof( reqcount ));
    reqcount = ntohl( reqcount );
    ibuf += sizeof( reqcount );

    nlmask = *ibuf++;
    nlchar = *ibuf++;

    if ( ad_dfileno( &ofork->of_ad ) != -1 ) {
	eid = ADEID_DFORK;
	xlate = ofork->of_vol->v_flags & AFPVOL_CRLF ? crlf(ofork) : 0;
    } else if (ad_hfileno( &ofork->of_ad ) != -1 ) {
	eid = ADEID_RFORK;
    } else {
      *rbuflen = 0;
      return AFPERR_EOF;
    }

    if ( reqcount < 0 ) {
      *rbuflen = 0;
      return( AFPERR_EOF );
    } else if (!reqcount) {
      *rbuflen = 0;
      return AFP_OK;
    }

    /* reqcount isn't always truthful. we need to deal with that. */
    if ((size = ad_size(&ofork->of_ad, eid)) == 0) {
      *rbuflen = 0;
      return AFPERR_EOF;
    }

    if (ad_tmplock(&ofork->of_ad, eid, ADLOCK_RD, offset, 0, 
		   reqcount) < 0) {
      *rbuflen = 0;
      return AFPERR_LOCK;
    }      

#define min(a,b)	((a)<(b)?(a):(b))
    *rbuflen = min( reqcount, *rbuflen );
    err = read_file(ofork, eid, offset, nlmask, nlchar, rbuf, rbuflen,
		    xlate);
    if (err < 0) {
      ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 0, reqcount);
      return err;
    }

    /* dsi can stream requests. we can only do this if we're not checking
     * for an end-of-line character. oh well. */
    if ((obj->proto == AFPPROTO_DSI) && (*rbuflen < reqcount) && !nlmask) {
      DSI *dsi = obj->handle;
      
      /* subtract off the offset */
      size -= offset;
      if (reqcount > size) {
	reqcount = size;
	err = AFPERR_EOF;
      }

      if (obj->options.debug) {
	printf( "(read) reply: %d/%d, %d\n", *rbuflen,
		reqcount, dsi->clientID);
	bprint(rbuf, *rbuflen);
      }

      offset += *rbuflen;

      /* dsi_readinit() returns size of next read buffer. by this point,
       * we know that we're sending some data. if we fail, something
       * horrible happened. */
      if ((*rbuflen = dsi_readinit(dsi, rbuf, *rbuflen, reqcount, err)) < 0) 
	goto afp_read_exit;
      
      /* due to the nature of afp packets, we have to exit if we get
         an error. we can't do this with translation on. */
#ifdef HAVE_SENDFILE_READ
      if (!(xlate || obj->options.debug)) {
	if (ad_readfile(&ofork->of_ad, eid, dsi->socket, offset, 
			dsi->datasize) < 0) {
	  syslog(LOG_ERR, "afp_read: ad_readfile: %m");
	  goto afp_read_exit;
	}

	dsi_readdone(dsi);
	goto afp_read_done;
      }
#endif

      /* fill up our buffer. */
      while (*rbuflen > 0) {
	cc = read_file(ofork, eid, offset, nlmask, nlchar, rbuf, 
		       rbuflen, xlate);
	if (cc < 0) 
	  goto afp_read_exit;

	offset += *rbuflen;
	if (obj->options.debug) {
	  printf( "(read) reply: %d, %d\n", *rbuflen, dsi->clientID);
	  bprint(rbuf, *rbuflen);
	}

	/* dsi_read() also returns buffer size of next allocation */
	cc = dsi_read(dsi, rbuf, *rbuflen); /* send it off */
	if (cc < 0) 
	  goto afp_read_exit;
	*rbuflen = cc;
      }
      dsi_readdone(dsi);
      goto afp_read_done;

afp_read_exit:
      syslog(LOG_ERR, "afp_read: %m");
      dsi_readdone(dsi);
      ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 0, reqcount);
      obj->exit(1);
    }

afp_read_done:
    ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 0, reqcount);
    return err;
}

afp_flush(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol *vol;
    u_int16_t vid;

    *rbuflen = 0;
    ibuf += 2;

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

    of_flush(vol);
    return( AFP_OK );
}

afp_flushfork(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    u_short		ofrefnum;

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

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_flushfork: of_find: %m" );
	return( AFPERR_PARAM );
    }

    if ( flushfork( ofork ) < 0 ) {
	syslog( LOG_ERR, "afp_flushfork: %m" );
    }

    return( AFP_OK );
}

/* this is very similar to closefork */
flushfork( ofork )
    struct ofork	*ofork;
{
    struct timeval tv;
    int len, err = 0, doflush = 0;

    if ( ad_dfileno( &ofork->of_ad ) != -1 &&
	    fsync( ad_dfileno( &ofork->of_ad )) < 0 ) {
	syslog( LOG_ERR, "flushfork: dfile(%d) %m", 
		ad_dfileno(&ofork->of_ad) );
	err = -1;
    }

    if ( ad_hfileno( &ofork->of_ad ) != -1 ) {
        if (ofork->of_flags & AFPFORK_RSRC) {
	  len = ad_getentrylen(&ofork->of_ad, ADEID_RFORK);
	  ad_refresh(&ofork->of_ad);
	  if (len != ad_getentrylen(&ofork->of_ad, ADEID_RFORK)) {
	    ad_setentrylen(&ofork->of_ad, ADEID_RFORK, len);
	    doflush++;
	  }
	}

        if ((ofork->of_flags & AFPFORK_DIRTY) &&
	    (gettimeofday(&tv, NULL) == 0)) {
	  ad_setdate(&ofork->of_ad, AD_DATE_MODIFY|AD_DATE_UNIX, tv.tv_sec);
	  ofork->of_flags &= ~AFPFORK_DIRTY;
	  doflush++;
	}

	/* flush the header. */
	if (doflush && (ad_flush(&ofork->of_ad, ADFLAGS_HF) < 0)) 
	  err = -1;
	  
	if (fsync( ad_hfileno( &ofork->of_ad )) < 0)
	  err = -1;

	if (err < 0)
	  syslog( LOG_ERR, "flushfork: hfile(%d) %m", 
		  ad_hfileno(&ofork->of_ad) );
    }

    return( err );
}

afp_closefork(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    struct timeval      tv;
    int			adflags, aint, doflush = 0;
    u_short		ofrefnum;

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

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_closefork: of_find: %m" );
	return( AFPERR_PARAM );
    }

    adflags = 0;
    if ( ad_dfileno( &ofork->of_ad ) != -1 ) {
	adflags |= ADFLAGS_DF;
    }

    if ( ad_hfileno( &ofork->of_ad ) != -1 ) {
	adflags |= ADFLAGS_HF;

	aint = ad_getentrylen( &ofork->of_ad, ADEID_RFORK );
	ad_refresh( &ofork->of_ad );
	if ((ofork->of_flags & AFPFORK_DIRTY) &&
	    (gettimeofday(&tv, NULL) == 0)) {
	    ad_setdate(&ofork->of_ad, AD_DATE_MODIFY | AD_DATE_UNIX,
		       tv.tv_sec);
	    doflush++;
	}

	/*
	 * Only set the rfork's length if we're closing the rfork.
	 */
	if ((ofork->of_flags & AFPFORK_RSRC) && aint !=
		ad_getentrylen( &ofork->of_ad, ADEID_RFORK )) {
	    ad_setentrylen( &ofork->of_ad, ADEID_RFORK, aint );
	    doflush++;
	}
	if ( doflush ) {
	    ad_flush( &ofork->of_ad, adflags );
	}
    }

    /* *** with fcntl locks, we might need to re-establish them for
     *     other open files. this means we should probably keep a
     *     record of locks that exist for files. something like a
     *     lock->fd->filename mapping should work. */
    if ( ad_close( &ofork->of_ad, adflags ) < 0 ) {
	syslog( LOG_ERR, "afp_closefork: ad_close: %m" );
	return( AFPERR_PARAM );
    }

    of_dealloc( ofork );
    return( AFP_OK );
}


static __inline__ ssize_t write_file(struct ofork *ofork, int eid,
				     off_t offset, int endflag, 
				     char *rbuf, size_t rbuflen,
				     const int xlate)
{
    char *p, *q;
    ssize_t cc;

    /*
     * If this file is of type TEXT, swap \015 to \012.
     */
    if (xlate) {
	for ( p = rbuf, q = p + rbuflen; p < q; p++ ) {
	    if ( *p == '\015' ) {
		*p = '\012';
	    } else if ( *p == '\012' ) {
	        *p = '\015';
	    }
	}
    }

    if (( cc = ad_write(&ofork->of_ad, eid, offset, endflag, 
			 rbuf, rbuflen)) < 0 ) {
	switch ( errno ) {
	case EDQUOT :
	case EFBIG :
	case ENOSPC :
	    return( AFPERR_DFULL );
	default :
	    syslog( LOG_ERR, "afp_write: ad_write: %m" );
	    return( AFPERR_PARAM );
	}
    }
    
    return cc;
}

int afp_write(obj, ibuf, ibuflen, rbuf, rbuflen)
    AFPObj              *obj;
    char                *ibuf, *rbuf;
    int                 ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    int32_t           	offset, reqcount;
    int		        endflag, cc, eid, xlate = 0;
    u_short		ofrefnum;
    int err = AFP_OK;

    /* figure out parameters */
    ibuf++;
    endflag = (unsigned char) *ibuf++;
    bcopy( ibuf, &ofrefnum, sizeof( u_short ));
    ibuf += sizeof( u_short );
    bcopy( ibuf, &offset, sizeof( offset ));
    offset = ntohl( offset );
    ibuf += sizeof( offset );
    bcopy( ibuf, &reqcount, sizeof( reqcount ));
    reqcount = ntohl( reqcount );
    ibuf += sizeof( reqcount );

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	syslog( LOG_ERR, "afp_write: of_find: %m" );
	if (obj->proto == AFPPROTO_DSI) {
	  dsi_writeinit(obj->handle, rbuf, *rbuflen);
	  dsi_writeflush(obj->handle);
	}
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }
#ifdef AFS
    writtenfork = ofork;
#endif AFS

    if ( ofork->of_flags & AFPFORK_DATA) {
	eid = ADEID_DFORK;
	xlate = (ofork->of_vol->v_flags & AFPVOL_CRLF) ? crlf(ofork) : 0;
    } else if (ofork->of_flags & AFPFORK_RSRC) {
	eid = ADEID_RFORK;
    } else 
        err = AFPERR_EOF;

    /* let's reduce the number of calls to ad_size */
    if (endflag) {
      offset += ad_size(&ofork->of_ad, eid);
      endflag = 0;
    }
      
    if (!err && (ad_tmplock(&ofork->of_ad, eid, ADLOCK_WR, offset, 
			    endflag, reqcount) < 0))
        err = AFPERR_LOCK;

    /* this is yucky, but dsi can stream i/o and asp can't */
    switch (obj->proto) {

#ifndef NO_AFPASP
    case AFPPROTO_ASP:
      if (err) {
	*rbuflen = 0;
	return err;
      }

      if (asp_wrtcont(obj->handle, rbuf, rbuflen) < 0) {
	*rbuflen = 0;
	syslog( LOG_ERR, "afp_write: asp_wrtcont: %m" );
	return( AFPERR_PARAM );
      }

      if (obj->options.debug) {
	printf("(write) len: %d\n", *rbuflen);
	bprint(rbuf, *rbuflen);
      }

      if ((cc = write_file(ofork, eid, offset, endflag, 
			   rbuf, *rbuflen, xlate)) < 0) {
	*rbuflen = 0;
	ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 
		   endflag, reqcount);
	return cc;
      }
      break;
#endif /* no afp/asp */

    case AFPPROTO_DSI:
      {
	DSI *dsi = obj->handle;
	int i;

	if (err) {
	  dsi_writeinit(obj->handle, rbuf, *rbuflen);
	  dsi_writeflush(obj->handle);
	  *rbuflen = 0;
	  return err;
	}

	/* find out what we have already and write it out. */
	if ((cc = dsi_writeinit(dsi, rbuf, *rbuflen)) == 0 ||
	    (cc = write_file(ofork, eid, offset, endflag, 
			     rbuf, cc, xlate)) < 0) {
	  dsi_writeflush(dsi);
	  *rbuflen = 0;
	  ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 
		     endflag, reqcount);
	  return cc;
	}
	
#if 0
#ifdef HAVE_SENDFILE_WRITE
	if (!(xlate || obj->options.debug)) {
	  if ((i = ad_writefile(&ofork->of_ad, eid, dsi->socket,
				offset, endflag, dsi->datasize)) < 0) {
	    switch (errno) {
	    case EDQUOT :
	    case EFBIG :
	    case ENOSPC :
	      i = AFPERR_DFULL;
	      break;
	    default :
	      syslog( LOG_ERR, "afp_write: ad_writefile: %m" );
	      goto afp_write_loop;
	    }
	    goto afp_write_err;
	  }

	  cc += i;
	  goto afp_write_done;
	}
#endif
#endif

	/* loop until everything gets written. currently
         * dsi_write handles the end case by itself. */
afp_write_loop:
	while (i = dsi_write(dsi, rbuf, *rbuflen)) {
	  if ( obj->options.debug ) {
	    printf("(write) command cont'd: %d\n", i);
	    bprint(rbuf, i);
	  }

	  if ((i = write_file(ofork, eid, offset + cc, 
			      endflag, rbuf, i, xlate)) < 0) {
afp_write_err:
	    dsi_writeflush(dsi);
	    *rbuflen = 0;
	    ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 
		   endflag, reqcount);
	    return i;
	  }
	  cc += i;
	}
      }
    break;
    }

afp_write_done:
    ad_tmplock(&ofork->of_ad, eid, ADLOCK_CLR, offset, 
	       endflag, reqcount);
    if ( ad_hfileno( &ofork->of_ad ) != -1 ) 
      ofork->of_flags |= AFPFORK_DIRTY;

    offset += cc;  
    offset = htonl( offset );
    bcopy( &offset, rbuf, sizeof(offset));
    *rbuflen = sizeof(offset);
    return( AFP_OK );
}

afp_getforkparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct ofork	*ofork;
    int			buflen, ret;
    u_short		ofrefnum, bitmap;

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

    if (( ofork = of_find( ofrefnum )) == NULL ) {
	*rbuflen = 0;
	syslog( LOG_ERR, "afp_getforkparams: of_find: %m" );
	return( AFPERR_PARAM );
    }

    buflen = *rbuflen - sizeof( u_short );
    if (( ret = getforkparams( ofork, bitmap,
	    rbuf + sizeof( u_short ), &buflen, 0 )) != AFP_OK ) {
	*rbuflen = 0;
	return( ret );
    }


    *rbuflen = buflen + sizeof( u_short );
    bitmap = htons( bitmap );
    bcopy( &bitmap, rbuf, sizeof( u_short ));
    return( AFP_OK );
}

getforkparams(ofork, bitmap, buf, buflen, attrbits )
    struct ofork	*ofork;
    u_short		bitmap;
    char		*buf;
    int			*buflen;
    const u_int16_t     attrbits;
{
    struct stat		st;
    struct extmap	*em;
    char		*data, *nameoff = NULL, *upath;
    int			bit = 0, isad = 1;
    u_int32_t           aint;
    u_int16_t		ashort;

    if ( ad_hfileno( &ofork->of_ad ) == -1 ) {
	isad = 0;
    } else {
	aint = ad_getentrylen( &ofork->of_ad, ADEID_RFORK );
	if ( ad_refresh( &ofork->of_ad ) < 0 ) {
	    syslog( LOG_ERR, "getforkparams: ad_refresh: %m");
	    return( AFPERR_PARAM );
	}
	/* See afp_closefork() for why this is bad */
	ad_setentrylen( &ofork->of_ad, ADEID_RFORK, aint );
    }

    /* can only get the length of the opened fork */
    if (((bitmap & (1<<FILPBIT_DFLEN)) && (ofork->of_flags & AFPFORK_RSRC)) ||
	((bitmap & (1<<FILPBIT_RFLEN)) && (ofork->of_flags & AFPFORK_DATA))) {
	return( AFPERR_BITMAP );
    }

    if ( bitmap & ( 1<<FILPBIT_DFLEN | 1<<FILPBIT_FNUM |
		    (1 << FILPBIT_CDATE) | (1 << FILPBIT_MDATE) |
		    (1 << FILPBIT_BDATE))) {
        upath = mtoupath(ofork->of_vol, ofork->of_name);
	if ( ad_dfileno( &ofork->of_ad ) == -1 ) {
           if ( stat( upath, &st ) < 0 )
               return( AFPERR_NOOBJ );
	} else {
	    if ( fstat( ad_dfileno( &ofork->of_ad ), &st ) < 0 ) {
		return( AFPERR_BITMAP );
	    }
	}
    }

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

	switch ( bit ) {
	case FILPBIT_ATTR :
	    if ( isad ) {
		ad_getattr(&ofork->of_ad, &ashort);
	    } else {
		ashort = 0;
	    }
	    if (attrbits)
	      ashort = htons(ntohs(ashort) | attrbits);
	    bcopy( &ashort, data, sizeof( ashort ));
	    data += sizeof( ashort );
	    break;

	case FILPBIT_PDID :
	    bcopy( &ofork->of_dir->d_did, data, sizeof( aint ));
	    data += sizeof( u_int32_t );
	    break;

	case FILPBIT_CDATE :
	    if (!isad ||
		 (ad_getdate(&ofork->of_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(&ofork->of_ad, AD_DATE_MODIFY, &aint) < 0))
		aint = AD_DATE_FROM_UNIX(st.st_mtime);
	    bcopy( &aint, data, sizeof( aint ));
	    data += sizeof( aint );
	    break;

	case FILPBIT_BDATE :
	    if (!isad ||
	        (ad_getdate(&ofork->of_ad, AD_DATE_BACKUP, &aint) < 0))
		aint = AD_DATE_START;
	    bcopy( &aint, data, sizeof( aint ));
	    data += sizeof( aint );
	    break;

	case FILPBIT_FINFO :
	    memcpy(data, isad ? 
		   (void *) ad_entry(&ofork->of_ad, ADEID_FINDERI) :
		   (void *) ufinderi, 32);

	    if ( !isad || 
		 memcmp( ad_entry( &ofork->of_ad, ADEID_FINDERI ),
			 ufinderi, 8 ) == 0 ) {
		bcopy( ufinderi, data, 8 );
		if (( em = getextmap( ofork->of_name )) != 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_int16_t);
	    break;

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

	case FILPBIT_FNUM :
	    /*
	     * See file.c getfilparams() for why this is done this
	     * way.
	     */
#if AD_VERSION > AD_VERSION1
	    if (isad) 
	      memcpy(&aint, ad_entry(&ofork->of_ad, ADEID_DID), sizeof(aint));
	    else
	      aint = 0;
	    
	    if (!(aint = cnid_add(ofork->of_vol->v_db, &st, 
				  ofork->of_dir->d_did, 
				  upath, strlen(upath), aint))) {
#endif
#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( aint ));
	    data += sizeof( aint );
	    break;

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

	default :
	    return( AFPERR_BITMAP );
	}
	bitmap = bitmap>>1;
	bit++;
    }

    if ( nameoff ) {
	ashort = htons( data - buf );
	bcopy( &ashort, nameoff, sizeof( u_short ));
	aint = strlen( ofork->of_name );
	aint = ( aint > MACFILELEN ) ? MACFILELEN : aint;
	*data++ = aint;
	bcopy( ofork->of_name, data, aint );
	data += aint;
    }

    *buflen = data - buf;
    return( AFP_OK );
}
