1 #ifndef ZLIB_DEMO
2 // preserve.cpp                           Copyright (C) Codemist, 1990-2021
3 #else
4 // zlibdemo.cpp                           Copyright (C) Codemist, 1990-2021
5 #endif
6 
7 // The file preserve.cpp can be preprocessed to generate zlibdemo.cpp,
8 // which is why the header line above is "strange".
9 
10 
11 /**************************************************************************
12  * Copyright (C) 2021, Codemist.                         A C Norman       *
13  *                                                                        *
14  * Redistribution and use in source and binary forms, with or without     *
15  * modification, are permitted provided that the following conditions are *
16  * met:                                                                   *
17  *                                                                        *
18  *     * Redistributions of source code must retain the relevant          *
19  *       copyright notice, this list of conditions and the following      *
20  *       disclaimer.                                                      *
21  *     * Redistributions in binary form must reproduce the above          *
22  *       copyright notice, this list of conditions and the following      *
23  *       disclaimer in the documentation and/or other materials provided  *
24  *       with the distribution.                                           *
25  *                                                                        *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    *
27  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      *
28  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS      *
29  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE         *
30  * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,   *
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,   *
32  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS  *
33  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
34  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR  *
35  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF     *
36  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH   *
37  * DAMAGE.                                                                *
38  *************************************************************************/
39 
40 // $Id: preserve.cpp 5736 2021-03-16 10:41:22Z arthurcnorman $
41 
42 
43 #ifndef ZLIB_DEMO
44 
45 // If this file is compiled with ZLIB_DEMO defined it will be a program that
46 // can be used as either
47 //     zlibdemo source dest
48 // to compress the source file and create the destination one, or
49 //     slibdemo -d source dest
50 // to uncompress from source to dest.
51 //
52 // I will use "unifdef" to create the separate file zlibdemo.cpp from this one
53 // but this is the master version...
54 
55 
56 #include "headers.h"
57 
58 // The following extra includes should probably be hidden away elsewere
59 // and/or abtracted away a bit. They are here so I can check if a file-name
60 // refers to a directory.
61 
62 #include <sys/stat.h>
63 #include <sys/types.h>
64 #include <zlib.h>
65 
66 #ifndef S_IFMT
67 # ifdef __S_IFMT
68 #  define S_IFMT __S_IFMT
69 # endif
70 #endif
71 
72 #ifndef S_IFDIR
73 # ifdef __S_IFDIR
74 #  define S_IFDIR __S_IFDIR
75 # endif
76 #endif
77 
78 #ifndef S_IFREG
79 # ifdef __S_IFREG
80 #  define S_IFREG __S_IFREG
81 # endif
82 #endif
83 
84 #ifndef S_ISLNK
85 # ifdef S_IFLNK
86 #  ifdef S_IFMT
87 #   define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
88 #  endif
89 # endif
90 #endif
91 
92 #ifdef BUILTIN_IMAGE
93 
94 // The following included file should define
95 //    const unsigned char *reduce_image = "....";
96 //    #define REDUCE_IMAGE_SIZE nnn
97 // where the data is that which might otherwise have lived in a file.
98 
99 #include "image.cpp"
100 #endif
101 
102 #else // ZLIB_DEMO
103 
104 // Free-standing demonstration of how I use zlib to compress image files.
105 
106 #include <cstdio>
107 #include <zlib.h>
108 #include <cstring>
109 #include <cassert>
110 
111 
112 std::FILE *src, *dest;
113 
Igetc()114 static int Igetc()
115 {   return std::getc(src);
116 }
117 
Iputc(int ch)118 static bool Iputc(int ch)
119 {   return (std::putc(ch, dest) == EOF);
120 }
121 
Iread(void * buff,size_t size)122 bool Iread(void *buff, size_t size)
123 // Reads (size) bytes into the indicated buffer.  Returns true if
124 // if fails to read the expected number of bytes.
125 {   unsigned char *p = reinterpret_cast<unsigned char *>(buff);
126     while (size > 0)
127     {   int c;
128         if ((c = Igetc()) == EOF) return true;
129         *p++ = c;
130         size--;
131     }
132     return false;
133 }
134 
Iwrite(const void * buff,size_t size)135 bool Iwrite(const void *buff, size_t size)
136 // Writes (size) bytes from the given buffer, returning true if trouble.
137 {   const unsigned char *p = reinterpret_cast<const unsigned char *>
138                              (buff);
139     for (size_t i=0; i<size; i++)
140         if (Iputc(p[i])) return true;
141     return false;
142 }
143 
144 #endif
145 
146 // I will use zlib to compress image files. The code here arranges to
147 // buffer chunks of data for compressing or decompressing and adds
148 // CRC checking so that corrupted data can be noticed.
149 
150 #define CHUNK ((size_t)16384)
151 
152 static z_stream strm;
153 static unsigned char in[CHUNK];
154 static unsigned char out[CHUNK];
155 
Zreturncode(int rc)156 const char *Zreturncode(int rc)
157 {   switch (rc)
158 {       default:
159             return "Unknown return code from zlib";
160         case Z_OK:
161             return "OK";
162         case Z_STREAM_END:
163             return "STREAM_END";
164         case Z_NEED_DICT:
165             return "NEED_DICT";
166         case Z_ERRNO:
167             return "ERRNO";
168         case Z_STREAM_ERROR:
169             return "STREAM_ERROR";
170         case Z_DATA_ERROR:
171             return "DATA_ERROR";
172         case Z_MEM_ERROR:
173             return "MEM_ERROR";
174         case Z_BUF_ERROR:
175             return "BUF_ERROR";
176         case Z_VERSION_ERROR:
177             return "VERSION_ERROR";
178     }
179 }
180 
def_init()181 bool def_init()
182 {   strm.zalloc = Z_NULL;
183     strm.zfree = Z_NULL;
184     strm.opaque = Z_NULL;
185     int rc = deflateInit(&strm, Z_BEST_COMPRESSION);
186     strm.avail_in = 0;
187     return (rc != Z_OK);
188 }
189 
190 // Zputc will return true if it FAILS.
191 
Zputc(int ch)192 bool Zputc(int ch)
193 {   in[strm.avail_in++] = ch;
194     if (strm.avail_in != CHUNK) return false; // Just buffer the data
195     strm.next_in = in;
196     do
197     {   strm.next_out = out;
198         strm.avail_out = CHUNK;
199 // Here I know I have plenty of space in the output buffer, and furthermore
200 // I should have CHUNK of data in the input buffer. So progress should be
201 // possible and hence Z_BUF_ERROR should never be returned. Z_STREAM_END
202 // can only arise if Z_FINISH had been passed as the flush input to deflate,
203 // and Z_STREAM_ERROR is a genuine error...
204         int rc = deflate(&strm, Z_NO_FLUSH);
205         if (rc != Z_OK) return true;
206         unsigned int n = CHUNK - strm.avail_out;
207 // Sometimes even though I have provided a whole chunk of input there
208 // will not (yet) be any output available. I do not want to emit a block
209 // of length zero both because that would be silly and because I use a
210 // block-length of zero to signify end of stream.
211         if (n != 0)
212         {
213 // Write the length of the block as 2 bytes.
214             if (Iputc(n >> 8)) return true;
215             if (Iputc(n)) return true;
216 // Calculate and write a CRC for this block.
217             int crc_out = crc32(crc32(0, nullptr, 0), out, n);
218             if (Iputc(crc_out>>24)) return true;
219             if (Iputc(crc_out>>16)) return true;
220             if (Iputc(crc_out>>8)) return true;
221             if (Iputc(crc_out)) return true;
222             if (Iwrite(out, n)) return true;
223         }
224 // I will keep going until I have used up all this input block.
225     }
226     while (strm.avail_in != 0);
227     return false;
228 }
229 
def_finish()230 bool def_finish()
231 {   do
232     {   strm.next_in = in;
233         strm.avail_out = CHUNK;
234         strm.next_out = out;
235         int rc;
236         if ((rc = deflate(&strm, Z_FINISH)) != Z_OK &&
237             rc != Z_STREAM_END) return true;
238         size_t n = CHUNK - strm.avail_out;
239         if (n != 0)
240         {   if (Iputc(n >> 8)) return true;
241             if (Iputc(n)) return true;
242             int crc_out = crc32(crc32(0, nullptr, 0), out, n);
243             if (Iputc(crc_out>>24)) return true;
244             if (Iputc(crc_out>>16)) return true;
245             if (Iputc(crc_out>>8)) return true;
246             if (Iputc(crc_out)) return true;
247             if (Iwrite(out, n)) return true;
248         }
249     }
250     while (strm.avail_out == 0);
251     int rc = deflateEnd(&strm);
252     if (rc != Z_OK) return true;
253     if (Iputc(0)) return true;  // Termination bytes
254     if (Iputc(0)) return true;
255     return false;
256 }
257 
Zwrite(const void * b,size_t n)258 bool Zwrite(const void *b, size_t n)
259 {   const char *c = reinterpret_cast<const char *>(b);
260     while (n-- != 0) if (Zputc(*c++)) return true;
261     return false;
262 }
263 
264 static size_t n_out;
265 static unsigned char *p_out;
266 static int z_eof;
267 
inf_init()268 bool inf_init()
269 {   strm.zalloc = Z_NULL;
270     strm.zfree = Z_NULL;
271     strm.opaque = Z_NULL;
272     n_out = 0;
273     p_out = out;
274     z_eof = 0;
275     int rc = inflateInit(&strm);
276     strm.avail_in = 0;
277     strm.next_in = in;
278     return (rc != Z_OK);
279 }
280 
281 // Although the value of EOF on *many* systems will be -1, the standards do
282 // not guarantee that. So for Zgetc() I will arrange that I explictly return
283 // (-1) for end-of-file and error conditions, so that I can rely on that.
284 
Zgetc()285 int Zgetc()
286 {   for (;;)
287     {   if (n_out !=
288             0)    // Use byte from the current decompressed chunk.
289         {   n_out--;
290             return *p_out++;
291         }
292         if (z_eof != 0) return (z_eof = -1);
293 // Here the zlib output buffer is empty. If the input buffer contains anything
294 // at all I will just call inflate() again.
295 // I do not believe that inflate can ever leave input untouched in its input
296 // buffer while there is space in the output buffer.
297         if (strm.avail_in == 0)
298         {
299 // The compressed material is arrabnged in blocks. Each block starts with
300 // a 2-byte length field.
301             int ch, n = Igetc();
302 // If I get an end of file (or error) report when trying to read part of the
303 // length information then the stream is corrupted, and I just return and end
304 // of file marker. When I do so I set z_eof so that further calls to Zgetc()
305 // will also return (-1).
306             if (n == EOF) return (z_eof = -1);
307             ch = Igetc();
308             if (ch == EOF) return (z_eof = -1);
309             n = (n << 8) | ch;
310 // If the block-length is 0 we have reached the end of the stream. Because
311 // I am trying to read data here I must have already emitted all the bytes
312 // that could be generated from everything that had been seen earlier, so
313 // I can return an end-of-file marker.
314             if (n == 0) return (z_eof = -1);
315 // Following the 2-byte length there is a 4-byte CRC. Again I watch carefully
316 // so that an EOF while trying to read this will be reported back to the
317 // caller (just as and end of file condition).
318             int crc_needed = Igetc();
319             if (crc_needed == EOF) return (z_eof = -1);
320             for (int i=0; i<3; i++)
321             {   int w = Igetc();
322                 if (w == EOF) return (z_eof = -1);
323                 crc_needed = (crc_needed << 8) | w;
324             }
325 // Now I read the number of bytes I have been told to expect. If I am not
326 // able to read thet many it is an error.
327             if (Iread(in, n)) return (z_eof = -1);
328 // Compute a CRC on the block just read and complain if it is not as
329 // expected.
330             int crc = crc32(crc32(0, nullptr, 0), in, n);
331             if (crc != crc_needed) return (z_eof = -1);
332 // Set the zlib input buffer information so that this new block can be
333 // processed. There will be at least 1 new byte of data!
334             strm.next_in = in;
335             strm.avail_in = n;
336         }
337         strm.next_out = out;
338         strm.avail_out = CHUNK;
339 // Inflate whatever data is in the input buffer. I require that this EITHER
340 // removes at least one byte from the input buffer OR puts at least one
341 // byte in the output buffer, or that it return Z_STREAM_END. If it grabs
342 // input then it will eventually empty the input buffer so more will be read.
343 // Whenever it generates output that is offered to the caller.
344         int rc = inflate(&strm, Z_SYNC_FLUSH);
345 // I check the return code and exit if something has gone wrong.
346         if (rc != Z_OK &&
347             rc != Z_STREAM_END &&
348             rc != Z_BUF_ERROR) return (z_eof = -1);
349         if (rc == Z_STREAM_END) z_eof = (-1);
350         p_out = out;
351         n_out = CHUNK - strm.avail_out;
352     }
353 }
354 
Zread(void * b,size_t n)355 bool Zread(void *b, size_t n)
356 {   char *c = reinterpret_cast<char *>(b);
357     while (n-- != 0)
358     {   int n = Zgetc();
359         if (n == -1) return true;
360         *c++ = n;
361     }
362     return false;
363 }
364 
inf_finish()365 bool inf_finish()
366 {   return (inflateEnd(&strm) != Z_OK);
367 }
368 
369 #ifdef ZLIB_DEMO
370 
371 // compress or decompress from src to dest
372 
main(int argc,char ** argv)373 int main(int argc, char **argv)
374 {
375 // Check argument number and format. I expect either
376 //      zlibdemo src compressed-dest
377 // OR   zlibdemo -d compressed-src dest
378     if (argc < 3 ||
379         (argc == 3 && std::strcmp(argv[1], "-d") == 0) ||
380         (argc == 4 && std::strcmp(argv[1], "-d") != 0) ||
381         argc > 4)
382     {   std::fputs("Usage: zlibdemo [-d] source dest\n", stderr);
383         return 1;
384     }
385 
386     if (argc == 3)
387     {   src = std::strcmp(argv[1], "-") == 0 ? stdin : std::fopen(argv[1],
388                 "r");
389         assert(src != nullptr);
390         dest = std::fopen(argv[2], "wb");
391         assert(dest != nullptr);
392         def_init();
393         int ch;
394         while ((ch = std::getc(src)) != EOF) Zputc(ch);
395         def_finish();
396         std::fclose(src);
397         std::fclose(dest);
398         return 0;
399     }
400 
401     else
402     {   src = std::fopen(argv[2], "rb");
403         assert(src != nullptr);
404         dest = std::strcmp(argv[3], "-") == 0 ? stdout : std::fopen(argv[3],
405                 "w");
406         assert(dest != nullptr);
407         inf_init();
408         int ch;
409         while ((ch = Zgetc()) != -1) std::putc(ch, dest);
410         inf_finish();
411         std::fclose(src);
412         std::fclose(dest);
413         return 0;
414     }
415 }
416 
417 // end of zlibdemo.cpp
418 
419 
420 #else // ZLIB_DEMO
421 
422 // These routines pack multiple binary files into one big one.  The
423 // good effect is that I expect fseek to be faster than fopen, and as
424 // a result accessing fasl files will be faster.  The bad news is that
425 // when I update files I may need to compact them, and doing so will
426 // be very tedious.  In this model I do not permit arbitrary interleaving
427 // of read and write operations.
428 
set_dirused(directory_header * h,int v)429 static void set_dirused(directory_header *h, int v)
430 {   h->dirused = static_cast<unsigned char>(v & 0xff);
431     h->dirext = static_cast<unsigned char>((h->dirext & 0xf0) + ((
432             v>>8) & 0x0f));
433 }
434 
435 static directory empty_directory =
436 {
437 // This statically allocated "directory" exists to use as a fall-back if
438 // it proves impossible to allocate space for a genuine directory record.
439 // Thus it only comes into play in situations when I am in the process
440 // of failing fairly drastically!
441     {   'C', MIDDLE_INITIAL, 'L', IMAGE_FORMAT_VERSION,
442         0, 0, 0, 0,
443         {0, 0, 0, 0}
444     },
445     nullptr,
446     nullptr,
447     "EmptyFile",
448     {{"\nEmpty       ** *** not dated *** **"}}
449 };
450 
451 // In a way that may look clumsy I store file offsets and lengths as
452 // sequences of three or four characters.  The object of this
453 // explicit control over memory layout is so that directories produced by
454 // this code have a layout that is not sensitive to the byte-order used
455 // by the computer involved.  I also put a few newline characters into
456 // my directory structure so that if one uses an ordinary text editor to
457 // inspect an image file the set of modules and their datestamps should
458 // be easily visible.
459 
bits32(char * v)460 static int32_t bits32(char *v)
461 {   int32_t r = v[3] & 0xff;
462     r = (r << 8) | (v[2] & 0xff);
463     r = (r << 8) | (v[1] & 0xff);
464     return (r << 8) | (v[0] & 0xff);
465 }
466 
bits24(char * v)467 static int32_t bits24(char *v)
468 {   int32_t r = v[2] & 0xff;
469     r = (r << 8) | (v[1] & 0xff);
470     return (r << 8) | (v[0] & 0xff);
471 }
472 
setbits32(char * v,int32_t r)473 static void setbits32(char *v, int32_t r)
474 {   *v++ = static_cast<char>(r);
475     *v++ = static_cast<char>(r >> 8);
476     *v++ = static_cast<char>(r >> 16);
477     *v   = static_cast<char>(r >> 24);
478 }
479 
setbits24(char * v,int32_t r)480 static void setbits24(char *v, int32_t r)
481 {   *v++ = static_cast<char>(r);
482     *v++ = static_cast<char>(r >> 8);
483     *v   = static_cast<char>(r >> 16);
484 }
485 
486 static directory *current_input_directory;   // not used!
487 static int nativedir = 0;
488 static directory_entry *current_output_entry;
489 static directory *current_output_directory = nullptr;
490 static bool any_output_request;
491 static char would_be_output_directory[DIRNAME_LENGTH];
492 
493 #define I_INACTIVE 0
494 #define I_READING  1
495 #define I_WRITING  2
496 
497 static int Istatus = I_INACTIVE;
498 
499 #ifdef BUILTIN_IMAGE
500 const unsigned char *binary_read_filep;
501 #else
502 std::FILE *binary_read_file;
503 #endif
504 std::FILE *binary_write_file;
505 static long int read_bytes_remaining, write_bytes_written;
506 
make_empty_directory(const char * name)507 static directory *make_empty_directory(const char *name)
508 // The sole purpose of this empty directory is to carry with it the
509 // name of the file that I had tried to open.
510 {   directory *d  = reinterpret_cast<directory *>(
511         new (std::nothrow) char[sizeof(directory)]);
512     if (d == nullptr) return &empty_directory;
513     d->h.C = 'C'; d->h.S = MIDDLE_INITIAL; d->h.L = 'L';
514     d->h.version = IMAGE_FORMAT_VERSION;
515     d->h.dirsize = 0;
516     d->h.dirused = 0;
517     d->h.dirext = 0;
518     d->h.updated = 0;   // NB read-only
519     d->f = nullptr;
520     d->full_filename = nullptr;
521     std::strncpy(d->filename, name, DIRNAME_LENGTH);
522     d->filename[DIRNAME_LENGTH-1] = 0;
523     std::memset(d->h.eof, 0, 4);
524     return d;
525 }
526 
make_pending_directory(const char * name,int pds)527 static directory *make_pending_directory(const char *name, int pds)
528 {   directory *d;
529     int n = sizeof(directory) + (DIRECTORY_SIZE-1)*sizeof(directory_entry);
530     int l = std::strlen(name) + 1 -
531             DIRNAME_LENGTH -
532             DIRECTORY_SIZE*sizeof(directory_entry);
533 // Here I extend the directory header with enough extra bytes to hold the
534 // full name of the file... Once the file has been opened the (potential)
535 // extra data becomes unnecessary. However with room for DIRECTORY_SIZE
536 // entries already it would seem bizarre if the path-name ever actually
537 // overflowed here.
538     if (l > 0) n += l;
539     d = reinterpret_cast<directory *>(new (std::nothrow) char[n]);
540     if (d == nullptr) return &empty_directory;
541     d->h.C = 'C'; d->h.S = MIDDLE_INITIAL; d->h.L = 'L';
542     d->h.version = IMAGE_FORMAT_VERSION;
543     d->h.dirsize = DIRECTORY_SIZE & 0xff;
544     d->h.dirused = 0;
545     d->h.dirext = (DIRECTORY_SIZE >> 4) & 0xf0;
546     d->h.updated = D_PENDING | D_WRITE_OK;
547     // Well I HOPE that writing will be OK
548     d->f = nullptr;
549     std::strcpy(d->filename, name);  // guaranteed enough space here
550     if (pds) d->full_filename = nullptr;
551     else
552     {   char *s = new (std::nothrow) char[std::strlen(name)+1];
553         if (s == nullptr) my_abort();
554         std::strcpy(s, name);
555         d->full_filename = s;
556     }
557     std::memset(d->h.eof, 0, 4);
558     return d;
559 }
560 
make_native_directory(const char * shortname,const char * fullname,int ro)561 static directory *make_native_directory(const char *shortname,
562                                         const char *fullname, int ro)
563 {   directory *d = reinterpret_cast<directory *>(
564         new (std::nothrow) char[sizeof(directory)]);
565     if (d == nullptr) return &empty_directory;
566     d->h.C = 'C'; d->h.S = MIDDLE_INITIAL; d->h.L = 'L';
567     d->h.version = IMAGE_FORMAT_VERSION;
568     d->h.dirsize = DIRECTORY_SIZE & 0xff;
569     d->h.dirused = 0;
570     d->h.dirext = (DIRECTORY_SIZE >> 4) & 0xf0;
571     d->h.updated = ro ? 0 : D_WRITE_OK;
572     d->f = nullptr;
573     std::strncpy(d->filename, shortname, sizeof(d->filename));
574     d->filename[DIRNAME_LENGTH-1] = 0;
575     {   char *s = new (std::nothrow) char[std::strlen(fullname)+1];
576         if (s == nullptr) my_abort();
577         std::strcpy(s, fullname);
578         d->full_filename = s;
579     }
580     std::memset(d->h.eof, 0, 4);
581     return d;
582 }
583 
clear_entry(directory_entry * d)584 static void clear_entry(directory_entry *d)
585 {   d->D_newline = NEWLINE_CHAR;
586     std::memset(&d->D_name, ' ', name_size);
587     std::memcpy(&d->D_name, "<Unused>", 8);
588     std::memset(&d->D_date, ' ', date_size);
589     (&d->D_date)[0] = '-';
590     std::memset(&d->D_position, 0, 4);
591     std::memset(&d->D_size, 0, 3);
592 }
593 
version_moan(int v)594 static bool version_moan(int v)
595 {   if (v == IMAGE_FORMAT_VERSION) return false;
596     return true;
597 }
598 
599 #ifdef BUILTIN_IMAGE
builtinread(void * b,size_t s,size_t n)600 static int builtinread(void *b, size_t s, size_t n)
601 {   size_t i;
602     unsigned char *w = reinterpret_cast<unsigned char *>(b);
603     for (i=0; i<n*s; i++)
604         *w++ = *binary_read_filep++;
605     return n;
606 }
607 
608 #endif
609 
open_pds(const char * name,int mode)610 directory *open_pds(const char *name, int mode)
611 // Given a file-name, open the associated file, make space for
612 // a directory and return same. This now has to detect if the file-name
613 // should refer to a directory rather than a composite file. This case
614 // arises if the file that is named already exists and is a directory OR
615 // if it does not exist and its name it specified with a trailing "/".
616 // The mode is one of PDS_INPUT, PDS_OUTPUT or PDS_PENDING where the last
617 // sets up for an output directory that must be created on first use.
618 // Also if BUILTING_DIRECTORY is true then only one image file can be used
619 // and that has to be the built-in one!
620 {   char expanded[LONGEST_LEGAL_FILENAME];
621     directory hdr, *d;
622     bool write_OK = false, fileExists, nameDir, fileDir;
623     struct stat buf;
624     std::FILE *f;
625     int l, i, n;
626     std::memset(expanded, 0, sizeof(expanded));
627     l = std::strlen(name);
628     nameDir = (name[l-1] == '/') || (name[l-1] == '\\');
629     f = nullptr;
630     process_file_name(expanded, name, l);
631     i = std::strlen(expanded) - 1;
632     if (expanded[i] == '/' ||
633         expanded[i] == '\\') expanded[i] = 0; // Trim any final "/"
634     fileExists = fileDir = false;
635 #ifdef BUILTIN_IMAGE
636     fileExists = true;
637     binary_read_filep = reduce_image;
638 #else
639     if (stat(expanded, &buf) != -1)
640     {   fileExists = true;
641         if ((buf.st_mode & S_IFMT) == S_IFDIR) fileDir = true;
642     }
643     if (nameDir && fileExists && !fileDir)
644         return make_empty_directory(expanded);
645     if (mode != PDS_INPUT)
646     {   any_output_request = true;
647         std::strncpy(would_be_output_directory, expanded, DIRNAME_LENGTH-1);
648         would_be_output_directory[DIRNAME_LENGTH-1] = 0;
649         if (fileExists && fileDir)
650             return make_native_directory(name, expanded, 0);
651         else if (nameDir && mode == PDS_PENDING)
652             return make_pending_directory(expanded, 0);
653         else if (nameDir)
654         {   Cmkdir(expanded);
655             return make_native_directory(name, expanded, 0);
656         }
657         else if (fileExists) f = std::fopen(expanded, "r+b");
658         else f = nullptr;
659         if (f != nullptr) write_OK = true;
660         else if (mode == PDS_PENDING)
661         {   f = std::fopen(expanded, "rb");
662             if (f == nullptr) return make_pending_directory(expanded, !nameDir);
663         }
664         else
665         {   f = std::fopen(expanded, "w+b");
666             if (f != nullptr) write_OK = true;
667         }
668     }
669 // If I wanted the file for input or if I tried it for output and failed
670 // then I open for input.
671     if (f == nullptr)
672     {   if (!fileExists) return make_empty_directory(expanded);
673         if (fileDir) return make_native_directory(name, expanded, 1);
674         f = std::fopen(expanded, "rb");
675     }
676 // If the file does not exist I will just hand back a directory that shows
677 // no files in it.  This seems as easy a thing to do at this stage as I can
678 // think of.  Maybe I should warn the user?
679     if (f == nullptr) return make_empty_directory(expanded);
680     std::fseek(f, 0, SEEK_SET);     // Ensure I am at start of the file
681 #endif
682     hdr.h.C = hdr.h.S = hdr.h.L = 0;
683 #ifdef BUILTIN_IMAGE
684     if (builtinread(&hdr.h, sizeof(directory_header), 1) != 1 ||
685 #else
686     if (std::fread(&hdr.h, sizeof(directory_header), 1, f) != 1 ||
687 #endif
688         hdr.h.C != 'C' ||
689         hdr.h.S != MIDDLE_INITIAL ||
690         hdr.h.L != 'L' ||
691 // Image format versions are somewhat delicate things. I will not change
692 // this format often or lightly and the tests I make will then be set up to
693 // cope with updates from the immediately previous version. The testing code
694 // will need review each time I consider such a change. For the current
695 // upgrade I will allow opening of files from version N-1, but I will
696 // specifically lock out reading an initial heap-image from such. The issue
697 // of people who start with an old file and then write a fresh image back into
698 // it will be viewed as too messy to worry about in detail, but I hope that
699 // I have made it so that writing a new base image (via PRESERVE) updates the
700 // version info.
701         version_moan(hdr.h.version) ||
702         get_dirused(hdr) > get_dirsize(hdr) ||
703         bits32(hdr.h.eof) < (int32_t)sizeof(directory_header))
704     {
705 // Here I did not find a satisfactory header to the directory.  If I wanted
706 // to open the file for input I just return an empty directory, otherwise I
707 // need to create a new one.
708         if (!write_OK) return make_empty_directory(expanded);
709 // This next bit is never used in the BUILTIN_DIRECTOT case
710         std::fseek(f, 0, SEEK_SET);
711         n = DIRECTORY_SIZE;      // Size for a directory
712         d = reinterpret_cast<directory *>(
713             new char[sizeof(directory)+(n-1)*sizeof(directory_entry)]);
714         if (d == nullptr) return &empty_directory;
715         d->h.C = 'C'; d->h.S = MIDDLE_INITIAL; d->h.L = 'L';
716         d->h.version = IMAGE_FORMAT_VERSION;
717         d->h.dirsize = static_cast<unsigned char>(n & 0xff);
718         d->h.dirused = 0;
719         d->h.dirext = static_cast<unsigned char>((n >> 4) & 0xf0);
720         d->h.updated = D_WRITE_OK | D_UPDATED;
721         d->full_filename = nullptr;  // A PDS not a native dircetory
722         for (i=0; i<n; i++) clear_entry(&d->d[i]);
723         if (std::fwrite(&d->h, sizeof(directory_header), 1, f) != 1)
724             return make_empty_directory(expanded);
725         if (std::fwrite(&d->d[0], sizeof(directory_entry), (size_t)n,
726                         f) != (size_t)n)
727             return make_empty_directory(expanded);
728         d->f = f;
729         std::strncpy(d->filename, expanded, DIRNAME_LENGTH);
730         d->filename[DIRNAME_LENGTH-1] = 0;
731         setbits32(d->h.eof, (int32_t)std::ftell(f));
732         return d;
733     }
734     hdr.h.updated = write_OK ? D_WRITE_OK : 0;
735     n = get_dirsize(hdr);
736     d = reinterpret_cast<directory *>(
737         new (std::nothrow) char[sizeof(directory)+
738                                 (n-1)*sizeof(directory_entry)]);
739     if (d == nullptr) return &empty_directory;
740     std::memcpy(&d->h, &hdr.h, sizeof(directory_header));
741 #ifdef BUILTIN_IMAGE
742     if (builtinread(&d->d[0], sizeof(directory_entry),
743                     (size_t)n) != (size_t)n)
744 #else
745     if (std::fread(&d->d[0], sizeof(directory_entry), (size_t)n,
746                    f) != (size_t)n)
747 #endif
748         return make_empty_directory(expanded);
749 // Here the directory seemed OK
750     d->f = f;
751     std::strncpy(d->filename, expanded, DIRNAME_LENGTH);
752     d->filename[DIRNAME_LENGTH-1] = 0;
753     d->full_filename = nullptr;
754 // For binary files ANSI specify that the values used with fseek and ftell
755 // are simple counts of the number of characters in the file, and hence
756 // it is proper to save ftell() values from one run to the next.
757     return d;
758 }
759 
unpending(directory * d)760 static int unpending(directory *d)
761 {   std::FILE *f;
762     int32_t i, n;
763     if (d->full_filename != nullptr)
764     {   Cmkdir(d->full_filename);
765         d->h.updated &= ~D_PENDING;
766         d->h.updated |= D_WRITE_OK;  // suppose directories always updatable
767         return false;
768     }
769     f = std::fopen(d->filename, "w+b");
770     if (f == nullptr) return true;
771     d->f = f;
772     d->filename[DIRNAME_LENGTH-1] = 0;  // truncate the name now
773     n = DIRECTORY_SIZE;      // Size for a directory
774 // (the next bits were done when the pending directory was first created
775 //  d->h.C = 'C'; d->h.S = MIDDLE_INITIAL; d->h.L = 'L';
776 //  d->h.version = IMAGE_FORMAT_VERSION;
777 //  d->h.dirsize = n & 0xff;
778 //  d->h.dirused = 0;
779 //  d->h.dirext = (n >> 4) & 0xf0;
780     d->h.updated = D_WRITE_OK | D_UPDATED;
781     for (i=0; i<n; i++) clear_entry(&d->d[i]);
782     if (std::fwrite(&d->h, sizeof(directory_header), 1, f) != 1)
783         return true;
784     if (std::fwrite(&d->d[0], sizeof(directory_entry), (size_t)n,
785                     f) != (size_t)n)
786         return true;
787     setbits32(d->h.eof, (int32_t)std::ftell(f));
788     return false;
789 }
790 
Iinit()791 void Iinit()
792 {   size_t i;
793     Istatus = I_INACTIVE;
794     current_input_directory = nullptr;
795     current_output_entry = nullptr;
796     current_output_directory = nullptr;
797 #ifdef BUILTIN_IMAGE
798     binary_read_filep = reduce_image;
799 #else
800     binary_read_file = binary_write_file = nullptr;
801 #endif
802     read_bytes_remaining = write_bytes_written = 0;
803     any_output_request = false;
804     std::strcpy(would_be_output_directory, "<unknown>");
805     for (i=0; i<fasl_files.size(); i++)
806     {   if (!fasl_files[i].inUse ||
807             fasl_files[i].name == nullptr) continue;
808         else if (0x40000000+i == output_directory)
809             fasl_files[i].dir = open_pds(fasl_files[i].name, PDS_PENDING);
810         else
811             fasl_files[i].dir = open_pds(fasl_files[i].name,
812                                          i == output_directory ? PDS_OUTPUT :
813                                          PDS_INPUT);
814     }
815 }
816 
817 // The code here was originally written to support module names up to
818 // 11 characters, but it has now been extended to support long names as
819 // well.
820 // The mechanism used is as follows:
821 // The name field in a directory entry is 12 characters long. For system
822 // special pseudo-modules all 12 characters are used for a name, and the
823 // cases used at present are InitialImage and HelpDataFile. For all
824 // user names the name is padded with blanks, and so user names of up
825 // to 11 characters live in the field with the 12th character a blank.
826 // To support long names I use values 0x80 and up in this 12th position.
827 // (NB position 12 is at offset 11 because of zero-base counting!)
828 // The first segment of a long name uses 11 characters of the user name
829 // and puts 0x80 in the 12th. Subsequent directory entries are used
830 // to hold more characters of the name. These hold 11 characters in the
831 // name field and 24 in the date, and put values 0x81, 0x82 etc in
832 // character 12. They will have a zero length field, but their position
833 // field MUST match that of the first record. This requirement is so that
834 // when I sort a directory the parts of a long name are kept both
835 // together and in the correct order. The last part of a long name has
836 // 0xff in position 12. The result is that I can distinguish the case
837 // of
838 // (.) a regular username of up to 11 chars (blank in position 12)
839 // (.) a system special file (non-blank, but under 0x80 in posn 12)
840 // (.) the start of a long name (0x80)
841 // (.) a middle part of a long name (0x81 ...)
842 // (.) the final part of a long name (0xff).
843 // when I match names here I will only allow a long-name match if my
844 // directory is pointing at the first part of a long name.
845 // As a further minor usefulness here if I find a match the non-zero value I
846 // return is the number of entries involved.
847 
samename(const char * n1,directory * d,int j,size_t len)848 static int samename(const char *n1, directory *d, int j, size_t len)
849 // Compare the given names, given that n1 is of length len and n2 is
850 // blank-padded to exactly name_size characters. The special cases
851 // with n1 nullptr allow len to encode what I am looking for.
852 {   const char *n2 = &d->d[j].D_name;
853     size_t i, n, recs;
854     if (len == IMAGE_CODE)
855         return (std::memcmp(n2, "InitialImage", 12) == 0);
856     if (len == HELP_CODE)
857         return (std::memcmp(n2, "HelpDataFile", 12) == 0);
858     if (len == BANNER_CODE)
859         return (std::memcmp(n2, "Start-Banner", 12) == 0);
860     if ((intptr_t)len < 0)   // Hard code has never been fully supported
861         // and the use of "negative length codes" for
862         // using it is dodgy!
863     {   char hard[16];
864         std::sprintf(hard, "HardCode<%.2x>", static_cast<int>((-len) & 0xff));
865         return (std::memcmp(n2, hard, 12) == 0);
866     }
867     if ((n2[11] & 0xff) > 0x80) return 0;
868     n = 0;
869 #define next_char_of_name (n++ < len ? *n1++ : ' ')
870     for (i=0; i<11; i++)
871         if (n2[i] != next_char_of_name) return 0;
872     if ((n2[11] & 0x80) == 0) return ((n >= len) ? 1 : 0);
873     recs = 1;
874     do
875     {   n2 = &d->d[++j].D_name;
876         for (i=0; i<11; i++)
877             if (n2[i] != next_char_of_name) return 0;
878         for (i=12; i<36; i++)
879             if (n2[i] != next_char_of_name) return 0;
880         recs++;
881     }
882     while ((n2[11] & 0xff) != 0xff);
883 #undef next_char_of_name
884     if (n < len) return 0;
885     else return recs;
886 }
887 
fasl_file_name(char * nn,directory * d,const char * name,size_t len)888 static void fasl_file_name(char *nn, directory *d, const char *name,
889                            size_t len)
890 {   size_t np;
891     std::strcpy(nn, d->full_filename);
892     np = std::strlen(nn);
893 #ifdef WIN32
894     nn[np++] = '\\';
895 #else
896     nn[np++] = '/';
897 #endif
898     if (name == nullptr)
899     {   if (len == IMAGE_CODE) std::strcpy(&nn[np], "InitialImage");
900         else if (len == HELP_CODE) std::strcpy(&nn[np], "HelpDataFile");
901         else if (len == BANNER_CODE) std::strcpy(&nn[np], "Start-Banner");
902         else if ((intptr_t)len < 0) std::sprintf(&nn[np], "HardCode-%.2x",
903                     static_cast<int>((-len) & 0xff));
904     }
905     else
906     {   std::memcpy(&nn[np], name, len);
907         std::strcpy(&nn[np+len], ".fasl");
908     }
909 }
910 
911 
open_input(directory * d,const char * name,size_t len,size_t offset)912 static bool open_input(directory *d, const char *name, size_t len,
913                        size_t offset)
914 // Set up binary_read_file to access the given module, returning true
915 // if it was not found in the given directory. I used to pass the
916 // names "InitialImage" and "HelpDataFile" directly to this function, but
917 // to allow for long module names I am changing things so that these special
918 // cases are indicated by passing down a nullptr string for the name and giving
919 // an associated length of -1 or -2 (resp).
920 {   int i;
921     if (Istatus != I_INACTIVE || d == nullptr) return true;
922     nativedir = false;
923     if (d->full_filename != nullptr) // native directory mode
924     {   char nn[LONGEST_LEGAL_FILENAME];
925         std::memset(nn, 0, sizeof(nn));
926         fasl_file_name(nn, d, name, len);
927 #ifdef BUILTIN_IMAGE
928         binary_read_filep = reduce_image;
929         read_bytes_remaining = REDUCE_IMAGE_SIZE;
930 #else
931         if ((binary_read_file = std::fopen(nn, "rb")) == nullptr) return true;
932         std::fseek(binary_read_file, 0L, SEEK_END);
933         read_bytes_remaining = std::ftell(binary_read_file);
934 #endif
935 #ifdef BUILTIN_IMAGE
936         binary_read_filep = reduce_image + offset;
937 #else
938         std::fseek(binary_read_file, static_cast<long>(offset), SEEK_SET);
939 #endif
940         Istatus = I_READING;
941         nativedir = true;
942         return false;
943     }
944 // I use simple linear search to scan the directory - mainly because I
945 // expect directories to be fairly small and once I have found a file
946 // I will take a long while to process it, so any clumsiness here is
947 // not critical.
948 // This linear search may not be so smart any more, in that REDUCE ends
949 // up with about 750 modules, and if I store machine code versions of all
950 // of these for (say) 4 architectures I end up with almost 4000 directory
951 // entries...
952 // I will not allow myself to read from whichever file is currently open
953 // for output.
954 // Because samename() is careful to ensure it only reports a match when
955 // pointed at the start of a long name it is OK to search in steps of 1
956 // here.
957     for (i=0; i<get_dirused(*d); i++)
958     {   if (samename(name, d, i, len) &&
959             &d->d[i] != current_output_entry)
960         {
961 #ifdef BUILTIN_IMAGE
962             binary_read_filep = reduce_image;
963             read_bytes_remaining = bits24(&d->d[i].D_size);
964             binary_read_filep += bits32(&d->d[i].D_position)+offset;
965             i = 0;
966 #else
967             binary_read_file = d->f;
968             read_bytes_remaining = bits24(&d->d[i].D_size);
969             i = std::fseek(binary_read_file,
970                            bits32(&d->d[i].D_position)+offset, SEEK_SET);
971 #endif
972             if (i == 0)     // If fseek succeeded  it returned zero
973             {   Istatus = I_READING;
974                 return false;
975             }
976             else return true;
977         }
978     }
979     return true;
980 }
981 
IreInit()982 void IreInit()
983 {
984 }
985 
for_qsort(void const * aa,void const * bb)986 static int for_qsort(void const *aa, void const *bb)
987 {   directory_entry *a = (directory_entry *)aa,
988                          *b = (directory_entry *)bb;
989     long int ap = bits32(&a->D_position), bp = bits32(&b->D_position);
990     if (ap < bp) return -1;
991     else if (ap > bp) return 1;
992 // I make the position of the module in the image my primary sort key.
993 // Over-long module names are coped with by giving each part of the
994 // name the same position field, but marking the 12th character of the
995 // name field (D_space) with 0x80, 0x81 ... in extension records. Note that
996 // a regular short module name has a blank character there, while the special
997 // cases of "InitialImage" and "HelpDataFile" each have 'e' there,
998 // "Start-Banner" has 'r', while hard code has '>'.
999 // So bytes 0x80 and up are clearly (if hackily!) distinguished.
1000     ap = a->D_space & 0xff, bp = b->D_space & 0xff;
1001     if (ap < bp) return -1;
1002     else if (ap > bp) return 1;
1003     else return 0;
1004 }
1005 
sort_directory(directory * d)1006 static void sort_directory(directory *d)
1007 {   std::qsort(reinterpret_cast<void *>(d->d),
1008                (size_t)get_dirused(*d),
1009                sizeof(directory_entry), for_qsort);
1010 }
1011 
enlarge_directory(int current_size)1012 static directory *enlarge_directory(int current_size)
1013 {   int n = (3*current_size)/2;
1014     int newsize = sizeof(directory)+(n-1)*sizeof(directory_entry);
1015     int newpos = sizeof(directory_header)+n*sizeof(directory_entry);
1016 // enlarge_directory() is only called when an output library is known
1017 // to exist, so I do not need to check for that here.
1018     int dirno = library_number(qvalue(output_library));
1019     directory *d1 = fasl_files[dirno].dir;
1020     if (n > current_size+20) n = current_size+20;
1021     for (;;)
1022     {   directory_entry *first;
1023         std::FILE *f;
1024         char buffer[512];  // I hope this is not done too often, since this
1025         // is not a very big buffer size for the copy.
1026         int32_t firstpos, firstlen, newfirst, eofpos;
1027         sort_directory(d1);
1028         first = &d1->d[0];
1029         firstpos = bits32(&first->D_position);
1030         if (firstpos >= newpos) break;
1031 // Here I need to copy a module up to the end of the file to make room
1032 // for the enlarged directory
1033         firstlen = bits24(&first->D_size);
1034         newfirst = eofpos = bits32(d1->h.eof);
1035         f = d1->f;
1036         while (firstlen >= (int32_t)sizeof(buffer))
1037         {   std::fseek(f, firstpos, SEEK_SET);
1038             if (std::fread(buffer, sizeof(buffer), 1, f) != 1) return nullptr;
1039             std::fseek(f, eofpos, SEEK_SET);
1040             if (std::fwrite(buffer, sizeof(buffer), 1, f) != 1) return nullptr;
1041             firstlen -= sizeof(buffer);
1042             firstpos += sizeof(buffer);
1043             eofpos += sizeof(buffer);
1044         }
1045         if (firstlen != 0)
1046         {   std::fseek(f, firstpos, SEEK_SET);
1047             if (std::fread(buffer, firstlen, 1, f) != 1) return nullptr;
1048             std::fseek(f, eofpos, SEEK_SET);
1049             if (std::fwrite(buffer, firstlen, 1, f) != 1) return nullptr;
1050             eofpos += firstlen;
1051         }
1052         setbits32(&first->D_position, newfirst);
1053         if ((first->D_space & 0xff) == 0x80)
1054         {   do
1055             {   first++;
1056                 setbits32(&first->D_position, newfirst);
1057             }
1058             while ((first->D_space & 0xff) != 0xff);
1059         }
1060         setbits32(d1->h.eof, eofpos);
1061     }
1062     std::fseek(d1->f, newpos, SEEK_SET);
1063     directory *d2 = reinterpret_cast<directory *>(new char[newsize]);
1064     if (d2 == nullptr) return nullptr;
1065     std::memcpy(d2, d1, sizeof(directory)+(current_size-1)*sizeof(directory_entry));
1066     d2->h.dirsize = static_cast<unsigned char>(n & 0xff);
1067     d2->h.dirext = static_cast<unsigned char>((d2->h.dirext & 0x0f) +
1068                        ((n>>4) & 0xf0));
1069     d2->h.updated |= D_COMPACT | D_UPDATED;
1070     while (n>current_size) clear_entry(&d2->d[--n]);
1071     fasl_files[dirno].dir = d2;
1072     delete [] reinterpret_cast<char *>(d1);
1073     return d2;
1074 }
1075 
open_output(const char * name,size_t len)1076 bool open_output(const char *name, size_t len)
1077 // Set up binary_write_file to access the given module, returning true
1078 // if anything went wrong. Remember name==nullptr for initial image & help
1079 // data.
1080 {   int i, j, n;
1081     const char *ct;
1082     char hard[16];
1083     directory *d;
1084     std::time_t t = std::time(nullptr);
1085     LispObject oo = qvalue(output_library);
1086 #ifdef BUILTIN_IMAGE
1087     return true;
1088 #endif
1089     nativedir = false;
1090     if (!is_library(oo)) return true;
1091     d = fasl_files[library_number(oo)].dir;
1092     if (d == nullptr) return true;  // closed handle, I guess
1093     if ((d->h.updated & D_WRITE_OK) == 0) return true;
1094 // The main effect of the next line will be to prohibit opening a new
1095 // FASL file while I am in the middle of reading one that already exists.
1096 // Indeed this is a restriction, but at present it seems a very reasonable
1097 // on for me to apply.
1098     if (Istatus != I_INACTIVE) return true;
1099     if (d->h.updated & D_PENDING)
1100     {
1101 // See comments in fasl.c under Lbanner for why I choose to report a failure
1102 // rather then do and unpending() when the output module I am creating is
1103 // just to contain banner information.
1104         if (name==nullptr && len==BANNER_CODE) return true;
1105         if (unpending(d)) return true;
1106     }
1107     current_output_directory = d;
1108     if (d->full_filename != nullptr) // native directory mode
1109     {   char nn[LONGEST_LEGAL_FILENAME];
1110         std::memset(nn, 0, sizeof(nn));
1111         fasl_file_name(nn, d, name, len);
1112         if ((binary_write_file = std::fopen(nn,
1113                                             "wb")) == nullptr) return true;
1114         write_bytes_written = 0;
1115         Istatus = I_WRITING;
1116         nativedir = true;
1117         return false;
1118     }
1119 // I use simple linear search to scan the directory - mainly because I
1120 // expect directories to be fairly small and once I have found a file
1121 // I will take a long while to process it, so any clumsiness here is
1122 // not critical. Again note it is OK to scan in steps of 1 despite the
1123 // fact that long-names are stored split across consecutive directory slots.
1124     for (i=0; i<get_dirused(*d); i++)
1125     {   if (samename(name, d, i, len))
1126         {   current_output_entry = &d->d[i];
1127             d->h.updated |= D_COMPACT | D_UPDATED;
1128             if (t == (std::time_t)(-1)) ct = "not dated";
1129             else ct = std::ctime(&t);
1130 // Note that I treat the result handed back by ctime() as delicate, in that
1131 // I do not do any library calls between calling ctime and copying the
1132 // string it returns to somewhere that is under my own control.
1133             std::strncpy(&d->d[i].D_date, ct, date_size);
1134             binary_write_file = d->f;
1135             write_bytes_written = 0;
1136             std::memcpy(&d->d[i].D_position, d->h.eof, 4);
1137 // For long names I must put the location in each record
1138             if (d->d[i].D_space & 0x80)
1139             {   j = 0;
1140                 do
1141                 {   j++;
1142                     std::memcpy(&d->d[i+j].D_position, d->h.eof, 4);
1143                 }
1144                 while ((d->d[i+j].D_space & 0xff) != 0xff);
1145             }
1146             i = std::fseek(binary_write_file, bits32(d->h.eof), SEEK_SET);
1147             if (i == 0) Istatus = I_WRITING;
1148             else current_output_directory = nullptr;
1149             if (name == nullptr && len == IMAGE_CODE)
1150                 d->h.version = IMAGE_FORMAT_VERSION;
1151             return i;
1152         }
1153     }
1154 // Here the name did not already exist, and so I will need to enter it into
1155 // the directory.  If I get here the variable i points to the first unused
1156 // directory entry.
1157     if (len == IMAGE_CODE)
1158     {   name = "InitialImage";
1159         n = 1;
1160         d->h.version = IMAGE_FORMAT_VERSION;
1161     }
1162     else if (len == HELP_CODE) name = "HelpDataFile", len = IMAGE_CODE,
1163                                    n = 1;
1164     else if (len == BANNER_CODE) name = "Start-Banner", len = IMAGE_CODE,
1165                                      n = 1;
1166     else if ((intptr_t)len < 0)
1167     {   std::sprintf(hard, "HardCode<%.2x>",
1168                      static_cast<int>((-len) & 0xff));
1169         name = hard, len = IMAGE_CODE, n = 1;
1170     }
1171     else if (len <= 11) n = 1;
1172     else if (len <= 11+11+24) n = 2;
1173     else if (len <= 11+11+11+24+24) n = 3;
1174     else return true;  // Name longer than 81 chars not supported, sorry
1175     while (i+n > static_cast<int>(get_dirsize(*d)))
1176     {   d = enlarge_directory(i);
1177         current_output_directory = d;
1178         if (d == nullptr) return true;
1179     }
1180     current_output_entry = &d->d[i];
1181     if (len == IMAGE_CODE)
1182     {   d->d[i].D_newline = NEWLINE_CHAR;
1183         std::memcpy(&d->d[i].D_name, name, 12);
1184         std::memset(&d->d[i].D_date, ' ', date_size);
1185         std::memset(&d->d[i].D_size, 0, 3);
1186         std::memcpy(&d->d[i].D_position, d->h.eof, 4);
1187     }
1188     else
1189     {   size_t np;
1190         const char *p;
1191 // First I will clear all the relevant fields to blanks.
1192         for (j=0; j<n; j++)
1193         {   d->d[i+j].D_newline = '\n';
1194             std::memset(&d->d[i+j].D_name, ' ', name_size);
1195             std::memset(&d->d[i+j].D_date, ' ', date_size);
1196             std::memset(&d->d[i+j].D_size, 0, 3);
1197             std::memcpy(&d->d[i+j].D_position, d->h.eof, 4);
1198         }
1199 #define next_char_of_name (np++ >= len ? ' ' : *p++)
1200         np = 0;
1201         p = name;
1202         for (j=0; j<n; j++)
1203         {   int k;
1204             for (k=0; k<11; k++) (&d->d[i+j].D_name)[k] = next_char_of_name;
1205             if (j != 0)
1206                 for (k=0; k<24; k++)
1207                     (&d->d[i+j].D_date)[k] = next_char_of_name;
1208             if (j == 0 && n == 1) d->d[i+j].D_space = ' ';
1209             else if (j == n-1) d->d[i+j].D_space = 0xff;
1210             else d->d[i+j].D_space = static_cast<char>(0x80+j);
1211 #undef next_char_of_name
1212         }
1213     }
1214     if (t == (std::time_t)(-1)) ct = "** *** not dated *** ** ";
1215     else ct = std::ctime(&t);
1216     std::memcpy(&d->d[i].D_date, ct, date_size);
1217     set_dirused(&d->h, get_dirused(*d)+n);
1218     binary_write_file = d->f;
1219     write_bytes_written = 0;
1220     d->h.updated |= D_UPDATED;
1221     i = std::fseek(binary_write_file, bits32(d->h.eof), SEEK_SET);
1222     if (i == 0)
1223     {   Istatus = I_WRITING;
1224         return false;
1225     }
1226     else
1227     {   current_output_directory = nullptr;
1228         return true;
1229     }
1230 }
1231 
list_one_native(string Cname,string Cleafname,int why,long int size)1232 static void list_one_native(string Cname, string Cleafname,
1233                             int why, long int size)
1234 {   const char *name = Cname.c_str();
1235     struct stat statbuff;
1236     char shortname[100];
1237     char *p;
1238     if (why != SCAN_FILE) return;
1239     stat(name, &statbuff);      // read the date on the file
1240     while (*name != 0) name++;
1241 // I need a comment about why the loop on the next line is guaranteed to
1242 // terminate. Well I only ought to be executing this code if the image
1243 // is represented as an operating-system directory, and when scan_directory
1244 // inspects it all the files in it are within it (gee!) and so have names
1245 // along the line of "csl.img/compat.fasl". So I really do expect to find
1246 // a directory separator character within the name.
1247     while (*name != '/' && *name != '\\') name--;
1248     std::strncpy(shortname, name+1, sizeof(shortname)-1);
1249     shortname[sizeof(shortname)-1] = 0;
1250     p = shortname;
1251     while (*p != 0 && std::strcmp(p, ".fasl") != 0) p++;
1252     *p = 0;
1253     if (std::strlen(shortname) > 12) trace_printf(
1254             "    %s\n                  %-24.24s    size: %ld\n",
1255             shortname, std::ctime(&(statbuff.st_mtime)),
1256             size);
1257     else trace_printf(
1258             "    %-12.12s  %-24.24s    size: %ld\n",
1259             shortname, std::ctime(&(statbuff.st_mtime)),
1260             size);
1261 }
1262 
1263 
list_one_library(LispObject oo,bool out_only)1264 static void list_one_library(LispObject oo, bool out_only)
1265 {   int j;
1266     directory *d = fasl_files[library_number(oo)].dir;
1267     if (d->full_filename != nullptr)
1268     {   string name = d->full_filename;
1269         trace_printf("Directory %s\n", d->full_filename);
1270         scan_directory(name, list_one_native);
1271         return;
1272     }
1273     trace_printf("\nFile %s (dirsize %ld  length %ld",
1274                  d->filename, static_cast<long>(get_dirsize(*d)),
1275                  static_cast<long>(bits32(d->h.eof)));
1276     j = d->h.updated;
1277     if (j != 0) trace_printf(",");
1278     if (j & D_WRITE_OK) trace_printf(" Writable");
1279     if (j & D_UPDATED)  trace_printf(" Updated");
1280     if (j & D_COMPACT)  trace_printf(" NeedsCompaction");
1281     if (j & D_PENDING)  trace_printf(" Pending");
1282     if (out_only) trace_printf(" OutputOnly");
1283     trace_printf("):\n");
1284 // The format string used here will need adjustment if you ever change the
1285 // number of characters used to store names or dates.
1286     for (j=0; j<get_dirused(*d); j++)
1287     {   int n = 0;
1288         if (d->d[j].D_space & 0x80)
1289         {   trace_printf("    %.11s", &d->d[j].D_name);
1290             do
1291             {   n++;
1292                 trace_printf("%.11s%.24s",
1293                              &d->d[j+n].D_name, &d->d[j+n].D_date);
1294             }
1295             while ((d->d[j+n].D_space & 0xff) != 0xff);
1296             trace_printf(
1297                 "\n                  %-24.24s    position %-7ld size: %ld\n",
1298                 &d->d[j].D_date,
1299                 static_cast<long>(bits32(&d->d[j].D_position)),
1300                 static_cast<long>(bits24(&d->d[j].D_size)));
1301             j += n;
1302         }
1303         else trace_printf(
1304                 "    %-12.12s  %-24.24s    position %-7ld size: %ld\n",
1305                 &d->d[j].D_name, &d->d[j].D_date,
1306                 static_cast<long>(bits32(&d->d[j].D_position)),
1307                 static_cast<long>(bits24(&d->d[j].D_size)));
1308     }
1309 }
1310 
Ilist()1311 void Ilist()
1312 {   LispObject il = qvalue(input_libraries), w;
1313     LispObject ol = qvalue(output_library);
1314     while (consp(il))
1315     {   w = car(il); il = cdr(il);
1316         if (!is_library(w)) continue;
1317         if (w == ol) ol = nil;
1318         list_one_library(w, false);
1319     }
1320     if (is_library(ol)) list_one_library(ol, true);
1321 }
1322 
1323 static LispObject mods;
1324 
collect_modules(string Cname,string Cleafname,int why,long int size)1325 static void collect_modules(string Cname, string Cleafname,
1326                             int why, long int size)
1327 {   int k = 0;
1328     LispObject v;
1329     char *p = reinterpret_cast<char *>(&celt(boffo, 0));
1330     if (why != SCAN_FILE) return;
1331     Save save(mods);
1332     const char *name = Cleafname.c_str();
1333     while (*name != '.' && *name != 0)
1334     {   *p++ = *name++;
1335         k++;
1336     }
1337     if (std::strcmp(name, ".fasl") != 0) return;
1338     v = iintern(boffo, k, lisp_package, 0);
1339     if (exceptionPending()) return;
1340     save.restore(mods);
1341     mods = cons(v, mods);
1342 }
1343 
Llibrary_members(LispObject env,LispObject oo)1344 LispObject Llibrary_members(LispObject env, LispObject oo)
1345 {   int i, j, k;
1346     directory *d = fasl_files[library_number(oo)].dir;
1347     LispObject v, r = nil;
1348     char *p;
1349     if (d->full_filename != nullptr)
1350     {   mods = nil;
1351         string name = d->full_filename;
1352         scan_directory(d->full_filename, collect_modules);
1353         return onevalue(mods);
1354     }
1355     for (j=0; j<get_dirused(*d); j++)
1356     {   int n = 0;
1357         p = reinterpret_cast<char *>(&celt(boffo, 0));
1358         k = 0;
1359         if (d->d[j].D_space & 0x80)
1360         {   for (i=0; i<11; i++)
1361             {   *p++ = (&d->d[j].D_name)[i];
1362                 k++;
1363             }
1364             do
1365             {   n++;
1366                 for (i=0; i<11; i++)
1367                 {   *p++ = (&d->d[j+n].D_name)[i];
1368                     k++;
1369                 }
1370             }
1371             while ((d->d[j+n].D_space & 0xff) != 0xff);
1372             j += n;
1373         }
1374         else
1375         {   if (std::memcmp(&d->d[j].D_name, "InitialImage", 12) == 0 ||
1376                 std::memcmp(&d->d[j].D_name, "HelpDataFile", 12) == 0 ||
1377                 std::memcmp(&d->d[j].D_name, "Start-Banner", 12) == 0 ||
1378                 (std::memcmp(&d->d[j].D_name, "HardCode<", 9) == 0 &&
1379                  (&d->d[j].D_name)[11] == '>'))
1380                 continue;  // not user modules
1381             for (i=0; i<12; i++)
1382             {   *p++ = (&d->d[j].D_name)[i];
1383                 k++;
1384             }
1385         }
1386         while (k>0 && p[-1] == ' ') k--, p--;
1387         *p = 0;
1388         Save save(r);
1389         v = iintern(boffo, k, lisp_package, 0);
1390         errexit();
1391         save.restore(r);
1392         r = cons(v, r);
1393     }
1394     return onevalue(r);
1395 }
1396 
Llibrary_members0(LispObject env)1397 LispObject Llibrary_members0(LispObject env)
1398 // This returns a list of the modules in the first library on the current
1399 // search path.
1400 {   LispObject il = qvalue(input_libraries), w;
1401     LispObject ol = qvalue(output_library);
1402     while (consp(il))
1403     {   w = car(il); il = cdr(il);
1404         if (!is_library(w)) continue;
1405         return Llibrary_members(nil, w);
1406     }
1407     if (is_library(ol)) return Llibrary_members(nil, ol);
1408     else return onevalue(nil);
1409 }
1410 
Imodulep1(int i,const char * name,size_t len,char * datestamp,size_t * size,char * expanded_name)1411 bool Imodulep1(int i, const char *name, size_t len, char *datestamp,
1412                size_t *size,
1413                char *expanded_name)
1414 // Hands back information about whether the given module exists in the
1415 // image file with index i.
1416 {   directory *d = fasl_files[i].dir;
1417     if (d == nullptr) return true;
1418     if (d->full_filename != nullptr)
1419     {   char nn[LONGEST_LEGAL_FILENAME];
1420         struct stat statbuff;
1421         std::memset(nn, 0, sizeof(nn));
1422         fasl_file_name(nn, d, name, len);
1423         if (stat(nn, &statbuff) != 0) return true;   // file not present
1424         std::strcpy(expanded_name, nn);
1425         std::strcpy(datestamp, std::ctime(&(statbuff.st_mtime)));
1426 // Note that FASL modules here will surely never even start to get towards
1427 // the size-limits of a 32-bit integer!
1428         *size = (int32_t)statbuff.st_size;
1429         return false;
1430     }
1431     for (int j=0; j<get_dirused(*d); j++)
1432     {   if (samename(name, d, j, len))
1433         {   const char *n = fasl_files[i].dir->filename;
1434             const char *p1 = "(", *p2 = ")";
1435             if (d->full_filename != nullptr)
1436             {
1437 #ifdef WIN32
1438                 p1 = "\\";
1439 #else
1440                 p1 = "/";
1441 #endif
1442                 p2 = "";
1443             }
1444             std::memcpy(datestamp, &d->d[j].D_date, date_size);
1445             *size = bits24(&d->d[j].D_size);
1446             if (name == nullptr) std::sprintf(expanded_name,
1447                                                   "%s%sInitialImage%s", n, p1, p2);
1448             else std::sprintf(expanded_name,
1449                                   "%s%s%.*s%s", n, p1, static_cast<int>(len), name, p2);
1450             return false;
1451         }
1452     }
1453     return true;
1454 }
1455 
Imodulep(const char * name,size_t len,char * datestamp,size_t * size,char * expanded_name)1456 bool Imodulep(const char *name, size_t len, char *datestamp,
1457               size_t *size,
1458               char *expanded_name)
1459 // Hands back information about whether the given module exists, and
1460 // if it does when it was written.  Code should be very similar to
1461 // that in Iopen.
1462 {   for (LispObject il = qvalue(input_libraries); consp(il);
1463          il=cdr(il))
1464     {   LispObject oo = car(il);
1465         if (!is_library(oo)) continue;
1466         if (!Imodulep1(library_number(oo), name, len, datestamp, size,
1467                        expanded_name))
1468             return false;
1469     }
1470     return true;
1471 }
1472 
1473 directory *rootDirectory = nullptr;
1474 
IopenRoot(char * expanded_name,size_t hard,int sixtyfour)1475 bool IopenRoot(char *expanded_name, size_t hard, int sixtyfour)
1476 // Opens the "InitialImage" file so that it can be loaded. Note that
1477 // when I am about to do this I do not have a valid heap image loaded, and
1478 // so it would NOT be possible to use the regular search-path mechanism for
1479 // libraries. Therefore I will just use images as specified from the
1480 // command line (or by default).
1481 {   const char *n;
1482     size_t i;
1483     if (hard == 0) hard = IMAGE_CODE;
1484     for (i=0; i<fasl_files.size(); i++)
1485     {   if (!fasl_files[i].inUse) continue;
1486         bool bad = open_input(fasl_files[i].dir, nullptr, hard, 0);
1487 // The name that I return (for possible display in error messages) will be
1488 // either that of the file that was opened, or one relating to the last
1489 // entry in the search path.
1490         n = fasl_files[i].dir->filename;
1491 
1492         if (hard == IMAGE_CODE) rootDirectory = fasl_files[i].dir;
1493 
1494         if (expanded_name != nullptr)
1495         {   if (hard == IMAGE_CODE)
1496             {   std::sprintf(expanded_name, "%s(InitialImage)", n);
1497             }
1498             else if (hard == BANNER_CODE)
1499                 std::sprintf(expanded_name, "%s(InitialImage)", n);
1500             else std::sprintf(expanded_name, "%s(HardCode<%.2x>)",
1501                                   n, static_cast<int>((-hard) & 0xff));
1502         }
1503         if (!bad) return false;
1504     }
1505     return true;
1506 }
1507 
1508 
Iopen(const char * name,size_t len,int forinput,char * expanded_name)1509 bool Iopen(const char *name, size_t len, int forinput,
1510            char *expanded_name)
1511 // Make file with the given name available through this package of
1512 // routines.  (name) is a pointer to a string (len characters valid) that
1513 // names a fasl file.  (forinput) specifies the direction of the transfer
1514 // to set up. Returns true if something failed.
1515 // name can be nullptr when a module is opened for output, and then output
1516 // is sent to "InitialImage".
1517 // The same is done for input, but it would be more sensible to use
1518 // IopenRoot() to access the root image.
1519 {   const char *n;
1520     if (name == nullptr) len = IMAGE_CODE;
1521     if (forinput != IOPEN_OUT)
1522     {   int i;
1523         LispObject il = qvalue(input_libraries);
1524         while (consp(il))
1525         {   bool bad;
1526             LispObject oo = car(il); il = cdr(il);
1527             if (!is_library(oo)) continue;
1528             i = library_number(oo);
1529             bad = open_input(fasl_files[i].dir, name, len, 0);
1530 // The name that I return (for possible display in error messages) will be
1531 // either that of the file that was opened, or one relating to the last
1532 // entry in the search path.
1533             n = fasl_files[i].dir->filename;
1534             if (expanded_name != nullptr)
1535             {   const char *p1 = "(", *p2 = ")";
1536                 if (fasl_files[i].dir->full_filename != nullptr)
1537                 {
1538 #ifdef WIN32
1539                     p1 = "\\";
1540 #else
1541                     p1 = "/";
1542 #endif
1543                     p2 = "";
1544                 }
1545                 std::sprintf(expanded_name, "%s%s%.*s%s", n, p1,
1546                              static_cast<int>(len), name, p2);
1547             }
1548             if (!bad) return false;
1549         }
1550         return true;
1551     }
1552     if (!any_output_request)
1553     {   if (expanded_name != nullptr)
1554             std::strcpy(expanded_name, "<no output file specified>");
1555         return true;
1556     }
1557     n = would_be_output_directory;
1558     if (expanded_name != nullptr)
1559     {   const char *p1 = "(", *p2 = ")";
1560         LispObject oo = qvalue(output_library);
1561         directory *d;
1562         if (!is_library(oo)) return true;
1563         d = fasl_files[library_number(oo)].dir;
1564         if (d->full_filename != nullptr)
1565         {
1566 #ifdef WIN32
1567             p1 = "\\";
1568 #else
1569             p1 = "/";
1570 #endif
1571             p2 = "";
1572         }
1573         if (len == IMAGE_CODE)
1574             std::sprintf(expanded_name, "%s%sInitialImage%s", p1, n, p2);
1575         else std::sprintf(expanded_name, "%s%s%.*s%s", n, p1,
1576                               static_cast<int>(len), name, p2);
1577     }
1578     return open_output(name, len);
1579 }
1580 
Iwriterootp(char * expanded_name)1581 bool Iwriterootp(char *expanded_name)
1582 // Test if it will be possible to write out an image file. Used
1583 // by (preserve) so it can report that this would fail without actually
1584 // doing anything too drastic.
1585 {   directory *d;
1586     LispObject oo = qvalue(output_library);
1587     if (!any_output_request)
1588     {   std::strcpy(expanded_name, "<no output file specified>");
1589         return true;
1590     }
1591     std::sprintf(expanded_name, "%s(InitialImage)",
1592                  would_be_output_directory);
1593     if (!is_library(oo)) return true;
1594     d = fasl_files[library_number(oo)].dir;
1595     if (d == nullptr) return true;  // closed handle, I guess
1596 // At present for native directories the WRITE_OK flag is left set without
1597 // proper checking of file access permissions.
1598     if ((d->h.updated & D_WRITE_OK) == 0) return true;
1599     if (Istatus != I_INACTIVE) return true;
1600     return false;
1601 }
1602 
Iopen_banner(int code)1603 bool Iopen_banner(int code)
1604 // Get ready to handle the startup banner.
1605 // code = 0    open for reading
1606 // code = -1   open for writing
1607 // code = -2   delete banner file
1608 {   if (code == -2) return Idelete(nullptr, BANNER_CODE);
1609     else if (code == 0)
1610     {   LispObject il = qvalue(input_libraries);
1611         while (consp(il))
1612         {   bool bad;
1613             LispObject oo = car(il); il = cdr(il);
1614             if (!is_library(oo)) continue;
1615             bad = open_input(fasl_files[library_number(oo)].dir,
1616                              nullptr, BANNER_CODE, 0);
1617             if (!bad) return false;
1618         }
1619         return true;
1620     }
1621     if (!any_output_request) return true;
1622     else return open_output(nullptr, BANNER_CODE);
1623 }
1624 
1625 // Set up binary_read_file to read from standard input. Return true if
1626 // things fail.
1627 
Iopen_from_stdin()1628 bool Iopen_from_stdin()
1629 {   if (Istatus != I_INACTIVE) return true;
1630 #ifdef BUILTIN_IMAGE
1631     binary_read_filep = nullptr;
1632 #else
1633     binary_read_file = nullptr;
1634 #endif
1635     read_bytes_remaining = -1;
1636     Istatus = I_READING;
1637     return false;
1638 }
1639 
Iopen_to_stdout()1640 bool Iopen_to_stdout()
1641 {   if (Istatus != I_INACTIVE) return true;
1642     Istatus = I_WRITING;
1643     return false;
1644 }
1645 
Idelete(const char * name,size_t len)1646 bool Idelete(const char *name, size_t len)
1647 {   int i, nrec;
1648     directory *d;
1649     LispObject oo = qvalue(output_library);
1650     if (!is_library(oo)) return true;
1651     d = fasl_files[library_number(oo)].dir;
1652     if (d == nullptr ||
1653         (d->h.updated && D_WRITE_OK) == 0 ||
1654         Istatus != I_INACTIVE) return true;
1655     if (d->full_filename != nullptr)
1656     {   char nn[LONGEST_LEGAL_FILENAME];
1657         std::memset(nn, 0, sizeof(nn));
1658         fasl_file_name(nn, d, name, len);
1659         return (std::remove(nn) != 0);
1660     }
1661     for (i=0; i<get_dirused(*d); i++)
1662     {   if ((nrec = samename(name, d, i, len)) != 0)
1663         {   int j;
1664             set_dirused(&d->h, get_dirused(*d)-nrec);
1665             for (j=i; j<get_dirused(*d); j++)
1666                 d->d[j] = d->d[j+nrec];
1667 // I tidy up the now-unused entry - in some sense this is a redundant
1668 // operation, but I think it makes the file seem neater, which may possibly
1669 // help avoid confusion and ease debugging.
1670             while (nrec-- != 0)
1671             {   std::memset(&d->d[j].D_name, ' ', name_size);
1672                 std::memcpy(&d->d[j].D_name, "<Unused>", 8);
1673                 std::memset(&d->d[j].D_date, ' ', date_size);
1674                 (&d->d[j].D_date)[0] = '-';
1675                 setbits32(&d->d[j].D_position, 0);
1676                 setbits24(&d->d[j].D_size, 0);
1677                 j++;
1678             }
1679             d->h.updated |= D_COMPACT | D_UPDATED;
1680             return false;
1681         }
1682     }
1683     return true;
1684 }
1685 
Icopy(const char * name,size_t len)1686 bool Icopy(const char *name, size_t len)
1687 // Find the named module in one of the input files, and if the place that
1688 // it is found is not already the output file copy it to the output. These days
1689 // either (or neither or both!) the places could be either my own 1-file
1690 // image-files or they could be native directories. But for now I will just
1691 // ignore that and only support the older situation. That is because I am lazy!
1692 {   int i, ii, j, n;
1693     long int k, l, save = read_bytes_remaining;
1694     char hard[16];
1695     directory *d, *id;
1696     LispObject il, oo = qvalue(output_library);
1697     if (!is_library(oo)) return true;
1698     d = fasl_files[library_number(oo)].dir;
1699 // Only valid if there is an output file and nothing else is going on.
1700     if (d == nullptr ||
1701         (d->h.updated & D_WRITE_OK) == 0 ||
1702         Istatus != I_INACTIVE) return true;
1703     if (d->h.updated & D_PENDING)
1704     {   if (unpending(d)) return true;
1705     }
1706 // The next line refuses to copy INTO a native directory...
1707     if (d->full_filename != nullptr) return true;
1708 // Search for a suitable input module to copy...
1709     for (il=qvalue(input_libraries); consp(il); il = cdr(il))
1710     {   oo = car(il);
1711         if (!is_library(oo)) continue;
1712         i = library_number(oo);
1713         id = fasl_files[i].dir;
1714 // Not updated for native dirs yet
1715         if (id->full_filename != nullptr) continue;
1716         for (ii=0; ii<get_dirused(*id); ii++)
1717             if (samename(name, id, ii, len)) goto found;
1718     }
1719     return true;     // Module to copy not found
1720 found:
1721 // If the potential input module found was in the output directory exit now.
1722     if (id == d) return false;
1723 // Now scan output directory to see where to put result
1724     for (i=0; i<get_dirused(*d); i++)
1725 // Not updated for native dirs yet
1726         if (samename(name, d, i, len))
1727         {   d->h.updated |= D_UPDATED | D_COMPACT;
1728             goto ofound;
1729         }
1730 // The file was not previously present in the output directory, so
1731 // I need to insert it. The code here is copies from open_output and is
1732 // now messy enoug that I should really move it to a sub-function.
1733     if (len == IMAGE_CODE)
1734         name = "InitialImage", n = 1;
1735     else if (len == HELP_CODE)
1736         name = "HelpDataFile", len = IMAGE_CODE, n = 1;
1737     else if (len == BANNER_CODE)
1738         name = "Start-Banner", len = IMAGE_CODE, n = 1;
1739     else if ((intptr_t)len < 0)
1740     {   std::sprintf(hard, "HardCode<%.2x>",
1741                      static_cast<int>((-len) & 0xff));
1742         name = hard, len = IMAGE_CODE, n = 1;
1743     }
1744     else if (len <= 11) n = 1;
1745     else if (len <= 11+11+24) n = 2;
1746     else if (len <= 11+11+11+24+24) n = 3;
1747     else return true;  // Name longer than 81 chars not supported, sorry
1748     while (i+n > static_cast<int>(get_dirsize(*d)))
1749     {   d = enlarge_directory(i);
1750         current_output_directory = d;
1751         if (d == nullptr) return true;
1752     }
1753     current_output_entry = &d->d[i];
1754     if (len == IMAGE_CODE)
1755     {   d->d[i].D_newline = NEWLINE_CHAR;
1756         std::memcpy(&d->d[i].D_name, name, 12);
1757         std::memset(&d->d[i].D_date, ' ', date_size);
1758         std::memset(&d->d[i].D_size, 0, 3);
1759         std::memcpy(&d->d[i].D_position, d->h.eof, 4);
1760     }
1761     else
1762     {   size_t np;
1763         const char *p;
1764 // First I will clear all the relevant fields to blanks.
1765         for (j=0; j<n; j++)
1766         {   d->d[i+j].D_newline = '\n';
1767             std::memset(&d->d[i+j].D_name, ' ', name_size);
1768             std::memset(&d->d[i+j].D_date, ' ', date_size);
1769             std::memset(&d->d[i+j].D_size, 0, 3);
1770             std::memcpy(&d->d[i+j].D_position, d->h.eof, 4);
1771         }
1772 #define next_char_of_name (np++ >= len ? ' ' : *p++)
1773         np = 0;
1774         p = name;
1775         for (j=0; j<n; j++)
1776         {   for (k=0; k<11; k++) (&d->d[i+j].D_name)[k] = next_char_of_name;
1777             if (j != 0)
1778                 for (k=0; k<24; k++)
1779                     (&d->d[i+j].D_date)[k] = next_char_of_name;
1780             if (j == 0 && n == 1) d->d[i+j].D_space = ' ';
1781             else if (j == n-1) d->d[i+j].D_space = 0xff;
1782             else d->d[i+j].D_space = static_cast<char>(0x80+j);
1783 #undef next_char_of_name
1784         }
1785     }
1786     set_dirused(&d->h, get_dirused(*d)+n);
1787 ofound:
1788     std::memcpy(&d->d[i].D_date, &id->d[ii].D_date, date_size);
1789     trace_printf("\nCopy %.*s from %s to %s\n",
1790                  len, name, id->filename, d->filename);
1791     std::memcpy(&d->d[i].D_position, d->h.eof, 4);
1792     if (d->d[i].D_space & 0x80)
1793     {   n = 0;
1794         do
1795         {   n++;
1796             std::memcpy(&d->d[i+n].D_position, d->h.eof, 4);
1797         }
1798         while ((d->d[i+n].D_space & 0xff) != 0xff);
1799     }
1800 // I provisionally set the size to zero so that if something goes wrong
1801 // I will still have a tolerably sensible image file.
1802     std::memset(&d->d[i].D_size, 0, 3);
1803     d->h.updated |= D_UPDATED;
1804     if (std::fseek(d->f, bits32(&d->d[i].D_position), SEEK_SET) != 0 ||
1805         std::fseek(id->f, bits32(&id->d[ii].D_position),
1806                    SEEK_SET) != 0) return true;
1807     l = bits24(&id->d[ii].D_size);
1808     for (k=0; k<l; k++)
1809     {   int c = std::getc(id->f);
1810         if (c == EOF) return true;
1811         std::putc(c, d->f);
1812     }
1813     read_bytes_remaining = 0;
1814     read_bytes_remaining = save;
1815     if (std::fflush(d->f) != 0) return true;
1816     setbits24(&d->d[i].D_size, (int32_t)l);
1817     setbits32(d->h.eof, (int32_t)std::ftell(d->f));
1818     return false;
1819 }
1820 
IcloseInput()1821 bool IcloseInput()
1822 // Terminate processing one whatever subfile has been being processed.
1823 // returns nonzero if there was trouble.
1824 {   Istatus = I_INACTIVE;
1825 #ifndef BUILTIN_IMAGE
1826     if (nativedir)
1827     {   if (std::fclose(binary_read_file) != 0) return true;
1828     }
1829 #endif
1830     return false;
1831 }
1832 
IcloseOutput()1833 bool IcloseOutput()
1834 // Terminate processing one whatever subfile has been being processed.
1835 // returns nonzero if there was trouble. Write a checksum to the file.
1836 // There is a jolly joke here!  I MUST NOT try to pick up the identification
1837 // of the output directory from the lisp-level variable output_directory
1838 // because (preserve) calls this AFTER it has utterly mangled the heap (to
1839 // put all pointers into relative form). To allow for this the variable
1840 // current_output_directory identifies the directory within which a file
1841 // was most recently opened.
1842 {   int r;
1843     directory *d = current_output_directory;
1844     Istatus = I_INACTIVE;
1845     current_output_directory = nullptr;
1846     if (d->full_filename != nullptr)
1847     {   r = (std::fclose(binary_write_file) != 0);
1848         binary_write_file = nullptr;
1849         return r;
1850     }
1851     setbits24(&current_output_entry->D_size,
1852               (int32_t)write_bytes_written);
1853     r = std::fflush(d->f);
1854     setbits32(d->h.eof, (int32_t)std::ftell(d->f));
1855 // I bring the directory at the start of the output file up to date at this
1856 // stage - the effect is that if things crash somehow I have a better
1857 // chance of resuming from where disaster hit.
1858     std::fseek(d->f, 0, SEEK_SET);
1859     if (std::fwrite(&d->h, sizeof(directory_header), 1,
1860                     d->f) != 1) r = true;
1861     if (std::fwrite(&d->d[0], sizeof(directory_entry),
1862                     (size_t)get_dirsize(*d), d->f) !=
1863         (size_t)get_dirsize(*d)) r = true;
1864     if (std::fflush(d->f) != 0) r = true;
1865     d->h.updated &= ~D_UPDATED;
1866     current_output_entry = nullptr;
1867     return r;
1868 }
1869 
deldir(directory * d,bool r)1870 static bool deldir(directory *d, bool r)
1871 {   delete [] reinterpret_cast<char *>(d);
1872     return r;
1873 }
1874 
finished_with(int j)1875 bool finished_with(int j)
1876 {   directory *d = fasl_files[j].dir;
1877     fasl_files[j].dir = nullptr;
1878     if (fasl_files[j].name != nullptr) delete [] fasl_files[j].name;
1879     fasl_files[j].name = nullptr;
1880     fasl_files[j].inUse = false;
1881     fasl_files[j].isOutput = false;
1882     if (d == nullptr) return false;
1883     if (d->h.updated & D_COMPACT)
1884     {   int i;
1885         long int hwm;
1886         if (d->f == nullptr) return deldir(d, true);
1887         d->h.updated |= D_UPDATED;
1888         sort_directory(d);
1889         hwm = sizeof(directory_header) +
1890               get_dirsize(*d)*(long int)sizeof(directory_entry);
1891         for (i=0; i<get_dirused(*d); i++)
1892         {   long int pos = bits32(&d->d[i].D_position);
1893             if (pos != hwm)
1894             {   char *b = 16 + (char *)stack;
1895                 char small_buffer[64];
1896                 long int len = bits24(&d->d[i].D_size);
1897                 long int newpos = hwm;
1898                 while (len != 0)
1899                 {   size_t n =
1900                         (size_t)((CSL_PAGE_SIZE - 64 -
1901                                   ((char *)stack -
1902                                    reinterpret_cast<char *>(stackBase))) &
1903                                  (~(int32_t)0xff));
1904 // I only perform compression of the file when I am in the process of stopping,
1905 // and in that case the Lisp stack is not in use, so I use if as a buffer.
1906 // WELL the above statement used to be true, but now it is not, since the
1907 // function CLOSE-LIBRARY does exactly what I have declared is never
1908 // possible. But all is not lost - I can afford to use that part of
1909 // the stack that remains unused. In cases where CLOSE-LIBRARY is called
1910 // just before a stack overflow was due the result will be utterly terrible
1911 // (on speed) but it should still be correct. So what you will see is that
1912 // I start my buffer 16 bytes above the active part of the stack, and
1913 // let it run to within 48 bytes of the top of the stack page, but
1914 // rounded down so I do transfers in multiples of 256 bytes. If there
1915 // is really no (Lisp) stack free I use a 64 byte local buffer.
1916                     if (n == 0) b = small_buffer, n = sizeof(small_buffer);
1917                     if (len < (long int)n) n = (size_t)len;
1918                     std::fseek(d->f, pos, SEEK_SET);
1919                     size_t rc = std::fread(b, 1, n, d->f);
1920                     if (rc != n) fatal_error(err_read_failure);
1921                     pos = std::ftell(d->f);
1922                     std::fseek(d->f, newpos, SEEK_SET);
1923                     std::fwrite(b, 1, n, d->f);
1924                     newpos = std::ftell(d->f);
1925                     len -= n;
1926                 }
1927                 setbits32(&d->d[i].D_position, (int32_t)hwm);
1928             }
1929             hwm += bits24(&d->d[i].D_size) + 4L;
1930         }
1931         std::fflush(d->f);
1932         if (hwm != bits32(d->h.eof))
1933         {   truncate_file(d->f, hwm);
1934             setbits32(d->h.eof, (int32_t)hwm);
1935         }
1936     }
1937     if (d->h.updated & D_UPDATED)
1938     {   if (d->f == nullptr || std::fflush(d->f) != 0) return deldir(d, true);
1939         std::fseek(d->f, 0, SEEK_SET);
1940         if (std::fwrite(&d->h, sizeof(directory_header), 1,
1941                         d->f) != 1) return deldir(d, true);
1942         if (std::fwrite(&d->d[0], sizeof(directory_entry),
1943                         (size_t)get_dirsize(*d), d->f) !=
1944             (size_t)get_dirsize(*d)) return deldir(d, true);
1945         if (std::fflush(d->f) != 0) return deldir(d, true);
1946     }
1947     if (d->h.updated & D_PENDING) return deldir(d, false);
1948     else if (d->f != nullptr && std::fclose(d->f) != 0) return deldir(d, true);
1949     else return deldir(d, false);
1950 }
1951 
Ifinished()1952 bool Ifinished()
1953 // Indicates total completion of all work on image files, and so calls
1954 // for things to be (finally) tidied up.  Again returns true of anything
1955 // has gone wrong.
1956 {
1957 // Need to close all files here... loads of calls to fflush and fclose.
1958 // Actually only output files are a real issue here. And then only
1959 // the ones that are flagged as needing compaction.
1960     size_t j;
1961     bool failed = false;
1962     for (j=0; j<fasl_files.size(); j++)
1963     {   if (!fasl_files[j].inUse) continue;
1964         else if (finished_with(j)) failed = true;
1965     }
1966     return failed;
1967 }
1968 
Igetc()1969 int Igetc()
1970 // Returns next byte from current image sub-file, or EOF if either
1971 // real end-of-file or on failure. As a special fudge here (ugh) I
1972 // use a negative value of read_bytes_remaining to indicate that
1973 // input should NOT be from the usual image-file mechanism, but from
1974 // the currently selected standard input. Setting things up that way
1975 // then supports processing of FASL files from almost arbitrary
1976 // sources.
1977 {   long int n_left = read_bytes_remaining;
1978     int c;
1979     if (n_left <= 0)
1980     {   if (n_left == 0) return EOF;
1981         else
1982         {   LispObject stream = qvalue(standard_input);
1983             if (!is_stream(stream)) return EOF;
1984             if_error(c = getc_stream(stream),
1985                      return EOF);
1986         }
1987     }
1988     else
1989     {   read_bytes_remaining = n_left - 1;
1990 #ifdef BUILTIN_IMAGE
1991         if (binary_read_filep - reduce_image == REDUCE_IMAGE_SIZE) c = -1;
1992         else c = *binary_read_filep++;
1993 #else
1994         c = std::getc(binary_read_file);
1995 #endif
1996     }
1997     if (c == EOF) return c;
1998     return (c & 0xff);
1999 }
2000 
Iread(void * buff,size_t size)2001 bool Iread(void *buff, size_t size)
2002 // Reads (size) bytes into the indicated buffer.  Returns true if
2003 // if fails to read the expected number of bytes.
2004 {   unsigned char *p = reinterpret_cast<unsigned char *>(buff);
2005     while (size > 0)
2006     {   int c = Igetc();
2007         if (c == EOF) return true;
2008         *p++ = c;
2009         size--;
2010     }
2011     return false;
2012 }
2013 
Ioutsize()2014 long int Ioutsize()
2015 {   return write_bytes_written;
2016 }
2017 
Iputc(int ch)2018 bool Iputc(int ch)
2019 // Puts one character into image system, returning true if there
2020 // was trouble.
2021 // If start-module is given a Lisp stream as an argument then it will
2022 // save that in fasl_stream and the code must write bytes to there. Otherwise
2023 // (ie in the normal situation!) I will have used Iopen to set up the
2024 // stream, and it will have set binary_write_file to the stream and positioned
2025 // it at the point I should start writing.
2026 {   write_bytes_written++;
2027     if (fasl_stream != nil && fasl_stream != SPID_NIL)
2028         putc_stream(ch, fasl_stream);
2029     else if (std::putc(ch, binary_write_file) == EOF) return true;
2030     return false;
2031 }
2032 
Iwrite(const void * buff,size_t size)2033 bool Iwrite(const void *buff, size_t size)
2034 // Writes (size) bytes from the given buffer, returning true if trouble.
2035 {   const unsigned char *p = reinterpret_cast<const unsigned char *>
2036                              (buff);
2037     for (size_t i=0; i<size; i++)
2038         if (Iputc(p[i])) return true;
2039     return false;
2040 }
2041 
preserve(const char * banner,size_t len)2042 void preserve(const char *banner, size_t len)
2043 {   int32_t i;
2044     if (Iopen(nullptr, 0, IOPEN_OUT, nullptr))
2045     {   err_printf("+++ PRESERVE failed to open image file\n");
2046         return;
2047     }
2048 // I set a whole bunch of things to NIL here.  If spurious data is left over
2049 // in global list-bases from a previous calculation it could clog up the
2050 // heap and waste a lot of space...
2051     for (i=0; i<=50; i++) workbase[i] = nil;
2052     exit_tag = exit_value = catch_tags =
2053                                 codevec = litvec = B_reg = faslvec = faslgensyms = nil;
2054     Lmapstore(nil, fixnum_of_int(4)); // Reset all counts to zero.
2055     {   char msg[128];
2056         std::time_t t0 = std::time(0);
2057         for (i=0; i<128; i++) msg[i] = ' ';
2058         if (len > 60) len = 60; // truncate if necessary
2059         if (len == 0 || banner[0] == 0) msg[0] = 0;
2060         else std::sprintf(msg, "%.*s", static_cast<int>(len), banner);
2061 // 26 bytes starting from byte 64 shows the time of the dump
2062         std::sprintf(msg+64, "%.25s\n", std::ctime(&t0));
2063 // 16 bytes starting at byte 90 are for a checksum of the u01.c etc checks
2064         get_user_files_checksum(reinterpret_cast<unsigned char *>(&msg[90]));
2065 // 106 to 109 free at present but available if checksum goes to 160 bits
2066         msg[110] = 0;
2067         msg[111] = 0;
2068 // Write initial record uncompresssed...
2069         Iwrite(msg, 112); // Exactly 112 bytes in the header records
2070     }
2071     def_init();  // I should check the return code...
2072     write_everything(); // needs a return code to report any failure?
2073 
2074 #ifndef COMMON
2075     Zwrite("\n\nEnd of CSL dump file\n\n", 24);  // return code
2076 #else
2077     Zwrite("\n\nEnd of CCL dump file\n\n", 24);  // return code
2078 #endif
2079     def_finish();   // I should check the return code...
2080 // Here I pad the image file to be a multiple of 4 bytes long.  Since it is a
2081 // binary file the '\n' characters I put in will always be just 1 byte each
2082 // (for text files that might have expanded).  See comments in fasl.c for
2083 // a diatribe about why I do this, or at least why rather a long while ago
2084 // this was necessary on at least one sort of computer.
2085 // Note that this is writing directly to the file... not via the compression
2086 // layer.
2087     {   int k = static_cast<int>((-write_bytes_written) & 3);
2088         while (k != 0) k--, Iputc(NEWLINE_CHAR);
2089     }
2090 // I need to check for write errors here and moan if there were any...
2091     if (IcloseOutput()) error(0, err_write_err);
2092     return;
2093 }
2094 
2095 #endif // ZLIB_DEMO
2096 
2097 // end of file preserve.cpp
2098