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

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

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

#ifndef NO_LARGE_VOL_SUPPORT
#if BYTE_ORDER == BIG_ENDIAN
#define hton64(x)       (x)
#else
#define hton64(x)       ((u_int64_t) (htonl(((x) >> 32) & 0xffffffffLL)) | \
                         (u_int64_t) ((htonl(x) & 0xffffffffLL) << 32))
#endif
#endif

static struct vol *volumes = NULL;
static int		lastvid = 0;
#if AD_VERSION == AD_VERSION1
static char		*Trash = "\02\024Network Trash Folder";
#endif
static struct extmap	*extmap = NULL, *defextmap = NULL;

#define VOLOPT_NUM  9
#define VOLPASSLEN  8
struct vol_options {
  char *access, *codepage, *dbpath, *allowdots, *password;
  int casefold, flags;
};

/* i do it this way to keep it next to the vol_options declaration */
static __inline__ void volfree(struct vol_options *options)
{
  if (options->codepage) 
    free(options->codepage);
  if (options->access) 
    free(options->access); 
  if (options->allowdots)
    free(options->allowdots);
  if (options->password)
    free(options->password);
#if AD_VERSION > AD_VERSION1
  if (options->dbpath)
    free(options->dbpath);
#endif
}

/* to make sure that val is valid, make sure to select an opt that
   includes val */
#define optionok(buf,opt,val) (strstr((buf),(opt)) && ((val)[1] != '\0'))

/* handle all the options. tmp can't be NULL. */
static void volset(struct vol_options *options, char *volname,
		   int vlen, const char *nlspath, const char *tmp)
{
  char *val;
  int len;

  val = strchr(tmp, '=');
  if (optionok(tmp, "access=", val)) {
    if (options->access)
      free(options->access);
    options->access = strdup(val + 1);

  } else if (optionok(tmp, "codepage=", val)) {
    if (options->codepage)
      free(options->codepage);
    
    if (nlspath) {
      options->codepage = (char *) malloc((len = strlen(nlspath)) + 
					  strlen(val + 1) + 2);
      if (options->codepage) {
	strcpy(options->codepage, nlspath);
	if (nlspath[len - 1] != '/') /* add a / */
	  strcat(options->codepage, "/");
	strcat(options->codepage, val + 1);
      }
    } else {
      options->codepage = strdup(val + 1);
    }

  } else if (optionok(tmp, "casefold=", val)) {
    if (strcasecmp(val + 1, "tolower") == 0)
      options->casefold = AFPVOL_UMLOWER;
    else if (strcasecmp(val + 1, "toupper") == 0)
      options->casefold = AFPVOL_UMUPPER;
    else if (strcasecmp(val + 1, "xlatelower") == 0)
      options->casefold = AFPVOL_UUPPERMLOWER;
    else if (strcasecmp(val + 1, "xlateupper") == 0)
      options->casefold = AFPVOL_ULOWERMUPPER;

  } else if (optionok(tmp, "options=", val)) {
    char *p;
    
    if ((p = strtok(val + 1, ",")) == NULL) /* nothing */
      return;
    
    while (p) {
      if (strcasecmp(p, "prodos") == 0)
	options->flags |= AFPVOL_A2VOL;
      else if (strcasecmp(p, "crlf") == 0)
	options->flags |= AFPVOL_CRLF;
      else if (strcasecmp(p, "noadouble") == 0)
	options->flags |= AFPVOL_NOADOUBLE;
      p = strtok(NULL, ",");
    }

#if AD_VERSION > AD_VERSION1
  } else if (optionok(tmp, "dbpath=", val)) {
    if (options->dbpath)
      free(options->dbpath);
    
    options->dbpath = strdup(val + 1);
#endif
  } else if (optionok(tmp, "allowdots=",val)) {
    if (options->allowdots)
      free(options->allowdots);
    options->allowdots = strdup(val + 1);

  } else if (optionok(tmp, "password=", val)) {
    if (options->password)
      free(options->password);
    options->password = strdup(val + 1);

  } else {
    /* we'll assume it's a volume name. 
     * NOTE: this is where we should stick variable substitutions. */
    strncpy(volname, tmp, vlen);
  }
}

static int creatvol(const char *path, char *name, 
		     struct vol_options *options)
{
    struct vol	*volume;
    int		vlen;

