/* * 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: FLAGDIR utility for displaying and changing NetWare directory attributes. * Depends on: net.h, c32ncp.h, netcall.c requester helpers, c32ncp.c namespace/NCP helpers, tools.c shared utility routines. */ /* flagdir.c - Novell FLAGDIR-like DOS utility, Client32 version */ #include "net.h" #include "c32ncp.h" #include #include #define FD_NWFA_H 0x00000002UL #define FD_NWFA_SY 0x00000004UL #define FD_NWFA_P 0x00010000UL #define FD_NWFA_RI 0x00020000UL #define FD_NWFA_DI 0x00040000UL #define FD_DIR_BITS (FD_NWFA_H | FD_NWFA_SY | FD_NWFA_P | FD_NWFA_RI | FD_NWFA_DI) #define FD_DM_ATTRIBUTES 0x00000002UL #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 int fd_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 = 0x16; /* hidden/system/subdir */ 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 attributes at offset 8. */ if (attrs) *attrs = tool_get_dword_lh(repl.data + 8); return(0); } static int fd_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 = 0x16; /* hidden/system/subdir */ U32_TO_BE32(0xffffffffUL, req.searchsequence); U32_TO_32(FD_DM_ATTRIBUTES, req.change_bits); tool_put_dword_lh(req.attributes, attrs); 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 char *fd_last_sep(char *s) { char *last = NULL; char *p; for (p = s; *p; p++) { if (*p == '\\' || *p == '/' || *p == ':') last = p; } return(last); } static void fd_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 = fd_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); 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 fd_enter_parent(char *pattern, char *leaf, int leaf_size, char *oldcwd, int oldcwd_size) { char parent[260]; oldcwd[0] = '\0'; fd_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 fd_leave_parent(char *oldcwd) { if (oldcwd[0]) chdir(oldcwd); } static int fd_current_display_path(uint8 dhandle, char *out, int max) { char path[260]; char *p; if (!out || max < 2) return(-1); out[0] = '\0'; path[0] = '\0'; if (get_dir_path(dhandle, path) || !path[0]) return(-1); p = strchr(path, ':'); if (p) p++; else p = path; if (!*p) out[0] = '\0'; else strmaxcpy(out, p, max - 1); return(0); } static void fd_help(void) { fprintf(stdout, "386 Usage: Flagdir [path [option...]]\n"); fprintf(stdout, "Options: Normal\n"); fprintf(stdout, " System\n"); fprintf(stdout, " Hidden\n"); fprintf(stdout, " Deleteinhibit\n"); fprintf(stdout, " Purge\n"); fprintf(stdout, " Renameinhibit\n"); } static int fd_attr_mask(char *s, uint32 *setbits, uint32 *clearbits) { if (tool_strsame(s, "N") || tool_strsame(s, "NORMAL")) { *clearbits |= FD_DIR_BITS; } else if (tool_strsame(s, "S") || tool_strsame(s, "SY") || tool_strsame(s, "SYS") || tool_strsame(s, "SYSTEM")) { *setbits |= FD_NWFA_SY; } else if (tool_strsame(s, "H") || tool_strsame(s, "HIDDEN")) { *setbits |= FD_NWFA_H; } else if (tool_strsame(s, "DI") || tool_strsame(s, "DELETEINHIBIT")) { *setbits |= FD_NWFA_DI; } else if (tool_strsame(s, "P") || tool_strsame(s, "PURGE")) { *setbits |= FD_NWFA_P; } else if (tool_strsame(s, "RI") || tool_strsame(s, "RENAMEINHIBIT")) { *setbits |= FD_NWFA_RI; } else if (tool_strsame(s, "PRIVATE") || tool_strsame(s, "PR")) { fprintf(stderr, "Private is valid on NetWare 2.15 and above, except NetWare 386.\n"); return(-1); } else { fprintf(stderr, "Unknown attribute: %s.\n", s); return(-1); } return(0); } static void fd_print_attrs(uint32 attrs) { int any = 0; if (!(attrs & FD_DIR_BITS)) { fprintf(stdout, "Normal \n"); return; } if (attrs & FD_NWFA_SY) { fprintf(stdout, "System"); any = 1; } if (attrs & FD_NWFA_H) { fprintf(stdout, "%sHidden", any ? " " : ""); any = 1; } if (attrs & FD_NWFA_DI) { fprintf(stdout, "%sDeleteInhibit", any ? " " : ""); any = 1; } if (attrs & FD_NWFA_P) { fprintf(stdout, "%sPurge", any ? " " : ""); any = 1; } if (attrs & FD_NWFA_RI) { fprintf(stdout, "%sRenameInhibit", any ? " " : ""); any = 1; } fprintf(stdout, " \n"); } static void fd_display_header(char *path) { char up[260]; char prefix[260]; char header[300]; int len; char *p; if (tool_current_prefix(prefix, sizeof(prefix))) prefix[0] = '\0'; tool_upcopy(up, path, sizeof(up)); /* fd_process_one() enters the parent directory before displaying the * entry. Use the current NetWare prefix plus the leaf/relative name, * not the original F:\... argument, otherwise the drive-qualified path is * appended to the already changed current directory. */ strmaxcpy(header, prefix, sizeof(header) - 1); if (!tool_is_current_path(up) && up[0]) { len = strlen(header); if (len > 0 && header[len - 1] != ':' && header[len - 1] != '\\' && header[len - 1] != '/') { if (len < (int)sizeof(header) - 1) { header[len++] = '\\'; header[len] = '\0'; } } if ((int)(strlen(header) + strlen(up)) < (int)sizeof(header) - 1) strcat(header, up); } /* Novell FLAGDIR displays SERVER/SYS: and uses / as path separator in * the header line. */ for (p = header; *p; p++) { if (*p == '\\') *p = '/'; } fprintf(stdout, "%s \n", header); } static void fd_display_row(char *name, uint32 attrs) { char base[260]; tool_basename(base, name, sizeof(base)); fprintf(stdout, " %-12.12s ", base); fd_print_attrs(attrs); } static void fd_display(char *path, uint32 attrs) { fd_display_header(path); fd_display_row(path, attrs); } static int fd_is_directory(char *path) { struct find_t ff; unsigned attr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH; if (tool_is_current_path(path)) return(1); if (_dos_findfirst(path, attr, &ff)) return(0); return((ff.attrib & _A_SUBDIR) != 0); } static int fd_obtain(char *path, uint8 dhandle, uint32 *attrs) { if (!fd_ncp22_1e_obtain_attrs(path, attrs)) return(0); if (ncp87_06_obtain_rim_attributes(path, (uint16)dhandle, attrs, NULL, NULL, NULL) == 0) return(0); return(-1); } static int fd_modify(char *path, uint8 dhandle, uint32 attrs) { if (!fd_ncp22_25_modify_attrs(path, attrs)) return(0); if (ncp87_07_modify_dos_attributes(path, (uint16)dhandle, attrs, NULL, NULL, NULL) == 0) return(0); return(-1); } static int fd_process_one(char *path, char *display_path, uint8 dhandle, uint32 setbits, uint32 clearbits, int have_change, int show_header) { char leaf[260]; char oldcwd[260]; char *ncp_path; char *check_path; uint32 attrs; uint32 newattrs; int rc = 0; oldcwd[0] = '\0'; leaf[0] = '\0'; if (!tool_is_current_path(path)) { if (fd_enter_parent(path, leaf, sizeof(leaf), oldcwd, sizeof(oldcwd))) { fprintf(stderr, "Directory %s not found.\n", path); return(1); } check_path = leaf; ncp_path = leaf; } else { check_path = path; ncp_path = ""; } if (!fd_is_directory(check_path)) { fd_display_header(ncp_path[0] ? ncp_path : path); fprintf(stdout, "Directory %s not found.", ncp_path[0] ? ncp_path : path); fd_leave_parent(oldcwd); return(1); } if (fd_obtain(ncp_path, dhandle, &attrs)) { if (tool_is_current_path(path) && display_path && !display_path[0] && !have_change) { attrs = 0; } else { fprintf(stderr, "Unable to get directory attributes.\n"); fd_leave_parent(oldcwd); return(1); } } if (have_change) { newattrs = (attrs | setbits) & ~clearbits; if (newattrs != attrs) { if (fd_modify(ncp_path, dhandle, newattrs)) { fprintf(stderr, "Unable to change attributes.\n"); fd_leave_parent(oldcwd); return(1); } attrs = newattrs; fd_obtain(ncp_path, dhandle, &attrs); } } if (show_header) fd_display(ncp_path[0] ? ncp_path : display_path, attrs); else fd_display_row(ncp_path[0] ? ncp_path : display_path, attrs); fd_leave_parent(oldcwd); return(rc); } static int fd_process_wild(char *spec, uint8 dhandle, uint32 setbits, uint32 clearbits, int have_change) { struct find_t ff; char dir[260]; char pat[260]; char search[260]; char full[260]; int found = 0; int rc = 0; int lines = 0; int continuous = 0; unsigned attr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH; tool_parent_pattern(dir, pat, spec, sizeof(dir), sizeof(pat)); tool_join_path(search, dir, pat, sizeof(search)); if (_dos_findfirst(search, attr, &ff) != 0) { fprintf(stderr, "Directory %s not found.\n", spec); return(1); } fd_display_header(spec); do { if ((ff.attrib & _A_SUBDIR) && strcmp(ff.name, ".") && strcmp(ff.name, "..")) { tool_join_path(full, dir, ff.name, sizeof(full)); if (fd_process_one(full, ff.name, dhandle, setbits, clearbits, have_change, 0)) rc = 1; found++; tool_page_line(&lines, &continuous); } } while (_dos_findnext(&ff) == 0); if (!found) { fprintf(stderr, "Directory %s not found.\n", spec); return(1); } return(rc); } int func_flagdir(int argc, char *argv[], int mode) { char *path = "."; char display_path[260]; uint8 connid = 0; uint8 dhandle = 0; uint32 setbits = 0; uint32 clearbits = 0; int have_change = 0; int i; (void)mode; if (argc > 1 && (tool_strsame(argv[1], "/?") || tool_strsame(argv[1], "-?") || tool_strsame(argv[1], "?"))) { fd_help(); return(0); } if (argc > 1) path = argv[1]; if (tool_current_dhandle(&connid, &dhandle)) { fprintf(stderr, "FlagDir only works on network directories.\n"); return(1); } for (i = 2; i < argc; i++) { if (fd_attr_mask(argv[i], &setbits, &clearbits)) return(1); have_change = 1; } if (tool_has_wildcards(path)) return(fd_process_wild(path, dhandle, setbits, clearbits, have_change)); if (tool_is_current_path(path)) { if (fd_current_display_path(dhandle, display_path, sizeof(display_path))) strcpy(display_path, "."); } else { strmaxcpy(display_path, path, sizeof(display_path) - 1); } return(fd_process_one(path, display_path, dhandle, setbits, clearbits, have_change, 1)); }