Files
mars-dosutils/ndir.c
Mario Fetka 40e7ebf939 ncpcalls: rename remaining C32 transport symbols
Rename the remaining C32-prefixed transport helpers, probe helpers, types and
constants to protocol-oriented NCP names.

This removes implementation-oriented c32/C32 naming from the shared NCP API and
transport layer while keeping the current c32ncp.c/c32ncp.h file names until
the later ncpapi.c/ncpapi.h rename. The new names avoid conflicts by using
ncp_* helper names and NCP_* type/constant prefixes.

No behavior change.
2026-05-29 11:32:09 +02:00

1245 lines
35 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: NDIR utility for Novell-style directory listings and metadata display.
* Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines.
*/
/* ndir.c - first Novell NDIR-like directory listing utility */
#include "net.h"
#include "c32ncp.h"
#include <dos.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef S_IFDIR
#define S_IFDIR 0040000
#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 NDIR_ATTR_RENAME_INHIBIT 0x00020000UL
#define NDIR_ATTR_DELETE_INHIBIT 0x00040000UL
#define NDIR_MAX_SCAN_ENTRIES 160
#define NDIR_OPT_FILES_ONLY 0x0001
#define NDIR_OPT_DIRS_ONLY 0x0002
#define NDIR_OPT_RIGHTS 0x0004
#define NDIR_OPT_DATES 0x0008
#define NDIR_OPT_SUB 0x0010
#define NDIR_OPT_SHORT 0x0020
#define NDIR_OPT_FILTER_H 0x0040
#define NDIR_OPT_FILTER_RO 0x0080
#define NDIR_OPT_FILTER_SY 0x0100
#define NDIR_OPT_FILTER_A 0x0200
#define NDIR_OPT_FILTER_NOT 0x0400
#define NDIR_OPT_FILTER_ANY (NDIR_OPT_FILTER_H|NDIR_OPT_FILTER_RO|NDIR_OPT_FILTER_SY|NDIR_OPT_FILTER_A)
static int ndir_collect_subtotals = 0;
static int ndir_sub_file_count = 0;
static unsigned long ndir_sub_total_bytes = 0L;
static unsigned long ndir_sub_total_blocks = 0L;
/* NCP effective-rights bits returned by NCP87 subfunction 29. */
#define NDIR_NCP_RIGHT_READ 0x0001
#define NDIR_NCP_RIGHT_WRITE 0x0002
#define NDIR_NCP_RIGHT_CREATE 0x0008
#define NDIR_NCP_RIGHT_DELETE 0x0010
#define NDIR_NCP_RIGHT_OWNER 0x0020
#define NDIR_NCP_RIGHT_SEARCH 0x0040
#define NDIR_NCP_RIGHT_MODIFY 0x0080
#define NDIR_NCP_RIGHT_SUPER 0x0100
static void ndir_dos_date(unsigned date, char *out);
static void ndir_dos_datetime(unsigned date, unsigned time, char *out);
#define NDIR_OLD_RIGHT_S 0x01
#define NDIR_OLD_RIGHT_R 0x02
#define NDIR_OLD_RIGHT_W 0x04
#define NDIR_OLD_RIGHT_C 0x08
#define NDIR_OLD_RIGHT_E 0x10
#define NDIR_OLD_RIGHT_M 0x20
#define NDIR_OLD_RIGHT_F 0x40
#define NDIR_OLD_RIGHT_A 0x80
static void ndir_usage(void)
{
fprintf(stdout, "\n");
fprintf(stdout, "usage: NDIR [path] [/option...]\n");
fprintf(stdout, "path: [path] [filename] [,filename, ...] (up to 16 in chain)\n");
fprintf(stdout, "options: [format], [flag], [sortspec], [restriction], [FO] (files only),\n");
fprintf(stdout, " [DO] (directories only), [SUBdirectories], [Continuous], [HELP]\n");
fprintf(stdout, "\n");
fprintf(stdout, "format: DATES, RIGHTS, MACintosh, LONGnames\n");
fprintf(stdout, "\n");
fprintf(stdout, "flag: [NOT] RO, S, A, X, H, SY, T, I, P, RA, WA, CI, DI, RI\n");
fprintf(stdout, "\n");
fprintf(stdout, "sortspec: [REVerse] SORT [OWner], [SIze], [UPdate], [CReate],\n");
fprintf(stdout, " [ACcess], [ARchive], [UNsorted]\n");
fprintf(stdout, "\n");
fprintf(stdout, "restriction: OWner <operator> <name>\n");
fprintf(stdout, " SIze <operator> <number>\n");
fprintf(stdout, " UPdate <operator> <date>\n");
fprintf(stdout, " CReate <operator> <date>\n");
fprintf(stdout, " ACcess <operator> <date>\n");
fprintf(stdout, " ARchive <operator> <date>\n");
fprintf(stdout, "\n");
fprintf(stdout, " operator: [NOT] LEss than, GReater than,\n");
fprintf(stdout, " EQual to, BEFore, AFTer\n");
fprintf(stdout, "\n");
fprintf(stdout, "To search filenames equivalent to any of the capitalized KEYWORD options\n");
fprintf(stdout, "shown above, the filename must be preceded by a drive letter or path.\n");
}
static int ndir_is_help(char *s)
{
if (!s) return(0);
return(tool_is_help_arg(s) || tool_strsame(s, "/HELP") ||
tool_strsame(s, "-HELP") || tool_strsame(s, "HELP"));
}
static int ndir_is_files_only(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/FO") || tool_strsame(s, "-FO") ||
tool_strsame(s, "/FILESONLY") || tool_strsame(s, "-FILESONLY") ||
tool_strsame(s, "FO") || tool_strsame(s, "FILESONLY"));
}
static int ndir_is_dirs_only(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/DO") || tool_strsame(s, "-DO") ||
tool_strsame(s, "/DIRSONLY") || tool_strsame(s, "-DIRSONLY") ||
tool_strsame(s, "/DIRECTORIES") || tool_strsame(s, "-DIRECTORIES") ||
tool_strsame(s, "DO") || tool_strsame(s, "DIRSONLY") ||
tool_strsame(s, "DIRECTORIES"));
}
static int ndir_filter_option(char *s, int *flag)
{
if (!s || !flag) return(0);
if (tool_strsame(s, "/H") || tool_strsame(s, "-H") || tool_strsame(s, "H")) {
*flag = NDIR_OPT_FILTER_H; return(1);
}
if (tool_strsame(s, "/RO") || tool_strsame(s, "-RO") || tool_strsame(s, "RO")) {
*flag = NDIR_OPT_FILTER_RO; return(1);
}
if (tool_strsame(s, "/SY") || tool_strsame(s, "-SY") || tool_strsame(s, "SY") ||
tool_strsame(s, "/SYSTEM") || tool_strsame(s, "-SYSTEM") || tool_strsame(s, "SYSTEM")) {
*flag = NDIR_OPT_FILTER_SY; return(1);
}
if (tool_strsame(s, "/A") || tool_strsame(s, "-A") || tool_strsame(s, "A")) {
*flag = NDIR_OPT_FILTER_A; return(1);
}
return(0);
}
static int ndir_is_not(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/NOT") || tool_strsame(s, "-NOT") || tool_strsame(s, "NOT"));
}
static int ndir_is_continuous(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/CONTINUOUS") || tool_strsame(s, "-CONTINUOUS") ||
tool_strsame(s, "/CONTINUE") || tool_strsame(s, "-CONTINUE") ||
tool_strsame(s, "/C") || tool_strsame(s, "-C") ||
tool_strsame(s, "CONTINUOUS") || tool_strsame(s, "CONTINUE"));
}
static int ndir_is_rights(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/RIGHTS") || tool_strsame(s, "-RIGHTS") ||
tool_strsame(s, "RIGHTS"));
}
static int ndir_is_dates(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/DATES") || tool_strsame(s, "-DATES") ||
tool_strsame(s, "DATES"));
}
static int ndir_is_subdirs(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/SUB") || tool_strsame(s, "-SUB") ||
tool_strsame(s, "/SUBDIRECTORIES") || tool_strsame(s, "-SUBDIRECTORIES") ||
tool_strsame(s, "SUB") || tool_strsame(s, "SUBDIRECTORIES"));
}
static int ndir_is_short(char *s)
{
if (!s) return(0);
return(tool_strsame(s, "/SHORT") || tool_strsame(s, "-SHORT") ||
tool_strsame(s, "/BRIEF") || tool_strsame(s, "-BRIEF") ||
tool_strsame(s, "SHORT") || tool_strsame(s, "BRIEF"));
}
static int ndir_is_accepted_stub(char *s)
{
if (!s) return(0);
/*
* Keep this version tolerant of common NDIR format/options so users
* can compare command lines while unimplemented formats remain harmless.
*/
return(0);
}
static int ndir_path_is_dir(char *path)
{
struct stat st;
if (!path || !*path || tool_is_current_path(path))
return(1);
if (stat(path, &st) == 0) {
if (st.st_mode & S_IFDIR)
return(1);
}
return(0);
}
static void ndir_split_spec(char *spec, char *dir, char *pat)
{
if (!spec || !*spec || tool_is_current_path(spec)) {
strmaxcpy(dir, ".", 259);
strmaxcpy(pat, "*.*", 259);
return;
}
if (!tool_has_wildcards(spec) && ndir_path_is_dir(spec)) {
strmaxcpy(dir, spec, 259);
strmaxcpy(pat, "*.*", 259);
return;
}
tool_parent_pattern(dir, pat, spec, 260, 260);
}
static void ndir_dos_date(unsigned date, char *out)
{
int year;
int month;
int day;
year = ((date >> 9) & 0x7f) + 1980;
month = (date >> 5) & 0x0f;
day = date & 0x1f;
sprintf(out, "%d-%02d-%02d", month, day, year % 100);
}
static void ndir_dos_datetime_or_blank(unsigned date, unsigned time, char *out)
{
if (!date) {
strcpy(out, "0-00-00 0:00");
return;
}
ndir_dos_datetime(date, time, out);
}
static void ndir_dos_date_or_blank(unsigned date, char *out)
{
if (!date) {
strcpy(out, "0-00-00");
return;
}
ndir_dos_date(date, out);
}
static void ndir_dos_datetime(unsigned date, unsigned time, char *out)
{
int year;
int month;
int day;
int hour;
int minute;
char ap;
year = ((date >> 9) & 0x7f) + 1980;
month = (date >> 5) & 0x0f;
day = date & 0x1f;
hour = (time >> 11) & 0x1f;
minute = (time >> 5) & 0x3f;
ap = (hour >= 12) ? 'p' : 'a';
if (hour == 0)
hour = 12;
else if (hour > 12)
hour -= 12;
/*
* Novell NDIR keeps the date/time column at 15 chars:
* 5-28-26 10:00a
* 5-28-26 9:00a
*/
sprintf(out, "%d-%02d-%02d %2d:%02d%c",
month, day, year % 100, hour, minute, ap);
}
static unsigned long ndir_blocks_for_size(unsigned long size)
{
/*
* Novell NDIR reports allocated bytes/blocks. For this first DOS
* findfirst/findnext implementation, approximate with 4 KiB allocation
* units so small test files match the observed Novell output more closely.
*/
if (size == 0L)
return(0L);
return((size + 4095L) / 4096L);
}
static void ndir_flags(uint32 attr, char *out)
{
/* Novell NDIR default flag field: [Rw-A--Sy--------DR]. */
out[0] = '[';
out[1] = 'R';
out[2] = (attr & _A_RDONLY) ? 'o' : 'w';
out[3] = '-';
out[4] = (attr & _A_ARCH) ? 'A' : '-';
out[5] = '-';
out[6] = (attr & _A_HIDDEN) ? 'H' : '-';
if (attr & _A_SYSTEM) {
out[7] = 'S';
out[8] = 'y';
} else {
out[7] = '-';
out[8] = '-';
}
memset(out + 9, '-', 8);
out[17] = (attr & NDIR_ATTR_DELETE_INHIBIT) ? 'D' : '-';
out[18] = (attr & NDIR_ATTR_RENAME_INHIBIT) ? 'R' : '-';
out[19] = ']';
out[20] = '\0';
}
typedef struct ndir_find_entry {
struct find_t ff;
uint32 attrs;
uint32 owner_id;
uint16 create_date;
uint16 create_time;
uint16 modify_date;
uint16 modify_time;
uint16 archive_date;
uint16 archive_time;
uint16 access_date;
uint16 inherited_rights;
int have_info;
} NDIR_FIND_ENTRY;
static void ndir_split_name_ext(char *src, char *name, char *ext)
{
char tmp[20];
char *dot;
strmaxcpy(tmp, src ? src : "", sizeof(tmp) - 1);
dot = strrchr(tmp, '.');
if (dot) {
*dot++ = '\0';
strmaxcpy(ext, dot, 3);
} else {
ext[0] = '\0';
}
strmaxcpy(name, tmp, 13);
}
static int ndir_name_cmp(char *a, char *b)
{
char aa[20];
char bb[20];
tool_upcopy(aa, a, sizeof(aa));
tool_upcopy(bb, b, sizeof(bb));
return(strcmp(aa, bb));
}
static void ndir_sort_entries(NDIR_FIND_ENTRY *list, int count)
{
int i, j;
for (i = 0; i < count; i++) {
for (j = i + 1; j < count; j++) {
if (ndir_name_cmp(list[i].ff.name, list[j].ff.name) > 0) {
NDIR_FIND_ENTRY tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
}
}
}
static void ndir_format_owner(uint32 objid, char *out, int max)
{
uint16 objtyp;
uint8 objname[50];
if (objid && !ncp17_36_get_bindery_object_name(objid, objname, &objtyp)) {
strmaxcpy(out, (char *)objname, max - 1);
return;
}
/* All files created by the test setup are owned by SUPERVISOR. */
strmaxcpy(out, "SUPERVISOR", max - 1);
}
static void ndir_format_number(unsigned long value, char *out)
{
char tmp[20];
int len;
int first;
int pos = 0;
int i;
sprintf(tmp, "%lu", value);
len = strlen(tmp);
first = len % 3;
if (!first) first = 3;
for (i = 0; i < len; i++) {
if (i && ((i - first) % 3) == 0)
out[pos++] = ',';
out[pos++] = tmp[i];
}
out[pos] = '\0';
}
static int ndir_attr_filter_match(uint32 attr, int options)
{
int have = 0;
int match = 1;
if (options & NDIR_OPT_FILTER_H) { have = 1; match = match && ((attr & _A_HIDDEN) != 0); }
if (options & NDIR_OPT_FILTER_RO) { have = 1; match = match && ((attr & _A_RDONLY) != 0); }
if (options & NDIR_OPT_FILTER_SY) { have = 1; match = match && ((attr & _A_SYSTEM) != 0); }
if (options & NDIR_OPT_FILTER_A) { have = 1; match = match && ((attr & _A_ARCH) != 0); }
if (!have) return(1);
if (options & NDIR_OPT_FILTER_NOT)
return(!match);
return(match);
}
static void ndir_parent_path(char *dst, char *src, int max)
{
char tmp[260];
char *p;
tool_upcopy(tmp, src, sizeof(tmp));
p = strrchr(tmp, '\\');
if (!p) p = strrchr(tmp, ':');
if (p) {
if (*p == ':') {
p[1] = '\0';
} else {
*p = '\0';
}
strmaxcpy(dst, tmp, max - 1);
} else {
dst[0] = '\0';
}
}
static void ndir_old_rights_string(uint8 old_rights, char *out)
{
out[0] = (old_rights & NDIR_OLD_RIGHT_S) ? 'S' : '-';
out[1] = (old_rights & NDIR_OLD_RIGHT_R) ? 'R' : '-';
out[2] = (old_rights & NDIR_OLD_RIGHT_W) ? 'W' : '-';
out[3] = (old_rights & NDIR_OLD_RIGHT_C) ? 'C' : '-';
out[4] = (old_rights & NDIR_OLD_RIGHT_E) ? 'E' : '-';
out[5] = (old_rights & NDIR_OLD_RIGHT_M) ? 'M' : '-';
out[6] = (old_rights & NDIR_OLD_RIGHT_F) ? 'F' : '-';
out[7] = (old_rights & NDIR_OLD_RIGHT_A) ? 'A' : '-';
out[8] = '\0';
}
static int ndir_ncp22_scan_entry(char *name, int want_dir, uint32 *attrs)
{
struct {
uint16 len;
uint8 func;
uint8 dirhandle;
uint8 search_attributes;
uint8 searchsequence[4];
uint8 namlen;
uint8 name[12];
} req;
struct {
uint16 len;
uint8 data[128];
} repl;
uint8 connid = 0;
uint8 dhandle = 0;
uint8 namlen = 0;
if (tool_current_dhandle(&connid, &dhandle))
return(-1);
memset(&req, 0, sizeof(req));
memset(&repl, 0, sizeof(repl));
if (tool_copy_ncp22_name(req.name, name, &namlen))
return(-1);
req.func = 0x1e; /* NCP22/30 Scan Directory */
req.dirhandle = dhandle;
req.search_attributes = want_dir ? 0x16 : 0x06;
/* FLAG uses 0x06 for file scans; FLAGDIR uses 0x16 for directories. */
U32_TO_BE32(0xffffffffUL, req.searchsequence);
req.namlen = namlen;
req.len = (uint16)(1 + 1 + 1 + 4 + 1 + namlen);
repl.len = sizeof(repl.data);
neterrno = Net_Call(0xE200, &req, &repl);
if (neterrno)
return(-1);
/*
* NCP22/30 returns the old directory-scan structure. The first fields are:
* dword searchsequence
* dword directory entry number
* dword attributes
* This is the same legacy scan path used by Novell NDIR/FLAGDIR before the
* Client32 namespace calls are used for richer metadata.
*/
if (attrs)
*attrs = tool_get_dword_lh(repl.data + 8);
return(0);
}
static int ndir_get_ncp_info(char *path, NCP_NDIR_INFO *info)
{
uint8 connid = 0;
uint8 dhandle = 0;
if (!info)
return(1);
memset(info, 0, sizeof(*info));
if (tool_current_dhandle(&connid, &dhandle))
return(1);
return(ncp87_06_obtain_ndir_info(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
info,
NULL, NULL, NULL));
}
static void ndir_inherited_rights(char *path, char *out)
{
NCP_NDIR_INFO info;
strcpy(out, "--------");
if (!ndir_get_ncp_info(path, &info))
ndir_old_rights_string((uint8)info.inherited_rights, out);
}
static void ndir_rights_string(uint16 ncp_rights, char *out)
{
out[0] = (ncp_rights & NDIR_NCP_RIGHT_SUPER) ? 'S' : '-';
out[1] = (ncp_rights & NDIR_NCP_RIGHT_READ) ? 'R' : '-';
out[2] = (ncp_rights & NDIR_NCP_RIGHT_WRITE) ? 'W' : '-';
out[3] = (ncp_rights & NDIR_NCP_RIGHT_CREATE) ? 'C' : '-';
out[4] = (ncp_rights & NDIR_NCP_RIGHT_DELETE) ? 'E' : '-';
out[5] = (ncp_rights & NDIR_NCP_RIGHT_MODIFY) ? 'M' : '-';
out[6] = (ncp_rights & NDIR_NCP_RIGHT_SEARCH) ? 'F' : '-';
out[7] = (ncp_rights & NDIR_NCP_RIGHT_OWNER) ? 'A' : '-';
out[8] = '\0';
}
static void ndir_effective_rights(char *path, char *out)
{
uint8 connid = 0;
uint8 dhandle = 0;
uint8 eff_old = 0;
uint16 ncp_rights = 0;
char usepath[260];
int newhandle;
strcpy(out, "--------");
if (tool_current_dhandle(&connid, &dhandle))
return;
/*
* Prefer Client32 NCP87 effective-rights. If that fails for a listed
* entry, fall back to the older directory-handle effective-rights path
* that RIGHTS also uses.
*/
if (!ncp87_1d_get_effective_rights(tool_is_current_path(path) ? "" : path,
(uint16)dhandle,
&ncp_rights,
NULL, NULL, NULL)) {
ndir_rights_string(ncp_rights, out);
return;
}
ndir_parent_path(usepath, path, sizeof(usepath));
newhandle = alloc_temp_dir_handle(dhandle, usepath, 0, &eff_old);
if (newhandle >= 0) {
dealloc_dir_handle(newhandle);
ndir_old_rights_string(eff_old, out);
return;
}
if (usepath[0]) {
int subdir = 1;
int r = ncp16_02_get_directory_entry(dhandle, (uint8 *)usepath, &subdir,
NULL, NULL, NULL);
if (r >= 0)
ndir_old_rights_string((uint8)r, out);
} else {
ndir_old_rights_string(0xff, out);
}
}
static void ndir_print_file(char *dir, NDIR_FIND_ENTRY *ent, int options,
int *line_count, int *continuous)
{
char dt[24];
char d[12];
char fl[24];
char path[260];
char eff[10];
char inh[10];
char arch[24];
char acc[12];
char crea[24];
char owner[50];
char n[16];
char e[6];
tool_join_path(path, dir, ent->ff.name, sizeof(path));
if (ent->have_info)
ndir_dos_datetime(ent->modify_date, ent->modify_time, dt);
else
ndir_dos_datetime(ent->ff.wr_date, ent->ff.wr_time, dt);
ndir_dos_date(ent->ff.wr_date, d);
ndir_flags(ent->attrs, fl);
ndir_split_name_ext(ent->ff.name, n, e);
ndir_format_owner(ent->owner_id, owner, sizeof(owner));
if (options & NDIR_OPT_RIGHTS) {
ndir_effective_rights(path, eff);
ndir_inherited_rights(path, inh);
fprintf(stdout, "%-14.14s%-3.3s %-20.20s [%8.8s] [%8.8s] %-13.13s\n",
n, e, fl, inh, eff, owner);
} else if (options & NDIR_OPT_DATES) {
char archive_mark;
if (ent->have_info) {
ndir_dos_datetime_or_blank(ent->archive_date, ent->archive_time, arch);
ndir_dos_date_or_blank(ent->access_date, acc);
ndir_dos_datetime_or_blank(ent->create_date, ent->create_time, crea);
} else {
strcpy(arch, "0-00-00 0:00");
strmaxcpy(acc, d, sizeof(acc) - 1);
strmaxcpy(crea, dt, sizeof(crea) - 1);
}
archive_mark = (ent->attrs & _A_ARCH) ? 'A' : '-';
fprintf(stdout, "%-14.14s%-3.3s %s %s %c %s %s \n",
n, e, dt, arch, archive_mark, acc, crea);
} else {
fprintf(stdout, "%-14.14s%-3.3s%14lu %-15.15s%-20.20s %-9.9s\n",
n, e, (unsigned long)ent->ff.size, dt, fl, owner);
}
tool_page_line(line_count, continuous);
}
static void ndir_print_dir(char *dir, NDIR_FIND_ENTRY *ent, int options,
int *line_count, int *continuous)
{
char dt[24];
char path[260];
char eff[10];
char inh[10];
char owner[50];
NCP_NDIR_INFO info;
tool_join_path(path, dir, ent->ff.name, sizeof(path));
if (!ndir_get_ncp_info(path, &info)) {
ndir_dos_datetime(info.creation_date, info.creation_time, dt);
ndir_format_owner(info.creator_id, owner, sizeof(owner));
} else {
ndir_dos_datetime(ent->ff.wr_date, ent->ff.wr_time, dt);
strcpy(owner, "SUPERVISOR");
}
ndir_effective_rights(path, eff);
ndir_inherited_rights(path, inh);
fprintf(stdout, "%-18.18s[%8.8s] [%8.8s] %-14.14s%s \n",
ent->ff.name, inh, eff, owner, dt);
tool_page_line(line_count, continuous);
}
static void ndir_fill_entry_info(char *dir, NDIR_FIND_ENTRY *ent)
{
char path[260];
NCP_NDIR_INFO info;
int have_ncp22_attrs;
ent->attrs = (uint32)ent->ff.attrib;
/*
* Prefer the legacy NCP22 directory scan for the attribute word, matching
* the Novell NDIR/FLAG trace. The NCP22 scan is handle-relative and can
* fail when NDIR lists an explicit path without changing the current DOS
* directory handle, so remember success explicitly instead of treating a
* non-zero DOS attribute byte as a successful NCP attribute word.
*/
have_ncp22_attrs = !ndir_ncp22_scan_entry(ent->ff.name,
(ent->ff.attrib & _A_SUBDIR) != 0,
&ent->attrs);
ent->owner_id = 1L;
ent->create_date = ent->ff.wr_date;
ent->create_time = ent->ff.wr_time;
ent->modify_date = ent->ff.wr_date;
ent->modify_time = ent->ff.wr_time;
ent->archive_date = 0;
ent->archive_time = 0;
ent->access_date = ent->ff.wr_date;
ent->inherited_rights = 0xff;
ent->have_info = 0;
tool_join_path(path, dir, ent->ff.name, sizeof(path));
if (!ndir_get_ncp_info(path, &info)) {
/* Keep attributes obtained through the legacy NCP22 scan when available. */
if (!have_ncp22_attrs)
ent->attrs = info.attributes;
ent->owner_id = info.creator_id;
ent->create_date = info.creation_date;
ent->create_time = info.creation_time;
ent->modify_date = info.modify_date;
ent->modify_time = info.modify_time;
ent->archive_date = info.archive_date;
ent->archive_time = info.archive_time;
ent->access_date = info.last_access_date;
ent->inherited_rights = info.inherited_rights;
ent->have_info = 1;
}
}
static int ndir_collect_entries(char *dir, char *search, int want_dirs,
int options, NDIR_FIND_ENTRY *list,
int max_entries)
{
struct find_t ff;
int rc;
int count = 0;
rc = _dos_findfirst(search, _A_NORMAL | _A_RDONLY | _A_HIDDEN |
_A_SYSTEM | _A_ARCH | _A_SUBDIR, &ff);
while (rc == 0 && count < max_entries) {
if (!!(ff.attrib & _A_SUBDIR) == !!want_dirs) {
if (!want_dirs || !tool_is_dot_dir(ff.name)) {
list[count].ff = ff;
ndir_fill_entry_info(dir, &list[count]);
if (want_dirs) {
/* Novell NDIR attribute filters apply to files only.
* It still prints the directories header, but no directory rows.
*/
if (!(options & NDIR_OPT_FILTER_ANY))
count++;
} else if (ndir_attr_filter_match(list[count].attrs, options)) {
count++;
}
}
}
rc = _dos_findnext(&ff);
}
ndir_sort_entries(list, count);
return(count);
}
static int ndir_scan_files(char *dir, char *search, int options,
int *line_count, int *continuous,
int *file_count, unsigned long *total_bytes,
unsigned long *total_blocks)
{
NDIR_FIND_ENTRY list[NDIR_MAX_SCAN_ENTRIES];
int count;
int i;
count = ndir_collect_entries(dir, search, 0, options, list,
NDIR_MAX_SCAN_ENTRIES);
for (i = 0; i < count; i++) {
ndir_print_file(dir, &list[i], options, line_count, continuous);
(*file_count)++;
*total_bytes += (unsigned long)list[i].ff.size;
*total_blocks += ndir_blocks_for_size((unsigned long)list[i].ff.size);
if (ndir_collect_subtotals) {
ndir_sub_file_count++;
ndir_sub_total_bytes += (unsigned long)list[i].ff.size;
ndir_sub_total_blocks += ndir_blocks_for_size((unsigned long)list[i].ff.size);
}
}
return(count > 0);
}
static int ndir_scan_dirs(char *dir, char *search, int options,
int *line_count, int *continuous,
int *dir_count)
{
NDIR_FIND_ENTRY list[NDIR_MAX_SCAN_ENTRIES];
int count;
int i;
count = ndir_collect_entries(dir, search, 1, options, list,
NDIR_MAX_SCAN_ENTRIES);
for (i = 0; i < count; i++) {
ndir_print_dir(dir, &list[i], options, line_count, continuous);
(*dir_count)++;
}
return(count > 0);
}
static int ndir_has_matching_entries(char *dir, char *search, int options)
{
NDIR_FIND_ENTRY list[NDIR_MAX_SCAN_ENTRIES];
if (!(options & NDIR_OPT_DIRS_ONLY)) {
if (ndir_collect_entries(dir, search, 0, options, list, NDIR_MAX_SCAN_ENTRIES) > 0)
return(1);
}
if (!(options & NDIR_OPT_FILES_ONLY)) {
if (ndir_collect_entries(dir, search, 1, options, list, NDIR_MAX_SCAN_ENTRIES) > 0)
return(1);
}
return(0);
}
static int ndir_has_matching_dirs(char *dir, char *search, int options)
{
NDIR_FIND_ENTRY list[NDIR_MAX_SCAN_ENTRIES];
if (options & NDIR_OPT_FILES_ONLY)
return(0);
return(ndir_collect_entries(dir, search, 1, options, list, NDIR_MAX_SCAN_ENTRIES) > 0);
}
static int ndir_has_matching_files(char *dir, char *search, int options)
{
NDIR_FIND_ENTRY list[NDIR_MAX_SCAN_ENTRIES];
if (options & NDIR_OPT_DIRS_ONLY)
return(0);
return(ndir_collect_entries(dir, search, 0, options, list, NDIR_MAX_SCAN_ENTRIES) > 0);
}
static int ndir_list_one(char *spec, int options, int *continuous)
{
char dir[260];
char pat[260];
char search[260];
char display[300];
int got = 0;
int files_shown = 0;
int dirs_shown = 0;
int dirs_available = 0;
int files_available = 0;
int dirs_header_needed = 0;
int any_available = 0;
int file_count = 0;
int dir_count = 0;
unsigned long total_bytes = 0L;
unsigned long total_blocks = 0L;
char num_bytes[24];
char num_blocks[24];
int line_count = 0;
ndir_split_spec(spec, dir, pat);
tool_join_path(search, dir, pat, sizeof(search));
any_available = ndir_has_matching_entries(dir, search, options);
files_available = ndir_has_matching_files(dir, search, options);
dirs_available = ndir_has_matching_dirs(dir, search, options);
dirs_header_needed = dirs_available;
if ((options & NDIR_OPT_FILTER_ANY) && !(options & (NDIR_OPT_DIRS_ONLY|NDIR_OPT_FILES_ONLY)))
dirs_header_needed = 1;
if (!any_available && !(options & NDIR_OPT_SHORT)) {
if (options & NDIR_OPT_DIRS_ONLY) {
fprintf(stdout, "No files of given specification found or directory is empty.\n");
} else {
fprintf(stdout, " 0 total bytes in 0 files\n");
fprintf(stdout, " 0 total bytes in 0 blocks\n");
fprintf(stdout, "\n");
}
return(1);
}
tool_header_path(display, dir, sizeof(display));
fprintf(stdout, "%s\n", display);
if (!(options & NDIR_OPT_SHORT))
fprintf(stdout, "\n");
tool_page_line(&line_count, continuous);
if (!(options & NDIR_OPT_DIRS_ONLY) && files_available) {
if (!(options & NDIR_OPT_SHORT)) {
if (options & NDIR_OPT_RIGHTS) {
fprintf(stdout, " Inherited Effective\n");
fprintf(stdout, "Files: Flags Rights Rights Owner\n");
fprintf(stdout, "----------------- -------------------- ----------------------- ---------\n");
} else if (options & NDIR_OPT_DATES) {
fprintf(stdout, "Files: Last Updated Last Archived * Accessed Created/Copied\n");
fprintf(stdout, "----------------- --------------- ----------------- -------- ----------------\n");
} else {
fprintf(stdout, "Files: Size Last Updated Flags Owner\n");
fprintf(stdout, "----------------- ------------- --------------- -------------------- ---------\n");
}
tool_page_line(&line_count, continuous);
tool_page_line(&line_count, continuous);
}
files_shown = ndir_scan_files(dir, search, options, &line_count,
continuous, &file_count,
&total_bytes, &total_blocks);
if (files_shown) {
got = 1;
if ((options & NDIR_OPT_DATES) && !(options & NDIR_OPT_SHORT))
fprintf(stdout, "* Files marked A are flagged for subsequent archiving.\n");
}
}
if (!(options & NDIR_OPT_FILES_ONLY) && dirs_header_needed) {
if (!(options & NDIR_OPT_SHORT)) {
if (options & NDIR_OPT_DIRS_ONLY)
fprintf(stdout, "\n");
else if (files_shown)
fprintf(stdout, "\n");
else if (!files_available)
fprintf(stdout, "\n");
fprintf(stdout, " Inherited Effective\n");
fprintf(stdout, "Directories: Rights Rights Owner Created/Copied\n");
fprintf(stdout, "----------------- ----------------------- ------------ ----------------\n");
tool_page_line(&line_count, continuous);
tool_page_line(&line_count, continuous);
}
if (dirs_available) {
dirs_shown = ndir_scan_dirs(dir, search, options, &line_count,
continuous, &dir_count);
if (dirs_shown)
got = 1;
}
}
if (!got && (options & NDIR_OPT_DIRS_ONLY) && !(options & NDIR_OPT_SHORT))
fprintf(stdout, "No files of given specification found or directory is empty.\n");
if (options & NDIR_OPT_SHORT) {
if (file_count)
fprintf(stdout, " %lu bytes, %d files, %lu blocks\n",
total_bytes, file_count, total_blocks);
} else if (options & NDIR_OPT_DIRS_ONLY) {
fprintf(stdout, "\n");
fprintf(stdout, " 0 total bytes in 0 files\n");
fprintf(stdout, " 0 total bytes in 0 blocks\n");
fprintf(stdout, "\n");
} else {
ndir_format_number(total_bytes, num_bytes);
ndir_format_number(total_blocks * 4096L, num_blocks);
if (file_count == 0 && ndir_collect_subtotals && dirs_shown) {
/* Novell does not print an extra empty totals block for recursive
* directory-only intermediate sections. The following subdirectory
* header provides the visible separation. */
} else {
fprintf(stdout, "\n");
if (file_count == 0) {
fprintf(stdout, "%13s total bytes in %5d files\n", num_bytes, file_count);
fprintf(stdout, "%13s total bytes in %5lu blocks\n", num_blocks, total_blocks);
fprintf(stdout, "\n");
} else {
fprintf(stdout, "%13s bytes in %4d files\n", num_bytes, file_count);
fprintf(stdout, "%13s bytes in %4lu blocks\n", num_blocks, total_blocks);
}
}
}
return(got ? 0 : 1);
}
static int ndir_spec_has_output(char *spec, int options)
{
struct find_t ff;
char dir[260];
char pat[260];
char search[260];
int rc;
ndir_split_spec(spec, dir, pat);
tool_join_path(search, dir, pat, 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) {
if (!tool_is_dot_dir(ff.name) && !(options & NDIR_OPT_FILES_ONLY))
return(1);
} else {
if (!(options & NDIR_OPT_DIRS_ONLY))
return(1);
}
rc = _dos_findnext(&ff);
}
return(0);
}
static int ndir_list_subdirs(char *dir, char *pattern, int options,
int *continuous)
{
struct find_t ff;
char search[260];
char subdir[260];
char subspec[260];
int rc;
int result = 0;
int had_any = 0;
tool_join_path(search, dir, "*.*", 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)) {
tool_join_path(subdir, dir, ff.name, sizeof(subdir));
tool_join_path(subspec, subdir, pattern, sizeof(subspec));
if (ndir_spec_has_output(subspec, options & ~NDIR_OPT_SUB)) {
fprintf(stdout, "\n");
if (ndir_list_one(subspec, options & ~NDIR_OPT_SUB, continuous))
result = 1;
}
had_any = 1;
if (ndir_list_subdirs(subdir, pattern, options, continuous))
result = 1;
}
rc = _dos_findnext(&ff);
}
if (!had_any)
return(result);
return(result);
}
static int ndir_list(char *spec, int options, int *continuous)
{
char dir[260];
char pat[260];
int result;
char num_bytes[24];
char num_blocks[24];
if (options & NDIR_OPT_SUB) {
ndir_collect_subtotals = 1;
ndir_sub_file_count = 0;
ndir_sub_total_bytes = 0L;
ndir_sub_total_blocks = 0L;
}
result = ndir_list_one(spec, options & ~NDIR_OPT_SUB, continuous);
if (!(options & NDIR_OPT_SUB)) {
ndir_collect_subtotals = 0;
return(result);
}
ndir_split_spec(spec, dir, pat);
if (!pat[0])
strmaxcpy(pat, "*.*", sizeof(pat) - 1);
if (ndir_list_subdirs(dir, pat, options, continuous))
result = 1;
if (!(options & NDIR_OPT_SHORT) && !(options & NDIR_OPT_DIRS_ONLY)) {
ndir_format_number(ndir_sub_total_bytes, num_bytes);
ndir_format_number(ndir_sub_total_blocks * 4096L, num_blocks);
fprintf(stdout, "\n");
fprintf(stdout, "%13s total bytes in %5d files\n", num_bytes, ndir_sub_file_count);
fprintf(stdout, "%13s total bytes in %5lu blocks\n", num_blocks, ndir_sub_total_blocks);
fprintf(stdout, "\n");
}
ndir_collect_subtotals = 0;
return(result);
}
int func_ndir(int argc, char *argv[], int mode)
{
char *path = ".";
int have_path = 0;
int options = 0;
int continuous = 0;
int i;
int filterflag;
int next_not = 0;
(void)mode;
if (argc > 1 && ndir_is_help(argv[1])) {
ndir_usage();
return(0);
}
for (i = 1; i < argc; i++) {
if (ndir_is_not(argv[i])) {
options |= NDIR_OPT_FILTER_NOT;
next_not = 1;
continue;
}
filterflag = 0;
if (ndir_filter_option(argv[i], &filterflag)) {
options |= filterflag;
next_not = 0;
continue;
}
if (next_not) {
fprintf(stdout, "Type \"ndir /help\" on the command line for usage information.\n");
return(1);
}
if (ndir_is_help(argv[i])) {
ndir_usage();
return(0);
}
if (ndir_is_files_only(argv[i])) {
options |= NDIR_OPT_FILES_ONLY;
continue;
}
if (ndir_is_dirs_only(argv[i])) {
options |= NDIR_OPT_DIRS_ONLY;
continue;
}
if (ndir_is_continuous(argv[i])) {
continuous = 1;
continue;
}
if (ndir_is_rights(argv[i])) {
options |= NDIR_OPT_RIGHTS;
continue;
}
if (ndir_is_dates(argv[i])) {
options |= NDIR_OPT_DATES;
continue;
}
if (ndir_is_subdirs(argv[i])) {
options |= NDIR_OPT_SUB;
continue;
}
if (ndir_is_short(argv[i])) {
options |= NDIR_OPT_SHORT;
continue;
}
if (ndir_is_accepted_stub(argv[i])) {
continue;
}
if (tool_is_option(argv[i])) {
fprintf(stdout, "Type \"ndir /help\" on the command line for usage information.\n");
return(1);
}
if (have_path) {
fprintf(stdout, "Too many filenames in chain.\n");
return(1);
}
path = argv[i];
have_path = 1;
}
if (next_not) {
fprintf(stdout, "Type \"ndir /help\" on the command line for usage information.\n");
return(1);
}
if ((options & NDIR_OPT_FILES_ONLY) && (options & NDIR_OPT_DIRS_ONLY)) {
fprintf(stdout, "No files of given specification found or directory is empty.\n");
return(1);
}
return(ndir_list(path, options, &continuous));
}