    if ( name == NULL || *name == '\0' ) {
	if (( name = strrchr( path, '/' )) == NULL ) {
	    return -1;	/* Obviously not a fully qualified path */
	}
	name++;
    }

    for ( volume = volumes; volume; volume = volume->v_next ) {
	if ( strcasecmp( volume->v_name, name ) == 0 ) {
	    return -1;	/* Won't be able to access it, anyway... */
	}
    }

    vlen = strlen( name );
    if ( vlen > 27 ) {
	return -1;
    }

    if (( volume =
	    (struct vol *)calloc(1, sizeof( struct vol ))) == NULL ) {
	syslog( LOG_ERR, "creatvol: malloc: %m" );
	return -1;
    }
    if (( volume->v_name =
	    (char *)malloc( vlen + 1 )) == NULL ) {
	syslog( LOG_ERR, "creatvol: malloc: %m" );
	free(volume);
	return -1;
    }
    if (( volume->v_path =
	    (char *)malloc( strlen( path ) + 1 )) == NULL ) {
	syslog( LOG_ERR, "creatvol: malloc: %m" );
	free(volume->v_name);
	free(volume);
	return -1;
    }

    strcpy( volume->v_name, name );
    strcpy( volume->v_path, path );

#ifdef __svr4__
    volume->v_qfd = -1;
#endif
    volume->v_vid = lastvid++;
    volume->v_lastdid = 3;

    /* handle options */
    if (options) {
      /* should we casefold? */
      volume->v_casefold = options->casefold;

      /* shift in some flags */
      volume->v_flags = options->flags;

      /* read in the code pages */
      if (options->codepage)
	codepage_read(volume, options->codepage);

      if (options->password) 
	volume->v_password = strdup(options->password);

#if AD_VERSION > AD_VERSION1
      if (options->dbpath)
	volume->v_dbpath = strdup(options->dbpath);
#endif
    }

    volume->v_next = volumes;
    volumes = volume;
    return 0;
}

static char *myfgets( buf, size, fp )
    char	*buf;
    int		size;
    FILE	*fp;
{
    char	*p;
    int		c;

    p = buf;
    while ((( c = getc( fp )) != EOF ) && ( size > 0 )) {
	if ( c == '\n' || c == '\r' ) {
	    *p++ = '\n';
	    break;
	} else {
	    *p++ = c;
	}
	size--;
    }

    if ( p == buf ) {
	return( NULL );
    } else {
	*p = '\0';
	return( buf );
    }
}


/* check access list. this function wants something of the following
 * form:
 *        @group,name,name2,@group2,name3
 *
 * a NULL argument allows everybody to have access.
 */
static int accessvol(args, name)
    const char *args;
    const char *name;
{
    char buf[MAXPATHLEN + 1], *p;
    struct group *gr;

    if (!args)
      return 1;

    strncpy(buf, args, sizeof(buf));
    if ((p = strtok(buf, ",")) == NULL) /* nothing, return okay */
      return 1;

    while (p) {
      if (*p == '@') { /* it's a group */
	if ((gr = getgrnam(p + 1)) && gmem(gr->gr_gid))
	    return 1;
      } else if (strcmp(p, name) == 0) /* it's a user name */
	return 1;
      p = strtok(NULL, ",");
    }

    return 0;
}

/*
 * Read a volume configuration file and add the volumes contained within to
 * the global volume list.  If p2 is non-NULL, the file that is opened is
 * p1/p2
 *
 * Lines that begin with # and blank lines are ignored.
 * Volume lines are of the form:
 *		<unix path> [<volume name>] [access=<access>,...] \
 *                           [codepage=<file>] [casefold=<num>]
 *		<extension> TYPE [CREATOR]
 */
