/*
    ncopy.c - Copy file on a Netware server without Network Traffic
    Copyright (C) 1996 by Brian Reid and Tom Henderson.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


    Send bug reports for ncopy to "breid@tim.com"
 
    Still to do: support recursive copy with two arguments
    Both must be directories. (similar to rcp -r)


    Revision history:

	0.01  1996			Brian Reid and Tom Henderson
		Initial revision.

	0.02  1999			Petr Vandrovec <vandrove@vc.cvut.cz>
		Copy trustees and other Netware informations.

	1.00  1999, November 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added license.
		
	1.01  1999, November 21		Petr Vandrovec <vandrove@vc.cvut.cz>
		Copy MAC resource fork.

	1.02  2000, June 17		Petr Vandrovec <vandrove@vc.cvut.cz>
		Recursive copy.

	1.03  2001, August 9		Petr Vandrovec <vandrove@vc.cvut.cz>
		Copy trustees and other Netware informations on directories.
		Added -u option to copy different files only.

 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <ctype.h>
#include <mntent.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <dirent.h>
#include <ncp/nwcalls.h>

#include "private/libintl.h"
#define _(X) gettext(X)

#define dfprintf(X...) fprintf(X)

/****************************************************************************
 * Globals:
 *
 */
const char *VersionStr = "1.02";
static char *ProgramName;

#define RSRCSUBDIR	".rsrc/"

/* (initialized) command options  */

static int optVersion = 0;		/* -V TRUE if just want version */
static int optVerbose = 0;		/* -v TRUE if want verbose output */
static int optRecursive = 0;		/* -r TRUE if want recursive copy */
static int optNice = 0;			/* -n TRUE if we are cooperative (nice) */
static int optNiceFactorSel = 0;	/* -s TRUE if we selected a nice factor */
static int optNiceFactor = 10;		/* -s arg, number of 1M blocks to copy 
				   before sleeping for a second */
static u_int32_t CopyBlockSize = 1048576;	/* Size of the default block copy size */
static unsigned int NiceSleepTime = 1;	/* Number of seconds to sleep in Nice Mode */

static int optOwner = 0;		/* -pp */
static int optFlags = 0;		/* -p */
static int optTimes = 0;		/* -p too... Hack source... */
static int optTrustees = 0;		/* -t */
static int optMAC = 0;			/* -m */
static int optMACinRsrc = 0;		/* -M */
static int optOnlyChanged = 0;		/* -u */
static int BlocksCopied = 0;		/* Number of blocks copied */
static unsigned int MaxNcopyRetries = 25;	/* Maximum number of times to retry a failed
				   copy before giving up  */

/* Globals needed for signal handlers */
static NWCONN_HANDLE CurrentConn = NULL;	/* Connection of output file */
static u_int8_t *CurrentFile = NULL;	/* File info of output file */

/* Signal control structures */
static struct sigaction sHangupSig;
static struct sigaction sInterruptSig;
static struct sigaction sQuitSig;
static struct sigaction sTermSig;

/****************************************************************************
 *
 */
static void
usage(void) {
	fprintf(stderr, _("usage: %s [-V]\n"), ProgramName);
	fprintf(stderr, _("       %s [-vmMnppt] [-s amt] sourcefile destinationfile|directory\n"), ProgramName);
	fprintf(stderr, _("       %s [-vmMnppt] [-s amt] sourcefile [...] directory\n"), ProgramName);
	fprintf(stderr, _("       %s [-vmMnppt] [-s amt] -r sourcedir directory\n"), ProgramName);
}

/****************************************************************************
 * Return pointer to last component of the path.  
 * Returned string may have one or more "/" left on the end. 
 * ("/" returns pointer to "/", null returns pointer to null)
 * Return pointer to original string if no "/" in string. (except at end)
 */
static const char *
myBaseName(const char *path) {
	const char *p;

	for (p = &path[strlen(path)]; p != path; p--)
	{			/* skip ENDING "/" chars */
		if (*p && *p != '/')
			break;
	}
	if (p == path)
		return p;
	for (; p != path || *p == '/'; p--)
	{
		if (*p == '/')
			return ++p;
	}
	return p;
}

/****************************************************************************
 *
 */
static const char *
notDir(const char *path)
{
	struct stat buf;

	if (stat(path, &buf))
		return strerror(errno);		/* no permission? not exist? */
	if (!S_ISDIR(buf.st_mode))
		return _("not a directory");	/* not a directory */
	return NULL;	/* OK */
}

/****************************************************************************
 *
 */
