Subversion Repositories SvarDOS

Rev

Rev 2198 | Rev 2201 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
421 mateuszvis 1
/* This file is part of the SvarCOM project and is published under the terms
2
 * of the MIT license.
3
 *
1716 mateusz.vi 4
 * Copyright (C) 2021-2024 Mateusz Viste
421 mateuszvis 5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files (the "Software"),
8
 * to deal in the Software without restriction, including without limitation
9
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
 * and/or sell copies of the Software, and to permit persons to whom the
11
 * Software is furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
 * DEALINGS IN THE SOFTWARE.
23
 */
24
 
368 mateuszvis 25
/*
26
 * dir
27
 *
28
 * Displays a list of files and subdirectories in a directory.
29
 *
30
 * DIR [drive:][path][filename] [/P] [/W] [/A[:]attributes] [/O[[:]sortorder]] [/S] [/B] [/L]
31
 *
32
 * /P Pauses after each screenful of information.
33
 * /W Uses wide list format.
34
 *
35
 * /A Displays file with specified attributes:
36
 *     D Directories           R Read-only files     H Hidden files
37
 *     A Ready for archiving   S System files        - prefix meaning "not"
38
 *
39
 * /O List files in sorted order:
40
 *     N by name            S by size              E by extension
41
 *     D by date            G group dirs first     - prefix to reverse order
42
 *
43
 * /S Displays files in specified directory and all subdirectories.
44
 * /B Uses bare format (no heading information or summary)
45
 * /L Uses lowercases
2189 mateusz.vi 46
 *
47
 * about /S - recursive DIR on specified (or current) path and subdirectories:
48
 * prerequisite: some sort of mechanism that works as a stack pile of DTAs
49
 *
50
 * /S logic:
51
 * 1. do a FindFirst on current directory
52
 * 2. do FindNext calls in a loop, if a DIR entry is encountered, remember its
53
 *    name and put a copy of the current DTA on stack, then continue the
54
 *    listing without further interruption
55
 * 3. if a new DIR was discovered, do a FindFirst on it and jmp to 2.
56
 *    if no DIR found, then go to 4.
57
 * 4. look on the stack for a DTA.
58
 *    if any found, pop it and jmp to 2.
59
 *    otherwise job is done, exit.
368 mateuszvis 60
 */
61
 
396 mateuszvis 62
/* NOTE: /A attributes are matched in an exclusive way, ie. only files with
63
 *       the specified attributes are matched. This is different from how DOS
64
 *       itself matches attributes hence DIR cannot rely on the attributes
65
 *       filter within FindFirst.
66
 *
67
 * NOTE: Multiple /A are not supported - only the last one is significant.
68
 */
69
 
420 mateuszvis 70
 
1991 mateusz.vi 71
/* width of a column in wide mode output: 15 chars is the MINIMUM because
72
 * directories are enclosed in [BRACKETS] and they may have an extension, too.
73
 * Hence "[12345678.123]" is the longest we can get. Plus a delimiter space. */
74
#define WCOLWIDTH 15
424 mateuszvis 75
 
1991 mateusz.vi 76
 
1716 mateusz.vi 77
/* a "tiny" DTA is a DTA that is stripped from bytes that are not needed for
78
 * DIR operations */
79
_Packed struct TINYDTA {
80
/*  char reserved[21];
81
  unsigned char attr; */
82
  unsigned short time_sec2:5;
83
  unsigned short time_min:6;
84
  unsigned short time_hour:5;
85
  unsigned short date_dy:5;
86
  unsigned short date_mo:4;
87
  unsigned short date_yr:7;
88
  unsigned long size;
89
/*  char fname[13]; */
90
  char fname[12];
91
};
92
 
93
 
424 mateuszvis 94
/* fills freebytes with free bytes for drv (A=0, B=1, etc)
95
 * returns DOS ERR code on failure */
96
static unsigned short cmd_dir_df(unsigned long *freebytes, unsigned char drv) {
97
  unsigned short res = 0;
98
  unsigned short sects_per_clust = 0, avail_clusts = 0, bytes_per_sect = 0;
99
 
100
  _asm {
101
    push ax
102
    push bx
103
    push cx
104
    push dx
105
 
106
    mov ah, 0x36  /* DOS 2+ -- Get Disk Free Space */
107
    mov dl, [drv] /* A=1, B=2, etc (0 = DEFAULT DRIVE) */
108
    inc dl
109
    int 0x21      /* AX=sects_per_clust, BX=avail_clusts, CX=bytes_per_sect, DX=tot_clusters */
110
    cmp ax, 0xffff /* AX=0xffff on error (invalid drive) */
111
    jne COMPUTEDF
112
    mov [res], 0x0f /* fill res with DOS error code 15 ("invalid drive") */
113
    jmp DONE
114
 
115
    COMPUTEDF:
116
    /* freebytes = AX * BX * CX */
117
    mov [sects_per_clust], ax
118
    mov [avail_clusts], bx
119
    mov [bytes_per_sect], cx
120
 
121
    DONE:
122
    pop dx
123
    pop cx
124
    pop bx
125
    pop ax
126
  }
127
 
128
  /* multiple steps to avoid uint16 overflow */
129
  *freebytes = sects_per_clust;
130
  *freebytes *= avail_clusts;
131
  *freebytes *= bytes_per_sect;
132
 
133
  return(res);
134
}
135
 
136
 
528 mateuszvis 137
static void dir_pagination(unsigned short *availrows) {
138
  *availrows -= 1;
139
  if (*availrows == 0) {
140
    press_any_key();
141
    *availrows = screen_getheight() - 1;
142
  }
143
}
144
 
145
 
