Replace the local NCOPY endian buffer helpers with the shared tools.c helpers. This removes duplicate little-endian and big-endian get/put code while keeping the experimental NCOPY NCP request layouts unchanged. No behavior change.
1691 lines
45 KiB
C
1691 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, ncpapi.h, netcall.c requester helpers, ncpapi.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 "ncpapi.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, NCP_FILE_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 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_ncp_server(char *src, char *dst, unsigned long size)
|
|
{
|
|
NCP_FILE_HANDLE6 sh;
|
|
NCP_FILE_HANDLE6 dh;
|
|
NCP_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 (!ncp87_06_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 = ncp87_01_open_create_entry(srcname,
|
|
src_dhandle,
|
|
NCP_OC_MODE_OPEN,
|
|
0,
|
|
NCP_DAR_READ | NCP_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 = ncp87_01_open_create_entry(dstname,
|
|
dst_dhandle,
|
|
NCP_OC_MODE_OPEN |
|
|
NCP_OC_MODE_TRUNCATE |
|
|
NCP_OC_MODE_CREATE,
|
|
create_attrs,
|
|
NCP_DAR_WRITE | NCP_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 = 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)
|
|
ncp66_close_file(&dh);
|
|
if (src_open)
|
|
ncp66_close_file(&sh);
|
|
|
|
ncopy_debug_msg("c32-ncp87/ncp74 server-copy success");
|
|
return(0);
|
|
|
|
fail:
|
|
if (dst_open)
|
|
ncp66_close_file(&dh);
|
|
if (src_open)
|
|
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 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;
|
|
tool_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;
|
|
tool_put_word_lh(req.search_attrs, search_attrs);
|
|
tool_put_dword_lh(req.return_info_mask, 0x00000fffUL);
|
|
tool_put_dword_lh(req.create_attrs, create_attrs);
|
|
tool_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 = tool_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;
|
|
NCP_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 (!ncp87_06_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 = tool_get_dword_hl(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;
|
|
NCP_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 = ncp87_06_obtain_ndir_info(srcname, src_dhandle,
|
|
&sinfo, NULL, NULL, NULL);
|
|
if (!rc)
|
|
attrs = sinfo.attributes;
|
|
|
|
rc = ncp87_07_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 (tool_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) && !tool_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);
|
|
}
|