/* * 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: FLAG utility for displaying and changing NetWare DOS file attributes. * Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines. */ /* flag.c - Novell FLAG-like DOS utility, stage 1 */ #include "net.h" #include "c32ncp.h" #include /* * FLAG v4b: NCP 87 namespace DOS info. * * ncpfs reference: * ncp_ns_modify_entry_dos_info(): * subfunction 7, namespace DOS, search attrs SA_ALL, * ModifyInformationMask, struct ncp_dos_info, handle/path. * * We use dirstyle=0 (short directory handle) against the current DOS * directory handle and a one-component DOS filename. */ #define FLAG_NW_NS_DOS 0x00 #define FLAG_SA_ALL 0x0006 #define FLAG_RIM_ATTRIBUTES 0x00000004UL #define FLAG_DM_ATTRIBUTES 0x00000002UL #define NWFA_RO 0x00000001UL #define NWFA_H 0x00000002UL #define NWFA_SY 0x00000004UL #define NWFA_A 0x00000020UL #define NWFA_S 0x00000080UL #define NWFA_T 0x00001000UL #define NWFA_RA 0x00004000UL #define NWFA_WA 0x00008000UL #define NWFA_P 0x00010000UL #define NWFA_RI 0x00020000UL #define NWFA_DI 0x00040000UL #define NWFA_CI 0x00080000UL static int flag_add_handle_path(uint8 *p, uint8 dhandle, char *name) { int nlen; nlen = strlen(name); if (nlen > 255) nlen = 255; /* * handle/path: * volume/handle byte * dir base dword * dirstyle byte (0 = short dir handle) * path components: 1 component, length, bytes */ *p++ = dhandle; tool_put_dword_lh(p, 0L); p += 4; *p++ = 0; /* dirstyle = handle */ *p++ = 1; /* one path component */ *p++ = (uint8)nlen; memcpy(p, name, nlen); p += nlen; return(1 + 4 + 1 + 1 + 1 + nlen); } static int flag_ncp87_obtain_attrs(char *name, uint32 *attrs) { struct { uint16 len; uint8 data[320]; } req; struct { uint16 len; uint8 data[128]; } repl; uint8 connid = 0; uint8 dhandle = 0; uint8 *p; int hlen; if (tool_current_dhandle(&connid, &dhandle)) return(-1); /* * Prefer the verified Client32 NCP87 path. If it is not available, * fall back to the historical INT 21h/Net_Call path below. */ if (c32_ncp87_obtain_rim_attributes(name, (uint16)dhandle, attrs, NULL, NULL, NULL) == 0) return(0); memset(&req, 0, sizeof(req)); memset(&repl, 0, sizeof(repl)); p = req.data; *p++ = 6; /* subfunction: obtain file/subdir info */ *p++ = FLAG_NW_NS_DOS; /* source namespace */ *p++ = FLAG_NW_NS_DOS; /* target namespace */ tool_put_word_lh(p, FLAG_SA_ALL); p += 2; tool_put_dword_lh(p, FLAG_RIM_ATTRIBUTES); p += 4; hlen = flag_add_handle_path(p, dhandle, name); p += hlen; req.len = (uint16)(p - req.data); repl.len = sizeof(repl.data); neterrno = Net_Call(0xF257, &req, &repl); if (neterrno) return(-1); /* * With RIM_ATTRIBUTES only, ncpfs expects NSI_Attributes first. * First dword is the 32-bit Attributes field. */ if (attrs) *attrs = tool_get_dword_lh(repl.data); return(0); } static int flag_ncp22_1e_obtain_attrs(char *name, 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; /* Scan directory */ req.dirhandle = dhandle; req.search_attributes = 0x06; /* hidden/system */ 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 Scan Directory returns: * dword searchsequence * dword subdir * dword attributes * ... */ if (attrs) *attrs = tool_get_dword_lh(repl.data + 8); return(0); } static int flag_ncp22_25_modify_attrs(char *name, uint32 attrs) { 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 rest[104]; } req; struct { uint16 len; } repl; uint8 connid = 0; uint8 dhandle = 0; if (tool_current_dhandle(&connid, &dhandle)) return(-1); memset(&req, 0, sizeof(req)); memset(&repl, 0, sizeof(repl)); req.func = 0x25; /* Set directory/file information */ req.dirhandle = dhandle; req.search_attributes = 0x06; /* hidden/system */ U32_TO_BE32(0xffffffffUL, req.searchsequence); U32_TO_32(FLAG_DM_ATTRIBUTES, req.change_bits); U32_TO_32(attrs, req.attributes); 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(-1); return(0); } static int flag_obtain_attrs(char *name, uint32 *attrs) { if (!flag_ncp22_1e_obtain_attrs(name, attrs)) return(0); return(flag_ncp87_obtain_attrs(name, attrs)); } static int flag_ncp87_modify_attrs(char *name, uint32 attrs) { struct { uint16 len; uint8 data[384]; } req; struct { uint16 len; uint8 data[8]; } repl; uint8 connid = 0; uint8 dhandle = 0; uint8 *p; int hlen; if (tool_current_dhandle(&connid, &dhandle)) return(-1); /* * Prefer verified Client32 modify path. The old INT 21h/F257 modify path * can hang under DOS Client32 for high FLAG bits such as T/P/DI/RI. */ { uint16 actual = 0; uint16 hlo = 0; uint16 hhi = 0; if (!c32_ncp87_modify_dos_attributes(name, (uint16)dhandle, attrs, &actual, &hlo, &hhi)) return(0); } memset(&req, 0, sizeof(req)); memset(&repl, 0, sizeof(repl)); p = req.data; *p++ = 7; /* subfunction: modify DOS info */ *p++ = FLAG_NW_NS_DOS; *p++ = 0; /* reserved */ tool_put_word_lh(p, FLAG_SA_ALL); p += 2; tool_put_dword_lh(p, FLAG_DM_ATTRIBUTES); p += 4; /* modify mask: DM_ATTRIBUTES */ tool_put_dword_lh(p, attrs); p += 4; /* Attributes */ /* * Remaining ncp_dos_info fields. Mask says only Attributes is valid, * so these should be ignored, but ncpfs still sends the full structure. */ memset(p, 0, 34); p += 34; hlen = flag_add_handle_path(p, dhandle, name); p += hlen; req.len = (uint16)(p - req.data); repl.len = sizeof(repl.data); neterrno = Net_Call(0xF257, &req, &repl); if (neterrno) return(-1); return(0); } #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 static void flag_help(void) { fprintf(stdout, "USAGE: FLAG [path [ option | [+|-] attribute(s) ] [SUB]]\n"); fprintf(stdout, "\n"); fprintf(stdout, "386 Attributes:\n"); fprintf(stdout, "--------------\n"); fprintf(stdout, "\n"); fprintf(stdout, "RO Read Only\n"); fprintf(stdout, "RW Read Write\n"); fprintf(stdout, "S Sharable\n"); fprintf(stdout, "H Hidden\n"); fprintf(stdout, "Sy System\n"); fprintf(stdout, "T Transactional\n"); fprintf(stdout, "P Purge\n"); fprintf(stdout, "A Archive Needed\n"); fprintf(stdout, "RA Read Audit\n"); fprintf(stdout, "WA Write Audit\n"); fprintf(stdout, "CI Copy Inhibit\n"); fprintf(stdout, "DI Delete Inhibit\n"); fprintf(stdout, "RI Rename Inhibit\n"); fprintf(stdout, "\n"); fprintf(stdout, "All All\n"); fprintf(stdout, "N Normal\n"); fprintf(stdout, "SUB\n"); } static int flag_attr_mask(char *s, uint32 *setbits, uint32 *clearbits) { int set = 1; char *p = s; if (*p == '+') { set = 1; p++; } else if (*p == '-') { set = 0; p++; } if (!*p) return(-1); if (tool_strsame(p, "RO")) { if (set) { *setbits |= (NWFA_RO | NWFA_DI | NWFA_RI); } else { *clearbits |= (NWFA_RO | NWFA_DI | NWFA_RI); } } else if (tool_strsame(p, "RW")) { *clearbits |= (NWFA_RO | NWFA_DI | NWFA_RI); } else if (tool_strsame(p, "S")) { if (set) *setbits |= NWFA_S; else *clearbits |= NWFA_S; } else if (tool_strsame(p, "H")) { if (set) *setbits |= NWFA_H; else *clearbits |= NWFA_H; } else if (tool_strsame(p, "SY") || tool_strsame(p, "SYS") || tool_strsame(p, "SYSTEM")) { if (set) *setbits |= NWFA_SY; else *clearbits |= NWFA_SY; } else if (tool_strsame(p, "T")) { if (set) *setbits |= NWFA_T; else *clearbits |= NWFA_T; } else if (tool_strsame(p, "P")) { if (set) *setbits |= NWFA_P; else *clearbits |= NWFA_P; } else if (tool_strsame(p, "A")) { if (set) *setbits |= NWFA_A; else *clearbits |= NWFA_A; } else if (tool_strsame(p, "RA")) { if (set) *setbits |= NWFA_RA; else *clearbits |= NWFA_RA; } else if (tool_strsame(p, "WA")) { if (set) *setbits |= NWFA_WA; else *clearbits |= NWFA_WA; } else if (tool_strsame(p, "CI")) { if (set) *setbits |= NWFA_CI; else *clearbits |= NWFA_CI; } else if (tool_strsame(p, "DI")) { if (set) *setbits |= NWFA_DI; else *clearbits |= NWFA_DI; } else if (tool_strsame(p, "RI")) { if (set) *setbits |= NWFA_RI; else *clearbits |= NWFA_RI; } else if (tool_strsame(p, "N") || tool_strsame(p, "NORMAL")) { *clearbits |= (NWFA_RO | NWFA_H | NWFA_SY | NWFA_A | NWFA_S | NWFA_T | NWFA_P | NWFA_RA | NWFA_WA | NWFA_CI | NWFA_DI | NWFA_RI); } else if (tool_strsame(p, "ALL")) { *setbits |= (NWFA_RO | NWFA_H | NWFA_SY | NWFA_A | NWFA_S | NWFA_T | NWFA_P | NWFA_RA | NWFA_WA | NWFA_CI | NWFA_DI | NWFA_RI); } else { fprintf(stderr, "Unknown attribute encountered in command line.\n"); return(-1); } return(0); } static void flag_print_attrs(uint32 attr) { /* * Novell order: * Ro/Rw S A - H Sy T P Ra Wa CI DI RI */ fprintf(stdout, "[ "); fprintf(stdout, "%s ", (attr & NWFA_RO) ? "Ro" : "Rw"); fprintf(stdout, "%c ", (attr & NWFA_S) ? 'S' : '-'); fprintf(stdout, "%c ", (attr & NWFA_A) ? 'A' : '-'); fprintf(stdout, "- "); fprintf(stdout, "%c ", (attr & NWFA_H) ? 'H' : '-'); fprintf(stdout, "%s ", (attr & NWFA_SY) ? "Sy" : "--"); fprintf(stdout, "%c ", (attr & NWFA_T) ? 'T' : '-'); fprintf(stdout, "%c ", (attr & NWFA_P) ? 'P' : '-'); fprintf(stdout, "%s ", (attr & NWFA_RA) ? "Ra" : "--"); fprintf(stdout, "%s ", (attr & NWFA_WA) ? "Wa" : "--"); fprintf(stdout, "%s ", (attr & NWFA_CI) ? "CI" : "--"); fprintf(stdout, "%s ", (attr & NWFA_DI) ? "DI" : "--"); fprintf(stdout, "%s ", (attr & NWFA_RI) ? "RI" : "--"); fprintf(stdout, "]"); } static void flag_display_one(char *name, uint32 attr) { fprintf(stdout, " %-23s ", name); flag_print_attrs(attr); fprintf(stdout, " \n"); } static void flag_display_one_paged(char *name, uint32 attr, int *line_count, int *continuous) { flag_display_one(name, attr); tool_page_line(line_count, continuous); } static char *flag_last_sep(char *s) { char *last = NULL; char *p; for (p = s; *p; p++) { if (*p == '\\' || *p == '/' || *p == ':') last = p; } return(last); } static void flag_split_pattern(char *pattern, char *parent, int parent_size, char *leaf, int leaf_size) { char temp[260]; char *sep; int plen; parent[0] = '\0'; leaf[0] = '\0'; strmaxcpy(temp, pattern, sizeof(temp) - 1); sep = flag_last_sep(temp); if (!sep) { strmaxcpy(leaf, temp, leaf_size - 1); return; } if (*(sep + 1)) { strmaxcpy(leaf, sep + 1, leaf_size - 1); } else { strmaxcpy(leaf, "*.*", leaf_size - 1); } plen = (int)(sep - temp); /* * Keep drive-root paths as "F:\" rather than "F:". */ if (*sep == ':' && (*(sep + 1) == '\\' || *(sep + 1) == '/')) plen += 2; else if (*sep == ':' || *sep == '\\' || *sep == '/') plen += 1; if (plen >= parent_size) plen = parent_size - 1; memcpy(parent, temp, plen); parent[plen] = '\0'; } static int flag_enter_parent(char *pattern, char *leaf, int leaf_size, char *oldcwd, int oldcwd_size) { char parent[260]; oldcwd[0] = '\0'; flag_split_pattern(pattern, parent, sizeof(parent), leaf, leaf_size); if (!leaf[0]) strmaxcpy(leaf, "*.*", leaf_size - 1); if (!parent[0]) return(0); if (!getcwd(oldcwd, oldcwd_size)) oldcwd[0] = '\0'; if (chdir(parent)) { if (oldcwd[0]) chdir(oldcwd); return(-1); } return(0); } static void flag_leave_parent(char *oldcwd) { if (oldcwd[0]) chdir(oldcwd); } static int flag_list(char *pattern) { struct find_t ff; unsigned findattr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH; int found = 0; int line_count = 0; int continuous = 0; if (_dos_findfirst(pattern, findattr, &ff)) return(-1); do { if (!(ff.attrib & _A_SUBDIR)) { uint32 nwattrs; if (flag_obtain_attrs(ff.name, &nwattrs)) nwattrs = (uint32)ff.attrib; flag_display_one_paged(ff.name, nwattrs, &line_count, &continuous); found++; } } while (!_dos_findnext(&ff)); return(found); } static int flag_apply(char *pattern, uint32 setbits, uint32 clearbits) { struct find_t ff; unsigned findattr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH; int shown = 0; if (_dos_findfirst(pattern, findattr, &ff)) return(-1); do { uint32 attrs; uint32 newattrs; char fname[260]; if (ff.attrib & _A_SUBDIR) continue; strmaxcpy(fname, ff.name, sizeof(fname) - 1); if (flag_obtain_attrs(fname, &attrs)) attrs = (uint32)ff.attrib; newattrs = (attrs | (uint32)setbits) & ~((uint32)clearbits); if (newattrs != attrs) { if (flag_ncp22_25_modify_attrs(fname, newattrs) && flag_ncp87_modify_attrs(fname, newattrs)) { unsigned dosattr = (newattrs & (_A_RDONLY|_A_HIDDEN|_A_SYSTEM|_A_ARCH)); if (_dos_setfileattr(fname, dosattr)) { fprintf(stdout, "You don't have rights to change : %s\n", fname); continue; } } } if (flag_obtain_attrs(fname, &attrs)) attrs = newattrs; flag_display_one(fname, newattrs); shown++; } while (!_dos_findnext(&ff)); return(shown); } int func_flag(int argc, char *argv[], int mode) { char *path = "*.*"; int i; uint32 setbits = 0; uint32 clearbits = 0; int have_change = 0; int rc; (void)mode; if (argc > 1 && (tool_strsame(argv[1], "/?") || tool_strsame(argv[1], "-?") || tool_strsame(argv[1], "?"))) { flag_help(); return(0); } if (argc > 1) { path = argv[1]; if (tool_strsame(path, "SUB")) path = "*.*"; } for (i = 2; i < argc; i++) { if (tool_strsame(argv[i], "SUB")) continue; rc = flag_attr_mask(argv[i], &setbits, &clearbits); if (rc < 0) return(1); if (rc > 0) continue; have_change = 1; } { char leaf[260]; char oldcwd[260]; if (flag_enter_parent(path, leaf, sizeof(leaf), oldcwd, sizeof(oldcwd))) { flag_split_pattern(path, oldcwd, sizeof(oldcwd), leaf, sizeof(leaf)); fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf); return(1); } if (have_change) { rc = flag_apply(leaf, setbits, clearbits); flag_leave_parent(oldcwd); if (rc < 0) { fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf); return(1); } return(0); } rc = flag_list(leaf); flag_leave_parent(oldcwd); if (rc < 0) { fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf); return(1); } } return(0); }