static int
handleOptions(const int argc, char *const argv[])
{
	int opt;

	while ((opt = getopt(argc, argv, "vVns:ptmMru")) != EOF)
	{
		switch (opt)
		{

		case 'V':	/* Version */
			optVersion = 1;
			break;

		case 'v':	/* Verbose output */
			optVerbose = 1;
			break;

		case 'n':	/* Nice, cooperative copy */
			optNice = 1;
			break;

		case 's':	/* Nice Factor */
			optNiceFactorSel = 1;
			optNiceFactor = atoi(optarg);
			if (optNiceFactor < 1)
			{
				fprintf(stderr, _("%s: -s option requires positive numeric argument > 0\n"),
					ProgramName);
				return 1;
			}
			break;

		case 'p':	/* preserve flags */
			if (optTimes)
				optOwner = 1;	/* multiple 'p' */
			optFlags = optTimes = 1;
			break;
			
		case 't':
			optTrustees = 1;
			break;

		case 'm':	/* copy MAC resource fork */
			optMAC = 1;
			break;
			
		case 'M':	/* MAC resource fork in .rsrc subdirectory */
			optMACinRsrc = 1;
			break;
			
		case 'r':
			optRecursive = 1;
			break;

		case 'u':
			optOnlyChanged = 1;
			break;

		default:	/* invalid options or options without required arguments */
			return 1;
		}
		continue;
	}
	return 0;
}

/****************************************************************************
 * TODO: if recursive flag last MUST be a directory, even if only 2 args.
 */
static int
validateFileArgs(const int argc, char *const argv[])
{
	const char *p;
	if (argc == 0)
	{
		fprintf(stderr, _("%s: No arguments specified.\n"), ProgramName);
		return 1;
	}
	if (argc == 1)
	{
		fprintf(stderr, _("%s: No destination specified.\n"), ProgramName);
		return 1;
	}
	if ((optRecursive || argc > 2) && (p = notDir(argv[argc - 1])))
	{			/* last arg MUST be dir */
		fprintf(stderr, _("%s: %s: %s\n"), ProgramName, argv[argc - 1], p);
		return 1;
	}
	return 0;
}

static int xlatid(NWCONN_HANDLE scon, NWObjectID sID,
		NWCONN_HANDLE dcon, NWObjectID *dID) {
	NWCCODE err;
	char objectName[256];
	NWObjectType objectType;

	if (sID == 0) {
		/* set zero owner returns 0x8901 error from ncp_modify... */
		return 0;
	}	
	if (scon == dcon) {
		*dID = sID;
		return 1;
	}
	err = NWGetObjectName(scon, sID, objectName, &objectType);
	if (err) {
		fprintf(stderr, _("Cannot convert ID %08X to name: %s\n"),
			sID, strnwerror(err));
		return 0;
	}
	err = NWGetObjectID(dcon, objectName, objectType, dID);
	if (err) {
		fprintf(stderr, _("Cannot convert name %s(%04X) to ID: %s\n"),
			objectName, objectType, strnwerror(err));
		return 0;
	}
	return 1;
}

/****************************************************************************
 *  Interfaces with the ncplib to do the netware copy of the file.
 */
