/* * mars-nwe-dosutils - NetWare/DOS utility tools. * * Copyright (C) 2026 Mario Fetka * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* * Purpose: CREATOR utility for showing and changing NetWare file creator/owner metadata. * Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines. */ /* creator.c - friendly creator/modifier/archiver metadata test tool * * Uses the same NCP 22/25 Set Directory/File Information path as Novell * FILER for the special fileinfo/archive metadata fields. This is intended * for testing mars_nwe xattr-backed metadata without using the FILER GUI. */ #include "net.h" #include "c32ncp.h" #define DM_CREATE_DATE 0x00000004UL #define DM_CREATE_TIME 0x00000008UL #define DM_CREATOR_ID 0x00000010UL #define DM_ARCHIVE_DATE 0x00000020UL #define DM_ARCHIVE_TIME 0x00000040UL #define DM_ARCHIVER_ID 0x00000080UL #define DM_MODIFY_DATE 0x00000100UL #define DM_MODIFY_TIME 0x00000200UL #define DM_MODIFIER_ID 0x00000400UL #define BINDERY_USER 0x0001 #define BINDERY_GROUP 0x0002 #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 CREATOR_ATTR_RENAME_INHIBIT 0x00020000UL #define CREATOR_ATTR_DELETE_INHIBIT 0x00040000UL #define CREATOR_RIGHT_S 0x01 #define CREATOR_RIGHT_R 0x02 #define CREATOR_RIGHT_W 0x04 #define CREATOR_RIGHT_C 0x08 #define CREATOR_RIGHT_E 0x10 #define CREATOR_RIGHT_M 0x20 #define CREATOR_RIGHT_F 0x40 #define CREATOR_RIGHT_A 0x80 static void creator_usage(void) { fprintf(stdout, "Usage:\n"); fprintf(stdout, " CREATOR file /SHOW\n"); fprintf(stdout, " CREATOR file /CREATOR user-or-hexid\n"); fprintf(stdout, " CREATOR file /MODIFIER user-or-hexid\n"); fprintf(stdout, " CREATOR file /ARCHIVER user-or-hexid\n"); fprintf(stdout, " CREATOR file /CREATE user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n"); fprintf(stdout, " CREATOR file /MODIFY user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n"); fprintf(stdout, " CREATOR file /ARCHIVE user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n"); fprintf(stdout, " CREATOR file /ALL user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n"); fprintf(stdout, "\n"); fprintf(stdout, "Examples:\n"); fprintf(stdout, " CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /SHOW\n"); fprintf(stdout, " CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /CREATOR MARIO\n"); fprintf(stdout, " CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /ARCHIVE MARIO\n"); } static int hex_value(int c) { if (c >= '0' && c <= '9') return(c - '0'); if (c >= 'a' && c <= 'f') return(c - 'a' + 10); if (c >= 'A' && c <= 'F') return(c - 'A' + 10); return(-1); } static int parse_hex32(char *s, uint32 *out) { uint32 value = 0; int i; int v; if (!s || !*s || !out) return(-1); if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { s += 2; if (!*s) return(-1); } for (i = 0; s[i]; i++) { v = hex_value(s[i]); if (v < 0) return(-1); value = (value << 4) | (uint32)v; } *out = value; return(0); } static uint32 lookup_object_id(char *name) { uint32 id; uint8 namebuf[48]; if (!name || !*name) return(0); if (!parse_hex32(name, &id)) return(id); strmaxcpy(namebuf, name, sizeof(namebuf) - 1); upstr(namebuf); id = ncp17_35_get_bindery_object_id(namebuf, BINDERY_USER); if (id) return(id); id = ncp17_35_get_bindery_object_id(namebuf, BINDERY_GROUP); return(id); } static int parse_uint(char *s, int *out) { int v = 0; int i; if (!s || !*s || !out) return(-1); for (i = 0; s[i]; i++) { if (s[i] < '0' || s[i] > '9') return(-1); v = v * 10 + (s[i] - '0'); } *out = v; return(0); } static uint16 make_dos_date(int year, int month, int day) { if (year < 80) year += 2000; if (year < 1980) year = 1980; return((uint16)(((year - 1980) << 9) | (month << 5) | day)); } static uint16 make_dos_time(int hour, int min, int sec) { return((uint16)((hour << 11) | (min << 5) | (sec / 2))); } static void current_dos_datetime(uint16 *date_out, uint16 *time_out) { REGS regs; int year; int month; int day; int hour; int min; int sec; regs.h.ah = 0x2a; int86(0x21, ®s, ®s); year = regs.x.cx; month = regs.h.dh; day = regs.h.dl; regs.h.ah = 0x2c; int86(0x21, ®s, ®s); hour = regs.h.ch; min = regs.h.cl; sec = regs.h.dh; if (date_out) *date_out = make_dos_date(year, month, day); if (time_out) *time_out = make_dos_time(hour, min, sec); } static int parse_date_arg(char *s, uint16 *date_out) { char buf[32]; char *p1; char *p2; int y; int m; int d; uint32 hex; if (!s || !date_out) return(-1); if (!parse_hex32(s, &hex) && (s[0] == '0' || strlen(s) <= 4)) { *date_out = (uint16)hex; return(0); } strmaxcpy(buf, s, sizeof(buf)-1); p1 = strchr(buf, '-'); if (!p1) p1 = strchr(buf, '.'); if (!p1) return(-1); *p1++ = '\0'; p2 = strchr(p1, '-'); if (!p2) p2 = strchr(p1, '.'); if (!p2) return(-1); *p2++ = '\0'; if (parse_uint(buf, &y) || parse_uint(p1, &m) || parse_uint(p2, &d)) return(-1); /* Support both yyyy-mm-dd and dd.mm.yy. */ if (y < 100 && d > 100) { int tmp = y; y = d; d = tmp; } if (y < 100) y += 2000; if (m < 1 || m > 12 || d < 1 || d > 31) return(-1); *date_out = make_dos_date(y, m, d); return(0); } static int parse_time_arg(char *s, uint16 *time_out) { char buf[32]; char *p1; char *p2; int h; int m; int sec = 0; uint32 hex; if (!s || !time_out) return(-1); if (!parse_hex32(s, &hex) && (s[0] == '0' || strlen(s) <= 4)) { *time_out = (uint16)hex; return(0); } strmaxcpy(buf, s, sizeof(buf)-1); p1 = strchr(buf, ':'); if (!p1) return(-1); *p1++ = '\0'; p2 = strchr(p1, ':'); if (p2) *p2++ = '\0'; if (parse_uint(buf, &h) || parse_uint(p1, &m)) return(-1); if (p2 && parse_uint(p2, &sec)) return(-1); if (h < 0 || h > 23 || m < 0 || m > 59 || sec < 0 || sec > 59) return(-1); *time_out = make_dos_time(h, m, sec); return(0); } static void print_dos_date(uint16 date) { int year = ((date >> 9) & 0x7f) + 1980; int month = (date >> 5) & 0x0f; int day = date & 0x1f; fprintf(stdout, "%04d-%02d-%02d", year, month, day); } static void print_dos_time(uint16 time) { int hour = (time >> 11) & 0x1f; int min = (time >> 5) & 0x3f; int sec = (time & 0x1f) * 2; fprintf(stdout, "%02d:%02d:%02d", hour, min, sec); } static int split_path(char *path, char *parent, int parent_size, char *name, int name_size) { char tmp[260]; char *p; if (!path || !*path || !parent || !name) return(-1); strmaxcpy(tmp, path, sizeof(tmp)-1); korrpath(tmp); p = strrchr(tmp, '\\'); if (!p) p = strrchr(tmp, '/'); if (p) { *p++ = '\0'; strmaxcpy(parent, tmp, parent_size-1); strmaxcpy(name, p, name_size-1); } else { parent[0] = '\0'; strmaxcpy(name, tmp, name_size-1); } if (!name[0]) return(-1); return(0); } static int set_info(uint8 dhandle, char *name, uint32 bits, uint16 cdate, uint16 ctime, uint32 cid, uint16 adate, uint16 atime, uint32 aid, uint16 mdate, uint16 mtime, uint32 mid) { struct { uint16 len; uint8 func; uint8 dirhandle; uint8 search_attributes; uint8 searchsequence[4]; uint8 change_bits[4]; uint8 subdir[4]; uint8 attributes[4]; uint8 uniqueid; uint8 flags; uint8 namespace; uint8 namlen; uint8 name[12]; uint8 created_time[2]; uint8 created_date[2]; uint8 created_id[4]; uint8 archived_time[2]; uint8 archived_date[2]; uint8 archived_id[4]; uint8 updated_time[2]; uint8 updated_date[2]; uint8 updated_id[4]; uint8 rest[80]; } req; struct { uint16 len; } repl; memset(&req, 0, sizeof(req)); memset(&repl, 0, sizeof(repl)); req.func = 0x25; req.dirhandle = dhandle; req.search_attributes = 0x06; U32_TO_BE32(0xffffffffUL, req.searchsequence); U32_TO_32(bits, req.change_bits); U16_TO_16(ctime, req.created_time); U16_TO_16(cdate, req.created_date); U32_TO_BE32(cid, req.created_id); U16_TO_16(atime, req.archived_time); U16_TO_16(adate, req.archived_date); U32_TO_BE32(aid, req.archived_id); U16_TO_16(mtime, req.updated_time); U16_TO_16(mdate, req.updated_date); U32_TO_BE32(mid, req.updated_id); if (tool_copy_ncp22_name(req.name, name, &req.namlen)) return(-1); req.len = sizeof(req) - sizeof(req.len); neterrno = Net_Call(0xE200, &req, &repl); if (neterrno) return(-neterrno); return(0); } static void creator_attr_string(uint32 attr, char *out) { 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 & CREATOR_ATTR_DELETE_INHIBIT) ? 'D' : '-'; out[18] = (attr & CREATOR_ATTR_RENAME_INHIBIT) ? 'R' : '-'; out[19] = ']'; out[20] = '\0'; } static void creator_rights_string(uint16 rights, char *out) { uint8 old = (uint8)rights; out[0] = '['; out[1] = (old & CREATOR_RIGHT_S) ? 'S' : '-'; out[2] = (old & CREATOR_RIGHT_R) ? 'R' : '-'; out[3] = (old & CREATOR_RIGHT_W) ? 'W' : '-'; out[4] = (old & CREATOR_RIGHT_C) ? 'C' : '-'; out[5] = (old & CREATOR_RIGHT_E) ? 'E' : '-'; out[6] = (old & CREATOR_RIGHT_M) ? 'M' : '-'; out[7] = (old & CREATOR_RIGHT_F) ? 'F' : '-'; out[8] = (old & CREATOR_RIGHT_A) ? 'A' : '-'; out[9] = ']'; out[10] = '\0'; } static int show_info(char *name, uint8 dhandle) { C32_NDIR_INFO info; int rc; char attr_text[24]; char rights_text[12]; memset(&info, 0, sizeof(info)); rc = c32_ncp87_obtain_ndir_info(tool_is_current_path(name) ? "" : name, (uint16)dhandle, &info, NULL, NULL, NULL); if (rc) { fprintf(stdout, "CREATOR: obtain info failed for %s, rc=%d\n", name, rc); return(rc); } creator_attr_string(info.attributes, attr_text); creator_rights_string(info.inherited_rights, rights_text); fprintf(stdout, "File: %s\n", name); fprintf(stdout, "Attributes: %s (0x%08lX)\n", attr_text, (unsigned long)info.attributes); fprintf(stdout, "Create: "); print_dos_date(info.creation_date); fprintf(stdout, " "); print_dos_time(info.creation_time); fprintf(stdout, " creator=0x%08lX\n", (unsigned long)info.creator_id); fprintf(stdout, "Modify: "); print_dos_date(info.modify_date); fprintf(stdout, " "); print_dos_time(info.modify_time); fprintf(stdout, " modifier=0x%08lX\n", (unsigned long)info.modifier_id); fprintf(stdout, "Archive: "); print_dos_date(info.archive_date); fprintf(stdout, " "); print_dos_time(info.archive_time); fprintf(stdout, " archiver=0x%08lX\n", (unsigned long)info.archiver_id); fprintf(stdout, "Access date: "); print_dos_date(info.last_access_date); fprintf(stdout, "\nRights mask: %s (0x%04X)\n", rights_text, (unsigned)info.inherited_rights); return(0); } int func_creator(int argc, char *argv[], int mode) { char oldcwd[260]; char parent[260]; char name[64]; char *path; char *cmd; char *id_arg; uint8 dhandle = 0; uint32 id = 0; uint16 date; uint16 time; uint32 bits = 0; int rc = 0; int need_id = 0; (void)mode; if (argc < 3) { creator_usage(); return(1); } path = argv[1]; cmd = argv[2]; upstr(cmd); if (split_path(path, parent, sizeof(parent), name, sizeof(name))) { fprintf(stdout, "CREATOR: bad path %s\n", path); return(1); } if (!getcwd(oldcwd, sizeof(oldcwd))) oldcwd[0] = '\0'; if (parent[0] && chdir(parent)) { fprintf(stdout, "CREATOR: cannot change to parent %s\n", parent); return(1); } if (tool_current_dhandle_only(&dhandle)) { fprintf(stdout, "CREATOR: current drive is not a network drive\n"); if (oldcwd[0]) chdir(oldcwd); return(1); } if (!strcmp(cmd, "/SHOW") || !strcmp(cmd, "SHOW")) { rc = show_info(name, dhandle); if (oldcwd[0]) chdir(oldcwd); return(rc); } need_id = 1; if (argc < 4) { creator_usage(); if (oldcwd[0]) chdir(oldcwd); return(1); } id_arg = argv[3]; id = lookup_object_id(id_arg); if (!id) { fprintf(stdout, "CREATOR: object not found: %s\n", id_arg); if (oldcwd[0]) chdir(oldcwd); return(1); } current_dos_datetime(&date, &time); if (argc >= 5 && parse_date_arg(argv[4], &date)) { fprintf(stdout, "CREATOR: bad date '%s'\n", argv[4]); if (oldcwd[0]) chdir(oldcwd); return(1); } if (argc >= 6 && parse_time_arg(argv[5], &time)) { fprintf(stdout, "CREATOR: bad time '%s'\n", argv[5]); if (oldcwd[0]) chdir(oldcwd); return(1); } if (!strcmp(cmd, "/CREATOR") || !strcmp(cmd, "CREATOR")) { bits = DM_CREATOR_ID; rc = set_info(dhandle, name, bits, 0, 0, id, 0, 0, 0, 0, 0, 0); } else if (!strcmp(cmd, "/MODIFIER") || !strcmp(cmd, "MODIFIER")) { bits = DM_MODIFIER_ID; rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, 0, 0, 0, id); } else if (!strcmp(cmd, "/ARCHIVER") || !strcmp(cmd, "ARCHIVER")) { bits = DM_ARCHIVER_ID; rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, id, 0, 0, 0); } else if (!strcmp(cmd, "/CREATE") || !strcmp(cmd, "CREATE")) { bits = DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID; rc = set_info(dhandle, name, bits, date, time, id, 0, 0, 0, 0, 0, 0); } else if (!strcmp(cmd, "/MODIFY") || !strcmp(cmd, "MODIFY")) { bits = DM_MODIFY_DATE | DM_MODIFY_TIME | DM_MODIFIER_ID; rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, 0, date, time, id); } else if (!strcmp(cmd, "/ARCHIVE") || !strcmp(cmd, "ARCHIVE")) { bits = DM_ARCHIVE_DATE | DM_ARCHIVE_TIME | DM_ARCHIVER_ID; rc = set_info(dhandle, name, bits, 0, 0, 0, date, time, id, 0, 0, 0); } else if (!strcmp(cmd, "/ALL") || !strcmp(cmd, "ALL")) { bits = DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID | DM_ARCHIVE_DATE | DM_ARCHIVE_TIME | DM_ARCHIVER_ID | DM_MODIFY_DATE | DM_MODIFY_TIME | DM_MODIFIER_ID; rc = set_info(dhandle, name, bits, date, time, id, date, time, id, date, time, id); } else { creator_usage(); if (oldcwd[0]) chdir(oldcwd); return(1); } if (rc) { fprintf(stdout, "CREATOR: NCP22/25 set info failed for %s, rc=%d neterrno=0x%02X\n", path, rc, (unsigned)(rc < 0 ? -rc : 0)); } else { fprintf(stdout, "CREATOR: set %s on %s to 0x%08lX OK\n", cmd, path, (unsigned long)id); if (bits & (DM_CREATE_DATE | DM_MODIFY_DATE | DM_ARCHIVE_DATE)) { fprintf(stdout, "CREATOR: date/time "); print_dos_date(date); fprintf(stdout, " "); print_dos_time(time); fprintf(stdout, "\n"); } } if (!rc) show_info(name, dhandle); if (oldcwd[0]) chdir(oldcwd); return(rc ? rc : 0); }