static int readvolfile(nlspath, p1, p2, user )
    char        *nlspath;
    char	*p1, *p2;
    int		user;
{
    FILE		*fp;
    char		path[ MAXPATHLEN + 1], tmp[ MAXPATHLEN + 1],
			volname[ 28 ], buf[ BUFSIZ ],
			type[ 5 ], creator[ 5 ];
    char		*u, *p;
    struct passwd	*pw;
    struct vol_options  options;
    int                 i;

    if (!p1)
        return -1;

    strcpy( path, p1 );
    if ( p2 != NULL ) {
	strcat( path, "/" );
	strcat( path, p2 );
    }

    if (( fp = fopen( path, "r" )) == NULL ) {
	return( -1 );
    }

    while ( myfgets( buf, sizeof( buf ), fp ) != NULL ) {
	initline( strlen( buf ), buf );
	parseline( sizeof( path ) - 1, path );
	switch ( *path ) {
	case '\0' :
	case '#' :
	    continue;

	case '~' :
	    if (( p = strchr( path, '/' )) != NULL ) {
		*p++ = '\0';
	    }
	    u = path;
	    u++;
	    if ( *u == '\0' ) {
		u = username;
	    }
	    if ( u == NULL || ( pw = getpwnam( u )) == NULL ) {
		continue;
	    }
	    strcpy( tmp, pw->pw_dir );
	    if ( p != NULL && *p != '\0' ) {
		strcat( tmp, "/" );
		strcat( tmp, p );
	    }
	    strcpy( path, tmp );
	    /* fall through */

	case '/' :
	    /* this is sort of braindead. basically, i wan't to be
	     * able to specify things in any order, but i don't want to 
	     * re-write everything. 
	     *
	     * currently we have 9 options: 
	     *   volname
	     *   codepage=x
	     *   casefold=x
	     *   access=x,y,@z
	     *   options=prodos,crlf,noadouble
	     *   dbpath=x
	     *   password=x
	     *   allowdots=x,y,z  (not implemented yet)
	     */
	    memset(&options, 0, sizeof(options));
	    *volname = '\0';

	    /* read in up to 9 possible options */
	    for (i = 0; i < VOLOPT_NUM; i++) {
	      if (parseline( sizeof( tmp ) - 1, tmp ) < 0)
		break;

	      volset(&options, volname, sizeof(volname), 
		     nlspath, tmp);
	    }

	    if (accessvol(options.access, username))
	      creatvol( path, volname, &options);
	    volfree(&options);
	    break;

	case '.' :
	    parseline( sizeof( type ) - 1, type );
	    parseline( sizeof( creator ) - 1, creator );
	    setextmap( path, type, creator, user);
	    break;

	default :
	    break;
	}
    }
    if ( fclose( fp ) != 0 ) {
	syslog( LOG_ERR, "readvolfile: fclose: %m" );
    }
    return( 0 );
}

static void load_volumes(AFPObj *obj)
{
  struct passwd	*pwent;
  char *path = obj->options.nlspath;

  if ( !obj->options.uservolfirst ) {
    readvolfile(path, obj->options.systemvol, NULL, 0 );
  }

  if ( username == NULL || obj->options.nouservol) {
    readvolfile(path, obj->options.defaultvol, NULL, 1 );
  } else if (( pwent = getpwnam( username )) != NULL ) {
    /*
	 * Read user's AppleVolumes or .AppleVolumes file
	 * If neither are readable, read the default volumes file
	 */
    if ( readvolfile(path, pwent->pw_dir, "AppleVolumes", 1 ) < 0 &&
	 readvolfile(path, pwent->pw_dir, ".AppleVolumes", 1 ) < 0 &&
	 readvolfile(path, pwent->pw_dir, "applevolumes", 1 ) < 0 &&
	 readvolfile(path, pwent->pw_dir, ".applevolumes", 1 ) < 0 &&
	 obj->options.defaultvol != NULL ) {
      if ( readvolfile(path, obj->options.defaultvol, NULL, 1 ) < 0 ) {
	creatvol( pwent->pw_dir, NULL, NULL);
      }
    }
  }
  if ( obj->options.uservolfirst ) {
    readvolfile(path, obj->options.systemvol, NULL, 0 );
  }
}