static int
copyNWNW(
		const char* header,
		int sfd,
		NWCONN_HANDLE scon,
		const u_int8_t* sfhandle,
		const char* source,
		int dfd,
		NWCONN_HANDLE dcon,
		const u_int8_t* dfhandle,
		const char* destination) {
	ncp_off64_t totalSize;
	ncp_off64_t sourceOff;
	NWCCODE err;
	unsigned int retryCount;
	int failed;
	int ncopy = scon && (scon == dcon);
	int dolf = optVerbose;

	if (scon) {
		err = ncp_get_file_size(scon, sfhandle, &totalSize);
		if (err)
			totalSize = 0;
	} else {
		struct stat stats;
		
		err = fstat(sfd, &stats);
		if (err)
			totalSize = 0;
		else
			totalSize = stats.st_size;
	}

	sourceOff = 0;
	retryCount = 0;

	if (optVerbose)	{
		if (totalSize)
			printf(_("%s: %s -> %s %5.1f%%"), header, source, destination, 0.0);
		else
			printf(_("%s: %s -> %s %lld"), header, source, destination, 0LL);
		fflush(stdout);
	}
	failed = -1;
	while (retryCount < MaxNcopyRetries) {
		if (ncopy) {
			u_int32_t amountCopied;

			/* If we are being nice and we've copied enough blocks, go to sleep */
			if (optNice)
			{
				if (BlocksCopied == optNiceFactor)
				{
					sleep(NiceSleepTime);
					BlocksCopied = 0;
				} else
					++BlocksCopied;
			}
			err = ncp_copy_file(dcon, sfhandle,
					    dfhandle, sourceOff, 
					    sourceOff,
					    CopyBlockSize, &amountCopied);
			if (err) {
				/* In my testing this only happens when you run out of space 
			   	on the server.
			   	Netware seems to wait a bit before reporting space recently
			   	free'd.  I will just wait a bit before bombin */
				sleep(1);	/* Sleep for a second and try again */
				retryCount++;
			} else {
				if (!amountCopied) {
					ncopy = 0;
				}
				sourceOff += amountCopied;
			}
		} else {
			u_int8_t buffer[49152];
			long bytes;
			long written;
			
			if (scon)
				bytes = ncp_read(scon, sfhandle, sourceOff, sizeof(buffer), buffer);
			else
				bytes = read(sfd, buffer, sizeof(buffer));
			if (bytes < 0) {
				dolf = 0;
				if (optVerbose)
					putchar('\n');
				fprintf(stderr, _("%s: Cannot read %s\n"),
					ProgramName,
					source);
				break;
			}
			if (bytes == 0) {
				/* whole file? */
				if (sourceOff >= totalSize) {
					failed = 0;
					break;
				}
				sleep(1);
				retryCount++;
			} else {
				if (dcon)
					written = ncp_write(dcon, dfhandle, sourceOff, bytes, buffer);
				else
					written = write(dfd, buffer, bytes);
				if (written != bytes) {
					dolf = 0;
					if (optVerbose)
						putchar('\n');
					fprintf(stderr, _("%s: Cannot write %s\n"),
						ProgramName,
						destination);
					break;
				}
				sourceOff += bytes;
			}
		}
		if (optVerbose)
		{
			putchar('\r');
			if (totalSize && (totalSize >= sourceOff))
				printf(_("%s: %s -> %s %5.1f%%"), header, source, destination,
			       		((float) sourceOff) / totalSize * 100.0);
			else
				printf(_("%s: %s -> %s %lld"), header, source, destination,
					sourceOff);
			if (retryCount)
				printf(_("  %d retries"), retryCount);
			fflush(stdout);
		}
	}
	if (dolf)
		putchar('\n');
	return failed;
}

