/*
* mars-nwe-dosutils - NetWare/DOS utility tools.
*
* Copyright (C) 2026 Mario Fetka
*
* 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, see .
*/
/*
* Purpose: NCOPY utility implementation and experimental server-side-copy/NCP74 investigation code.
* Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines.
*/
/* ncopy.c - Novell NCOPY-like DOS utility, first implementation
*
* Prefer NetWare server-side copy through the DOS requester
* NWFileServerFileCopy wrapper (INT 21h AH=F3). A small DOS read/write path
* remains as fallback for non-NetWare paths or requester failures.
*/
#include "net.h"
#include "c32ncp.h"
#include
#include
#include
#include
#include
#include
#include
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef _A_NORMAL
#define _A_NORMAL 0x00
#endif
#ifndef _A_RDONLY
#define _A_RDONLY 0x01
#endif
#ifndef _A_HIDDEN
#define _A_HIDDEN 0x02
#endif
#ifndef _A_SYSTEM
#define _A_SYSTEM 0x04
#endif
#ifndef _A_SUBDIR
#define _A_SUBDIR 0x10
#endif
#ifndef _A_ARCH
#define _A_ARCH 0x20
#endif
#define NCOPY_OPT_S 0x0001
#define NCOPY_OPT_E 0x0002
#define NCOPY_OPT_V 0x0004
#define NCOPY_OPT_A 0x0008
#define NCOPY_OPT_M 0x0010
#define NCOPY_OPT_I 0x0020
#define NCOPY_OPT_C 0x0040
#define NCOPY_OPT_F 0x0080
#define NCOPY_BUF_SIZE 2048
/* Keep DGROUP small: net.exe is a DOS multicall binary. */
static char ncopy_buffer[NCOPY_BUF_SIZE];
static char ncopy_last_from[260];
static char ncopy_last_to[260];
static int ncopy_have_group = 0;
static int ncopy_debug_enabled(void)
{
char *p = getenv("NCOPYDBG");
return(p && *p && *p != '0');
}
static int ncopy_ncp74_enabled(void)
{
char *p = getenv("NCOPYNCP74");
return(p && *p && *p != '0');
}
static void ncopy_debug_msg(char *msg)
{
if (ncopy_debug_enabled())
fprintf(stdout, "NCOPYDBG: %s\n", msg);
}
static void ncopy_debug_rc(char *step, int rc)
{
if (ncopy_debug_enabled())
fprintf(stdout, "NCOPYDBG: %s rc=%d neterrno=%d errno=%d\n",
step, rc, neterrno, errno);
}
static void ncopy_debug_ul(char *step, unsigned long value)
{
if (ncopy_debug_enabled())
fprintf(stdout, "NCOPYDBG: %s=%lu\n", step, value);
}
static void ncopy_debug_handle6(char *step, C32_NWFILE_HANDLE6 *h)
{
if (ncopy_debug_enabled() && h) {
fprintf(stdout,
"NCOPYDBG: %s %02x %02x %02x %02x %02x %02x\n",
step,
(unsigned)h->h[0], (unsigned)h->h[1],
(unsigned)h->h[2], (unsigned)h->h[3],
(unsigned)h->h[4], (unsigned)h->h[5]);
}
}
static void ncopy_debug_handle_bytes(char *step, unsigned char *h)
{
if (ncopy_debug_enabled() && h) {
fprintf(stdout,
"NCOPYDBG: %s %02x %02x %02x %02x %02x %02x\n",
step,
(unsigned)h[0], (unsigned)h[1],
(unsigned)h[2], (unsigned)h[3],
(unsigned)h[4], (unsigned)h[5]);
}
}
static int ncopy_handle6_is_zero(unsigned char *h)
{
int i;
if (!h)
return(1);
for (i = 0; i < 6; ++i)
if (h[i])
return(0);
return(1);
}
static void ncopy_debug_bytes(char *step, unsigned char *p, unsigned len)
{
unsigned i;
if (!ncopy_debug_enabled() || !p)
return;
fprintf(stdout, "NCOPYDBG: %s", step);
if (len > 32)
len = 32;
for (i = 0; i < len; ++i)
fprintf(stdout, " %02x", (unsigned)p[i]);
fprintf(stdout, "\n");
}
static void ncopy_usage(void)
{
fprintf(stdout, "Usage: NCOPY [path] [[TO] path] [option]\n");
fprintf(stdout, "Options\n");
fprintf(stdout, "\t/s\tcopy subdirectories.\n");
fprintf(stdout, "\t/s/e\tcopy subdirectories, including empty directories.\n");
fprintf(stdout, "\t/f\tcopy sparse files.\n");
fprintf(stdout, "\t/h (/?) display this usage message.\n");
fprintf(stdout, "\t/a\tcopy files with archive bit set.\n");
fprintf(stdout, "\t/m\tcopy files with archive bit set, clear the bit.\n");
fprintf(stdout, "\t/i\tinform when non-DOS file information will be lost.\n");
fprintf(stdout, "\t/c\tcopy only DOS information.\n");
fprintf(stdout, "\t/v\tverify with a read after every write.\n");
}
static int ncopy_is_dir(char *path)
{
struct stat st;
if (!path || !*path)
return(0);
if (stat(path, &st) == 0 && (st.st_mode & S_IFDIR))
return(1);
return(0);
}
static int ncopy_has_wildcards(char *s)
{
return(s && (strchr(s, '*') || strchr(s, '?')));
}
static void ncopy_parent_pattern(char *dir, char *pat, char *path)
{
tool_parent_pattern(dir, pat, path, 260, 260);
if (!dir[0])
strmaxcpy(dir, ".", 259);
if (!pat[0])
strmaxcpy(pat, "*.*", 259);
}
static void ncopy_add_slash(char *dst, int max)
{
int len = strlen(dst);
if (len > 0 && len < max - 1) {
if (dst[len - 1] != '\\' && dst[len - 1] != ':' && dst[len - 1] != '/') {
dst[len++] = '\\';
dst[len] = '\0';
}
}
}
static void ncopy_join(char *out, char *dir, char *name, int max)
{
strmaxcpy(out, dir ? dir : "", max - 1);
ncopy_add_slash(out, max);
strmaxcpy(out + strlen(out), name ? name : "", max - strlen(out) - 1);
}
static void ncopy_basename(char *out, char *path, int max)
{
char tmp[260];
char *p;
strmaxcpy(tmp, path ? path : "", sizeof(tmp) - 1);
p = strrchr(tmp, '\\');
if (!p)
p = strrchr(tmp, '/');
if (!p)
p = strrchr(tmp, ':');
if (p)
strmaxcpy(out, p + 1, max - 1);
else
strmaxcpy(out, tmp, max - 1);
}
static void ncopy_display_path(char *out, char *path, int max)
{
char tmp[260];
char *p;
int first_sep = 1;
int pos = 0;
if (!out || max <= 0)
return;
out[0] = '\0';
if (!path)
path = "";
/* The Novell tool prints server/volume style paths. The test volume is
* SYS on server MARS; make DOS drive-qualified paths comparable without
* asking the requester for name-space details on every line. */
p = path;
if (p[0] && p[1] == ':') {
strmaxcpy(out, "MARS/SYS:", max - 1);
pos = strlen(out);
p += 2;
}
while (*p && pos < max - 1) {
if (*p == '\\' || *p == '/') {
if (first_sep) {
out[pos++] = '\\';
first_sep = 0;
} else {
out[pos++] = '/';
}
while (p[1] == '\\' || p[1] == '/')
p++;
} else {
out[pos++] = *p;
}
p++;
}
out[pos] = '\0';
}
static void ncopy_reset_output_group(void)
{
ncopy_last_from[0] = '\0';
ncopy_last_to[0] = '\0';
ncopy_have_group = 0;
}
static void ncopy_print_group(char *srcdir, char *dstdir)
{
char from[260];
char to[260];
ncopy_display_path(from, srcdir, sizeof(from));
ncopy_display_path(to, dstdir, sizeof(to));
if (ncopy_have_group && !strcmp(ncopy_last_from, from) &&
!strcmp(ncopy_last_to, to))
return;
if (ncopy_have_group)
fprintf(stdout, "\n\n");
fprintf(stdout, "From %s\n", from);
fprintf(stdout, "To %s\n", to);
strmaxcpy(ncopy_last_from, from, sizeof(ncopy_last_from) - 1);
strmaxcpy(ncopy_last_to, to, sizeof(ncopy_last_to) - 1);
ncopy_have_group = 1;
}
static void ncopy_finish_output_groups(void)
{
if (ncopy_have_group)
fprintf(stdout, "\n");
}
static void ncopy_print_file_map(char *srcname, char *dstname)
{
fprintf(stdout, " %-13.13s to %-13.13s\n", srcname, dstname);
}
static int ncopy_make_dir(char *path)
{
if (ncopy_is_dir(path))
return(0);
#ifdef __WATCOMC__
if (mkdir(path) == 0)
return(0);
#else
if (mkdir(path, 0777) == 0)
return(0);
#endif
if (ncopy_is_dir(path))
return(0);
return(-1);
}
static int ncopy_same_path(char *a, char *b)
{
char aa[260];
char bb[260];
tool_upcopy(aa, a, sizeof(aa));
tool_upcopy(bb, b, sizeof(bb));
return(strcmp(aa, bb) == 0);
}
static int ncopy_verify_file(char *src, char *dst)
{
int s, d;
unsigned sr, dr;
char b2[NCOPY_BUF_SIZE];
s = open(src, O_RDONLY | O_BINARY);
if (s < 0)
return(-1);
d = open(dst, O_RDONLY | O_BINARY);
if (d < 0) {
close(s);
return(-1);
}
for (;;) {
if (_dos_read(s, ncopy_buffer, NCOPY_BUF_SIZE, &sr)) {
close(s); close(d); return(-1);
}
if (_dos_read(d, b2, NCOPY_BUF_SIZE, &dr)) {
close(s); close(d); return(-1);
}
if (sr != dr || memcmp(ncopy_buffer, b2, sr)) {
close(s); close(d); return(-1);
}
if (sr == 0)
break;
}
close(s);
close(d);
return(0);
}
static int ncopy_split_dir_name(char *path, char *dir, char *name);
static int ncopy_alloc_parent_handle(char *path, uint8 *handle, int *allocated);
static void ncopy_close_ncp_file(uint8 nwhandle[6]);
static int ncopy_ncp74_copy_chunk(uint8 src_handle[6], uint8 dst_handle[6],
uint32 src_offset, uint32 dst_offset,
uint32 count, uint32 *copied);
static int ncopy_copy_data_c32_server(char *src, char *dst, unsigned long size)
{
C32_NWFILE_HANDLE6 sh;
C32_NWFILE_HANDLE6 dh;
C32_NDIR_INFO sinfo;
char srcdir[260];
char srcname[80];
char dstdir[260];
char dstname[80];
uint8 src_dhandle = 0;
uint8 dst_dhandle = 0;
int src_allocated = 0;
int dst_allocated = 0;
uint32 create_attrs = 0;
uint32 off = 0;
uint32 todo;
uint32 done;
int src_open = 0;
int dst_open = 0;
int rc;
memset(&sh, 0, sizeof(sh));
memset(&dh, 0, sizeof(dh));
memset(&sinfo, 0, sizeof(sinfo));
if (ncopy_split_dir_name(src, srcdir, srcname) ||
ncopy_split_dir_name(dst, dstdir, dstname))
return(-1);
ncopy_debug_msg("c32-ncp87/ncp74 server-copy begin");
ncopy_debug_ul("c32-server-copy size", size);
if (ncopy_alloc_parent_handle(src, &src_dhandle, &src_allocated)) {
ncopy_debug_msg("c32-server-copy failed source parent handle");
return(-1);
}
if (ncopy_alloc_parent_handle(dst, &dst_dhandle, &dst_allocated)) {
if (src_allocated)
dealloc_dir_handle(src_dhandle);
ncopy_debug_msg("c32-server-copy failed dest parent handle");
return(-1);
}
/* Best effort: use source NetWare attributes when creating the target.
* If this fails, still try the server-side copy with normal attributes;
* ncopy_copy_one() will restore DOS date/time and low DOS attributes. */
if (!c32_ncp87_obtain_ndir_info(srcname, src_dhandle,
&sinfo, NULL, NULL, NULL)) {
create_attrs = sinfo.attributes;
ncopy_debug_ul("c32-server-copy source attrs", create_attrs);
}
_dos_setfileattr(dst, _A_NORMAL);
rc = c32_ncp87_open_create_file(srcname,
src_dhandle,
C32_OC_MODE_OPEN,
0,
C32_DAR_READ | C32_DAR_DENY_WRITE,
0x8006,
&sh,
NULL,
NULL,
NULL,
NULL,
NULL);
if (rc) {
ncopy_debug_rc("c32 open source NCP87/01", rc);
goto fail;
}
src_open = 1;
ncopy_debug_handle6("c32 source handle", &sh);
rc = c32_ncp87_open_create_file(dstname,
dst_dhandle,
C32_OC_MODE_OPEN |
C32_OC_MODE_TRUNCATE |
C32_OC_MODE_CREATE,
create_attrs,
C32_DAR_WRITE | C32_DAR_DENY_READ,
0x8006,
&dh,
NULL,
NULL,
NULL,
NULL,
NULL);
if (rc) {
ncopy_debug_rc("c32 open/create dest NCP87/01", rc);
goto fail;
}
dst_open = 1;
ncopy_debug_handle6("c32 dest handle", &dh);
if (dst_allocated) {
dealloc_dir_handle(dst_dhandle);
dst_allocated = 0;
}
if (src_allocated) {
dealloc_dir_handle(src_dhandle);
src_allocated = 0;
}
if (ncopy_handle6_is_zero(sh.h) || ncopy_handle6_is_zero(dh.h)) {
ncopy_debug_msg("c32 server-copy has zero handle; falling back");
if (!ncopy_handle6_is_zero(dh.h))
ncopy_close_ncp_file(dh.h);
if (!ncopy_handle6_is_zero(sh.h))
ncopy_close_ncp_file(sh.h);
return(-1);
}
while (off < (uint32)size) {
todo = (uint32)size - off;
if (todo > 0x8000UL)
todo = 0x8000UL;
done = 0;
ncopy_debug_ul("c32-server-copy offset", off);
ncopy_debug_ul("c32-server-copy todo", todo);
rc = c32_ncp74_file_server_copy(&sh, &dh, off, off, todo, &done);
if (rc || done == 0) {
ncopy_debug_rc("c32 copy chunk NCP74", rc ? rc : -1);
goto fail;
}
ncopy_debug_ul("c32-server-copy copied", done);
off += done;
}
if (dst_open)
c32_ncp66_close_file(&dh);
if (src_open)
c32_ncp66_close_file(&sh);
ncopy_debug_msg("c32-ncp87/ncp74 server-copy success");
return(0);
fail:
if (dst_open)
c32_ncp66_close_file(&dh);
if (src_open)
c32_ncp66_close_file(&sh);
if (dst_allocated)
dealloc_dir_handle(dst_dhandle);
if (src_allocated)
dealloc_dir_handle(src_dhandle);
ncopy_debug_msg("c32-ncp87/ncp74 server-copy failed");
return(-1);
}
static uint32 ncopy_get_dword_be(uint8 *p)
{
return(GET_BE32(p));
}
static void ncopy_put_word_lh(uint8 *p, uint16 v)
{
p[0] = (uint8)(v & 0xff);
p[1] = (uint8)((v >> 8) & 0xff);
}
static void ncopy_put_dword_lh(uint8 *p, uint32 v)
{
p[0] = (uint8)(v & 0xff);
p[1] = (uint8)((v >> 8) & 0xff);
p[2] = (uint8)((v >> 16) & 0xff);
p[3] = (uint8)((v >> 24) & 0xff);
}
static uint32 ncopy_get_dword_lh(uint8 *p)
{
return((uint32)p[0] | ((uint32)p[1] << 8) |
((uint32)p[2] << 16) | ((uint32)p[3] << 24));
}
static unsigned ncopy_build_nw_handle_path(uint8 *buf, uint8 dhandle,
const char *name)
{
uint8 *p;
unsigned namelen;
memset(buf, 0, 320);
/* INT 21h AX=F257 uses the old 87.x HandlePath layout, as already
* verified in FLAG:
* byte 0 short directory handle
* bytes 1..4 directory base, little endian dword, zero for short handle
* byte 5 dirstyle, 0 = short directory handle
* byte 6 component count
* then length-prefixed path components
*
* v6 accidentally used the Client32 raw-fragment layout
* 00 ...
* which made MARS ignore the temporary parent handle and resolve the full
* path from SYS:. That is why nw.log still showed components
* TCOPY/SRC/A.TXT instead of only A.TXT under the temp handle.
*/
buf[0] = dhandle;
ncopy_put_dword_lh(buf + 1, 0);
buf[5] = 0; /* dirstyle = short directory handle */
buf[6] = 1; /* one path component */
p = buf + 7;
namelen = name ? strlen(name) : 0;
if (namelen > 255)
namelen = 255;
*p++ = (uint8)namelen;
if (namelen) {
memcpy(p, name, namelen);
p += namelen;
}
return((unsigned)(p - buf));
}
static int ncopy_open_create_ncp87_direct(char *path,
uint8 open_create_mode,
uint32 create_attrs,
uint16 desired_access,
uint16 search_attrs,
uint8 nwhandle[6],
unsigned long *size_out)
{
char dir[260];
char name[80];
uint8 dhandle = 0;
int allocated = 0;
unsigned path_len;
struct {
uint16 len;
uint8 subfunc;
uint8 namespace_id;
uint8 open_create_mode;
uint8 search_attrs[2];
uint8 return_info_mask[4];
uint8 create_attrs[4];
uint8 desired_access[2];
uint8 nwpath[320];
} req;
struct {
uint16 len;
uint8 handle[6];
uint8 action;
uint8 reserved;
uint8 info[256];
} repl;
if (size_out)
*size_out = 0;
if (!nwhandle || ncopy_split_dir_name(path, dir, name))
return(-1);
if (!name[0] || strlen(name) > 79)
return(-1);
if (ncopy_alloc_parent_handle(path, &dhandle, &allocated))
return(-1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
req.subfunc = 0x01; /* NCP 87/01 Open/Create File or Subdirectory */
req.namespace_id = 0; /* DOS namespace */
req.open_create_mode = open_create_mode;
ncopy_put_word_lh(req.search_attrs, search_attrs);
ncopy_put_dword_lh(req.return_info_mask, 0x00000fffUL);
ncopy_put_dword_lh(req.create_attrs, create_attrs);
ncopy_put_word_lh(req.desired_access, desired_access);
path_len = ncopy_build_nw_handle_path(req.nwpath, dhandle, name);
/* INT 21h AX=F257 uses the same request layout as the working FLAG
* code: a 16-bit length prefix followed by the NCP 87 subfunction.
* The v5 one-byte length made MARS see namespace_id (0x00) as ufunc. */
req.len = (uint16)(1 + 1 + 1 + 2 + 4 + 4 + 2 + path_len);
repl.len = (uint16)(sizeof(repl) - sizeof(repl.len));
ncopy_debug_ul("direct NCP87/01 mode", open_create_mode);
ncopy_debug_ul("direct NCP87/01 access", desired_access);
neterrno = Net_Call(0xF257, &req, &repl);
/* v12 diagnostic: on Client32/MARS the server visibly executes
* 0x57/01 and allocates a file handle, but the Net_Call reply buffer
* still contains only its preset length and zero payload. Dump both
* buffers after the call before we try to interpret anything. Some
* requesters use in-place buffers for selected F2xx calls. */
ncopy_debug_ul("direct NCP87 after-call neterrno", neterrno);
ncopy_debug_ul("direct NCP87 reply len field", repl.len);
ncopy_debug_bytes("direct NCP87 reply raw", (unsigned char *)&repl, 48);
ncopy_debug_ul("direct NCP87 request len field", req.len);
ncopy_debug_bytes("direct NCP87 request raw after", (unsigned char *)&req, 48);
if (allocated)
dealloc_dir_handle(dhandle);
if (neterrno) {
ncopy_debug_rc("direct open/create NCP 0x57/01", -1);
return(-1);
}
/* Safety/debug version: do not call NCP74 unless we have a non-zero
* handle. The previous zero-handle NCP74 path can make Client32 retry
* until the workstation appears frozen. */
memset(nwhandle, 0, 6);
memcpy(nwhandle, repl.handle, 4);
ncopy_debug_handle_bytes("direct NCP87 handle from reply", nwhandle);
if (ncopy_handle6_is_zero(nwhandle)) {
memset(nwhandle, 0, 6);
memcpy(nwhandle, ((unsigned char *)&req) + 2, 4);
ncopy_debug_handle_bytes("direct NCP87 handle candidate from request", nwhandle);
if (!ncopy_handle6_is_zero(nwhandle)) {
ncopy_debug_msg("direct NCP87 non-zero in-place candidate seen, still not trusted; skip NCP74");
memset(nwhandle, 0, 6);
}
}
if (ncopy_handle6_is_zero(nwhandle)) {
ncopy_debug_msg("direct NCP87 returned zero handle; skip NCP74 and fall back");
return(-1);
}
if (size_out)
*size_out = ncopy_get_dword_lh(repl.info + 10);
ncopy_debug_rc("direct open/create NCP 0x57/01", 0);
ncopy_debug_msg("direct NCP87 handle returned");
return(0);
}
static int ncopy_copy_data_direct_ncp87(char *src, char *dst, unsigned long size)
{
uint8 sh[6];
uint8 dh[6];
uint32 off = 0;
uint32 todo;
uint32 done;
C32_NDIR_INFO sinfo;
char srcdir[260], srcname[80];
uint8 src_dhandle = 0;
int src_allocated = 0;
uint32 create_attrs = 0;
memset(sh, 0, sizeof(sh));
memset(dh, 0, sizeof(dh));
memset(&sinfo, 0, sizeof(sinfo));
ncopy_debug_msg("direct old-NCP87/NCP74 server-copy begin");
ncopy_debug_ul("direct server-copy size", size);
if (!ncopy_split_dir_name(src, srcdir, srcname) &&
!ncopy_alloc_parent_handle(src, &src_dhandle, &src_allocated)) {
if (!c32_ncp87_obtain_ndir_info(srcname, src_dhandle,
&sinfo, NULL, NULL, NULL))
create_attrs = sinfo.attributes;
if (src_allocated)
dealloc_dir_handle(src_dhandle);
}
_dos_setfileattr(dst, _A_NORMAL);
if (ncopy_open_create_ncp87_direct(src,
0x01, /* open existing */
0,
0x0011, /* read + compat/binary */
0x0007, /* hidden/system search */
sh,
NULL))
return(-1);
if (ncopy_open_create_ncp87_direct(dst,
0x0a, /* Novell-like replace/create */
create_attrs,
0x0013, /* read/write + compat */
0x0007,
dh,
NULL)) {
ncopy_close_ncp_file(sh);
return(-1);
}
while (off < (uint32)size) {
todo = (uint32)size - off;
if (todo > 0x8000UL)
todo = 0x8000UL;
done = 0;
ncopy_debug_ul("direct server-copy offset", off);
ncopy_debug_ul("direct server-copy todo", todo);
if (ncopy_ncp74_copy_chunk(sh, dh, off, off, todo, &done) || done == 0) {
ncopy_close_ncp_file(dh);
ncopy_close_ncp_file(sh);
ncopy_debug_msg("direct old-NCP87/NCP74 server-copy failed copying chunk");
return(-1);
}
off += done;
}
ncopy_close_ncp_file(dh);
ncopy_close_ncp_file(sh);
ncopy_debug_msg("direct old-NCP87/NCP74 server-copy success");
return(0);
}
static int ncopy_split_dir_name(char *path, char *dir, char *name)
{
char tmp[260];
char *p;
if (!path || !*path || !dir || !name)
return(-1);
strmaxcpy(tmp, path, sizeof(tmp) - 1);
p = strrchr(tmp, '\\');
if (!p)
p = strrchr(tmp, '/');
if (!p)
p = strrchr(tmp, ':');
if (p) {
if (*p == ':') {
p[1] = '\0';
strmaxcpy(dir, tmp, 259);
strmaxcpy(name, p + 1, 79);
} else {
*p++ = '\0';
strmaxcpy(dir, tmp, 259);
strmaxcpy(name, p, 79);
}
} else {
strmaxcpy(dir, ".", 259);
strmaxcpy(name, tmp, 79);
}
if (!name[0])
return(-1);
return(0);
}
static void ncopy_ncp_relative_path(char *dst, char *src, int max)
{
char up[260];
char *p;
if (!dst || max <= 0)
return;
dst[0] = '\0';
if (!src || !*src || tool_is_current_path(src))
return;
tool_upcopy(up, src, sizeof(up));
p = up;
/* Old directory-handle based NCPs expect a path relative to the current
* NetWare directory handle. Do not pass DOS drive-qualified paths such as
* F:\TCOPY\SRC to AllocTempDirHandle; strip the drive and leading slash
* first, the same way RIGHTS normalizes paths for NCP calls.
*/
if (p[0] && p[1] == ':') {
p += 2;
if (*p == '\\' || *p == '/')
p++;
}
while (*p == '\\' || *p == '/')
p++;
strmaxcpy(dst, p, max - 1);
}
static int ncopy_alloc_parent_handle(char *path, uint8 *handle, int *allocated)
{
char dir[260];
char ncpdir[260];
char name[80];
uint8 connid = 0;
uint8 curhandle = 0;
uint8 eff = 0;
int h;
if (!handle || !allocated)
return(-1);
*handle = 0;
*allocated = 0;
if (ncopy_split_dir_name(path, dir, name))
return(-1);
if (tool_current_dhandle(&connid, &curhandle))
return(-1);
ncopy_ncp_relative_path(ncpdir, dir, sizeof(ncpdir));
if (!ncpdir[0] || tool_is_current_path(ncpdir) || !strcmp(ncpdir, ".")) {
*handle = curhandle;
ncopy_debug_rc("alloc parent current handle", 0);
return(0);
}
h = alloc_temp_dir_handle(curhandle, ncpdir, 0, &eff);
if (h < 0) {
ncopy_debug_rc("alloc parent temp handle", -1);
return(-1);
}
*handle = (uint8)h;
*allocated = 1;
ncopy_debug_rc("alloc parent temp handle", 0);
return(0);
}
static int ncopy_open_ncp_file(char *path, uint8 access, uint8 nwhandle[6])
{
char dir[260];
char name[80];
uint8 dhandle = 0;
int allocated = 0;
int namelen;
struct {
uint16 len;
uint8 func;
uint8 dhandle;
uint8 attrib;
uint8 access;
uint8 namelen;
uint8 name[80];
} req;
struct {
uint16 len;
uint8 handle[6];
uint8 reserved[2];
uint8 info[64];
} repl;
if (!nwhandle || ncopy_split_dir_name(path, dir, name))
return(-1);
namelen = strlen(name);
if (namelen < 1 || namelen > 79)
return(-1);
if (ncopy_alloc_parent_handle(path, &dhandle, &allocated))
return(-1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
req.func = 0x4c; /* Open File */
req.dhandle = dhandle;
req.attrib = 0x06; /* hidden/system search */
req.access = access;
req.namelen = (uint8)namelen;
memcpy(req.name, name, namelen);
req.len = (uint16)(1 + 1 + 1 + 1 + 1 + namelen);
repl.len = sizeof(repl) - sizeof(repl.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (allocated)
dealloc_dir_handle(dhandle);
if (neterrno) {
ncopy_debug_rc("open source NCP 0x4c", -1);
return(-1);
}
memcpy(nwhandle, repl.handle, 6);
ncopy_debug_rc("open source NCP 0x4c", 0);
return(0);
}
static int ncopy_create_ncp_file(char *path, uint8 attr, uint8 nwhandle[6])
{
char dir[260];
char name[80];
uint8 dhandle = 0;
int allocated = 0;
int namelen;
struct {
uint16 len;
uint8 func;
uint8 dhandle;
uint8 attr;
uint8 namelen;
uint8 name[80];
} req;
struct {
uint16 len;
uint8 handle[6];
uint8 reserved[2];
uint8 info[64];
} repl;
if (!nwhandle || ncopy_split_dir_name(path, dir, name))
return(-1);
namelen = strlen(name);
if (namelen < 1 || namelen > 79)
return(-1);
if (ncopy_alloc_parent_handle(path, &dhandle, &allocated))
return(-1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
req.func = 0x43; /* Create File, overwrite */
req.dhandle = dhandle;
req.attr = attr;
req.namelen = (uint8)namelen;
memcpy(req.name, name, namelen);
req.len = (uint16)(1 + 1 + 1 + 1 + namelen);
repl.len = sizeof(repl) - sizeof(repl.len);
neterrno = Net_Call(0xE200, &req, &repl);
if (allocated)
dealloc_dir_handle(dhandle);
if (neterrno) {
ncopy_debug_rc("create dest NCP 0x43", -1);
return(-1);
}
memcpy(nwhandle, repl.handle, 6);
ncopy_debug_rc("create dest NCP 0x43", 0);
return(0);
}
static void ncopy_close_ncp_file(uint8 nwhandle[6])
{
struct {
uint16 len;
uint8 reserved;
uint8 handle[6];
} req;
struct { uint16 len; } repl;
if (!nwhandle || ncopy_handle6_is_zero(nwhandle)) {
ncopy_debug_msg("close file skipped for zero handle");
return;
}
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
memcpy(req.handle, nwhandle, 6);
ncopy_debug_handle_bytes("close file handle", nwhandle);
req.len = 1 + 6;
repl.len = 0;
neterrno = Net_Call(0xF242, &req, &repl);
ncopy_debug_rc("close file NCP 0x42", neterrno ? -1 : 0);
}
static int ncopy_ncp74_copy_chunk(uint8 src_handle[6], uint8 dst_handle[6],
uint32 src_offset, uint32 dst_offset,
uint32 count, uint32 *copied)
{
struct {
uint16 len;
uint8 reserved;
uint8 src_handle[6];
uint8 dst_handle[6];
uint8 src_offset[4];
uint8 dst_offset[4];
uint8 count[4];
} req;
struct {
uint16 len;
uint8 copied[4];
} repl;
if (copied)
*copied = 0;
if (ncopy_handle6_is_zero(src_handle) || ncopy_handle6_is_zero(dst_handle)) {
ncopy_debug_msg("copy chunk NCP 0x4a skipped for zero handle");
return(-1);
}
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
memcpy(req.src_handle, src_handle, 6);
memcpy(req.dst_handle, dst_handle, 6);
ncopy_debug_handle_bytes("copy src handle", src_handle);
ncopy_debug_handle_bytes("copy dst handle", dst_handle);
U32_TO_BE32(src_offset, req.src_offset);
U32_TO_BE32(dst_offset, req.dst_offset);
U32_TO_BE32(count, req.count);
req.len = 1 + 6 + 6 + 4 + 4 + 4;
repl.len = 4;
neterrno = Net_Call(0xF24A, &req, &repl);
if (neterrno) {
ncopy_debug_rc("copy chunk NCP 0x4a", -1);
return(-1);
}
if (copied)
*copied = ncopy_get_dword_be(repl.copied);
ncopy_debug_rc("copy chunk NCP 0x4a", 0);
if (copied)
ncopy_debug_ul("copy chunk copied", (unsigned long)*copied);
return(0);
}
static int ncopy_copy_data_server(char *src, char *dst, unsigned long size)
{
uint8 sh[6];
uint8 dh[6];
uint32 off = 0;
uint32 todo;
uint32 done;
memset(sh, 0, sizeof(sh));
memset(dh, 0, sizeof(dh));
ncopy_debug_msg("server-copy begin");
ncopy_debug_ul("server-copy size", size);
_dos_setfileattr(dst, _A_NORMAL);
if (ncopy_open_ncp_file(src, 0x11, sh)) { /* read, binary */
ncopy_debug_msg("server-copy failed opening source");
return(-1);
}
if (ncopy_create_ncp_file(dst, 0, dh)) {
ncopy_debug_msg("server-copy failed creating destination");
ncopy_close_ncp_file(sh);
return(-1);
}
while (off < (uint32)size) {
todo = (uint32)size - off;
if (todo > 0x8000UL)
todo = 0x8000UL;
ncopy_debug_ul("server-copy offset", off);
ncopy_debug_ul("server-copy todo", todo);
if (ncopy_ncp74_copy_chunk(sh, dh, off, off, todo, &done) || done == 0) {
ncopy_debug_msg("server-copy failed copying chunk");
ncopy_close_ncp_file(dh);
ncopy_close_ncp_file(sh);
return(-1);
}
off += done;
}
ncopy_close_ncp_file(dh);
ncopy_close_ncp_file(sh);
ncopy_debug_msg("server-copy success");
return(0);
}
/*
* NetWare DOS requester FileServerFileCopy wrapper.
*
* The MARS-NWE admin Pascal API (admin/nwtp/nwfile.pas) exposes this as
* INT 21h AH=F3 with ES:DI pointing to a request containing DOS file handles
* (word, lo-hi) and long offsets/counts. Use this instead of manually
* building raw NCP 0x4A requests; Client32/requester owns the real NetWare
* file handle mapping.
*/
typedef struct ncopy_f3_req {
uint16 src_handle;
uint16 dst_handle;
uint32 src_offset;
uint32 dst_offset;
uint32 count;
} NCOPY_F3_REQ;
static int ncopy_int21_f3_copy_chunk(int src_handle, int dst_handle,
uint32 src_offset, uint32 dst_offset,
uint32 count, uint32 *copied)
{
NCOPY_F3_REQ req;
union REGS regs;
struct SREGS sregs;
uint32 c1;
uint32 c2;
if (copied)
*copied = 0;
memset(&req, 0, sizeof(req));
memset(®s, 0, sizeof(regs));
segread(&sregs);
req.src_handle = (uint16)src_handle;
req.dst_handle = (uint16)dst_handle;
req.src_offset = src_offset;
req.dst_offset = dst_offset;
req.count = count;
regs.h.ah = 0xF3;
sregs.es = FP_SEG(&req);
regs.x.di = FP_OFF(&req);
int86x(0x21, ®s, ®s, &sregs);
if (ncopy_debug_enabled()) {
fprintf(stdout,
"NCOPYDBG: int21 ah=f3 al=%u ax=%u cx=%u dx=%u srcfh=%u dstfh=%u count=%lu\n",
(unsigned)regs.h.al, (unsigned)regs.x.ax,
(unsigned)regs.x.cx, (unsigned)regs.x.dx,
(unsigned)src_handle, (unsigned)dst_handle,
(unsigned long)count);
}
if (regs.h.al != 0) {
if (errno == 0)
errno = regs.h.al;
return(-1);
}
c1 = ((uint32)regs.x.cx) | ((uint32)regs.x.dx << 16);
c2 = ((uint32)regs.x.dx) | ((uint32)regs.x.cx << 16);
if (c1 > 0 && c1 <= count)
*copied = c1;
else if (c2 > 0 && c2 <= count)
*copied = c2;
else
*copied = count;
return(0);
}
static int ncopy_copy_data_requester(char *src, char *dst, unsigned long size)
{
int s;
int d;
uint32 off = 0;
uint32 todo;
uint32 done;
ncopy_debug_msg("requester-copy begin");
ncopy_debug_ul("requester-copy size", size);
_dos_setfileattr(dst, _A_NORMAL);
s = open(src, O_RDONLY | O_BINARY);
if (s < 0) {
ncopy_debug_rc("requester open source", -1);
return(-1);
}
d = open(dst, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE);
if (d < 0) {
ncopy_debug_rc("requester create dest", -1);
close(s);
return(-1);
}
ncopy_debug_rc("requester open source", s);
ncopy_debug_rc("requester create dest", d);
while (off < (uint32)size) {
todo = (uint32)size - off;
if (todo > 0x7000UL)
todo = 0x7000UL;
done = 0;
ncopy_debug_ul("requester-copy offset", off);
ncopy_debug_ul("requester-copy todo", todo);
if (ncopy_int21_f3_copy_chunk(s, d, off, off, todo, &done) || done == 0) {
ncopy_debug_msg("requester-copy failed copying chunk");
close(d);
close(s);
return(-1);
}
ncopy_debug_ul("requester-copy copied", done);
off += done;
}
close(d);
close(s);
ncopy_debug_msg("requester-copy success");
return(0);
}
static int ncopy_copy_data(char *src, char *dst, unsigned long size)
{
int s, d;
unsigned got, put;
/*
* Do not run the direct F257 Open/Create path by default. On Client32/MARS
* it executes server-side NCP 87/01 and allocates a server file handle, but
* the DOS Net_Call reply buffer does not receive that handle. Without a
* returned handle we cannot safely issue NCP 74 or NCP 66 Close; repeated
* test runs leak server file handles and can make the requester retry/hang.
*
* Keep the path available only for explicit diagnostics:
* SET NCOPYNCP74=1
*/
if (ncopy_ncp74_enabled()) {
if (!ncopy_copy_data_direct_ncp87(src, dst, size))
return(0);
ncopy_debug_msg("direct NCP87/NCP74 server-copy failed; trying INT21 AH=F3 requester copy");
} else {
ncopy_debug_msg("direct NCP87/NCP74 server-copy disabled; set NCOPYNCP74=1 to probe it");
}
if (!ncopy_copy_data_requester(src, dst, size))
return(0);
ncopy_debug_msg("requester-copy failed; falling back to DOS read/write");
s = open(src, O_RDONLY | O_BINARY);
if (s < 0) {
fprintf(stdout, " Failed to open the %s file.\n", src);
return(-1);
}
_dos_setfileattr(dst, _A_NORMAL);
d = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE);
if (d < 0) {
close(s);
fprintf(stdout, " Failed to create file.\n");
return(-1);
}
for (;;) {
if (_dos_read(s, ncopy_buffer, NCOPY_BUF_SIZE, &got)) {
close(s); close(d);
fprintf(stdout, " Failure occurred while reading the file.\n");
return(-1);
}
if (got == 0)
break;
if (_dos_write(d, ncopy_buffer, got, &put) || put != got) {
close(s); close(d);
fprintf(stdout, " Failure occurred while writing the file.\n");
return(-1);
}
}
close(s);
close(d);
return(0);
}
static void ncopy_restore_netware_attrs(char *src, char *dst, unsigned fallback_attr)
{
char srcdir[260];
char srcname[80];
char dstdir[260];
char dstname[80];
uint8 src_dhandle = 0;
uint8 dst_dhandle = 0;
int src_allocated = 0;
int dst_allocated = 0;
C32_NDIR_INFO sinfo;
uint32 attrs;
int rc;
memset(&sinfo, 0, sizeof(sinfo));
if (ncopy_split_dir_name(src, srcdir, srcname) ||
ncopy_split_dir_name(dst, dstdir, dstname))
return;
if (ncopy_alloc_parent_handle(src, &src_dhandle, &src_allocated))
return;
if (ncopy_alloc_parent_handle(dst, &dst_dhandle, &dst_allocated)) {
if (src_allocated)
dealloc_dir_handle(src_dhandle);
return;
}
attrs = (uint32)(fallback_attr & (_A_RDONLY | _A_HIDDEN |
_A_SYSTEM | _A_ARCH));
rc = c32_ncp87_obtain_ndir_info(srcname, src_dhandle,
&sinfo, NULL, NULL, NULL);
if (!rc)
attrs = sinfo.attributes;
rc = c32_ncp87_modify_dos_attributes(dstname, dst_dhandle, attrs,
NULL, NULL, NULL);
ncopy_debug_rc("restore NetWare attrs NCP87/07", rc);
ncopy_debug_ul("restore NetWare attrs", attrs);
if (dst_allocated)
dealloc_dir_handle(dst_dhandle);
if (src_allocated)
dealloc_dir_handle(src_dhandle);
}
static int ncopy_copy_one(char *src, char *dst, struct find_t *ff, int options)
{
unsigned attr;
int d;
char srcdir[260];
char srcname[80];
char dstdir[260];
char dstname[80];
if (ncopy_same_path(src, dst)) {
fprintf(stdout, " The file will NOT be copied because the source and destination file names are the same.\n");
return(-1);
}
if ((options & (NCOPY_OPT_A | NCOPY_OPT_M)) && !(ff->attrib & _A_ARCH))
return(1);
if (ncopy_split_dir_name(src, srcdir, srcname)) {
strmaxcpy(srcdir, ".", sizeof(srcdir) - 1);
ncopy_basename(srcname, src, sizeof(srcname));
}
if (ncopy_split_dir_name(dst, dstdir, dstname)) {
strmaxcpy(dstdir, ".", sizeof(dstdir) - 1);
ncopy_basename(dstname, dst, sizeof(dstname));
}
ncopy_print_group(srcdir, dstdir);
ncopy_print_file_map(srcname, dstname);
if (ncopy_copy_data(src, dst, (unsigned long)ff->size))
return(-1);
d = open(dst, O_WRONLY | O_BINARY);
if (d >= 0) {
_dos_setftime(d, ff->wr_date, ff->wr_time);
close(d);
} else {
fprintf(stdout, " Failure setting destination file date and time.\n");
}
attr = ff->attrib & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
if (_dos_setfileattr(dst, attr))
fprintf(stdout, " Error setting file information.\n");
/* Preserve high NetWare DOS attributes such as Delete/Rename Inhibit.
* DOS setfileattr can only express the low DOS bits, which was the reason
* B.TXT ended up as [Ro-A--------------] instead of carrying DI/RI. */
ncopy_restore_netware_attrs(src, dst, attr);
if ((options & NCOPY_OPT_V) && ncopy_verify_file(src, dst))
fprintf(stdout, " Error copying file.\n");
if (options & NCOPY_OPT_M) {
unsigned newattr = ff->attrib & ~_A_ARCH;
_dos_setfileattr(src, newattr & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH));
}
return(0);
}
static int ncopy_copy_pattern(char *srcdir, char *pattern, char *dstdir,
int options, int *copied, int *failed);
static int ncopy_copy_subdirs(char *srcdir, char *dstdir, int options,
int *copied, int *failed)
{
struct find_t ff;
char search[260];
int rc;
ncopy_join(search, srcdir, "*.*", sizeof(search));
rc = _dos_findfirst(search, _A_NORMAL | _A_RDONLY | _A_HIDDEN |
_A_SYSTEM | _A_ARCH | _A_SUBDIR, &ff);
while (rc == 0) {
if ((ff.attrib & _A_SUBDIR) && !tool_is_dot_dir(ff.name)) {
char childsrc[260];
char childdst[260];
int before;
ncopy_join(childsrc, srcdir, ff.name, sizeof(childsrc));
ncopy_join(childdst, dstdir, ff.name, sizeof(childdst));
before = *copied;
ncopy_print_group(childsrc, childdst);
if (options & NCOPY_OPT_E)
ncopy_make_dir(childdst);
ncopy_copy_pattern(childsrc, "*.*", childdst, options, copied, failed);
ncopy_copy_subdirs(childsrc, childdst, options, copied, failed);
if (*copied > before)
ncopy_make_dir(childdst);
}
rc = _dos_findnext(&ff);
}
return(0);
}
static int ncopy_copy_pattern(char *srcdir, char *pattern, char *dstdir,
int options, int *copied, int *failed)
{
struct find_t ff;
char search[260];
int rc;
int any = 0;
ncopy_join(search, srcdir, pattern, sizeof(search));
rc = _dos_findfirst(search, _A_NORMAL | _A_RDONLY | _A_HIDDEN |
_A_SYSTEM | _A_ARCH, &ff);
while (rc == 0) {
if (!(ff.attrib & _A_SUBDIR)) {
char src[260];
char dst[260];
int r;
any = 1;
ncopy_join(src, srcdir, ff.name, sizeof(src));
ncopy_join(dst, dstdir, ff.name, sizeof(dst));
if (ncopy_make_dir(dstdir)) {
fprintf(stdout, " Illegal destination.\n");
return(-1);
}
r = ncopy_copy_one(src, dst, &ff, options);
if (r == 0)
(*copied)++;
else if (r < 0)
(*failed)++;
}
rc = _dos_findnext(&ff);
}
if ((options & NCOPY_OPT_S) != 0)
ncopy_copy_subdirs(srcdir, dstdir, options, copied, failed);
return(any ? 0 : 1);
}
static int ncopy_copy_single(char *src, char *dstarg, int options,
int *copied, int *failed)
{
struct find_t ff;
char dst[260];
char base[80];
int rc;
rc = _dos_findfirst(src, _A_NORMAL | _A_RDONLY | _A_HIDDEN |
_A_SYSTEM | _A_ARCH, &ff);
if (rc)
return(1);
if (ff.attrib & _A_SUBDIR)
return(1);
if (ncopy_is_dir(dstarg)) {
ncopy_basename(base, src, sizeof(base));
ncopy_join(dst, dstarg, base, sizeof(dst));
} else {
strmaxcpy(dst, dstarg, sizeof(dst) - 1);
}
rc = ncopy_copy_one(src, dst, &ff, options);
if (rc == 0)
(*copied)++;
else if (rc < 0)
(*failed)++;
return(0);
}
static void ncopy_summary(int copied, int failed)
{
if (copied == 0)
fprintf(stdout, " No files copied.\n");
else
fprintf(stdout, " %d file%s copied.\n", copied, copied == 1 ? "" : "s");
if (failed)
fprintf(stdout, " %d file%s NOT copied.\n", failed, failed == 1 ? "" : "s");
}
static int ncopy_upper_char(int c)
{
if (c >= 'a' && c <= 'z')
return(c - ('a' - 'A'));
return(c);
}
static int ncopy_parse_option(char *s, int *options)
{
char *p;
int c1;
int c2;
int c3;
if (!s || !*s)
return(0);
/* Novell NCOPY accepts case-insensitive options anywhere on the
* command line, including after the destination path:
* NCOPY SRC DST /V
* NCOPY SRC TO DST /S /E
* NCOPY /A SRC TO DST /S
* Keep this parser independent of shared helpers so DOS/Watcom argv
* strings such as lowercase /v are handled exactly here. */
if (s[0] != '/' && s[0] != '-')
return(0);
p = s;
while (*p == '/' || *p == '-')
p++;
if (!*p)
return(-1);
c1 = ncopy_upper_char((unsigned char)p[0]);
c2 = ncopy_upper_char((unsigned char)p[1]);
c3 = ncopy_upper_char((unsigned char)p[2]);
if (c1 == '?' && p[1] == '\0')
return(2);
if (c1 == 'H' && p[1] == '\0')
return(2);
if (c1 == 'H' && c2 == 'E' && c3 == 'L' &&
ncopy_upper_char((unsigned char)p[3]) == 'P' && p[4] == '\0')
return(2);
if (c1 == 'S' && p[1] == '\0') { *options |= NCOPY_OPT_S; return(1); }
if (c1 == 'E' && p[1] == '\0') { *options |= NCOPY_OPT_E; return(1); }
if (c1 == 'V' && p[1] == '\0') { *options |= NCOPY_OPT_V; return(1); }
if (c1 == 'A' && p[1] == '\0') { *options |= NCOPY_OPT_A; return(1); }
if (c1 == 'M' && p[1] == '\0') { *options |= NCOPY_OPT_M | NCOPY_OPT_A; return(1); }
if (c1 == 'I' && p[1] == '\0') { *options |= NCOPY_OPT_I; return(1); }
if (c1 == 'C' && p[1] == '\0') { *options |= NCOPY_OPT_C; return(1); }
if (c1 == 'F' && p[1] == '\0') { *options |= NCOPY_OPT_F; return(1); }
if (c1 == 'S' && c2 == '/' && c3 == 'E' && p[3] == '\0') {
*options |= NCOPY_OPT_S | NCOPY_OPT_E;
return(1);
}
if (c1 == 'S' && c2 == 'E' && p[2] == '\0') {
*options |= NCOPY_OPT_S | NCOPY_OPT_E;
return(1);
}
return(-1);
}
static int ncopy_is_to_separator(char *s)
{
return(s && tool_strsame(s, "TO"));
}
int func_ncopy(int argc, char *argv[], int mode)
{
char *src = NULL;
char *dst = NULL;
int options = 0;
int i;
int copied = 0;
int failed = 0;
int saw_to = 0;
int result;
char srcdir[260];
char pat[260];
(void)mode;
if (ncopy_debug_enabled()) {
fprintf(stdout, "NCOPYDBG: func_ncopy entered argc=%d\n", argc);
for (i = 0; i < argc; i++)
fprintf(stdout, "NCOPYDBG: argv[%d]=<%s>\n",
i, argv[i] ? argv[i] : "");
}
if (argc <= 1) {
ncopy_usage();
return(1);
}
for (i = 1; i < argc; i++) {
int opt = ncopy_parse_option(argv[i], &options);
/* Options are legal before the source, between source and TO, and after
* the destination. Parse them before treating anything as a path. */
if (opt == 2) {
ncopy_usage();
return(0);
}
if (opt == 1)
continue;
if (opt < 0) {
ncopy_usage();
return(1);
}
/* TO is an optional, case-insensitive separator, not a required keyword.
* NCOPY SRC DST and NCOPY SRC TO DST are equivalent. */
if (ncopy_is_to_separator(argv[i])) {
saw_to = 1;
continue;
}
if (!src)
src = argv[i];
else if (!dst)
dst = argv[i];
else {
fprintf(stdout, "You cannot copy multiple files to a single file.\n");
return(1);
}
}
if (saw_to && !dst) {
ncopy_usage();
return(1);
}
if ((options & NCOPY_OPT_E) && !(options & NCOPY_OPT_S)) {
fprintf(stdout, "The /E parameter (copy empty directories) is only valid with the /S parameter (copy sub directories).\n");
return(1);
}
if (!src || !dst) {
ncopy_usage();
return(1);
}
ncopy_reset_output_group();
if (ncopy_has_wildcards(src) || (options & NCOPY_OPT_S)) {
if (!ncopy_is_dir(dst)) {
fprintf(stdout, " Illegal destination.\n");
ncopy_summary(0, 0);
return(1);
}
if ((options & NCOPY_OPT_S) && ncopy_is_dir(src) && !ncopy_has_wildcards(src)) {
/* Novell NCOPY accepts a directory as the source root with /S. */
strmaxcpy(srcdir, src, sizeof(srcdir) - 1);
strmaxcpy(pat, "*.*", sizeof(pat) - 1);
} else {
ncopy_parent_pattern(srcdir, pat, src);
}
result = ncopy_copy_pattern(srcdir, pat, dst, options, &copied, &failed);
} else {
result = ncopy_copy_single(src, dst, options, &copied, &failed);
}
if (result > 0) {
fprintf(stdout, " Files not found.\n");
ncopy_summary(0, failed);
return(1);
}
ncopy_finish_output_groups();
ncopy_summary(copied, failed);
return(failed ? 1 : 0);
}