afp_getsrvrparms(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct timeval	tv;
    struct stat		st;
    struct vol		*volume;
    char	*data;
    int			vcnt, len;

  
    if (!volumes)
      load_volumes(obj);

    data = rbuf + 5;
    for ( vcnt = 0, volume = volumes; volume; volume = volume->v_next ) {
	if ( stat( volume->v_path, &st ) < 0 ) {
	    syslog( LOG_INFO, "afp_getsrvrparms: stat %s: %m", volume->v_path );
	    continue;		/* can't access directory */
	}
	if (!S_ISDIR(st.st_mode)) {
	    continue;		/* not a dir */
	}

	/* set password bit if there's a volume password */
	*data = (volume->v_password) ? AFPSRVR_PASSWD : 0;

 	/* Apple 2 clients running ProDOS-8 expect one volume to have
 	   bit 0 of this byte set.  They will not recognize anything
 	   on the server unless this is the case.  I have not
 	   completely worked this out, but it's related to booting
 	   from the server.  Support for that function is a ways
 	   off.. <shirsch@ibm.net> */
	*data++ |= (volume->v_flags & AFPVOL_A2VOL) ? AFPSRVR_CONFIGINFO : 0;
	len = strlen( volume->v_name );
	*data++ = len;
	memcpy(data, volume->v_name, len );
	data += len;
	vcnt++;
    }

    *rbuflen = data - rbuf;
    data = rbuf;
    if ( gettimeofday( &tv, 0 ) < 0 ) {
	syslog( LOG_ERR, "afp_getsrvrparms: gettimeofday: %m" );
	*rbuflen = 0;
	return AFPERR_PARAM;
    }
    tv.tv_sec = AD_DATE_FROM_UNIX(tv.tv_sec);
    memcpy(data, &tv.tv_sec, sizeof( u_int32_t));
    data += sizeof( u_int32_t);
    *data = vcnt;
    return( AFP_OK );
}

setextmap( ext, type, creator, user)
    char		*ext, *type, *creator;
    int			user;
{
    struct extmap	*em;

    for ( em = extmap; em; em = em->em_next ) {
	if ( strdiacasecmp( em->em_ext, ext ) == 0 ) {
	    break;
	}
    }

    if ( em == NULL ) {
	if (( em =
		(struct extmap *)malloc( sizeof( struct extmap ))) == NULL ) {
	    syslog( LOG_ERR, "setextmap: malloc: %m" );
	    return;
	}
	em->em_next = extmap;
	extmap = em;
    } else if ( !user ) {
	return;
    }

    strcpy( em->em_ext, ext );

    if ( *type == '\0' ) {
	memcpy(em->em_type, "????", sizeof( em->em_type ));
    } else {
	memcpy(em->em_type, type, sizeof( em->em_type ));
    }
    if ( *creator == '\0' ) {
	memcpy(em->em_creator, "UNIX", sizeof( em->em_creator ));
    } else {
	memcpy(em->em_creator, creator, sizeof( em->em_creator ));
    }

    if ( strcmp( ext, "." ) == 0 ) {
	defextmap = em;
    }
}

    struct extmap *
getextmap( path )
    char	*path;
{
    char	*p;
    struct extmap	*em;

    if (( p = strrchr( path, '.' )) == NULL ) {
	return( defextmap );
    }

    for ( em = extmap; em; em = em->em_next ) {
	if ( strdiacasecmp( em->em_ext, p ) == 0 ) {
	    break;
	}
    }
    if ( em == NULL ) {
	return( defextmap );
    } else {
	return( em );
    }
}

