/*
 * 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/stat.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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>

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

afp_getfildirparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat		st;
    struct vol		*vol;
    struct dir		*directory;
    int			did, buflen, ret;
    char		*path;
    u_short		fbitmap, dbitmap, 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 (( directory = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

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

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

    if ( stat( mtoupath(vol, path ), &st ) < 0 ) {
	return( AFPERR_NOOBJ );
    }

    buflen = *rbuflen - 3 * sizeof( u_short );
    if (S_ISDIR(st.st_mode)) {
	if (( ret = getdirparams(vol, dbitmap, ".", curdir,
		&st, rbuf + 3 * sizeof( u_short ), &buflen )) != AFP_OK ) {
	    return( ret );
	}
	*(rbuf + 2 * sizeof( u_short )) = 1<<7;	/* this is a directory */
    } else {
	if (( ret = getfilparams(vol, fbitmap, path, curdir, &st,
		rbuf + 3 * sizeof( u_short ), &buflen )) != AFP_OK ) {
	    return( ret );
	}
	*(rbuf + 2 * sizeof( u_short )) = 0;	/* this is a file */
    }
    *rbuflen = buflen + 3 * sizeof( u_short );
    fbitmap = htons( fbitmap );
    bcopy( &fbitmap, rbuf, sizeof( u_short ));
    rbuf += sizeof( u_short );
    dbitmap = htons( dbitmap );
    bcopy( &dbitmap, rbuf, sizeof( u_short ));
    rbuf += sizeof( u_short ) + sizeof( u_char );
    *rbuf = 0;

    return( AFP_OK );
}

afp_setfildirparams(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat	st;
    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 ( stat( mtoupath(vol, path ), &st ) < 0 ) {
	return( AFPERR_NOOBJ );
    }

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

    if (S_ISDIR(st.st_mode)) {
	rc = setdirparams(vol, path, bitmap, ibuf );
    } else {
	rc = setfilparams(vol, path, bitmap, ibuf );
    }
    if ( rc == AFP_OK ) {
	setvoltime(obj, vol );
    }
    return( rc );
}

afp_rename(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, *odir = NULL;
    char		*path, *buf, *upath, *newpath;
    char		*newadpath;
    u_int32_t		did;
    int			plen;
    u_int16_t		vid;
#if AD_VERSION > AD_VERSION1
    cnid_t              id;
#endif

    *rbuflen = 0;
    ibuf += 2;

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

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

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

    /* another place where we know about the path type */
    if ( *ibuf++ != 2 ) {
	return( AFPERR_PARAM );
    }
    plen = (unsigned char) *ibuf++;
    *( ibuf + plen ) = '\0';

    if ( *path == '\0' ) {
	if ( curdir->d_parent == NULL ) {
	    return( AFPERR_ACCESS );
	}
	odir = curdir;
	path = curdir->d_name;
	if ( movecwd( vol, curdir->d_parent ) < 0 ) {
	    return( AFPERR_NOOBJ );
	}
    }

#ifdef notdef
    if ( strcasecmp( path, ibuf ) == 0 ) {
	return( AFP_OK );
    }
#endif notdef

    /* if a curdir/newname ofork exists, return busy */
    if (of_findname(vol, curdir, ibuf))
        return AFPERR_BUSY;

    /* source == destination */
    if (strcmp(path, ibuf) == 0)
        return AFPERR_SAMEOBJ;

    newpath = obj->oldtmp;
    strcpy( newpath, mtoupath(vol, ibuf ));

    /* the strdiacasecmp deals with case-insensitive, case preserving
       filesystems */
    if (stat( newpath, &st ) == 0 && strdiacasecmp(path, ibuf)) {
	return( AFPERR_EXIST );
    }

    upath = mtoupath(vol, path);

#if AD_VERSION > AD_VERSION1
    id = cnid_get(vol->v_db, curdir->d_did, upath, strlen(upath));
#endif

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

#if AD_VERSION > AD_VERSION1
    if (stat(newpath, &st) < 0) /* this shouldn't fail */
      return AFPERR_MISC;
    cnid_update(vol->v_db, id, &st, curdir->d_did, newpath, strlen(newpath));
#endif

    if ( !odir ) {
        newadpath = obj->newtmp;
	strcpy( newadpath, ad_path( newpath, 0 ));
	if ( rename( ad_path( upath, 0 ), newadpath ) < 0 ) {
	    if ( errno == ENOENT ) {	/* no adouble header file */
		if (( unlink( newadpath ) < 0 ) && ( errno != ENOENT )) {
		    return( AFPERR_PARAM );
		}
		goto out;
	    }
	    return( AFPERR_PARAM );
	}

	if ( ad_open( newpath, ADFLAGS_HF, O_RDWR|O_CREAT, 0666, &ad ) < 0 ) {
	    return( AFPERR_PARAM );
	}
    } else {
        int isad = 1;
	if ( ad_open( newpath, vol_noadouble(vol)|ADFLAGS_HF|ADFLAGS_DIR, 
		      O_RDWR|O_CREAT, 0666, &ad ) < 0 ) {
	    if (!((errno == ENOENT) && vol_noadouble(vol)))
	      return( AFPERR_PARAM );
	    isad = 0;
	}
	if ((buf = realloc( odir->d_name, plen + 1 )) == NULL ) {
	    syslog( LOG_ERR, "afp_rename: realloc: %m" );
	    if (isad) {
	      ad_flush(&ad, ADFLAGS_HF); /* in case of create */
	      ad_close(&ad, ADFLAGS_HF);
	    }
	    return AFPERR_MISC;
	}
	odir->d_name = buf;
	strcpy( odir->d_name, ibuf );
	if (!isad)
	  goto out;
    }

    ad_setentrylen( &ad, ADEID_NAME, plen );
    bcopy( ibuf, ad_entry( &ad, ADEID_NAME ), plen );
    ad_flush( &ad, ADFLAGS_HF );
    ad_close( &ad, ADFLAGS_HF );

out:
    setvoltime(obj, vol );

    /* if it's still open, rename the ofork as well. */
    if (of_rename(vol, curdir, path, curdir, ibuf) < 0)
	return AFPERR_MISC;

    return( AFP_OK );
}


