/*
 * Copyright (c) 1990,1991 Regents of The University of Michigan.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation, and that the name of The University
 * of Michigan not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. This software is supplied as is without expressed or
 * implied warranties of any kind.
 *
 *	Research Systems Unix Group
 *	The University of Michigan
 *	c/o Mike Clark
 *	535 W. William Street
 *	Ann Arbor, Michigan
 *	+1-313-763-0525
 *	netatalk@itd.umich.edu
 */

#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/syslog.h>
#include <sys/mman.h>
#ifndef MAP_FAILED
#define MAP_FAILED ((void *) -1)
#endif

#include <netatalk/endian.h>
#include <atalk/adouble.h>

/*
 * AppleDouble entry default offsets.
 * The layout looks like this:
 *
 * this is the v1 layout:
 *	  255	  200		  16	  32		  N
 *	|  NAME	|    COMMENT	| FILEI	|    FINDERI	| RFORK	|
 *
 * we need to change it to look like this:
 *
 * v2 layout:
 * field       length (in bytes)
 * NAME        255
 * COMMENT     200
 * FILEDATESI  16     replaces FILEI
 * FINDERI     32  
 * DID          4     new
 * AFPFILEI     4     new
 * SHORTNAME   12     8.3 new
 * RFORK        N
 * 
 * so, all we need to do is replace FILEI with FILEDATESI, move RFORK
 * +56 bytes, and add in the new fields.
 *
 * NOTE: the HFS module will need similar modifications to interact with
 * afpd correctly.
 */
 
/* initial lengths of some of the fields */
#define ADEDLEN_INIT     0

#define ADEID_NUM_V1         5
#define ADEDOFF_NAME_V1	     (AD_HEADER_LEN + ADEID_NUM_V1*AD_ENTRY_LEN)
#define ADEDOFF_COMMENT_V1   (ADEDOFF_NAME_V1 + ADEDLEN_NAME)
#define ADEDOFF_FILEI        (ADEDOFF_COMMENT_V1 + ADEDLEN_COMMENT)
#define ADEDOFF_FINDERI_V1   (ADEDOFF_FILEI + ADEDLEN_FILEI)
#define ADEDOFF_RFORK_V1     (ADEDOFF_FINDERI_V1 + ADEDLEN_FINDERI)

/* i stick things in a slightly different order than their eid order in 
 * case i ever want to separate RootInfo behaviour from the rest of the 
 * stuff. */
#define ADEID_NUM_V2         9
#define ADEDOFF_NAME_V2      (AD_HEADER_LEN + ADEID_NUM_V2*AD_ENTRY_LEN)
#define ADEDOFF_COMMENT_V2   (ADEDOFF_NAME_V2 + ADEDLEN_NAME)
#define ADEDOFF_FILEDATESI   (ADEDOFF_COMMENT_V2 + ADEDLEN_COMMENT)
#define ADEDOFF_FINDERI_V2   (ADEDOFF_FILEDATESI + ADEDLEN_FILEDATESI)
#define ADEDOFF_DID	     (ADEDOFF_FINDERI_V2 + ADEDLEN_FINDERI)
#define ADEDOFF_AFPFILEI     (ADEDOFF_DID + ADEDLEN_DID)
#define ADEDOFF_SHORTNAME    (ADEDOFF_AFPFILEI + ADEDLEN_AFPFILEI)
#define ADEDOFF_PRODOSFILEI  (ADEDOFF_SHORTNAME + ADEDLEN_SHORTNAME)
#define ADEDOFF_RFORK_V2     (ADEDOFF_PRODOSFILEI + ADEDLEN_PRODOSFILEI)



/* we keep local copies of a bunch of stuff so that we can initialize things 
 * correctly. */

/* Bits in the finderinfo data. 
 * see etc/afpd/{directory.c,file.c} for the finderinfo structure
 * layout. */
#define FINDERINFO_CUSTOMICON 0x4
#define FINDERINFO_CLOSEDVIEW 0x100

/* offsets in finderinfo */
#define FINDERINFO_FLAGSOFF    8
#define FINDERINFO_FRVIEWOFF  14

/* this is to prevent changing timezones from causing problems with
   localtime volumes. the screw-up is 30 years. we use a delta of 5
   years.  */