afp_openvol(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat	st;
    char	*volname;
#if AD_VERSION == AD_VERSION1
    char *p;
#endif
    struct vol	*volume;
    struct dir	*dir;
    int		len, ret, buflen;
    u_int16_t	bitmap;

    ibuf += 2;
    memcpy(&bitmap, ibuf, sizeof( bitmap ));
    bitmap = ntohs( bitmap );
    ibuf += sizeof( bitmap );
    if (( bitmap & (1<<VOLPBIT_VID)) == 0 ) {
        ret = AFPERR_BITMAP;
	goto openvol_err;
    }

    len = (unsigned char)*ibuf++;
    volname = obj->oldtmp;
    memcpy(volname, ibuf, len );
    *(volname +  len) = '\0';
    ibuf += len;
    if ((len + 1) & 1) /* pad to an even boundary */
      ibuf++;

    if (!volumes)
      load_volumes(obj);

    for ( volume = volumes; volume; volume = volume->v_next ) {
	if ( strcasecmp( volname, volume->v_name ) == 0 ) {
	    break;
	}
    }

    if ( volume == NULL ) {
	ret = AFPERR_PARAM;
	goto openvol_err;
    }

    /* check for a volume password */
    if (volume->v_password && 
	strncmp(ibuf, volume->v_password, VOLPASSLEN)) {
        ret = AFPERR_ACCESS;
	goto openvol_err;
    }

    if (( volume->v_flags & AFPVOL_OPEN  ) == 0 ) {
	if (( dir = (struct dir *)calloc(1, sizeof( struct dir ))) == NULL ) {
	    syslog( LOG_ERR, "afp_openvol: malloc: %m" );
	    ret = AFPERR_MISC;
	    goto openvol_err;
	}
	dir->d_did = htonl( DIRDID_ROOT );
	if (( dir->d_name = (char *)malloc( strlen( volume->v_name ) + 1 ))
		== NULL ) {
	    syslog( LOG_ERR, "afp_openvol: malloc: %m" );
	    free(dir);
	    ret = AFPERR_MISC;
	    goto openvol_err;
	}
	strcpy( dir->d_name, volume->v_name );
	volume->v_dir = dir;
#if AD_VERSION > AD_VERSION1
	volume->v_db = cnid_open(volume->v_dbpath ? volume->v_dbpath :
				 volume->v_path); /* default */
	/* if we're read-only, attempt to make sure that fake ids are
	 * out of the range of already allocated ones. */
	volume->v_lastdid += cnid_nextid(volume->v_db);
#endif
	volume->v_flags |= AFPVOL_OPEN;
    }

    if ( stat( volume->v_path, &st ) < 0 ) {
	ret = AFPERR_PARAM;
	goto openvol_err;
    }

    buflen = *rbuflen - sizeof( bitmap );
    if (( ret = getvolparams( bitmap, volume, &st,
	    rbuf + sizeof(bitmap), &buflen )) != AFP_OK ) {
        goto openvol_err;
    }
    *rbuflen = buflen + sizeof( bitmap );
    bitmap = htons( bitmap );
    memcpy(rbuf, &bitmap, sizeof( bitmap ));

    curdir = volume->v_dir;
    if ( chdir( volume->v_path ) < 0 ) {
        ret = AFPERR_PARAM;
	goto openvol_err;
    }
#if AD_VERSION == AD_VERSION1
    /*
     * If you mount a volume twice, the second time the trash appears on
     * the desk-top.  That's because the Mac remembers the DID for the
     * trash (even for volumes in different zones, on different servers).
     * Just so this works better, we prime the DID cache with the trash,
     * fixing the trash at DID 3.
     */
    p = Trash;
    cname( volume, volume->v_dir, &p );
#endif

    return( AFP_OK );

openvol_err:
    *rbuflen = 0;
    return ret;
}

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

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

    vol->v_flags &= ~AFPVOL_OPEN;
    for ( ovol = volumes; ovol; ovol = ovol->v_next ) {
	if ( ovol->v_flags & AFPVOL_OPEN ) {
	    break;
	}
    }
    if ( ovol != NULL ) {
	curdir = ovol->v_dir;
	if ( chdir( ovol->v_path ) < 0 ) {
	    return( AFPERR_PARAM );
	}
    }

    freedir( vol->v_dir );
    vol->v_dir = NULL;
#if AD_VERSION > AD_VERSION1
    cnid_close(vol->v_db);
    vol->v_db = NULL;
#endif
    return( AFP_OK );
}

freedir( dir )
    struct dir	*dir;
{
    if ( dir->d_left != NULL ) {
	freedir( dir->d_left );
    }
    if ( dir->d_right != NULL ) {
	freedir( dir->d_right );
    }
    free( dir->d_name );
    free( dir );
}

afp_getvolparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat	st;
    struct vol	*vol;
    int		buflen, ret;
    u_int16_t	vid, bitmap;

    ibuf += 2;
    memcpy(&vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    memcpy(&bitmap, ibuf, sizeof( bitmap ));
    bitmap = ntohs( bitmap );

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

    if ( stat( vol->v_path, &st ) < 0 ) {
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }

    buflen = *rbuflen - sizeof( bitmap );
    if (( ret = getvolparams( bitmap, vol, &st,
	    rbuf + sizeof( bitmap ), &buflen )) != AFP_OK ) {
	*rbuflen = 0;
	return( ret );
    }
    *rbuflen = buflen + sizeof( bitmap );
    bitmap = htons( bitmap );
    memcpy(rbuf, &bitmap, sizeof( bitmap ));
    return( AFP_OK );
}