2193 mateusz.vi 146
/* add a new dirname to path, C:\XXX\*.EXE + YYY -> C:\XXX\YYY\*.EXE */
147
static void path_add(char *path, const char *dirname) {
2196 mateusz.vi 148
  short i, ostatni = -1;
2198 mateusz.vi 149
  //printf("path_add(%s,%s) -> ", path, dirname);
2193 mateusz.vi 150
  /* find the last backslash */
151
  for (i = 0; path[i] != 0; i++) {
152
    if (path[i] == '\\') ostatni = i;
153
  }
154
  /* abort on error */
155
  if (ostatni == -1) return;
156
  /* do the trick */
2196 mateusz.vi 157
  /* move ending to the right */
158
  memcpy_rtl(path + ostatni + strlen(dirname) + 1, path + ostatni, strlen(path + ostatni) + 1);
159
  /* fill in the space with dirname */
160
  memcpy_ltr(path + ostatni + 1, dirname, strlen(dirname));
2198 mateusz.vi 161
  //printf("'%s'\n", path);
2193 mateusz.vi 162
}
163
 
164
 
165
/* take back last dir from path, C:\XXX\YYY\*.EXE -> C:\XXX\*.EXE */
166
static void path_back(char *path) {
167
  short i, ostatni = -1, przedostatni = -1;
2198 mateusz.vi 168
  //printf("path_back(%s) -> ", path);
2193 mateusz.vi 169
  /* find the two last backslashes */
170
  for (i = 0; path[i] != 0; i++) {
171
    if (path[i] == '\\') {
172
      przedostatni = ostatni;
173
      ostatni = i;
174
    }
175
  }
176
  /* abort on error */
177
  if (przedostatni == -1) return;
178
  /* do the trick */
2196 mateusz.vi 179
  memcpy_ltr(path + przedostatni, path + ostatni, 1 + i - ostatni);
2198 mateusz.vi 180
  //printf("'%s'\n", path);
2193 mateusz.vi 181
}
182
 
183
 
542 mateuszvis 184
/* parse an attr list like "Ar-hS" and fill bitfield into attrfilter_may and attrfilter_must.
185
 * /AHS   -> adds S and H to mandatory attribs ("must")
186
 * /A-S   -> removes S from allowed attribs ("may")
187
 * returns non-zero on error. */
188
static int dir_parse_attr_list(const char *arg, unsigned char *attrfilter_may, unsigned char *attrfilter_must) {
189
  for (; *arg != 0; arg++) {
190
    unsigned char curattr;
191
    char not;
192
    if (*arg == '-') {
193
      not = 1;
194
      arg++;
195
    } else {
196
      not = 0;
197
    }
198
    switch (*arg) {
199
      case 'd':
200
      case 'D':
201
        curattr = DOS_ATTR_DIR;
202
        break;
203
      case 'r':
204
      case 'R':
205
        curattr = DOS_ATTR_RO;
206
        break;
207
      case 'a':
208
      case 'A':
209
        curattr = DOS_ATTR_ARC;
210
        break;
211
      case 'h':
212
      case 'H':
213
        curattr = DOS_ATTR_HID;
214
        break;
215
      case 's':
216
      case 'S':
217
        curattr = DOS_ATTR_SYS;
218
        break;
219
      default:
220
        return(-1);
221
    }
222
    /* update res bitfield */
223
    if (not) {
224
      *attrfilter_may &= ~curattr;
225
    } else {
226
      *attrfilter_must |= curattr;
227
    }
228
  }
229
  return(0);
230
}
231
 
232
 
1716 mateusz.vi 233
/* compare attributes in a DTA node to mandatory and optional attributes. returns 1 on match, 0 otherwise */
234
static int filter_attribs(const struct DTA *dta, unsigned char attrfilter_must, unsigned char attrfilter_may) {
235
  /* if mandatory attribs are requested, filter them now */
236
  if ((attrfilter_must & dta->attr) != attrfilter_must) return(0);
237
 
238
  /* if file contains attributes that are not allowed -> skip */
239
  if ((~attrfilter_may & dta->attr) != 0) return(0);
240
 
241
  return(1);
242
}
243
 
244
 
1719 mateusz.vi 245
static struct {
246
  struct TINYDTA far *dtabuf_root;
247
  char order[8]; /* GNESD values (ucase = lower first ; lcase = higher first) */
1739 mateusz.vi 248
  unsigned char sortownia[256]; /* collation table (used for NLS-aware sorts) */
1719 mateusz.vi 249
} glob_sortcmp_dat;
1716 mateusz.vi 250
 
1719 mateusz.vi 251
 
252
/* translates an order string like "GNE-S" into values fed into the order[]
253
 * table of glob_sortcmp_dat. returns 0 on success, non-zero otherwise. */
1724 mateusz.vi 254
static int dir_process_order_directive(const char *ordstring) {
1719 mateusz.vi 255
  const char *gnesd = "gnesd"; /* must be lower case */
256
  int ordi, orderi = 0, i;
257
 
258
  /* tabula rasa */
259
  glob_sortcmp_dat.order[0] = 0;
260
 
1721 mateusz.vi 261
  /* /O alone is a short hand for /OGN */
262
  if (*ordstring == 0) {
263
    glob_sortcmp_dat.order[0] = 'G';
264
    glob_sortcmp_dat.order[1] = 'N';
265
    glob_sortcmp_dat.order[2] = 0;
266
  }
267
 
1726 mateusz.vi 268
  /* stupid MSDOS compatibility ("DIR /O:GNE") */
269
  if (*ordstring == ':') ordstring++;
270
 
1719 mateusz.vi 271
  /* parsing */
272
  for (ordi = 0; ordstring[ordi] != 0; ordi++) {
273
    if (ordstring[ordi] == '-') {
274
      if ((ordstring[ordi + 1] == '-') || (ordstring[ordi + 1] == 0)) return(-1);
275
      continue;
276
    }
277
    if (orderi == sizeof(glob_sortcmp_dat.order)) return(-1);
278
 
279
    for (i = 0; gnesd[i] != 0; i++) {
280
      if ((ordstring[ordi] | 32) == gnesd[i]) { /* | 32 is lcase-ing the char */
281
        if ((ordi > 0) && (ordstring[ordi - 1] == '-')) {
282
          glob_sortcmp_dat.order[orderi] = gnesd[i];
283
        } else {
284
          glob_sortcmp_dat.order[orderi] = gnesd[i] ^ 32;
285
        }
286
        orderi++;
287
        break;
288
      }
289
    }
290
    if (gnesd[i] == 0) return(-1);
291
  }
292
 
293
  return(0);
294
}
295
 
