/* * Copyright (c) 2002, The Tendra Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * Crown Copyright (c) 1997 * * This TenDRA(r) Computer Program is subject to Copyright * owned by the United Kingdom Secretary of State for Defence * acting through the Defence Evaluation and Research Agency * (DERA). It is made available to Recipients with a * royalty-free licence for its use, reproduction, transfer * to other parties and amendment for any purpose not excluding * product development provided that any such use et cetera * shall be deemed to be acceptance of the following conditions:- * * (1) Its Recipients shall ensure that this Notice is * reproduced upon any copies or amended versions of it; * * (2) Any amended version of it shall be clearly marked to * show both the nature of and the organisation responsible * for the relevant amendment or amendments; * * (3) Its onward transfer from a recipient to another * party shall be deemed to be that party's acceptance of * these conditions; * * (4) DERA gives no warranty or assurance as to its * quality or suitability for any purpose and DERA accepts * no liability whatsoever in relation to any use to which * it may be put. * * $TenDRA: tendra/src/tools/tcc/archive.c,v 1.12 2005/10/22 13:17:00 stefanf Exp $ */ #include "config.h" #include "cstring.h" #include "msgcat.h" #include "external.h" #include "filename.h" #include "list.h" #include "archive.h" #include "flags.h" #include "options.h" #include "utility.h" /* * ARCHIVE BLOCK SIZE * * This defines the size of the chunks read and written by the archiving * routines. It should not exceed buffer_size. */ #define block_size buffer_size /* * READ A GIVEN FILE FROM A STREAM * * This routine reads n characters from the file f into a new file * named nm. It returns a nonzero value if an error occurs. */ static int read_file(char *nm, char *w, long n, FILE *f) { if (dry_run) { if (fseek (f, n, SEEK_CUR)) { MSG_error_when_stepping_over (nm); return (1); } } else { size_t m = (size_t) n; FILE *g = fopen (nm, w); if (g == null) { MSG_cant_open_copy_destination_file (nm); return (1); } while (m) { size_t r = m, s; pointer p = (pointer) buffer; if (r > (size_t) block_size) r = (size_t) block_size; s = fread (p, sizeof (char), r, f); if (s != r) { MSG_reading_error_when_creating (nm); IGNORE fclose (g); return (1); } s = fwrite (p, sizeof (char), r, g); if (s != r) { MSG_writing_error_when_creating (nm); IGNORE fclose (g); return (1); } m = (size_t) (m - r); } IGNORE fclose (g); } return (0); } /* * WRITE A GIVEN FILE TO A STREAM * * This routine copies the file named nm into the file f. It returns * a nonzero value if an error occurs. */ static int write_file(char *nm, char *rd, FILE *f) { FILE *g; size_t n, m; pointer p = (pointer) buffer; if (dry_run) return (0); g = fopen (nm, rd); if (g == null) { MSG_cant_open_copy_source_file (nm); return (1); } while (n = fread (p, sizeof (char), (size_t) block_size, g), n) { m = fwrite (p, sizeof (char), n, f); if (m != n) { MSG_writing_error_when_copying (nm); IGNORE fclose (g); return (0); } } IGNORE fclose (g); return (0); } /* * CAT A FILE * * This routine copies the file named nm to the standard output. It * returns a nonzero value if an error occurs. */ int cat_file(char *nm) { return (write_file (nm, "r", stdout)); } /* * CREATE A DIRECTORY * * This routine creates a directory called nm, returning zero if it * is successful. Two alternative versions of the routine are provided. * The first is POSIX compliant and uses mkdir and various mode * constants from sys/stat.h. The second raises an error - in this * case the mkdir function should be implemented by an external call. */ int make_dir(char *nm) { if (dry_run) return (0); #if FS_STAT { mode_t m = (mode_t) (S_IRWXU | S_IRWXG | S_IRWXO); int e = mkdir (nm, m); return (e); } #else { MSG_built_in_mkdir_function_not_implemented (); return (1); } #endif } /* * MOVE A FILE * * This routine moves the file named from to the file named to, * returning zero if it is successful. Normally the files will be * on different filesystems, so we can't always use rename. */ int move_file(char *from, char *to) { int e; FILE *f; if (dry_run) return (0); if (streq (from, to)) return (0); #if FS_STAT if (rename (from, to) == 0) return (0); if (errno != EXDEV) { MSG_cant_rename_file (from, to); return (1); } #endif f = fopen (to, "wb"); if (f == null) { MSG_cant_open_copy_destination_file (to); return (1); } e = write_file (from, "rb", f); IGNORE fclose (f); if (e) return (e); if (remove (from)) { MSG_cant_remove_source_file (from); return (1); } return (0); } /* * REMOVE A FILE * * This routine removes the file or directory named nm, returning zero * if it is successful. Two alternative versions of the routine are * provided. The first is POSIX compliant and uses stuff from * sys/stat.h and dirent.h. The second raises an error - in this case * the remove function should be implemented by an external call. */ int remove_file(char *nm) { if (dry_run) return (0); #if FS_DIRENT { struct stat st; int e = stat (nm, &st); if (e != -1) { mode_t m = (mode_t) st.st_mode; if (S_ISDIR (m)) { DIR *d = opendir (nm); if (d == null) { e = 1; } else { char *p; struct dirent *t; char buff [1000]; IGNORE sprintf (buff, "%s/", nm); p = buff + strlen (buff); while (t = readdir (d), t != null) { char *dnm = t->d_name; if (!streq (dnm, ".") && !streq (dnm, "..")) { IGNORE strcpy (p, dnm); if (remove_file (buff)) e = 1; } } IGNORE closedir (d); if (rmdir (nm)) e = 1; } } else { e = remove (nm); } } else { /* If the file didn't exist, don't worry */ if (errno == ENOENT) return (0); } if (e) { MSG_cant_remove_file (nm); return (1); } return (0); } #else { MSG_built_in_remove_function_not_implemented (); return (1); } #endif } /* * TOUCH A FILE * * This routine touches the file called nm. It returns 0 if it is * successful. */ int touch_file(char *nm, char *opt) { if (!dry_run) { FILE *f = fopen (nm, "w"); if (f == null) MSG_cant_touch_file (nm); if (opt && streq (opt, "-k")) { /* This is an empty C spec file */ static unsigned char cs [] = { 0x80 }; size_t s1 = sizeof (cs [0]); size_t sn = (size_t) (sizeof (cs) / s1); IGNORE fwrite ((pointer) cs, s1, sn, f); } else { IGNORE fputs ("EMPTY\n", f); } IGNORE fclose (f); } return (0); } /* * POOR MAN'S TEMPNAM FUNCTION * * The token temporary_name can be defined to be either tempnam (which * is in XPG3 but not POSIX) or this routine, which is designed to serve * a similar purpose. The routine uses tmpnam (which is in ANSI) to * create a temporary file name, and appends the suffix pfx to this name. * The dir argument is not used. */ #if !FS_TEMPNAM char * like_tempnam(const char *dir, const char *pfx) /* ARGSUSED */ { static char letter = 'a'; char *p = buffer; UNUSED (dir); IGNORE tmpnam (p); p += strlen (p); p [0] = letter; p [1] = '.'; IGNORE strcpy (p + 2, pfx); letter = (char) (letter + 1); return (string_copy (buffer)); } #endif /* * FIND THE SIZE OF A FILE * * This routine calculates the length of a file, returning zero for * non-existent files. Two versions of the routine are provided. * The first is POSIX compliant and uses stat from sys/stat.h to * access the length directly. The second just reads the file and * counts the number of characters. */ long file_size(char *nm) { #if FS_STAT { struct stat st; int e = stat (nm, &st); if (e == -1) return (0); return ((long) st.st_size); } #else { size_t n = 0, m; pointer p = (pointer) buffer; FILE *f = fopen (nm, "rb"); if (f == null) return (0); while (m = fread (p, sizeof (char), block_size, f), m != 0) { n = (size_t) (n + m); } IGNORE fclose (f); return ((long) n); } #endif } /* * FIND THE DATE STAMP OF A FILE * * This routine calculates the date stamp of a file. If the target * machine does not support stat, or this is a dry run, zero is always * returned. */ static long file_time(char *nm) { #if FS_STAT { int e; struct stat st; if (dry_run) return (0); e = stat (nm, &st); if (e == -1) { MSG_cant_access_file (nm); return (0); } return ((long) st.st_mtime); } #else { UNUSED (nm); return (0); } #endif } /* * ARCHIVE HEADER * * A TDF archive always starts with ARCHIVE_HEADER, and the main part * of the archive ends with ARCHIVE_TRAILER. */ #define ARCHIVE_HEADER "!TDF\n" #define ARCHIVE_TRAILER "-\n" /* * IS A FILE AN ARCHIVE? * * This routine returns 1 if the file named nm starts with ARCHIVE_HEADER * (and so is probably an archive), and 0 otherwise. */ boolean is_archive(char *nm) { boolean b = 0; FILE *f = fopen (nm, "rb"); if (f == null) return (b); if (fgets (buffer, 20, f) && streq (buffer, ARCHIVE_HEADER)) { b = 1; } IGNORE fclose (f); return (b); } /* * ARCHIVE FLAGS * * These flags control the output of the file names and options in the * output TDF archive. */ int archive_type = TDF_ARCHIVE; static boolean archive_full = 1; static boolean archive_links = 0; static boolean archive_names = 1; static boolean archive_options = 1; /* * PROCESS ARCHIVE OPTIONS * * This routine processes any outstanding archive options. */ void process_archive_opt(void) { list *p; for (p = opt_joiner; p != null; p = p->next) { char *opt = p->item; if (streq (opt, "-copy") || streq (opt, "-c")) { archive_links = 0; link_specs = 0; } else if (streq (opt, "-full") || streq (opt, "-f")) { archive_full = 1; } else if (streq (opt, "-link") || streq (opt, "-l")) { archive_links = 1; link_specs = 1; } else if (streq (opt, "-names") || streq (opt, "-n")) { archive_names = 1; } else if (streq (opt, "-no_names") || streq (opt, "-nn")) { archive_names = 0; } else if (streq (opt, "-no_options") || streq (opt, "-no")) { archive_options = 0; } else if (streq (opt, "-options") || streq (opt, "-o")) { archive_options = 1; } else if (streq (opt, "-short") || streq (opt, "-s")) { archive_full = 0; } else { MSG_unknown_archiver_option (opt); } } opt_joiner = null; return; } /* * BUILD AN ARCHIVE * * This routine creates a TDF archive called arch from the null-terminated * list of files and options, input. The string ARCHIVE_OPTION_START is * uses to indicate the end of the files and the beginning of the options. * The routine returns zero if it is successful. */ int build_archive(char *arch, char **input) { FILE *f; char **s; boolean end = 0; if (dry_run) return (0); f = fopen (arch, "wb"); if (f == null) { MSG_cant_open_output_archive (arch); return (1); } IGNORE fputs (ARCHIVE_HEADER, f); for (s = input; *s; s++) { if (end) { /* Archive options */ if (archive_options) { if (verbose) { comment (1, "... archive option %s\n", *s); } IGNORE fprintf (f, "%s\n", *s); } } else if (streq (*s, ARCHIVE_OPTION_START)) { /* Start of archive options */ IGNORE fputs (ARCHIVE_TRAILER, f); end = 1; } else if (archive_links && archive_type != TDF_ARCHIVE) { /* Archive file - link */ char *ln = *s; if (verbose) { comment (1, "... archive file %s (link)\n", ln); } if (archive_full) ln = find_fullname (ln); IGNORE fprintf (f, "> %ld %s\n", file_time (ln), ln); } else { /* Archive file - copy */ FILE *g; char *n = find_basename (*s); if (!archive_names) { int i, m = (int) strlen (n); buffer [0] = '*'; buffer [1] = 0; for (i = m - 1; i >= 0; i--) { if (n [i] == '.') { IGNORE strcpy (buffer + 1, n + i); break; } } n = buffer; } if (verbose) comment (1, "... archive file %s\n", *s); g = fopen (*s, "rb"); if (g == null) { MSG_cant_open_for_archiving (*s); IGNORE fclose (f); return (1); } else { pointer p = (pointer) buffer; size_t m = fread (p, sizeof (char), (size_t) block_size, g); IGNORE fprintf (f, "+ %ld %s\n", (long) m, n); while (m) { if (fwrite (p, sizeof (char), m, f) != m) { MSG_write_error_in_archive (arch); IGNORE fclose (f); return (1); } m = fread (p, sizeof (char), (size_t) block_size, g); if (m) IGNORE fprintf (f, "+ %ld +\n", (long) m); } IGNORE fclose (g); } } } if (!end) IGNORE fputs (ARCHIVE_TRAILER, f); IGNORE fclose (f); return (0); } /* * SPLIT AN ARCHIVE * * This routine splits the TDF archive named arch into its constituent * components. Any files from the archive are stored in the location * indicated by ret. The routine returns zero if it is successful. */ int split_archive(char *arch, filename **ret) { boolean failed = 0, go = 1; list *opts = null; filename *q = null; filename *output = null; boolean need_moves = 0; /* Open archive file */ FILE *f = fopen (arch, "rb"); if (f == null) { MSG_cant_open_input_archive (arch); failed = 1; goto archive_error; } /* Check for archive header */ if (fgets (buffer, buffer_size, f) == null || !streq (buffer, ARCHIVE_HEADER)) { MSG_illegal_input_archive (arch); failed = 1; goto archive_error; } /* Extract archived files */ do { if (fgets (buffer, buffer_size, f) == null) { MSG_premature_end_of_archive (arch); failed = 1; goto archive_error; } if (buffer [0] == '+' && buffer [1] == ' ') { /* Archived file - copy */ char c; long n = 0; char *w = "wb"; char *p = buffer + 2; int m = (int) strlen (buffer) - 1; if (buffer [m] == '\n') buffer [m] = 0; while (c = *(p++), c != ' ') { if (c < '0' || c > '9') { MSG_illegal_file_length_specifier_in_archive (arch); failed = 1; goto archive_error; } n = 10 * n + (c - '0'); } if (streq (p, "+")) { /* File continuations */ if (q == null) { MSG_illegal_file_continuation_in_archive (arch); failed = 1; goto archive_error; } w = "ab"; } else { filename *qo = q; if (streq (p, "*")) { /* Old form hidden names */ int k = where (INDEP_TDF); q = make_filename (no_filename, INDEP_TDF, k); } else if (strneq (p, "*.", 2)) { /* New form hidden names */ int t; p = string_copy (p); q = find_filename (p, UNKNOWN_TYPE); t = q->type; q = make_filename (no_filename, t, where (t)); } else { /* Unhidden names */ p = string_copy (p); q = find_filename (p, UNKNOWN_TYPE); q = make_filename (q, q->type, where (q->type)); } if (archive_type != TDF_ARCHIVE && qo) q->uniq = qo->uniq; if (q->type == archive_type && q->storage != TEMP_FILE) { filename *qn = make_filename (q, q->type, TEMP_FILE); qn->aux = q; qn->uniq = q->uniq; q = qn; need_moves = 1; } output = add_filename (output, q); if (verbose) { comment (1, "... extract file %s\n", q->name); } } if (read_file (q->name, w, n, f)) { MSG_read_error_in_archive (arch); failed = 1; goto archive_error; } } else if (buffer [0] == '>' && buffer [1] == ' ') { /* Archived file - link */ char c; long ad = 0, fd; filename *qo = q; char *p = buffer + 2; int m = (int) strlen (buffer) - 1; if (buffer [m] == '\n') buffer [m] = 0; while (c = *(p++), c != ' ') { if (c < '0' || c > '9') { MSG_illegal_link_information_in_archive (arch); failed = 1; goto archive_error; } ad = 10 * ad + (c - '0'); } q = find_filename (string_copy (p), UNKNOWN_TYPE); q->storage = PRESERVED_FILE; if (archive_type != TDF_ARCHIVE && qo) q->uniq = qo->uniq; output = add_filename (output, q); if (verbose) { comment (1, "... extract file %s (link)\n", q->name); } fd = file_time (q->name); if (ad && fd && ad != fd) { MSG_date_stamp_on_file_has_changed (q->name); } } else if (streq (buffer, ARCHIVE_TRAILER)) { /* Archived options */ char *p; int c, m; while (c = getc (f), c != EOF) { buffer [0] = (char) c; if (fgets (buffer + 1, buffer_size - 1, f) == null) { MSG_premature_end_of_archive (arch); failed = 1; goto archive_error; } m = (int) strlen (buffer) - 1; if (buffer [m] == '\n') buffer [m] = 0; p = string_copy (buffer); if (verbose) comment (1, "... extract option %s\n", p); opts = add_item (opts, p); } go = 0; } else { MSG_illegal_file_description_in_archive (arch); failed = 1; goto archive_error; } } while (go); /* Return */ archive_error : { IGNORE fclose (f); if (need_moves) { for (q = output; q != null; q = q->next) { if (q->aux && keeps_aux [archive_type]) { if (verbose) { comment (1, "... rename %s to %s\n", q->name, q->aux->name); } if (move_file (q->name, q->aux->name)) { failed = 1; } else { q->name = q->aux->name; q->storage = q->aux->storage; } } q->aux = null; } } *ret = output; if (opts) { process_options (opts, main_optmap, 0); opt_archive = add_list (opt_archive, opts); } if (failed) return (1); return (0); } }