#define TIMEWARP_DELTA 157680000


struct entry {
  u_int32_t id, offset, len;
};

#if AD_VERSION == AD_VERSION1 
static const struct entry entry_order[] = {
  {ADEID_NAME, ADEDOFF_NAME_V1, ADEDLEN_INIT},
  {ADEID_COMMENT, ADEDOFF_COMMENT_V1, ADEDLEN_INIT},
  {ADEID_FILEI, ADEDOFF_FILEI, ADEDLEN_FILEI},
  {ADEID_FINDERI, ADEDOFF_FINDERI_V1, ADEDLEN_FINDERI},
  {ADEID_RFORK, ADEDOFF_RFORK_V1, ADEDLEN_INIT},
  {0, 0, 0}
};
#else if AD_VERSION == AD_VERSION2
static const struct entry entry_order[] = {
  {ADEID_NAME, ADEDOFF_NAME_V2, ADEDLEN_INIT},
  {ADEID_COMMENT, ADEDOFF_COMMENT_V2, ADEDLEN_INIT},
  {ADEID_FILEDATESI, ADEDOFF_FILEDATESI, ADEDLEN_FILEDATESI},
  {ADEID_FINDERI, ADEDOFF_FINDERI_V2, ADEDLEN_FINDERI},
  {ADEID_DID, ADEDOFF_DID, ADEDLEN_DID},
  {ADEID_AFPFILEI, ADEDOFF_AFPFILEI, ADEDLEN_AFPFILEI},
  {ADEID_SHORTNAME, ADEDOFF_SHORTNAME, ADEDLEN_INIT},
  {ADEID_PRODOSFILEI, ADEDOFF_PRODOSFILEI, ADEDLEN_PRODOSFILEI},
  {ADEID_RFORK, ADEDOFF_RFORK_V2, ADEDLEN_INIT},
  {0, 0, 0}
};
#endif

#if AD_VERSION == AD_VERSION2

#ifndef __inline__
#define __inline__
#endif

