Subversion Repositories SvarDOS

Rev

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