a62007de37
[INTERPRETER] * BUG: Class overriding is not retroactive anymore when the class already has instances. * NEW: In tracing mode, indent the interpreter output according to the current stack depth.
798 lines
16 KiB
C
798 lines
16 KiB
C
/***************************************************************************
|
|
|
|
gbx_archive.c
|
|
|
|
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
|
|
|
|
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 __GBX_ARCHIVE_C
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gb_common.h"
|
|
#include "gb_error.h"
|
|
#include "gb_alloc.h"
|
|
|
|
#include "gbx_string.h"
|
|
#include "gbx_stream.h"
|
|
#include "gbx_component.h"
|
|
#include "gbx_regexp.h"
|
|
#include "gbx_exec.h"
|
|
#include "gbx_class.h"
|
|
#include "gbx_project.h"
|
|
|
|
#include "gbx_archive.h"
|
|
|
|
#include "gb_arch_temp.h"
|
|
|
|
//#define DEBUG_COMP 1
|
|
|
|
// main archive project (used only if gbx is run with -x flag)
|
|
ARCHIVE *ARCHIVE_main = NULL;
|
|
|
|
// Additional library search directory used in debug mode, normally '~/.config/share/gambas3/lib'
|
|
char *ARCHIVE_path = NULL;
|
|
|
|
static char *_local_path = NULL;
|
|
static ARCHIVE *_archive_list = NULL;
|
|
static char *arch_pattern = NULL;
|
|
static int arch_index = 0;
|
|
static ARCH *arch_dir = NULL;
|
|
static char *arch_prefix = NULL;
|
|
|
|
ARCHIVE *ARCHIVE_create(const char *name, const char *path)
|
|
{
|
|
ARCHIVE *arch;
|
|
|
|
ALLOC_ZERO(&arch, sizeof(ARCHIVE));
|
|
|
|
arch->name = name;
|
|
arch->path = STRING_new_zero(path);
|
|
|
|
arch->domain = STRING_new_zero(name ? name : "gb");
|
|
|
|
arch->translation_loaded = FALSE;
|
|
|
|
TABLE_create(&arch->classes, sizeof(CLASS_SYMBOL), TF_IGNORE_CASE);
|
|
|
|
LIST_insert(&_archive_list, arch, &arch->list);
|
|
|
|
return arch;
|
|
}
|
|
|
|
static void error_ARCHIVE_load_exported_class(COMPONENT *current)
|
|
{
|
|
COMPONENT_current = current;
|
|
}
|
|
|
|
void ARCHIVE_load_exported_class(ARCHIVE *arch, int pass)
|
|
{
|
|
char *buffer;
|
|
int len;
|
|
char *name;
|
|
char *file_name;
|
|
CLASS *class;
|
|
int i;
|
|
COMPONENT *current;
|
|
bool optional;
|
|
char c;
|
|
|
|
if (arch->exported_classes_loaded)
|
|
return;
|
|
|
|
current = COMPONENT_current;
|
|
COMPONENT_current = (COMPONENT *)arch->current_component;
|
|
|
|
ON_ERROR_1(error_ARCHIVE_load_exported_class, current)
|
|
{
|
|
/* COMPONENT_current is set => it will look in the archive */
|
|
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "load_exported_class: %s (component: %s)\n", arch->name, COMPONENT_current ? COMPONENT_current->name : "?");
|
|
#endif
|
|
|
|
if (FILE_exist(".list"))
|
|
{
|
|
if (pass & AR_FIND_ONLY)
|
|
{
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "<Find pass>\n");
|
|
#endif
|
|
|
|
STREAM_load(".list", &buffer, &len);
|
|
/* The file must end with a newline !*/
|
|
buffer[len - 1] = 0;
|
|
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "-----------\n%s\n----------\n\n", buffer);
|
|
#endif
|
|
|
|
ARRAY_create(&arch->exported);
|
|
|
|
name = strtok(buffer, "\n");
|
|
while (name)
|
|
{
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "Check %s global\n", name);
|
|
#endif
|
|
|
|
file_name = strchr(name, ' ');
|
|
if (file_name)
|
|
*file_name++ = 0;
|
|
|
|
len = strlen(name);
|
|
optional = FALSE;
|
|
|
|
for(;;)
|
|
{
|
|
c = name[len - 1];
|
|
if (c == '?')
|
|
{
|
|
optional = TRUE;
|
|
len--;
|
|
}
|
|
else if (c == '!')
|
|
len--;
|
|
else
|
|
break;
|
|
}
|
|
|
|
name[len] = 0;
|
|
|
|
if (!optional || CLASS_look_global(name, len) == NULL)
|
|
{
|
|
class = CLASS_find_export(file_name, name);
|
|
|
|
if (file_name)
|
|
{
|
|
ALLOC(&class->data, len + 1);
|
|
strcpy(class->data, file_name);
|
|
class->data[len] = 0;
|
|
}
|
|
|
|
class = CLASS_check_global(class);
|
|
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "Add to load: %p %s\n", class, name);
|
|
#endif
|
|
class->component = COMPONENT_current;
|
|
*((CLASS **)ARRAY_add(&arch->exported)) = class;
|
|
}
|
|
|
|
name = strtok(NULL, "\n");
|
|
}
|
|
|
|
FREE(&buffer);
|
|
}
|
|
|
|
if (pass & AR_FIND_ONLY) // That way the 'pass' flag is always ignored.
|
|
{
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "<Load pass>\n");
|
|
#endif
|
|
|
|
for (i = 0; i < ARRAY_count(arch->exported); i++)
|
|
{
|
|
#if DEBUG_COMP
|
|
fprintf(stderr, "load %p %s\n", arch->exported[i], arch->exported[i]->name);
|
|
#endif
|
|
CLASS_load(arch->exported[i]);
|
|
}
|
|
|
|
ARRAY_delete(&arch->exported);
|
|
arch->exported_classes_loaded = TRUE;
|
|
}
|
|
}
|
|
}
|
|
END_ERROR
|
|
|
|
COMPONENT_current = current;
|
|
}
|
|
|
|
// Archive path is an absolute path or :<vendor>/<library>:<version>
|
|
|
|
static char *exist_library(const char *dir, const char *name)
|
|
{
|
|
char *path;
|
|
char *n;
|
|
char *p;
|
|
|
|
n = STRING_new_zero(name);
|
|
|
|
path = (char *)FILE_cat(dir, n, NULL);
|
|
if (FILE_exist(path))
|
|
goto __RETURN_PATH;
|
|
|
|
n = STRING_add(n, ".gambas", -1);
|
|
path = (char *)FILE_cat(dir, n, NULL);
|
|
if (FILE_exist(path))
|
|
goto __RETURN_PATH;
|
|
|
|
p = rindex(n, ':');
|
|
if (p)
|
|
{
|
|
strcpy(p, ".gambas");
|
|
path = (char *)FILE_cat(dir, n, NULL);
|
|
if (FILE_exist(path))
|
|
goto __RETURN_PATH;
|
|
}
|
|
|
|
path = NULL;
|
|
|
|
__RETURN_PATH:
|
|
|
|
STRING_free(&n);
|
|
return path;
|
|
}
|
|
|
|
void ARCHIVE_load(ARCHIVE *arch, bool load_exp)
|
|
{
|
|
char *path;
|
|
char *dir;
|
|
const char *name;
|
|
char *env;
|
|
|
|
if (arch->path)
|
|
{
|
|
if (*arch->path == '/' && FILE_exist(arch->path))
|
|
{
|
|
path = (char *)arch->path;
|
|
}
|
|
else
|
|
{
|
|
if (*arch->path == ':')
|
|
{
|
|
if (!_local_path)
|
|
{
|
|
env = getenv("XDG_DATA_HOME");
|
|
if (env && *env)
|
|
_local_path = STRING_new_zero(FILE_cat(env, "gambas3/lib", NULL));
|
|
else
|
|
_local_path = STRING_new_zero(FILE_cat(FILE_get_home(), ".local/share/gambas3/lib", NULL));
|
|
}
|
|
|
|
name = &arch->path[1];
|
|
}
|
|
else
|
|
name = FILE_get_name(arch->path);
|
|
|
|
// For backward-compatibility, library name is searched in the current folder too, without the vendor.
|
|
|
|
if (!(path = exist_library(PROJECT_path, FILE_get_name(name))))
|
|
{
|
|
if (!_local_path || !(path = exist_library(_local_path, name)))
|
|
{
|
|
if (!ARCHIVE_path || !(path = exist_library(ARCHIVE_path, name)))
|
|
{
|
|
if (!(path = exist_library(COMPONENT_path, name)))
|
|
{
|
|
dir = STRING_new_zero(FILE_cat(PROJECT_exec_path, "bin", NULL));
|
|
path = exist_library(dir, name);
|
|
STRING_free(&dir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!path || !FILE_exist(path))
|
|
THROW(E_LIBRARY, arch->name, "cannot find library");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
path = FILE_buffer();
|
|
sprintf(path, ARCH_PATTERN, COMPONENT_path, arch->name);
|
|
}
|
|
|
|
arch->arch = ARCH_open(path);
|
|
arch->current_component = COMPONENT_current;
|
|
|
|
if (load_exp)
|
|
ARCHIVE_load_exported_class(arch, AR_FIND_AND_LOAD); //, dep);
|
|
}
|
|
|
|
|
|
void ARCHIVE_create_main(const char *path)
|
|
{
|
|
ARCHIVE_main = ARCHIVE_create(NULL, NULL);
|
|
|
|
if (path)
|
|
ARCHIVE_main->arch = ARCH_open(path);
|
|
else
|
|
ARCHIVE_main->arch = NULL;
|
|
}
|
|
|
|
|
|
void ARCHIVE_load_main()
|
|
{
|
|
ARCHIVE_load_exported_class(ARCHIVE_main, AR_FIND_AND_LOAD);
|
|
}
|
|
|
|
|
|
void ARCHIVE_delete(ARCHIVE *arch)
|
|
{
|
|
int i;
|
|
LIST_remove(&_archive_list, arch, &arch->list);
|
|
|
|
if (arch->arch)
|
|
ARCH_close(arch->arch);
|
|
|
|
for (i = 0; i < TABLE_count(arch->classes); i++)
|
|
IFREE(TABLE_get_symbol(arch->classes, i)->name);
|
|
|
|
TABLE_delete(&arch->classes);
|
|
|
|
STRING_free(&arch->domain);
|
|
STRING_free(&arch->version);
|
|
STRING_free(&arch->path);
|
|
|
|
FREE(&arch);
|
|
}
|
|
|
|
|
|
void ARCHIVE_init(void)
|
|
{
|
|
}
|
|
|
|
|
|
void ARCHIVE_exit(void)
|
|
{
|
|
if (ARCHIVE_main)
|
|
ARCHIVE_delete(ARCHIVE_main);
|
|
|
|
STRING_free(&arch_pattern);
|
|
STRING_free(&arch_prefix);
|
|
STRING_free(&ARCHIVE_path);
|
|
STRING_free(&_local_path);
|
|
}
|
|
|
|
|
|
/* ### *parch must be initialized to NULL or a valid archive */
|
|
/* Returns a true archive, never the main archive if we are not running an executable */
|
|
bool ARCHIVE_find_from_path(ARCHIVE **parch, const char **ppath)
|
|
{
|
|
int i;
|
|
CLASS *class;
|
|
const char *path = *ppath;
|
|
const char *p;
|
|
|
|
if (*parch)
|
|
return FALSE;
|
|
|
|
if (COMPONENT_current && COMPONENT_current->archive)
|
|
*parch = COMPONENT_current->archive;
|
|
else if (CP && CP->component && CP->component->archive)
|
|
*parch = CP->component->archive;
|
|
else
|
|
*parch = NULL;
|
|
|
|
//fprintf(stderr, "ARCHIVE_find_from_path: %s (%s)\n", *ppath, *parch ? (*parch)->name : "NULL");
|
|
|
|
if (strncmp(path, "./", 2) == 0 && path[2])
|
|
{
|
|
path += 2;
|
|
p = index(path, '/');
|
|
if (p)
|
|
{
|
|
int len = p - path;
|
|
char name[len + 1];
|
|
COMPONENT *comp;
|
|
|
|
strncpy(name, path, len);
|
|
name[len] = 0;
|
|
|
|
comp = COMPONENT_find(name);
|
|
if (comp && comp->archive)
|
|
{
|
|
*parch = comp->archive;
|
|
*ppath = p + 1;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strncmp(path, ".../", 4) == 0)
|
|
{
|
|
path += 4;
|
|
*parch = NULL;
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
while (strncmp(path, "../", 3) == 0)
|
|
{
|
|
path += 3;
|
|
if (*parch == NULL || *parch == ARCHIVE_main)
|
|
continue;
|
|
|
|
while (i < STACK_frame_count)
|
|
{
|
|
class = STACK_frame[i].cp;
|
|
//fprintf(stderr, "[%d] %s / %s\n", i, class ? class->name : "NULL", class && class->component && class->component->archive ? class->component->archive->name : "NULL");
|
|
if (class)
|
|
{
|
|
if (!class->component)
|
|
{
|
|
*parch = NULL;
|
|
break;
|
|
}
|
|
if (class->component && class->component->archive != *parch)
|
|
{
|
|
*parch = class->component->archive;
|
|
break;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (i == STACK_frame_count)
|
|
*parch = NULL;
|
|
}
|
|
}
|
|
|
|
if (*parch == NULL && EXEC_arch)
|
|
*parch = ARCHIVE_main;
|
|
|
|
//fprintf(stderr, "--> '%s' / %s\n", *parch ? (*parch)->name : "(null)", *ppath);
|
|
|
|
*ppath = path;
|
|
return *parch == NULL;
|
|
}
|
|
|
|
/* Can return the main archive even if we are not running an executable */
|
|
bool ARCHIVE_get_current(ARCHIVE **parch)
|
|
{
|
|
if (COMPONENT_current && COMPONENT_current->archive)
|
|
*parch = COMPONENT_current->archive;
|
|
else if (CP && CP->component && CP->component->archive)
|
|
*parch = CP->component->archive;
|
|
else
|
|
*parch = ARCHIVE_main;
|
|
|
|
return *parch == NULL;
|
|
}
|
|
|
|
|
|
bool ARCHIVE_get(ARCHIVE *arch, const char **ppath, ARCHIVE_FIND *find)
|
|
{
|
|
ARCH_FIND f;
|
|
struct stat buf;
|
|
|
|
if (!*ppath || **ppath == 0)
|
|
return TRUE;
|
|
|
|
if (ARCHIVE_find_from_path(&arch, ppath))
|
|
{
|
|
// no archive found, we try a lstat
|
|
if (!PROJECT_path)
|
|
return TRUE;
|
|
FILE_chdir(PROJECT_path);
|
|
if (stat(*ppath, &buf))
|
|
return TRUE;
|
|
|
|
find->pos = S_ISDIR(buf.st_mode) ? -1 : 0;
|
|
find->sym = NULL;
|
|
find->len = (int)buf.st_size;
|
|
find->index = -1;
|
|
find->arch = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if (!arch->arch) // archive has been created but loading has failed
|
|
return TRUE;
|
|
|
|
if (ARCH_find(arch->arch, *ppath, 0, &f))
|
|
return TRUE;
|
|
|
|
find->sym = f.sym;
|
|
find->pos = f.pos;
|
|
find->len = f.len;
|
|
find->index = f.index;
|
|
find->arch = arch;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
bool ARCHIVE_exist(ARCHIVE *arch, const char *path)
|
|
{
|
|
ARCHIVE_FIND find;
|
|
|
|
//if (get_current(&arch, &path))
|
|
// return FALSE;
|
|
|
|
return (!ARCHIVE_get(arch, &path, &find));
|
|
}
|
|
|
|
|
|
bool ARCHIVE_is_dir(ARCHIVE *arch, const char *path)
|
|
{
|
|
ARCHIVE_FIND find;
|
|
|
|
if (ARCHIVE_get(arch, &path, &find))
|
|
return FALSE;
|
|
|
|
return (find.pos < 0);
|
|
}
|
|
|
|
|
|
void ARCHIVE_stat(ARCHIVE *arch, const char *path, FILE_STAT *info)
|
|
{
|
|
ARCHIVE_FIND find = { 0 };
|
|
struct stat buf;
|
|
|
|
//if (get_current(&arch))
|
|
// THROW_SYSTEM(ENOENT, path);
|
|
|
|
if (ARCHIVE_get(arch, &path, &find))
|
|
THROW_SYSTEM(ENOENT, path);
|
|
|
|
if (find.arch)
|
|
fstat(find.arch->arch->fd, &buf);
|
|
else
|
|
stat(PROJECT_path, &buf);
|
|
|
|
info->type = find.pos < 0 ? GB_STAT_DIRECTORY : GB_STAT_FILE;
|
|
info->mode = 0400;
|
|
|
|
info->size = find.len;
|
|
info->atime = (int)buf.st_mtime;
|
|
info->mtime = (int)buf.st_mtime;
|
|
info->ctime = (int)buf.st_mtime;
|
|
info->hidden = (*FILE_get_name(path) == '.');
|
|
info->uid = (int)buf.st_uid;
|
|
info->gid = (int)buf.st_gid;
|
|
}
|
|
|
|
|
|
void ARCHIVE_read(ARCHIVE *arch, int pos, void *buffer, int len)
|
|
{
|
|
ARCH_read(arch->arch, pos, buffer, len);
|
|
}
|
|
|
|
|
|
void ARCHIVE_dir_first(ARCHIVE *arch, const char *path, const char *pattern, int attr)
|
|
{
|
|
ARCHIVE_FIND find;
|
|
char abs_path[16];
|
|
int abs_len;
|
|
|
|
/*if (get_current(&arch, &path))
|
|
{
|
|
arch_dir = NULL;
|
|
return;
|
|
}*/
|
|
|
|
if (ARCHIVE_get(arch, &path, &find))
|
|
{
|
|
arch_dir = NULL;
|
|
return;
|
|
}
|
|
|
|
// ?? "." means that we want to browse the archive.
|
|
if (!find.sym && !find.arch) // && !(path[0] == '.' && path[1] == 0))
|
|
{
|
|
// By calling FILE_dir_first() again with an absolute path, we are sure that next calls to
|
|
// FILE_dir_next() will never call ARCHIVE_dir_next().
|
|
FILE_dir_first(FILE_cat(PROJECT_path, path, NULL), pattern, attr);
|
|
return;
|
|
}
|
|
|
|
if (pattern == NULL || !*pattern)
|
|
pattern = "*";
|
|
|
|
arch = find.arch;
|
|
arch_dir = arch->arch;
|
|
arch_index = 0;
|
|
|
|
STRING_free(&arch_pattern);
|
|
arch_pattern = STRING_new_zero(pattern);
|
|
|
|
//if (arch_dir->header.version == 2)
|
|
//{
|
|
if (find.index >= 0)
|
|
abs_len = sprintf(abs_path, "/%d:", find.index);
|
|
else
|
|
abs_len = 0;
|
|
/*}
|
|
else
|
|
{
|
|
ARCH_get_absolute_path(path, strlen(path), abs_path, &abs_len);
|
|
|
|
if (abs_len && abs_path[abs_len - 1] != '/')
|
|
{
|
|
abs_path[abs_len] = '/';
|
|
abs_len++;
|
|
}
|
|
}*/
|
|
|
|
STRING_free(&arch_prefix);
|
|
if (abs_len)
|
|
arch_prefix = STRING_new(abs_path, abs_len);
|
|
}
|
|
|
|
|
|
bool ARCHIVE_dir_next(char **name, int *len, int attr)
|
|
{
|
|
ARCH_SYMBOL *asym;
|
|
SYMBOL *sym;
|
|
char *s = NULL;
|
|
int l = 0;
|
|
int lenp;
|
|
|
|
/*if (arch_fd < 0)
|
|
return FILE_dir_next(name, len);*/
|
|
|
|
if (!arch_dir)
|
|
return TRUE;
|
|
|
|
lenp = STRING_length(arch_prefix);
|
|
|
|
for(;;)
|
|
{
|
|
if (arch_index >= arch_dir->header.n_symbol)
|
|
return TRUE;
|
|
|
|
//sym = &arch_dir->symbol[arch_index].sym;
|
|
asym = &arch_dir->symbol[arch_dir->sort[arch_index]];
|
|
sym = &asym->sym;
|
|
|
|
if (arch_pattern == NULL)
|
|
break;
|
|
|
|
arch_index++;
|
|
|
|
if (attr == GB_STAT_DIRECTORY && (asym->pos >= 0))
|
|
continue;
|
|
|
|
if (sym->len < lenp)
|
|
continue;
|
|
|
|
if (lenp)
|
|
{
|
|
if (strncmp(sym->name, arch_prefix, lenp))
|
|
continue;
|
|
}
|
|
else // special case: root directory when header.version == 2
|
|
{
|
|
if (!strncmp(sym->name, "/", 1))
|
|
continue;
|
|
}
|
|
|
|
s = sym->name + lenp;
|
|
l = sym->len - lenp;
|
|
|
|
if (l < 0)
|
|
continue;
|
|
|
|
if (memchr(s, '/', l))
|
|
continue;
|
|
|
|
if (!REGEXP_match(arch_pattern, STRING_length(arch_pattern), s, l))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
*name = s;
|
|
*len = l;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
bool ARCHIVE_check_addr(char *addr)
|
|
{
|
|
ARCHIVE *arch;
|
|
ARCH *a;
|
|
|
|
LIST_for_each(arch, _archive_list)
|
|
{
|
|
a = arch->arch;
|
|
if (a && addr >= a->addr && addr < &a->addr[a->length])
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void ARCHIVE_browse(ARCHIVE *arch, void (*found)(const char *path, int64_t size))
|
|
{
|
|
int i;
|
|
ARCH_SYMBOL *asym;
|
|
SYMBOL *sym;
|
|
ARCH *a = arch->arch;
|
|
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 = STRING_new(sym->name, sym->len);
|
|
for(;;)
|
|
{
|
|
if (*path != '/')
|
|
break;
|
|
|
|
ip = atoi(&path[1]);
|
|
sym = &a->symbol[ip].sym;
|
|
|
|
temp = path;
|
|
path = STRING_new(sym->name, sym->len);
|
|
if (path[sym->len - 1] != '/')
|
|
path = STRING_add(path, "/", 1);
|
|
path = STRING_add(path, strchr(temp, ':') + 1, 0);
|
|
STRING_free(&temp);
|
|
}
|
|
|
|
(*found)(path, size);
|
|
STRING_free(&path);
|
|
}
|
|
}
|
|
|
|
char *ARCHIVE_get_version(ARCHIVE *arch)
|
|
{
|
|
COMPONENT *current;
|
|
char *buffer;
|
|
int len;
|
|
int n;
|
|
char *line;
|
|
|
|
if (!arch->version_loaded)
|
|
{
|
|
current = COMPONENT_current;
|
|
COMPONENT_current = (COMPONENT *)arch->current_component;
|
|
|
|
ON_ERROR_1(error_ARCHIVE_load_exported_class, current)
|
|
{
|
|
STREAM_load(".startup", &buffer, &len);
|
|
|
|
n = 0;
|
|
line = strtok(buffer, "\n");
|
|
while (line)
|
|
{
|
|
n++;
|
|
if (n == 5)
|
|
{
|
|
arch->version = STRING_new_zero(line);
|
|
break;
|
|
}
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
|
|
FREE(&buffer);
|
|
}
|
|
END_ERROR
|
|
|
|
COMPONENT_current = current;
|
|
arch->version_loaded = TRUE;
|
|
}
|
|
|
|
return arch->version;
|
|
}
|