source: vital-to8-sdk/sap/libsap.c @ 1

Last change on this file since 1 was 1, checked in by svn, 5 years ago

Import initial

File size: 43.6 KB
Line 
1/*  LibSAP
2 *  Version 0.9.4
3 *  Copyright (C) 2000-2003 Eric Botcazou
4 *
5 *  This program is free software; you can redistribute it and/or modify
6 *  it under the terms of the GNU General Public License as published by
7 *  the Free Software Foundation; either version 2 of the License, or
8 *  (at your option) any later version.
9 *
10 *  This program is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *  GNU General Public License for more details.
14 *
15 *  You should have received a copy of the GNU General Public License
16 *  along with this program; if not, write to the Free Software
17 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20
21#include <ctype.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <time.h>
26#include "libsap.h"
27
28
29int sap_errno;
30
31
32#define FILENAME_LENGTH 128
33#define MAX_ARCHIVE       8
34
35#define SAP_MAGIC_NUM  0xB3
36#define SAP_HEADER_SIZE  66
37static const char sap_header[]="\0SYSTEME D'ARCHIVAGE PUKALL S.A.P. "
38                               "(c) Alexandre PUKALL Avril 1998";
39
40
41static const int sap_format_table[][3] = {
42   { SAP_NTRACKS1, SAP_SECTSIZE1, SAP_TRACKSIZE1 },
43   { SAP_NTRACKS2, SAP_SECTSIZE2, SAP_TRACKSIZE2 }
44};
45
46#define SAP_NTRACKS(f)      sap_format_table[f-SAP_FORMAT1][0]
47#define SAP_SECTSIZE(f)     sap_format_table[f-SAP_FORMAT1][1]
48#define SAP_TRACKSIZE(f)    sap_format_table[f-SAP_FORMAT1][2]
49
50#define SAP_EXTSECTSIZE(f)  (SAP_SECTSIZE(f) + sizeof(sapsector_t) - SAP_SECTSIZE1)
51
52
53static short int crcpuk_temp;
54
55static short int puktable[]={
56   0x0000, 0x1081, 0x2102, 0x3183,
57   0x4204, 0x5285, 0x6306, 0x7387,
58   0x8408, 0x9489, 0xa50a, 0xb58b,
59   0xc60c, 0xd68d, 0xe70e, 0xf78f
60};
61
62
63#define NO_ARCHIVE     0
64#define EMPTY_ARCHIVE  1
65#define FILLED_ARCHIVE 2
66#define FULL_ARCHIVE   3
67
68static struct {
69   int format;
70   int state;
71   int ntracks;
72   FILE *file;
73} archive[MAX_ARCHIVE+1];
74
75#define ID_FORMAT(id)   archive[id].format
76#define ID_STATE(id)    archive[id].state
77#define ID_NTRACKS(id)  archive[id].ntracks
78#define ID_FILE(id)     archive[id].file
79
80
81/* constants for the TO logical disk format */
82#define TO_NSECTS                16
83#define TO_SECTOR_PER_BLOCK      8
84
85#define TO_SECTSIZE1             255  /* not 256 */
86#define TO_FAT_START1            257
87#define TO_NBLOCKS1              160
88#define TO_BLOCKSIZE1            (TO_SECTOR_PER_BLOCK*TO_SECTSIZE1)
89#define TO_FILESIZE_MAX1         (TO_NBLOCKS1-2)
90#define TO_DIR_START1            512
91
92#define TO_SECTSIZE2             127  /* not 128 */
93#define TO_FAT_START2            129
94#define TO_NBLOCKS2              80
95#define TO_BLOCKSIZE2            (TO_SECTOR_PER_BLOCK*TO_SECTSIZE2)
96#define TO_FILESIZE_MAX2         (TO_NBLOCKS2-2)
97#define TO_DIR_START2            256
98
99#define TO_FILLER_BYTE           0xE5
100#define TO_TAG_RESERVED          0xFE
101#define TO_TAG_FREE              0xFF
102#define TO_END_BLOCK_OFFSET      0xC0
103
104#define TO_DIRENTRY_LENGTH       32
105#define TO_NAME                  0
106#define TO_NAME_LENGTH           8
107#define TO_EXT                   8
108#define TO_EXT_LENGTH            3
109#define TO_FILE_TYPE             11
110#define TO_DATA_TYPE             12
111#define TO_FIRST_BLOCK           13
112#define TO_END_SIZE              14
113#define TO_COMMENT               16
114#define TO_COMMENT_LENGTH        8
115#define TO_DATE_DAY              24
116#define TO_DATE_MONTH            25
117#define TO_DATE_YEAR             26
118#define TO_CHG_MODE              30
119#define TO_CHG_CHECKSUM          31
120
121#define TO_DIRENTRY_PER_SECTOR1  8
122#define TO_NDIRENTRIES1          (TO_NSECTS-2)*TO_DIRENTRY_PER_SECTOR1
123
124#define TO_DIRENTRY_PER_SECTOR2  4
125#define TO_NDIRENTRIES2          (TO_NSECTS-2)*TO_DIRENTRY_PER_SECTOR2
126
127static const int to_format_table[][8] = {
128   { TO_SECTSIZE1, TO_FAT_START1, TO_NBLOCKS1, TO_BLOCKSIZE1,
129     TO_FILESIZE_MAX1, TO_DIR_START1, TO_DIRENTRY_PER_SECTOR1, TO_NDIRENTRIES1 },
130   { TO_SECTSIZE2, TO_FAT_START2, TO_NBLOCKS2, TO_BLOCKSIZE2,
131     TO_FILESIZE_MAX2, TO_DIR_START2, TO_DIRENTRY_PER_SECTOR2, TO_NDIRENTRIES2 }
132};
133
134#define TO_SECTSIZE(f)             to_format_table[f-SAP_FORMAT1][0]
135#define TO_FAT_START(f)            to_format_table[f-SAP_FORMAT1][1]
136#define TO_NBLOCKS(f)              to_format_table[f-SAP_FORMAT1][2]
137#define TO_BLOCKSIZE(f)            to_format_table[f-SAP_FORMAT1][3]
138#define TO_FILESIZE_MAX(f)         to_format_table[f-SAP_FORMAT1][4]
139#define TO_DIR_START(f)            to_format_table[f-SAP_FORMAT1][5]
140#define TO_DIRENTRY_PER_SECTOR(f)  to_format_table[f-SAP_FORMAT1][6]
141#define TO_NDIRENTRIES(f)          to_format_table[f-SAP_FORMAT1][7]
142
143
144/* constants for the TO memory layout */
145#define TO_BANKSIZE             16384
146
147
148/* file types */
149enum {
150   FTYPE_BASIC,
151   FTYPE_DATA,
152   FTYPE_MACHINE,
153   FTYPE_ASM,
154   FTYPE_OTHERS,
155   FTYPE_PARAGRAPHE = 0xA
156};
157
158static char ftype_sym[5] = { 'B', 'D', 'M', 'A', '*' };
159
160enum {
161   DTYPE_BINARY,
162   DTYPE_ASCII = 0xFF,
163   DTYPE_OTHERS = 2
164};
165
166static char dtype_sym[3] = { 'B', 'A', '*' };
167
168struct extension_t {
169   char tag[TO_EXT_LENGTH+1];
170   unsigned char ftype;
171   unsigned char dtype;
172};
173
174enum {
175   EXT_TYPE_BAS,
176   EXT_TYPE_DAT,
177   EXT_TYPE_ASC,
178   EXT_TYPE_ASM,
179   EXT_TYPE_BIN,
180   EXT_TYPE_MAP,
181   EXT_TYPE_CHG,
182   EXT_TYPE_CFG,
183   EXT_TYPE_BAT,
184   EXT_TYPE_CAR,
185   EXT_TYPE_PAR,
186   EXT_TYPE_UNKNOWN,
187   EXT_TYPE_MAX
188};
189
190static struct extension_t ext_type[EXT_TYPE_MAX] = {
191   { "BAS", FTYPE_BASIC,      DTYPE_BINARY },
192   { "DAT", FTYPE_DATA,       DTYPE_ASCII  },
193   { "ASC", FTYPE_DATA,       DTYPE_ASCII  },
194   { "ASM", FTYPE_ASM,        DTYPE_ASCII  },
195   { "BIN", FTYPE_MACHINE,    DTYPE_BINARY },
196   { "MAP", FTYPE_MACHINE,    DTYPE_BINARY },
197   { "CHG", FTYPE_DATA,       DTYPE_ASCII  },
198   { "CFG", FTYPE_MACHINE,    DTYPE_BINARY },
199   { "BAT", FTYPE_BASIC,      DTYPE_BINARY },
200   { "CAR", FTYPE_DATA,       DTYPE_ASCII  },
201   { "PAR", FTYPE_PARAGRAPHE, DTYPE_BINARY },
202   { "???", FTYPE_MACHINE,    DTYPE_BINARY }
203};
204
205
206#define MIN(x,y)     (((x) < (y)) ? (x) : (y))
207
208
209
210/************************************************/
211/***    private functions: helper routines    ***/
212/************************************************/
213
214
215/* get_id:
216 *  Finds the first empty id, returns 0 if has failed.
217 */
218static int get_id(void)
219{
220   int i;
221
222   for (i=MAX_ARCHIVE; i>0; i--) {
223      if (archive[i].state == NO_ARCHIVE)
224         break;
225   }
226
227   return i;
228}
229
230
231
232/* clean_string:
233 *  Helper function to clean up character strings.
234 */
235static inline void clean_string(unsigned char *str)
236{
237   while (*str) {
238      if ((*str<32) || (*str>127))
239         *str = '#';
240
241      str++;
242   }
243}
244
245
246
247/************************************************/
248/***   private functions: SAP format support  ***/
249/************************************************/
250
251
252/* crc_pukall:
253 *  Computes the new CRC from the c data.
254 */
255static void crc_pukall(short int c)
256{
257   short int index;
258
259   index = (crcpuk_temp ^ c) & 0xf;
260   crcpuk_temp = ((crcpuk_temp>>4) & 0xfff) ^ puktable[index];
261
262   c >>= 4;
263
264   index = (crcpuk_temp ^ c) & 0xf;
265   crcpuk_temp = ((crcpuk_temp>>4) & 0xfff) ^ puktable[index];
266}
267
268
269
270/* do_crc:
271 *  Computes the CRC for the specified SAP sector.
272 */
273static void do_crc(sapsector_t *sapsector, int format)
274{
275   int i;
276
277   crcpuk_temp = 0xffff;
278
279   crc_pukall(sapsector->format);
280   crc_pukall(sapsector->protection);
281   crc_pukall(sapsector->track);
282   crc_pukall(sapsector->sector);
283
284   for (i=0; i<SAP_SECTSIZE(format); i++)
285      crc_pukall(sapsector->data[i]);
286}
287
288
289
290/* do_read_sector:
291 *  Performs the low-level read operation for the specified sector.
292 */
293static void do_read_sector(sapID id, sapsector_t *sapsector)
294{
295   unsigned char buffer[sizeof(sapsector_t)];
296   int format = ID_FORMAT(id);
297   int i;
298
299   i=fread(buffer, sizeof(char), SAP_EXTSECTSIZE(format), ID_FILE(id));
300
301   sapsector->format     = buffer[0];
302   sapsector->protection = buffer[1];
303   sapsector->track      = buffer[2];
304   sapsector->sector     = buffer[3];
305
306   for (i=0; i<SAP_SECTSIZE(format); i++)
307       sapsector->data[i] = buffer[4+i]^SAP_MAGIC_NUM;
308
309   sapsector->data[SAP_SECTSIZE(format)] = buffer[4+i];
310   sapsector->data[SAP_SECTSIZE(format)+1] = buffer[4+i+1];
311}
312
313
314
315/* do_write_sector:
316 *  Performs the low-level write operation for the specified sector.
317 */
318static void do_write_sector(sapID id, sapsector_t *sapsector)
319{
320   unsigned char buffer[sizeof(sapsector_t)];
321   int format = ID_FORMAT(id);
322   int i;
323
324   /* fill in the write buffer */
325   buffer[0] = sapsector->format;
326   buffer[1] = sapsector->protection;
327   buffer[2] = sapsector->track;
328   buffer[3] = sapsector->sector;
329
330   for (i=0; i<SAP_SECTSIZE(format); i++)
331      buffer[4+i] = sapsector->data[i]^SAP_MAGIC_NUM;
332
333   /* compute the CRC */
334   do_crc(sapsector, format);
335   buffer[4+i] = sapsector->data[SAP_SECTSIZE(format)] = (crcpuk_temp>>8)&0xff;
336   buffer[4+i+1] = sapsector->data[SAP_SECTSIZE(format)+1] = crcpuk_temp&0xff;
337
338   fwrite(buffer, sizeof(char), SAP_EXTSECTSIZE(format), ID_FILE(id));
339}
340
341
342
343/* seek_pos:
344 *  Seeks the specified position in the SAP archive.
345 */
346static void seek_pos(sapID id, int track, int sect)
347{
348   int pos;
349
350   pos = SAP_HEADER_SIZE + (track*SAP_NSECTS + sect-1) * SAP_EXTSECTSIZE(ID_FORMAT(id));
351
352   fseek(ID_FILE(id), pos, SEEK_SET);
353}
354
355
356
357/************************************************/
358/***  private functions: Thomson DOS support  ***/
359/************************************************/
360
361
362
363/* get_file_size:
364 *  Returns the size in blocks of the specified file on disk.
365 */
366static int get_file_size(int format, int n, const unsigned char trk20_data[])
367{
368   const unsigned char *fat_data, *entry_data;
369   int block, size = 0;
370
371   fat_data = trk20_data + TO_FAT_START(format);
372   entry_data = trk20_data + TO_DIR_START(format) + n*TO_DIRENTRY_LENGTH;
373
374   block = entry_data[TO_FIRST_BLOCK];
375
376   while ((block<TO_NBLOCKS(format)) && (size<TO_FILESIZE_MAX(format))) {
377      size++;
378      block = fat_data[block];
379   }
380
381   return size;
382}
383
384
385
386/* get_dskf:
387 *  Returns the amount of free space in blocks on disk.
388 */
389static int get_dskf(int format, const unsigned char trk20_data[])
390{
391   const unsigned char *fat_data;
392   int i, dskf = 0;
393
394   fat_data = trk20_data + TO_FAT_START(format);
395
396   for (i=0; i<TO_NBLOCKS(format); i++) {
397      if (fat_data[i] == TO_TAG_FREE)
398         dskf++;
399   }
400
401   return dskf;
402}
403
404
405
406/* extract_dir_entry:
407 *  Extracts one dir entry and returns the number of characters.
408 */
409static int extract_dir_entry(int format, char buffer[], int buffer_size, int n, const unsigned char trk20_data[])
410{
411   const unsigned char *entry_data;
412   char name[TO_NAME_LENGTH+1], ext[TO_EXT_LENGTH+1], date[9], *comment;
413   unsigned char ftype, dtype;
414   int i, len, size;
415
416   entry_data = trk20_data + TO_DIR_START(format) + n*TO_DIRENTRY_LENGTH;
417
418   comment = malloc(TO_COMMENT_LENGTH+1);
419
420   if ((entry_data[TO_NAME] == 0) || (entry_data[TO_NAME] == TO_TAG_FREE))
421      return 0;
422
423   if (entry_data[TO_NAME] == 1) {
424      /* display only comment (if any) */
425      if (entry_data[TO_COMMENT]) {
426         comment[0] = '\0';
427         strncat(comment,(char*)(entry_data+TO_COMMENT), TO_COMMENT_LENGTH);
428
429         for (i=1; i<TO_COMMENT_LENGTH; i++) {
430            if (comment[i] == 18) { /* DC2 (repetition) ? */
431               len = strlen(comment);
432               comment = realloc(comment, len + comment[i+1]);
433               memmove(comment+i+comment[i+1], comment+i+2, len - (i+2) + 1);
434               memset(comment+i, comment[i-1], comment[i+1]);
435            }
436         }
437
438         return sprintf(buffer, "%30s%s\n", "", comment);
439      }
440      else {
441         return 0;
442      }
443   }
444
445   /* name */
446   name[0] = 0;
447   strncat(name,(char*)(entry_data+TO_NAME), TO_NAME_LENGTH);
448   clean_string((unsigned char*)name);
449
450   /* extension */
451   ext[0] = 0;
452   strncat(ext,(char*)(entry_data+TO_EXT), TO_EXT_LENGTH);
453   clean_string((unsigned char *)ext);
454
455   /* file type */
456   ftype = entry_data[TO_FILE_TYPE];
457   ftype = (ftype > 3 ? ftype_sym[4] : ftype_sym[ftype]);
458
459   /* data type */
460   dtype = entry_data[TO_DATA_TYPE];
461   dtype = (dtype == 0 ? dtype_sym[0] : (dtype == 0xFF ? dtype_sym[1] : dtype_sym[2]));
462
463   /* size */
464   size = get_file_size(format, n, trk20_data) * ((TO_BLOCKSIZE(format) + 1024) / 1024);
465
466   /* date */
467   if ((entry_data[TO_DATE_DAY] >= 1) && (entry_data[TO_DATE_DAY] <= 31) &&
468       (entry_data[TO_DATE_MONTH] >= 1) && (entry_data[TO_DATE_MONTH] <= 12) &&
469       (entry_data[TO_DATE_YEAR] <= 99))
470      sprintf(date, "%2d-%2d-%2d", entry_data[TO_DATE_DAY], entry_data[TO_DATE_MONTH], entry_data[TO_DATE_YEAR]);
471   else
472      date[0] = 0;
473
474   /* comment */
475   comment[0] = 0;
476   if (entry_data[TO_COMMENT])
477      strncat(comment,(char*)(entry_data+TO_COMMENT), TO_COMMENT_LENGTH);
478
479   /* display the entry */
480   i = sprintf(buffer, "%-8s %-3s %c %c %-3d %-8s %s\n", name, ext, ftype, dtype, size, date, comment);
481   free(comment);
482
483   return i;
484
485   (void) buffer_size;  /* DJGPP 2.03 lacks snprintf() */
486}
487
488
489
490/* get_filename:
491 *  Detects whether the file entry is valid and returns the filename if so.
492 */
493static int get_filename(char filename[], const unsigned char entry_data[])
494{
495   int i, j;
496
497   if ((entry_data[TO_NAME] == 0) || (entry_data[TO_NAME] == TO_TAG_FREE))
498      return -1;
499
500   /* name */
501   strncpy(filename,(char*)(entry_data+TO_NAME), TO_NAME_LENGTH);
502
503   for (i=0; i<TO_NAME_LENGTH; i++) {
504      if (filename[i] == 32)
505         break;
506   }
507
508   filename[i++] = '.';
509   filename[i] = 0;
510
511   /* extension */
512   strncat(filename,(char*)(entry_data+TO_EXT), TO_EXT_LENGTH);
513
514   for (j=0; j<TO_EXT_LENGTH; j++) {
515      if (filename[i+j] == 32)
516         break;
517   }
518
519   if (filename[i+j-1] == '.')
520      filename[i+j-1] = 0;
521   else
522      filename[i+j] = 0;
523
524   return 0;
525}
526
527
528
529/* decode_filename:
530 *  Decodes the specified raw filename into a valid dir entry.
531 */
532static void decode_filename(unsigned char entry_data[], const char filename[], int file_size)
533{
534   const char *p;
535   char shortname[128];
536   char ext[TO_EXT_LENGTH+1];
537   int i, type, len, ext_len = 0;
538   FILE *file;
539
540   /* zero the entry data */
541   memset(entry_data + TO_NAME, 32, TO_NAME_LENGTH + TO_EXT_LENGTH);
542   entry_data[TO_COMMENT] = 0;
543   memset(entry_data + TO_COMMENT+1, 32, TO_COMMENT_LENGTH-1);
544   memset(entry_data + TO_DATE_DAY, 0, TO_DIRENTRY_LENGTH - TO_DATE_DAY);
545
546   /* find short name */
547   len = strlen(filename);
548   p = filename + len - 1;   
549
550   while ((p>=filename) && (*p != '/'))
551      p--;
552
553   shortname[0] = 0;
554   strncat(shortname, p+1, sizeof(shortname) - 1);
555
556   /* find extension */
557   len = strlen(shortname);
558   p = shortname + len - 1;
559
560   while ((p>=shortname) && (*p != '.'))
561      p--;
562
563   if (p < shortname) {  /* no extension? */
564      memcpy(entry_data+TO_NAME, shortname, MIN(TO_NAME_LENGTH, len));
565      type = EXT_TYPE_MAX-1;
566   }
567   else {
568      /* name */
569      memcpy(entry_data+TO_NAME, shortname, MIN(TO_NAME_LENGTH, p - shortname));
570
571      /* extension */
572      ext_len = MIN(TO_EXT_LENGTH, shortname + len - 1 - p);
573      memcpy(entry_data+TO_EXT, p+1, ext_len);
574
575      /* build upper case extension */
576      ext[0] = 0;
577      strncat(ext, p+1, ext_len);
578      for (i=0; i<ext_len; i++)
579         ext[i] = toupper(ext[i]);
580
581      /* search for a standard extension */
582      for (type=0; type<EXT_TYPE_MAX-1; type++) {
583         if (strcmp(ext_type[type].tag, ext) == 0)
584            break;
585      }
586   }
587
588   /* set file and data types */
589   entry_data[TO_FILE_TYPE] = ext_type[type].ftype;
590   entry_data[TO_DATA_TYPE] = ext_type[type].dtype;
591
592   /* special treatment */
593   switch (type) {
594
595      case EXT_TYPE_BAS:
596         /* differentiate ASCII from BINARY data */
597         file = fopen(filename, "rb");
598         if (file) {
599            if ((fgetc(file) == '\r') && (fgetc(file) == '\n'))
600               entry_data[TO_DATA_TYPE] = DTYPE_ASCII;
601            fclose(file);
602         }
603         break;
604
605      case EXT_TYPE_CHG:
606         /* automatic launch + number of requested banks */
607         entry_data[TO_CHG_MODE] = 0x80 | ((file_size/TO_BANKSIZE)&0x3F);
608
609         /* 8-bit checksum */
610         for (i=0; i<TO_NAME_LENGTH; i++)
611            entry_data[TO_CHG_CHECKSUM] += entry_data[i];
612         break;
613   }
614}
615
616
617
618/* seek_file:
619 *  Seeks a file in the specified directory and returns its index number if found.
620 */
621static int seek_file(int format, const char filename[], const unsigned char dir_data[])
622{
623   char entry_name[TO_NAME_LENGTH + 1 + TO_EXT_LENGTH + 1];
624   int i;
625
626   for (i=0; i<TO_NDIRENTRIES(format); i++) {
627      if ((get_filename(entry_name, dir_data+i*TO_DIRENTRY_LENGTH) == 0) && (strcmp(entry_name, filename) == 0))
628         return i;
629   }
630
631   return -1;
632}
633
634
635
636/* wildcard handling code by Michael Bulkin (M.A.Bukin@inp.nsk.su) */
637#define WCD_MATCH_TRY 0
638#define WCD_MATCH_ONE 1
639#define WCD_MATCH_ANY 2
640
641
642struct WCD_MATCH_DATA
643{
644   int type;
645   const char *s1;
646   const char *s2;
647};
648
649
650
651/* wcdcmp:
652 *  Compares two strings ('*' matches any number of characters,
653 *  '?' matches any character).
654 */
655static int wcdcmp(const char *s1, const char *s2)
656{
657   static unsigned int size = 0;
658   static struct WCD_MATCH_DATA *data = NULL;
659   const char *s1end;
660   int index, c1, c2;
661
662   /* handle NULL arguments */
663   if ((!s1) && (!s2)) {
664      if (data) {
665         free(data);
666         data = NULL;
667      }
668
669      return 1;
670   }
671
672   s1end = s1 + strlen(s1);
673
674   /* allocate larger working area if necessary */
675   if (data && (size < strlen(s2))) {
676      free(data);
677      data = NULL;
678   }
679
680   if (!data) {
681      size = strlen(s2);
682      data = malloc(sizeof(struct WCD_MATCH_DATA) * size * 2 + 1);
683      if (!data)
684         return 1;
685   }
686
687   index = 0;
688   data[0].s1 = s1;
689   data[0].s2 = s2;
690   data[0].type = WCD_MATCH_TRY;
691
692   while (index >= 0) {
693      s1 = data[index].s1;
694      s2 = data[index].s2;
695      c1 = *s1;
696      c2 = *s2;
697
698      switch (data[index].type) {
699
700      case WCD_MATCH_TRY:
701         if (c2 == 0) {
702            /* pattern exhausted */
703            if (c1 == 0)
704               return 0;
705            else
706               index--;
707         }
708         else if (c1 == 0) {
709            /* string exhausted */
710            while (*s2 == '*')
711               s2++;
712            if (*s2 == 0)
713               return 0;
714            else
715               index--;
716         }
717         else if (c2 == '*') {
718            /* try to match the rest of pattern with empty string */
719            data[index++].type = WCD_MATCH_ANY;
720            data[index].s1 = s1end;
721            data[index].s2 = s2 + 1;
722            data[index].type = WCD_MATCH_TRY;
723         }
724         else if ((c2 == '?') || (c1 == c2)) {
725            /* try to match the rest */
726            data[index++].type = WCD_MATCH_ONE;
727            data[index].s1 = s1 + 1;
728            data[index].s2 = s2 + 1;
729            data[index].type = WCD_MATCH_TRY;
730         }
731         else
732            index--;
733         break;
734
735      case WCD_MATCH_ONE:
736         /* the rest of string did not match, try earlier */
737         index--;
738         break;
739
740      case WCD_MATCH_ANY:
741         /* rest of string did not match, try add more chars to string tail */
742         if (--data[index + 1].s1 >= s1) {
743            data[index + 1].type = WCD_MATCH_TRY;
744            index++;
745         }
746         else
747            index--;
748         break;
749
750      default:
751         /* this is a bird? This is a plane? No it's a bug!!! */
752         return 1;
753      }
754   }
755
756   return 1;
757}
758
759
760
761/* seek_wilcard:
762 *  Seeks a wildcard in the specified directory and returns its index number if found.
763 */
764static int seek_wildcard(int format, char filename[], const char pattern[], const unsigned char dir_data[])
765{
766   char entry_name[TO_NAME_LENGTH + 1 + TO_EXT_LENGTH + 1];
767   static int i = 0;
768   int j;
769
770   while (i<TO_NDIRENTRIES(format)) {
771      j = i++;
772
773      if ((get_filename(entry_name, dir_data+j*TO_DIRENTRY_LENGTH) == 0) && (wcdcmp(entry_name, pattern) == 0)) {
774         strcpy(filename, entry_name);
775         return j;
776      }
777   }
778
779   i = 0;
780
781   /* to avoid leaking memory */
782   wcdcmp(NULL, NULL);
783
784   return -1;
785}
786
787
788
789/* find_free_block_sym:
790 *  Finds a free block using a symmetrical search.
791 */
792static int find_free_block_sym(int format, unsigned char fat_data[])
793{
794   int block;
795
796   block = TO_NBLOCKS(format)/2 - 1;
797
798   while (block < TO_NBLOCKS(format)) {
799      if (fat_data[block] == TO_TAG_FREE)
800         return block;
801
802      if (fat_data[TO_NBLOCKS(format) - block - 2] == TO_TAG_FREE)  /* fat_data[-1] is always 0 */
803         return TO_NBLOCKS(format) - block - 2;
804
805      block++;
806   }
807
808   return -1;
809}
810
811
812
813/* find_free_block_rel:
814 *  Finds a free block using a relative search.
815 */
816static int find_free_block_rel(int format, int block, unsigned char fat_data[])
817{
818   if (block < TO_NBLOCKS(format)/2) {
819      while (block >= 0) {
820         if (fat_data[block] == TO_TAG_FREE)
821            break;
822
823         block--;
824      }
825   }
826   else {
827      while (block < TO_NBLOCKS(format)) {
828         if (fat_data[block] == TO_TAG_FREE)
829            break;
830
831         block++;
832      }
833   }
834
835   if ((block<0) || (block>=TO_NBLOCKS(format)))
836      block = find_free_block_sym(format, fat_data);
837
838   return block;
839}
840
841
842
843/* do_add_file:
844 *  Performs the low-level add operation for the specified file entry.
845 */
846static void do_add_file(sapID id, FILE *file, int file_size, unsigned char entry_data[], int n, unsigned char trk20_data[])
847{
848   sapsector_t sapsector;
849   unsigned char *fat_data;
850   unsigned int block, next_block;
851   int sect;
852   int dummy;
853
854   /* find the first block */
855   fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id));
856
857   block = find_free_block_sym(ID_FORMAT(id), fat_data);
858   entry_data[TO_FIRST_BLOCK] = block;
859
860   sapsector.format = 0;
861   sapsector.protection = 0;
862
863   /* write full blocks */
864   while (file_size > TO_BLOCKSIZE(ID_FORMAT(id))) {
865      sapsector.track =  block/2;
866      sapsector.sector = 1 + (block%2 ? TO_SECTOR_PER_BLOCK : 0);
867      seek_pos(id, sapsector.track, sapsector.sector);
868
869      for (sect=0; sect<TO_SECTOR_PER_BLOCK; sect++) {
870         dummy=fread(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file);
871         do_write_sector(id, &sapsector);
872         sapsector.sector++;
873      }
874
875      fat_data[block] = TO_TAG_RESERVED;  /* temporarily reserved */
876      next_block = find_free_block_rel(ID_FORMAT(id), block, fat_data);
877
878      fat_data[block] = next_block;
879      block = next_block;
880
881      file_size -= TO_BLOCKSIZE(ID_FORMAT(id));
882   }
883
884   /* write remaining full sectors */
885   sapsector.track = block/2;
886   sapsector.sector = 1 + (block%2 ? TO_SECTOR_PER_BLOCK : 0);
887   seek_pos(id, sapsector.track, sapsector.sector);
888
889   fat_data[block] = TO_END_BLOCK_OFFSET + 1;
890
891   while (file_size > TO_SECTSIZE(ID_FORMAT(id))) {
892      dummy=fread(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file);
893      do_write_sector(id, &sapsector);
894      sapsector.sector++;
895
896      fat_data[block]++;
897      file_size -= TO_SECTSIZE(ID_FORMAT(id));
898   }
899
900   /* write remaining individual bytes */
901   dummy=fread(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file);
902   do_write_sector(id, &sapsector);
903
904   entry_data[TO_END_SIZE] = file_size>>8;
905   entry_data[TO_END_SIZE+1] = file_size;
906
907   /* write the new entry into the directory */
908   memcpy(trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH, entry_data, TO_DIRENTRY_LENGTH);
909}
910
911
912
913/* do_delete_file:
914 *  Performs the low-level delete operation for the specified file entry and
915 *  returns the size of file.
916 */
917static int do_delete_file(sapID id, const char filename[], int n, unsigned char trk20_data[])
918{
919   unsigned char *fat_data, *entry_data;
920   int block, old_block, size = 0;
921
922   (void) filename;
923
924   fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id));
925   entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH;
926
927   /* delete the file entry */
928   entry_data[TO_NAME] = 0;
929
930   /* read start block */
931   block = entry_data[TO_FIRST_BLOCK];
932
933   /* mark FAT entries with the TO_TAG_FREE tag */
934   while ((block<TO_NBLOCKS(ID_FORMAT(id))) && (size<TO_FILESIZE_MAX(ID_FORMAT(id)))) {
935      old_block = block;
936      block = fat_data[block];
937      fat_data[old_block] = TO_TAG_FREE;
938      size++;
939   }
940
941   size *= TO_BLOCKSIZE(ID_FORMAT(id));
942
943   if (block > TO_END_BLOCK_OFFSET+1)
944      size += (block - TO_END_BLOCK_OFFSET - 1) * TO_SECTSIZE(ID_FORMAT(id));
945
946   size += (entry_data[TO_END_SIZE]<<8) + entry_data[TO_END_SIZE+1];
947
948   return size;
949}
950
951
952
953/* do_extract_file:
954 *  Performs the low-level extract operation for the specified file entry and
955 *  returns the size of file.
956 */
957static int do_extract_file(sapID id, const char filename[], int n, unsigned char trk20_data[])
958{
959   sapsector_t sapsector;
960   unsigned char *fat_data, *entry_data;
961   int sect, end_size, size = 0;
962   unsigned int block;
963   FILE *file;
964
965   fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id));
966   entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH;
967
968   /* read start block */
969   block = entry_data[TO_FIRST_BLOCK];
970
971   if ((file=fopen(filename, "wb")) == NULL) {
972      sap_errno = SAP_EPERM;
973      return 0;
974   }
975
976   /* extract full blocks */
977   while ((fat_data[block]<TO_NBLOCKS(ID_FORMAT(id))) && (size<TO_FILESIZE_MAX(ID_FORMAT(id)))) {
978      seek_pos(id, block/2, 1 + (block%2 ? TO_SECTOR_PER_BLOCK : 0));
979
980      for (sect=0; sect<TO_SECTOR_PER_BLOCK; sect++) {
981         do_read_sector(id, &sapsector);
982         fwrite(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file);
983      }
984
985      block = fat_data[block];
986      size++;
987   }
988
989   size *= TO_BLOCKSIZE(ID_FORMAT(id));
990
991   /* extract remaining full sectors */
992   seek_pos(id, block/2, 1 + (block%2 ? TO_SECTOR_PER_BLOCK : 0));
993
994   for (sect=0; sect<(fat_data[block] - TO_END_BLOCK_OFFSET - 1); sect++) {
995      do_read_sector(id, &sapsector);
996      fwrite(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file);
997      size += TO_SECTSIZE(ID_FORMAT(id));
998   }
999
1000   /* extract remaining individual bytes */
1001   do_read_sector(id, &sapsector);
1002   end_size = (entry_data[TO_END_SIZE]<<8) + entry_data[TO_END_SIZE+1];
1003   fwrite(sapsector.data, sizeof(char), end_size, file);
1004   size += end_size;
1005
1006   fclose(file);
1007
1008   return size;
1009}
1010
1011
1012
1013/************************************************/
1014/***       public low-level functions         ***/
1015/************************************************/
1016
1017
1018/* _ExtractDir:
1019 *  Extract the directory from track 20 and returns the number of lines.
1020 */
1021int _ExtractDir(char buffer[], int buffer_size, int drive, int density, const unsigned char trk20_data[])
1022{
1023   unsigned char disk_name[TO_NAME_LENGTH+1];
1024   int dskf, i, len, pos, lines = 0;
1025   int format = (density == 1 ? SAP_FORMAT2 : SAP_FORMAT1);
1026
1027   /* name of the volume */
1028   strncpy((char*)disk_name, (char*)trk20_data, TO_NAME_LENGTH);
1029
1030   if ((disk_name[0] == 0) || (disk_name[0] == TO_TAG_FREE)) {
1031      strcpy((char*)disk_name, "No Name ");
1032   }
1033   else {
1034      disk_name[TO_NAME_LENGTH] = 0;
1035      clean_string(disk_name);
1036   }
1037
1038   /* dskf */
1039   dskf = get_dskf(format, trk20_data) * ((TO_BLOCKSIZE(format) + 1024) / 1024);
1040
1041   /* header */
1042   pos = sprintf(buffer, "%s density   %d:%s DSKF = %d\n", (density == 1 ? "Single" : "Double"), drive, disk_name, dskf);
1043   lines++;
1044
1045   /* directory entries */
1046   for (i=0; i<TO_NDIRENTRIES(format); i++) {
1047      len = extract_dir_entry(format, buffer+pos, buffer_size-pos, i, trk20_data);
1048
1049      if (len>0) {
1050         pos += len;
1051         lines++;
1052      }
1053   }
1054
1055   return lines;
1056}
1057
1058
1059
1060/* _ForEachFile:
1061 *  Executes the specified callback funtion on each file matching the pattern.
1062 */
1063int _ForEachFile(sapID id, const char pattern[], sapfilecb_t callback, int save_back)
1064{
1065   char filename[TO_NAME_LENGTH + 1 + TO_EXT_LENGTH + 1];
1066   unsigned char trk20_data[SAP_TRACKSIZE1], *dir_data;
1067   int n, ret = 0;
1068
1069   switch (ID_STATE(id)) {
1070
1071      case NO_ARCHIVE:
1072         sap_errno = SAP_EINVAL;
1073         return 0;
1074
1075      case EMPTY_ARCHIVE:
1076         sap_errno = SAP_EBUSY;
1077         return 0;
1078
1079      case FILLED_ARCHIVE:
1080         sap_errno = SAP_EBUSY;
1081         return 0;
1082
1083      case FULL_ARCHIVE:
1084         break;
1085   }
1086
1087   /* read track 20 */
1088   sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1089
1090   dir_data = trk20_data + TO_DIR_START(ID_FORMAT(id));
1091
1092   if (strpbrk(pattern, "?*")) {  /* wildcards? */
1093      while ((n=seek_wildcard(ID_FORMAT(id), filename, pattern, dir_data)) >= 0)
1094         ret += callback(id, filename, n, trk20_data);
1095   }
1096   else {
1097      n = seek_file(ID_FORMAT(id), pattern, dir_data);
1098
1099      if (n<0)
1100         sap_errno = SAP_ENOENT;
1101      else
1102         ret = callback(id, pattern, n, trk20_data);
1103   }
1104
1105   if (save_back) {
1106      /* save track 20 */
1107      sap_WriteSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1108   }
1109
1110   return ret;
1111}
1112
1113
1114
1115/************************************************/
1116/***  API functions: physical format support  ***/
1117/************************************************/
1118
1119
1120/* OpenArchive:
1121 *  Opens an existing archive,
1122 *  returns the sapID on success or SAP_ERROR on error.
1123 */
1124sapID sap_OpenArchive(const char filename[], int *format)
1125{
1126   char header[SAP_HEADER_SIZE];
1127   sapID id;
1128   int dummy;
1129
1130   /* seek for an empty id */
1131   if (!(id=get_id())) {
1132      sap_errno = SAP_ETOOMANY;
1133      return SAP_ERROR;
1134   }
1135
1136   if ((ID_FILE(id)=fopen(filename, "rb+")) == NULL) {
1137      sap_errno = SAP_ENOENT;
1138      return SAP_ERROR;
1139   }
1140
1141   /* read the header */
1142   dummy=fread(header, sizeof(char), SAP_HEADER_SIZE, ID_FILE(id));
1143
1144   /* find the format */
1145   if ((header[0] != SAP_FORMAT1) && (header[0] != SAP_FORMAT2)) {
1146      fclose(ID_FILE(id));
1147      sap_errno = SAP_EBADF;
1148      return SAP_ERROR;
1149   }
1150
1151   *format = header[0];
1152   
1153   /* check the header */
1154   header[0] = 0;
1155
1156   if (strncmp(header, sap_header, SAP_HEADER_SIZE) != 0) {
1157      fclose(ID_FILE(id));
1158      sap_errno = SAP_EBADF;
1159      return SAP_ERROR;
1160   }
1161
1162   ID_FORMAT(id) = *format;
1163   ID_STATE(id) = FULL_ARCHIVE;
1164   ID_NTRACKS(id) = SAP_NTRACKS(*format);
1165
1166   return id;
1167}
1168
1169
1170
1171/* CreateArchive:
1172 *  Creates an archive skeleton and writes the header,
1173 *  returns the sapID on success or SAP_ERROR on error.
1174 */
1175sapID sap_CreateArchive(const char filename[], int format)
1176{
1177   char header[SAP_HEADER_SIZE];
1178   sapID id;
1179
1180   if ((format != SAP_FORMAT1) && (format != SAP_FORMAT2)) {
1181      sap_errno = SAP_EINVAL;
1182      return SAP_ERROR;
1183   }
1184
1185   /* seek for an empty id */
1186   if (!(id=get_id())) {
1187      sap_errno = SAP_ETOOMANY;
1188      return SAP_ERROR;
1189   }
1190
1191   if ((ID_FILE(id)=fopen(filename, "wb")) == NULL) {
1192      sap_errno = SAP_EPERM;
1193      return SAP_ERROR;
1194   }
1195
1196   /* write the header */
1197   memcpy(header, sap_header, SAP_HEADER_SIZE);
1198   header[0] = format;
1199
1200   fwrite(header, sizeof(char), SAP_HEADER_SIZE, ID_FILE(id));
1201
1202   ID_FILE(id) = freopen(filename, "rb+", ID_FILE(id));
1203   seek_pos(id, 0 ,1);
1204
1205   ID_FORMAT(id) = format;
1206   ID_STATE(id) = EMPTY_ARCHIVE;
1207   ID_NTRACKS(id) = 0;
1208
1209   return id;
1210}
1211
1212
1213
1214/* CloseArchive:
1215 *  Closes the archive, filling it up with empty tracks if needed,
1216 *  returns SAP_OK on success or SAP_ERROR on error.
1217 */
1218int sap_CloseArchive(sapID id)
1219{
1220   sapsector_t sapsector;
1221   int format = ID_FORMAT(id);
1222   int track, sect;
1223
1224   switch (ID_STATE(id)) {
1225
1226      case NO_ARCHIVE:
1227         sap_errno = SAP_EINVAL;
1228         return SAP_ERROR;
1229
1230      case EMPTY_ARCHIVE:
1231      case FILLED_ARCHIVE:
1232         sapsector.format = 0;
1233         sapsector.protection = 0;
1234         memset(sapsector.data, TO_FILLER_BYTE, SAP_SECTSIZE(format));
1235
1236         for (track=ID_NTRACKS(id); track<SAP_NTRACKS(format); track++) {
1237            for (sect=1; sect<=SAP_NSECTS; sect++) {
1238               sapsector.track = track;
1239               sapsector.sector = sect;
1240               do_write_sector(id, &sapsector);
1241            }
1242         }
1243         break;
1244
1245      case FULL_ARCHIVE:
1246         break;
1247   }
1248
1249   fclose(ID_FILE(id));
1250   ID_STATE(id) = NO_ARCHIVE;
1251
1252   return SAP_OK;
1253}
1254
1255
1256
1257/* FillArchive:
1258 *  Fills an empty archive sector by sector, it's up to
1259 *  the user to call it the right number of times,
1260 *  returns SAP_OK on success or SAP_ERROR on error.
1261 */
1262int sap_FillArchive(sapID id, sapsector_t *sapsector)
1263{
1264   static int sect;
1265
1266   switch (ID_STATE(id)) {
1267
1268      case NO_ARCHIVE:
1269         sap_errno = SAP_EINVAL;
1270         return SAP_ERROR;
1271
1272      case EMPTY_ARCHIVE:
1273         ID_STATE(id) = FILLED_ARCHIVE;
1274         sect = 1;
1275         /* no break */
1276
1277      case FILLED_ARCHIVE:
1278         do_write_sector(id, sapsector);
1279
1280         if (++sect == SAP_NSECTS+1) {
1281            sect = 1;
1282
1283            if (++ID_NTRACKS(id) == SAP_NTRACKS(ID_FORMAT(id)))
1284               ID_STATE(id) = FULL_ARCHIVE;
1285         }
1286         break;
1287
1288      case FULL_ARCHIVE:
1289         sap_errno = SAP_ENOSPC;
1290         return SAP_ERROR;
1291   } /* end of switch */
1292
1293   return SAP_OK;
1294}
1295
1296
1297
1298/* ReadSector:
1299 *  Reads the specified sector and returns SAP_OK or a flagged code:
1300 *    SAP_NO_STD_FMT: non standard format
1301 *    SAP_PROTECTED : protected sector
1302 *    SAP_BAD_SECTOR: bad sector identifiers
1303 *    SAP_CRC_ERROR : CRC error
1304 */
1305int sap_ReadSector(sapID id, int track, int sect, sapsector_t *sapsector)
1306{
1307   int flag = SAP_OK;
1308   int format = ID_FORMAT(id);
1309
1310   switch (ID_STATE(id)) {
1311
1312      case NO_ARCHIVE:
1313         sap_errno = SAP_EINVAL;
1314         return SAP_ERROR;
1315
1316      case EMPTY_ARCHIVE:
1317         sap_errno = SAP_EEMPTY;
1318         return SAP_ERROR;
1319
1320      case FILLED_ARCHIVE:
1321         sap_errno = SAP_EBUSY;
1322         return SAP_ERROR;
1323
1324      case FULL_ARCHIVE:
1325         break;
1326   }
1327
1328   seek_pos(id, track, sect);
1329   do_read_sector(id, sapsector);
1330
1331   if (sapsector->format != 0)
1332      flag |= SAP_NO_STD_FMT;
1333
1334   if (sapsector->protection != 0)
1335      flag |= SAP_PROTECTED;
1336
1337   if ((sapsector->track != track) || (sapsector->sector != sect))
1338      flag |= SAP_BAD_SECTOR;
1339
1340   do_crc(sapsector, format);
1341
1342   if ((sapsector->data[SAP_SECTSIZE(format)] != ((crcpuk_temp>>8)&0xff))
1343        || (sapsector->data[SAP_SECTSIZE(format)] != (crcpuk_temp&0xff)))
1344      flag |= SAP_CRC_ERROR;
1345
1346   return flag;
1347}
1348
1349
1350
1351/* ReadSectorEx:
1352 *  Reads one or more sectors from the same track,
1353 *  returns SAP_OK on success or SAP_ERROR on error.
1354 */
1355int sap_ReadSectorEx(sapID id, int track, int sect, int nsects, unsigned char data[])
1356{
1357   sapsector_t sapsector;
1358   int format = ID_FORMAT(id);
1359   int i;
1360
1361   switch (ID_STATE(id)) {
1362
1363      case NO_ARCHIVE:
1364         sap_errno = SAP_EINVAL;
1365         return SAP_ERROR;
1366
1367      case EMPTY_ARCHIVE:
1368         sap_errno = SAP_EEMPTY;
1369         return SAP_ERROR;
1370
1371      case FILLED_ARCHIVE:
1372         sap_errno = SAP_EBUSY;
1373         return SAP_ERROR;
1374
1375      case FULL_ARCHIVE:
1376         break;
1377   }
1378
1379   seek_pos(id, track, sect);
1380
1381   for (i=0; i<nsects; i++) {
1382      do_read_sector(id, &sapsector);
1383      memcpy(data + i*SAP_SECTSIZE(format), sapsector.data, SAP_SECTSIZE(format));
1384   }
1385
1386   return SAP_OK;
1387}
1388
1389
1390
1391/* WriteSector:
1392 *  Writes the specified sector and
1393 *  returns SAP_OK on success or SAP_ERROR on error.
1394 */
1395int sap_WriteSector(sapID id, int track, int sect, sapsector_t *sapsector)
1396{
1397   switch (ID_STATE(id)) {
1398
1399      case NO_ARCHIVE:
1400         sap_errno = SAP_EINVAL;
1401         return SAP_ERROR;
1402
1403      case EMPTY_ARCHIVE:
1404         sap_errno = SAP_EEMPTY;
1405         return SAP_ERROR;
1406
1407      case FILLED_ARCHIVE:
1408         sap_errno = SAP_EBUSY;
1409         return SAP_ERROR;
1410
1411      case FULL_ARCHIVE:
1412         break;
1413   }
1414
1415   seek_pos(id, track, sect);
1416   do_write_sector(id, sapsector);
1417
1418   return SAP_OK;
1419}
1420
1421
1422
1423/* WriteSectorEx:
1424 *  Writes one or more sectors into the same track,
1425 *  returns SAP_OK on success or SAP_ERROR on error.
1426 */
1427int sap_WriteSectorEx(sapID id, int track, int sect, int nsects, const unsigned char data[])
1428{
1429   sapsector_t sapsector;
1430   int format = ID_FORMAT(id);
1431   int i;
1432
1433   switch (ID_STATE(id)) {
1434
1435      case NO_ARCHIVE:
1436         sap_errno = SAP_EINVAL;
1437         return SAP_ERROR;
1438
1439      case EMPTY_ARCHIVE:
1440         sap_errno = SAP_EEMPTY;
1441         return SAP_ERROR;
1442
1443      case FILLED_ARCHIVE:
1444         sap_errno = SAP_EBUSY;
1445         return SAP_ERROR;
1446
1447      case FULL_ARCHIVE:
1448         break;
1449   }
1450
1451   seek_pos(id, track, sect);
1452
1453   sapsector.format = 0;
1454   sapsector.protection = 0;
1455   sapsector.track = track;
1456
1457   for (i=0; i<nsects; i++) {
1458      memcpy(sapsector.data, data + i*SAP_SECTSIZE(format), SAP_SECTSIZE(format));
1459      sapsector.sector = sect + i;
1460      do_write_sector(id, &sapsector);
1461   }
1462
1463   return SAP_OK;
1464}
1465
1466
1467
1468/************************************************/
1469/***  API functions: logical format support   ***/
1470/************************************************/
1471
1472
1473/* FormatArchive:
1474 *  Formats an archive using the Thomson BASIC DOS format,
1475 *  returns SAP_OK on success or SAP_ERROR on error.
1476 */
1477int sap_FormatArchive(sapID id, int capacity)
1478{
1479   int format = ID_FORMAT(id);
1480   int track, sect;
1481
1482   sapsector_t sapsector;
1483
1484   switch (ID_STATE(id)) {
1485
1486      case NO_ARCHIVE:
1487         sap_errno = SAP_EINVAL;
1488         return SAP_ERROR;
1489
1490      case EMPTY_ARCHIVE:
1491         break;
1492
1493      case FILLED_ARCHIVE:
1494         sap_errno = SAP_EBUSY;
1495         return SAP_ERROR;
1496
1497      case FULL_ARCHIVE:
1498         seek_pos(id, 0, 1);
1499         break;
1500   }
1501
1502   if (!(capacity == SAP_TRK40) && !(capacity == SAP_TRK80)) {
1503      sap_errno = SAP_EINVAL;
1504      return SAP_ERROR;
1505   }
1506
1507   sapsector.format = 0;
1508   sapsector.protection = 0;
1509   memset(sapsector.data, TO_FILLER_BYTE, SAP_SECTSIZE(format));
1510
1511   for (track=0; track<SAP_NTRACKS(format); track++) {
1512      for (sect=1; sect<SAP_NSECTS+1; sect++) {
1513         sapsector.track = track;
1514         sapsector.sector = sect;
1515         do_write_sector(id, &sapsector);
1516      }
1517   }
1518
1519   /* write track 20 */
1520   sapsector.track = 20;
1521   memset(sapsector.data, TO_TAG_FREE, SAP_SECTSIZE(format));
1522   seek_pos(id, 20, 1);
1523
1524   for (sect=1; sect<SAP_NSECTS+1; sect++) {
1525      sapsector.sector = sect;
1526      do_write_sector(id, &sapsector);
1527   }
1528
1529   /* FAT */
1530   sapsector.track = 20;
1531   sapsector.sector = 2;
1532   memset(sapsector.data, TO_TAG_RESERVED, SAP_SECTSIZE(format));
1533
1534   /* first byte */
1535   sapsector.data[0] = 0;
1536
1537   /* lower zone */
1538   memset(sapsector.data + 1, TO_TAG_FREE, 40);
1539
1540   /* upper zone */
1541   if (capacity == SAP_TRK80)  /* not (format == SAP_FORMAT1) */
1542      memset(sapsector.data + 43, TO_TAG_FREE, TO_NBLOCKS1 - 43 + 1);
1543   else
1544      memset(sapsector.data + 43, TO_TAG_FREE, TO_NBLOCKS2 - 43 + 1);
1545
1546   seek_pos(id, 20, 2);
1547   do_write_sector(id, &sapsector);
1548
1549   ID_STATE(id) = FULL_ARCHIVE;
1550   ID_NTRACKS(id) = SAP_NTRACKS(format);
1551
1552   return SAP_OK;
1553}
1554
1555
1556
1557/* ListArchive:
1558 *  Builds a list of files contained in the archive,
1559 *  returns the number of lines on success or 0 on error.
1560 */
1561int sap_ListArchive(sapID id, char buffer[], int buffer_size)
1562{
1563   unsigned char trk20_data[SAP_TRACKSIZE1];
1564
1565   switch (ID_STATE(id)) {
1566
1567      case NO_ARCHIVE:
1568         sap_errno = SAP_EINVAL;
1569         return 0;
1570
1571      case EMPTY_ARCHIVE:
1572         sap_errno = SAP_EEMPTY;
1573         return 0;
1574
1575      case FILLED_ARCHIVE:
1576         sap_errno = SAP_EBUSY;
1577         return 0;
1578
1579      case FULL_ARCHIVE:
1580         break;
1581   }
1582
1583   /* read track 20 */
1584   sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1585
1586   return _ExtractDir(buffer, buffer_size, 0, ID_FORMAT(id) == SAP_FORMAT1 ? 2 : 1, trk20_data);
1587}
1588
1589
1590
1591/* AddFile:
1592 *  Adds the specified file to the archive,
1593 *  returns the size of the file in bytes on success or 0 on error.
1594 */
1595int sap_AddFile(sapID id, const char filename[])
1596{
1597   unsigned char entry_data[TO_DIRENTRY_LENGTH];
1598   unsigned char trk20_data[SAP_TRACKSIZE1], *fat_data, *dir_data;
1599   int free_n=-1, prev_n=-1;
1600   int i, dskf, file_size = 0;
1601   FILE *file;
1602
1603   switch (ID_STATE(id)) {
1604
1605      case NO_ARCHIVE:
1606         sap_errno = SAP_EINVAL;
1607         return 0;
1608
1609      case EMPTY_ARCHIVE:
1610         sap_errno = SAP_EBUSY;
1611         return 0;
1612
1613      case FILLED_ARCHIVE:
1614         sap_errno = SAP_EBUSY;
1615         return 0;
1616
1617      case FULL_ARCHIVE:
1618         break;
1619   }
1620
1621   /* open the file */
1622   if ((file=fopen(filename, "rb")) == NULL) {
1623      sap_errno = SAP_ENOENT;
1624      return 0;
1625   }
1626
1627   /* find size of the file */
1628   while (fgetc(file) != EOF)
1629      file_size++;
1630
1631   if (file_size == 0) {
1632      fclose(file);
1633      sap_errno = SAP_ENFILE;
1634      return 0;
1635   }
1636
1637   fseek(file, 0, SEEK_SET);
1638
1639   /* decode the filename */
1640   decode_filename(entry_data, filename, file_size);
1641
1642   /* read track 20 */
1643   sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1644
1645   fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id));
1646   dir_data = trk20_data + TO_DIR_START(ID_FORMAT(id));
1647
1648   /* simultaneously seek already free entry and previous entry */
1649   for (i=0; i<TO_NDIRENTRIES(ID_FORMAT(id)); i++) {
1650      if ((dir_data[i*TO_DIRENTRY_LENGTH] == 0) || (dir_data[i*TO_DIRENTRY_LENGTH] == TO_TAG_FREE)) {
1651         if (free_n<0)
1652            free_n = i;
1653      }
1654      else {
1655         if (strncmp((char*)(dir_data+i*TO_DIRENTRY_LENGTH),(char*) entry_data, TO_NAME_LENGTH + TO_EXT_LENGTH) == 0) {
1656            prev_n = i;
1657            break;
1658         }
1659      }
1660   }
1661
1662   if ((free_n<0) && (prev_n<0)) {
1663      fclose(file);
1664      sap_errno = SAP_ENOSPC;
1665      return 0;
1666   }
1667
1668   /* test for enough free disk space */
1669   dskf = get_dskf(ID_FORMAT(id), trk20_data);
1670
1671   if (prev_n >=0)
1672      dskf += get_file_size(ID_FORMAT(id), prev_n, trk20_data);
1673
1674   if ((dskf*TO_BLOCKSIZE(ID_FORMAT(id))) < file_size) {
1675      sap_errno = SAP_EFBIG;
1676      fclose(file);
1677      return 0;
1678   }
1679
1680   /* delete previous entry */
1681   if (prev_n >= 0) {
1682      do_delete_file(id, filename, prev_n, trk20_data);
1683      free_n = prev_n;
1684   }
1685
1686   /* phew! we can finally add the file... */
1687   do_add_file(id, file, file_size, entry_data, free_n, trk20_data);
1688
1689   /* update directory and FAT */
1690   sap_WriteSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1691
1692   fclose(file);
1693
1694   return file_size;
1695}
1696
1697
1698
1699/* DeleteFile:
1700 *  Deletes the specified files from the archive,
1701 *  returns the total size of the files in bytes on success or 0 on error.
1702 */
1703int sap_DeleteFile(sapID id, const char pattern[])
1704{
1705   return _ForEachFile(id, pattern, do_delete_file, 1);
1706}
1707
1708
1709
1710/* ExtractFile:
1711 *  Extracts the specified files from the archive,
1712 *  returns the total size of the files in bytes on success or 0 on error.
1713 */
1714int sap_ExtractFile(sapID id, const char pattern[])
1715{
1716   return _ForEachFile(id, pattern, do_extract_file, 0);
1717}
1718
1719
1720
1721/* GetFileInfo:
1722 *  Fills in a structure with the info for the specified file
1723 *  returns SAP_OK on success or SAP_ERROR on error.
1724 */
1725int sap_GetFileInfo(sapID id, const char filename[], sapfileinfo_t *info)
1726{
1727   unsigned char trk20_data[SAP_TRACKSIZE1], *dir_data, *fat_data, *entry_data;
1728   struct tm tim;
1729   int n, block, i=0;
1730
1731   switch (ID_STATE(id)) {
1732
1733      case NO_ARCHIVE:
1734         sap_errno = SAP_EINVAL;
1735         return 0;
1736
1737      case EMPTY_ARCHIVE:
1738         sap_errno = SAP_EEMPTY;
1739         return 0;
1740
1741      case FILLED_ARCHIVE:
1742         sap_errno = SAP_EBUSY;
1743         return 0;
1744
1745      case FULL_ARCHIVE:
1746         break;
1747   }
1748
1749   /* read track 20 */
1750   sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data);
1751
1752   dir_data = trk20_data + TO_DIR_START(ID_FORMAT(id));
1753
1754   n = seek_file(ID_FORMAT(id), filename, dir_data);
1755
1756   if (n<0) {
1757      sap_errno = SAP_ENOENT;
1758      return SAP_ERROR;
1759   }
1760
1761   fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id));
1762   entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH;
1763
1764   /* block chain */
1765   info->nblocks = get_file_size(ID_FORMAT(id), n, trk20_data);
1766   info->block = malloc(info->nblocks*sizeof(int));
1767
1768   block = entry_data[TO_FIRST_BLOCK];
1769
1770   while ((block<TO_NBLOCKS(ID_FORMAT(id))) && (i<TO_FILESIZE_MAX(ID_FORMAT(id)))) {
1771      info->block[i++] = block;
1772      block = fat_data[block];
1773   }
1774
1775   /* common attributes */
1776   info->size = info->nblocks*TO_BLOCKSIZE(ID_FORMAT(id)) + entry_data[TO_END_SIZE];
1777   info->file_type = entry_data[TO_FILE_TYPE];
1778   info->data_type = entry_data[TO_DATA_TYPE];
1779
1780   /* date */
1781   if ((entry_data[TO_DATE_DAY] >= 1) && (entry_data[TO_DATE_DAY] <= 31) &&
1782       (entry_data[TO_DATE_MONTH] >= 1) && (entry_data[TO_DATE_MONTH] <= 12) &&
1783       (entry_data[TO_DATE_YEAR] <= 99)) {
1784      memset(&tim, 0, sizeof(struct tm));
1785      tim.tm_mday = entry_data[TO_DATE_DAY];
1786      tim.tm_mon = entry_data[TO_DATE_MONTH];
1787      tim.tm_year = entry_data[TO_DATE_YEAR];
1788      info->date = mktime(&tim);
1789   }
1790   else {
1791      info->date = 0;
1792   }
1793
1794   /* comment */
1795   info->comment[0] = '\0';
1796   if (entry_data[TO_COMMENT])
1797      strncat(info->comment,(char*)( entry_data+TO_COMMENT), TO_COMMENT_LENGTH);
1798
1799   return SAP_OK;
1800}
1801
Note: See TracBrowser for help on using the repository browser.