static int
netwareCopyFile(int fdIn, int fdOut,
		const char *source,
		const char *destination,
		mode_t sourceMode, int dofile)
{
	NWCCODE err;
	struct nw_info_struct2 sfi, dfi;
	u_int8_t sfhandle[6];
	u_int8_t dfhandle[6];
	NWCONN_HANDLE scon, dcon;
	struct NWCCRootEntry sentry, dentry;
	int retValue = 2;
	int ncopy = 0;

	err = ncp_open_fd(fdIn, &scon);
	if (err) {
		dfprintf(stderr, _("%s: Unable to open source file: %s\n"),
			ProgramName, strnwerror(err));
		scon = NULL;
		goto srcDetermined;
	}
	err = NWCCGetConnInfo(scon, NWCC_INFO_ROOT_ENTRY, 
		sizeof(sentry), &sentry);
	if (err) {
		dfprintf(stderr, _("%s: Unable to stat source file: %s\n"),
			ProgramName, strnwerror(err));
		ncp_close(scon);
		scon = NULL;
		goto srcDetermined;
	}
	if (dofile) {
		err = ncp_ns_open_create_entry(scon, NW_NS_DOS, SA_ALL | 0x48,
			1, sentry.volume, sentry.dirEnt, NULL, 0, 
			-1, OC_MODE_OPEN, 0, AR_READ_ONLY, 
			RIM_ALL,
			&sfi, sizeof(sfi), NULL, NULL, sfhandle);
	} else {
		err = ncp_ns_obtain_entry_info(scon, NW_NS_DOS, SA_ALL | 0x48,
			1, sentry.volume, sentry.dirEnt, NULL, 0,
			NW_NS_DOS, RIM_ALL,
			&sfi, sizeof(sfi));
	}
	if (err) {
		dfprintf(stderr, _("%s: Unable to open source file: %s\n"),
			ProgramName, strnwerror(err));
		ncp_close(scon);
		scon = NULL;
		goto srcDetermined;
	}

srcDetermined:;
	{
		/* Grrr... All kernels since 2.1.43 up to 2.2.10 & 2.3.9 corrupts 
		           DosDirNum on file open :-( stat() fixes it, fstat does not */
		struct stat statbuf;
		stat(destination, &statbuf);
	}
	err = ncp_open_fd(fdOut, &dcon);
	if (err) {
		dfprintf(stderr, _("%s: Unable to open output file: %s\n"),
			ProgramName, strnwerror(err));
		dcon = NULL;
		goto dstDetermined;
	}
	err = NWCCGetConnInfo(dcon, NWCC_INFO_ROOT_ENTRY, 
		sizeof(dentry), &dentry);
	if (err) {
		dfprintf(stderr, _("%s: Unable to stat destination file: %s\n"),
			ProgramName, strnwerror(err));
		ncp_close(dcon);
		dcon = NULL;
		goto dstDetermined;
	}
	if (dofile) {
		err = ncp_ns_open_create_entry(dcon, NW_NS_DOS, SA_ALL,
			1, dentry.volume, dentry.dirEnt, NULL, 0,
			-1, OC_MODE_OPEN, 0, AR_WRITE_ONLY,
			0,
			&dfi, sizeof(dfi), NULL, NULL, dfhandle);
		if (err) {
			dfprintf(stderr, _("%s: Unable to open destination file: %s\n"),
				ProgramName, strnwerror(err));
			ncp_close(dcon);
			dcon = NULL;
			goto dstDetermined;
		}
	}
dstDetermined:;
	if (scon && dcon) {	
		NWCONN_NUM snum, dnum;
		
		if (!NWGetConnectionNumber(scon, &snum) 
		 && !NWGetConnectionNumber(dcon, &dnum)
		 && (snum == dnum)) {
			/* there is a chance that it is same connection */
			unsigned char saddr[100];
			unsigned char daddr[100];
			NWCCTranAddr srec = {0, sizeof(saddr), saddr};
			NWCCTranAddr drec = {0, sizeof(daddr), daddr};

			err = NWCCGetConnInfo(scon, NWCC_INFO_TRAN_ADDR, sizeof(srec), &srec);
			if (!err) err = NWCCGetConnInfo(dcon, NWCC_INFO_TRAN_ADDR, sizeof(drec), &drec);
			if (!err) {
				if ((srec.type == drec.type) 
				 && (srec.len == drec.len)
				 && !memcpy(saddr, daddr, srec.len)) {
				 	ncopy = 1;
				}
			} else {
				err = NWCCGetConnInfo(scon, NWCC_INFO_SERVER_NAME, sizeof(saddr), saddr);
				if (!err) err = NWCCGetConnInfo(dcon, NWCC_INFO_SERVER_NAME, sizeof(daddr), daddr);
				if (!err) {
					ncopy = !strcmp(saddr, daddr);
				}
			}
			if (ncopy) {
				ncp_close(scon);
				scon = dcon;
			}
		}
	}
	if (dofile) {
		/* Set globals in case a signal happens while copying */
		CurrentConn = dcon;
		CurrentFile = dfhandle;

		retValue = copyNWNW(_("NetWare copy"),
				    fdIn, scon, sfhandle, source,
				    fdOut, dcon, dfhandle, destination);
		if (dcon) {
			if (ncp_close_file(dcon, dfhandle) != 0)
			{
				fprintf(stderr, _("%s: Close failed for %s\n"), ProgramName, destination);
				retValue = 1;
			}
			CurrentFile = NULL;
		}
		if (scon) {
			if (ncp_close_file(scon, sfhandle) != 0)
			{
				fprintf(stderr, _("%s: Close failed for %s\n"), ProgramName, source);
				if (!retValue)
					retValue = 1;
			}
		}
		if ((optMAC || optMACinRsrc) && !retValue) {
			NWCONN_HANDLE sMAC = NULL;
			NWCONN_HANDLE dMAC = NULL;
			int sFD = -1;
			int dFD = -1;
			const char* sourceRsrc = source;
			const char* destinationRsrc = destination;
			char sourceB[MAXPATHLEN+1];
			char destinationB[MAXPATHLEN+1];

			if (scon && optMAC) {
				struct nw_info_struct2 srcMAC;

				err = ncp_ns_obtain_entry_info(scon, NW_NS_DOS, SA_ALL,
					1, sentry.volume, sentry.dirEnt, NULL, 0,
					NW_NS_MAC, RIM_DIRECTORY,
					&srcMAC, sizeof(srcMAC));
				if (err) {
					/* it is not fatal... it is user problem... */
					dfprintf(stderr, _("MAC namespace is not supported on source %s: %s\n"), source, strnwerror(err));
					/* look for .rsrc */
				} else {
					err = ncp_ns_open_create_entry(scon, NW_NS_MAC, SA_ALL,
						1, srcMAC.Directory.volNumber, srcMAC.Directory.dirEntNum, NULL, 0,
						1, OC_MODE_OPEN, 0, AR_READ_ONLY,
						0, 
						NULL, 0, NULL, NULL, sfhandle);
					if (err) {
						/* NWE_SERVER_FAILURE is returned on NSS volume if there does not exist resource fork yet... */
						/* ignore silently */
						if (err != NWE_SERVER_FAILURE) {
							fprintf(stderr, _("Unable to open MAC resource fork on source %s: %s\n"), source, strnwerror(err));
							retValue = 1;
						}
						/* no MAC... */
						goto nomac;
					}
					sMAC = scon;
				}
			}
			if (!sMAC) {
				const char* p;
				char* d;
			
				if (!optMACinRsrc)
					goto nomac;

				p = strrchr(source, '/');
				if (p)
					p = p + 1;
				else
					p = source;
				memcpy(sourceB, source, p - source);
				d = sourceB + (p - source);
				d = stpcpy(d, RSRCSUBDIR);
				strcpy(d, p);
				sFD = open(sourceB, O_RDONLY);
				if (sFD == -1)
					goto nomac;
				sourceRsrc = sourceB;
			}
			if (dcon && optMAC) {
				struct nw_info_struct2 dstMAC;

				err = ncp_ns_obtain_entry_info(dcon, NW_NS_DOS, SA_ALL,
					1, dentry.volume, dentry.dirEnt, NULL, 0,
					NW_NS_MAC, RIM_DIRECTORY,
					&dstMAC, sizeof(dstMAC));
				if (err) {
					/* possible information loss... maybe fallback to create .rsrc/name */
					fprintf(stderr, _("MAC namespace is not supported on destination %s: %s\n"), destination, strnwerror(err));
					retValue = 1;
				} else {
					err = ncp_ns_open_create_entry(dcon, NW_NS_MAC, SA_ALL,
						1, dstMAC.Directory.volNumber, dstMAC.Directory.dirEntNum, NULL, 0,
						1, OC_MODE_OPEN, 0, AR_WRITE_ONLY,
						0,
						NULL, 0, NULL, NULL, dfhandle);
					if (err) {
						/* it is fatal... */
						fprintf(stderr, _("Unable to create MAC resource fork on destination %s: %s\n"), destination, strnwerror(err));
						retValue = 1;
						goto nomac;
					}
					dMAC = dcon;
				}
			}
			if (!dMAC) {
				const char* p;
				char* d;
			
				if (!optMACinRsrc) {
					/* try to create .rsrc/destination */
					fprintf(stderr, _("Unable to copy MAC resource fork of %s because of %s does not support resource forks\n"),
						source,
						destination);
						retValue = 1;
					goto nomac;
				}
			
				p = strrchr(destination, '/');
				if (p)
					p = p + 1;
				else
					p = destination;
				memcpy(destinationB, destination, p - destination);
				d = destinationB + (p - destination);
				d = stpcpy(d, RSRCSUBDIR);
				strcpy(d, p);
				dFD = open(destinationB, O_CREAT | O_TRUNC | O_WRONLY, sourceMode);
				if (dFD == -1) {
					if (errno == ENOENT) {
						*(d-1) = 0;
						mkdir(destinationB, sourceMode | ((sourceMode & 0444) >> 2) | ((sourceMode & 0222) >> 1));
						*(d-1) = '/';
						dFD = open(destinationB, O_CREAT | O_TRUNC | O_WRONLY, sourceMode);
					}
					if (dFD == -1) {
						int err2 = errno;
					
						fprintf(stderr, _("Unable to copy MAC resource fork: %s: %s\n"),
							destinationB,
							strerror(err2));
						retValue = 1;
						goto nomac;
					}
				}
				destinationRsrc = destinationB;
			}
			retValue = copyNWNW(_("NetWare copy (resource fork)"), 
				    sFD, sMAC, sfhandle, sourceRsrc,
				    dFD, dMAC, dfhandle, destinationRsrc);
nomac:;
			if (dMAC)
				ncp_close_file(dMAC, dfhandle);
			if (dFD != -1)
				close(dFD);
			if (sMAC)
				ncp_close_file(sMAC, sfhandle);
			if (sFD != -1)
				close(sFD);
		}
	} else {
		retValue = 0;
	}
	if ((optTrustees) && (retValue == 0) && scon) {
		u_int32_t iter = 0;
		while (1) {
			TRUSTEE_INFO ts[128];
			unsigned int tstcount = 128;
			unsigned int sptr;
			unsigned int dptr;
				
			if (ncp_ns_trustee_scan(scon, NW_NS_DOS, SA_ALL,
					1, sentry.volume, sentry.dirEnt, 
					NULL, 0,
					&iter, ts, &tstcount))
				break;
			if (tstcount == 0)
				break;
			if (!dcon) {
				fprintf(stderr, _("Cannot set trustees on %s because of %s\n"),
					destination,
					_("not NetWare filesystem"));
				break;
			}
			dptr = 0;
			for (sptr = 0; sptr < tstcount; sptr++) {
				if (xlatid(scon, ts[sptr].objectID, dcon, &ts[dptr].objectID)) {
					ts[dptr].objectRights = ts[sptr].objectRights;
					dptr++;
				}
			}
			if (dptr) {
				err = ncp_ns_trustee_add(dcon, NW_NS_DOS, 0x0016,
					1, dentry.volume, dentry.dirEnt, 
					NULL, 0,
					ts, dptr, 0xFFFF);
				if (err) {
					fprintf(stderr, _("Cannot set trustees on %s because of %s\n"),
						destination,
						strnwerror(err));
				}
			}
		}
	}
	if ((optOwner || optFlags || optTimes) && (retValue == 0) && scon) {
		if (!dcon) {
			fprintf(stderr, _("Cannot set file attributes on %s because of %s\n"),
				destination,
				_("not NetWare filesystem"));
		} else {
			struct ncp_dos_info nwmd;
			u_int32_t mask = 0;
			memset(&nwmd, 0, sizeof(nwmd));
			if (optOwner) {
				if (xlatid(scon, sfi.Creation.ID, dcon, &nwmd.Creation.ID))
					mask |= DM_CREATOR_ID;
				if (xlatid(scon, sfi.Modify.ID, dcon, &nwmd.Modify.ID))
					mask |= DM_MODIFIER_ID;
				if (xlatid(scon, sfi.Archive.ID, dcon, &nwmd.Archive.ID))
					mask |= DM_ARCHIVER_ID;
			}
			if (optFlags) {
				nwmd.Attributes = sfi.Attributes.Attributes;
				nwmd.Rights.Grant = sfi.Rights;
				nwmd.Rights.Revoke = ~sfi.Rights;
				mask |= DM_ATTRIBUTES | DM_INHERITED_RIGHTS_MASK;
			}
			if (optTimes) {
				nwmd.Creation.Date = sfi.Creation.Date;
				nwmd.Creation.Time = sfi.Creation.Time;
				nwmd.Modify.Date = sfi.Modify.Date;
				nwmd.Modify.Time = sfi.Modify.Time;
				nwmd.Archive.Date = sfi.Archive.Date;
				nwmd.Archive.Time = sfi.Archive.Time;
				nwmd.LastAccess.Date = sfi.LastAccess.Date;
				mask |= DM_CREATE_DATE | DM_CREATE_TIME |
				        DM_MODIFY_DATE | DM_MODIFY_TIME |
					DM_ARCHIVE_DATE | DM_ARCHIVE_TIME |
					DM_LAST_ACCESS_DATE;
			}
			if (mask) {
				err = ncp_ns_modify_entry_dos_info(
					dcon, NW_NS_DOS, SA_ALL, 
					1, dentry.volume, dentry.dirEnt, NULL, 0,
					mask, &nwmd);
				if (err) {
					fprintf(stderr, _("Cannot set file attributes on %s because of %s\n"),
						destination,
						strnwerror(err));
				}
			}
		}
	}
	/* Clear signal handling globals */
	CurrentConn = NULL;
	if (dcon)
		ncp_close(dcon);
	if (scon && !ncopy)
		ncp_close(scon);
	return retValue;
}