afp_setvolparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct adouble ad;
    struct vol	*vol;
    u_int16_t	vid, bitmap;
    u_int32_t   aint;

    ibuf += 2;
    *rbuflen = 0;

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

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

    /* we can only set the backup date. */
    if (bitmap != VOLPBIT_BDATE)
      return AFPERR_BITMAP;

    if ( ad_open( vol->v_path, ADFLAGS_HF|ADFLAGS_DIR, O_RDWR, 
		  0666, &ad ) < 0 ) {
      if (errno == EROFS)
	return AFPERR_VLOCK;

      return AFPERR_ACCESS;
    }

    memcpy(&aint, ibuf, sizeof(aint));
    ad_setdate(&ad, AD_DATE_BACKUP, aint);
    ad_flush(&ad, ADFLAGS_HF);
    ad_close(&ad);
    return( AFP_OK );
}

getvolspace( vol, bfree, btotal, xbfree, xbtotal, bsize )
    struct vol	*vol;
    u_int32_t	*bfree, *btotal, *bsize;
    VolSpace    *xbfree, *xbtotal;
{
    int	        spaceflag, rc;
    u_int32_t   maxsize;
    VolSpace	qfree, qtotal;

    spaceflag = AFPVOL_GVSMASK & vol->v_flags;
    /* report up to 2GB if afp version is < 2.2 (4GB if not) */
    maxsize = (vol->v_flags & AFPVOL_A2VOL) ? 0x01fffe00 :
      ((afp_version < 22) ? 0x7fffffffL : 0xffffffffL);

#ifdef AFS
    if ( spaceflag == AFPVOL_NONE || spaceflag == AFPVOL_AFSGVS ) {
	if ( afs_getvolspace( vol, xbfree, xbtotal, bsize ) == AFP_OK ) {
	    vol->v_flags = ( ~AFPVOL_GVSMASK & vol->v_flags ) | AFPVOL_AFSGVS;
	    goto getvolspace_done;
	}
    }
#endif AFS

    if (( rc = ustatfs_getvolspace( vol, xbfree, xbtotal,
				    bsize)) != AFP_OK ) {
	return( rc );
    }

#define min(a,b)	((a)<(b)?(a):(b))
    if ( spaceflag == AFPVOL_NONE || spaceflag == AFPVOL_UQUOTA ) {
	if ( uquota_getvolspace( vol, &qfree, &qtotal, *bsize ) == AFP_OK ) {
	    vol->v_flags = ( ~AFPVOL_GVSMASK & vol->v_flags ) | AFPVOL_UQUOTA;
	    *xbfree = min(*xbfree, qfree);
	    *xbtotal = min( *xbtotal, qtotal);
	    goto getvolspace_done;
	}
    }
    vol->v_flags = ( ~AFPVOL_GVSMASK & vol->v_flags ) | AFPVOL_USTATFS;

getvolspace_done:
    *bfree = min( *xbfree, maxsize);
    *btotal = min( *xbtotal, maxsize);
    return( AFP_OK );
}