296
 
297
static int sortcmp(const void *dtaid1, const void *dtaid2) {
298
  struct TINYDTA far *dta1 = &(glob_sortcmp_dat.dtabuf_root[*((unsigned short *)dtaid1)]);
299
  struct TINYDTA far *dta2 = &(glob_sortcmp_dat.dtabuf_root[*((unsigned short *)dtaid2)]);
300
  char *ordconf = glob_sortcmp_dat.order;
301
 
302
  /* debug stuff
303
  {
304
    int i;
305
    printf("%lu vs %lu | ", dta1->size, dta2->size);
306
    for (i = 0; dta1->fname[i] != 0; i++) printf("%c", dta1->fname[i]);
307
    printf(" vs ");
308
    for (i = 0; dta2->fname[i] != 0; i++) printf("%c", dta2->fname[i]);
309
    printf("\n");
310
  } */
311
 
312
  for (;;) {
313
    int r = -1;
314
    if (*ordconf & 32) r = 1;
315
 
316
    switch (*ordconf | 32) {
317
      case 'g': /* sort by type (directories first, then files) */
318
        if ((dta1->time_sec2 & DOS_ATTR_DIR) > (dta2->time_sec2 & DOS_ATTR_DIR)) return(0 - r);
319
        if ((dta1->time_sec2 & DOS_ATTR_DIR) < (dta2->time_sec2 & DOS_ATTR_DIR)) return(r);
320
        break;
321
      case ' ': /* default (last resort) sort: by name */
322
      case 'e': /* sort by extension */
323
      case 'n': /* sort by filename */
324
      {
325
        const char far *f1 = dta1->fname;
326
        const char far *f2 = dta2->fname;
327
        int i, limit = 12;
328
        /* special handling for '.' and '..' entries */
329
        if ((f1[0] == '.') && (f2[0] != '.')) return(0 - r);
330
        if ((f2[0] == '.') && (f1[0] != '.')) return(r);
331
 
332
        if ((*ordconf | 32) == 'e') {
333
          /* fast-forward to extension or end of filename */
334
          while ((*f1 != 0) && (*f1 != '.')) f1++;
335
          while ((*f2 != 0) && (*f2 != '.')) f2++;
336
          limit = 4; /* TINYDTA structs are not nul-terminated */
337
        }
338
        /* cmp */
339
        for (i = 0; i < limit; i++) {
1739 mateusz.vi 340
          if ((glob_sortcmp_dat.sortownia[(unsigned char)(*f1)]) < (glob_sortcmp_dat.sortownia[(unsigned char)(*f2)])) return(0 - r);
341
          if ((glob_sortcmp_dat.sortownia[(unsigned char)(*f1)]) > (glob_sortcmp_dat.sortownia[(unsigned char)(*f2)])) return(r);
1719 mateusz.vi 342
          if (*f1 == 0) break;
343
          f1++;
344
          f2++;
345
        }
346
      }
347
        break;
348
      case 's': /* sort by size */
349
        if (dta1->size > dta2->size) return(r);
350
        if (dta1->size < dta2->size) return(0 - r);
351
        break;
352
      case 'd': /* sort by date */
353
        if (dta1->date_yr < dta2->date_yr) return(0 - r);
354
        if (dta1->date_yr > dta2->date_yr) return(r);
355
        if (dta1->date_mo < dta2->date_mo) return(0 - r);
356
        if (dta1->date_mo > dta2->date_mo) return(r);
357
        if (dta1->date_dy < dta2->date_dy) return(0 - r);
358
        if (dta1->date_dy > dta2->date_dy) return(r);
359
        if (dta1->time_hour < dta2->time_hour) return(0 - r);
360
        if (dta1->time_hour > dta2->time_hour) return(r);
361
        if (dta1->time_min < dta2->time_min) return(0 - r);
362
        if (dta1->time_min > dta2->time_min) return(r);
363
        break;
364
    }
365
 
366
    if (*ordconf == 0) break;
367
    ordconf++;
368
  }
369
 
370
  return(0);
371
}
372
 
373
 
542 mateuszvis 374
#define DIR_ATTR_DEFAULT (DOS_ATTR_RO | DOS_ATTR_DIR | DOS_ATTR_ARC)
375
 
1724 mateusz.vi 376
struct dirrequest {
377
  unsigned char attrfilter_may;
378
  unsigned char attrfilter_must;
379
  const char *filespecptr;
420 mateuszvis 380
 
396 mateuszvis 381
  #define DIR_FLAG_PAUSE  1
382
  #define DIR_FLAG_RECUR  4
420 mateuszvis 383
  #define DIR_FLAG_LCASE  8
1719 mateusz.vi 384
  #define DIR_FLAG_SORT  16
1724 mateusz.vi 385
  unsigned char flags;
368 mateuszvis 386
 
420 mateuszvis 387
  #define DIR_OUTPUT_NORM 1
388
  #define DIR_OUTPUT_WIDE 2
389
  #define DIR_OUTPUT_BARE 3
1724 mateusz.vi 390
  unsigned char format;
391
};
420 mateuszvis 392
 
1719 mateusz.vi 393
 
