diff --git a/TODO b/TODO index eb95ee6e3..405b5bf4b 100644 --- a/TODO +++ b/TODO @@ -21,4 +21,7 @@ xbps-fetch: xbps-digest: - blake2b support +xbps-rindex: + - clean should also clean files.plist entries + Issues listed at https://github.com/void-linux/xbps/issues diff --git a/bin/xbps-query/Makefile b/bin/xbps-query/Makefile index 23aa3afdc..f2ee56db7 100644 --- a/bin/xbps-query/Makefile +++ b/bin/xbps-query/Makefile @@ -3,6 +3,6 @@ TOPDIR = ../.. BIN = xbps-query OBJS = main.o list.o show-deps.o show-info-files.o -OBJS += ownedby.o search.o ../xbps-install/util.o +OBJS += ownedby.o search.o ../xbps-install/util.o ../xbps-install/fetch_cb.o include $(TOPDIR)/mk/prog.mk diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 2a1747253..5c12564e8 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -50,6 +50,7 @@ int repo_show_pkg_namedesc(struct xbps_handle *, xbps_object_t, void *, /* from ownedby.c */ int ownedby(struct xbps_handle *, const char *, bool, bool); +int ownedhash(struct xbps_handle *, const char *, bool, bool); /* From list.c */ unsigned int find_longest_pkgver(struct xbps_handle *, xbps_object_t); diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 44316c1ad..292f81c88 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -42,7 +42,8 @@ usage(bool fail) " -c, --cachedir Path to cachedir\n" " -d, --debug Debug mode shown to stderr\n" " -h, --help Show usage\n" - " -i, --ignore-conf-repos Ignore repositories defined in xbps.d\n" + " -F, --update-files Updates the files-database\n" + " -i, --ignore-conf-repos Ignore repositories defined in xbps.d\n" " -M, --memory-sync Remote repository data is fetched and stored\n" " in memory, ignoring on-disk repodata archives\n" " -p, --property PROP[,...] Show properties for PKGNAME\n" @@ -64,7 +65,9 @@ usage(bool fail) " -m, --list-manual-pkgs List packages installed explicitly\n" " -O, --list-orphans List package orphans\n" " -o, --ownedby FILE Search for package files by matching STRING or REGEX\n" - " -S, --show PKG Show information for PKG [default mode]\n" + " --ownedhash FILE Search for package files by matching hash of FILE\n" + " or treating FILE as hash if not present\n" + " -S, --show PKG Show information for PKG [default mode]\n" " -s, --search PKG Search for packages by matching PKG, STRING or REGEX\n" " --cat=FILE PKG Print FILE from PKG binpkg to stdout\n" " -f, --files PKG Show package files for PKG\n" @@ -77,13 +80,14 @@ usage(bool fail) int main(int argc, char **argv) { - const char *shortopts = "C:c:df:hHiLlMmOo:p:Rr:s:S:VvX:x:"; + const char *shortopts = "C:c:dFf:hHiLlMmOo:p:Rr:s:S:VvX:x:"; const struct option longopts[] = { { "config", required_argument, NULL, 'C' }, { "cachedir", required_argument, NULL, 'c' }, { "debug", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "ignore-conf-repos", no_argument, NULL, 'i' }, + { "update-files", no_argument, NULL, 'F' }, { "list-repos", no_argument, NULL, 'L' }, { "list-pkgs", no_argument, NULL, 'l' }, { "list-hold-pkgs", no_argument, NULL, 'H' }, @@ -92,6 +96,7 @@ main(int argc, char **argv) { "list-manual-pkgs", no_argument, NULL, 'm' }, { "list-orphans", no_argument, NULL, 'O' }, { "ownedby", required_argument, NULL, 'o' }, + { "ownedhash", required_argument, NULL, 4 }, { "property", required_argument, NULL, 'p' }, { "repository", optional_argument, NULL, 'R' }, { "rootdir", required_argument, NULL, 'r' }, @@ -108,17 +113,18 @@ main(int argc, char **argv) { NULL, 0, NULL, 0 }, }; struct xbps_handle xh; + struct xbps_fetch_cb_data xfer; const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile; int c, flags, rv; - bool list_pkgs, list_repos, orphans, own, list_repolock; + bool list_pkgs, list_repos, orphans, own, ownhash, list_repolock; bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; - bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; + bool show, pkg_search, regex, repo_mode, opmode, fulldeptree, update_files; rootdir = cachedir = confdir = props = pkg = catfile = NULL; flags = rv = c = 0; - list_pkgs = list_repos = list_hold = orphans = pkg_search = own = false; + list_pkgs = list_repos = list_hold = orphans = pkg_search = own = ownhash = false; list_manual = list_repolock = show_prop = show_files = false; - regex = show = show_deps = show_rdeps = fulldeptree = false; + regex = show = show_deps = show_rdeps = fulldeptree = false, update_files = false; repo_mode = opmode = false; memset(&xh, 0, sizeof(xh)); @@ -138,6 +144,9 @@ main(int argc, char **argv) pkg = optarg; show_files = opmode = true; break; + case 'F': + update_files = true; + break; case 'H': list_hold = opmode = true; break; @@ -213,6 +222,10 @@ main(int argc, char **argv) case 3: list_repolock = opmode = true; break; + case 4: + pkg = optarg; + ownhash = opmode = true; + break; case '?': default: usage(true); @@ -247,6 +260,8 @@ main(int argc, char **argv) xbps_strlcpy(xh.confdir, confdir, sizeof(xh.confdir)); xh.flags = flags; + xh.fetch_cb = fetch_file_progress_cb; + xh.fetch_cb_data = &xfer; if ((rv = xbps_init(&xh)) != 0) { xbps_error_printf("Failed to initialize libxbps: %s\n", @@ -254,6 +269,9 @@ main(int argc, char **argv) exit(EXIT_FAILURE); } + if (update_files) + xbps_rpool_sync_files(&xh); + if (list_repos) { /* list repositories */ rv = repo_list(&xh); @@ -282,6 +300,10 @@ main(int argc, char **argv) /* ownedby mode */ rv = ownedby(&xh, pkg, repo_mode, regex); + } else if (ownhash) { + /* ownedby mode */ + rv = ownedhash(&xh, pkg, repo_mode, regex); + } else if (pkg_search) { /* search mode */ rv = search(&xh, repo_mode, pkg, props, regex); diff --git a/bin/xbps-query/ownedby.c b/bin/xbps-query/ownedby.c index baa82c3b4..2d8783a11 100644 --- a/bin/xbps-query/ownedby.c +++ b/bin/xbps-query/ownedby.c @@ -23,185 +23,263 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include +#include "defs.h" +#include "fetch.h" +#include "xbps_api_impl.h" + +#include +#include +#include #include -#include -#include +#include +#include +#include #include -#include #include #include #include - +#include +#include #include -#include "defs.h" -struct ffdata { - bool rematch; - const char *pat, *repouri; - regex_t regex; - xbps_array_t allkeys; - xbps_dictionary_t filesd; + +struct thread_data { + pthread_t this_thread; + struct archive* ar; + bool byhash; + const char* expr; + const regex_t* regex; + pthread_mutex_t* archive_mutex; + pthread_mutex_t* print_mutex; }; -static void -match_files_by_pattern(xbps_dictionary_t pkg_filesd, - xbps_dictionary_keysym_t key, - struct ffdata *ffd, - const char *pkgver) -{ - xbps_array_t array; - const char *keyname = NULL, *typestr = NULL; - - keyname = xbps_dictionary_keysym_cstring_nocopy(key); - - if (strcmp(keyname, "files") == 0) - typestr = "regular file"; - else if (strcmp(keyname, "links") == 0) - typestr = "link"; - else if (strcmp(keyname, "conf_files") == 0) - typestr = "configuration file"; - else - return; - - array = xbps_dictionary_get_keysym(pkg_filesd, key); - for (unsigned int i = 0; i < xbps_array_count(array); i++) { - xbps_object_t obj; - const char *filestr = NULL, *tgt = NULL; - - obj = xbps_array_get(array, i); - xbps_dictionary_get_cstring_nocopy(obj, "file", &filestr); - if (filestr == NULL) - continue; - xbps_dictionary_get_cstring_nocopy(obj, "target", &tgt); - if (ffd->rematch) { - if (regexec(&ffd->regex, filestr, 0, 0, 0) == 0) { - printf("%s: %s%s%s (%s)\n", - pkgver, filestr, - tgt ? " -> " : "", - tgt ? tgt : "", - typestr); - } - } else { - if ((fnmatch(ffd->pat, filestr, FNM_PERIOD)) == 0) { - printf("%s: %s%s%s (%s)\n", - pkgver, filestr, - tgt ? " -> " : "", - tgt ? tgt : "", - typestr); - } - } +static inline int parse_line(char** fields, int fields_size, char* line) { + char* next; + int i = 0; + + while (i < fields_size - 1) { + if (!(next = strchr(line, '%'))) + break; + + *next = '\0'; + + fields[i++] = *line ? line : NULL; + line = next + 1; } + fields[i++] = *line ? line : NULL; + + return i; +} + +static inline void print_line(pthread_mutex_t* mutex, const char* pkg, char** file) { + if (mutex) pthread_mutex_lock(mutex); + printf("%s: %s", pkg, file[1]); + if (file[2]) + printf(" -> %s", file[2]); + printf("\n"); + if (mutex) pthread_mutex_unlock(mutex); } -static int -ownedby_pkgdb_cb(struct xbps_handle *xhp, - xbps_object_t obj, - const char *obj_key UNUSED, - void *arg, - bool *done UNUSED) -{ - xbps_dictionary_t pkgmetad; - xbps_array_t files_keys; - struct ffdata *ffd = arg; - const char *pkgver = NULL; - - (void)obj_key; - (void)done; - - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - pkgmetad = xbps_pkgdb_get_pkg_files(xhp, pkgver); - if (pkgmetad == NULL) - return 0; - - files_keys = xbps_dictionary_all_keys(pkgmetad); - for (unsigned int i = 0; i < xbps_array_count(files_keys); i++) { - match_files_by_pattern(pkgmetad, - xbps_array_get(files_keys, i), ffd, pkgver); +static char* archive_get_file(struct archive* ar, struct archive_entry* entry) { + ssize_t buflen; + char* buf; + + assert(ar != NULL); + assert(entry != NULL); + + buflen = archive_entry_size(entry); + buf = malloc(buflen + 1); + if (buf == NULL) + return NULL; + if (archive_read_data(ar, buf, buflen) != buflen) { + free(buf); + return NULL; } - xbps_object_release(pkgmetad); - xbps_object_release(files_keys); + buf[buflen] = '\0'; + return buf; +} - return 0; +static void* thread_func(void* data_ptr) { + struct thread_data* data = data_ptr; + struct archive_entry* entry; + + for (;;) { + char* pkg; + char* content; + char* line, *end_line; + char* file[3]; + int ar_rv; + + if (data->archive_mutex) pthread_mutex_lock(data->archive_mutex); + if ((ar_rv = archive_read_next_header(data->ar, &entry)) == ARCHIVE_OK) { + pkg = strdup(archive_entry_pathname(entry)); + content = archive_get_file(data->ar, entry); + } + if (data->archive_mutex) pthread_mutex_unlock(data->archive_mutex); + + if (ar_rv != ARCHIVE_OK) + break; + + line = strtok_r(content, "\n", &end_line); + while (line) { + parse_line(file, 3, line); + if (data->byhash) { + if (file[0] != NULL && strcasecmp(file[0], data->expr) == 0) + print_line(data->print_mutex, pkg, file); + } else if (data->expr != NULL) { + if (strcasestr(file[1], data->expr) != NULL || (file[2] && strcasestr(file[2], data->expr))) { + print_line(data->print_mutex, pkg, file); + } + } else { // regex + if (!regexec(data->regex, file[1], 0, NULL, 0) || + (file[2] && !regexec(data->regex, file[2], 0, NULL, 0))) { + print_line(data->print_mutex, pkg, file); + } + } + line = strtok_r(NULL, "\n", &end_line); + } + + free(pkg); + free(content); + } + + return NULL; } +static int repo_search_files(struct xbps_handle* xh, const char* repouri, bool byhash, const char* expr, regex_t* regex) { + struct archive* ar; + FILE* ar_file; + char* files_uri; + char* reponame_escaped; + pthread_mutex_t archive_mutex, print_mutex; + int maxthreads; + struct thread_data* thds; -static int -repo_match_cb(struct xbps_handle *xhp, - xbps_object_t obj, - const char *key UNUSED, - void *arg, - bool *done UNUSED) -{ - char bfile[PATH_MAX]; - xbps_dictionary_t filesd; - xbps_array_t files_keys; - struct ffdata *ffd = arg; - const char *pkgver = NULL; - int r; - - xbps_dictionary_set_cstring_nocopy(obj, "repository", ffd->repouri); - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - - r = xbps_pkg_path_or_url(xhp, bfile, sizeof(bfile), obj); - if (r < 0) { - xbps_error_printf("could not get package path: %s\n", strerror(-r)); - return -r; + if (!xbps_repository_is_remote(repouri)) { + files_uri = xbps_xasprintf("%s/%s-files", repouri, xh->target_arch ? xh->target_arch : xh->native_arch); + } else { + if (!(reponame_escaped = xbps_get_remote_repo_string(repouri))) + return false; + files_uri = xbps_xasprintf("%s/%s/%s-files", xh->metadir, reponame_escaped, xh->target_arch ? xh->target_arch : xh->native_arch); + free(reponame_escaped); } - filesd = xbps_archive_fetch_plist(bfile, "/files.plist"); - if (!filesd) { - xbps_error_printf("%s: couldn't fetch files.plist from %s: %s\n", - pkgver, bfile, strerror(errno)); - return EINVAL; + + if ((ar_file = fopen(files_uri, "r")) == NULL) { + xbps_dbg_printf("[repo] `%s' failed to open file-data archive %s\n", files_uri, strerror(errno)); + return false; } - files_keys = xbps_dictionary_all_keys(filesd); - for (unsigned int i = 0; i < xbps_array_count(files_keys); i++) { - match_files_by_pattern(filesd, - xbps_array_get(files_keys, i), ffd, pkgver); + + ar = archive_read_new(); + assert(ar); + + archive_read_support_filter_gzip(ar); + archive_read_support_filter_bzip2(ar); + archive_read_support_filter_xz(ar); + archive_read_support_filter_lz4(ar); + archive_read_support_filter_zstd(ar); + archive_read_support_format_tar(ar); + + if (archive_read_open_FILE(ar, ar_file) != ARCHIVE_OK) { + xbps_dbg_printf("[repo] `%s' failed to open repodata archive %s\n", files_uri, archive_error_string(ar)); + archive_read_close(ar); + fclose(ar_file); + return false; } - xbps_object_release(files_keys); - xbps_object_release(filesd); + maxthreads = (int) sysconf(_SC_NPROCESSORS_ONLN); + if (maxthreads <= 1) { + struct thread_data thd = { + .ar = ar, + .byhash = byhash, + .expr = expr, + .regex = regex, + .archive_mutex = NULL, + .print_mutex = NULL + }; + + thread_func(&thd); + } else { + thds = calloc(maxthreads, sizeof(*thds)); + assert(thds); + + pthread_mutex_init(&archive_mutex, NULL); + pthread_mutex_init(&print_mutex, NULL); + + for (int i = 0; i < maxthreads; i++) { + int rv; + + thds[i].ar = ar; + thds[i].byhash = byhash; + thds[i].expr = expr; + thds[i].regex = regex; + thds[i].archive_mutex = &archive_mutex; + thds[i].print_mutex = &print_mutex; + + if ((rv = pthread_create(&thds[i].this_thread, NULL, thread_func, &thds[i])) != 0) { + xbps_error_printf("unable to create thread: %s, retrying...\n", strerror(rv)); + i--; + } + } + + for (int i = 0; i < maxthreads; i++) { + pthread_join(thds[i].this_thread, NULL); + } + } + + fclose(ar_file); + free(files_uri); return 0; } -static int -repo_ownedby_cb(struct xbps_repo *repo, void *arg, bool *done UNUSED) -{ - xbps_array_t allkeys; - struct ffdata *ffd = arg; - int rv; +int ownedby(struct xbps_handle* xh, const char* file, bool repo_mode UNUSED, bool useregex) { + const char* expr = NULL; + regex_t regex; - ffd->repouri = repo->uri; - allkeys = xbps_dictionary_all_keys(repo->idx); - rv = xbps_array_foreach_cb_multi(repo->xhp, allkeys, repo->idx, repo_match_cb, ffd); - xbps_object_release(allkeys); + if (useregex) { + if (regcomp(®ex, file, REG_EXTENDED | REG_NOSUB | REG_ICASE) != 0) { + xbps_error_printf("xbps-locate: invalid regular expression\n"); + exit(1); + } + } else { + expr = file; + } - return rv; -} + for (unsigned int i = 0; i < xbps_array_count(xh->repositories); i++) { + const char* repo_uri; + xbps_array_get_cstring_nocopy(xh->repositories, i, &repo_uri); + repo_search_files(xh, repo_uri, false, expr, ®ex); + } -int -ownedby(struct xbps_handle *xhp, const char *pat, bool repo, bool regex) -{ - struct ffdata ffd; - int rv; + if (useregex) + regfree(®ex); - ffd.rematch = false; - ffd.pat = pat; + return 0; +} + +int ownedhash(struct xbps_handle* xh, const char* file, bool repo_mode UNUSED, bool regex UNUSED) { + const char* expr; + struct stat fstat; + char hash[XBPS_SHA256_SIZE]; - if (regex) { - ffd.rematch = true; - if (regcomp(&ffd.regex, ffd.pat, REG_EXTENDED|REG_NOSUB|REG_ICASE) != 0) - return EINVAL; + if (stat(file, &fstat) != -1) { + if (!S_ISREG(fstat.st_mode)) { + xbps_error_printf("query: `%s' exist but is not a regular file\n", file); + return 1; + } + if (!xbps_file_sha256(hash, sizeof(hash), file)) { + xbps_error_printf("query: cannot get hash of `%s': %s\n", file, strerror(errno)); + return 1; + } + expr = hash; + } else { + expr = file; } - if (repo) - rv = xbps_rpool_foreach(xhp, repo_ownedby_cb, &ffd); - else - rv = xbps_pkgdb_foreach_cb(xhp, ownedby_pkgdb_cb, &ffd); - if (regex) - regfree(&ffd.regex); + for (unsigned int i = 0; i < xbps_array_count(xh->repositories); i++) { + const char* repo_uri; + xbps_array_get_cstring_nocopy(xh->repositories, i, &repo_uri); + repo_search_files(xh, repo_uri, true, expr, NULL); + } - return rv; + return 0; } diff --git a/bin/xbps-rindex/Makefile b/bin/xbps-rindex/Makefile index 6dc200fb4..5e62569ce 100644 --- a/bin/xbps-rindex/Makefile +++ b/bin/xbps-rindex/Makefile @@ -2,7 +2,7 @@ TOPDIR = ../.. -include $(TOPDIR)/config.mk BIN = xbps-rindex -OBJS = main.o index-add.o index-clean.o remove-obsoletes.o repoflush.o sign.o +OBJS = main.o index-add.o files-add.o index-clean.o remove-obsoletes.o repoflush.o sign.o include $(TOPDIR)/mk/prog.mk diff --git a/bin/xbps-rindex/defs.h b/bin/xbps-rindex/defs.h index d99b59f38..2de6b0086 100644 --- a/bin/xbps-rindex/defs.h +++ b/bin/xbps-rindex/defs.h @@ -31,7 +31,10 @@ #define _XBPS_RINDEX "xbps-rindex" /* From index-add.c */ -int index_add(struct xbps_handle *, int, int, char **, bool, const char *); +int index_add(struct xbps_handle *, int, int, char **, bool, const char *, int*); + +/* From files-add.c */ +int files_add(struct xbps_handle *, int, int, char **, bool, const char *, int*, int*); /* From index-clean.c */ int index_clean(struct xbps_handle *, const char *, bool, const char *); @@ -45,7 +48,7 @@ int sign_repo(struct xbps_handle *, const char *, const char *, int sign_pkgs(struct xbps_handle *, int, int, char **, const char *, bool); /* From repoflush.c */ -bool repodata_flush(struct xbps_handle *, const char *, const char *, - xbps_dictionary_t, xbps_dictionary_t, const char *); +bool repodata_flush(struct xbps_handle *, const char *, const char *, xbps_dictionary_t, xbps_dictionary_t, const char *); +bool repodata_flush_files(struct xbps_handle *, const char *, const char *, const char *); #endif /* !_XBPS_RINDEX_DEFS_H_ */ diff --git a/bin/xbps-rindex/files-add.c b/bin/xbps-rindex/files-add.c new file mode 100644 index 000000000..660aa809a --- /dev/null +++ b/bin/xbps-rindex/files-add.c @@ -0,0 +1,432 @@ +/*- + * Copyright (c) 2023 Friedel Schon. + * 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, 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. + */ + +#include "defs.h" +#include "xbps/xbps_array.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OPEN_AR_READ(ar) \ + { \ + (ar) = archive_read_new(); \ + assert(ar); \ + archive_read_support_filter_gzip(ar); \ + archive_read_support_filter_bzip2(ar); \ + archive_read_support_filter_xz(ar); \ + archive_read_support_filter_lz4(ar); \ + archive_read_support_filter_zstd(ar); \ + archive_read_support_format_tar(ar); \ + } + + +static void +list_packages(xbps_array_t dest, struct archive* ar) { + struct archive_entry* entry; + const char* path; + + while (archive_read_next_header(ar, &entry) == ARCHIVE_OK) { + path = archive_entry_pathname(entry); + if (strcmp(path, "HASHES") == 0) + continue; + xbps_array_add_cstring(dest, path); + archive_read_data_skip(ar); + } +} + +static const char* match_pkgname_in_array(xbps_array_t array, const char* str) { + xbps_object_iterator_t iter; + xbps_object_t obj; + const char* pkgdep = NULL; + char pkgname[XBPS_NAME_SIZE]; + + assert(xbps_object_type(array) == XBPS_TYPE_ARRAY); + assert(str != NULL); + + iter = xbps_array_iterator(array); + assert(iter); + + while ((obj = xbps_object_iterator_next(iter))) { + /* match by pkgname against pkgver */ + pkgdep = xbps_string_cstring_nocopy(obj); + if (!xbps_pkg_name(pkgname, XBPS_NAME_SIZE, pkgdep)) + break; + if (strcmp(pkgname, str) == 0) { + xbps_object_iterator_release(iter); + return pkgdep; + } + } + + xbps_object_iterator_release(iter); + return NULL; +} + +static inline struct archive_entry* +make_entry(const char* pkgname, size_t size) { + struct archive_entry* entry; + + entry = archive_entry_new(); + if (entry == NULL) + return NULL; + + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_entry_set_uname(entry, "root"); + archive_entry_set_gname(entry, "root"); + archive_entry_set_pathname(entry, pkgname); + archive_entry_set_size(entry, size); + + return entry; +} + +static inline int +make_file_string(xbps_dictionary_t pkg, char* buffer, int buffer_size) { + const char *pkg_file, *pkg_target, *pkg_sha256; + int result; + + if (!xbps_dictionary_get_cstring_nocopy(pkg, "file", &pkg_file)) + return errno = EINVAL, 0; + + if (!xbps_dictionary_get_cstring_nocopy(pkg, "target", &pkg_target)) + pkg_target = ""; + + if (!xbps_dictionary_get_cstring_nocopy(pkg, "sha256", &pkg_sha256)) + pkg_sha256 = ""; + + if ((result = snprintf(buffer, buffer_size, "%s%%%s%%%s\n", pkg_sha256, pkg_file, pkg_target)) >= buffer_size) + return errno = ENOBUFS, 0; + + if (result == -1) + return 0; + + return result; +} + +static inline int +length_file_string(xbps_dictionary_t pkg) { + const char* field; + size_t size = 3; // 2x ':' + '\n' + + size += xbps_dictionary_get_cstring_nocopy(pkg, "file", &field) + ? strlen(field) + : 0; + + size += xbps_dictionary_get_cstring_nocopy(pkg, "target", &field) + ? strlen(field) + : 0; + + size += xbps_dictionary_get_cstring_nocopy(pkg, "sha256", &field) + ? strlen(field) + : 0; + + return size; +} + +int files_add(struct xbps_handle* xhp, int args, int argmax, char** argv, bool force, const char* compression, int* count_added, int* count_total) { + static char pkg_line[4096]; + xbps_dictionary_t props_plist, files_plist; + xbps_array_t existing_files, ignore_packages; + char * tmprepodir = NULL, *repodir = NULL, *new_ar_path, *rlockfname = NULL, *files_uri = NULL; + int rv = 0, ret = 0, rlockfd = -1; + FILE* old_ar_file; + int new_ar_file; + struct archive * old_ar = NULL, *new_ar; + struct archive_entry* entry; + mode_t mask; + + *count_added = 0, *count_total = 0; + + existing_files = xbps_array_create(); + ignore_packages = xbps_array_create(); + + assert(argv); + /* + * Read the repository data or create index dictionaries otherwise. + */ + if ((tmprepodir = strdup(argv[args])) == NULL) + return ENOMEM; + + repodir = dirname(tmprepodir); + if (!xbps_repo_lock(xhp, repodir, &rlockfd, &rlockfname)) { + xbps_error_printf("xbps-rindex: cannot lock repository " + "%s: %s\n", + repodir, strerror(errno)); + rv = -1; + goto out; + } + + files_uri = xbps_repo_path_with_name(xhp, repodir, "files"); + if ((old_ar_file = fopen(files_uri, "r")) == NULL) { + if (errno != ENOENT) { + xbps_error_printf("[repo] `%s' failed to open archive %s\n", files_uri, strerror(errno)); + goto out; + } + } else { + OPEN_AR_READ(old_ar); + + if (archive_read_open_FILE(old_ar, old_ar_file) == ARCHIVE_FATAL) { + xbps_dbg_printf("[repo] `%s' failed to open repodata archive %s\n", files_uri, archive_error_string(old_ar)); + return false; + } + + list_packages(existing_files, old_ar); + *count_total = xbps_array_count(existing_files); + archive_read_close(old_ar); + fseek(old_ar_file, 0, SEEK_SET); + OPEN_AR_READ(old_ar); + if (archive_read_open_FILE(old_ar, old_ar_file) == ARCHIVE_FATAL) { + xbps_dbg_printf("[repo] `%s' failed to open repodata archive %s\n", files_uri, archive_error_string(old_ar)); + return false; + } + } + + new_ar_path = xbps_xasprintf("%s.XXXXXXXXXX", files_uri); + assert(new_ar_path); + mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + if ((new_ar_file = mkstemp(new_ar_path)) == -1) + return false; + + umask(mask); + + new_ar = archive_write_new(); + assert(new_ar); + + if (compression == NULL || strcmp(compression, "zstd") == 0) { + archive_write_add_filter_zstd(new_ar); + archive_write_set_options(new_ar, "compression-level=9"); + } else if (strcmp(compression, "gzip") == 0) { + archive_write_add_filter_gzip(new_ar); + archive_write_set_options(new_ar, "compression-level=9"); + } else if (strcmp(compression, "bzip2") == 0) { + archive_write_add_filter_bzip2(new_ar); + archive_write_set_options(new_ar, "compression-level=9"); + } else if (strcmp(compression, "lz4") == 0) { + archive_write_add_filter_lz4(new_ar); + archive_write_set_options(new_ar, "compression-level=9"); + } else if (strcmp(compression, "xz") == 0) { + archive_write_add_filter_xz(new_ar); + archive_write_set_options(new_ar, "compression-level=9"); + } else if (strcmp(compression, "none") == 0) { + /* empty */ + } else { + return false; + } + + archive_write_set_format_pax_restricted(new_ar); + if (archive_write_open_fd(new_ar, new_ar_file) != ARCHIVE_OK) + return false; + + /* + * Process all packages specified in argv. + */ + for (int i = args; i < argmax; i++) { + const char * arch = NULL, *pkg = argv[i], *dbpkgver = NULL, *pkgname = NULL, *pkgver = NULL; + struct archive_entry* file_entry; + xbps_array_t keys; + size_t total_size = 0; + + assert(pkg); + /* + * Read metadata props plist dictionary from binary package. + */ + if ((props_plist = xbps_archive_fetch_plist(pkg, "/props.plist")) == NULL) { + xbps_error_printf("index: failed to read %s metadata for `%s', skipping!\n", XBPS_PKGPROPS, pkg); + continue; + } + + xbps_dictionary_get_cstring_nocopy(props_plist, "architecture", &arch); + xbps_dictionary_get_cstring_nocopy(props_plist, "pkgver", &pkgver); + if (!xbps_pkg_arch_match(xhp, arch, NULL)) { + fprintf(stderr, "index: skipping %s, unmatched arch (%s)\n", pkgver, arch); + xbps_object_release(props_plist); + } + + xbps_dictionary_get_cstring_nocopy(props_plist, "pkgname", &pkgname); + if (!force && (dbpkgver = match_pkgname_in_array(existing_files, pkgname))) { + /* Only check version if !force */ + ret = xbps_cmpver(pkgver, dbpkgver); + + /* + * If the considered package reverts the package in the index, + * consider the current package as the newer one. + */ + if (ret < 0 && xbps_pkg_reverts(props_plist, dbpkgver)) { + ret = 1; + /* + * If package in the index reverts considered package, consider the + * package in the index as the newer one. + */ + } else if (ret > 0 && xbps_pkg_reverts(props_plist, pkgver)) { + ret = -1; + } + + /* Same version or index version greater */ + if (ret <= 0) { + xbps_object_release(props_plist); + continue; + } + + xbps_array_add_cstring(ignore_packages, dbpkgver); + + (*count_total)--; + printf("files: updating `%s' -> `%s' (%s)\n", dbpkgver, pkgver, arch); + } + + if ((files_plist = xbps_archive_fetch_plist(pkg, "/files.plist")) == NULL) { + xbps_error_printf("files: failed to read files.plist metadata for `%s', skipping!\n", pkg); + xbps_object_release(props_plist); + continue; + } + + keys = xbps_dictionary_all_keys(files_plist); + for (unsigned int j = 0; j < xbps_array_count(keys); j++) { + xbps_array_t files = xbps_dictionary_get_keysym(files_plist, xbps_array_get(keys, j)); + + for (unsigned int k = 0; k < xbps_array_count(files); k++) + total_size += length_file_string(xbps_array_get(files, k)); + } + + file_entry = make_entry(pkgver, total_size); + if (archive_write_header(new_ar, file_entry) != ARCHIVE_OK) { + xbps_warn_printf("files: unable to write entry for %s: %s\n", pkgver, archive_error_string(new_ar)); + archive_entry_free(file_entry); + xbps_object_release(props_plist); + xbps_object_release(files_plist); + continue; + } + + for (unsigned int j = 0; j < xbps_array_count(keys); j++) { + int size; + xbps_array_t files = xbps_dictionary_get_keysym(files_plist, xbps_array_get(keys, j)); + + for (unsigned int k = 0; k < xbps_array_count(files); k++) { + if (!(size = make_file_string(xbps_array_get(files, k), pkg_line, sizeof(pkg_line)))) { + xbps_warn_printf("files: unable to create file-entry for %s: %s\n", pkgver, strerror(errno)); + continue; + } + + if (archive_write_data(new_ar, pkg_line, size) == -1) { + xbps_warn_printf("files: unable to file-write entry for %s: %s\n", pkgver, archive_error_string(new_ar)); + continue; + } + } + } + + + if (archive_write_finish_entry(new_ar) != ARCHIVE_OK) { + archive_entry_free(file_entry); + exit(archive_errno(new_ar)); + } + archive_entry_free(file_entry); + + (*count_added)++; + (*count_total)++; + printf("files: added `%s' (%s)\n", pkgver, arch); + + xbps_object_release(props_plist); + xbps_object_release(files_plist); + } + + if (*count_added > 0) { + if (old_ar != NULL) { + char buffer[1024]; + size_t buffer_size; + const char* pkgname; + + while (archive_read_next_header(old_ar, &entry) == ARCHIVE_OK) { + pkgname = archive_entry_pathname(entry); + if (xbps_match_string_in_array(ignore_packages, pkgname)) { + archive_read_data_skip(old_ar); + continue; + } + + printf("files: copying `%s' from old archive\n", pkgname); + + if (archive_write_header(new_ar, entry) != ARCHIVE_OK) { + archive_entry_free(entry); + continue; + } + + while ((buffer_size = archive_read_data(old_ar, buffer, sizeof(buffer))) > 0) { + assert(archive_write_data(new_ar, buffer, buffer_size) > 0); + } + + archive_write_finish_entry(new_ar); + } + + archive_read_free(old_ar); + } + /* Write data to tempfile and rename */ + if (archive_write_close(new_ar) != ARCHIVE_OK) + return false; + if (archive_write_free(new_ar) != ARCHIVE_OK) + return false; + + if (fchmod(new_ar_file, 0664) == -1) { + close(new_ar_file); + unlink(new_ar_path); + goto out; + } + close(new_ar_file); + if (rename(new_ar_path, files_uri) == -1) { + unlink(new_ar_path); + goto out; + } + } else { + if (old_ar != NULL) + archive_read_free(old_ar); + + /* Write data to tempfile and rename */ + if (archive_write_close(new_ar) != ARCHIVE_OK) + return false; + if (archive_write_free(new_ar) != ARCHIVE_OK) + return false; + + close(new_ar_file); + unlink(new_ar_path); + } + +out: + xbps_repo_unlock(rlockfd, rlockfname); + + if (tmprepodir) + free(tmprepodir); + + return rv; +} diff --git a/bin/xbps-rindex/index-add.c b/bin/xbps-rindex/index-add.c index 00b3132c9..fc3c25d40 100644 --- a/bin/xbps-rindex/index-add.c +++ b/bin/xbps-rindex/index-add.c @@ -200,7 +200,7 @@ repodata_commit(struct xbps_handle *xhp, const char *repodir, } int -index_add(struct xbps_handle *xhp, int args, int argmax, char **argv, bool force, const char *compression) +index_add(struct xbps_handle *xhp, int args, int argmax, char **argv, bool force, const char *compression, int *count) { xbps_dictionary_t idx, idxmeta, idxstage, binpkgd, curpkgd; struct xbps_repo *repo = NULL, *stage = NULL; @@ -383,7 +383,7 @@ index_add(struct xbps_handle *xhp, int args, int argmax, char **argv, bool force _XBPS_RINDEX, strerror(errno)); goto out; } - printf("index: %u packages registered.\n", xbps_dictionary_count(idx)); + *count = xbps_dictionary_count(idx); out: xbps_object_release(idx); diff --git a/bin/xbps-rindex/main.c b/bin/xbps-rindex/main.c index da80d3d00..4a156d827 100644 --- a/bin/xbps-rindex/main.c +++ b/bin/xbps-rindex/main.c @@ -159,9 +159,16 @@ main(int argc, char **argv) exit(EXIT_FAILURE); } - if (add_mode) - rv = index_add(&xh, optind, argc, argv, force, compression); - else if (clean_mode) + if (add_mode) { + int index_count = 0, files_count = 0, files_added = 0; + + rv = index_add(&xh, optind, argc, argv, force, compression, &index_count); + if (rv == 0) + rv = files_add(&xh, optind, argc, argv, force, compression, &files_added, &files_count); + + printf("index: %d packages registered\n", index_count); + printf("files: %d packages registered, %d new packages\n", files_count, files_added); + } else if (clean_mode) rv = index_clean(&xh, argv[optind], hashcheck, compression); else if (rm_mode) rv = remove_obsoletes(&xh, argv[optind]); diff --git a/include/xbps.h.in b/include/xbps.h.in index 5ccd51e94..70b6beafb 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -121,6 +121,12 @@ */ #define XBPS_REPOIDX_META "index-meta.plist" +/** + * @def XBPS_REPO_FILES + * Filename for the repository files property list. + */ +#define XBPS_REPO_FILES "files.plist" + /** * @def XBPS_FLAG_VERBOSE * Verbose flag that can be used in the function callbacks to alter @@ -1431,7 +1437,13 @@ struct xbps_repo { * * Proplib dictionary associated with the repository index-meta. */ - xbps_dictionary_t idxmeta; + xbps_dictionary_t idxmeta; + /** + * @var files + * + * Proplib dictionary associated with the repository files. + */ + xbps_dictionary_t files; /** * @var uri * @@ -1442,6 +1454,12 @@ struct xbps_repo { * @private */ int fd; + /** + * @var filear + * + * archive_read instance for *-files + */ + struct archive *filear; /** * var is_remote * @@ -1471,6 +1489,8 @@ void xbps_rpool_release(struct xbps_handle *xhp); */ int xbps_rpool_sync(struct xbps_handle *xhp, const char *uri); +int xbps_rpool_sync_files(struct xbps_handle* xhp); + /** * Iterates over the repository pool and executes the \a fn function * callback passing in the void * \a arg argument to it. The bool pointer @@ -2368,6 +2388,11 @@ xbps_plist_array_from_file(const char *path); xbps_dictionary_t xbps_plist_dictionary_from_file(const char *path); +/** + +*/ +char* xbps_get_remote_repo_string(const char *path); + /**@}*/ #ifdef __cplusplus diff --git a/include/xbps_api_impl.h b/include/xbps_api_impl.h index aac5b11b5..f4a7f8d7f 100644 --- a/include/xbps_api_impl.h +++ b/include/xbps_api_impl.h @@ -99,7 +99,6 @@ int HIDDEN xbps_transaction_fetch(struct xbps_handle *, int HIDDEN xbps_transaction_pkg_deps(struct xbps_handle *, xbps_array_t, xbps_dictionary_t); int HIDDEN xbps_transaction_internalize(struct xbps_handle *, xbps_object_iterator_t); -char HIDDEN *xbps_get_remote_repo_string(const char *); int HIDDEN xbps_repo_sync(struct xbps_handle *, const char *); int HIDDEN xbps_file_hash_check_dictionary(struct xbps_handle *, xbps_dictionary_t, const char *, const char *); @@ -120,5 +119,6 @@ xbps_array_t HIDDEN xbps_get_pkg_fulldeptree(struct xbps_handle *, struct xbps_repo HIDDEN *xbps_regget_repo(struct xbps_handle *, const char *); int HIDDEN xbps_conf_init(struct xbps_handle *); +int HIDDEN xbps_repo_sync_files(struct xbps_handle* xh, const char* uri); #endif /* !_XBPS_API_IMPL_H_ */ diff --git a/lib/Makefile b/lib/Makefile index 0cf6ac84f..481f82cc7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -48,7 +48,7 @@ OBJS += pubkey2fp.o package_fulldeptree.o OBJS += download.o initend.o pkgdb.o OBJS += plist.o plist_find.o plist_match.o archive.o OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o -OBJS += repo.o repo_sync.o +OBJS += repo.o repo_sync.o repo_sync_files.o OBJS += rpool.o cb_util.o proplib_wrapper.o OBJS += package_alternatives.o OBJS += conf.o log.o diff --git a/lib/repo.c b/lib/repo.c index 8d2aa6bd1..6808f9716 100644 --- a/lib/repo.c +++ b/lib/repo.c @@ -40,6 +40,7 @@ #include #include +#include "fetch.h" #include "xbps_api_impl.h" /** @@ -58,7 +59,7 @@ xbps_repo_path_with_name(struct xbps_handle *xhp, const char *url, const char *n { assert(xhp); assert(url); - assert(strcmp(name, "repodata") == 0 || strcmp(name, "stagedata") == 0); + assert(strcmp(name, "repodata") == 0 || strcmp(name, "stagedata") == 0 || strcmp(name, "files") == 0); return xbps_xasprintf("%s/%s-%s", url, xhp->target_arch ? xhp->target_arch : xhp->native_arch, name); @@ -707,3 +708,39 @@ xbps_repo_key_import(struct xbps_repo *repo) free(rkeyfile); return rv; } + +char* +xbps_get_remote_repo_string(const char *uri) +{ + struct url *url; + size_t i; + char *p; + + if ((url = fetchParseURL(uri)) == NULL) + return NULL; + + /* + * Replace '.' ':' and '/' characters with underscores, so that + * provided URL: + * + * http://nocturno.local:8080/repo/x86_64 + * + * becomes: + * + * http___nocturno_local_8080_repo_x86_64 + */ + if (url->port != 0) + p = xbps_xasprintf("%s://%s:%u%s", url->scheme, + url->host, url->port, url->doc); + else + p = xbps_xasprintf("%s://%s%s", url->scheme, + url->host, url->doc); + + fetchFreeURL(url); + for (i = 0; i < strlen(p); i++) { + if (p[i] == '.' || p[i] == '/' || p[i] == ':') + p[i] = '_'; + } + + return p; +} \ No newline at end of file diff --git a/lib/repo_sync.c b/lib/repo_sync.c index 2f803a0c4..8eedc2c06 100644 --- a/lib/repo_sync.c +++ b/lib/repo_sync.c @@ -34,42 +34,6 @@ #include "xbps_api_impl.h" #include "fetch.h" -char HIDDEN * -xbps_get_remote_repo_string(const char *uri) -{ - struct url *url; - size_t i; - char *p; - - if ((url = fetchParseURL(uri)) == NULL) - return NULL; - - /* - * Replace '.' ':' and '/' characters with underscores, so that - * provided URL: - * - * http://nocturno.local:8080/repo/x86_64 - * - * becomes: - * - * http___nocturno_local_8080_repo_x86_64 - */ - if (url->port != 0) - p = xbps_xasprintf("%s://%s:%u%s", url->scheme, - url->host, url->port, url->doc); - else - p = xbps_xasprintf("%s://%s%s", url->scheme, - url->host, url->doc); - - fetchFreeURL(url); - for (i = 0; i < strlen(p); i++) { - if (p[i] == '.' || p[i] == '/' || p[i] == ':') - p[i] = '_'; - } - - return p; -} - /* * Returns -1 on error, 0 if transfer was not necessary (local/remote * size and/or mtime match) and 1 if downloaded successfully. diff --git a/lib/repo_sync_files.c b/lib/repo_sync_files.c new file mode 100644 index 000000000..005826bc4 --- /dev/null +++ b/lib/repo_sync_files.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2009-2014 Juan Romero Pardines. + * 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, 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. + */ + +#include + +#include +#include +#include +#include +#include + +#include "xbps.h" +#include "xbps_api_impl.h" +#include "fetch.h" + + +int HIDDEN xbps_repo_sync_files(struct xbps_handle* xh, const char* uri) { + mode_t prev_umask; + const char *arch, *fetchstr = NULL; + char * repodata, *lrepodir, *uri_fixedp; + int rv = 0; + + /* ignore non remote repositories */ + if (!xbps_repository_is_remote(uri)) + return 0; + + uri_fixedp = xbps_get_remote_repo_string(uri); + if (uri_fixedp == NULL) + return -1; + + if (xh->target_arch) + arch = xh->target_arch; + else + arch = xh->native_arch; + + /* + * Full path to repository directory to store the plist + * index file. + */ + lrepodir = xbps_xasprintf("%s/%s", xh->metadir, uri_fixedp); + free(uri_fixedp); + /* + * Create repodir in metadir. + */ + prev_umask = umask(022); + if ((rv = xbps_mkpath(lrepodir, 0755)) == -1 && errno != EEXIST) { + xbps_error_printf("[reposync] to create repodir `%s': %s\n", lrepodir, strerror(errno)); + umask(prev_umask); + free(lrepodir); + return rv; + } + if (chdir(lrepodir) == -1) { + xbps_error_printf("[reposync] failed to change dir to repodir `%s': %s\n", lrepodir, strerror(errno)); + umask(prev_umask); + free(lrepodir); + return -1; + } + free(lrepodir); + /* + * Remote repository plist index full URL. + */ + repodata = xbps_xasprintf("%s/%s-files", uri, arch); + + /* reposync start cb */ + printf("[*] Updating file-database `%s' ...\n", repodata); + /* + * Download plist index file from repository. + */ + if ((rv = xbps_fetch_file(xh, repodata, NULL)) != 1 && fetchLastErrCode != FETCH_UNCHANGED) { + /* reposync error cb */ + fetchstr = xbps_fetch_error_string(); + + xbps_error_printf("[reposync] failed to fetch file `%s': %s\n", repodata, fetchstr ? fetchstr : strerror(errno)); + } else if (rv == 1) + rv = 0; + umask(prev_umask); + + free(repodata); + + return rv; +} + +int xbps_rpool_sync_files(struct xbps_handle* xhp) { + const char* repouri = NULL; + + for (unsigned int i = 0; i < xbps_array_count(xhp->repositories); i++) { + xbps_array_get_cstring_nocopy(xhp->repositories, i, &repouri); + if (xbps_repo_sync_files(xhp, repouri) == -1) { + xbps_dbg_printf( + "[rpool] `%s' failed to fetch repository data: %s\n", + repouri, fetchLastErrCode == 0 ? strerror(errno) : xbps_fetch_error_string()); + continue; + } + } + return 0; +} \ No newline at end of file