getvolparams( bitmap, vol, st, buf, buflen )
    u_short	bitmap;
    struct vol	*vol;
    struct stat	*st;
    char	*buf;
    int		*buflen;
{
    struct adouble	ad;
    int			bit = 0, aint, isad = 1;
    u_short		ashort;
    u_int32_t		bfree, btotal, bsize;
    VolSpace            xbfree, xbtotal; /* extended bytes */
    char		*data, *nameoff = NULL;
    char                *slash;

    /* courtesy of jallison@whistle.com:
     * For MacOS8.x support we need to create the
     * .Parent file here if it doesn't exist. */
    
    if ( ad_open( vol->v_path, vol_noadouble(vol) | 
		  ADFLAGS_HF|ADFLAGS_DIR, O_RDWR | O_CREAT, 
		  0666, &ad ) < 0 ) {
	  isad = 0;

    } else if (ad_getoflags( &ad, ADFLAGS_HF ) & O_CREAT) {
          slash = strrchr( vol->v_path, '/' );
          if(slash)
              slash++;
          else
              slash = vol->v_path;

	  ad_setentrylen( &ad, ADEID_NAME, strlen( slash ));
	  bcopy( slash, ad_entry( &ad, ADEID_NAME ),
		 ad_getentrylen( &ad, ADEID_NAME ));
	  ad_flush(&ad, ADFLAGS_HF);
    }

    if (( bitmap & ( (1<<VOLPBIT_BFREE)|(1<<VOLPBIT_BTOTAL) |
		     (1<<VOLPBIT_XBFREE)|(1<<VOLPBIT_XBTOTAL) |
		     (1<<VOLPBIT_BSIZE)) ) != 0 ) {
	if ( getvolspace( vol, &bfree, &btotal, &xbfree, &xbtotal,
			  &bsize) < 0 ) {
	    if ( isad ) {
		ad_close( &ad, ADFLAGS_HF );
	    }
	    return( AFPERR_PARAM );
	}
    }

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

	switch ( bit ) {
	case VOLPBIT_ATTR :
#if AD_VERSION > AD_VERSION1
	    ashort = VOLPBIT_ATTR_FILEID;
#else
	    ashort = 0;
#endif
	    /* check for read-only */
	    if ((utime(vol->v_path, NULL) < 0) && (errno == EROFS))
	      ashort |= VOLPBIT_ATTR_RO;
	    ashort = htons(ashort);
	    memcpy(data, &ashort, sizeof( ashort ));
	    data += sizeof( ashort );
	    break;

	case VOLPBIT_SIG :
	    ashort = htons( AFPVOLSIG_DEFAULT );
	    memcpy(data, &ashort, sizeof( ashort ));
	    data += sizeof( ashort );
	    break;

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

	case VOLPBIT_MDATE :
	    if ( st->st_mtime > vol->v_time ) {
		vol->v_time = st->st_mtime;
		aint = AD_DATE_FROM_UNIX(st->st_mtime);
	    } else {
		aint = AD_DATE_FROM_UNIX(vol->v_time);
	    }
	    memcpy(data, &aint, sizeof( aint ));
	    data += sizeof( aint );
	    break;

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

	case VOLPBIT_VID :
	    memcpy(data, &vol->v_vid, sizeof( vol->v_vid ));
	    data += sizeof( vol->v_vid );
	    break;

	case VOLPBIT_BFREE :
	    bfree = htonl( bfree );
	    memcpy(data, &bfree, sizeof( bfree ));
	    data += sizeof( bfree );
	    break;

	case VOLPBIT_BTOTAL :
	    btotal = htonl( btotal );
	    memcpy(data, &btotal, sizeof( btotal ));
	    data += sizeof( btotal );
	    break;

#ifndef NO_LARGE_VOL_SUPPORT
	case VOLPBIT_XBFREE :
	    xbfree = hton64( xbfree );
	    memcpy(data, &xbfree, sizeof( xbfree ));
	    data += sizeof( xbfree );
	    break;

	case VOLPBIT_XBTOTAL :
	    xbtotal = hton64( xbtotal );
	    memcpy(data, &xbtotal, sizeof( xbtotal ));
	    data += sizeof( xbfree );
	    break;
#endif

	case VOLPBIT_NAME :
	    nameoff = data;
	    data += sizeof( u_int16_t );
	    break;

	case VOLPBIT_BSIZE:  /* block size */
	    bsize = htonl(bsize);
	    memcpy(data, &bsize, sizeof(bsize));
	    data += sizeof(bsize);
	    break;

	default :
	    if ( isad ) {
	        ad_close( &ad, ADFLAGS_HF );
	    }
	    return( AFPERR_BITMAP );
	}
	bitmap = bitmap>>1;
	bit++;
    }
    if ( nameoff ) {
	ashort = htons( data - buf );
	memcpy(nameoff, &ashort, sizeof( ashort ));
	aint = strlen( vol->v_name );
	*data++ = aint;
	memcpy(data, vol->v_name, aint );
	data += aint;
    }
    if ( isad ) {
        ad_close( &ad, ADFLAGS_HF );
    }
    *buflen = data - buf;
    return( AFP_OK );
}

struct vol *
getvolbyvid( vid )
    short	vid;
{
    struct vol	*vol;

    for ( vol = volumes; vol; vol = vol->v_next ) {
	if ( vid == vol->v_vid ) {
	    break;
	}
    }
    if ( vol == NULL || ( vol->v_flags & AFPVOL_OPEN ) == 0 ) {
	return( NULL );
    }

    return( vol );
}

setvoltime(obj, vol )
    AFPObj *obj;
    struct vol	*vol;
{
    struct timeval	tv;

    /* fail w/out dying */
    if ( gettimeofday( &tv, 0 ) < 0 ) {
	syslog( LOG_ERR, "setvoltime: gettimeofday: %m" );
	return;
    }
    
    /* a little granularity */
    if (vol->v_time < tv.tv_sec) {
      vol->v_time = tv.tv_sec;
      obj->attention(obj->handle, AFPATTN_NOTIFY | AFPATTN_VOLCHANGED);
    }
}