1724 mateusz.vi 394
static int dir_parse_cmdline(struct dirrequest *req, const char **argv) {
395
  for (; *argv != NULL; argv++) {
396
    if (*argv[0] == '/') {
397
      const char *arg = *argv + 1;
396 mateuszvis 398
      char neg = 0;
399
      /* detect negations and get actual argument */
542 mateuszvis 400
      if (*arg == '-') {
401
        neg = 1;
402
        arg++;
403
      }
396 mateuszvis 404
      /* */
542 mateuszvis 405
      switch (*arg) {
396 mateuszvis 406
        case 'a':
407
        case 'A':
542 mateuszvis 408
          arg++;
409
          /* preset defaults */
1724 mateusz.vi 410
          req->attrfilter_may = DIR_ATTR_DEFAULT;
411
          req->attrfilter_must = 0;
542 mateuszvis 412
          /* /-A only allowed without further parameters (used to cancel possible previous /Asmth) */
413
          if (neg) {
414
            if (*arg != 0) {
415
              nls_outputnl_err(0, 2); /* invalid switch */
1724 mateusz.vi 416
              return(-1);
542 mateuszvis 417
            }
418
          } else {
1085 mateusz.vi 419
            /* skip colon if present */
420
            if (*arg == ':') arg++;
542 mateuszvis 421
            /* start with "allow everything" */
1724 mateusz.vi 422
            req->attrfilter_may = (DOS_ATTR_ARC | DOS_ATTR_DIR | DOS_ATTR_HID | DOS_ATTR_SYS | DOS_ATTR_RO);
423
            if (dir_parse_attr_list(arg, &(req->attrfilter_may), &(req->attrfilter_must)) != 0) {
542 mateuszvis 424
              nls_outputnl_err(0, 3); /* invalid parameter format */
1724 mateusz.vi 425
              return(-1);
542 mateuszvis 426
            }
427
          }
396 mateuszvis 428
          break;
399 mateuszvis 429
        case 'b':
430
        case 'B':
1724 mateusz.vi 431
          req->format = DIR_OUTPUT_BARE;
399 mateuszvis 432
          break;
421 mateuszvis 433
        case 'l':
434
        case 'L':
1724 mateusz.vi 435
          req->flags |= DIR_FLAG_LCASE;
420 mateuszvis 436
          break;
421 mateuszvis 437
        case 'o':
438
        case 'O':
1720 mateusz.vi 439
          if (neg) {
1724 mateusz.vi 440
            req->flags &= (0xff ^ DIR_FLAG_SORT);
1720 mateusz.vi 441
            break;
442
          }
1724 mateusz.vi 443
          if (dir_process_order_directive(arg+1) != 0) {
1719 mateusz.vi 444
            nls_output_err(0, 3); /* invalid parameter format */
445
            output(": ");
446
            outputnl(arg);
1724 mateusz.vi 447
            return(-1);
1719 mateusz.vi 448
          }
1724 mateusz.vi 449
          req->flags |= DIR_FLAG_SORT;
421 mateuszvis 450
          break;
396 mateuszvis 451
        case 'p':
452
        case 'P':
1724 mateusz.vi 453
          req->flags |= DIR_FLAG_PAUSE;
454
          if (neg) req->flags &= (0xff ^ DIR_FLAG_PAUSE);
396 mateuszvis 455
          break;
421 mateuszvis 456
        case 's':
457
        case 'S':
2193 mateusz.vi 458
          req->flags |= DIR_FLAG_RECUR;
420 mateuszvis 459
          break;
421 mateuszvis 460
        case 'w':
461
        case 'W':
1724 mateusz.vi 462
          req->format = DIR_OUTPUT_WIDE;
421 mateuszvis 463
          break;
393 mateuszvis 464
        default:
542 mateuszvis 465
          nls_outputnl_err(0, 2); /* invalid switch */
1724 mateusz.vi 466
          return(-1);
393 mateuszvis 467
      }
468
    } else {  /* filespec */
1724 mateusz.vi 469
      if (req->filespecptr != NULL) {
542 mateuszvis 470
        nls_outputnl_err(0, 4); /* too many parameters */
1724 mateusz.vi 471
        return(-1);
393 mateuszvis 472
      }
1724 mateusz.vi 473
      req->filespecptr = *argv;
393 mateuszvis 474
    }
475
  }
368 mateuszvis 476
 
1724 mateusz.vi 477
  return(0);
478
}
393 mateuszvis 479
 
1724 mateusz.vi 480
 
2193 mateusz.vi 481
#define MAX_SORTABLE_FILES 8192
482
 
