Subversion Repositories SvarDOS

Rev

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