/****************************************************************************
 *  Decides whether to use the traditional file copy or the netware remote
 *  file copy.
 */
static int
selectFn(const struct dirent* entry) {
	if (entry->d_name[0] == '.') {
		if (entry->d_name[1] == 0)
			return 0;
		if (entry->d_name[1] == '.' && entry->d_name[2] == 0)
			return 0;
	}
	return 1;
}

static char sourcePath[MAXPATHLEN * 2];
static char destinationPath[MAXPATHLEN * 2];

static int
copyFiles(void)
{
	int oldUMask;
	int retVal;
	int fdIn, fdOut;
	struct stat statBuf;
#ifdef NCOPY_DEBUG
	printf(_("Param Src   '%s'\n"
	         "Param Dest  '%s'\n"), sourcePath, destinationPath);
#endif

	fdIn = open(sourcePath, O_RDONLY);
	if (fdIn == -1)
	{
		fprintf(stderr, _("%s: Cannot open %s, %s\n"), ProgramName, sourcePath,
			strerror(errno));
		return 1;
	}
	if (fstat(fdIn, &statBuf))
	{
		fprintf(stderr, _("%s: Cannot stat %s, %s\n"), ProgramName, sourcePath,
			strerror(errno));
		close(fdIn);
		return 1;
	}
	if (S_ISDIR(statBuf.st_mode))
	{
		int ecode = 0;
		
		if (optRecursive) {
			struct stat dstBuf;
			struct dirent** entries;
			int rent;
			int i;
			char *dpx;
			char *spx;

			if (!stat(destinationPath, &dstBuf)) {
				if (!S_ISDIR(dstBuf.st_mode)) {
					fprintf(stderr, _("%s: %s: %s\n"), ProgramName, destinationPath, _("not a directory"));
					close(fdIn);
					return 0;
				}
				oldUMask = umask(0);
				if (dstBuf.st_mode != statBuf.st_mode) {
					if (chmod(destinationPath, statBuf.st_mode & 0777)) {
						fprintf(stderr, _("%s: Cannot chmod %s: %s\n"), ProgramName,
							destinationPath, strerror(errno));
					}
				}
			} else {
				oldUMask = umask(0);
				if (mkdir(destinationPath, statBuf.st_mode & 0777)) {
					fprintf(stderr, _("%s: Cannot create %s: %s\n"), ProgramName, destinationPath,
						strerror(errno));
					umask(oldUMask);
					close(fdIn);
					return 1;
				}
			}
			fdOut = open(destinationPath, O_RDONLY);
			if (fdOut == -1)
			{
				fprintf(stderr, _("%s: Cannot create %s, %s\n"), ProgramName, destinationPath,
					strerror(errno));
				umask(oldUMask);
				close(fdIn);
				return 1;
			}

			retVal = netwareCopyFile(fdIn, fdOut, sourcePath, destinationPath, statBuf.st_mode, 0);
			close(fdOut);
			umask(oldUMask);
			close(fdIn);
			rent = scandir(sourcePath, &entries, selectFn, alphasort);
			if (rent < 0) {
				fprintf(stderr, _("%s: Cannot read %s: %s\n"), ProgramName, sourcePath, strerror(errno));
				umask(oldUMask);
				close(fdIn);
				return 1;
			}
			dpx = destinationPath + strlen(destinationPath);
			*dpx = '/';
			spx = sourcePath + strlen(sourcePath);
			*spx = '/';

			for (i = 0; i < rent; i++) {
				int tmp;
				
				strcpy(dpx + 1, entries[i]->d_name);
				strcpy(spx + 1, entries[i]->d_name);
				tmp = copyFiles();
				if (tmp)
					ecode = tmp;
				if (tmp > 1)
					break;
				
			}
			*spx = 0;
			*dpx = 0;
			umask(oldUMask);
			close(fdIn);
		} else {
			fprintf(stderr, _("%s: %s: omitting directory\n"), ProgramName, sourcePath);
			close(fdIn);
		}
		return ecode;
	}

	oldUMask = umask(0);
	if (optOnlyChanged) {
		struct stat statBufOut;

		if (!stat(destinationPath, &statBufOut)) {
			if (statBuf.st_size == statBufOut.st_size &&
			    statBuf.st_mtime == statBufOut.st_mtime) {
			    	printf("%s and %s are same\n", sourcePath, destinationPath);
			    	fdOut = open(destinationPath, O_RDONLY);
			    	if (fdOut == -1) {
			    		goto canopen;
			    	}
			    	retVal = netwareCopyFile(fdIn, fdOut, sourcePath, destinationPath, statBuf.st_mode, 0);
			    	goto cpok;
			}
		}
	}
	fdOut = open(destinationPath, O_CREAT | O_TRUNC | O_WRONLY, statBuf.st_mode);
	if (fdOut == -1)
	{
canopen:;
		fprintf(stderr, _("%s: Cannot create %s, %s\n"), ProgramName, destinationPath,
			strerror(errno));
		umask(oldUMask);
		close(fdIn);
		return 1;
	}

	retVal = netwareCopyFile(fdIn, fdOut, sourcePath, destinationPath, statBuf.st_mode, 1);
cpok:;
	close(fdOut);
	umask(oldUMask);
	close(fdIn);
	return retVal;
}


