-
Notifications
You must be signed in to change notification settings - Fork 5
/
altairdsk.c
2339 lines (2145 loc) · 62.8 KB
/
altairdsk.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
*****************************************************************************
* ALTAIR Disk Tools
*
* Manipulate Altair CPM Disk Images
*
*****************************************************************************
*/
/*
* MIT License
*
* Copyright (c) 2023 Paul Hatchman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#if !defined(_WIN32)
#define O_BINARY 0
#endif
#define MAX_SECT_SIZE 256 /* Maximum size of a disk sector read */
#define MAX_DIRS 1024 /* Maximum size of directory table */
/* Current max is HDD_5MB_1024 format with 1024 entries */
#define MAX_ALLOCS 2048 /* Maximum size of allocation table */
/* Current max is 8MB FDC+ type with 2046 allocs */
#define DIR_ENTRY_LEN 32 /* Length of a single directory entry (extent)*/
#define ALLOCS_PER_EXT 16 /* Number of allocations in a directory entry (extent) */
#define RECORD_MAX 128 /* Max records per directory entry (extent) */
#define FILENAME_LEN 8
#define TYPE_LEN 3
#define FULL_FILENAME_LEN (FILENAME_LEN+TYPE_LEN+2)
#define MAX_USER 15
#define DELETED_FLAG 0xe5
/* Configuration for MITS 8" controller which writes to the raw sector */
struct disk_offsets {
int start_track; /* starting track which this offset applies */
int end_track; /* ending track */
int off_data; /* offset of the data portion */
int off_track_nr; /* offset of track number */
int off_sect_nr; /* offset of sector number */
int off_stop; /* offset of stop byte */
int off_zero; /* offset of zero byte */
int off_csum; /* offset of checksum */
int csum_method; /* Checksum method. Only supports methods 0 and 1 for Altair 8" */
};
/* Disk format Parameters */
struct disk_type {
const char* type; /* String type name */
int sector_len; /* length of sector in bytes (must be 128) */
int sector_data_len; /* length of data part of sector in bytes. Note only supports 128 */
int num_tracks; /* Total tracks */
int reserved_tracks; /* Number of tracks reserved by the OS */
int sectors_per_track; /* Number of sectors per track */
int block_size; /* Size of Block / Allocation */
int num_directories; /* maximum number of directories / extents supported */
int directory_allocs; /* number of allocations reserved for directory entries */
int image_size; /* size of disk image (for auto-detection) */
int skew_table_size; /* number of entries in skew table */
int *skew_table; /* Pointer to the sector skew table */
int (*skew_function)(int,int); /* logical to physical sector skew conversion */
void (*format_function)(int); /* pointer to formatting function */
struct disk_offsets offsets[2]; /* Raw sector offsets for MITS 8" controller */
};
/* On-disk representation of a directory entry */
typedef struct raw_dir_entry
{
uint8_t user; /* User (0-15). 0xE5 = Deleted */
char filename[FILENAME_LEN];
char type[TYPE_LEN];
uint8_t extent_l; /* Extent number low 0-31 */
uint8_t reserved;
uint8_t extent_h; /* Extent number high 32x */
uint8_t num_records; /* Number of sectors used for this directory entry */
uint8_t allocation[ALLOCS_PER_EXT]; /* List of allocations used for the file */
} raw_dir_entry;
/* Sanitised version of a directory entry */
typedef struct cpm_dir_entry
{
int index; /* Zero based directory number */
uint8_t valid; /* Valid if used for a file */
raw_dir_entry raw_entry; /* On-disk representation */
int extent_nr;
int user;
char filename[FILENAME_LEN+1];
char type[TYPE_LEN+1];
char attribs[3]; /* R - Read-Only, W - Read-Write, S - System */
char full_filename[FILENAME_LEN+TYPE_LEN+2]; /* filename.ext format */
int num_records;
int num_allocs;
int allocation[ALLOCS_PER_EXT]; /* Only 8 of the 16 are used. As the 2-byte allocs
* in the raw_entry are converted to a single value */
struct cpm_dir_entry* next_entry; /* pointer to next directory entry if multiple */
} cpm_dir_entry;
cpm_dir_entry dir_table[MAX_DIRS]; /* Directory entires in order read from "disk" */
cpm_dir_entry* sorted_dir_table[MAX_DIRS]; /* Pointers to entries sorted by
* valid, filename+type, user, extent_nr */
uint8_t alloc_table[MAX_ALLOCS]; /* Allocation table. 0 = Unused, 1 = Used */
struct disk_type *disk_type; /* Pointer to the disk image type */
void format_disk(int fd);
void mits8in_format_disk(int fd);
/* Skew table. Converts logical sectors to on-disk sectors */
/* MITS Floppy has it's own skew routine, and needs a 1-based skew table */
int mits_skew_table[] = {
1,9,17,25,3,11,19,27,05,13,21,29,7,15,23,31,
2,10,18,26,4,12,20,28,06,14,22,30,8,16,24,32
};
int mits8in_skew_function(int track, int logical_sector)
{
if (track < 6)
{
return mits_skew_table[logical_sector];
}
/* This additional skew is required for strange historical reasons */
return (((mits_skew_table[logical_sector] - 1) * 17) % 32) + 1;
}
/* Standard 8" floppy drive */
struct disk_type MITS8IN_FORMAT = {
.type = "FDD_8IN",
.sector_len = 137,
.sector_data_len = 128,
.num_tracks = 77,
.reserved_tracks = 2,
.sectors_per_track = 32,
.block_size = 2048,
.num_directories = 64,
.directory_allocs = 2,
.image_size = 337568, /* Note images formatted in simh are 337664 */
.skew_table_size = sizeof(mits_skew_table),
.skew_table = mits_skew_table,
.skew_function = &mits8in_skew_function,
.format_function = &mits8in_format_disk,
.offsets = {
{ 0, 5, 3, 0, 0, 131, 133, 132, 0 },
{ 6, 77, 7, 0, 1, 135, 136, 4, 1 }
}
};
/* The FDC+ controller supports an 8MB "floppy" disk */
struct disk_type MITS8IN8MB_FORMAT = {
.type = "FDD_8IN_8MB",
.sector_len = 137,
.sector_data_len = 128,
.num_tracks = 2048,
.reserved_tracks = 2,
.sectors_per_track = 32,
.block_size = 4096,
.num_directories = 512,
.directory_allocs = 4,
.image_size = 8978432,
.skew_table_size = sizeof(mits_skew_table),
.skew_table = mits_skew_table,
.skew_function = &mits8in_skew_function,
.format_function = &mits8in_format_disk,
.offsets = {
{ 0, 5, 3, 0, 0, 131, 133, 132, 0 },
{ 6, 77, 7, 0, 1, 135, 136, 4, 1 }
}
};
/* Skew table for the 5MB HDD. Note that we require a
* skew for each CPM sector. Not physical sector */
int hd5mb_skew_table[] = {
0,1,14,15,28,29,42,43,8,9,22,23,
36,37,2,3,16,17,30,31,44,45,10,11,
24,25,38,39,4,5,18,19,32,33,46,47,
12,13,26,27,40,41,6,7,20,21,34,35,
48,49,62,63,76,77,90,91,56,57,70,71,
84,85,50,51,64,65,78,79,92,93,58,59,
72,73,86,87,52,53,66,67,80,81,94,95,
60,61,74,75,88,89,54,55,68,69,82,83
};
int standard_skew_function(int track, int logical_sector)
{
return disk_type->skew_table[logical_sector] + 1;
}
/* MITS 5MB HDD Format */
struct disk_type MITS5MBHDD_FORMAT = {
.type = "HDD_5MB",
.sector_len = 128,
.sector_data_len = 128,
.num_tracks = 406,
.reserved_tracks = 1,
.sectors_per_track = 96,
.block_size = 4096,
.num_directories = 256,
.directory_allocs = 2,
.image_size = 4988928,
.skew_table_size = sizeof(hd5mb_skew_table),
.skew_table = hd5mb_skew_table,
.skew_function = &standard_skew_function,
.format_function = &format_disk,
.offsets = {
{ 0, 406, 0, -1, -1, -1, -1, -1, -1 },
{ -1, -1, 0, -1, -1, -1, -1, -1, -1 }
}
};
/* MITS 5MB HDD Format with 1024 directory entries */
struct disk_type MITS5MBHDD1024_FORMAT = {
.type = "HDD_5MB_1024",
.sector_len = 128,
.sector_data_len = 128,
.num_tracks = 406,
.reserved_tracks = 1,
.sectors_per_track = 96,
.block_size = 4096,
.num_directories = 1024,
.directory_allocs = 8,
.image_size = 4988928,
.skew_table_size = sizeof(hd5mb_skew_table),
.skew_table = hd5mb_skew_table,
.skew_function = &standard_skew_function,
.format_function = &format_disk,
.offsets = {
{ 0, 406, 0, -1, -1, -1, -1, -1, -1 },
{ -1, -1, 0, -1, -1, -1, -1, -1, -1 }
}
};
int tarbell_skew_table[] = {
0, 6, 12, 18, 24, 4,
10, 16, 22, 2, 8, 14,
20, 1, 7, 13, 19, 25,
5, 11, 17, 23, 3, 9,
15, 21
};
/* Tarbell Floppy format */
struct disk_type TARBELLFDD_FORMAT = {
.type = "FDD_TAR",
.sector_len = 128,
.sector_data_len = 128,
.num_tracks = 77,
.reserved_tracks = 2,
.sectors_per_track = 26,
.block_size = 1024,
.num_directories = 64,
.directory_allocs = 2,
.image_size = 256256,
.skew_table_size = sizeof(hd5mb_skew_table),
.skew_table = tarbell_skew_table,
.skew_function = &standard_skew_function,
.format_function = &format_disk,
.offsets = {
{ 0, 77, 0, -1, -1, -1, -1, -1, -1 },
{ -1, -1, 0, -1, -1, -1, -1, -1, -1 }
}
};
int fdd15mb_skew_table[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39,
40,41,42,43,44,45,46,47,48,49,
50,51,52,53,54,55,56,57,58,59,
60,61,62,63,64,65,66,67,68,69,
70,71,72,73,74,75,76,77,78,79
};
/* FDC+ controller supports 1.5MB floppy disks */
struct disk_type FDD15MB_FORMAT = {
.type = "FDD_1.5MB",
.sector_len = 128,
.sector_data_len = 128,
.num_tracks = 149,
.reserved_tracks = 1,
.sectors_per_track = 80,
.block_size = 4096,
.num_directories = 256,
.directory_allocs = 2,
.image_size = 1525760,
.skew_table_size = sizeof(fdd15mb_skew_table),
.skew_table = fdd15mb_skew_table,
.skew_function = &standard_skew_function,
.format_function = &format_disk,
.offsets = {
{ 0, 77, 0, -1, -1, -1, -1, -1, -1 },
{ -1, -1, 0, -1, -1, -1, -1, -1, -1 }
}
};
/* Global Variables */
int VERBOSE = 0; /* Print out Sector read/write and other information */
int EXIT_VALUE = EXIT_SUCCESS;
void print_usage(char* argv0);
void print_mits_5mb_1k_warning(FILE* fp);
void error_exit(int eno, char *str, ...);
void error(int eno, char *str, ...);
void directory_list(int user);
void raw_directory_list();
void copy_from_cpm(int cpm_fd, int host_fd, cpm_dir_entry* dir_entry, int text_mode);
void copy_to_cpm(int cpm_fd, int host_fd, const char* cpm_filename, const char* host_filename, int user);
void extract_cpm(int cpm_fd, int host_fd);
void install_cpm(int cpm_fd, int host_fd);
void load_directory_table(int fd);
cpm_dir_entry* find_dir_by_filename(const char *full_filename, cpm_dir_entry *prev_entry, int wildcards, int user);
int exist_filename_other_users(cpm_dir_entry *entry);
int filename_equals(const char* fn1, const char* fn2, int wildcards);
cpm_dir_entry* find_free_dir_entry(void);
void raw_to_cpmdir(cpm_dir_entry* entry);
int find_free_alloc(void);
void copy_filename(raw_dir_entry *entry, const char *filename);
void erase_file(int fd, cpm_dir_entry* entry);
ssize_t safe_read(int fd, void *buf, size_t count);
ssize_t full_write(int fd, const void *buf, size_t count);
void write_dir_entry(int fd, cpm_dir_entry* entry);
void read_sector(int fd, int alloc_num, int rec_num, void* buffer);
void write_sector(int fd, int alloc_num, int rec_num, void* buffer);
void write_raw_sector(int fd, int track, int sector, void* buffer);
void convert_track_sector(int allocation, int record, int* track, int* sector);
uint8_t calc_checksum(uint8_t *buffer);
void validate_cpm_filename(const char *filename, char *validated_filename);
int compare_sort(const void *a, const void *b);
int compare_sort_ptr(const void *a, const void *b);
int get_raw_allocation(raw_dir_entry* raw, int entry_nr);
void set_raw_allocation(raw_dir_entry *entry, int entry_nr, int alloc);
int is_first_extent(cpm_dir_entry* dir_entry);
char* strip_quotes(char* filename);
void disk_format_disk(int fd);
int disk_sector_len();
int disk_data_sector_len();
int disk_num_tracks();
int disk_reserved_tracks();
int disk_sectors_per_track();
int disk_block_size();
int disk_num_directories();
int disk_skew_table_size();
int disk_skew_sector(int track_nr, int logical_sector);
int disk_track_len();
int disk_total_allocs();
int disk_recs_per_alloc();
int disk_recs_per_extent();
int disk_directory_allocs();
int disk_dirs_per_sector();
int disk_dirs_per_alloc();
struct disk_offsets *disk_get_offsets(int track_nr);
int disk_off_track_nr(int track_nr);
int disk_off_sect_nr(int track_nr);
int disk_off_data(int track_nr);
int disk_off_stop(int track_nr);
int disk_off_zero(int track_nr);
int disk_off_csum(int track_nr);
int disk_csum_method(int track_nr);
int disk_detect_type(int fd);
void disk_set_type(int img_fd, const char* type);
void disk_dump_parameters();
void disk_format_disk(int fd);
int main(int argc, char**argv)
{
int open_mode = O_RDONLY; /* read or write depending on selected options */
mode_t open_umask = 0666;
/* command line options */
int opt;
int do_dir = 0, do_raw = 0, do_get = 0;
int do_put = 0 , do_help = 0, do_format = 0;
int do_erase = 0, do_multiput = 0, do_multiget = 0;
int do_multierase = 0, do_extractsystem = 0, do_writesystem = 0;
int has_type = 0; /* has the user specified a type? */
int text_mode = -1; /* default to auto-detect text/binary */
char *disk_filename = NULL; /* Altair disk image filename */
char from_filename[PATH_MAX]; /* filename to get / put */
char to_filename[PATH_MAX]; /* filename to get / put */
char *image_type; /* manually specify type of disk image */
int user = -1; /* The CP/M user -1 = all users*/
/* Default to 8" floppy. This default should not be used. Just here for safety */
disk_type = &MITS8IN_FORMAT;
/* parse command line options */
while ((opt = getopt(argc, argv, "drhgGpPvFeEtbxsT:u:")) != -1)
{
switch (opt)
{
case 'h':
do_help = 1;
break;
case 'd':
do_dir = 1;
open_mode = O_RDONLY;
break;
case 'r':
do_raw = 1;
open_mode = O_RDONLY;
break;
case 'g':
do_get = 1;
open_mode = O_RDONLY;
break;
case 'G':
do_multiget = 1;
open_mode = O_RDONLY;
break;
case 'p':
do_put = 1;
open_mode = O_RDWR;
break;
case 'P':
do_multiput = 1;
open_mode = O_RDWR;
break;
case 'v':
VERBOSE = 1;
break;
case 'e':
do_erase = 1;
open_mode = O_RDWR;
break;
case 'E':
do_multierase = 1;
open_mode = O_RDWR;
break;
case 'F':
do_format = 1;
open_mode = O_WRONLY | O_CREAT | O_TRUNC;
break;
case 't':
text_mode = 1;
break;
case 'b':
text_mode = 0;
break;
case 'T':
has_type = 1;
image_type = optarg;
break;
case 'u':
{
char* end;
user = strtol(optarg, &end, 10);
if(*end != '\0' || user < 0 || user > 15)
{
error_exit(0, "User must be a valid number between 0 and 15\n");
}
break;
}
case 'x':
do_extractsystem = 1;
open_mode = O_RDONLY;
break;
case 's':
do_writesystem = 1;
open_mode = O_RDWR;
break;
case '?':
exit(EXIT_FAILURE);
}
}
/* make sure only one option is selected */
int nr_opts = do_dir + do_raw + do_help +
do_put + do_get + do_format +
do_erase + do_multiget + do_multiput +
do_multierase + do_extractsystem + do_writesystem;
if (nr_opts > 1)
{
fprintf(stderr, "%s: Too many options supplied.\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
/* default to directory listing if no option supplied */
if (nr_opts == 0)
{
do_dir = 1;
}
if (do_help)
{
print_usage(argv[0]);
exit(EXIT_SUCCESS);
}
/* get the disk image filename */
if (optind == argc)
{
fprintf(stderr, "%s: <disk_image> not supplied.\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
else
{
/* get the Altair disk image filename */
disk_filename = argv[optind++];
}
/* Get and Put need a from_filename and an optional to_filename*/
/* Erase, extract and system just need a single filename */
if (do_get || do_put || do_erase || do_extractsystem || do_writesystem)
{
if (optind == argc)
{
fprintf(stderr, "%s: <filename> not supplied\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
else
{
strcpy(from_filename, argv[optind++]);
if (!(do_erase || do_extractsystem || do_writesystem) && optind < argc)
{
strcpy(to_filename, argv[optind++]);
}
else
{
strcpy(to_filename,from_filename);
}
}
}
/* For multiget and multi-put, just make sure at least 1 filename supplied */
/* Filenames will be processed later */
if (do_multiget || do_multiput || do_multierase)
{
if (optind == argc)
{
fprintf(stderr, "%s: <filename ...> not supplied\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
}
else if (optind != argc)
{
fprintf(stderr, "%s: Too many arguments supplied.\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
/*
* Start of processing
*/
int fd_img = -1; /* fd of disk image */
/* Open the Altair disk image*/
if ((fd_img = open(disk_filename, open_mode | O_BINARY, open_umask)) < 0)
{
error_exit(errno, "Error opening disk image file %s", disk_filename);
}
/* Set the format of this image */
if (has_type)
{
disk_set_type(fd_img, image_type);
}
else
{
/* try to auto-detect type */
if (disk_detect_type(fd_img) < 0)
{
if (!do_format)
{
error_exit(0, "Unknown disk image type. Use -h to see supported types and -T to force a type.");
}
else
{
// For format we default to mits 8IN
disk_type = &MITS8IN_FORMAT;
fprintf(stderr, "Defaulting to disk type: %s\n", disk_type->type);
}
}
}
if (VERBOSE)
disk_dump_parameters();
/* Initialize allocation table - Reserve allocations used by directories */
for (int i = 0; i < disk_directory_allocs(); i++)
{
alloc_table[i] = 1;
}
/* Read all directory entries - except for commands that don't need it */
if (!do_format && !do_extractsystem && !do_writesystem)
{
load_directory_table(fd_img);
}
/* Raw Directory Listing */
if (do_raw)
{
raw_directory_list();
exit(EXIT_VALUE);
}
/* Formatted directory listing */
if (do_dir)
{
directory_list(user);
exit(EXIT_VALUE);
}
/* Copy file from disk image to host */
if (do_get)
{
/* does the file exist in CPM? */
cpm_dir_entry* entry = find_dir_by_filename(basename(from_filename), NULL, 0, user);
if (entry == NULL)
{
error_exit(ENOENT, "Error copying file %s", from_filename);
}
/* Try and remove file file we are about to get */
if ((unlink(to_filename) < 0) && (errno != ENOENT))
{
error_exit(errno, "Error removing old file %s", to_filename);
}
/* open file to save into */
int fd_file = open(to_filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0666);
if (fd_file < 0)
{
error_exit(errno, "Error opening file %s", from_filename);
}
/* finally copy the file from disk image*/
copy_from_cpm(fd_img, fd_file, entry, text_mode);
exit(EXIT_VALUE);
}
/* Copy multiple files from disk image to host */
if (do_multiget)
{
char this_filename[FULL_FILENAME_LEN + 3]; /* current file being copied +3 for _uu (user number) */
while (optind != argc)
{
int file_found = 0;
cpm_dir_entry *entry = NULL;
strcpy(from_filename, strip_quotes(argv[optind++]));
/* process all filenames */
while(1)
{
/* The filename may contain wildcards. If so, loop for each expanded filename */
entry = find_dir_by_filename(from_filename, entry, 1, user);
if (entry == NULL)
{
/* If not at least 1 file found for this from_filename */
if (!file_found)
{
error(ENOENT, "Error copying %s", from_filename);
}
/* process next file */
break;
}
/* Check if there are multiple copies of this filename for different users
* If so, append _user to the filename
* But don't append user number for user 0 */
if (user == -1 &&
entry->user != 0 &&
exist_filename_other_users(entry))
{
sprintf(this_filename, "%s_%d", entry->full_filename, entry->user);
}
else
{
strcpy(this_filename, entry->full_filename);
}
file_found = 1;
/* delete the host file we are about to copy into */
if ((unlink(this_filename) < 0) && (errno != ENOENT))
{
error(errno, "Skipping file. Error removing old file %s.", this_filename);
break;
}
/* create the file to copy into */
int fd_file = open(this_filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0666);
if (fd_file < 0)
{
error(errno, "Skipping file. Error opening file %s", this_filename);
break;
}
/* copy it */
copy_from_cpm(fd_img, fd_file, entry, text_mode);
close(fd_file);
}
}
exit(EXIT_VALUE);
}
/* Copy file from host to disk image */
if (do_put)
{
int fd_file = open(from_filename, O_RDONLY);
if (fd_file < 0)
{
error_exit(errno, "Error opening file %s", from_filename);
}
copy_to_cpm(fd_img, fd_file, basename(to_filename), from_filename, user);
exit(EXIT_VALUE);
}
/* Copy multiple files from host to disk image */
if (do_multiput)
{
/* process for each file passed on the command file */
while (optind != argc)
{
strcpy(from_filename, argv[optind++]);
strcpy(to_filename, from_filename);
int fd_file = open(from_filename, O_RDONLY | O_BINARY);
if (fd_file < 0)
{
error(errno, "Error opening file %s", from_filename);
continue;
}
copy_to_cpm(fd_img, fd_file, basename(to_filename), from_filename, user);
close(fd_file);
}
exit(EXIT_VALUE);
}
/* erase a single file from the disk image */
if (do_erase)
{
cpm_dir_entry *entry = find_dir_by_filename(from_filename, NULL, 0, user);
if (entry == NULL)
{
error_exit(ENOENT, "Error erasing %s", from_filename);
}
erase_file(fd_img, entry);
exit(EXIT_VALUE);
}
if(do_multierase)
{
while (optind != argc)
{
int file_found = 0;
cpm_dir_entry *entry = NULL;
strcpy(from_filename, strip_quotes(argv[optind++]));
/* process all filenames */
while(1)
{
/* The filename may contain wildcards. If so, loop for each expanded filename */
entry = find_dir_by_filename(from_filename, entry, 1, user);
if (entry == NULL)
{
/* If not at least 1 file found for this from_filename */
if (!file_found)
{
error(ENOENT, "Error erasing %s", from_filename);
}
/* process next file */
break;
}
file_found = 1;
erase_file(fd_img, entry);
}
}
exit(EXIT_VALUE);
}
/* format and existing image or create a newly formatted image */
if (do_format)
{
if (disk_type == &MITS5MBHDD1024_FORMAT)
{
print_mits_5mb_1k_warning(stderr);
}
/* Call the disk-specific format function */
disk_format_disk(fd_img);
exit(EXIT_VALUE);
}
/* Extract the CP/M system files*/
if (do_extractsystem)
{
int fd_file = open(to_filename, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, open_umask);
if (fd_file < 0)
{
error_exit(errno, "Error opening %s", to_filename);
}
extract_cpm(fd_img, fd_file);
exit(EXIT_VALUE);
}
/* Copy system tracks onto disk image */
if (do_writesystem)
{
int fd_file = open(from_filename, O_RDONLY | O_BINARY, open_umask);
if (fd_file < 0)
{
error_exit(errno, "Error opening %s", from_filename);
}
install_cpm(fd_img, fd_file);
exit(EXIT_VALUE);
}
exit(EXIT_VALUE);
}
/*
* Usage information
*/
void print_usage(char* argv0)
{
char *progname = basename(argv0);
printf("%s: -[d|r|F]v [-T <type>] [-u <user>] <disk_image>\n", progname);
printf("%s: -[g|p|e][t|b]v [-T <type>] [-u <user>] <disk_image> <src_filename> [dst_filename]\n", progname);
printf("%s: -[G|P|E][t|b]v [-T <type>] [-u <user>] <disk_image> <filename ...>\n", progname);
printf("%s: -[x|s]v [-T <type>] <disk_image> <system_image>\n", progname);
printf("%s: -h\n", progname);
printf("\t-d\tDirectory listing (default)\n");
printf("\t-r\tRaw directory listing\n");
printf("\t-F\tFormat existing or create new disk image. Defaults to %s\n", MITS8IN_FORMAT.type);
printf("\t-g\tGet - Copy file from Altair disk image to host\n");
printf("\t-G\tGet Multiple - Copy multiple files from Altair disk image to host\n");
printf("\t \t wildcards * and ? are supported e.g '*.COM'\n");
printf("\t-p\tPut - Copy file from host to Altair disk image\n");
printf("\t-P\tPut Multiple - Copy multiple files from host to Altair disk image\n");
printf("\t-e\tErase a file\n");
printf("\t-E\tErase multiple files - wildcards supported\n");
printf("\t-t\tPut/Get a file in text mode\n");
printf("\t-b\tPut/Get a file in binary mode\n");
printf("\t-u\tUser - Restrict operation to CP/M user\n");
printf("\t-x\tExtract CP/M system (from a bootable disk image) to a file\n");
printf("\t-s\tWrite saved CP/M system image to disk image (make disk bootable)\n");
printf("\t-T\tDisk image type. Auto-detected if possible. Supported types are:\n");
printf("\t\t\t* %s - MITS 8\" Floppy Disk (Default)\n", MITS8IN_FORMAT.type);
printf("\t\t\t* %s - MITS 5MB Hard Disk\n", MITS5MBHDD_FORMAT.type);
printf("\t\t\t* %s - MITS 5MB, with 1024 directories (!!!)\n", MITS5MBHDD1024_FORMAT.type);
printf("\t\t\t* %s - Tarbell Floppy Disk\n", TARBELLFDD_FORMAT.type);
printf("\t\t\t* %s - FDC+ 1.5MB Floppy Disk\n", FDD15MB_FORMAT.type);
printf("\t\t\t* %s - FDC+ 8MB \"Floppy\" Disk\n", MITS8IN8MB_FORMAT.type);
printf("\t-v\tVerbose - Prints image type and sector read/write information\n");
printf("\t-h\tHelp\n\n");
print_mits_5mb_1k_warning(stdout);
}
/*
* Print a warning that this format cannot be auto-detected.
*/
void print_mits_5mb_1k_warning(FILE* fp)
{
fprintf(fp, "!!! The %s type cannot be auto-detected. Always use -T with this format,\n", MITS5MBHDD1024_FORMAT.type);
fprintf(fp, "otherwise your disk image will auto-detect as the standard 5MB type and could be corrupted.\n");
}
/*
* Print formatted error string and exit.
*/
void error_exit(int eno, char *str, ...)
{
va_list argp;
va_start(argp, str);
vfprintf(stderr, str, argp);
if (eno > 0)
fprintf(stderr,": %s\n", strerror(eno));
else
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
/*
* Print formatted error string.
* Set global EXIT_VALUE to failure value.
*/
void error(int eno, char *str, ...)
{
va_list argp;
va_start(argp, str);
vfprintf(stderr, str, argp);
if (eno > 0)
fprintf(stderr,": %s\n", strerror(eno));
else
fprintf(stderr, "\n");
va_end(argp);
EXIT_VALUE = EXIT_FAILURE;
}
/*
* Print nicely formatted directory listing
* user - -1 = all users, otherwise restrict to that user number
*/
void directory_list(int user)
{
int file_count = 0;
int kb_used = 0;
int kb_free = 0;
int entry_count = 0;
int kb_total = (disk_total_allocs() - disk_directory_allocs()) * disk_block_size() / 1024;
printf("Name Ext Length Used U At\n");
cpm_dir_entry *entry = NULL;
int this_records = 0;
int this_allocs = 0;
int this_kb = 0;
char *last_filename = "";
int last_user = -1;
for (int i = 0 ; i < disk_num_directories() ; i++)
{
/* Valid entries are sorted before invalid ones. So stop on first invalid */
entry = sorted_dir_table[i];
if (!entry->valid)
{
break;
}
entry_count++;
/* skip if not for all users or not for this user */
if (user != -1 && user != entry->user)
continue;
/* If this is the first record for this file, then reset the file totals */
if((strcmp(entry->full_filename, last_filename) != 0) ||
(entry->user != last_user))
{
file_count++;
this_records = 0;
this_allocs = 0;
this_kb = 0;
last_filename = entry->full_filename;
last_user = entry->user;
}
this_records += entry->num_records;
this_allocs += entry->num_allocs;
/* If there are no more dir entries for this file, print out the file details */
if(entry->next_entry == NULL)
{
this_kb += (this_allocs * disk_block_size()) / 1024;
kb_used += this_kb;
printf("%s %s %7dB %3dK %d %s\n",
entry->filename,
entry->type,
this_records * disk_sector_len(),
this_kb,
entry->user,
entry->attribs);
}
}
for (int i = disk_directory_allocs() ; i < disk_total_allocs() ; i++)
{
if(alloc_table[i] == 0)
{
kb_free+= disk_block_size() / 1024;
}
}
printf("%d file(s), occupying %dK of %dK total capacity\n",
file_count, kb_used, kb_total);
printf("%d directory entries and %dK bytes remain\n",
disk_num_directories() - entry_count, kb_free);
}
/*
* Print raw directory table.
*/