Subversion Repositories SvarDOS

Rev

Rev 2216 | 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
 *
1736 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
 
403 mateuszvis 25
/*
26
 * copy
27
 */
28
 
29
/* /A - Used to copy ASCII files. Applies to the filename preceding it and to
30
 * all following filenames. Files will be copied until an end-of-file mark is
31
 * encountered in the file being copied. If an end-of-file mark is encountered
32
 * in the file, the rest of the file is not copied. DOS will append an EOF
33
 * mark at the end of the copied file.
34
 *
35
 * /B - Used to copy binary files. Applies to the filename preceding it and to
36
 * all following filenames. Copied files will be read by size (according to
37
 * the number of bytes indicated in the file`s directory listing). An EOF mark
38
 * is not placed at the end of the copied file.
39
 *
40
 * /V - Checks after the copy to assure that a file was copied correctly. If
41
 * the copy cannot be verified, the program will display an error message.
42
 * Using this option will result in a slower copying process.
409 mateuszvis 43
 *
44
 * special case: "COPY A+B+C+D" means "append B, C and D files to the A file"
45
 * if A does not exist, then "append C and D to B", etc.
403 mateuszvis 46
 */
47
 
48
struct copy_setup {
49
  const char *src[64];
50
  unsigned short src_count; /* how many sources are declared */
503 mateuszvis 51
  char cursrc[256];         /* buffer for currently processed src */
409 mateuszvis 52
  char dst[256];
53
  unsigned short dstlen;
403 mateuszvis 54
  char src_asciimode[64];
55
  char dst_asciimode;
56
  char last_asciimode; /* /A or /B impacts the file preceding it and becomes the new default for all files that follow */
57
  char verifyflag;
58
  char lastitemwasplus;
501 mateuszvis 59
  unsigned short databufsz;
2165 mateusz.vi 60
  unsigned short databufseg_custom; /* seg of buffer if dynamically allocated (0 otherwise) */
501 mateuszvis 61
  char databuf[1];
403 mateuszvis 62
};
63
 
409 mateuszvis 64
 
2165 mateusz.vi 65
static void dos_freememseg(unsigned short seg);
66
#pragma aux dos_freememseg = \
67
"mov ah, 0x49" \
68
"int 0x21" \
69
modify [ax] \
70
parm [es]
71
 
72
 
2175 mateusz.vi 73
/* returns 0 on success */
74
static unsigned short dos_delfile(const char *f);
75
#pragma aux dos_delfile = \
76
"mov ah, 0x41" \
77
"xor cx, cx" \
78
"int 0x21" \
79
"jc DONE" \
80
"xor ax, ax" \
81
"DONE:" \
82
modify [cx] \
83
parm [dx] \
84
value [ax]
85
 
86
 
412 mateuszvis 87
/* copies src to dst, overwriting or appending to the destination.
88
 * - copy is performed in ASCII mode if asciiflag set (stop at first EOF in src
89
 *   and append an EOF in dst).
90
 * - returns zero on success, DOS error code on error */