/****************************************************************************
 * 
 * HERE
 *
 * Brian may NEED "fake" path if he prints error messages?
 * or I may need a way to get his error messages so I can
 * print them with the "fake" path.
 * My current error messages are on the REAL path, which would be confusing...
 d
 * (1-source problem, 2-destination problem, 3-other fatal)
 * We need to decide when to exit or continue the loop, 
 * and what to return when we do exit the loop.
 * Is it failure if 3 files are to be copied, and 1 fails?
 * If one copy fails, we stay in the loop, right?
 * Is it failure if destination fails?
 * Do we Stay in the loop?
 */
static int
copyRealPaths(void)
{
	return copyFiles();
}

/****************************************************************************
 * guaranteed argc is at least 2 and
 * if argc > 2 last parameter is a directory
 * by validateFileArgs()
 */
static int
handleFileArgs(int argc, char *const argv[])
{
	int loop;
	const char *destination;
	int copyStatus;
	int returnCode = 0;	/* default program exit code */
	const char *baseNamePtr;

	destination = argv[argc - 1];	/* get LAST argument */
	for (loop = 0; loop < (argc - 1); loop++)
	{			/* all file arguments, but last */
		strncpy(destinationPath, destination, MAXPATHLEN);
		destinationPath[MAXPATHLEN] = 0;
		if ((argc > 2) || (!notDir(argv[argc - 1])))
		{		/* destination is a dir */
			if (*destinationPath != '/' || *(destinationPath + 1))
				strcat(destinationPath, "/");
			baseNamePtr = myBaseName(argv[loop]);	/* get the file name */
			strcat(destinationPath, baseNamePtr);	/* add it on end of directory */
		}
		strncpy(sourcePath, argv[loop], MAXPATHLEN);
		sourcePath[MAXPATHLEN] = 0;
		copyStatus = copyRealPaths();	/* do the copy */
		if (copyStatus > 1)
			return copyStatus;	/* fatal failure? bye */
		if (copyStatus == 1)
			returnCode = 1;		/* a partial failure? we can continue */
	}
	return returnCode;	/* return what will be the program exit code */
}
/****************************************************************************
 * 
 */