static __inline__ int ad_v1tov2(struct adouble *ad, const char *path)
{
   /* fix some stuff up if we're given a path. */
   if (path && (ad->ad_version == AD_VERSION1)) {
      struct stat st;
      struct timeval tv;
      u_int16_t attr;
      char *buf;
      int fd, err = 1;

      /* convert from v1 to v2. what does this mean?
       *  1) change FILEI into FILEDATESI
       *  2) create space for SHORTNAME, AFPFILEI, DID, and PRODOSI
       *  3) move FILEI attributes into AFPFILEI
       *  4) initialize ACCESS field of FILEDATESI.
       *
       *  so, we need 4*12 (entry ids) + 12 (shortname) + 4 (afpfilei) +
       *  4 (did) + 8 (prodosi) = 76 more bytes.  */

#define SHIFTDATA (AD_DATASZ2 - AD_DATASZ1)

      /* bail if we can't get a lock */
      if (ad_tmplock(ad, ADEID_RFORK, ADLOCK_WR, 0, 0, 0) < 0) 
	goto bail_err;

      if ((fd = open(path, O_RDWR)) < 0) 
	goto bail_lock;
      
      if (gettimeofday(&tv, NULL) < 0) 
        goto bail_lock;

      if (fstat(fd, &st) ||
	  ftruncate(fd, st.st_size + SHIFTDATA) < 0) {
	goto bail_open;
      }
	  
      /* last place for failure. */
      if ((void *) (buf = (char *) 
		    mmap(NULL, st.st_size + SHIFTDATA,
			 PROT_WRITE, MAP_SHARED, fd, 0)) == 
	  MAP_FAILED) {
	goto bail_truncate;
      }

      err = 0;
      /* move the RFORK */
      memmove(buf + ad->ad_eid[ADEID_RFORK].ade_off + SHIFTDATA,
	      buf + ad->ad_eid[ADEID_RFORK].ade_off, 
	      ad->ad_eid[ADEID_RFORK].ade_len);

#if 0
      /* shift any offsets that might have gotten changed. we actually
         don't use any of the other eids, so this isn't necessary. */
      for (eid = 0; eid < ADEID_MAX; eid++) {
	if (ad->ad_eid[ eid ].ade_off) {
	    ad->ad_eid[ eid ].ade_off += SHIFTDATA;
	}
      }
#endif

      /* now, fix up our copy of the header */
      memset(ad->ad_filler, 0, sizeof(ad->ad_filler));

      /* replace FILEI with FILEDATESI */
      ad_getattr(ad, &attr);
      ad->ad_eid[ADEID_FILEDATESI].ade_off = ADEDOFF_FILEDATESI;
      ad->ad_eid[ADEID_FILEDATESI].ade_len = ADEDLEN_FILEDATESI;
      ad->ad_eid[ADEID_FILEI].ade_off = 0;
      ad->ad_eid[ADEID_FILEI].ade_len = 0;

      /* add in the new entries */
      ad->ad_eid[ADEID_DID].ade_off = ADEDOFF_DID;
      ad->ad_eid[ADEID_DID].ade_len = ADEDLEN_DID;
      ad->ad_eid[ADEID_AFPFILEI].ade_off = ADEDOFF_AFPFILEI;
      ad->ad_eid[ADEID_AFPFILEI].ade_len = ADEDLEN_AFPFILEI;
      ad->ad_eid[ADEID_SHORTNAME].ade_off = ADEDOFF_SHORTNAME;
      ad->ad_eid[ADEID_SHORTNAME].ade_len = ADEDLEN_INIT;
      ad->ad_eid[ADEID_PRODOSFILEI].ade_off = ADEDOFF_PRODOSFILEI;
      ad->ad_eid[ADEID_PRODOSFILEI].ade_len = ADEDLEN_PRODOSFILEI;

      /* shift the old entries (NAME, COMMENT, FINDERI, RFORK) */
      ad->ad_eid[ADEID_NAME].ade_off = ADEDOFF_NAME_V2;
      ad->ad_eid[ADEID_COMMENT].ade_off = ADEDOFF_COMMENT_V2;
      ad->ad_eid[ADEID_FINDERI].ade_off = ADEDOFF_FINDERI_V2;
      ad->ad_eid[ADEID_RFORK].ade_off = ADEDOFF_RFORK_V2;

      /* switch to v2 */
      ad->ad_version = AD_VERSION2;

      /* move our data buffer to make space for the new entries. */
      memmove(ad->ad_data + ADEDOFF_NAME_V2, ad->ad_data + ADEDOFF_NAME_V1,
	      ADEDOFF_RFORK_V1 - ADEDOFF_NAME_V1);

      /* now, fill in the space with appropriate stuff. we're
         operating as a v2 file now. */
      ad_setdate(ad, AD_DATE_ACCESS | AD_DATE_UNIX, tv.tv_sec);
      memset(ad_entry(ad, ADEID_DID), 0, ADEDLEN_DID);
      memset(ad_entry(ad, ADEID_AFPFILEI), 0, ADEDLEN_AFPFILEI);
      ad_setattr(ad, attr);
      memset(ad_entry(ad, ADEID_SHORTNAME), 0, ADEDLEN_SHORTNAME);
      memset(ad_entry(ad, ADEID_PRODOSFILEI), 0, ADEDLEN_PRODOSFILEI);
      
      /* rebuild the header */
      ad_rebuild_header(ad);

      /* write the header back out and cleanup */
      memcpy(buf, ad->ad_data, sizeof(ad->ad_data));
      munmap(buf, st.st_size + SHIFTDATA);
      close(fd);
      ad_tmplock(ad, ADEID_RFORK, ADLOCK_CLR, 0, 0, 0);
      return 0;

bail_truncate:
      ftruncate(fd, st.st_size);
bail_open:
      close(fd);
bail_lock:
      ad_tmplock(ad, ADEID_RFORK, ADLOCK_CLR, 0, 0, 0);
bail_err:
      return err;
    }
    return( 0 );
}
#endif



/*
 * Put the .AppleDouble where it needs to be:
 *
 *	    /	a/.AppleDouble/b
 *	a/b
 *	    \	b/.AppleDouble/.Parent
 */
