Files
mars-dosutils/c32ncp.c
Mario Fetka e7f67fa004 c32ncp: document NCP API wrapper functions
Add function-level comments to the NCP API wrappers in c32ncp.c.

Document the local packet helpers, Client32/Raw5 handle-path setup, NCP87
entry information and DOS information calls, effective rights lookup,
trustee-related NCP22/NCP87 wrappers, and the experimental NCP87/01,
NCP74 and NCP66 helpers used by the NCOPY investigation.

The comments are written so the file can later be renamed to ncpapi.c without
changing the documented API purpose.

No behavior change.
2026-05-29 10:57:57 +02:00

1668 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: Namespace and file-system NCP API helper implementation used by the NetWare DOS tools.
* Depends on: net.h, c32ncp.h, kern_wasm.asm/kern.asm for Client32 probe entry points, and netcall.c for shared requester state. This file is planned to become ncpapi.c.
*/
#include "net.h"
#include "c32ncp.h"
/* c32ncp.c - namespace/file-system NCP API helpers for mars-dosutils */
/*
* c32_put_word_lh
*
* Purpose:
* Writes a 16-bit little-endian value into a request buffer.
*
* Notes:
* This file will later become ncpapi.c. These local helpers remain here
* for now so the NCP API wrappers keep their packet construction isolated
* from the more general tools.c helpers.
*/
static void c32_put_word_lh(uint8 *p, uint16 v)
{
p[0] = (uint8)(v & 0xff);
p[1] = (uint8)((v >> 8) & 0xff);
}
/*
* c32_put_dword_lh
*
* Purpose:
* Writes a 32-bit little-endian value into a request buffer.
*/
static void c32_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);
}
/*
* c32_put_dword_hl
*
* Purpose:
* Writes a 32-bit high-low value into a request buffer.
*
* Notes:
* Some older file and trustee NCPs use high-low object ids or offsets even
* when the surrounding NCP87 fields are little-endian.
*/
static void c32_put_dword_hl(uint8 *p, uint32 v)
{
p[0] = (uint8)((v >> 24) & 0xff);
p[1] = (uint8)((v >> 16) & 0xff);
p[2] = (uint8)((v >> 8) & 0xff);
p[3] = (uint8)(v & 0xff);
}
/*
* c32_get_word_lh
*
* Purpose:
* Reads a 16-bit little-endian value from a reply buffer.
*/
static uint16 c32_get_word_lh(uint8 *p)
{
return((uint16)(p[0] | ((uint16)p[1] << 8)));
}
/*
* c32_get_dword_lh
*
* Purpose:
* Reads a 32-bit little-endian value from a reply buffer.
*/
static uint32 c32_get_dword_lh(uint8 *p)
{
return((uint32)p[0] |
((uint32)p[1] << 8) |
((uint32)p[2] << 16) |
((uint32)p[3] << 24));
}
/*
* c32_get_dword_hl
*
* Purpose:
* Reads a 32-bit high-low value from a reply buffer.
*/
static uint32 c32_get_dword_hl(uint8 *p)
{
return(((uint32)p[0] << 24) |
((uint32)p[1] << 16) |
((uint32)p[2] << 8) |
(uint32)p[3]);
}
/*
* c32_build_handle_path
*
* Purpose:
* Builds the Client32-compatible NWHandlePathStruct used by NCP87 request
* wrappers when the caller already has one to three path components.
*
* Parameters:
* dhandle/dirbase/style describe the starting directory context. c1..c3
* are written as length-prefixed path components.
*
* Returns:
* Number of bytes used by the path structure.
*/
static UI c32_build_handle_path(uint8 *buf, uint8 dhandle,
uint16 dirbase, uint8 style,
int count,
const char *c1, const char *c2, const char *c3)
{
uint8 *p;
int l;
UI used;
/*
* DeveloperNet/ncpdos16 path structure used by NCP87/S6 through
* Client32 COMPATNcpRequestReply.
*
* This is the exact shape verified by TESTS NCP87C32AUTO:
* 00 02 00 00 00 00 01 09 4C 4F 47 49 4E 2E 45 58 45
*
* Meaning:
* word[1] = short dir handle
* word[3] = dir base
* byte[5] = dirstyle
* byte[6] = component count
* then len/name components
*
* The old/simple struct used by the INT 21h F257 fallback is not accepted
* by this Client32 path.
*/
memset(buf, 0, 0x140);
if (dhandle) {
c32_put_word_lh(buf + 1, (uint16)dhandle);
c32_put_word_lh(buf + 3, dirbase);
buf[5] = style;
} else {
buf[5] = 0xff;
}
p = buf + 6;
*p++ = (uint8)count;
if (count > 0 && c1) {
l = strlen(c1);
if (l > 255) l = 255;
*p++ = (uint8)l;
memcpy(p, c1, l);
p += l;
}
if (count > 1 && c2) {
l = strlen(c2);
if (l > 255) l = 255;
*p++ = (uint8)l;
memcpy(p, c2, l);
p += l;
}
if (count > 2 && c3) {
l = strlen(c3);
if (l > 255) l = 255;
*p++ = (uint8)l;
memcpy(p, c3, l);
p += l;
}
used = (UI)(p - buf);
c32_put_word_lh(buf + 0x13c, used);
return(used);
}
/*
* c32_build_handle_path_from_dos_path
*
* Purpose:
* Converts a DOS-style path string into the Client32-compatible
* NWHandlePathStruct used by the NCP87 wrappers.
*
* Notes:
* Drive prefixes, leading slashes and simple current-directory components
* are skipped so tool callers can pass ordinary DOS paths.
*/
static UI c32_build_handle_path_from_dos_path(uint8 *buf, uint8 dhandle,
uint16 dirbase, uint8 style,
const char *dospath)
{
uint8 *p;
uint8 *countp;
int count = 0;
const char *s;
UI used;
memset(buf, 0, 0x140);
if (dhandle) {
c32_put_word_lh(buf + 1, (uint16)dhandle);
c32_put_word_lh(buf + 3, dirbase);
buf[5] = style;
} else {
buf[5] = 0xff;
}
p = buf + 6;
countp = p++;
s = dospath;
if (!s) s = "";
/*
* DOS tools mostly pass relative paths against the current directory
* handle. Accept simple DOS decoration here so RIGHTS can pass "." or
* ".\\UDIR\\FILE" without constructing path components in the caller.
*/
if (s[0] && s[1] == ':')
s += 2;
while (*s == '\\' || *s == '/')
s++;
while (*s && p < buf + 0x138 && count < 32) {
const char *start;
int len;
while (*s == '\\' || *s == '/')
s++;
if (*s == '.'
&& (s[1] == '\0' || s[1] == '\\' || s[1] == '/')) {
s++;
continue;
}
start = s;
while (*s && *s != '\\' && *s != '/')
s++;
len = (int)(s - start);
if (len <= 0)
continue;
if (len > 255)
len = 255;
if (p + 1 + len >= buf + 0x138)
break;
*p++ = (uint8)len;
memcpy(p, start, len);
p += len;
count++;
}
*countp = (uint8)count;
used = (UI)(p - buf);
c32_put_word_lh(buf + 0x13c, used);
return(used);
}
/*
* Current verified Client32 path for mars-nwe DOS utilities:
*
* C32_MapVar_Probe(4,0) -> connRefLocal FFFF:FFFE
* C32_OpenRef_Probe(connRefLocal) -> Client32 handle, e.g. 0101:0001
*
* C32_MapVar_Probe currently contains the confirmed Mars server-name scan
* shape. It is intentionally kept small and isolated here so FLAG and later
* tools do not carry the old exploratory tests.
*/
/*
* c32_get_ncp_handle
*
* Purpose:
* Resolves the active MARS/NetWare connection into the Client32 NCP handle
* used by the Raw5 requester probe.
*
* Requester path:
* C32_MapVar_Probe followed by C32_OpenRef_Probe.
*
* Returns:
* 0 on success. Non-zero values indicate that the connection reference or
* Client32 NCP handle could not be obtained.
*/
int c32_get_ncp_handle(uint16 *handle_lo, uint16 *handle_hi)
{
uint8 mapout[32];
uint8 openout[32];
uint16 map_ret_ax, map_ret_dx;
uint16 cref_lo, cref_hi;
uint16 open_ret_ax, open_ret_dx;
if (!handle_lo || !handle_hi)
return(1);
*handle_lo = 0;
*handle_hi = 0;
memset(mapout, 0, sizeof(mapout));
C32_MapVar_Probe(4, 0, mapout);
map_ret_ax = c32_get_word_lh(mapout + 14);
map_ret_dx = c32_get_word_lh(mapout + 16);
cref_lo = c32_get_word_lh(mapout + 22);
cref_hi = c32_get_word_lh(mapout + 24);
if (map_ret_ax != 0 || map_ret_dx != 0 || (cref_lo == 0 && cref_hi == 0))
return(2);
memset(openout, 0, sizeof(openout));
C32_OpenRef_Probe(cref_lo, cref_hi, openout);
open_ret_ax = c32_get_word_lh(openout + 14);
open_ret_dx = c32_get_word_lh(openout + 16);
*handle_lo = c32_get_word_lh(openout + 18);
*handle_hi = c32_get_word_lh(openout + 20);
if (open_ret_ax != 0 || open_ret_dx != 0 || (*handle_lo == 0 && *handle_hi == 0))
return(3);
return(0);
}
/*
* c32_copy_open_reply_to_handle6
*
* Purpose:
* Extracts the six-byte server file handle from an NCP87/01 Open/Create
* reply fragment for later NCP74 Copy or NCP66 Close calls.
*
* Notes:
* The exact handle layout is still part of the NCOPY investigation. This
* helper keeps the extraction in one place so the code can be corrected
* without touching the callers.
*/
static void c32_copy_open_reply_to_handle6(C32_NWFILE_HANDLE6 *dst,
const uint8 *src)
{
/*
* NCP 74 File Server Copy and NCP 66 Close File both take a six-byte
* server file handle. Client32's NCP87 Open/Create reply supplies that
* six-byte handle at the start of the first reply fragment; the following
* bytes are OpenCreateAction and Reserved.
*
* The previous patch only copied four bytes and zero-filled h[4..5]. That
* is enough to create a visible zero-length target file, but it is not the
* handle that NCP74/NCP66 own. The bad close leaves Client32/MARS seeing
* the target as still open, so the fallback DOS/requester open reports
* "File in use during a file open" for every file.
*/
memset(dst->h, 0, sizeof(dst->h));
memcpy(dst->h, src, 6);
}
/*
* ncp87_01_open_create_entry
*
* Purpose:
* Opens or creates a namespace file/directory entry and returns the server
* file handle required by copy/close style NCPs.
*
* NCP:
* Function 0x57 / subfunction 0x01, Open/Create File or Subdirectory.
*
* Requester path:
* Client32 Raw5 fragment request.
*
* Notes:
* This wrapper is currently experimental for NCOPY. Earlier tests showed
* that the old INT 21h F257 path reaches MARS-NWE but does not reliably
* return the open file handle to the DOS caller.
*/
int ncp87_01_open_create_entry(const char *path_name,
uint16 dir_handle,
uint8 open_create_mode,
uint32 create_attrs,
uint16 desired_access,
uint16 search_attrs,
C32_NWFILE_HANDLE6 *handle_out,
uint32 *file_size_out,
uint8 *open_create_action_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[0x140];
uint8 rep0[0x180];
uint8 rep1[0x40];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
UI path_len;
int rc;
if (handle_out)
memset(handle_out, 0, sizeof(*handle_out));
if (file_size_out) *file_size_out = 0;
if (open_create_action_out) *open_create_action_out = 0;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
if (!path_name || !handle_out)
return(1);
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* NCP 87 subfunction 1: Open/Create File or Subdirectory.
*
* Request payload after the NCP87 subfunction byte:
* byte NameSpace (0 = DOS)
* byte OpenCreateMode (OPEN/TRUNCATE/CREATE bits)
* word SearchAttributes (Lo-Hi)
* dword ReturnInfoMask (Lo-Hi)
* dword CreateAttributes (Lo-Hi)
* word DesiredAccessRights (Lo-Hi)
* NWHandlePathStruct
*
* The Client32 Raw5 requester path sends the subfunction
* header as fragment 0 and the NWHandlePathStruct as fragment 1.
*/
memset(hdr, 0, sizeof(hdr));
hdr[0] = 1; /* NCP87 subfunction 1 */
hdr[1] = 0; /* DOS namespace */
hdr[2] = open_create_mode;
c32_put_word_lh(hdr + 3, search_attrs);
c32_put_dword_lh(hdr + 5, 0x00000FFFUL); /* RIM_ALL, keeps size handy */
c32_put_dword_lh(hdr + 9, create_attrs);
c32_put_word_lh(hdr + 13, desired_access);
path_len = c32_build_handle_path_from_dos_path(path, (uint8)dir_handle,
0, 0, path_name);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 15,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
c32_copy_open_reply_to_handle6(handle_out, rep0 + 0);
if (open_create_action_out)
*open_create_action_out = rep0[6];
/* NetWareInfoStruct follows FileHandle[6]/OpenCreateAction/Reserved. */
if (file_size_out)
*file_size_out = c32_get_dword_lh(rep0 + 8 + 10);
return(0);
}
/*
* ncp74_file_server_copy
*
* Purpose:
* Copies file data server-side between two already-open NetWare file
* handles.
*
* NCP:
* Function 0x4a, File Server Copy.
*
* Requester path:
* Direct F24A Net_Call wrapper.
*
* Notes:
* Offsets and byte count are encoded high-low. The source and destination
* handles must be the six-byte server handles accepted by the file server.
*/
int ncp74_file_server_copy(const C32_NWFILE_HANDLE6 *src,
const C32_NWFILE_HANDLE6 *dst,
uint32 src_offset,
uint32 dst_offset,
uint32 count,
uint32 *copied_out)
{
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_out)
*copied_out = 0;
if (!src || !dst)
return(1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
memcpy(req.src_handle, src->h, 6);
memcpy(req.dst_handle, dst->h, 6);
c32_put_dword_hl(req.src_offset, src_offset);
c32_put_dword_hl(req.dst_offset, dst_offset);
c32_put_dword_hl(req.count, count);
req.len = 1 + 6 + 6 + 4 + 4 + 4;
repl.len = 4;
neterrno = Net_Call(0xF24A, &req, &repl);
if (neterrno)
return(-neterrno);
if (copied_out)
*copied_out = c32_get_dword_hl(repl.copied);
return(0);
}
/*
* ncp66_close_file
*
* Purpose:
* Closes a NetWare server file handle returned by an open/create wrapper.
*
* NCP:
* Function 0x42 / decimal 66, Close File.
*
* Requester path:
* Direct F242 Net_Call wrapper.
*/
int ncp66_close_file(const C32_NWFILE_HANDLE6 *handle)
{
struct {
uint8 len;
uint8 reserved;
uint8 handle[6];
} req;
struct { uint8 len; } repl;
if (!handle)
return(1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
memcpy(req.handle, handle->h, 6);
req.len = 1 + 6;
repl.len = 0;
neterrno = Net_Call(0xF242, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
/*
* ncp87_06_obtain_rim_attributes
*
* Purpose:
* Reads only the DOS attribute field for a directory entry using the
* NCP87 return-information-mask mechanism.
*
* NCP:
* Function 0x57 / subfunction 0x06, Obtain File or Subdirectory
* Information, RIM_ATTRIBUTES.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_06_obtain_rim_attributes(const char *name,
uint16 dir_handle,
uint32 *attr_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[0x140];
uint8 rep0[0x60];
uint8 rep1[0x110];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
int path_len;
int rc;
if (!name || !attr_out)
return(1);
*attr_out = 0;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
memset(hdr, 0, sizeof(hdr));
hdr[0] = 6; /* NCP87 subfunction 6 */
hdr[1] = 0; /* source namespace DOS */
hdr[2] = 0; /* target namespace DOS */
c32_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
c32_put_dword_lh(hdr + 5, 0x00000004UL); /* RIM_ATTRIBUTES */
path_len = c32_build_handle_path(path, (uint8)dir_handle, 0, 0, 1,
name, NULL, NULL);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, (UI)path_len,
rep0, 0x4d,
rep1, 0x100,
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
/*
* Verified reply layout for RIM_ATTRIBUTES:
* REP0+4 little-endian dword = DOS attributes
* Example LOGIN.EXE: 20h archive.
*/
*attr_out = c32_get_dword_lh(rep0 + 4);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
return(0);
}
/*
* ncp87_06_obtain_ndir_info
*
* Purpose:
* Reads the DOS namespace information block used by NDIR for Novell-style
* metadata output.
*
* NCP:
* Function 0x57 / subfunction 0x06, Obtain File or Subdirectory
* Information, RIM_ALL.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_06_obtain_ndir_info(const char *path_name,
uint16 dir_handle,
C32_NDIR_INFO *info_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[0x140];
uint8 rep0[0x180];
uint8 rep1[0x40];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
UI path_len;
int rc;
int namelen;
if (!info_out)
return(1);
memset(info_out, 0, sizeof(*info_out));
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* NCP87 subfunction 6: Obtain File or Subdirectory Information.
*
* This asks for the classic DOS info block (RIM_ALL). ncpfs' old
* nw_info_struct layout is:
* +00 space allocated
* +04 attributes
* +20 creation time/date/id
* +28 modify time/date/id
* +36 last access date
* +38 archive time/date/id
* +46 inherited rights mask
* +48 directory numbers
* +76 name length/name
*/
memset(hdr, 0, sizeof(hdr));
hdr[0] = 6; /* NCP87 subfunction 6 */
hdr[1] = 0; /* source namespace DOS */
hdr[2] = 0; /* target namespace DOS */
c32_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
c32_put_dword_lh(hdr + 5, 0x00000FFFUL); /* RIM_ALL */
path_len = c32_build_handle_path_from_dos_path(path, (uint8)dir_handle,
0, 0, path_name);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
info_out->space_allocated = c32_get_dword_lh(rep0 + 0);
info_out->attributes = c32_get_dword_lh(rep0 + 4);
info_out->flags = c32_get_word_lh(rep0 + 8);
info_out->data_size = c32_get_dword_lh(rep0 + 10);
info_out->total_size = c32_get_dword_lh(rep0 + 14);
info_out->number_of_streams = c32_get_word_lh(rep0 + 18);
info_out->creation_time = c32_get_word_lh(rep0 + 20);
info_out->creation_date = c32_get_word_lh(rep0 + 22);
info_out->creator_id = c32_get_dword_hl(rep0 + 24);
info_out->modify_time = c32_get_word_lh(rep0 + 28);
info_out->modify_date = c32_get_word_lh(rep0 + 30);
info_out->modifier_id = c32_get_dword_hl(rep0 + 32);
info_out->last_access_date = c32_get_word_lh(rep0 + 36);
info_out->archive_time = c32_get_word_lh(rep0 + 38);
info_out->archive_date = c32_get_word_lh(rep0 + 40);
info_out->archiver_id = c32_get_dword_hl(rep0 + 42);
info_out->inherited_rights = c32_get_word_lh(rep0 + 46);
info_out->dir_ent_num = c32_get_dword_lh(rep0 + 48);
info_out->dos_dir_num = c32_get_dword_lh(rep0 + 52);
info_out->vol_number = c32_get_dword_lh(rep0 + 56);
info_out->ea_data_size = c32_get_dword_lh(rep0 + 60);
info_out->ea_key_count = c32_get_dword_lh(rep0 + 64);
info_out->ea_key_size = c32_get_dword_lh(rep0 + 68);
info_out->ns_creator = c32_get_dword_lh(rep0 + 72);
namelen = rep0[76];
if (namelen > 255)
namelen = 255;
info_out->name_len = (uint8)namelen;
memcpy(info_out->name, rep0 + 77, namelen);
info_out->name[namelen] = '\0';
return(0);
}
/*
* ncp87_07_modify_dos_info
*
* Purpose:
* Writes selected DOS namespace metadata fields for a file or directory.
*
* NCP:
* Function 0x57 / subfunction 0x07, Modify File or Subdirectory DOS
* Information.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_07_modify_dos_info(const char *name,
uint16 dir_handle,
uint32 change_mask,
C32_DOS_MODIFY_INFO *info,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 modbuf[80];
uint8 path[0x140];
uint8 rep0[0x20];
uint8 rep1[0x20];
uint8 rawout[32];
uint8 *p;
UI mod_len;
UI path_len;
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
int rc;
if (!name || !info)
return(1);
if (actual_out)
*actual_out = 0;
if (handle_lo_out)
*handle_lo_out = 0;
if (handle_hi_out)
*handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* NCP 87 subfunction 7: Modify DOS information.
*
* DOS info payload layout verified by FLAG and MARS' NCP22/25 handler:
* dword attributes
* word creation time
* word creation date
* dword creator id (HI-LOW)
* word modify time
* word modify date
* dword modifier id (HI-LOW)
* word last access date
* word archive time
* word archive date
* dword archiver id (HI-LOW)
* word inherited rights mask
* dword maximum space
*/
memset(modbuf, 0, sizeof(modbuf));
p = modbuf;
*p++ = 7; /* subfunction: modify DOS info */
*p++ = 0; /* namespace DOS */
*p++ = 0; /* reserved */
c32_put_word_lh(p, 0x0006); p += 2; /* SA_ALL */
c32_put_dword_lh(p, change_mask); p += 4;
c32_put_dword_lh(p, info->attributes); p += 4;
c32_put_word_lh(p, info->creation_time); p += 2;
c32_put_word_lh(p, info->creation_date); p += 2;
c32_put_dword_hl(p, info->creator_id); p += 4;
c32_put_word_lh(p, info->modify_time); p += 2;
c32_put_word_lh(p, info->modify_date); p += 2;
c32_put_dword_hl(p, info->modifier_id); p += 4;
c32_put_word_lh(p, info->last_access_date); p += 2;
c32_put_word_lh(p, info->archive_time); p += 2;
c32_put_word_lh(p, info->archive_date); p += 2;
c32_put_dword_hl(p, info->archiver_id); p += 4;
c32_put_word_lh(p, info->inherited_rights); p += 2;
c32_put_dword_lh(p, info->maximum_space); p += 4;
mod_len = (UI)(p - modbuf);
path_len = c32_build_handle_path(path, (uint8)dir_handle, 0, 0, 1,
name, NULL, NULL);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
modbuf, mod_len,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out)
*actual_out = actual_lo;
if (handle_lo_out)
*handle_lo_out = handle_lo;
if (handle_hi_out)
*handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
return(0);
}
/*
* ncp87_07_modify_dos_attributes
*
* Purpose:
* Convenience wrapper around ncp87_07_modify_dos_info() for changing only
* the DOS attributes field.
*
* NCP:
* Function 0x57 / subfunction 0x07 with DM_ATTRIBUTES.
*/
int ncp87_07_modify_dos_attributes(char *name,
uint16 dir_handle,
uint32 attrs,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
C32_DOS_MODIFY_INFO info;
memset(&info, 0, sizeof(info));
info.attributes = attrs;
/*
* NCP 87 modify DOS information uses DM_ATTRIBUTES (0x00000002).
* RIM_ATTRIBUTES (0x00000004) is the read/obtain mask; using it here
* leaves high attribute bits such as Delete/Rename Inhibit unchanged or
* cleared on some servers/clients.
*/
return(ncp87_07_modify_dos_info(name, dir_handle, 0x00000002UL,
&info, actual_out,
handle_lo_out, handle_hi_out));
}
/*
* ncp87_1d_get_effective_rights
*
* Purpose:
* Reads the effective rights mask for a path.
*
* NCP:
* Function 0x57 / subfunction 0x1d, Get Effective Rights.
*
* Requester path:
* Client32 Raw5 fragment request with an NWHandlePathStruct.
*/
int ncp87_1d_get_effective_rights(const char *path_name,
uint16 dir_handle,
uint16 *rights_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[0x140];
uint8 rep0[0x20];
uint8 rep1[0x20];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
uint16 rights0;
uint16 rights4;
UI path_len;
int rc;
if (!rights_out)
return(1);
*rights_out = 0;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* NCP 87 subfunction 29: Get effective rights.
*
* This mirrors ncpfs ncp_get_eff_directory_rights():
* byte 29
* byte source namespace
* byte target namespace
* word search attributes, little endian
* dword reserved, zero
* handle/path
*
* Reply is a little-endian word with NCP rights bits.
*/
memset(hdr, 0, sizeof(hdr));
hdr[0] = 29;
hdr[1] = 0; /* source namespace DOS */
hdr[2] = 0; /* target namespace DOS */
c32_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
c32_put_dword_lh(hdr + 5, 0L); /* reserved */
path_len = c32_build_handle_path_from_dos_path(path, (uint8)dir_handle,
0, 0, path_name);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
rights0 = c32_get_word_lh(rep0 + 0);
rights4 = c32_get_word_lh(rep0 + 4);
/*
* Most NCP replies start at REP0+0. The existing RIM_ATTRIBUTES helper
* found attributes at REP0+4 on Client32. Accept the +4 location only
* if +0 is empty, so restricted rights value 0 still works when +4 is
* also zero.
*/
if (rights0 == 0 && rights4 != 0)
rights0 = rights4;
*rights_out = rights0;
return(0);
}
/*
* ncp87_1d_get_effective_rights_by_dirent
*
* Purpose:
* Reads effective rights using a volume number and DOS directory number
* instead of a textual path.
*
* NCP:
* Function 0x57 / subfunction 0x1d, Get Effective Rights.
*
* Notes:
* NDIR uses this form after obtaining directory-entry metadata.
*/
int ncp87_1d_get_effective_rights_by_dirent(uint8 vol_number,
uint32 dos_dir_number,
uint16 *rights_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[16];
uint8 rep0[0x20];
uint8 rep1[0x20];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
uint16 rights0;
uint16 rights4;
int rc;
if (!rights_out)
return(1);
*rights_out = 0;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* SDK/ncpfs-style NCP87 subfunction 29:
* byte 0x1D
* byte source namespace DOS
* byte reserved/target namespace
* word SA_ALL
* dword 0
* byte volume number
* dword directory number
* byte dirstyle 1
* byte component count 0
*/
memset(hdr, 0, sizeof(hdr));
hdr[0] = 0x1d;
hdr[1] = 0;
hdr[2] = 0;
c32_put_word_lh(hdr + 3, 0x0006);
c32_put_dword_lh(hdr + 5, 0L);
memset(path, 0, sizeof(path));
path[0] = vol_number;
c32_put_dword_lh(path + 1, dos_dir_number);
path[5] = 1;
path[6] = 0;
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, 7,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
rights0 = (uint16)(rep0[0] | ((uint16)rep0[1] << 8));
rights4 = (uint16)(rep0[4] | ((uint16)rep0[5] << 8));
if (rights0 == 0 && rights4 != 0)
rights0 = rights4;
*rights_out = rights0;
return(0);
}
/*
* c32_build_ncp22_trustee_path
*
* Purpose:
* Builds the volume-qualified path format required by old NCP22 trustee
* calls from the DOS utility display/header path.
*
* Notes:
* This helper intentionally strips the server component and leaves a
* SYS:DIR\FILE style path for directory handle 0 requests.
*/
static int c32_build_ncp22_trustee_path(char *out, const char *path_name, int max)
{
char header[300];
char *p;
char *colon;
if (!out || max < 2)
return(-1);
out[0] = '\0';
/* Build the same server/volume display path used by the DOS tools, then
* strip the server component. NCP22/27 accepts a volume-qualified path
* such as SYS:DIR\FILE with directory handle 0, matching Novell GRANT's
* old trustee call as seen in the server trace. */
tool_header_path(header, (char *)(path_name ? path_name : "."), sizeof(header));
colon = strchr(header, ':');
if (!colon)
return(-1);
p = header;
while (*p && p < colon) {
if (*p == '\\' || *p == '/') {
p++;
break;
}
p++;
}
if (!*p || p >= colon)
p = header;
strmaxcpy(out, p, max - 1);
return(0);
}
/*
* ncp22_27_set_trustee_rights
*
* Purpose:
* Adds or updates a trustee assignment using the old directory services NCP
* fallback path.
*
* NCP:
* Function 0x16 / subfunction 0x27, Add Extended Trustee.
*
* Requester path:
* E200 Net_Call wrapper with a volume-qualified path.
*/
int ncp22_27_set_trustee_rights(const char *path_name,
uint16 dir_handle,
uint32 object_id,
uint16 rights)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 trustee_id[4];
uint8 trustee_rights[2];
uint8 pathlen;
uint8 path[255];
} req;
struct { uint16 len; } repl;
char ncppath[260];
int pathlen;
(void)dir_handle;
if (c32_build_ncp22_trustee_path(ncppath, path_name, sizeof(ncppath)))
return(30);
pathlen = strlen(ncppath);
if (pathlen > 255)
return(31);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
req.func = 0x27; /* NCP22/27 Add Ext Trustee */
req.dirhandle = 0; /* volume-qualified path follows */
c32_put_dword_hl(req.trustee_id, object_id);
c32_put_word_lh(req.trustee_rights, rights);
req.pathlen = (uint8)pathlen;
memcpy(req.path, ncppath, pathlen);
req.len = 9 + pathlen;
repl.len = 0;
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
/*
* ncp22_2b_delete_trustee_rights
*
* Purpose:
* Deletes a trustee assignment using the old directory services NCP
* fallback path.
*
* NCP:
* Function 0x16 / subfunction 0x2b, Delete Trustee.
*
* Requester path:
* E200 Net_Call wrapper with a volume-qualified path.
*/
int ncp22_2b_delete_trustee_rights(const char *path_name,
uint16 dir_handle,
uint32 object_id)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 trustee_id[4];
uint8 reserved;
uint8 pathlen;
uint8 path[255];
} req;
struct { uint16 len; } repl;
char ncppath[260];
int pathlen;
(void)dir_handle;
if (c32_build_ncp22_trustee_path(ncppath, path_name, sizeof(ncppath)))
return(30);
pathlen = strlen(ncppath);
if (pathlen > 255)
return(31);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
req.func = 0x2b; /* NCP22/43 Delete Trustee */
req.dirhandle = 0; /* volume-qualified path follows */
c32_put_dword_hl(req.trustee_id, object_id);
req.reserved = 0; /* NCP22/2B has a reserved byte before pathlen */
req.pathlen = (uint8)pathlen;
memcpy(req.path, ncppath, pathlen);
req.len = 8 + pathlen;
repl.len = 0;
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-neterrno);
return(0);
}
/*
* ncp87_0a_add_trustee_rights
*
* Purpose:
* Adds trustee rights using the namespace-aware NCP87 trustee API.
*
* NCP:
* Function 0x57 / subfunction 0x0a, Add Trustee Set.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_0a_add_trustee_rights(const char *path_name,
uint16 dir_handle,
uint32 object_id,
uint16 rights,
uint16 rights_mask,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 reqpath[0x180];
uint8 rep0[0x20];
uint8 rep1[0x20];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
UI path_struct_len;
UI reqpath_len;
uint8 *tp;
int rc;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
/*
* NCP 87 subfunction 10: Add trustee.
*
* This mirrors ncpfs ncp_ns_trustee_add():
* byte 10
* byte namespace DOS
* byte reserved
* word search attributes, little endian
* word rights mask, little endian
* word object count, little endian
* handle/path
* trustee array at request offset 16 + 307
*
* Client32 Raw5 has two request fragments, so the second fragment carries
* the handle/path and the padded trustee record.
*/
memset(hdr, 0, sizeof(hdr));
hdr[0] = 10;
hdr[1] = 0; /* DOS namespace */
hdr[2] = 0; /* reserved */
c32_put_word_lh(hdr + 3, 0x8006); /* SA_ALL: files/subdirs + system + hidden */
c32_put_word_lh(hdr + 5, rights_mask);
c32_put_word_lh(hdr + 7, 1); /* one trustee */
memset(reqpath, 0, sizeof(reqpath));
path_struct_len = c32_build_handle_path_from_dos_path(reqpath,
(uint8)dir_handle,
0, 0,
path_name);
/*
* ncpfs seeks to absolute packet offset 16+307 before writing the
* trustee list. The NCP request header is 7 bytes, so inside the NCP
* payload the trustee list begins at:
*
* (16 + 307) - 7 = 316
*
* Our first Raw5 request fragment is the 9-byte subfunction header,
* so the trustee list begins in the second fragment at:
*
* 316 - 9 = 307
*/
if (path_struct_len > 307)
return(2);
tp = reqpath + 307;
c32_put_dword_hl(tp, object_id); tp += 4;
c32_put_word_lh(tp, rights); tp += 2;
reqpath_len = (UI)(tp - reqpath);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
reqpath, reqpath_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
return(0);
}
/*
* ncp87_05_find_trustee_rights
*
* Purpose:
* Scans trustee entries for a path until the requested bindery object id is
* found.
*
* NCP:
* Function 0x57 / subfunction 0x05, Scan Trustee Set.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_05_find_trustee_rights(const char *path_name,
uint16 dir_handle,
uint32 object_id,
uint16 *rights_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 path[0x140];
uint8 rep0[0x120];
uint8 rep1[0x120];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
uint32 seq = 0;
UI path_len;
int rc;
if (rights_out) *rights_out = 0;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
if (!rights_out)
return(1);
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
for (;;) {
uint16 count;
uint16 i;
uint8 *tp;
uint32 next_seq;
memset(hdr, 0, sizeof(hdr));
hdr[0] = 5; /* NCP87 subfunction 5: scan trustees */
hdr[1] = 0; /* DOS namespace */
hdr[2] = 0; /* reserved */
c32_put_word_lh(hdr + 3, 0x8006); /* SA_ALL: files/subdirs + system + hidden */
c32_put_dword_lh(hdr + 5, seq); /* search sequence, starts at zero */
path_len = c32_build_handle_path_from_dos_path(path,
(uint8)dir_handle,
0, 0,
path_name);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(0xff); /* Client32 returns an error when no trustees are present. */
next_seq = c32_get_dword_lh(rep0 + 0);
count = c32_get_word_lh(rep0 + 4);
if (count > 20)
count = 20;
tp = rep0 + 6;
for (i = 0; i < count; i++) {
uint32 tid = c32_get_dword_hl(tp);
uint16 trights = c32_get_word_lh(tp + 4);
if (tid == object_id || c32_get_dword_lh(tp) == object_id) {
*rights_out = trights;
return(0);
}
tp += 6;
}
if (next_seq == 0xffffffffUL || next_seq == seq)
break;
seq = next_seq;
}
return(0xff); /* no trustee found / no more entries */
}
/*
* ncp87_0b_delete_trustee_rights
*
* Purpose:
* Deletes trustee rights using the namespace-aware NCP87 trustee API.
*
* NCP:
* Function 0x57 / subfunction 0x0b, Delete Trustee Set.
*
* Requester path:
* Client32 Raw5 fragment request.
*/
int ncp87_0b_delete_trustee_rights(const char *path_name,
uint16 dir_handle,
uint32 object_id,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out)
{
uint16 handle_lo, handle_hi;
uint8 hdr[16];
uint8 reqpath[0x180];
uint8 rep0[0x20];
uint8 rep1[0x20];
uint8 rawout[32];
uint16 raw_ret_ax, raw_ret_dx;
uint16 actual_lo;
UI path_struct_len;
UI reqpath_len;
uint8 *tp;
int rc;
if (actual_out) *actual_out = 0;
if (handle_lo_out) *handle_lo_out = 0;
if (handle_hi_out) *handle_hi_out = 0;
rc = c32_get_ncp_handle(&handle_lo, &handle_hi);
if (rc)
return(10 + rc);
memset(hdr, 0, sizeof(hdr));
hdr[0] = 11; /* NCP87 subfunction 11: delete trustee */
hdr[1] = 0; /* DOS namespace */
hdr[2] = 0; /* reserved */
c32_put_word_lh(hdr + 3, 1); /* one trustee */
memset(reqpath, 0, sizeof(reqpath));
path_struct_len = c32_build_handle_path_from_dos_path(reqpath,
(uint8)dir_handle,
0, 0,
path_name);
if (path_struct_len > 307)
return(2);
tp = reqpath + 307;
c32_put_dword_hl(tp, object_id); tp += 4;
c32_put_word_lh(tp, 0); tp += 2;
reqpath_len = (UI)(tp - reqpath);
memset(rep0, 0, sizeof(rep0));
memset(rep1, 0, sizeof(rep1));
memset(rawout, 0, sizeof(rawout));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 5,
reqpath, reqpath_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_get_word_lh(rawout + 18);
if (actual_out) *actual_out = actual_lo;
if (handle_lo_out) *handle_lo_out = handle_lo;
if (handle_hi_out) *handle_hi_out = handle_hi;
if (raw_ret_ax != 0 || raw_ret_dx != 0)
return(20);
return(0);
}