afp_delete(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol		*vol;
    struct dir		*dir;
    char		*path, *upath;
    int			did, rc;
    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 );
    }

    if ( *path == '\0' ) {
	rc = deletecurdir( vol, obj->oldtmp, AFPOBJ_TMPSIZ);
    } else if (of_findname(vol, curdir, path)) {
        rc = AFPERR_BUSY;
    } else if ((rc = deletefile( upath = mtoupath(vol, path ))) == AFP_OK) {
#if AD_VERSION > AD_VERSION1 /* get rid of entry */
        cnid_t id = cnid_get(vol->v_db, curdir->d_did, upath, strlen(upath));
	cnid_delete(vol->v_db, id);
#endif
    }
    if ( rc == AFP_OK ) {
	setvoltime(obj, vol );
    }
    return( rc );
}

char *ctoupath( vol, dir, name )
    struct vol	*vol;
    struct dir	*dir;
    char	*name;
{
    struct dir	*d;
    static char	path[ MAXPATHLEN + 1];
    char	*p, *u;
    int		len;

    p = path + sizeof( path ) - 1;
    *p = '\0';
    u = mtoupath(vol, name );
    len = strlen( u );
    p -= len;
    strncpy( p, u, len );
    for ( d = dir; d->d_parent; d = d->d_parent ) {
	*--p = '/';
	u = mtoupath(vol, d->d_name );
	len = strlen( u );
	p -= len;
	strncpy( p, u, len );
    }
    *--p = '/';
    len = strlen( vol->v_path );
    p -= len;
    strncpy( p, vol->v_path, len );

    return( p );
}


afp_moveandrename(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct vol	*vol;
    struct dir	*sdir, *ddir, *odir = NULL;
    struct stat st;
    char	*oldname, *newname;
    char        *path, *p, *upath; 
    int		did, rc;
    int		plen;
    short	vid;
#if AD_VERSION > AD_VERSION1
    cnid_t      id;
#endif

    *rbuflen = 0;
    ibuf += 2;

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

    /* source did followed by dest did */
    bcopy( ibuf, &did, sizeof( int ));
    ibuf += sizeof( int );
    if (( sdir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_PARAM );
    }

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

    /* source pathname */
    if (( path = cname( vol, sdir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }

    sdir = curdir;
    newname = obj->newtmp;
    oldname = obj->oldtmp;
    if ( *path != '\0' ) {
        /* not a directory */
	strcpy(newname, path);
	strcpy(oldname, path); /* an extra copy for of_rename */
#if AD_VERSION > AD_VERSION1
	p = mtoupath(vol, path);
	id = cnid_get(vol->v_db, sdir->d_did, p, strlen(p));
#endif
	p = ctoupath( vol, sdir, newname );
    } else {
	odir = curdir;
	strcpy( newname, odir->d_name );
	strcpy(oldname, odir->d_name); 
	p = ctoupath( vol, odir->d_parent, newname );
#if AD_VERSION > AD_VERSION1
	id = curdir->d_did; /* we already have the CNID */
#endif
    }
    /*
     * p now points to the full pathname of the source fs object.
     */

    /* get the destination directory */
    if (( ddir = dirsearch( vol, did )) == NULL ) {
	return( AFPERR_PARAM );
    }
    if (( path = cname( vol, ddir, &ibuf )) == NULL ) {
	return( AFPERR_NOOBJ );
    }
    if ( *path != '\0' ) {
	return( AFPERR_BADTYPE );
    }

    /* one more place where we know about path type */
    if ( *ibuf++ != 2 ) {
	return( AFPERR_PARAM );
    }

    if (( plen = (unsigned char)*ibuf++ ) != 0 ) {
	strncpy( newname, ibuf, plen );
	newname[ plen ] = '\0';
    }

    upath = mtoupath(vol, newname);

    /* source == destination */
    if (curdir == sdir) {
      if (strcmp(oldname, newname) == 0)
	return AFPERR_SAMEOBJ;
      
      /* deal with case insensitive, case-preserving filesystems. */
      if ((stat(upath, &st) == 0) && strdiacasecmp(oldname, newname)) 
	return AFPERR_EXIST;
      
    } else if (stat(upath, &st ) == 0)
      return( AFPERR_EXIST );
      
    if ( !odir ) {
      if (of_findname(vol, curdir, newname)) {
	rc = AFPERR_BUSY;
      } else if ((rc = renamefile( p, upath, newname, 
				   vol_noadouble(vol) )) == AFP_OK) {
	/* if it's still open, rename the ofork as well. */
	rc = of_rename(vol, sdir, oldname, curdir, newname);
      }
    } else {
	rc = renamedir(p, upath, odir, curdir, newname, vol_noadouble(vol));
    }

    if ( rc == AFP_OK ) {
#if AD_VERSION > AD_VERSION1
        /* renaming may have moved the file/dir across a filesystem */
        if (stat(upath, &st) < 0) 
	  return AFPERR_MISC;
	
	/* fix up the catalog entry */
	cnid_update(vol->v_db, id, &st, curdir->d_did, upath, strlen(upath));
#endif      
	setvoltime(obj, vol );
    }
    return( rc );
}