1724 mateusz.vi 483
static enum cmd_result cmd_dir(struct cmd_funcparam *p) {
484
  struct DTA *dta = (void *)0x80; /* set DTA to its default location at 80h in PSP */
485
  struct TINYDTA far *dtabuf = NULL; /* used to buffer results when sorting is enabled */
486
  unsigned short dtabufcount = 0;
487
  unsigned short i;
488
  unsigned short availrows;  /* counter of available rows on display (used for /P) */
489
  unsigned short screenw = screen_getwidth();
490
  unsigned short wcols = screenw / WCOLWIDTH; /* number of columns in wide mode */
491
  unsigned char wcolcount;
492
  struct {
493
    struct nls_patterns nls;
494
    char buff64[64];
495
    char path[128];
2193 mateusz.vi 496
    struct DTA dtastack[64]; /* used for /S, max number of subdirs in DOS5 is 42 (A/B/C/...) */
497
    unsigned char dtastacklen;
498
    unsigned short orderidx[MAX_SORTABLE_FILES / sizeof(struct TINYDTA)];
499
  } *buf;
1724 mateusz.vi 500
  unsigned long summary_fcount = 0;
501
  unsigned long summary_totsz = 0;
502
  unsigned char drv = 0;
503
  struct dirrequest req;
2200 mateusz.vi 504
  unsigned short summary_alignpos;
505
  unsigned short uint32maxlen = 14; /* 13 is the max len of a 32 bit number with thousand separators (4'000'000'000) */
506
  if (screenw < 80) uint32maxlen = 10;
1724 mateusz.vi 507
 
508
  if (cmd_ishlp(p)) {
509
    nls_outputnl(37,0); /* "Displays a list of files and subdirectories in a directory" */
510
    outputnl("");
511
    nls_outputnl(37,1); /* "DIR [drive:][path][filename] [/P] [/W] [/A[:]attributes] [/O[[:]sortorder]] [/S] [/B] [/L]" */
512
    outputnl("");
513
    nls_outputnl(37,2); /* "/P Pauses after each screenful of information" */
514
    nls_outputnl(37,3); /* "/W Uses wide list format" */
515
    outputnl("");
516
    nls_outputnl(37,4); /* "/A Displays files with specified attributes:" */
517
    nls_outputnl(37,5); /* "    D Directories            R Read-only files        H Hidden files" */
518
    nls_outputnl(37,6); /* "    A Ready for archiving    S System files           - prefix meaning "not"" */
519
    outputnl("");
520
    nls_outputnl(37,7); /* "/O List files in sorted order:" */
521
    nls_outputnl(37,8); /* "    N by name                S by size                E by extension" */
522
    nls_outputnl(37,9); /* "    D by date                G group dirs first       - prefix to reverse order" */
523
    outputnl("");
524
    nls_outputnl(37,10); /* "/S Displays files in specified directory and all subdirectories" */
525
    nls_outputnl(37,11); /* "/B Uses bare format (no heading information or summary)" */
526
    nls_outputnl(37,12); /* "/L Uses lowercases" */
2193 mateusz.vi 527
    goto OK;
1724 mateusz.vi 528
  }
529
 
2193 mateusz.vi 530
  /* allocate buf */
531
  buf = calloc(sizeof(*buf), 1);
532
  if (buf == NULL) {
533
    nls_output_err(255, 8); /* insufficient memory */
534
    goto FAIL;
535
  }
536
 
1739 mateusz.vi 537
  /* zero out glob_sortcmp_dat and init the collation table */
538
  bzero(&glob_sortcmp_dat, sizeof(glob_sortcmp_dat));
539
  for (i = 0; i < 256; i++) {
540
    glob_sortcmp_dat.sortownia[i] = i;
541
    /* sorting should be case-insensitive */
1740 mateusz.vi 542
    if ((i >= 'A') && (i <= 'Z')) glob_sortcmp_dat.sortownia[i] |= 32;
1739 mateusz.vi 543
  }
544
 
1743 mateusz.vi 545
  /* try to replace (or complement) my naive collation table with an NLS-aware
1744 mateusz.vi 546
   * version provided by the kernel (or NLSFUNC)
1745 mateusz.vi 547
   * see https://github.com/SvarDOS/bugz/issues/68 for some thoughts */
548
  {
1743 mateusz.vi 549
    _Packed struct nlsseqtab {
550
      unsigned char id;
551
      unsigned short taboff;
552
      unsigned short tabseg;
553
    } collat;
554
    void *colptr = &collat;
555
    unsigned char errflag = 1;
556
    _asm {
557
      push ax
558
      push bx
559
      push cx
560
      push dx
561
      push di
562
      push es
563
 
564
      mov ax, 0x6506  /* DOS 3.3+ - Get collating sequence table */
565
      mov bx, 0xffff  /* code page, FFFFh = "current" */
566
      mov cx, 5       /* size of buffer at ES:DI */
567
      mov dx, 0xffff  /* country id, FFFFh = "current" */
568
      push ds
569
      pop es          /* ES:DI = address of buffer for the 5-bytes struct */
570
      mov di, colptr
571
      int 0x21
572
      jc FAIL
573
      xor al, al
574
      mov errflag, al
575
      FAIL:
576
 
577
      pop es
578
      pop di
579
      pop dx
580
      pop cx
581
      pop bx
582
      pop ax
583
    }
584
 
585
    if ((errflag == 0) && (collat.id == 6)) {
586
      unsigned char far *ptr = MK_FP(collat.tabseg, collat.taboff);
587
      unsigned short count = *(unsigned short far *)ptr;
1745 mateusz.vi 588
#ifdef DIR_DUMPNLSCOLLATE
589
      printf("NLS AT %04X:%04X (%u elements)\n", collat.tabseg, collat.taboff, count);
590
#endif
1743 mateusz.vi 591
      if (count <= 256) { /* you never know */
592
        ptr += 2; /* skip the count header */
593
        for (i = 0; i < count; i++) {
594
          glob_sortcmp_dat.sortownia[i] = ptr[i];
1745 mateusz.vi 595
#ifdef DIR_DUMPNLSCOLLATE
596
          printf(" %03u", ptr[i]);
597
          if ((i & 15) == 15) {
598
            printf("\n");
599
            fflush(stdout);
600
          }
601
#endif
1743 mateusz.vi 602
        }
603
      }
604
    }
605
  }
606
 
1724 mateusz.vi 607
  i = nls_getpatterns(&(buf->nls));
608
  if (i != 0) nls_outputnl_doserr(i);
609
 
610
  /* disable usage of thousands separator on narrow screens */
611
  if (screenw < 80) buf->nls.thousep[0] = 0;
612
 
1725 mateusz.vi 613
  /*** PARSING COMMAND LINE STARTS *******************************************/
614
 
615
  /* init req with some defaults */
616
  bzero(&req, sizeof(req));
617
  req.attrfilter_may = DIR_ATTR_DEFAULT;
618
  req.format = DIR_OUTPUT_NORM;
619
 
620
  /* process DIRCMD first (so it can be overidden by user's cmdline) */
621
  {
622
  const char far *dircmd = env_lookup_val(p->env_seg, "DIRCMD");
623
  if (dircmd != NULL) {
624
    const char *argvptrs[32];
625
    cmd_explode(buf->buff64, dircmd, argvptrs);
626
    if ((dir_parse_cmdline(&req, argvptrs) != 0) || (req.filespecptr != NULL)) {
627
      nls_output(255, 10);/* bad environment */
628
      output(" - ");
629
      outputnl("DIRCMD");
2193 mateusz.vi 630
      goto FAIL;
1725 mateusz.vi 631
    }
632
  }
633
  }
634
 
635
  /* parse user's command line */
2193 mateusz.vi 636
  if (dir_parse_cmdline(&req, p->argv) != 0) goto FAIL;
1724 mateusz.vi 637
 
2193 mateusz.vi 638
  /*** PARSING COMMAND LINE DONE *********************************************/
639
 
1725 mateusz.vi 640
  /* if no filespec provided, then it's about the current directory */
641
  if (req.filespecptr == NULL) req.filespecptr = ".";
642
 
528 mateuszvis 643
  availrows = screen_getheight() - 2;
644
 
417 mateuszvis 645
  /* special case: "DIR drive:" (truename() fails on "C:" under MS-DOS 6.0) */
1724 mateusz.vi 646
  if ((req.filespecptr[0] != 0) && (req.filespecptr[1] == ':') && (req.filespecptr[2] == 0)) {
647
    if ((req.filespecptr[0] >= 'a') && (req.filespecptr[0] <= 'z')) {
648
      buf->path[0] = req.filespecptr[0] - ('a' - 1);
417 mateuszvis 649
    } else {
1724 mateusz.vi 650
      buf->path[0] = req.filespecptr[0] - ('A' - 1);
399 mateuszvis 651
    }
1717 mateusz.vi 652
    i = curpathfordrv(buf->path, buf->path[0]);
417 mateuszvis 653
  } else {
1724 mateusz.vi 654
    i = file_truename(req.filespecptr, buf->path);
399 mateuszvis 655
  }
417 mateuszvis 656
  if (i != 0) {
538 mateuszvis 657
    nls_outputnl_doserr(i);
2193 mateusz.vi 658
    goto FAIL;
417 mateuszvis 659
  }
393 mateuszvis 660
 
2198 mateusz.vi 661
  /* volume label and serial */
1724 mateusz.vi 662
  if (req.format != DIR_OUTPUT_BARE) {
1717 mateusz.vi 663
    drv = buf->path[0];
399 mateuszvis 664
    if (drv >= 'a') {
665
      drv -= 'a';
666
    } else {
667
      drv -= 'A';
668
    }
1717 mateusz.vi 669
    cmd_vol_internal(drv, buf->buff64);
2198 mateusz.vi 670
  }
671
 
672
  NEXT_ITER: /* re-entry point for /S recursing */
673
 
674
  if (req.format != DIR_OUTPUT_BARE) {
1717 mateusz.vi 675
    sprintf(buf->buff64, svarlang_str(37,20)/*"Directory of %s"*/, buf->path);
399 mateuszvis 676
    /* trim at first '?', if any */
1717 mateusz.vi 677
    for (i = 0; buf->buff64[i] != 0; i++) if (buf->buff64[i] == '?') buf->buff64[i] = 0;
678
    outputnl(buf->buff64);
399 mateuszvis 679
    outputnl("");
528 mateuszvis 680
    availrows -= 3;
399 mateuszvis 681
  }
682
 
417 mateuszvis 683
  /* if dir: append a backslash (also get its len) */
1717 mateusz.vi 684
  i = path_appendbkslash_if_dir(buf->path);
393 mateuszvis 685
 
417 mateuszvis 686
  /* if ends with a \ then append ????????.??? */
1717 mateusz.vi 687
  if (buf->path[i - 1] == '\\') strcat(buf->path, "????????.???");
393 mateuszvis 688
 
2197 mateusz.vi 689
  /* ask DOS for list of files, but only with allowed attribs */
690
  i = findfirst(dta, buf->path, req.attrfilter_may);
417 mateuszvis 691
  if (i != 0) {
2197 mateusz.vi 692
    if (req.flags & DIR_FLAG_RECUR) goto CHECK_RECURS;
538 mateuszvis 693
    nls_outputnl_doserr(i);
2193 mateusz.vi 694
    goto FAIL;
417 mateuszvis 695
  }
696
 
1716 mateusz.vi 697
  /* if sorting is involved, then let's buffer all results (and sort them) */
1724 mateusz.vi 698
  if (req.flags & DIR_FLAG_SORT) {
1716 mateusz.vi 699
    /* allocate a memory buffer - try several sizes until one succeeds */
2194 mateusz.vi 700
    unsigned short max_dta_bufcount;
701
 
702
    /* compute the amount of DTAs I can buffer */
703
    for (max_dta_bufcount = MAX_SORTABLE_FILES; max_dta_bufcount != 0; max_dta_bufcount /= 2) {
704
      dtabuf = _fmalloc(max_dta_bufcount * sizeof(struct TINYDTA));
1716 mateusz.vi 705
      if (dtabuf != NULL) break;
706
    }
2194 mateusz.vi 707
    /* printf("max_dta_bufcount = %u\n", max_dta_bufcount); */
1716 mateusz.vi 708
 
709
    if (dtabuf == NULL) {
710
      nls_outputnl_doserr(8); /* out of memory */
2193 mateusz.vi 711
      goto FAIL;
1716 mateusz.vi 712
    }
713
 
714
    /* remember the address so I can free it afterwards */
1719 mateusz.vi 715
    glob_sortcmp_dat.dtabuf_root = dtabuf;
1716 mateusz.vi 716
 
717
    do {
718
      /* filter out files with uninteresting attributes */
1724 mateusz.vi 719
      if (filter_attribs(dta, req.attrfilter_must, req.attrfilter_may) == 0) continue;
1716 mateusz.vi 720
 
1719 mateusz.vi 721
      /* normalize "size" of directories to zero because kernel returns garbage
722
       * sizes for directories which might confuse the sorting routine later */
723
      if (dta->attr & DOS_ATTR_DIR) dta->size = 0;
724
 
1716 mateusz.vi 725
      _fmemcpy(&(dtabuf[dtabufcount]), ((char *)dta) + 22, sizeof(struct TINYDTA));
726
 
727
      /* save attribs in sec field, otherwise zero it (this field is not
728
       * displayed and dropping the attr field saves 2 bytes per entry) */
729
      dtabuf[dtabufcount++].time_sec2 = (dta->attr & 31);
730
 
731
      /* do I have any space left? */
732
      if (dtabufcount == max_dta_bufcount) {
1719 mateusz.vi 733
        //TODO some kind of user notification might be nice here
1716 mateusz.vi 734
        //outputnl("TOO MANY ENTRIES FOR SORTING! LIST IS UNSORTED");
735
        break;
736
      }
737
 
738
    } while (findnext(dta) == 0);
739
 
1742 mateusz.vi 740
    /* no match? kein gluck! (this can happen when filtering attribs with /A:xxx
741
     * because while findfirst() succeeds, all entries can be rejected) */
742
    if (dtabufcount == 0) {
743
      nls_outputnl_doserr(2); /* "File not found" */
2193 mateusz.vi 744
      goto FAIL;
1742 mateusz.vi 745
    }
746
 
1716 mateusz.vi 747
    /* sort the list - the tricky part is that my array is a far address while
1719 mateusz.vi 748
     * qsort works only with near pointers, so I have to use an ugly (and
749
     * global) auxiliary table */
750
    for (i = 0; i < dtabufcount; i++) buf->orderidx[i] = i;
751
    qsort(buf->orderidx, dtabufcount, 2, &sortcmp);
1716 mateusz.vi 752
 
1719 mateusz.vi 753
    /* preload first entry (last from orderidx, since entries are sorted in reverse) */
1716 mateusz.vi 754
    dtabufcount--;
1719 mateusz.vi 755
    _fmemcpy(((unsigned char *)dta) + 22, &(dtabuf[buf->orderidx[dtabufcount]]), sizeof(struct TINYDTA));
756
    dta->attr = dtabuf[buf->orderidx[dtabufcount]].time_sec2; /* restore attr from the abused time_sec2 field */
1716 mateusz.vi 757
  }
758
 
420 mateuszvis 759
  wcolcount = 0; /* may be used for columns counting with wide mode */
396 mateuszvis 760
 
1716 mateusz.vi 761
  for (;;) {
542 mateuszvis 762
 
1716 mateusz.vi 763
    /* filter out attributes (skip if entry comes from buffer, then it was already veted) */
1741 mateusz.vi 764
    if (filter_attribs(dta, req.attrfilter_must, req.attrfilter_may) == 0) goto NEXT_ENTRY;
542 mateuszvis 765
 
766
    /* turn string lcase (/L) */
1724 mateusz.vi 767
    if (req.flags & DIR_FLAG_LCASE) _strlwr(dta->fname); /* OpenWatcom extension, probably does not care about NLS so results may be odd with non-A-Z characters... */
368 mateuszvis 768
 
424 mateuszvis 769
    summary_fcount++;
770
    if ((dta->attr & DOS_ATTR_DIR) == 0) summary_totsz += dta->size;
771
 
1724 mateusz.vi 772
    switch (req.format) {
420 mateuszvis 773
      case DIR_OUTPUT_NORM:
774
        /* print fname-space-extension (unless it's "." or "..", then print as-is) */
775
        if (dta->fname[0] == '.') {
776
          output(dta->fname);
777
          i = strlen(dta->fname);
778
          while (i++ < 12) output(" ");
779
        } else {
1717 mateusz.vi 780
          file_fname2fcb(buf->buff64, dta->fname);
781
          memmove(buf->buff64 + 9, buf->buff64 + 8, 4);
782
          buf->buff64[8] = ' ';
783
          output(buf->buff64);
420 mateuszvis 784
        }
785
        output(" ");
1960 mateusz.vi 786
        /* either <DIR> or right aligned 13 or 10 chars byte size, depending
787
         * on the presence of a thousands delimiter (max 2'000'000'000) */
788
        {
789
          unsigned short szlen = 10 + (strlen(buf->nls.thousep) * 3);
790
          memset(buf->buff64, ' ', 16);
791
          if (dta->attr & DOS_ATTR_DIR) {
792
            strcpy(buf->buff64 + szlen, svarlang_str(37,21));
793
          } else {
794
            nls_format_number(buf->buff64 + 12, dta->size, &(buf->nls));
795
          }
796
          output(buf->buff64 + strlen(buf->buff64) - szlen);
420 mateuszvis 797
        }
1960 mateusz.vi 798
        /* one spaces and NLS DATE */
1717 mateusz.vi 799
        buf->buff64[0] = ' ';
1141 mateusz.vi 800
        if (screenw >= 80) {
1960 mateusz.vi 801
          nls_format_date(buf->buff64 + 1, dta->date_yr + 1980, dta->date_mo, dta->date_dy, &(buf->nls));
1141 mateusz.vi 802
        } else {
1960 mateusz.vi 803
          nls_format_date(buf->buff64 + 1, (dta->date_yr + 80) % 100, dta->date_mo, dta->date_dy, &(buf->nls));
1141 mateusz.vi 804
        }
1717 mateusz.vi 805
        output(buf->buff64);
420 mateuszvis 806
 
807
        /* one space and NLS TIME */
1717 mateusz.vi 808
        nls_format_time(buf->buff64 + 1, dta->time_hour, dta->time_min, 0xff, &(buf->nls));
809
        outputnl(buf->buff64);
420 mateuszvis 810
        break;
811
 
812
      case DIR_OUTPUT_WIDE: /* display in columns of 12 chars per item */
813
        i = strlen(dta->fname);
814
        if (dta->attr & DOS_ATTR_DIR) {
815
          i += 2;
816
          output("[");
817
          output(dta->fname);
818
          output("]");
819
        } else {
820
          output(dta->fname);
821
        }
822
        while (i++ < WCOLWIDTH) output(" ");
823
        if (++wcolcount == wcols) {
824
          wcolcount = 0;
825
          outputnl("");
528 mateuszvis 826
        } else {
827
          availrows++; /* wide mode is the only one that does not write one line per file */
420 mateuszvis 828
        }
829
        break;
830
 
831
      case DIR_OUTPUT_BARE:
832
        outputnl(dta->fname);
833
        break;
396 mateuszvis 834
    }
368 mateuszvis 835
 
1724 mateusz.vi 836
    if (req.flags & DIR_FLAG_PAUSE) dir_pagination(&availrows);
420 mateuszvis 837
 
1741 mateusz.vi 838
    NEXT_ENTRY:
1716 mateusz.vi 839
    /* take next entry, either from buf or disk */
840
    if (dtabufcount > 0) {
841
      dtabufcount--;
1719 mateusz.vi 842
      _fmemcpy(((unsigned char *)dta) + 22, &(dtabuf[buf->orderidx[dtabufcount]]), sizeof(struct TINYDTA));
843
      dta->attr = dtabuf[buf->orderidx[dtabufcount]].time_sec2; /* restore attr from the abused time_sec2 field */
1716 mateusz.vi 844
    } else {
845
      if (findnext(dta) != 0) break;
846
    }
420 mateuszvis 847
 
1716 mateusz.vi 848
  }
849
 
528 mateuszvis 850
  if (wcolcount != 0) {
851
    outputnl(""); /* in wide mode make sure to end on a clear row */
1724 mateusz.vi 852
    if (req.flags & DIR_FLAG_PAUSE) dir_pagination(&availrows);
528 mateuszvis 853
  }
420 mateuszvis 854
 
424 mateuszvis 855
  /* print out summary (unless bare output mode) */
1724 mateusz.vi 856
  if (req.format != DIR_OUTPUT_BARE) {
1960 mateusz.vi 857
    /* x file(s) (maximum of files in a FAT-32 directory is 65'535) */
858
    memset(buf->buff64, ' ', 8);
859
    i = nls_format_number(buf->buff64 + 8, summary_fcount, &(buf->nls));
2200 mateusz.vi 860
    summary_alignpos = sprintf(buf->buff64 + 8 + i, " %s ", svarlang_str(37,22)/*"file(s)"*/);
1717 mateusz.vi 861
    output(buf->buff64 + i);
424 mateuszvis 862
    /* xxxx bytes */
1960 mateusz.vi 863
    memset(buf->buff64, ' ', 14);
1717 mateusz.vi 864
    i = nls_format_number(buf->buff64 + uint32maxlen, summary_totsz, &(buf->nls));
865
    output(buf->buff64 + i + 1);
424 mateuszvis 866
    output(" ");
990 mateusz.vi 867
    nls_outputnl(37,23); /* "bytes" */
1724 mateusz.vi 868
    if (req.flags & DIR_FLAG_PAUSE) dir_pagination(&availrows);
424 mateuszvis 869
  }
870
 
2193 mateusz.vi 871
  /* /S processing */
2197 mateusz.vi 872
  CHECK_RECURS:
873
  /* if /S then look for a subdir */
874
  if (req.flags & DIR_FLAG_RECUR) {
875
    /* do the findfirst on *.* instead of reusing the user filter */
876
    char *s;
877
    char backup[4];
2200 mateusz.vi 878
    //printf("orig path='%s' new=", buf->path);
2197 mateusz.vi 879
    for (s = buf->path; *s != 0; s++);
880
    for (; s[-1] != '\\'; s--);
881
    memcpy_ltr(backup, s, 4);
882
    memcpy_ltr(s, "*.*", 4);
2200 mateusz.vi 883
    //printf("'%s'\n", buf->path);
2197 mateusz.vi 884
    if (findfirst(dta, buf->path, DOS_ATTR_DIR) == 0) {
885
      memcpy_ltr(s, backup, 4);
886
      for (;;) {
887
        if ((dta->fname[0] != '.') && (dta->attr & DOS_ATTR_DIR)) break;
888
        if (findnext(dta) != 0) goto NOSUBDIR;
889
      }
2200 mateusz.vi 890
      //printf("GOT DIR (/S): '%s'\n", dta->fname);
2197 mateusz.vi 891
      /* add dir to path and redo scan */
892
      memcpy_ltr(&(buf->dtastack[buf->dtastacklen]), dta, sizeof(struct DTA));
893
      buf->dtastacklen++;
894
      path_add(buf->path, dta->fname);
895
      goto NEXT_ITER;
896
    }
897
    memcpy_ltr(s, backup, 4);
2193 mateusz.vi 898
  }
2197 mateusz.vi 899
  NOSUBDIR:
900
 
2193 mateusz.vi 901
  while (buf->dtastacklen > 0) {
902
    /* rewind path one directory back, pop the next dta and do a FindNext */
903
    path_back(buf->path);
904
    buf->dtastacklen--;
905
    TRYNEXTENTRY:
906
    if (findnext(&(buf->dtastack[buf->dtastacklen])) != 0) continue;
907
    if ((buf->dtastack[buf->dtastacklen].attr & DOS_ATTR_DIR) == 0) goto TRYNEXTENTRY;
2200 mateusz.vi 908
    if (buf->dtastack[buf->dtastacklen].fname[0] == '.') goto TRYNEXTENTRY;
2193 mateusz.vi 909
    /* something found -> add dir to path and redo scan */
910
    path_add(buf->path, buf->dtastack[buf->dtastacklen].fname);
911
    goto NEXT_ITER;
912
  }
913
 
2200 mateusz.vi 914
  /* print out disk space available (unless bare output mode) */
915
  if (req.format != DIR_OUTPUT_BARE) {
916
    /* xxxx bytes free */
917
    i = cmd_dir_df(&summary_totsz, drv);
918
    if (i != 0) nls_outputnl_doserr(i);
919
    memset(buf->buff64, ' ', summary_alignpos + 8 + uint32maxlen); /* align the freebytes value to same column as totbytes */
920
    i = nls_format_number(buf->buff64 + summary_alignpos + 8 + uint32maxlen, summary_totsz, &(buf->nls));
921
    output(buf->buff64 + i + 1);
922
    output(" ");
923
    nls_outputnl(37,24); /* "bytes free" */
924
    if (req.flags & DIR_FLAG_PAUSE) dir_pagination(&availrows);
925
  }
926
 
1716 mateusz.vi 927
  /* free the buffer memory (if used) */
1719 mateusz.vi 928
  if (glob_sortcmp_dat.dtabuf_root != NULL) _ffree(glob_sortcmp_dat.dtabuf_root);
1716 mateusz.vi 929
 
2193 mateusz.vi 930
  FAIL:
931
  free(buf);
932
  return(CMD_FAIL);
933
 
934
  OK:
935
  free(buf);
533 mateuszvis 936
  return(CMD_OK);
368 mateuszvis 937
}