Files
mars-dosutils/ncopy.c
Mario Fetka 50524cf759 dosutils: add GPL-2 headers and file descriptions
Add GPL-2-or-later license headers to the DOS utility source files and
document the purpose and local dependencies of each C, header and assembler
file.

Preserve the original Martin Stover copyright attribution for the historic
MARS-NWE utility sources, including files that did not previously carry an
explicit header but are part of the original tool set. Add Mario Fetka as the
2026 copyright holder for the current maintenance work, and use Mario-only
headers for files without original Martin Stover ownership.

Also add a root-level COPYING file containing the GPL-2 license text.
2026-05-29 08:07:09 +02:00

1722 lines
45 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <dos.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <io.h>
#include <errno.h>
#include <stdlib.h>
#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 <word dhandle> <word dirbase> ...
* 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(&regs, 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, &regs, &regs, &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);
}