2165 mateusz.vi 91
static unsigned short cmd_copy_internal(const char *dst, char dstascii, const char *src, char srcascii, unsigned char appendflag, void far *buff, unsigned short buffsz) {
412 mateuszvis 92
  unsigned short errcode = 0;
93
  unsigned short srch = 0xffff, dsth = 0xffff;
2165 mateusz.vi 94
  unsigned short buffseg = FP_SEG(buff);
95
  unsigned short buffoff =  FP_OFF(buff);
96
 
412 mateuszvis 97
  _asm {
2175 mateusz.vi 98
    push ax
99
    push bx
100
    push cx
101
    push dx
2165 mateusz.vi 102
    push ds
412 mateuszvis 103
 
104
    /* open src */
105
    OPENSRC:
106
    mov ax, 0x3d00 /* DOS 2+ -- open an existing file, read access mode */
107
    mov dx, src    /* ASCIIZ fname */
108
    int 0x21       /* CF clear on success, handle in AX */
502 mateuszvis 109
    jc FAIL
412 mateuszvis 110
    mov [srch], ax /* store src handle in memory */
111
 
112
    /* check appendflag so I know if I have to try opening dst for append */
113
    xor al, al
114
    or al, [appendflag]
115
    jz CREATEDST
116
 
117
    /* try opening dst first if appendflag set */
118
    mov ax, 0x3d01 /* DOS 2+ -- open an existing file, write access mode */
119
    mov dx, dst    /* ASCIIZ fname */
120
    int 0x21       /* CF clear on success, handle in AX */
121
    jc CREATEDST   /* failed to open file (file does not exist) */
122
    mov [dsth], ax /* store dst handle in memory */
123
 
124
    /* got file open, LSEEK to end of it now so future data is appended */
125
    mov bx, ax     /* file handle in BX (was still in AX) */
126
    mov ax, 0x4202 /* DOS 2+ -- set file pointer to end of file + CX:DX */
127
    xor cx, cx     /* offset zero */
128
    xor dx, dx     /* offset zero */
129
    int 0x21       /* CF set on error */
130
    jc FAIL
2174 mateusz.vi 131
    jmp short COPY
412 mateuszvis 132
 
133
    /* create dst */
134
    CREATEDST:
135
    mov ah, 0x3c   /* DOS 2+ -- create a file */
136
    mov dx, dst
137
    xor cx, cx     /* zero out attributes */
138
    int 0x21       /* handle in AX on success, CF set on error */
139
    jc FAIL
140
    mov [dsth], ax /* store dst handle in memory */
141
 
142
    /* perform actual copy */
143
    COPY:
2165 mateusz.vi 144
    mov ds, buffseg
145
    COPY_LOOP:
412 mateuszvis 146
    /* read a block from src */
147
    mov ah, 0x3f   /* DOS 2+ -- read from file */
148
    mov bx, [srch]
149
    mov cx, [buffsz]
2165 mateusz.vi 150
    mov dx, [buffoff] /* DX points to buffer */
412 mateuszvis 151
    int 0x21       /* CF set on error, bytes read in AX (0=EOF) */
152
    jc FAIL        /* abort on error */
153
    /* EOF? (ax == 0) */
413 mateuszvis 154
    test ax, ax
155
    jz ENDOFFILE
412 mateuszvis 156
    /* write block of AX bytes to dst */
157
    mov cx, ax     /* block length */
158
    mov ah, 0x40   /* DOS 2+ -- write to file (CX bytes from DS:DX) */
159
    mov bx, [dsth] /* file handle */
2165 mateusz.vi 160
    /* mov dx, buffoff */ /* DX points to buffer already */
412 mateuszvis 161
    int 0x21       /* CF clear and AX=CX on success */
162
    jc FAIL
1736 mateusz.vi 163
    cmp ax, cx     /* should be equal, otherwise failed */
2175 mateusz.vi 164
    mov ax, 0x27   /* preset to DOS error "Insufficient disk space" */
2165 mateusz.vi 165
    je COPY_LOOP
166
    jmp short FAIL
412 mateuszvis 167
 
168
    ENDOFFILE:
413 mateuszvis 169
    /* if dst ascii mode -> add an EOF (ASCII mode not supported for the time being) */
412 mateuszvis 170
 
2174 mateusz.vi 171
    jmp short CLOSESRC
412 mateuszvis 172
 
173
    FAIL:
174
    mov [errcode], ax
175
 
1736 mateusz.vi 176
    /* close src and dst, but first take care to clone the timestamp to dst */
412 mateuszvis 177
    CLOSESRC:
178
    mov bx, [srch]
1736 mateusz.vi 179
    cmp bx, 0xffff /* skip if not a file */
412 mateuszvis 180
    je CLOSEDST
1736 mateusz.vi 181
    mov ax, 0x5700 /* DOS 2+ - GET FILE'S LAST-WRITTEN DATE AND TIME */
182
    int 0x21  /* time and date are in CX and DX now */
183
    /* proceed with closing the file */
184
    /* mov bx, [srch] */
412 mateuszvis 185
    mov ah, 0x3e   /* DOS 2+ -- close a file handle */
186
    int 0x21
187
 
188
    CLOSEDST:
189
    mov bx, [dsth]
190
    cmp bx, 0xffff
191
    je DONE
2175 mateusz.vi 192
    /* set timestamp, unless an error occured or operation was appending */
193
    xor ax, ax
194
    cmp [errcode], ax
195
    jne SKIPDATESAVE
1736 mateusz.vi 196
    /* skip timesetting also if appending */
197
    cmp [appendflag], al
198
    jne SKIPDATESAVE
199
    /* do the job */
200
    mov ax, 0x5701 /* DOS 2+ - SET FILE'S LAST-WRITTEN DATE AND TIME */
201
    /* BX=file handle  CX=TIME  DX=DATE */
202
    int 0x21
203
    SKIPDATESAVE:
412 mateuszvis 204
    mov ah, 0x3e   /* DOS 2+ -- close a file handle */
205
    int 0x21
206
 
2175 mateusz.vi 207
    /* remove dst file on error (remember DS still points to databuff) */
208
    cmp word ptr [errcode], 0
209
    jz DONE
210
    mov ah, 0x41
211
    mov dx, dst
212
    xor cx, cx
213
    pop ds
214
    int 0x21
215
    push ds
216
 
412 mateuszvis 217
    DONE:
2165 mateusz.vi 218
 
219
    pop ds
2175 mateusz.vi 220
    pop dx
221
    pop cx
222
    pop bx
223
    pop ax
412 mateuszvis 224
  }
225
  return(errcode);
226
}
227
 