char *
ad_path( path, adflags )
    char	*path;
    int		adflags;
{
    static char	pathbuf[ MAXPATHLEN + 1];
    char	c, *slash, buf[MAXPATHLEN + 1];

    strncpy(buf, path, MAXPATHLEN);
    if ( adflags & ADFLAGS_DIR ) {
	strncpy( pathbuf, buf, MAXPATHLEN );
	if ( *buf != '\0' ) {
	    strcat( pathbuf, "/" );
	}
	slash = ".Parent";
    } else {
	if (( slash = strrchr( buf, '/' )) != NULL ) {
	    c = *++slash;
	    *slash = '\0';
	    strncpy( pathbuf, buf, MAXPATHLEN);
	    *slash = c;
	} else {
	    pathbuf[ 0 ] = '\0';
	    slash = buf;
	}
    }
    strncat( pathbuf, ".AppleDouble/", MAXPATHLEN - strlen(pathbuf));
    strncat( pathbuf, slash, MAXPATHLEN - strlen(pathbuf));

    return( pathbuf );
}

/*
 * Support inherited protection modes for AppleDouble files.  The supplied
 * mode is ANDed with the parent directory's mask value in lieu of "umask",
 * and that value is returned.
 */

#define DEFMASK 7700	/* be conservative */

int
ad_mode( path, mode )
    char		*path;
    int			mode;
{
    static char		modebuf[ MAXPATHLEN + 1];
    struct stat		stbuf;
    char 		*slash;

    if ( mode == 0 ) {
	return( mode );		/* save on syscalls */
    }

    if ( strlen( path ) >= MAXPATHLEN ) {
	return( mode & DEFMASK );  /* can't do it */
    }

    /*
     * For a path with directories in it, remove the final component
     * (path or subdirectory name) to get the name we want to stat.
     * For a path which is just a filename, use "." instead.
     */
    strcpy( modebuf, path );
    if (( slash = strrchr( modebuf, '/' )) != NULL ) {
	*slash = '\0';		/* remove pathname component */
    } else {
	modebuf[0] = '.';	/* use current directory */
	modebuf[1] = '\0';
    }

    if ( stat( modebuf, &stbuf ) != 0 ) {
	return( mode & DEFMASK );	/* bail out... can't stat dir? */
    }

    return( mode & stbuf.st_mode );
}

/*
 * Use mkdir() with mode bits taken from ad_mode().
 */
int
ad_mkdir( path, mode )
    char		*path;
    int			mode;
{
    return mkdir( path, ad_mode( path, mode ) );
}

/*
 * It's not possible to open the header file O_RDONLY -- the read
 * will fail and return an error.
 */
ad_open( path, adflags, oflags, mode, ad )
    char		*path;
    int			adflags, oflags, mode;
    struct adouble	*ad;
{
    const struct entry  *eid;
    struct stat         st;
    char		*slash, *ad_p;
    int			hoflags, admode;
    u_int16_t           fileview;

    ad_dfileno(ad) = -1;
    ad_hfileno(ad) = -1;
    ad_dlock(ad).l_type = ADLOCK_CLR;
    ad_hlock(ad).l_type = ADLOCK_CLR;
    ad->ad_flags = adflags;

    if ( adflags & ADFLAGS_DF ) {
	if (( ad->ad_df.adf_fd =
		open( path, oflags, ad_mode( path, mode ) )) < 0 ) {
	    return( -1 );
	}
	ad->ad_df.adf_off = 0;
	ad->ad_df.adf_flags = oflags;
    }