static void
handleSignals(int sigNumber)
{
	/* Ignore Signal Handling while cleaning up */

	/* SIGHUP */
	sHangupSig.sa_handler = SIG_IGN;
	if (sigaction(SIGHUP, &sHangupSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset to ignore SIGHUP signal failed: %s"),
			ProgramName, strerror(errno));
	}
	/* SIGINT */
	sInterruptSig.sa_handler = SIG_IGN;
	if (sigaction(SIGINT, &sInterruptSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset to ignore SIGINT signal failed: %s"),
			ProgramName, strerror(errno));
	}
	/* SIGQUIT */
	sQuitSig.sa_handler = SIG_IGN;
	if (sigaction(SIGQUIT, &sQuitSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset to ignore SIGQUIT signal failed: %s"),
			ProgramName, strerror(errno));
	}
	/* SIGTERM */
	sTermSig.sa_handler = SIG_IGN;
	if (sigaction(SIGTERM, &sTermSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset to ignore SIGTERM signal failed: %s"),
			ProgramName, strerror(errno));
	}
	/* If we don't close the ncp output file, we have to ncpumount and
	   ncpmount before we can get rid of it.  */
	if (CurrentFile)
	{
		/*  Issue a warning if we cannot close the file */
		/*  If an error occurs we probably have to umount/mount to 
		   remove the file */
		if (ncp_close_file(CurrentConn, CurrentFile) != 0)
		{
			fprintf(stderr, _("%s: unclean close of output file"), ProgramName);
		}
		CurrentFile = NULL;
	}
	if (CurrentConn) {
		ncp_close(CurrentConn);
		CurrentConn = NULL;
	}
	exit(128 + sigNumber);
}