228
 
533 mateuszvis 229
static enum cmd_result cmd_copy(struct cmd_funcparam *p) {
403 mateuszvis 230
  struct copy_setup *setup = (void *)(p->BUFFER);
231
  unsigned short i;
409 mateuszvis 232
  unsigned short copiedcount_in = 0, copiedcount_out = 0; /* number of input/output copied files */
233
  struct DTA *dta = (void *)0x80; /* use DTA at default location in PSP */
403 mateuszvis 234
 
235
  if (cmd_ishlp(p)) {
990 mateusz.vi 236
    nls_outputnl(38,0); /* "Copies one or more files to another location." */
403 mateuszvis 237
    outputnl("");
990 mateusz.vi 238
    nls_outputnl(38,1); /* "COPY [/A|/B] source [/A|/B] [+source [/A|/B] [+...]] [destination [/A|/B]] [/V]" */
403 mateuszvis 239
    outputnl("");
990 mateusz.vi 240
    nls_outputnl(38,2); /* "source       Specifies the file or files to be copied" */
241
    nls_outputnl(38,3); /* "/A           Indicates an ASCII text file" */
242
    nls_outputnl(38,4); /* "/B           Indicates a binary file" */
243
    nls_outputnl(38,5); /* "destination  Specifies the directory and/or filename for the new file(s)" */
244
    nls_outputnl(38,6); /* "/V           Verifies that new files are written correctly" */
403 mateuszvis 245
    outputnl("");
990 mateusz.vi 246
    nls_outputnl(38,7); /* "To append files, specify a single file for destination, but multiple (...)" */
413 mateuszvis 247
    outputnl("");
990 mateusz.vi 248
    nls_outputnl(38,8); /* "NOTE: /A and /B are no-ops, provided only for compatibility reasons" */
533 mateuszvis 249
    return(CMD_OK);
403 mateuszvis 250
  }
251
 
252
  /* parse cmdline and fill the setup struct accordingly */
253
 
2213 mateusz.vi 254
  sv_bzero(setup, sizeof(*setup));
2188 mateusz.vi 255
  setup->databufsz = (p->BUFFERSZ - sizeof(*setup)) & 0xfe00; /* use a multiple of 512 for better DOS performances */
403 mateuszvis 256
 
257
  for (i = 0; i < p->argc; i++) {
258
 
259
    /* switch? */
260
    if (p->argv[i][0] == '/') {
261
      if ((imatch(p->argv[i], "/a")) || (imatch(p->argv[i], "/b"))) {
262
        setup->last_asciimode = 'b';
263
        if (imatch(p->argv[i], "/a")) setup->last_asciimode = 'a';
264
        /* */
409 mateuszvis 265
        if (setup->dst[0] != 0) {
403 mateuszvis 266
          setup->dst_asciimode = setup->last_asciimode;
267
        } else if (setup->src_count != 0) {
268
          setup->src_asciimode[setup->src_count - 1] = setup->last_asciimode;
269
        }
270
      } else if (imatch(p->argv[i], "/v")) {
271
        setup->verifyflag = 1;
272
      } else {
990 mateusz.vi 273
        nls_outputnl(0,2); /* "Invalid switch" */
533 mateuszvis 274
        return(CMD_FAIL);
403 mateuszvis 275
      }
276
      continue;
277
    }
278
 
279
    /* not a switch - must be either a source, a destination or a + */
280
    if (p->argv[i][0] == '+') {
281
      /* a plus cannot appear after destination or before first source */
409 mateuszvis 282
      if ((setup->dst[0] != 0) || (setup->src_count == 0)) {
990 mateusz.vi 283
        nls_outputnl(0,1); /* "Invalid syntax" */
533 mateuszvis 284
        return(CMD_FAIL);
403 mateuszvis 285
      }
286
      setup->lastitemwasplus = 1;
287
      /* a plus may be immediately followed by a filename - if so, emulate
288
       * a new argument */
289
      if (p->argv[i][1] != 0) {
290
        p->argv[i] += 1;
291
        i--;
292
      }
293
      continue;
294
    }
295
 
296
    /* src? (first non-switch or something that follows a +) */
297
    if ((setup->lastitemwasplus) || (setup->src_count == 0)) {
298
      setup->src[setup->src_count] = p->argv[i];
299
      setup->src_asciimode[setup->src_count] = setup->last_asciimode;
300
      setup->src_count++;
301
      setup->lastitemwasplus = 0;
302
      continue;
303
    }
304
 
305
    /* must be a dst then */
409 mateuszvis 306
    if (setup->dst[0] != 0) {
990 mateusz.vi 307
      nls_outputnl(0,1); /* "Invalid syntax" */
533 mateuszvis 308
      return(CMD_FAIL);
403 mateuszvis 309
    }
409 mateuszvis 310
    if (file_truename(p->argv[i], setup->dst) != 0) {
990 mateusz.vi 311
      nls_outputnl(0,8); /* "Invalid destination" */
533 mateuszvis 312
      return(CMD_FAIL);
409 mateuszvis 313
    }
403 mateuszvis 314
    setup->dst_asciimode = setup->last_asciimode;
409 mateuszvis 315
    /* if dst is a directory then append a backslash */
415 mateuszvis 316
    setup->dstlen = path_appendbkslash_if_dir(setup->dst);
403 mateuszvis 317
  }
318
 
319
  /* DEBUG: output setup content ("if 1" to enable) */
418 mateuszvis 320
  #if 0
403 mateuszvis 321
  printf("src: ");
322
  for (i = 0; i < setup->src_count; i++) {
323
    if (i != 0) printf(", ");
324
    printf("%s [%c]", setup->src[i], setup->src_asciimode[i]);
325
  }
326
  printf("\r\n");
327
  printf("dst: %s [%c]\r\n", setup->dst, setup->dst_asciimode);
328
  printf("verify: %s\r\n", (setup->verifyflag)?"ON":"OFF");
329
  #endif
330
 
409 mateuszvis 331
  /* must have at least one source */
332
  if (setup->src_count == 0) {
990 mateusz.vi 333
    nls_outputnl(0,7); /* "Required parameter missing" */
533 mateuszvis 334
    return(CMD_FAIL);
409 mateuszvis 335
  }
403 mateuszvis 336
 
2165 mateusz.vi 337
  /* alloc as much memory as possible */
338
  {
339
    unsigned short alloc_size = 0;
340
    unsigned short alloc_seg = 0;
341
    _asm {
342
      push ax
343
      push bx
344
      push dx
345
 
346
      mov ah, 0x48  /* DOS 2+ allocate memory */
2188 mateusz.vi 347
      mov bx, 4064  /* number of segments to allocate (4064 segs = 127 * 512 bytes) */
2165 mateusz.vi 348
      mov dx, bx    /* my own variable to hold result */
349
      int 0x21      /* on success AX=segment, on error AX=max number of segments */
350
      jnc DONE
351
      TRY_AGAIN:
2188 mateusz.vi 352
      /* ask again, this time using the max segments value obtained earlier
353
       * (but truncated to a multiple of 512 for better copy performances) */
354
      and ax, 0xffe0 /* it's segments, not bytes (32 segs = 512 bytes) */
2165 mateusz.vi 355
      mov bx, ax
356
      mov dx, bx
357
      mov ah, 0x48
358
      int 0x21
359
      jnc DONE
360
      xor dx, dx
361
      DONE:
362
      /* dx = size ; ax = segment (error if dx = 0) */
363
      test dx, dx
364
      jz FINITO
365
      mov alloc_seg, ax
366
      mov alloc_size, dx
367
      FINITO:
368
 
369
      pop dx
370
      pop bx
371
      pop ax
372
    }
373
 
374
    if (alloc_size != 0) {
375
      /* if dos malloc returned less than the buffer I already have then free it */
376
      if (alloc_size * 16 < setup->databufsz) {
377
        dos_freememseg(alloc_seg);
378
      } else {
379
        setup->databufseg_custom = alloc_seg;
380
        setup->databufsz = alloc_size * 16;
381
      }
382
    }
383
  }
384
 
409 mateuszvis 385
  /* perform the operation based on setup directives:
386
   * iterate over every source and copy it to dest */
387
 
388
  for (i = 0; i < setup->src_count; i++) {
389
    unsigned short t;
503 mateuszvis 390
    unsigned short cursrclen;
409 mateuszvis 391
    unsigned short pathendoffset;
392
 
393
    /* resolve truename of src and write it to buffer */
503 mateuszvis 394
    t = file_truename(setup->src[i], setup->cursrc);
409 mateuszvis 395
    if (t != 0) {
396
      output(setup->src[i]);
397
      output(" - ");
538 mateuszvis 398
      nls_outputnl_doserr(t);
409 mateuszvis 399
      continue;
400
    }
2214 mateusz.vi 401
    cursrclen = sv_strlen(setup->cursrc); /* remember cursrc length */
409 mateuszvis 402
 
403
    /* if length zero, skip (not sure why this would be possible, though) */
503 mateuszvis 404
    if (cursrclen == 0) continue;
409 mateuszvis 405
 
406
    /* if src does not end with a backslash AND it is a directory then append a backslash */
503 mateuszvis 407
    cursrclen = path_appendbkslash_if_dir(setup->cursrc);
409 mateuszvis 408
 
409
    /* if src ends with a '\' then append *.* */
503 mateuszvis 410
    if (setup->cursrc[cursrclen - 1] == '\\') {
2215 mateusz.vi 411
      sv_strcat(setup->cursrc, "*.*");
409 mateuszvis 412
    }
413
 
503 mateuszvis 414
    /* remember where the path in cursrc ends */
415
    for (t = 0; setup->cursrc[t] != 0; t++) {
416
      if (setup->cursrc[t] == '\\') pathendoffset = t + 1;
409 mateuszvis 417
    }
418
 
419
    /* */
503 mateuszvis 420
    if (findfirst(dta, setup->cursrc, 0) != 0) {
409 mateuszvis 421
      continue;
422
    }
423
 
424
    do {
412 mateuszvis 425
      char appendflag;
409 mateuszvis 426
      if (dta->attr & DOS_ATTR_DIR) continue; /* skip directories */
427
 
428
      /* compute full path/name of the file */
2216 mateusz.vi 429
      sv_strcpy(setup->cursrc + pathendoffset, dta->fname);
409 mateuszvis 430
 
431
      /* if there was no destination, then YOU are the destination now!
432
       * this handles situations like COPY a.txt+b.txt+c.txt */
2173 mateusz.vi 433
      if (setup->dst[0] == 0) {
2216 mateusz.vi 434
        sv_strcpy(setup->dst, setup->cursrc);
2214 mateusz.vi 435
        setup->dstlen = sv_strlen(setup->dst);
409 mateuszvis 436
        copiedcount_in++;
437
        copiedcount_out++;
438
        continue;
439
      }
440
 
441
      /* is dst ending with a backslash? then append fname to it */
2216 mateusz.vi 442
      if (setup->dst[setup->dstlen - 1] == '\\') sv_strcpy(setup->dst + setup->dstlen, dta->fname);
409 mateuszvis 443
 
503 mateuszvis 444
      /* now cursrc contains the full source and dst contains the full dest... COPY TIME! */
409 mateuszvis 445
 
446
      /* if dst file exists already -> overwrite it or append?
447
          - if dst is a dir (dstlen-1 points at a \\) -> overwrite
448
          - otherwise: if copiedcount_in==0 overwrite, else append */
503 mateuszvis 449
      output(setup->cursrc);
409 mateuszvis 450
      if ((setup->dst[setup->dstlen - 1] == '\\') || (copiedcount_in == 0)) {
412 mateuszvis 451
        appendflag = 0;
409 mateuszvis 452
        output(" > ");
453
        copiedcount_out++;
454
      } else {
412 mateuszvis 455
        appendflag = 1;
409 mateuszvis 456
        output(" >> ");
457
      }
458
      outputnl(setup->dst);
459
 
2165 mateusz.vi 460
      t = cmd_copy_internal(setup->dst, 0, setup->cursrc, 0, appendflag, (setup->databufseg_custom != 0)?MK_FP(setup->databufseg_custom, 0):setup->databuf, setup->databufsz);
412 mateuszvis 461
      if (t != 0) {
538 mateuszvis 462
        nls_outputnl_doserr(t);
2165 mateusz.vi 463
        /* free memory block if it is not the static BUFF */
464
        if (setup->databufseg_custom != 0) dos_freememseg(setup->databufseg_custom);
533 mateuszvis 465
        return(CMD_FAIL);
412 mateuszvis 466
      }
467
 
409 mateuszvis 468
      copiedcount_in++;
469
    } while (findnext(dta) == 0);
470
 
471
  }
472
 
2224 mateusz.vi 473
  ustoa(setup->databuf, copiedcount_out, 0, '0');
474
  sv_strcpy(setup->databuf + 8, svarlang_str(38,9)); /* "% file(s) copied" */
475
  sv_insert_str_in_str(setup->databuf + 8, setup->databuf);
476
  outputnl(setup->databuf + 8);
409 mateuszvis 477
 
2165 mateusz.vi 478
  /* free memory block if it is not the static BUFF */
479
  if (setup->databufseg_custom != 0) dos_freememseg(setup->databufseg_custom);
480
 
533 mateuszvis 481
  return(CMD_OK);
403 mateuszvis 482
}