Greetings Attached is the pre-pre-pre version of the emerge.c. Right now it *almost* does the ftp. Calling wget_main isn't working, though, and I haven't figured out why, and my head is tired of looking for it, so I'm going to leave it like that. Currently the app expects the index file to look like this (in order!) sys-devel/binutils{1.2;2.3;3.4;} sys-devel/development-sources{2.6.11;} x11-wm/busybox{4.5;} This patch will apply to busybox-1.00. Hope I can get access to subversion so we can all play on this together. Otherwise, please send updates back to me. Looking forward to working with you all. It's not everything it could be, of course, not all spiffy, not especially correct, but hey, it's a start. :) Ben Index: archival/Makefile.in =================================================================== --- archival/Makefile.in (revision 9925) +++ archival/Makefile.in (working copy) @@ -30,6 +30,7 @@ ARCHIVAL-$(CONFIG_CPIO) += cpio.o ARCHIVAL-$(CONFIG_DPKG) += dpkg.o ARCHIVAL-$(CONFIG_DPKG_DEB) += dpkg_deb.o +ARCHIVAL-$(CONFIG_EMERGE) += emerge.o ARCHIVAL-$(CONFIG_GUNZIP) += gunzip.o ARCHIVAL-$(CONFIG_GZIP) += gzip.o ARCHIVAL-$(CONFIG_RPM2CPIO) += rpm2cpio.o Index: archival/emerge.c =================================================================== --- archival/emerge.c (revision 0) +++ archival/emerge.c (revision 0) @@ -0,0 +1,579 @@ +/* +* Distributed under the terms of the GNU General Public License v2 +* $Header: $ +* +******************************************************************** +* 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 of the +* License, 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., 59 Temple Place - Suite 330, Boston, +* MA 02111-1307, USA. +*/ + +/* 1) The ability to fetch a native portage built package .tbz2 + * 2) No changes in package format handling. + * (ie no .ipk/.apk/.homebrew) + * 3) The ability to record the contents of a package exactly the same way + * portage does. (/var/db/pkg/$PN/CONTENTS) + * 4) The ability to uninstall a binary package based on whats in listed in + * the CONTENTS file. + * 5) The ability to omit files from being installed based on a setting + * called INSTALL_MASK. This should behave exactly like the current portage + * implementation if the INSTALL_MASK feature which accepts globing. + * ex: INSTALL_MASK="*.a /usr/lib/ *.o /usr/include *.pl" + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define file_exists(f) (access(f, R_OK) == 0) +#define dir_exists(f) (access(f, R_OK) == 0) + +#define PM (PATH_MAX) +#define PKG_NAME_LEN (32) +#define CAT_NAME_LEN (16) +#define VERSION_LEN (16) +#define BINHOST_LEN (32) + +llist_t *pkg_index; /* a list of category structs: data is a (category_t) */ +typedef struct category_s { + llist_t *packages; + char name[CAT_NAME_LEN]; +} category_t; + +typedef struct package_s { + llist_t *versions; + category_t *category; + char name[PKG_NAME_LEN]; +} package_t; + +/* versions are just strings; each version-string is separated by ';' */ +/* see debug_print_index for how to run through this structure */ + +char *searchable_packages; /* this is a qsorted bsearchable list of package_t's */ +enum Action { + /* actions: + fetch, unmerge, query, sync, list, update, clean + */ + NONE = 0, FETCH, UNMERGE, QUERY, SYNC, LIST, UPDATE, CLEAN, MERGE +}; + +struct configurables { + char *root; + char *index; + char *tmpdir; + char *binhost; + char *pkgdir; + int verbose; + int debug; + int pretend; + int usepkgonly; + enum Action action; + llist_t *packages; +}; + +static struct configurables conf; + +void einfo(char *str) +{ + bb_fprintf(stdout, ">>> %s\n", str); +} + +void eerror(char *str) +{ + bb_fprintf(stderr, "!!! %s\n", str); +} + +void die(char *str) +{ + eerror(str); + _exit(EXIT_FAILURE); +} + +static int do_unpack() +{ + /* A portage built package more or less is a bzip2 level 9 compressed + tarball with some extra metadata concatenated to the end of the tbz2ball + and a small header. The metadata is discarded at install time but would + prove useful to parse to ensure that we are fetching a binary package + that matches our devices mini make.conf (ie does CHOST= in the metadata + match the CHOST= that we are about to install a pkg for) */ + return 1; +} + +static int fetch_package(package_t * package) +{ + /* using the package in package-name (which is category/package), figure + * out the actual package name and URL. Then fetch it. return 0 when it works. + */ + /* append the category/package to the binhost to create the url */ + + static char fullurl[CAT_NAME_LEN + PKG_NAME_LEN + BINHOST_LEN]; + + sprintf(fullurl, "%s/%s/%s-%s.tar.bz2", conf.binhost, + package->category->name, package->name, package->versions->data); + bb_printf("fullurl is %s\n", fullurl); + { + char *cmdline[] = { "wget", "-c", "-q", fullurl }; + int rval = wget_main(4, cmdline); + + bb_printf("child status is %d\n", rval); + return rval; + } + return 1; +} + +static package_t *find_package(char *pkgname) +{ + /* pkgname is what was specified on the command line, like "emerge foo" + * look in the Index structure, find the foo, return the package structure + */ + /* this could be replaced with a bsearch later... */ + package_t *package = 0; + llist_t *c = pkg_index, *p = 0; + category_t *cat; + + while (c != 0) { + cat = (category_t *) c->data; + p = cat->packages; + while (p != 0) { + package = (package_t *) p->data; + if (strncmp(package->name, pkgname, PKG_NAME_LEN) == 0) + return package; + p = p->link; + } + c = c->link; + } + return 0; +} + + +static int do_fetch() +{ + llist_t *tp = conf.packages; + package_t *package = 0; + + while (tp != 0) { + package = find_package(tp->data); + if (!package) + die("Programming error: no package specified!"); + if (fetch_package(package) != 0) + die("Failed to fetch package"); + tp = tp->link; + } + return 0; +} + +static int check_if_installed(char *package) +{ + return 0; + /* we know what packages are installed by looking in /var/db/portage ... */ +} +static int do_merge() +{ +#if 0 + llist_t *package = conf.packages; + int prev_installed = 0; + + while (package != 0) { + decode_package_name(package->data); /* fills cpv_name and package_name */ + prev_installed = check_if_installed(package->data); /* probably return more useful info, like version + number, package name, something like that */ + if (resolve_package_name(package->data) != 0) /* sticks full pkg name in package_name */ + die("FAILED to resolve package name"); + fetch_package(); /* uses pkg_name. good thing we're not MT!! may die() */ + checksum_package(); /* uses pkg_name. may die() */ + unpack_package(); /* ditto */ + remove_prev( /*??? */ ); + update_db(); /* ditto */ + package = package->link; + } +#endif + return 0; +} +static int do_unmerge() +{ + return 1; +} +static int do_query() +{ + return 1; +} +static int do_sync() +{ + return 1; +} +static int do_list() +{ + return 1; +} +static int do_update() +{ + return 1; +} +static int do_clean() +{ + return 1; +} + +static int mkalldirs(char *dir) +{ + return bb_make_directory(dir, -1, FILEUTILS_RECUR); +} + +static void complete_configuration() +{ + /* if the items in the configuration struct are not set up, try to get from the environment. + if that fails--error! */ +#undef FOO +#define FOO "Try adding it to your /etc/make.conf, exporting it as an environment variable, or specifying it on the command line" + /* verify that environment variables exist before going on, fix them on disk if necessary */ + if (conf.pkgdir == 0) { + conf.pkgdir = getenv("PKGDIR"); + if (conf.pkgdir == 0) + die("PKGDIR is not set. " FOO); + } + if (conf.binhost == 0) { + conf.binhost = getenv("PORTAGE_BINHOST"); + if (conf.binhost == 0) + die("PORTAGE_BINHOST is not set. " FOO); + } + if (conf.tmpdir == 0) { + conf.tmpdir = getenv("PORTAGE_TMPDIR"); + if (conf.tmpdir == 0) + die("PORTAGE_TMPDIR is not set. " FOO); + } + if (conf.root == 0) { + conf.root = getenv("ROOT"); + if (conf.root == 0) + die("ROOT is not set. " FOO); + } +#undef FOO +} + +/* busybox has their own funny getopt structure, which depends on bits... */ +#define EMERGE_HELP (1<<0) +#define EMERGE_DEBUG (1<<1) +#define EMERGE_PRETEND (1<<2) +#define EMERGE_VERBOSE (1<<3) +#define EMERGE_FETCH (1<<4) +#define EMERGE_USEPKGONLY (1<<5) +#define EMERGE_UNMERGE (1<<6) +#define EMERGE_QUERY (1<<7) +#define EMERGE_INFO (1<<8) +#define EMERGE_SYNC (1<<9) +#define EMERGE_LIST (1<<10) +#define EMERGE_UPDATE (1<<11) +#define EMERGE_CLEAN (1<<12) + +static struct option const emerge_long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 0, 0, 'd'}, + {"pretend", 0, 0, 'p'}, + {"verbose", 0, 0, 'v'}, + {"fetch", 0, 0, 'f'}, + {"usepkgonly", 0, 0, 'K'}, + {"unmerge", 0, 0, 'C'}, + {"query", 0, 0, 'q'}, + {"info", 0, 0, 'i'}, + {"sync", 0, 0, 's'}, + {"list", 0, 0, 'l'}, + {"update", 0, 0, 'u'}, + {"clean", 0, 0, 'c'}, + {"root", 1, 0, 'R'}, + {"index", 1, 0, 'I'}, + {"pkgdir", 1, 0, 'P'}, + {"tmpdir", 1, 0, 'T'}, + {"binhost", 1, 0, 'B'}, + {NULL, 0, NULL, 0} +}; + +#define OPTIONS_FLAGS "hdpvfKCqislucR:I:P:T:B:" +void parseargs(int argc, char *argv[]) +{ + unsigned long opt; + + opterr = 0; + /* this complementaly only works if a conflict is specified in both directions, + thus f~q:q~f will block f & q from being specified together. that seems + pretty wrong to me */ + bb_opt_complementaly = "f~Cqisluc:i~pfKCqsluc:q~f"; + bb_applet_long_options = emerge_long_options; + + /* need to allocate these flags, not let bb do it! */ + conf.root = conf.index = conf.pkgdir = conf.tmpdir = conf.binhost = 0; + opt = bb_getopt_ulflags(argc, argv, OPTIONS_FLAGS, + &conf.root, + &conf.index, + &conf.pkgdir, &conf.tmpdir, &conf.binhost); + + /* Check one and only one context option was given */ + /* this doesn't work... */ + if (opt & 0x80000000UL) { + bb_printf("conflict!\n"); + bb_show_usage(); + } + + if (opt & EMERGE_HELP) + bb_show_usage(); + + /* runtime-configuration */ + if (opt & EMERGE_DEBUG) + conf.debug = 1; + if (opt & EMERGE_PRETEND) + conf.pretend = 1; + if (opt & EMERGE_VERBOSE) + conf.verbose = 1; + if (opt & EMERGE_USEPKGONLY) + conf.usepkgonly = 1; + + /* actions */ + if (opt & EMERGE_INFO + || (argv[optind] && strncmp(argv[optind], "info", 4) == 0)) { + /* info: perform info, then exit */ + bb_printf("info\n"); + exit(0); + } else { + if (opt & EMERGE_FETCH) + conf.action = FETCH; + else if (opt & EMERGE_UNMERGE) + conf.action = UNMERGE; + else if (opt & EMERGE_QUERY) + conf.action = QUERY; + else if (opt & EMERGE_SYNC) + conf.action = SYNC; + else if (opt & EMERGE_LIST) + conf.action = LIST; + else if (opt & EMERGE_UPDATE) + conf.action = UPDATE; + else if (opt & EMERGE_CLEAN) + conf.action = CLEAN; + else + conf.action = MERGE; + } + + /* this adds the remainder of the arguments (i.e. the packages) reversed, so they + are processed in the order specified (which may be important */ + int optend = argc - 1; + + while (optend >= optind) { + conf.packages = llist_add_to(conf.packages, argv[optend]); + optend--; + } + +} + + +static void debug_print_index() +{ + llist_t *c = pkg_index, *p, *v; + category_t *cat; + package_t *pkg; + + while (c != 0) { + cat = (category_t *) c->data; + bb_printf("category %s\n", cat->name); + p = cat->packages; + while (p != 0) { + pkg = (package_t *) p->data; + bb_printf(" package %s\n", pkg->name); + v = pkg->versions; + bb_printf(" versions "); + while (v != 0) { + bb_printf("%s ", v->data); + v = v->link; + } + bb_printf("\n"); + p = p->link; + } + c = c->link; + } +} + +#undef Z +#define Z (CAT_NAME_LEN+PKG_NAME_LEN+VERSION_LEN) +static char buf[Z], tmp[Z]; +static void add_package_version(category_t * cat, int idx) +{ + /* buf[idx] is the '/' which separates the category and the package_version */ + int tidx = 0; + package_t *pack; + char *ver; + + ++idx; + while (buf[idx] != '{' && idx < PKG_NAME_LEN + CAT_NAME_LEN) { /* is this condition right? */ + tmp[tidx++] = buf[idx++]; + } + tmp[tidx] = 0; + bb_printf("got package named %s\n", tmp); + pack = xmalloc(sizeof(package_t)); + pack->versions = 0; + strncpy(pack->name, tmp, tidx); + pack->name[tidx] = 0; + /* loop through versions in buf, extracting and putting into version_ll */ + ++idx; + tidx = 0; + while (buf[idx] != '}' && idx < Z) { + if (buf[idx] == ';') { + tmp[tidx] = 0; + ver = xmalloc(tidx + 1 * sizeof(char *)); + if (ver == 0) + die("Failed to malloc space for version string"); + strncpy(ver, tmp, tidx); + ver[tidx] = 0; + bb_printf("adding version %s\n", ver); + pack->versions = llist_add_to(pack->versions, ver); + pack->category = cat; + tidx = 0; + ++idx; + ver = 0; + } else { + /* still collecting version */ + tmp[tidx] = buf[idx]; + ++tidx; + ++idx; + } + } + cat->packages = llist_add_to(cat->packages, (char *) pack); + bb_printf("done adding packages\n"); +} + +static int process_index() +{ + /* fill in the char** index data structure from the index file */ + int rval = 1, idx = 0, tidx = 0; + category_t *cat = 0; + FILE *index_file; + int fd = 0; + + index_file = bb_xfopen(conf.index, "r"); + if (index_file == 0) + die("Failed to open index file"); + while (rval == 1) { + idx = tidx = 0; + bb_printf("gonna read...\n"); + rval = fscanf(index_file, "%s", buf); + if (rval == EOF || rval != 1) + /* file ended */ + break; + while (buf[idx] != '/' && idx < CAT_NAME_LEN) { + tmp[tidx++] = buf[idx++]; + } + if (idx == CAT_NAME_LEN) + die("Index has bad data, couldn't find '/'"); + tmp[idx] = 0; + /* tmp has the category name */ + /* is this a new category? */ + if (cat == 0 || strncmp(cat->name, tmp, Z) != 0) { + if (cat != 0) { + /* add old category to llist */ + bb_printf("adding last cat: %s\n", cat->name); + pkg_index = llist_add_to(pkg_index, (char *) cat); + cat = 0; + } + /* there is no category at all (i.e. the first entry ever) */ + cat = xmalloc(sizeof(category_t)); + if (cat == 0) + die("Failed to malloc category struct"); + cat->packages = 0; + strncpy(cat->name, tmp, CAT_NAME_LEN); + bb_printf("got new cat name %s\n", cat->name); + } + /* process the package and the version of this entry */ + bb_printf("adding p_v to cat %s\n", cat->name); + add_package_version(cat, idx); + } + if (cat != 0) /* add the last one to the ll */ + pkg_index = llist_add_to(pkg_index, (char *) cat); +#undef L + return 0; +} + +int emerge_main(int argc, char **argv) +{ + char tmpp[PM]; + unsigned int len; + llist_t *tlist; + + if (argc < 2) + bb_show_usage(); + + conf.verbose = 0; + conf.debug = 0; + conf.pretend = 0; + conf.usepkgonly = 0; + conf.packages = 0; + conf.action = NONE; + + parseargs(argc, argv); + + printf("conf.index is %s\n", conf.index); + tlist = conf.packages; + bb_printf("will handle packages "); + while (tlist) { + bb_printf("%s ", tlist->data); + tlist = tlist->link; + } + + /* for some of the actions, we won't need the index, but + * I'll care about that later */ + process_index(); + debug_print_index(); + /* now configurations may have some items, and some not. function may exit */ + complete_configuration(); + + + /* at this point, all configuration is known */ + assert(conf.action != NONE); + + /* make sure PKGDIR exists on disk */ + len = strnlen(conf.pkgdir, PM); + strncpy(tmpp, conf.pkgdir, len); + strncat(tmpp, "/All", PM - 1 - len); + if (!dir_exists(tmpp)) /* dir_exists checks R_OK */ + if (mkalldirs(tmpp) != 0) + die("FAILED to make PKGDIR/All"); + +#undef ASP +#define ASP " all specified packages" + /* now we are all set up. perform the action */ + if (conf.action == FETCH && do_fetch(0) != 0) + die("FAILED to fetch" ASP); + else if (conf.action == UNMERGE && do_unmerge() != 0) + die("FAILED to unmerge" ASP); + else if (conf.action == QUERY && do_query() != 0) + die("FAILED to query" ASP); + else if (conf.action == SYNC && do_sync() != 0) + die("FAILED to sync"); + else if (conf.action == LIST && do_list() != 0) + die("FAILED to list" ASP); + else if (conf.action == UPDATE && do_update() != 0) + die("FAILED to update" ASP); + else if (conf.action == CLEAN && do_clean() != 0) + die("FAILED to clean" ASP); + else if (conf.action == MERGE && do_merge() != 0) + die("FAILED to merge" ASP); +#undef ASP + + _exit(EXIT_SUCCESS); +} Index: archival/Config.in =================================================================== --- archival/Config.in (revision 9925) +++ archival/Config.in (working copy) @@ -97,6 +97,14 @@ However it saves space as none of the extra dpkg-deb, ar or tar options are needed, they are linked to internally. +config CONFIG_EMERGE + bool "emerge workalike for gentoo packages" + default n + depends on CONFIG_WGET && CONFIG_FEATURE_TAR_BZIP2 + help + This allows busybox to fetch and install native build packages + from the gnetoo portage system. + config CONFIG_GUNZIP bool "gunzip" default n Index: include/usage.h =================================================================== --- include/usage.h (revision 9925) +++ include/usage.h (working copy) @@ -531,6 +531,30 @@ "$ echo "Erik\\nis\\ncool"\n" \ "Erik\\nis\\ncool\n") +#define emerge_trivial_usage \ + "[-hdpvfKCqislucR:I:P:T:B:] package_name" +#define emerge_full_usage \ + "emerge is a utility to install, remove and manage Gentoo packages.\n" \ + "Options:\n" \ + "\t-h|--help : This help\n" \ + "\t-d|--debug : set -x\n" \ + "\t-p|--pretend : pretend only\n" \ + "\t-v|--verbose : set verbose flag\n" \ + "\t-f|--fetch : fetch binary files\n" \ + "\t-K|--usepkgonly : use binary package\n" \ + "\t-C|--unmerge : uninstall a binary package\n" \ + "\t-q|--query : query a package\n" \ + "\t--info|info : display info\n" \ + "\t--sync : perform $opt operation\n" \ + "\t--list : update index and list packages\n" \ + "\t--update : update index file\n" \ + "\t--clean : clean up tmpfiles\n" \ + "\t--root= : define ROOT=\n" \ + "\t--index= : define INDEX_FILE=\n" \ + "\t--pkgdir= : define PKGDIR=\n" \ + "\t--tmpdir= : define PORTAGE_TMPDIR=\n" \ + "\t--binhost= : define PORTAGE_BINHOST=\n" + #define env_trivial_usage \ "[-iu] [-] [name=value]... [command]" #define env_full_usage \ Index: include/applets.h =================================================================== --- include/applets.h (revision 9925) +++ include/applets.h (working copy) @@ -178,6 +178,9 @@ #if defined(CONFIG_FEATURE_GREP_EGREP_ALIAS) APPLET_NOUSAGE("egrep", grep_main, _BB_DIR_BIN, _BB_SUID_NEVER) #endif +#if defined(CONFIG_EMERGE) + APPLET(emerge, emerge_main, _BB_DIR_BIN, _BB_SUID_NEVER) +#endif #ifdef CONFIG_ENV APPLET(env, env_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER) #endif