/*
* Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
* 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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1