/* ndir.c - first Novell NDIR-like directory listing utility */ #include "net.h" #include "c32ncp.h" #include #include #include #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_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 /* 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, "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 \n"); fprintf(stdout, " SIze \n"); fprintf(stdout, " UPdate \n"); fprintf(stdout, " CReate \n"); fprintf(stdout, " ACcess \n"); fprintf(stdout, " ARchive \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_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, "%02d-%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 A"); 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; sprintf(out, "%02d-%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(unsigned attr, char *out) { /* * Keep the first positions close to Novell's default NDIR display, * for example [RW-A------------]. */ out[0] = '['; out[1] = 'R'; out[2] = (attr & _A_RDONLY) ? 'O' : 'W'; out[3] = (attr & _A_SYSTEM) ? 'S' : '-'; out[4] = (attr & _A_ARCH) ? 'A' : '-'; out[5] = (attr & _A_HIDDEN) ? 'H' : '-'; memset(out + 6, '-', 11); out[17] = ']'; out[18] = '\0'; } 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_get_ncp_info(char *path, C32_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(c32_ncp87_obtain_ndir_info(tool_is_current_path(path) ? "" : path, (uint16)dhandle, info, NULL, NULL, NULL)); } static void ndir_inherited_rights(char *path, char *out) { C32_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 (!c32_ncp87_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 = ncp_16_02(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 int ndir_is_dot_dir(char *name) { if (!name) return(0); if (name[0] == '.' && name[1] == '\0') return(1); if (name[0] == '.' && name[1] == '.' && name[2] == '\0') return(1); return(0); } static void ndir_print_file(char *dir, struct find_t *ff, int options, int *line_count, int *continuous) { char dt[24]; char d[12]; char fl[20]; /* [RW-A------------] plus NUL */ char path[260]; char eff[10]; char inh[10]; char arch[24]; char acc[12]; char crea[24]; C32_NDIR_INFO info; int have_info = 0; tool_join_path(path, dir, ff->name, sizeof(path)); have_info = !ndir_get_ncp_info(path, &info); if (have_info) ndir_dos_datetime(info.modify_date, info.modify_time, dt); else ndir_dos_datetime(ff->wr_date, ff->wr_time, dt); ndir_dos_date(ff->wr_date, d); ndir_flags(ff->attrib, fl); if (options & NDIR_OPT_RIGHTS) { ndir_effective_rights(path, eff); ndir_inherited_rights(path, inh); fprintf(stdout, "%-16.16s %-18.18s [%8.8s] [%8.8s]\n", ff->name, fl, inh, eff); } else if (options & NDIR_OPT_DATES) { /* * DOS findfirst gives us the update timestamp only. Keep the Novell * /DATES layout and use placeholders for archive/access data until * full NetWare namespace date fields are available. */ if (have_info) { ndir_dos_datetime_or_blank(info.archive_date, info.archive_time, arch); ndir_dos_date_or_blank(info.last_access_date, acc); ndir_dos_datetime_or_blank(info.creation_date, info.creation_time, crea); fprintf(stdout, "%-16.16s %-17.17s %-17.17s %-8.8s %-17.17s\n", ff->name, dt, arch, acc, crea); } else { fprintf(stdout, "%-16.16s %-17.17s 0-00-00 0:00 A %-8.8s %-17.17s\n", ff->name, dt, d, dt); } } else { fprintf(stdout, "%-16.16s %12lu %-17.17s %-18.18s\n", ff->name, (unsigned long)ff->size, dt, fl); } tool_page_line(line_count, continuous); } static void ndir_print_dir(char *dir, struct find_t *ff, int options, int *line_count, int *continuous) { char dt[24]; char path[260]; char eff[10]; char inh[10]; C32_NDIR_INFO info; tool_join_path(path, dir, ff->name, sizeof(path)); if (!ndir_get_ncp_info(path, &info)) ndir_dos_datetime(info.creation_date, info.creation_time, dt); else ndir_dos_datetime(ff->wr_date, ff->wr_time, dt); if (options & (NDIR_OPT_RIGHTS | NDIR_OPT_DIRS_ONLY)) { ndir_effective_rights(path, eff); ndir_inherited_rights(path, inh); fprintf(stdout, "%-16.16s [%8.8s] [%8.8s] %-17.17s\n", ff->name, inh, eff, dt); } else { fprintf(stdout, "%-16.16s %-17.17s\n", ff->name, dt); } tool_page_line(line_count, continuous); } 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) { struct find_t ff; int rc; int shown = 0; 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)) { ndir_print_file(dir, &ff, options, line_count, continuous); (*file_count)++; *total_bytes += (unsigned long)ff.size; *total_blocks += ndir_blocks_for_size((unsigned long)ff.size); shown = 1; } rc = _dos_findnext(&ff); } return(shown); } static int ndir_scan_dirs(char *dir, char *search, int options, int *line_count, int *continuous, int *dir_count) { struct find_t ff; int rc; int shown = 0; 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) && !ndir_is_dot_dir(ff.name)) { ndir_print_dir(dir, &ff, options, line_count, continuous); (*dir_count)++; shown = 1; } rc = _dos_findnext(&ff); } return(shown); } 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 file_count = 0; int dir_count = 0; unsigned long total_bytes = 0L; unsigned long total_blocks = 0L; int line_count = 0; ndir_split_spec(spec, dir, pat); tool_join_path(search, dir, pat, sizeof(search)); tool_header_path(display, dir, sizeof(display)); fprintf(stdout, "%s\n", display); tool_page_line(&line_count, continuous); if (!(options & NDIR_OPT_DIRS_ONLY)) { if (!(options & NDIR_OPT_SHORT)) { if (options & NDIR_OPT_RIGHTS) { 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_FILES_ONLY)) { if (!(options & NDIR_OPT_DIRS_ONLY) && !(options & NDIR_OPT_SHORT)) fprintf(stdout, "\n"); if (!(options & NDIR_OPT_SHORT)) { if (options & (NDIR_OPT_RIGHTS | NDIR_OPT_DIRS_ONLY)) { fprintf(stdout, "Directories: Rights Rights Owner Created/Copied\n"); fprintf(stdout, "---------------- ----------- ----------- ----------- -----------------\n"); } else { fprintf(stdout, "Directories:\n"); fprintf(stdout, "----------------\n"); } tool_page_line(&line_count, continuous); tool_page_line(&line_count, continuous); } 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_DIRS_ONLY)) { if (options & NDIR_OPT_SHORT) { if (file_count) fprintf(stdout, " %lu bytes, %d files, %lu blocks\n", total_bytes, file_count, total_blocks); } else { fprintf(stdout, "\n"); fprintf(stdout, "%12lu bytes in %5d files\n", total_bytes, file_count); fprintf(stdout, "%12lu bytes in %5lu blocks\n", total_blocks * 4096L, 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 (!ndir_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) && !ndir_is_dot_dir(ff.name)) { tool_join_path(subdir, dir, ff.name, sizeof(subdir)); tool_join_path(subspec, subdir, pattern, sizeof(subspec)); if (!(options & NDIR_OPT_SHORT) || 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; result = ndir_list_one(spec, options & ~NDIR_OPT_SUB, continuous); if (!(options & NDIR_OPT_SUB)) return(result); ndir_split_spec(spec, dir, pat); if (!pat[0]) strmaxcpy(pat, "*.*", sizeof(pat) - 1); return(ndir_list_subdirs(dir, pat, options, continuous) ? 1 : result); } int func_ndir(int argc, char *argv[], int mode) { char *path = "."; int have_path = 0; int options = 0; int continuous = 0; int i; (void)mode; if (argc > 1 && ndir_is_help(argv[1])) { ndir_usage(); return(0); } for (i = 1; i < argc; i++) { 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 ((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)); }