    if ( adflags & ADFLAGS_HF ) {
	ad_p = ad_path( path, adflags );
	admode = ad_mode( ad_p, mode );

	hoflags = oflags & ~O_CREAT;
	if (( ad->ad_hf.adf_fd = open( ad_p, hoflags, admode )) < 0 ) {
	    if ( errno == ENOENT && hoflags != oflags ) {
		/*
		 * We're expecting to create a new adouble header file,
		 * here.
		 */
		errno = 0;
		if (( ad->ad_hf.adf_fd = open( ad_p, oflags,
			admode )) < 0 ) {
		    /*
		     * Probably .AppleDouble doesn't exist, try to
		     * mkdir it.
		     */
		    if ((errno == ENOENT) && 
			((adflags & ADFLAGS_NOADOUBLE) == 0)) {
			if (( slash = strrchr( ad_p, '/' )) == NULL ) {
			    ad_close( ad, adflags );
			    return( -1 );
			}
			*slash = '\0';
			errno = 0;
			if ( ad_mkdir( ad_p, 0777 ) < 0 ) {
			    ad_close( ad, adflags );
			    return( -1 );
			}
			*slash = '/';
			if (( ad->ad_hf.adf_fd = open( ad_p, oflags,
				ad_mode( ad_p, mode) )) < 0 ) {
			    ad_close( ad, adflags );
			    return( -1 );
			}
		    } else {
			ad_close( ad, adflags );
			return( -1 );
		    }
		}
		ad->ad_hf.adf_flags = oflags;
	    } else {
		ad_close( ad, adflags );
		return( -1 );
	    }
	} else if ((fstat(ad->ad_hf.adf_fd, &st) == 0) && 
		   (st.st_size == 0)) {
	    /* for 0 length files, treat them as new. */
	    ad->ad_hf.adf_flags = oflags;
	} else {
	    ad->ad_hf.adf_flags = hoflags;
	}
	ad->ad_hf.adf_off = 0;

	/*
	 * This is a new adouble header file. Initialize the structure,
	 * instead of reading it.
	 */
	bzero( (char *)ad->ad_eid, sizeof( ad->ad_eid ));
	if ( ad->ad_hf.adf_flags & ( O_TRUNC | O_CREAT )) {
	    struct timeval tv;

	    ad->ad_magic = AD_MAGIC;
	    ad->ad_version = AD_VERSION;
	    bzero( ad->ad_filler, sizeof( ad->ad_filler ));
	    bzero( ad->ad_data, sizeof( ad->ad_data ));
	    
	    eid = entry_order;
	    while (eid->id) {
	      ad->ad_eid[eid->id].ade_off = eid->offset;
	      ad->ad_eid[eid->id].ade_len = eid->len;
	      eid++;
	    }

	    /* put something sane in the directory finderinfo */
	    if (adflags & ADFLAGS_DIR) {
		/* set default view */
	        fileview = htons(FINDERINFO_CLOSEDVIEW);
		bcopy(&fileview, ad_entry(ad, ADEID_FINDERI) +
		      FINDERINFO_FRVIEWOFF, sizeof(fileview));
	    }

	    if (gettimeofday(&tv, NULL) < 0) {
	      ad_close(ad, adflags);
	      return -1;
	    }

	    /* put something sane in the date fields */
	    ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, tv.tv_sec);
	    ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, tv.tv_sec);
	    ad_setdate(ad, AD_DATE_ACCESS | AD_DATE_UNIX, tv.tv_sec);
	    ad_setdate(ad, AD_DATE_BACKUP, AD_DATE_START);
	} else {
	    /*
	     * Read the adouble header in and parse it.
	     */
	    if ((ad_refresh( ad ) < 0)
#if AD_VERSION == AD_VERSION2
		|| (ad_v1tov2(ad, ad_p) < 0)
#endif
		) {
		ad_close( ad, adflags );
		return( -1 );
	    }
	}
    }
    return( 0 );
}


ad_refresh( ad )
    struct adouble	*ad;
 
{
    u_int32_t		eid, len, off;
    char		*buf, *end;
    u_int16_t		nentries;
    static int          warning = 0;

    if ( ad->ad_hf.adf_fd == -1 ) {
	return( -1 );
    }

    if ( ad->ad_hf.adf_off != 0 ) {
	if ( lseek( ad->ad_hf.adf_fd, 0L, SEEK_SET ) < 0L ) {
	    return( -1 );
	}
	ad->ad_hf.adf_off = 0;
    }

    /* read the header */
    if ( read( ad->ad_hf.adf_fd, ad->ad_data, AD_HEADER_LEN) !=
	 AD_HEADER_LEN) {
	if ( errno == 0 ) {
	    errno = EIO;
	}
	return( -1 );
    }
    ad->ad_hf.adf_off = AD_HEADER_LEN;

