source: MondoRescue/branches/stable/mindi-busybox/archival/dpkg.c@ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 16 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

File size: 53.4 KB
RevLine 
[1770]1/* vi: set sw=4 ts=4: */
[821]2/*
[1770]3 * mini dpkg implementation for busybox.
4 * this is not meant as a replacement for dpkg
[821]5 *
[1770]6 * written by glenn mcgrath with the help of others
7 * copyright (c) 2001 by glenn mcgrath
[821]8 *
[1770]9 * started life as a busybox implementation of udpkg
[821]10 *
[1770]11 * licensed under gplv2 or later, see file license in this tarball for details.
[821]12 */
13
14/*
[1770]15 * known difference between busybox dpkg and the official dpkg that i don't
[821]16 * consider important, its worth keeping a note of differences anyway, just to
17 * make it easier to maintain.
[1770]18 * - the first value for the confflile: field isnt placed on a new line.
19 * - when installing a package the status: field is placed at the end of the
20 * section, rather than just after the package: field.
[821]21 *
[1770]22 * bugs that need to be fixed
[821]23 * - (unknown, please let me know when you find any)
24 *
25 */
26
[1770]27#include "libbb.h"
[821]28#include "unarchive.h"
29
[1770]30/* note: if you vary hash_prime sizes be aware,
31 * 1) tweaking these will have a big effect on how much memory this program uses.
32 * 2) for computational efficiency these hash tables should be at least 20%
[821]33 * larger than the maximum number of elements stored in it.
[1770]34 * 3) all _hash_prime's must be a prime number or chaos is assured, if your looking
[821]35 * for a prime, try http://www.utm.edu/research/primes/lists/small/10000.txt
[1770]36 * 4) if you go bigger than 15 bits you may get into trouble (untested) as its
37 * sometimes cast to an unsigned, if you go to 16 bit you will overlap
[821]38 * int's and chaos is assured, 16381 is the max prime for 14 bit field
39 */
40
41/* NAME_HASH_PRIME, Stores package names and versions,
42 * I estimate it should be at least 50% bigger than PACKAGE_HASH_PRIME,
43 * as there a lot of duplicate version numbers */
44#define NAME_HASH_PRIME 16381
45
46/* PACKAGE_HASH_PRIME, Maximum number of unique packages,
47 * It must not be smaller than STATUS_HASH_PRIME,
48 * Currently only packages from status_hashtable are stored in here, but in
49 * future this may be used to store packages not only from a status file,
50 * but an available_hashtable, and even multiple packages files.
51 * Package can be stored more than once if they have different versions.
52 * e.g. The same package may have different versions in the status file
53 * and available file */
54#define PACKAGE_HASH_PRIME 10007
55typedef struct edge_s {
[1770]56 unsigned operator:4; /* was:3 */
57 unsigned type:4;
58 unsigned name:16; /* was:14 */
59 unsigned version:16; /* was:14 */
[821]60} edge_t;
61
62typedef struct common_node_s {
[1770]63 unsigned name:16; /* was:14 */
64 unsigned version:16; /* was:14 */
65 unsigned num_of_edges:16; /* was:14 */
[821]66 edge_t **edge;
67} common_node_t;
68
69/* Currently it doesnt store packages that have state-status of not-installed
70 * So it only really has to be the size of the maximum number of packages
71 * likely to be installed at any one time, so there is a bit of leeway here */
72#define STATUS_HASH_PRIME 8191
73typedef struct status_node_s {
[1770]74 unsigned package:16; /* was:14 */ /* has to fit PACKAGE_HASH_PRIME */
75 unsigned status:16; /* was:14 */ /* has to fit STATUS_HASH_PRIME */
[821]76} status_node_t;
77
[1770]78/* Were statically declared here, but such a big bss is nommu-unfriendly */
79static char **name_hashtable; /* [NAME_HASH_PRIME + 1] */
80static common_node_t **package_hashtable; /* [PACKAGE_HASH_PRIME + 1] */
81static status_node_t **status_hashtable; /* [STATUS_HASH_PRIME + 1] */
82
[821]83/* Even numbers are for 'extras', like ored dependencies or null */
84enum edge_type_e {
85 EDGE_NULL = 0,
86 EDGE_PRE_DEPENDS = 1,
87 EDGE_OR_PRE_DEPENDS = 2,
88 EDGE_DEPENDS = 3,
89 EDGE_OR_DEPENDS = 4,
90 EDGE_REPLACES = 5,
91 EDGE_PROVIDES = 7,
92 EDGE_CONFLICTS = 9,
93 EDGE_SUGGESTS = 11,
94 EDGE_RECOMMENDS = 13,
95 EDGE_ENHANCES = 15
96};
97enum operator_e {
98 VER_NULL = 0,
99 VER_EQUAL = 1,
100 VER_LESS = 2,
101 VER_LESS_EQUAL = 3,
102 VER_MORE = 4,
103 VER_MORE_EQUAL = 5,
104 VER_ANY = 6
105};
106
107typedef struct deb_file_s {
108 char *control_file;
109 char *filename;
[1770]110 unsigned package:16; /* was:14 */
[821]111} deb_file_t;
112
113
[1770]114static void make_hash(const char *key, unsigned *start, unsigned *decrement, const int hash_prime)
[821]115{
[1770]116 unsigned long hash_num = key[0];
[821]117 int len = strlen(key);
118 int i;
119
120 /* Maybe i should have uses a "proper" hashing algorithm here instead
121 * of making one up myself, seems to be working ok though. */
[1770]122 for (i = 1; i < len; i++) {
[821]123 /* shifts the ascii based value and adds it to previous value
124 * shift amount is mod 24 because long int is 32 bit and data
125 * to be shifted is 8, don't want to shift data to where it has
126 * no effect*/
127 hash_num += ((key[i] + key[i-1]) << ((key[i] * i) % 24));
128 }
[1770]129 *start = (unsigned) hash_num % hash_prime;
130 *decrement = (unsigned) 1 + (hash_num % (hash_prime - 1));
[821]131}
132
133/* this adds the key to the hash table */
134static int search_name_hashtable(const char *key)
135{
[1770]136 unsigned probe_address = 0;
137 unsigned probe_decrement = 0;
[821]138
139 make_hash(key, &probe_address, &probe_decrement, NAME_HASH_PRIME);
[1770]140 while (name_hashtable[probe_address] != NULL) {
[821]141 if (strcmp(name_hashtable[probe_address], key) == 0) {
[1770]142 return probe_address;
[821]143 }
[1770]144 probe_address -= probe_decrement;
145 if ((int)probe_address < 0) {
146 probe_address += NAME_HASH_PRIME;
147 }
[821]148 }
[1770]149 name_hashtable[probe_address] = xstrdup(key);
150 return probe_address;
[821]151}
152
153/* this DOESNT add the key to the hashtable
154 * TODO make it consistent with search_name_hashtable
155 */
[1770]156static unsigned search_status_hashtable(const char *key)
[821]157{
[1770]158 unsigned probe_address = 0;
159 unsigned probe_decrement = 0;
[821]160
161 make_hash(key, &probe_address, &probe_decrement, STATUS_HASH_PRIME);
[1770]162 while (status_hashtable[probe_address] != NULL) {
[821]163 if (strcmp(key, name_hashtable[package_hashtable[status_hashtable[probe_address]->package]->name]) == 0) {
164 break;
165 }
[1770]166 probe_address -= probe_decrement;
167 if ((int)probe_address < 0) {
168 probe_address += STATUS_HASH_PRIME;
169 }
[821]170 }
[1770]171 return probe_address;
[821]172}
173
174/* Need to rethink version comparison, maybe the official dpkg has something i can use ? */
175static int version_compare_part(const char *version1, const char *version2)
176{
177 int upstream_len1 = 0;
178 int upstream_len2 = 0;
179 char *name1_char;
180 char *name2_char;
181 int len1 = 0;
182 int len2 = 0;
183 int tmp_int;
184 int ver_num1;
185 int ver_num2;
186
187 if (version1 == NULL) {
[1770]188 version1 = xstrdup("");
[821]189 }
190 if (version2 == NULL) {
[1770]191 version2 = xstrdup("");
[821]192 }
193 upstream_len1 = strlen(version1);
194 upstream_len2 = strlen(version2);
195
196 while ((len1 < upstream_len1) || (len2 < upstream_len2)) {
197 /* Compare non-digit section */
198 tmp_int = strcspn(&version1[len1], "0123456789");
[1770]199 name1_char = xstrndup(&version1[len1], tmp_int);
[821]200 len1 += tmp_int;
201 tmp_int = strcspn(&version2[len2], "0123456789");
[1770]202 name2_char = xstrndup(&version2[len2], tmp_int);
[821]203 len2 += tmp_int;
204 tmp_int = strcmp(name1_char, name2_char);
205 free(name1_char);
206 free(name2_char);
207 if (tmp_int != 0) {
[1770]208 return tmp_int;
[821]209 }
210
211 /* Compare digits */
212 tmp_int = strspn(&version1[len1], "0123456789");
[1770]213 name1_char = xstrndup(&version1[len1], tmp_int);
[821]214 len1 += tmp_int;
215 tmp_int = strspn(&version2[len2], "0123456789");
[1770]216 name2_char = xstrndup(&version2[len2], tmp_int);
[821]217 len2 += tmp_int;
218 ver_num1 = atoi(name1_char);
219 ver_num2 = atoi(name2_char);
220 free(name1_char);
221 free(name2_char);
222 if (ver_num1 < ver_num2) {
[1770]223 return -1;
[821]224 }
[1770]225 if (ver_num1 > ver_num2) {
226 return 1;
[821]227 }
228 }
[1770]229 return 0;
[821]230}
231
232/* if ver1 < ver2 return -1,
233 * if ver1 = ver2 return 0,
234 * if ver1 > ver2 return 1,
235 */
[1770]236static int version_compare(const unsigned ver1, const unsigned ver2)
[821]237{
238 char *ch_ver1 = name_hashtable[ver1];
239 char *ch_ver2 = name_hashtable[ver2];
240
241 char epoch1, epoch2;
242 char *deb_ver1, *deb_ver2;
243 char *ver1_ptr, *ver2_ptr;
244 char *upstream_ver1;
245 char *upstream_ver2;
246 int result;
247
248 /* Compare epoch */
249 if (ch_ver1[1] == ':') {
250 epoch1 = ch_ver1[0];
251 ver1_ptr = strchr(ch_ver1, ':') + 1;
252 } else {
253 epoch1 = '0';
254 ver1_ptr = ch_ver1;
255 }
256 if (ch_ver2[1] == ':') {
257 epoch2 = ch_ver2[0];
258 ver2_ptr = strchr(ch_ver2, ':') + 1;
259 } else {
260 epoch2 = '0';
261 ver2_ptr = ch_ver2;
262 }
263 if (epoch1 < epoch2) {
[1770]264 return -1;
[821]265 }
266 else if (epoch1 > epoch2) {
[1770]267 return 1;
[821]268 }
269
270 /* Compare upstream version */
[1770]271 upstream_ver1 = xstrdup(ver1_ptr);
272 upstream_ver2 = xstrdup(ver2_ptr);
[821]273
274 /* Chop off debian version, and store for later use */
275 deb_ver1 = strrchr(upstream_ver1, '-');
276 deb_ver2 = strrchr(upstream_ver2, '-');
277 if (deb_ver1) {
278 deb_ver1[0] = '\0';
279 deb_ver1++;
280 }
281 if (deb_ver2) {
282 deb_ver2[0] = '\0';
283 deb_ver2++;
284 }
285 result = version_compare_part(upstream_ver1, upstream_ver2);
[1770]286 if (!result)
287 /* Compare debian versions */
288 result = version_compare_part(deb_ver1, deb_ver2);
[821]289
290 free(upstream_ver1);
291 free(upstream_ver2);
[1770]292 return result;
[821]293}
294
[1770]295static int test_version(const unsigned version1, const unsigned version2, const unsigned operator)
[821]296{
297 const int version_result = version_compare(version1, version2);
[1770]298 switch (operator) {
299 case VER_ANY:
300 return TRUE;
301 case VER_EQUAL:
302 return (version_result == 0);
303 case VER_LESS:
304 return (version_result < 0);
305 case VER_LESS_EQUAL:
306 return (version_result <= 0);
307 case VER_MORE:
308 return (version_result > 0);
309 case VER_MORE_EQUAL:
310 return (version_result >= 0);
[821]311 }
[1770]312 return FALSE;
[821]313}
314
315
[1770]316static int search_package_hashtable(const unsigned name, const unsigned version, const unsigned operator)
[821]317{
[1770]318 unsigned probe_address = 0;
319 unsigned probe_decrement = 0;
[821]320
321 make_hash(name_hashtable[name], &probe_address, &probe_decrement, PACKAGE_HASH_PRIME);
[1770]322 while (package_hashtable[probe_address] != NULL) {
[821]323 if (package_hashtable[probe_address]->name == name) {
324 if (operator == VER_ANY) {
[1770]325 return probe_address;
[821]326 }
327 if (test_version(package_hashtable[probe_address]->version, version, operator)) {
[1770]328 return probe_address;
[821]329 }
330 }
331 probe_address -= probe_decrement;
332 if ((int)probe_address < 0) {
333 probe_address += PACKAGE_HASH_PRIME;
334 }
335 }
[1770]336 return probe_address;
[821]337}
338
339/*
340 * This function searches through the entire package_hashtable looking
341 * for a package which provides "needle". It returns the index into
342 * the package_hashtable for the providing package.
343 *
344 * needle is the index into name_hashtable of the package we are
345 * looking for.
346 *
347 * start_at is the index in the package_hashtable to start looking
348 * at. If start_at is -1 then start at the beginning. This is to allow
349 * for repeated searches since more than one package might provide
350 * needle.
351 *
352 * FIXME: I don't think this is very efficient, but I thought I'd keep
353 * it simple for now until it proves to be a problem.
354 */
[1770]355static int search_for_provides(int needle, int start_at)
356{
[821]357 int i, j;
358 common_node_t *p;
359 for (i = start_at + 1; i < PACKAGE_HASH_PRIME; i++) {
360 p = package_hashtable[i];
[1770]361 if (p == NULL)
362 continue;
363 for (j = 0; j < p->num_of_edges; j++)
364 if (p->edge[j]->type == EDGE_PROVIDES && p->edge[j]->name == needle)
[821]365 return i;
366 }
367 return -1;
368}
369
370/*
371 * Add an edge to a node
372 */
373static void add_edge_to_node(common_node_t *node, edge_t *edge)
374{
375 node->num_of_edges++;
376 node->edge = xrealloc(node->edge, sizeof(edge_t) * (node->num_of_edges + 1));
377 node->edge[node->num_of_edges - 1] = edge;
378}
379
380/*
381 * Create one new node and one new edge for every dependency.
382 *
383 * Dependencies which contain multiple alternatives are represented as
384 * an EDGE_OR_PRE_DEPENDS or EDGE_OR_DEPENDS node, followed by a
385 * number of EDGE_PRE_DEPENDS or EDGE_DEPENDS nodes. The name field of
386 * the OR edge contains the full dependency string while the version
387 * field contains the number of EDGE nodes which follow as part of
388 * this alternative.
389 */
[1770]390static void add_split_dependencies(common_node_t *parent_node, const char *whole_line, unsigned edge_type)
[821]391{
[1770]392 char *line = xstrdup(whole_line);
[821]393 char *line2;
394 char *line_ptr1 = NULL;
395 char *line_ptr2 = NULL;
396 char *field;
397 char *field2;
398 char *version;
399 edge_t *edge;
400 edge_t *or_edge;
401 int offset_ch;
402
403 field = strtok_r(line, ",", &line_ptr1);
404 do {
405 /* skip leading spaces */
406 field += strspn(field, " ");
[1770]407 line2 = xstrdup(field);
[821]408 field2 = strtok_r(line2, "|", &line_ptr2);
[1770]409 or_edge = NULL;
410 if ((edge_type == EDGE_DEPENDS || edge_type == EDGE_PRE_DEPENDS)
411 && (strcmp(field, field2) != 0)
412 ) {
413 or_edge = xmalloc(sizeof(edge_t));
[821]414 or_edge->type = edge_type + 1;
415 or_edge->name = search_name_hashtable(field);
[1770]416 or_edge->version = 0; // tracks the number of alternatives
[821]417 add_edge_to_node(parent_node, or_edge);
418 }
419
420 do {
[1770]421 edge = xmalloc(sizeof(edge_t));
[821]422 edge->type = edge_type;
423
424 /* Skip any extra leading spaces */
425 field2 += strspn(field2, " ");
426
427 /* Get dependency version info */
428 version = strchr(field2, '(');
429 if (version == NULL) {
430 edge->operator = VER_ANY;
431 /* Get the versions hash number, adding it if the number isnt already in there */
432 edge->version = search_name_hashtable("ANY");
433 } else {
434 /* Skip leading ' ' or '(' */
[1770]435 version += strspn(version, " (");
[821]436 /* Calculate length of any operator characters */
437 offset_ch = strspn(version, "<=>");
438 /* Determine operator */
439 if (offset_ch > 0) {
440 if (strncmp(version, "=", offset_ch) == 0) {
441 edge->operator = VER_EQUAL;
442 }
443 else if (strncmp(version, "<<", offset_ch) == 0) {
444 edge->operator = VER_LESS;
445 }
446 else if (strncmp(version, "<=", offset_ch) == 0) {
447 edge->operator = VER_LESS_EQUAL;
448 }
449 else if (strncmp(version, ">>", offset_ch) == 0) {
450 edge->operator = VER_MORE;
451 }
452 else if (strncmp(version, ">=", offset_ch) == 0) {
453 edge->operator = VER_MORE_EQUAL;
454 } else {
[1770]455 bb_error_msg_and_die("illegal operator");
[821]456 }
457 }
458 /* skip to start of version numbers */
459 version += offset_ch;
460 version += strspn(version, " ");
461
462 /* Truncate version at trailing ' ' or ')' */
463 version[strcspn(version, " )")] = '\0';
464 /* Get the versions hash number, adding it if the number isnt already in there */
465 edge->version = search_name_hashtable(version);
466 }
467
468 /* Get the dependency name */
469 field2[strcspn(field2, " (")] = '\0';
470 edge->name = search_name_hashtable(field2);
471
[1770]472 if (or_edge)
[821]473 or_edge->version++;
474
475 add_edge_to_node(parent_node, edge);
[1770]476 field2 = strtok_r(NULL, "|", &line_ptr2);
477 } while (field2 != NULL);
478
[821]479 free(line2);
[1770]480 field = strtok_r(NULL, ",", &line_ptr1);
481 } while (field != NULL);
482
[821]483 free(line);
484}
485
486static void free_package(common_node_t *node)
487{
[1770]488 unsigned i;
[821]489 if (node) {
490 for (i = 0; i < node->num_of_edges; i++) {
491 free(node->edge[i]);
492 }
493 free(node->edge);
494 free(node);
495 }
496}
497
[1770]498/*
499 * Gets the next package field from package_buffer, seperated into the field name
500 * and field value, it returns the int offset to the first character of the next field
501 */
502static int read_package_field(const char *package_buffer, char **field_name, char **field_value)
[821]503{
[1770]504 int offset_name_start = 0;
505 int offset_name_end = 0;
506 int offset_value_start = 0;
507 int offset_value_end = 0;
508 int offset = 0;
509 int next_offset;
510 int name_length;
511 int value_length;
512 int exit_flag = FALSE;
[821]513
[1770]514 if (package_buffer == NULL) {
515 *field_name = NULL;
516 *field_value = NULL;
517 return -1;
518 }
519 while (1) {
520 next_offset = offset + 1;
521 switch (package_buffer[offset]) {
522 case '\0':
523 exit_flag = TRUE;
524 break;
525 case ':':
526 if (offset_name_end == 0) {
527 offset_name_end = offset;
528 offset_value_start = next_offset;
529 }
530 /* TODO: Name might still have trailing spaces if ':' isnt
531 * immediately after name */
532 break;
533 case '\n':
534 /* TODO: The char next_offset may be out of bounds */
535 if (package_buffer[next_offset] != ' ') {
536 exit_flag = TRUE;
537 break;
538 }
539 case '\t':
540 case ' ':
541 /* increment the value start point if its a just filler */
542 if (offset_name_start == offset) {
543 offset_name_start++;
544 }
545 if (offset_value_start == offset) {
546 offset_value_start++;
547 }
548 break;
549 }
550 if (exit_flag) {
551 /* Check that the names are valid */
552 offset_value_end = offset;
553 name_length = offset_name_end - offset_name_start;
554 value_length = offset_value_end - offset_value_start;
555 if (name_length == 0) {
556 break;
557 }
558 if ((name_length > 0) && (value_length > 0)) {
559 break;
560 }
561
562 /* If not valid, start fresh with next field */
563 exit_flag = FALSE;
564 offset_name_start = offset + 1;
565 offset_name_end = 0;
566 offset_value_start = offset + 1;
567 offset_value_end = offset + 1;
568 offset++;
569 }
570 offset++;
571 }
572 *field_name = NULL;
573 if (name_length) {
574 *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
575 }
576 *field_value = NULL;
577 if (value_length > 0) {
578 *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
579 }
580 return next_offset;
581}
582
583static unsigned fill_package_struct(char *control_buffer)
584{
585 static const char field_names[] ALIGN1 =
586 "Package\0""Version\0"
587 "Pre-Depends\0""Depends\0""Replaces\0""Provides\0"
588 "Conflicts\0""Suggests\0""Recommends\0""Enhances\0";
589
590 common_node_t *new_node = xzalloc(sizeof(common_node_t));
[821]591 char *field_name;
592 char *field_value;
593 int field_start = 0;
594 int num = -1;
595 int buffer_length = strlen(control_buffer);
596
597 new_node->version = search_name_hashtable("unknown");
598 while (field_start < buffer_length) {
[1770]599 unsigned field_num;
[821]600
601 field_start += read_package_field(&control_buffer[field_start],
602 &field_name, &field_value);
603
604 if (field_name == NULL) {
[1770]605 goto fill_package_struct_cleanup; /* Oh no, the dreaded goto statement! */
[821]606 }
607
[1770]608 field_num = index_in_strings(field_names, field_name);
609 switch (field_num) {
610 case 0: /* Package */
611 new_node->name = search_name_hashtable(field_value);
612 break;
613 case 1: /* Version */
614 new_node->version = search_name_hashtable(field_value);
615 break;
616 case 2: /* Pre-Depends */
617 add_split_dependencies(new_node, field_value, EDGE_PRE_DEPENDS);
618 break;
619 case 3: /* Depends */
620 add_split_dependencies(new_node, field_value, EDGE_DEPENDS);
621 break;
622 case 4: /* Replaces */
623 add_split_dependencies(new_node, field_value, EDGE_REPLACES);
624 break;
625 case 5: /* Provides */
626 add_split_dependencies(new_node, field_value, EDGE_PROVIDES);
627 break;
628 case 6: /* Conflicts */
629 add_split_dependencies(new_node, field_value, EDGE_CONFLICTS);
630 break;
631 case 7: /* Suggests */
632 add_split_dependencies(new_node, field_value, EDGE_SUGGESTS);
633 break;
634 case 8: /* Recommends */
635 add_split_dependencies(new_node, field_value, EDGE_RECOMMENDS);
636 break;
637 case 9: /* Enhances */
638 add_split_dependencies(new_node, field_value, EDGE_ENHANCES);
639 break;
[821]640 }
[1770]641 fill_package_struct_cleanup:
[821]642 free(field_name);
643 free(field_value);
644 }
645
646 if (new_node->version == search_name_hashtable("unknown")) {
647 free_package(new_node);
[1770]648 return -1;
[821]649 }
650 num = search_package_hashtable(new_node->name, new_node->version, VER_EQUAL);
[1770]651 free_package(package_hashtable[num]);
652 package_hashtable[num] = new_node;
653 return num;
[821]654}
655
656/* if num = 1, it returns the want status, 2 returns flag, 3 returns status */
[1770]657static unsigned get_status(const unsigned status_node, const int num)
[821]658{
659 char *status_string = name_hashtable[status_hashtable[status_node]->status];
660 char *state_sub_string;
[1770]661 unsigned state_sub_num;
[821]662 int len;
663 int i;
664
665 /* set tmp_string to point to the start of the word number */
666 for (i = 1; i < num; i++) {
667 /* skip past a word */
668 status_string += strcspn(status_string, " ");
669 /* skip past the separating spaces */
670 status_string += strspn(status_string, " ");
671 }
[1770]672 len = strcspn(status_string, " \n");
673 state_sub_string = xstrndup(status_string, len);
[821]674 state_sub_num = search_name_hashtable(state_sub_string);
675 free(state_sub_string);
[1770]676 return state_sub_num;
[821]677}
678
[1770]679static void set_status(const unsigned status_node_num, const char *new_value, const int position)
[821]680{
[1770]681 const unsigned new_value_len = strlen(new_value);
682 const unsigned new_value_num = search_name_hashtable(new_value);
683 unsigned want = get_status(status_node_num, 1);
684 unsigned flag = get_status(status_node_num, 2);
685 unsigned status = get_status(status_node_num, 3);
[821]686 int want_len = strlen(name_hashtable[want]);
687 int flag_len = strlen(name_hashtable[flag]);
688 int status_len = strlen(name_hashtable[status]);
689 char *new_status;
690
691 switch (position) {
[1770]692 case 1:
[821]693 want = new_value_num;
694 want_len = new_value_len;
695 break;
[1770]696 case 2:
[821]697 flag = new_value_num;
698 flag_len = new_value_len;
699 break;
[1770]700 case 3:
[821]701 status = new_value_num;
702 status_len = new_value_len;
703 break;
704 default:
705 bb_error_msg_and_die("DEBUG ONLY: this shouldnt happen");
706 }
707
[1770]708 new_status = xasprintf("%s %s %s", name_hashtable[want], name_hashtable[flag], name_hashtable[status]);
[821]709 status_hashtable[status_node_num]->status = search_name_hashtable(new_status);
710 free(new_status);
711}
712
[1770]713static const char *describe_status(int status_num)
714{
715 int status_want, status_state;
716 if (status_hashtable[status_num] == NULL || status_hashtable[status_num]->status == 0)
717 return "is not installed or flagged to be installed";
[821]718
719 status_want = get_status(status_num, 1);
720 status_state = get_status(status_num, 3);
721
[1770]722 if (status_state == search_name_hashtable("installed")) {
723 if (status_want == search_name_hashtable("install"))
[821]724 return "is installed";
[1770]725 if (status_want == search_name_hashtable("deinstall"))
[821]726 return "is marked to be removed";
[1770]727 if (status_want == search_name_hashtable("purge"))
[821]728 return "is marked to be purged";
729 }
[1770]730 if (status_want == search_name_hashtable("unknown"))
[821]731 return "is in an indeterminate state";
[1770]732 if (status_want == search_name_hashtable("install"))
[821]733 return "is marked to be installed";
734
735 return "is not installed or flagged to be installed";
736}
737
738
739static void index_status_file(const char *filename)
740{
741 FILE *status_file;
742 char *control_buffer;
743 char *status_line;
744 status_node_t *status_node = NULL;
[1770]745 unsigned status_num;
[821]746
[1770]747 status_file = xfopen(filename, "r");
748 while ((control_buffer = xmalloc_fgets_str(status_file, "\n\n")) != NULL) {
749 const unsigned package_num = fill_package_struct(control_buffer);
[821]750 if (package_num != -1) {
751 status_node = xmalloc(sizeof(status_node_t));
752 /* fill_package_struct doesnt handle the status field */
753 status_line = strstr(control_buffer, "Status:");
754 if (status_line != NULL) {
755 status_line += 7;
756 status_line += strspn(status_line, " \n\t");
[1770]757 status_line = xstrndup(status_line, strcspn(status_line, "\n"));
[821]758 status_node->status = search_name_hashtable(status_line);
759 free(status_line);
760 }
761 status_node->package = package_num;
762 status_num = search_status_hashtable(name_hashtable[package_hashtable[status_node->package]->name]);
763 status_hashtable[status_num] = status_node;
764 }
765 free(control_buffer);
766 }
767 fclose(status_file);
768}
769
770static void write_buffer_no_status(FILE *new_status_file, const char *control_buffer)
771{
772 char *name;
773 char *value;
774 int start = 0;
775 while (1) {
776 start += read_package_field(&control_buffer[start], &name, &value);
777 if (name == NULL) {
778 break;
779 }
780 if (strcmp(name, "Status") != 0) {
781 fprintf(new_status_file, "%s: %s\n", name, value);
782 }
783 }
784}
785
786/* This could do with a cleanup */
787static void write_status_file(deb_file_t **deb_file)
788{
[1770]789 FILE *old_status_file = xfopen("/var/lib/dpkg/status", "r");
790 FILE *new_status_file = xfopen("/var/lib/dpkg/status.udeb", "w");
[821]791 char *package_name;
792 char *status_from_file;
793 char *control_buffer = NULL;
794 char *tmp_string;
795 int status_num;
796 int field_start = 0;
797 int write_flag;
798 int i = 0;
799
800 /* Update previously known packages */
[1770]801 while ((control_buffer = xmalloc_fgets_str(old_status_file, "\n\n")) != NULL) {
802 tmp_string = strstr(control_buffer, "Package:");
803 if (tmp_string == NULL) {
[821]804 continue;
805 }
806
807 tmp_string += 8;
808 tmp_string += strspn(tmp_string, " \n\t");
[1770]809 package_name = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
[821]810 write_flag = FALSE;
811 tmp_string = strstr(control_buffer, "Status:");
812 if (tmp_string != NULL) {
813 /* Seperate the status value from the control buffer */
814 tmp_string += 7;
815 tmp_string += strspn(tmp_string, " \n\t");
[1770]816 status_from_file = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
[821]817 } else {
818 status_from_file = NULL;
819 }
820
821 /* Find this package in the status hashtable */
822 status_num = search_status_hashtable(package_name);
823 if (status_hashtable[status_num] != NULL) {
824 const char *status_from_hashtable = name_hashtable[status_hashtable[status_num]->status];
825 if (strcmp(status_from_file, status_from_hashtable) != 0) {
826 /* New status isnt exactly the same as old status */
827 const int state_status = get_status(status_num, 3);
[1770]828 if ((strcmp("installed", name_hashtable[state_status]) == 0)
829 || (strcmp("unpacked", name_hashtable[state_status]) == 0)
830 ) {
[821]831 /* We need to add the control file from the package */
832 i = 0;
[1770]833 while (deb_file[i] != NULL) {
[821]834 if (strcmp(package_name, name_hashtable[package_hashtable[deb_file[i]->package]->name]) == 0) {
835 /* Write a status file entry with a modified status */
836 /* remove trailing \n's */
837 write_buffer_no_status(new_status_file, deb_file[i]->control_file);
838 set_status(status_num, "ok", 2);
[1770]839 fprintf(new_status_file, "Status: %s\n\n",
840 name_hashtable[status_hashtable[status_num]->status]);
[821]841 write_flag = TRUE;
842 break;
843 }
844 i++;
845 }
846 /* This is temperary, debugging only */
847 if (deb_file[i] == NULL) {
[1770]848 bb_error_msg_and_die("ALERT: cannot find a control file, "
849 "your status file may be broken, status may be "
850 "incorrect for %s", package_name);
[821]851 }
852 }
853 else if (strcmp("not-installed", name_hashtable[state_status]) == 0) {
854 /* Only write the Package, Status, Priority and Section lines */
855 fprintf(new_status_file, "Package: %s\n", package_name);
856 fprintf(new_status_file, "Status: %s\n", status_from_hashtable);
857
858 while (1) {
859 char *field_name;
860 char *field_value;
861 field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value);
862 if (field_name == NULL) {
863 break;
864 }
865 if ((strcmp(field_name, "Priority") == 0) ||
866 (strcmp(field_name, "Section") == 0)) {
867 fprintf(new_status_file, "%s: %s\n", field_name, field_value);
868 }
869 }
870 write_flag = TRUE;
871 fputs("\n", new_status_file);
872 }
873 else if (strcmp("config-files", name_hashtable[state_status]) == 0) {
874 /* only change the status line */
875 while (1) {
876 char *field_name;
877 char *field_value;
878 field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value);
879 if (field_name == NULL) {
880 break;
881 }
882 /* Setup start point for next field */
883 if (strcmp(field_name, "Status") == 0) {
884 fprintf(new_status_file, "Status: %s\n", status_from_hashtable);
885 } else {
886 fprintf(new_status_file, "%s: %s\n", field_name, field_value);
887 }
888 }
889 write_flag = TRUE;
890 fputs("\n", new_status_file);
891 }
892 }
893 }
894 /* If the package from the status file wasnt handle above, do it now*/
[1770]895 if (!write_flag) {
[821]896 fprintf(new_status_file, "%s\n\n", control_buffer);
897 }
898
899 free(status_from_file);
900 free(package_name);
901 free(control_buffer);
902 }
903
904 /* Write any new packages */
[1770]905 for (i = 0; deb_file[i] != NULL; i++) {
[821]906 status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[i]->package]->name]);
907 if (strcmp("reinstreq", name_hashtable[get_status(status_num, 2)]) == 0) {
908 write_buffer_no_status(new_status_file, deb_file[i]->control_file);
909 set_status(status_num, "ok", 2);
910 fprintf(new_status_file, "Status: %s\n\n", name_hashtable[status_hashtable[status_num]->status]);
911 }
912 }
913 fclose(old_status_file);
914 fclose(new_status_file);
915
916
917 /* Create a separate backfile to dpkg */
918 if (rename("/var/lib/dpkg/status", "/var/lib/dpkg/status.udeb.bak") == -1) {
919 struct stat stat_buf;
920 xstat("/var/lib/dpkg/status", &stat_buf);
921 /* Its ok if renaming the status file fails because status
922 * file doesnt exist, maybe we are starting from scratch */
[1770]923 bb_error_msg("no status file found, creating new one");
[821]924 }
925
926 if (rename("/var/lib/dpkg/status.udeb", "/var/lib/dpkg/status") == -1) {
[1770]927 bb_error_msg_and_die("DANGER: cannot create status file, "
928 "you need to manually repair your status file");
[821]929 }
930}
931
932/* This function returns TRUE if the given package can satisfy a
933 * dependency of type depend_type.
934 *
935 * A pre-depends is satisfied only if a package is already installed,
936 * which a regular depends can be satisfied by a package which we want
937 * to install.
938 */
939static int package_satisfies_dependency(int package, int depend_type)
940{
941 int status_num = search_status_hashtable(name_hashtable[package_hashtable[package]->name]);
942
943 /* status could be unknown if package is a pure virtual
944 * provides which cannot satisfy any dependency by itself.
945 */
[1770]946 if (status_hashtable[status_num] == NULL)
[821]947 return 0;
948
949 switch (depend_type) {
950 case EDGE_PRE_DEPENDS: return get_status(status_num, 3) == search_name_hashtable("installed");
951 case EDGE_DEPENDS: return get_status(status_num, 1) == search_name_hashtable("install");
952 }
953 return 0;
954}
955
956static int check_deps(deb_file_t **deb_file, int deb_start, int dep_max_count)
957{
958 int *conflicts = NULL;
959 int conflicts_num = 0;
960 int i = deb_start;
961 int j;
962
963 /* Check for conflicts
964 * TODO: TEST if conflicts with other packages to be installed
965 *
966 * Add install packages and the packages they provide
967 * to the list of files to check conflicts for
968 */
969
970 /* Create array of package numbers to check against
971 * installed package for conflicts*/
972 while (deb_file[i] != NULL) {
[1770]973 const unsigned package_num = deb_file[i]->package;
[821]974 conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1));
975 conflicts[conflicts_num] = package_num;
976 conflicts_num++;
977 /* add provides to conflicts list */
978 for (j = 0; j < package_hashtable[package_num]->num_of_edges; j++) {
979 if (package_hashtable[package_num]->edge[j]->type == EDGE_PROVIDES) {
980 const int conflicts_package_num = search_package_hashtable(
981 package_hashtable[package_num]->edge[j]->name,
982 package_hashtable[package_num]->edge[j]->version,
983 package_hashtable[package_num]->edge[j]->operator);
984 if (package_hashtable[conflicts_package_num] == NULL) {
985 /* create a new package */
[1770]986 common_node_t *new_node = xzalloc(sizeof(common_node_t));
[821]987 new_node->name = package_hashtable[package_num]->edge[j]->name;
988 new_node->version = package_hashtable[package_num]->edge[j]->version;
989 package_hashtable[conflicts_package_num] = new_node;
990 }
991 conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1));
992 conflicts[conflicts_num] = conflicts_package_num;
993 conflicts_num++;
994 }
995 }
996 i++;
997 }
998
999 /* Check conflicts */
1000 i = 0;
1001 while (deb_file[i] != NULL) {
1002 const common_node_t *package_node = package_hashtable[deb_file[i]->package];
1003 int status_num = 0;
1004 status_num = search_status_hashtable(name_hashtable[package_node->name]);
1005
1006 if (get_status(status_num, 3) == search_name_hashtable("installed")) {
1007 i++;
1008 continue;
1009 }
1010
1011 for (j = 0; j < package_node->num_of_edges; j++) {
1012 const edge_t *package_edge = package_node->edge[j];
1013
1014 if (package_edge->type == EDGE_CONFLICTS) {
[1770]1015 const unsigned package_num =
[821]1016 search_package_hashtable(package_edge->name,
1017 package_edge->version,
1018 package_edge->operator);
1019 int result = 0;
1020 if (package_hashtable[package_num] != NULL) {
1021 status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]);
1022
1023 if (get_status(status_num, 1) == search_name_hashtable("install")) {
1024 result = test_version(package_hashtable[deb_file[i]->package]->version,
1025 package_edge->version, package_edge->operator);
1026 }
1027 }
1028
1029 if (result) {
[1770]1030 bb_error_msg_and_die("package %s conflicts with %s",
[821]1031 name_hashtable[package_node->name],
1032 name_hashtable[package_edge->name]);
1033 }
1034 }
1035 }
1036 i++;
1037 }
1038
1039
1040 /* Check dependendcies */
1041 for (i = 0; i < PACKAGE_HASH_PRIME; i++) {
1042 int status_num = 0;
1043 int number_of_alternatives = 0;
1044 const edge_t * root_of_alternatives = NULL;
1045 const common_node_t *package_node = package_hashtable[i];
1046
1047 /* If the package node does not exist then this
1048 * package is a virtual one. In which case there are
1049 * no dependencies to check.
1050 */
[1770]1051 if (package_node == NULL) continue;
[821]1052
1053 status_num = search_status_hashtable(name_hashtable[package_node->name]);
1054
1055 /* If there is no status then this package is a
1056 * virtual one provided by something else. In which
1057 * case there are no dependencies to check.
1058 */
[1770]1059 if (status_hashtable[status_num] == NULL) continue;
[821]1060
1061 /* If we don't want this package installed then we may
1062 * as well ignore it's dependencies.
1063 */
1064 if (get_status(status_num, 1) != search_name_hashtable("install")) {
1065 continue;
1066 }
1067
1068 /* This code is tested only for EDGE_DEPENDS, since I
1069 * have no suitable pre-depends available. There is no
1070 * reason that it shouldn't work though :-)
1071 */
1072 for (j = 0; j < package_node->num_of_edges; j++) {
1073 const edge_t *package_edge = package_node->edge[j];
[1770]1074 unsigned package_num;
[821]1075
[1770]1076 if (package_edge->type == EDGE_OR_PRE_DEPENDS
1077 || package_edge->type == EDGE_OR_DEPENDS
1078 ) { /* start an EDGE_OR_ list */
[821]1079 number_of_alternatives = package_edge->version;
1080 root_of_alternatives = package_edge;
1081 continue;
[1770]1082 }
1083 if (number_of_alternatives == 0) { /* not in the middle of an EDGE_OR_ list */
[821]1084 number_of_alternatives = 1;
1085 root_of_alternatives = NULL;
1086 }
1087
1088 package_num = search_package_hashtable(package_edge->name, package_edge->version, package_edge->operator);
1089
1090 if (package_edge->type == EDGE_PRE_DEPENDS ||
[1770]1091 package_edge->type == EDGE_DEPENDS) {
[821]1092 int result=1;
1093 status_num = 0;
1094
1095 /* If we are inside an alternative then check
1096 * this edge is the right type.
1097 *
1098 * EDGE_DEPENDS == OR_DEPENDS -1
1099 * EDGE_PRE_DEPENDS == OR_PRE_DEPENDS -1
1100 */
[1770]1101 if (root_of_alternatives && package_edge->type != root_of_alternatives->type - 1)
1102 bb_error_msg_and_die("fatal error, package dependencies corrupt: %d != %d - 1",
[821]1103 package_edge->type, root_of_alternatives->type);
1104
1105 if (package_hashtable[package_num] != NULL)
1106 result = !package_satisfies_dependency(package_num, package_edge->type);
1107
1108 if (result) { /* check for other package which provide what we are looking for */
1109 int provider = -1;
1110
[1770]1111 while ((provider = search_for_provides(package_edge->name, provider)) > -1) {
1112 if (package_hashtable[provider] == NULL) {
1113 puts("Have a provider but no package information for it");
[821]1114 continue;
1115 }
1116 result = !package_satisfies_dependency(provider, package_edge->type);
1117
[1770]1118 if (result == 0)
[821]1119 break;
1120 }
1121 }
1122
1123 /* It must be already installed, or to be installed */
1124 number_of_alternatives--;
1125 if (result && number_of_alternatives == 0) {
[1770]1126 if (root_of_alternatives)
[821]1127 bb_error_msg_and_die(
[1770]1128 "package %s %sdepends on %s, "
[821]1129 "which cannot be satisfied",
1130 name_hashtable[package_node->name],
1131 package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "",
1132 name_hashtable[root_of_alternatives->name]);
[1770]1133 bb_error_msg_and_die(
1134 "package %s %sdepends on %s, which %s\n",
1135 name_hashtable[package_node->name],
1136 package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "",
1137 name_hashtable[package_edge->name],
1138 describe_status(status_num));
1139 }
1140 if (result == 0 && number_of_alternatives) {
[821]1141 /* we've found a package which
1142 * satisfies the dependency,
1143 * so skip over the rest of
1144 * the alternatives.
1145 */
1146 j += number_of_alternatives;
1147 number_of_alternatives = 0;
1148 }
1149 }
1150 }
1151 }
1152 free(conflicts);
[1770]1153 return TRUE;
[821]1154}
1155
1156static char **create_list(const char *filename)
1157{
1158 FILE *list_stream;
1159 char **file_list = NULL;
1160 char *line = NULL;
1161 int count = 0;
1162
1163 /* don't use [xw]fopen here, handle error ourself */
1164 list_stream = fopen(filename, "r");
1165 if (list_stream == NULL) {
[1770]1166 return NULL;
[821]1167 }
1168
[1770]1169 while ((line = xmalloc_getline(list_stream)) != NULL) {
[821]1170 file_list = xrealloc(file_list, sizeof(char *) * (count + 2));
1171 file_list[count] = line;
1172 count++;
1173 }
1174 fclose(list_stream);
1175
1176 if (count == 0) {
[1770]1177 return NULL;
[821]1178 }
[1770]1179 file_list[count] = NULL;
1180 return file_list;
[821]1181}
1182
1183/* maybe i should try and hook this into remove_file.c somehow */
1184static int remove_file_array(char **remove_names, char **exclude_names)
1185{
1186 struct stat path_stat;
[1770]1187 int remove_flag = 1; /* not removed anything yet */
1188 int i, j;
[821]1189
1190 if (remove_names == NULL) {
[1770]1191 return 0;
[821]1192 }
1193 for (i = 0; remove_names[i] != NULL; i++) {
1194 if (exclude_names != NULL) {
[1770]1195 for (j = 0; exclude_names[j] != NULL; j++) {
[821]1196 if (strcmp(remove_names[i], exclude_names[j]) == 0) {
[1770]1197 goto skip;
[821]1198 }
1199 }
1200 }
[1770]1201 /* TODO: why we are checking lstat? we can just try rm/rmdir */
1202 if (lstat(remove_names[i], &path_stat) < 0) {
1203 continue;
[821]1204 }
[1770]1205 if (S_ISDIR(path_stat.st_mode)) {
1206 remove_flag &= rmdir(remove_names[i]); /* 0 if no error */
1207 } else {
1208 remove_flag &= unlink(remove_names[i]); /* 0 if no error */
1209 }
1210 skip:
1211 continue;
[821]1212 }
[1770]1213 return (remove_flag == 0);
[821]1214}
1215
1216static int run_package_script(const char *package_name, const char *script_type)
1217{
1218 struct stat path_stat;
1219 char *script_path;
1220 int result;
1221
[1770]1222 script_path = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, script_type);
[821]1223
1224 /* If the file doesnt exist is isnt a fatal */
1225 result = lstat(script_path, &path_stat) < 0 ? EXIT_SUCCESS : system(script_path);
1226 free(script_path);
[1770]1227 return result;
[821]1228}
1229
[1770]1230static const char *const all_control_files[] = {
1231 "preinst", "postinst", "prerm", "postrm",
1232 "list", "md5sums", "shlibs", "conffiles",
1233 "config", "templates", NULL
1234};
[821]1235
1236static char **all_control_list(const char *package_name)
1237{
[1770]1238 unsigned i = 0;
[821]1239 char **remove_files;
1240
1241 /* Create a list of all /var/lib/dpkg/info/<package> files */
1242 remove_files = xzalloc(sizeof(all_control_files));
1243 while (all_control_files[i]) {
[1770]1244 remove_files[i] = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, all_control_files[i]);
[821]1245 i++;
1246 }
1247
[1770]1248 return remove_files;
[821]1249}
1250
1251static void free_array(char **array)
1252{
1253 if (array) {
[1770]1254 unsigned i = 0;
[821]1255 while (array[i]) {
1256 free(array[i]);
1257 i++;
1258 }
1259 free(array);
1260 }
1261}
1262
1263/* This function lists information on the installed packages. It loops through
1264 * the status_hashtable to retrieve the info. This results in smaller code than
1265 * scanning the status file. The resulting list, however, is unsorted.
1266 */
1267static void list_packages(void)
1268{
1269 int i;
1270
[1770]1271 puts(" Name Version");
1272 puts("+++-==============-==============");
[821]1273
1274 /* go through status hash, dereference package hash and finally strings */
[1770]1275 for (i = 0; i < STATUS_HASH_PRIME+1; i++) {
1276 if (status_hashtable[i]) {
1277 const char *stat_str; /* status string */
[821]1278 const char *name_str; /* package name */
1279 const char *vers_str; /* version */
1280 char s1, s2; /* status abbreviations */
1281 int spccnt; /* space count */
1282 int j;
1283
1284 stat_str = name_hashtable[status_hashtable[i]->status];
1285 name_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->name];
1286 vers_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->version];
1287
1288 /* get abbreviation for status field 1 */
1289 s1 = stat_str[0] == 'i' ? 'i' : 'r';
1290
1291 /* get abbreviation for status field 2 */
[1770]1292 for (j = 0, spccnt = 0; stat_str[j] && spccnt < 2; j++) {
1293 if (stat_str[j] == ' ') spccnt++;
[821]1294 }
1295 s2 = stat_str[j];
1296
1297 /* print out the line formatted like Debian dpkg */
1298 printf("%c%c %-14s %s\n", s1, s2, name_str, vers_str);
1299 }
[1770]1300 }
[821]1301}
1302
[1770]1303static void remove_package(const unsigned package_num, int noisy)
[821]1304{
1305 const char *package_name = name_hashtable[package_hashtable[package_num]->name];
1306 const char *package_version = name_hashtable[package_hashtable[package_num]->version];
[1770]1307 const unsigned status_num = search_status_hashtable(package_name);
[821]1308 const int package_name_length = strlen(package_name);
1309 char **remove_files;
1310 char **exclude_files;
1311 char list_name[package_name_length + 25];
1312 char conffile_name[package_name_length + 30];
1313
[1770]1314 if (noisy)
1315 printf("Removing %s (%s)...\n", package_name, package_version);
[821]1316
1317 /* run prerm script */
[1770]1318 if (run_package_script(package_name, "prerm") != 0) {
[821]1319 bb_error_msg_and_die("script failed, prerm failure");
1320 }
1321
1322 /* Create a list of files to remove, and a separate list of those to keep */
1323 sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name);
1324 remove_files = create_list(list_name);
1325
1326 sprintf(conffile_name, "/var/lib/dpkg/info/%s.conffiles", package_name);
1327 exclude_files = create_list(conffile_name);
1328
[1770]1329 /* Some directories can't be removed straight away, so do multiple passes */
1330 while (remove_file_array(remove_files, exclude_files)) /*repeat */;
[821]1331 free_array(exclude_files);
1332 free_array(remove_files);
1333
1334 /* Create a list of files in /var/lib/dpkg/info/<package>.* to keep */
1335 exclude_files = xzalloc(sizeof(char*) * 3);
[1770]1336 exclude_files[0] = xstrdup(conffile_name);
1337 exclude_files[1] = xasprintf("/var/lib/dpkg/info/%s.postrm", package_name);
[821]1338
1339 /* Create a list of all /var/lib/dpkg/info/<package> files */
1340 remove_files = all_control_list(package_name);
1341
1342 remove_file_array(remove_files, exclude_files);
1343 free_array(remove_files);
1344 free_array(exclude_files);
1345
1346 /* rename <package>.conffile to <package>.list */
1347 rename(conffile_name, list_name);
1348
1349 /* Change package status */
1350 set_status(status_num, "config-files", 3);
1351}
1352
[1770]1353static void purge_package(const unsigned package_num)
[821]1354{
1355 const char *package_name = name_hashtable[package_hashtable[package_num]->name];
1356 const char *package_version = name_hashtable[package_hashtable[package_num]->version];
[1770]1357 const unsigned status_num = search_status_hashtable(package_name);
[821]1358 char **remove_files;
1359 char **exclude_files;
1360 char list_name[strlen(package_name) + 25];
1361
[1770]1362 printf("Purging %s (%s)...\n", package_name, package_version);
[821]1363
1364 /* run prerm script */
1365 if (run_package_script(package_name, "prerm") != 0) {
1366 bb_error_msg_and_die("script failed, prerm failure");
1367 }
1368
1369 /* Create a list of files to remove */
1370 sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name);
1371 remove_files = create_list(list_name);
1372
1373 exclude_files = xzalloc(sizeof(char*));
1374
1375 /* Some directories cant be removed straight away, so do multiple passes */
[1770]1376 while (remove_file_array(remove_files, exclude_files)) /* repeat */;
[821]1377 free_array(remove_files);
1378
1379 /* Create a list of all /var/lib/dpkg/info/<package> files */
1380 remove_files = all_control_list(package_name);
1381 remove_file_array(remove_files, exclude_files);
1382 free_array(remove_files);
1383 free(exclude_files);
1384
1385 /* run postrm script */
[1770]1386 if (run_package_script(package_name, "postrm") != 0) {
1387 bb_error_msg_and_die("postrm failure.. set status to what?");
[821]1388 }
1389
1390 /* Change package status */
1391 set_status(status_num, "not-installed", 3);
1392}
1393
1394static archive_handle_t *init_archive_deb_ar(const char *filename)
1395{
1396 archive_handle_t *ar_handle;
1397
1398 /* Setup an ar archive handle that refers to the gzip sub archive */
1399 ar_handle = init_handle();
1400 ar_handle->filter = filter_accept_list_reassign;
[1770]1401 ar_handle->src_fd = xopen(filename, O_RDONLY);
[821]1402
[1770]1403 return ar_handle;
[821]1404}
1405
1406static void init_archive_deb_control(archive_handle_t *ar_handle)
1407{
1408 archive_handle_t *tar_handle;
1409
1410 /* Setup the tar archive handle */
1411 tar_handle = init_handle();
1412 tar_handle->src_fd = ar_handle->src_fd;
1413
1414 /* We don't care about data.tar.* or debian-binary, just control.tar.* */
[1770]1415#if ENABLE_FEATURE_DEB_TAR_GZ
1416 llist_add_to(&(ar_handle->accept), (char*)"control.tar.gz");
[821]1417#endif
[1770]1418#if ENABLE_FEATURE_DEB_TAR_BZ2
1419 llist_add_to(&(ar_handle->accept), (char*)"control.tar.bz2");
[821]1420#endif
1421
1422 /* Assign the tar handle as a subarchive of the ar handle */
1423 ar_handle->sub_archive = tar_handle;
1424}
1425
1426static void init_archive_deb_data(archive_handle_t *ar_handle)
1427{
1428 archive_handle_t *tar_handle;
1429
1430 /* Setup the tar archive handle */
1431 tar_handle = init_handle();
1432 tar_handle->src_fd = ar_handle->src_fd;
1433
1434 /* We don't care about control.tar.* or debian-binary, just data.tar.* */
[1770]1435#if ENABLE_FEATURE_DEB_TAR_GZ
1436 llist_add_to(&(ar_handle->accept), (char*)"data.tar.gz");
[821]1437#endif
[1770]1438#if ENABLE_FEATURE_DEB_TAR_BZ2
1439 llist_add_to(&(ar_handle->accept), (char*)"data.tar.bz2");
[821]1440#endif
1441
1442 /* Assign the tar handle as a subarchive of the ar handle */
1443 ar_handle->sub_archive = tar_handle;
1444}
1445
1446static char *deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, llist_t *myaccept)
1447{
1448 ar_handle->sub_archive->action_data = data_extract_to_buffer;
1449 ar_handle->sub_archive->accept = myaccept;
1450 ar_handle->sub_archive->filter = filter_accept_list;
1451
1452 unpack_ar_archive(ar_handle);
1453 close(ar_handle->src_fd);
1454
[1770]1455 return ar_handle->sub_archive->buffer;
[821]1456}
1457
1458static void data_extract_all_prefix(archive_handle_t *archive_handle)
1459{
1460 char *name_ptr = archive_handle->file_header->name;
1461
1462 name_ptr += strspn(name_ptr, "./");
1463 if (name_ptr[0] != '\0') {
[1770]1464 archive_handle->file_header->name = xasprintf("%s%s", archive_handle->buffer, name_ptr);
[821]1465 data_extract_all(archive_handle);
1466 }
1467}
1468
1469static void unpack_package(deb_file_t *deb_file)
1470{
1471 const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name];
[1770]1472 const unsigned status_num = search_status_hashtable(package_name);
1473 const unsigned status_package_num = status_hashtable[status_num]->package;
[821]1474 char *info_prefix;
[1770]1475 char *list_filename;
[821]1476 archive_handle_t *archive_handle;
1477 FILE *out_stream;
1478 llist_t *accept_list = NULL;
1479 int i = 0;
1480
1481 /* If existing version, remove it first */
1482 if (strcmp(name_hashtable[get_status(status_num, 3)], "installed") == 0) {
1483 /* Package is already installed, remove old version first */
[1770]1484 printf("Preparing to replace %s %s (using %s)...\n", package_name,
[821]1485 name_hashtable[package_hashtable[status_package_num]->version],
1486 deb_file->filename);
1487 remove_package(status_package_num, 0);
1488 } else {
[1770]1489 printf("Unpacking %s (from %s)...\n", package_name, deb_file->filename);
[821]1490 }
1491
1492 /* Extract control.tar.gz to /var/lib/dpkg/info/<package>.filename */
[1770]1493 info_prefix = xasprintf("/var/lib/dpkg/info/%s.", package_name);
[821]1494 archive_handle = init_archive_deb_ar(deb_file->filename);
1495 init_archive_deb_control(archive_handle);
1496
[1770]1497 while (all_control_files[i]) {
1498 char *c = xasprintf("./%s", all_control_files[i]);
[821]1499 llist_add_to(&accept_list, c);
1500 i++;
1501 }
1502 archive_handle->sub_archive->accept = accept_list;
1503 archive_handle->sub_archive->filter = filter_accept_list;
1504 archive_handle->sub_archive->action_data = data_extract_all_prefix;
1505 archive_handle->sub_archive->buffer = info_prefix;
1506 archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
1507 unpack_ar_archive(archive_handle);
1508
1509 /* Run the preinst prior to extracting */
1510 if (run_package_script(package_name, "preinst") != 0) {
1511 /* when preinst returns exit code != 0 then quit installation process */
[1770]1512 bb_error_msg_and_die("subprocess pre-installation script returned error");
[821]1513 }
1514
1515 /* Extract data.tar.gz to the root directory */
1516 archive_handle = init_archive_deb_ar(deb_file->filename);
1517 init_archive_deb_data(archive_handle);
1518 archive_handle->sub_archive->action_data = data_extract_all_prefix;
[1770]1519 archive_handle->sub_archive->buffer = (char*)"/"; /* huh? */
[821]1520 archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
1521 unpack_ar_archive(archive_handle);
1522
1523 /* Create the list file */
[1770]1524 list_filename = xasprintf("/var/lib/dpkg/info/%s.list", package_name);
1525 out_stream = xfopen(list_filename, "w");
[821]1526 while (archive_handle->sub_archive->passed) {
1527 /* the leading . has been stripped by data_extract_all_prefix already */
1528 fputs(archive_handle->sub_archive->passed->data, out_stream);
1529 fputc('\n', out_stream);
1530 archive_handle->sub_archive->passed = archive_handle->sub_archive->passed->link;
1531 }
1532 fclose(out_stream);
1533
1534 /* change status */
1535 set_status(status_num, "install", 1);
1536 set_status(status_num, "unpacked", 3);
1537
1538 free(info_prefix);
[1770]1539 free(list_filename);
[821]1540}
1541
1542static void configure_package(deb_file_t *deb_file)
1543{
1544 const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name];
1545 const char *package_version = name_hashtable[package_hashtable[deb_file->package]->version];
1546 const int status_num = search_status_hashtable(package_name);
1547
[1770]1548 printf("Setting up %s (%s)...\n", package_name, package_version);
[821]1549
1550 /* Run the postinst script */
1551 if (run_package_script(package_name, "postinst") != 0) {
1552 /* TODO: handle failure gracefully */
[1770]1553 bb_error_msg_and_die("postinst failure.. set status to what?");
[821]1554 }
1555 /* Change status to reflect success */
1556 set_status(status_num, "install", 1);
1557 set_status(status_num, "installed", 3);
1558}
1559
[1770]1560int dpkg_main(int argc, char **argv);
[821]1561int dpkg_main(int argc, char **argv)
1562{
1563 deb_file_t **deb_file = NULL;
1564 status_node_t *status_node;
[1770]1565 char *str_f;
[821]1566 int opt;
1567 int package_num;
1568 int deb_count = 0;
1569 int state_status;
1570 int status_num;
1571 int i;
[1770]1572 enum {
1573 OPT_configure = 0x1,
1574 OPT_force_ignore_depends = 0x2,
1575 OPT_install = 0x4,
1576 OPT_list_installed = 0x8,
1577 OPT_purge = 0x10,
1578 OPT_remove = 0x20,
1579 OPT_unpack = 0x40,
1580 };
[821]1581
[1770]1582 opt = getopt32(argv, "CF:ilPru", &str_f);
1583 //if (opt & OPT_configure) ... // -C
1584 if (opt & OPT_force_ignore_depends) { // -F (--force in official dpkg)
1585 if (strcmp(str_f, "depends"))
1586 opt &= ~OPT_force_ignore_depends;
[821]1587 }
[1770]1588 //if (opt & OPT_install) ... // -i
1589 //if (opt & OPT_list_installed) ... // -l
1590 //if (opt & OPT_purge) ... // -P
1591 //if (opt & OPT_remove) ... // -r
1592 //if (opt & OPT_unpack) ... // -u (--unpack in official dpkg)
1593 argc -= optind;
1594 argv += optind;
1595 /* check for non-option argument if expected */
1596 if (!opt || (!argc && !(opt && OPT_list_installed)))
[821]1597 bb_show_usage();
1598
[1770]1599 name_hashtable = xzalloc(sizeof(name_hashtable[0]) * (NAME_HASH_PRIME + 1));
1600 package_hashtable = xzalloc(sizeof(package_hashtable[0]) * (PACKAGE_HASH_PRIME + 1));
1601 status_hashtable = xzalloc(sizeof(status_hashtable[0]) * (STATUS_HASH_PRIME + 1));
1602
[821]1603/* puts("(Reading database ... xxxxx files and directories installed.)"); */
1604 index_status_file("/var/lib/dpkg/status");
1605
1606 /* if the list action was given print the installed packages and exit */
[1770]1607 if (opt & OPT_list_installed) {
[821]1608 list_packages();
[1770]1609 return EXIT_SUCCESS;
[821]1610 }
1611
1612 /* Read arguments and store relevant info in structs */
[1770]1613 while (*argv) {
[821]1614 /* deb_count = nb_elem - 1 and we need nb_elem + 1 to allocate terminal node [NULL pointer] */
[1770]1615 deb_file = xrealloc(deb_file, sizeof(deb_file[0]) * (deb_count + 2));
1616 deb_file[deb_count] = xzalloc(sizeof(deb_file[0][0]));
1617 if (opt & (OPT_install | OPT_unpack)) {
1618 /* -i/-u: require filename */
[821]1619 archive_handle_t *archive_handle;
1620 llist_t *control_list = NULL;
1621
1622 /* Extract the control file */
[1770]1623 llist_add_to(&control_list, (char*)"./control");
1624 archive_handle = init_archive_deb_ar(argv[0]);
[821]1625 init_archive_deb_control(archive_handle);
1626 deb_file[deb_count]->control_file = deb_extract_control_file_to_buffer(archive_handle, control_list);
1627 if (deb_file[deb_count]->control_file == NULL) {
[1770]1628 bb_error_msg_and_die("cannot extract control file");
[821]1629 }
[1770]1630 deb_file[deb_count]->filename = xstrdup(argv[0]);
[821]1631 package_num = fill_package_struct(deb_file[deb_count]->control_file);
1632
1633 if (package_num == -1) {
[1770]1634 bb_error_msg("invalid control file in %s", argv[0]);
1635 argv++;
[821]1636 continue;
1637 }
[1770]1638 deb_file[deb_count]->package = (unsigned) package_num;
[821]1639
1640 /* Add the package to the status hashtable */
[1770]1641 if (opt & (OPT_unpack | OPT_install)) {
[821]1642 /* Try and find a currently installed version of this package */
1643 status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[deb_count]->package]->name]);
1644 /* If no previous entry was found initialise a new entry */
[1770]1645 if (status_hashtable[status_num] == NULL
1646 || status_hashtable[status_num]->status == 0
1647 ) {
1648 status_node = xmalloc(sizeof(status_node_t));
[821]1649 status_node->package = deb_file[deb_count]->package;
1650 /* reinstreq isnt changed to "ok" until the package control info
1651 * is written to the status file*/
1652 status_node->status = search_name_hashtable("install reinstreq not-installed");
1653 status_hashtable[status_num] = status_node;
1654 } else {
1655 set_status(status_num, "install", 1);
1656 set_status(status_num, "reinstreq", 2);
1657 }
1658 }
[1770]1659 } else if (opt & (OPT_configure | OPT_purge | OPT_remove)) {
1660 /* -C/-p/-r: require package name */
[821]1661 deb_file[deb_count]->package = search_package_hashtable(
[1770]1662 search_name_hashtable(argv[0]),
1663 search_name_hashtable("ANY"), VER_ANY);
[821]1664 if (package_hashtable[deb_file[deb_count]->package] == NULL) {
[1770]1665 bb_error_msg_and_die("package %s is uninstalled or unknown", argv[0]);
[821]1666 }
1667 package_num = deb_file[deb_count]->package;
1668 status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]);
1669 state_status = get_status(status_num, 3);
1670
1671 /* check package status is "installed" */
[1770]1672 if (opt & OPT_remove) {
1673 if (strcmp(name_hashtable[state_status], "not-installed") == 0
1674 || strcmp(name_hashtable[state_status], "config-files") == 0
1675 ) {
1676 bb_error_msg_and_die("%s is already removed", name_hashtable[package_hashtable[package_num]->name]);
[821]1677 }
1678 set_status(status_num, "deinstall", 1);
[1770]1679 } else if (opt & OPT_purge) {
[821]1680 /* if package status is "conf-files" then its ok */
1681 if (strcmp(name_hashtable[state_status], "not-installed") == 0) {
[1770]1682 bb_error_msg_and_die("%s is already purged", name_hashtable[package_hashtable[package_num]->name]);
[821]1683 }
1684 set_status(status_num, "purge", 1);
1685 }
1686 }
1687 deb_count++;
[1770]1688 argv++;
[821]1689 }
[1770]1690 if (!deb_count)
1691 bb_error_msg_and_die("no package files specified");
[821]1692 deb_file[deb_count] = NULL;
1693
1694 /* Check that the deb file arguments are installable */
[1770]1695 if (!(opt & OPT_force_ignore_depends)) {
[821]1696 if (!check_deps(deb_file, 0, deb_count)) {
[1770]1697 bb_error_msg_and_die("dependency check failed");
[821]1698 }
1699 }
1700
1701 /* TODO: install or remove packages in the correct dependency order */
1702 for (i = 0; i < deb_count; i++) {
1703 /* Remove or purge packages */
[1770]1704 if (opt & OPT_remove) {
[821]1705 remove_package(deb_file[i]->package, 1);
1706 }
[1770]1707 else if (opt & OPT_purge) {
[821]1708 purge_package(deb_file[i]->package);
1709 }
[1770]1710 else if (opt & OPT_unpack) {
[821]1711 unpack_package(deb_file[i]);
1712 }
[1770]1713 else if (opt & OPT_install) {
[821]1714 unpack_package(deb_file[i]);
1715 /* package is configured in second pass below */
1716 }
[1770]1717 else if (opt & OPT_configure) {
[821]1718 configure_package(deb_file[i]);
1719 }
1720 }
1721 /* configure installed packages */
[1770]1722 if (opt & OPT_install) {
[821]1723 for (i = 0; i < deb_count; i++)
1724 configure_package(deb_file[i]);
1725 }
1726
1727 write_status_file(deb_file);
1728
[1770]1729 if (ENABLE_FEATURE_CLEAN_UP) {
1730 for (i = 0; i < deb_count; i++) {
1731 free(deb_file[i]->control_file);
1732 free(deb_file[i]->filename);
1733 free(deb_file[i]);
1734 }
[821]1735
[1770]1736 free(deb_file);
[821]1737
[1770]1738 for (i = 0; i < NAME_HASH_PRIME; i++) {
1739 free(name_hashtable[i]);
1740 }
[821]1741
[1770]1742 for (i = 0; i < PACKAGE_HASH_PRIME; i++) {
[821]1743 free_package(package_hashtable[i]);
1744 }
1745
[1770]1746 for (i = 0; i < STATUS_HASH_PRIME; i++) {
1747 free(status_hashtable[i]);
1748 }
1749
1750 free(status_hashtable);
1751 free(package_hashtable);
1752 free(name_hashtable);
[821]1753 }
1754
[1770]1755 return EXIT_SUCCESS;
[821]1756}
Note: See TracBrowser for help on using the repository browser.