/*************************************************************************** gbc_archive.c (c) 2000-2011 BenoƮt Minisini This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***************************************************************************/ #define __GBC_ARCHIVE_C #include "config.h" #include #include #include #include #include #include #include #include #include "gb_common.h" #include "gb_error.h" #include "gb_str.h" #include "gb_file.h" #include "gb_magic.h" #include "gb_common_swap.h" #include "gbc_chown.h" #include "gbc_archive.h" /*#define DEBUG*/ #define TEMP_EXEC ".temp.gambas" char *ARCH_project; char *ARCH_project_name; char *ARCH_output = NULL; bool ARCH_verbose = FALSE; bool ARCH_swap = FALSE; static int arch_dir_pos; static TABLE *arch_table; static FILE *arch_file = NULL; static char *arch_buffer; static int pos_start; static void write_int(uint val) { if (ARCH_swap) SWAP_int((int *)&val); if (fwrite(&val, sizeof(uint), 1, arch_file) < 1) THROW("Write error"); } static void write_short(ushort val) { if (ARCH_swap) SWAP_short((short *)&val); if (fwrite(&val, sizeof(ushort), 1, arch_file) < 1) THROW("Write error"); } static int get_pos(void) { return ftell(arch_file); } static void write_int_at(int pos, uint val) { int old_pos = get_pos(); fseek(arch_file, pos, SEEK_SET); write_int(val); fseek(arch_file, old_pos, SEEK_SET); } static void write_string(const char *str, int len) { if (fwrite(str, sizeof(char), len, arch_file) < len) THROW("Write error"); } static void make_executable(void) { const char *err; struct stat info; FILE_chdir(FILE_get_dir(ARCH_project)); // If we cannot make the archive executable, just print a warning. gbs create an executable cache // inside /tmp, and /tmp may be mounted with the "noexec" flag. if (stat(TEMP_EXEC, &info) || chmod(TEMP_EXEC, info.st_mode | S_IXUSR | S_IXGRP | S_IXOTH)) fprintf(stderr, "gba: warning: cannot change executable permissions\n"); FILE_set_owner(TEMP_EXEC, FILE_cat(FILE_get_dir(ARCH_project), ".project", NULL)); if (FILE_exist(ARCH_output) && unlink(ARCH_output)) { err = "Cannot remove previous executable"; goto __ERROR; } if (rename(TEMP_EXEC, ARCH_output)) { err = "Cannot create executable"; goto __ERROR; } return; __ERROR: THROW("Cannot make executable: &1: &2", err, strerror(errno)); } void ARCH_define_output(const char *path) { STR_free(ARCH_output); if (path && *path != '/') path = FILE_cat(FILE_get_current_dir(), path, NULL); ARCH_output = STR_copy(path); } void ARCH_define_project(const char *project) { char *name; char *dir; const char *path; if (project == NULL) project = FILE_get_current_dir(); FILE_chdir(project); dir = STR_copy(FILE_get_current_dir()); arch_dir_pos = strlen(dir) + 1; path = FILE_cat(dir, ".startup", NULL); if (FILE_exist(path)) ARCH_project = STR_copy(path); else ARCH_project = STR_copy(FILE_cat(dir, ".project", NULL)); name = STR_copy(FILE_get_name(dir)); /*ARCH_project_name = STR_copy(FILE_set_ext(name, NULL));*/ ARCH_project_name = STR_copy(name); if (!ARCH_output) ARCH_define_output(strcat((char *)FILE_cat(dir, ARCH_project_name, NULL), ".gambas")); STR_free(name); STR_free(dir); } void ARCH_init(void) { TABLE_create(&arch_table, sizeof(ARCH_SYMBOL), TF_NORMAL); ALLOC(&arch_buffer, 4096, "ARCH_init"); arch_file = fopen(".temp.gambas", "w"); if (arch_file == NULL) THROW("Cannot create temporary archive file: &1", ARCH_output); fputs("#! /usr/bin/env gbr" GAMBAS_VERSION_STRING "\n", arch_file); while (get_pos() < 31) fprintf(arch_file, " "); fprintf(arch_file, "\n"); write_int(ARCH_MAGIC); write_int(ARCH_VERSION); if (ARCH_verbose) printf("Format version: %d\n", ARCH_VERSION); pos_start = get_pos(); write_int(0); write_int(0); write_int(0); write_int(0); write_int_at(pos_start, get_pos()); } #if ARCH_VERSION == 2 static void compress_file_name(const char *src, int lsrc, char **dst, int *ldst) { char *p; static char tpath[PATH_MAX]; char tpath2[PATH_MAX]; int len; int ind; strncpy(tpath, src, lsrc); tpath[lsrc] = 0; len = lsrc; if (ARCH_verbose) printf("%s", tpath); for(;;) { p = index(tpath + 1, '/'); if (!p) break; if (!TABLE_find_symbol(arch_table, tpath, p - tpath, &ind)) { *p = 0; THROW("&1: not in archive", tpath); } len = snprintf(tpath2, sizeof(tpath2), "/%d:%s", ind, p + 1); strcpy(tpath, tpath2); } if (ARCH_verbose) printf(" -> %s\n", tpath); *dst = tpath; *ldst = len; } #endif void ARCH_exit(void) { int i; ARCH_SYMBOL *sym; int pos_str; /* Write strings */ write_int_at(pos_start + sizeof(int), get_pos()); pos_str = 0; for (i = 0; i < TABLE_count(arch_table); i++) { sym = (ARCH_SYMBOL *)TABLE_get_symbol(arch_table, i); write_string(sym->sym.name, sym->sym.len); } /* Write file names */ write_int_at(pos_start + sizeof(int) * 2, get_pos()); write_int_at(pos_start + sizeof(int) * 3, TABLE_count(arch_table)); for (i = 0; i < TABLE_count(arch_table); i++) { sym = (ARCH_SYMBOL *)TABLE_get_symbol(arch_table, i); //write_short((ushort)i); write_int(pos_str); write_int(sym->sym.len); write_int(sym->pos); write_int(sym->len); pos_str += sym->sym.len; } for (i = 0; i < TABLE_count(arch_table); i++) write_short(arch_table->sort[i]); /* Close file */ fclose(arch_file); make_executable(); /* Free everything */ for (i = 0; i < TABLE_count(arch_table); i++) STR_free(TABLE_get_symbol(arch_table, i)->name); TABLE_delete(&arch_table); STR_free(ARCH_output); STR_free(ARCH_project); STR_free(ARCH_project_name); FREE(&arch_buffer, "ARCH_exit"); } int ARCH_add_file(const char *path) { char *rel_path; ARCH_SYMBOL *sym; FILE *file; struct stat info; int len, len_read; int ind; #if ARCH_VERSION == 2 compress_file_name(&path[arch_dir_pos], strlen(&path[arch_dir_pos]), &rel_path, &len); rel_path = STR_copy(rel_path); #else rel_path = STR_copy(&path[arch_dir_pos]); len = strlen(rel_path); #endif TABLE_add_symbol(arch_table, rel_path, len, &ind); sym = (ARCH_SYMBOL *)TABLE_get_symbol(arch_table, ind); sym->pos = get_pos(); file = fopen(path, "r"); if (file == NULL) THROW("Cannot open file: &1", path); fstat(fileno(file), &info); if (S_ISDIR(info.st_mode)) { sym->pos = -1; sym->len = 0; fclose(file); if (ARCH_verbose) printf("Adding directory %s", rel_path); } else { sym->len = info.st_size; len = sym->len; while (len > 0) { len_read = fread(arch_buffer, 1, sizeof(arch_buffer), file); if (len_read > 0) fwrite(arch_buffer, 1, len_read, arch_file); if (len_read < sizeof(arch_buffer)) { if (ferror(file)) THROW("Cannot read file: &1: &2", path, strerror(errno)); else break; } } fclose(file); if (ARCH_verbose) printf("Adding file %s (%d bytes)", rel_path, sym->len); } if (ARCH_verbose) printf(" -> %d\n", ind); return ind; } #if 0 void ARCH_browse(ARCH *a, void (*found)(const char *path, int64_t size)) { int i; ARCH_SYMBOL *asym; SYMBOL *sym; char *path; char *temp; int size; int ip; for (i = 0; i < a->header.n_symbol; i++) { asym = &a->symbol[i]; sym = &asym->sym; size = asym->len; path = STR_copy_len(sym->name, sym->len); for(;;) { if (*path != '/') break; ip = atoi(&path[1]); sym = &a->symbol[ip].sym; temp = path; path = STR_copy_len(sym->name, sym->len); if (path[sym->len - 1] != '/') STRING_add(&path, "/", 1); STRING_add(&path, strchr(temp, ':') + 1, 0); STRING_free(&temp); } (*found)(path, size); STR_free(path); } } #endif