    /*
     * we know that magic, version, homefs, and nentries are less
     * than data, so we don't check whether we exceed end.
     */
    buf = ad->ad_data;
    bcopy( buf, (char *)&ad->ad_magic, sizeof( ad->ad_magic ));
    ad->ad_magic = ntohl( ad->ad_magic );
    buf += sizeof( ad->ad_magic );
    bcopy( buf, (char *)&ad->ad_version, sizeof( ad->ad_version ));
    ad->ad_version = ntohl( ad->ad_version );

    /* tag broken v1 headers. just assume they're all right. 
     * XXX: in the future, you'll need the v1compat flag. */
    if (/*(ad->ad_flags & ADFLAGS_V1COMPAT) && */
	!ad->ad_magic && !ad->ad_version) {
        if (!warning) {
	   syslog(LOG_DEBUG, "warning: fixing up broken v1 header.");
	   warning++;
	}
      ad->ad_magic = AD_MAGIC;
      ad->ad_version = AD_VERSION1;
    }

    if ((ad->ad_magic != AD_MAGIC) || ((ad->ad_version != AD_VERSION1)
#if AD_VERSION == AD_VERSION2
				       && (ad->ad_version != AD_VERSION2)
#endif
				       )) {
      errno = EIO;
      syslog(LOG_DEBUG, "ad_refresh: can't parse AppleDouble header.");
      return -1;
    }

    buf += sizeof( ad->ad_version );
    bcopy( buf, ad->ad_filler, sizeof( ad->ad_filler ));
    buf += sizeof( ad->ad_filler );
    bcopy( buf, (char *)&nentries, sizeof( nentries ));
    nentries = ntohs( nentries );
    buf += sizeof( nentries );

    /* now, read in the entries. we cheat and assume that we can infer the 
     * number from the appledouble version. */
    if (((ad->ad_version == AD_VERSION1) && /* v1 */ 
	 (read(ad->ad_hf.adf_fd, buf, len = (AD_DATASZ1 - AD_HEADER_LEN)) != 
	  len))
#if AD_VERSION == AD_VERSION2
	|| ((ad->ad_version == AD_VERSION2) && /* v2 */
	    (read(ad->ad_hf.adf_fd, buf, len = (AD_DATASZ2 - AD_HEADER_LEN)) !=
	     len))
#endif
	) {
	if ( errno == 0 ) {
	    errno = EIO;
	}
	return( -1 );
    }


    ad->ad_hf.adf_off += len;
    end = ad->ad_data + len;
    for (; nentries > 0; nentries-- ) {
	if ( buf + sizeof( eid ) + sizeof( off ) + sizeof( len ) > end ) {
	    errno = EIO;
	    return( -1 );
	}

	bcopy( buf, (char *)&eid, sizeof( eid ));
	eid = ntohl( eid );
	buf += sizeof( eid );
	bcopy( buf, (char *)&off, sizeof( off ));
	off = ntohl( off );
	buf += sizeof( off );
	bcopy( buf, (char *)&len, sizeof( len ));
	len = ntohl( len );
	buf += sizeof( len );

	if ( 0 < eid && eid < ADEID_MAX ) {
	    ad->ad_eid[ eid ].ade_off = off;
	    ad->ad_eid[ eid ].ade_len = len;
	} else {
	    syslog( LOG_DEBUG, "ad_refresh: nentries %hd  eid %d\n",
		    nentries, eid );
	}
    }

    /* fix up broken dates */
    if (ad->ad_version == AD_VERSION1) {
      struct stat st;
      int32_t aint;
      
      if (fstat(ad->ad_hf.adf_fd, &st) < 0) {
	return 1; /* fail silently */
      }
      
      /* check to see if the ad date is wrong. just see if we have
      * a modification date in the future. */
      if (((ad_getdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, &aint)) == 0) &&
	  (aint > TIMEWARP_DELTA + st.st_mtime)) {
	ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, aint - AD_DATE_DELTA);
	ad_getdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, &aint);
	ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, aint - AD_DATE_DELTA);
	ad_getdate(ad, AD_DATE_BACKUP | AD_DATE_UNIX, &aint);
	ad_setdate(ad, AD_DATE_BACKUP | AD_DATE_UNIX, aint - AD_DATE_DELTA);
      }
    }

    return 0;
}