/****************************************************************************
 * We'll trap Hangup, Interrupt, Quit or Terminate
 */
static int
trapSignals(void)
{
	if (sigaction(SIGHUP, NULL, &sHangupSig))
	{			/* init structure fields */
		fprintf(stderr, _("%s: Get HANGUP signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	sHangupSig.sa_handler = handleSignals;
	if (sigaction(SIGHUP, &sHangupSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset HANGUP signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	if (sigaction(SIGINT, NULL, &sInterruptSig))
	{			/* init structure fields */
		fprintf(stderr, _("%s: Get INTERRUPT signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	sInterruptSig.sa_handler = handleSignals;
	if (sigaction(SIGINT, &sInterruptSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset INTERRUPT signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	if (sigaction(SIGQUIT, NULL, &sQuitSig))
	{			/* init structure fields */
		fprintf(stderr, _("%s: Get QUIT signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	sQuitSig.sa_handler = handleSignals;
	if (sigaction(SIGQUIT, &sQuitSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset QUIT signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	if (sigaction(SIGTERM, NULL, &sTermSig))
	{			/* init structure fields */
		fprintf(stderr, _("%s: Get TERMINATE signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	sTermSig.sa_handler = handleSignals;
	if (sigaction(SIGTERM, &sTermSig, NULL) == -1)
	{
		fprintf(stderr, _("%s: Reset TERMINATE signal action failed: %s"),
			ProgramName, strerror(errno));
		return 1;
	}
	return 0;
}

/****************************************************************************
 *
 */
int
main(int argc, char *const argv[])
{
	int returnCode;
	ProgramName = strrchr(argv[0], '/');
	if (ProgramName)
		ProgramName++;
	else
		ProgramName = argv[0];

	setlocale(LC_ALL, "");
	bindtextdomain(NCPFS_PACKAGE, LOCALEDIR);
	textdomain(NCPFS_PACKAGE);
	
	if (handleOptions(argc, argv))
	{			/* bad option, missing option parameter */
		usage();
		return 1;
	}
	if (optVersion)
	{			/* only option not requiring any arguments */
		printf(_("%s version %s\n"), ProgramName, VersionStr);
		return 0;
	}
	if (validateFileArgs(argc - optind, argv + optind))
	{
		usage();
		return 1;
	}
	if (trapSignals())
		return 1;
	returnCode = handleFileArgs(argc - optind, argv + optind);
	return returnCode;
}
