source: trunk/athena/etc/rpmupdate/rpmupdate.c @ 15879

Revision 15879, 25.0 KB checked in by ghudson, 24 years ago (diff)
Update for rpm 4.x.
Line 
1/* Copyright 2000 by the Massachusetts Institute of Technology.
2 *
3 * Permission to use, copy, modify, and distribute this
4 * software and its documentation for any purpose and without
5 * fee is hereby granted, provided that the above copyright
6 * notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting
8 * documentation, and that the name of M.I.T. not be used in
9 * advertising or publicity pertaining to distribution of the
10 * software without specific, written prior permission.
11 * M.I.T. makes no representations about the suitability of
12 * this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 */
15
16/* rpmupdate - Update a workstation from an old list of RPMs to a new
17 * list, applying the update policies of a public or private
18 * workstation as indicated by the flags.
19 */
20
21static const char rcsid[] = "$Id: rpmupdate.c,v 1.2 2001-03-26 20:16:03 ghudson Exp $";
22
23#define _GNU_SOURCE
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <sys/utsname.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <stdarg.h>
30#include <string.h>
31#include <ctype.h>
32#include <unistd.h>
33#include <fcntl.h>
34#include <assert.h>
35#include <errno.h>
36#include <rpmlib.h>
37#include <misc.h>       /* From /usr/include/rpm */
38
39#define HASHSIZE 1009
40
41struct rev {
42  int present;                  /* If 0, rest of structure is invalid */
43  int epoch;
44  char *version;
45  char *release;
46};
47
48struct package {
49  char *pkgname;
50  char *filename;               /* File name in new list */
51  struct rev oldlistrev;
52  struct rev newlistrev;
53  struct rev instrev;           /* Most recent version installed */
54  int erase;
55  struct package *next;
56};
57
58struct notify_data {
59  FD_t fd;
60  int hashmarks_flag;
61  int hashmarks_printed;
62};
63
64enum act { UPDATE, ERASE, NONE };
65
66static char *progname;
67
68static void read_old_list(struct package **pkgtab, const char *oldlistname);
69static void read_new_list(struct package **pkgtab, const char *newlistname);
70static void read_installed_versions(struct package **pkgtab);
71static void perform_updates(struct package **pkgtab, int public, int dryrun,
72                            int hashmarks);
73static void *notify(const void *h, const rpmCallbackType what,
74                    const unsigned long amount, const unsigned long total,
75                    const void *pkgKey, void *data);
76static enum act decide_public(struct package *pkg);
77static enum act decide_private(struct package *pkg);
78static void schedule_update(struct package *pkg, rpmTransactionSet rpmdep);
79static void display_action(struct package *pkg, enum act action);
80static void update_lilo(struct package *pkg);
81static char *fudge_arch_in_filename(char *filename);
82static void printrev(struct rev *rev);
83static int revcmp(struct rev *rev1, struct rev *rev2);
84static int revsame(struct rev *rev1, struct rev *rev2);
85static void freerev(struct rev *rev);
86static void parse_line(const char *path, char **pkgname, int *epoch,
87                       char **version, char **release, char **filename);
88struct package *get_package(struct package **table, const char *pkgname);
89static unsigned int hash(const char *str);
90static const char *find_back(const char *start, const char *end, char c);
91static const char *skip_to_value(const char *p, const char *varname);
92static void *emalloc(size_t size);
93static void *erealloc(void *ptr, size_t size);
94static char *estrdup(const char *s);
95static char *estrndup(const char *s, size_t n);
96static int easprintf(char **ptr, const char *fmt, ...);
97static int read_line(FILE *fp, char **buf, int *bufsize);
98static void die(const char *fmt, ...);
99static void usage(void);
100
101int main(int argc, char **argv)
102{
103  struct package *pkgtab[HASHSIZE];
104  int i, c, public = 0, dryrun = 0, hashmarks = 0;
105  const char *oldlistname, *newlistname;
106
107  /* Initialize rpmlib. */
108  rpmReadConfigFiles(NULL, NULL);
109
110  /* Parse options. */
111  rpmSetVerbosity(RPMMESS_NORMAL);
112  progname = strrchr(argv[0], '/');
113  progname = (progname == NULL) ? argv[0] : progname + 1;
114  while ((c = getopt(argc, argv, "hnpv")) != EOF)
115    {
116      switch (c)
117        {
118        case 'h':
119          hashmarks = 1;
120          break;
121        case 'n':
122          dryrun = 1;
123          break;
124        case 'p':
125          public = 1;
126          break;
127        case 'v':
128          rpmIncreaseVerbosity();
129          break;
130        case '?':
131          usage();
132        }
133    }
134  argc -= optind;
135  argv += optind;
136  if (argc != 2)
137    usage();
138  oldlistname = argv[0];
139  newlistname = argv[1];
140
141  /* Initialize the package hash table. */
142  for (i = 0; i < HASHSIZE; i++)
143    pkgtab[i] = NULL;
144
145  /* Read the lists and the current versions into the hash table. */
146  read_old_list(pkgtab, oldlistname);
147  read_new_list(pkgtab, newlistname);
148  read_installed_versions(pkgtab);
149
150  /* Walk the table and perform the required updates. */
151  perform_updates(pkgtab, public, dryrun, hashmarks);
152
153  if (!dryrun)
154    update_lilo(get_package(pkgtab, "kernel"));
155
156  exit(0);
157}
158
159static void read_old_list(struct package **pkgtab, const char *oldlistname)
160{
161  FILE *fp;
162  char *buf = NULL, *pkgname, *version, *release;
163  int bufsize, epoch;
164  struct package *pkg;
165
166  fp = fopen(oldlistname, "r");
167  if (!fp)
168    die("Can't read old list %s", oldlistname);
169
170  while (read_line(fp, &buf, &bufsize) == 0)
171    {
172      parse_line(buf, &pkgname, &epoch, &version, &release, NULL);
173      pkg = get_package(pkgtab, pkgname);
174      pkg->oldlistrev.present = 1;
175      pkg->oldlistrev.epoch = epoch;
176      pkg->oldlistrev.version = version;
177      pkg->oldlistrev.release = release;
178      free(pkgname);
179    }
180  free(buf);
181}
182
183static void read_new_list(struct package **pkgtab, const char *newlistname)
184{
185  FILE *fp;
186  char *buf = NULL, *pkgname, *version, *release, *filename;
187  int bufsize, epoch;
188  struct package *pkg;
189
190  fp = fopen(newlistname, "r");
191  if (!fp)
192    die("Can't read old list %s", newlistname);
193
194  while (read_line(fp, &buf, &bufsize) == 0)
195    {
196      parse_line(buf, &pkgname, &epoch, &version, &release, &filename);
197      pkg = get_package(pkgtab, pkgname);
198      pkg->filename = filename;
199      pkg->newlistrev.present = 1;
200      pkg->newlistrev.epoch = epoch;
201      pkg->newlistrev.version = version;
202      pkg->newlistrev.release = release;
203      free(pkgname);
204    }
205  free(buf);
206}
207
208static void read_installed_versions(struct package **pkgtab)
209{
210  rpmdbMatchIterator mi;
211  Header h;
212  char *pkgname, *version, *release;
213  struct package *pkg;
214  rpmdb db;
215  int_32 *epoch;
216  struct rev rev;
217
218  if (rpmdbOpen(NULL, &db, O_RDONLY, 0644))
219    die("Can't open RPM database for reading");
220
221  mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, NULL, 0);
222  if (mi == NULL)
223    die("Failed to initialize database iterator\n");
224  while ((h = rpmdbNextIterator(mi)) != NULL)
225    {
226      headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &pkgname, NULL);
227      if (!headerGetEntry(h, RPMTAG_EPOCH, NULL, (void **) &epoch, NULL))
228        epoch = NULL;
229      headerGetEntry(h, RPMTAG_VERSION, NULL, (void **) &version, NULL);
230      headerGetEntry(h, RPMTAG_RELEASE, NULL, (void **) &release, NULL);
231
232      /* Two versions of the same package can be installed on a system
233       * with some coercion.  If so, make sure that instrev gets set to
234       * the highest one.
235       */
236      rev.present = 1;
237      rev.epoch = (epoch == NULL) ? -1 : *epoch;
238      rev.version = estrdup(version);
239      rev.release = estrdup(release);
240      pkg = get_package(pkgtab, pkgname);
241      if (!pkg->instrev.present)
242        pkg->instrev = rev;
243      else if (revcmp(&rev, &pkg->instrev) > 0)
244        {
245          freerev(&pkg->instrev);
246          pkg->instrev = rev;
247        }
248      else
249        freerev(&rev);
250    }
251
252  rpmdbClose(db);
253}
254
255static void perform_updates(struct package **pkgtab, int public, int dryrun,
256                            int hashmarks)
257{
258  int r, i;
259  struct package *pkg;
260  rpmdb db;
261  rpmTransactionSet rpmdep;
262  rpmProblemSet probs = NULL;
263  struct rpmDependencyConflict *conflicts;
264  int nconflicts;
265  rpmdbMatchIterator mi;
266  Header h;
267  enum act action;
268  char *pkgname;
269  struct notify_data ndata;
270
271  if (!dryrun)
272    {
273      if (rpmdbOpen(NULL, &db, O_RDWR, 0644))
274        die("Can't open RPM database for writing");
275      rpmdep = rpmtransCreateSet(db, NULL);
276    }
277
278  /* Decide what to do for each package.  Add updates to the
279   * transaction set.  Flag erasures for the next bit of code.
280   * If we're doing a dry run, say what we're going to do here.
281   */
282  for (i = 0; i < HASHSIZE; i++)
283    {
284      for (pkg = pkgtab[i]; pkg; pkg = pkg->next)
285        {
286          if (public)
287            action = decide_public(pkg);
288          else
289            action = decide_private(pkg);
290          if (dryrun)
291            display_action(pkg, action);
292          else if (action == UPDATE)
293            schedule_update(pkg, rpmdep);
294          else if (action == ERASE)
295            pkg->erase = 1;
296        }
297    }
298
299  if (dryrun)
300    return;
301
302  /* Walk through the database and add transactions to erase packages
303   * which we've flagged for erasure.  We do erasures this way
304   * (instead of remembering the offset in read_installed_versions())
305   * because the database offsets might have changed since we read the
306   * versions and because this way if a package we want to erase is
307   * installed at two different versions on the system, we will remove
308   * both packages.
309   */
310  mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, NULL, 0);
311  if (mi == NULL)
312    die("Failed to initialize database iterator\n");
313  while ((h = rpmdbNextIterator(mi)) != NULL)
314    {
315      headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &pkgname, NULL);
316      pkg = get_package(pkgtab, pkgname);
317      if (pkg->erase)
318        {
319          /* What we'd really like to do is display hashmarks while
320           * we're actually removing the package.  But librpm doesn't
321           * issue callbacks for erased RPMs, so we can't do that
322           * currently.  Instead, tell people what packages we're
323           * going to erase.
324           */
325          if (hashmarks)
326            printf("Scheduling removal of package %s\n", pkgname);
327          rpmtransRemovePackage(rpmdep, rpmdbGetIteratorOffset(mi));
328        }
329    }
330
331  /* The transaction set is complete.  Check for dependency problems. */
332  if (rpmdepCheck(rpmdep, &conflicts, &nconflicts) != 0)
333    exit(1);
334  if (conflicts)
335    {
336      fprintf(stderr, "Update would break dependencies:\n");
337      printDepProblems(stderr, conflicts, nconflicts);
338      rpmdepFreeConflicts(conflicts, nconflicts);
339      exit(1);
340    }
341
342  ndata.hashmarks_flag = hashmarks;
343  ndata.hashmarks_printed = 0;
344  r = rpmRunTransactions(rpmdep, notify, &ndata, NULL, &probs, 0,
345                         RPMPROB_FILTER_OLDPACKAGE|RPMPROB_FILTER_REPLACEPKG);
346  if (r < 0)
347    die("Failed to run transactions\n");
348  else if (r > 0)
349    {
350      fprintf(stderr, "Update failed due to the following problems:\n");
351      rpmProblemSetPrint(stderr, probs);
352      exit(1);
353    }
354
355  rpmdbClose(db);
356}
357
358/* Callback function for rpmRunTransactions. */
359static void *notify(const void *arg, const rpmCallbackType what,
360                    const unsigned long amount, const unsigned long total,
361                    const void *pkgKey, void *data)
362{
363  Header h = (Header) arg;
364  const char *filename = pkgKey;
365  struct notify_data *ndata = data;
366  int n;
367
368  switch (what)
369    {
370    case RPMCALLBACK_INST_OPEN_FILE:
371      ndata->fd = fdOpen(filename, O_RDONLY, 0);
372      return ndata->fd;
373
374    case RPMCALLBACK_INST_CLOSE_FILE:
375      fdClose(ndata->fd);
376      return NULL;
377
378    case RPMCALLBACK_INST_START:
379      if (ndata->hashmarks_flag)
380        {
381          ndata->hashmarks_printed = 0;
382          printf("%-28s", headerSprintf(h, "%{NAME}", rpmTagTable,
383                                        rpmHeaderFormats, NULL));
384          fflush(stdout);
385        }
386      return NULL;
387
388    case RPMCALLBACK_INST_PROGRESS:
389      if (ndata->hashmarks_flag)
390        {
391          n = (amount == total) ? 50 : 50.0 * amount / total;
392          for (; ndata->hashmarks_printed < n; ndata->hashmarks_printed++)
393            putchar('#');
394          if (amount == total)
395            putchar('\n');
396          fflush(stdout);
397        }
398      return NULL;
399
400    default:
401      return NULL;
402    }
403}
404
405/* Apply the public workstation rules to decide what to do with a
406 * package.
407 */
408static enum act decide_public(struct package *pkg)
409{
410  /* Erase any installed package which isn't in the new list. */
411  if (pkg->instrev.present && !pkg->newlistrev.present)
412    return ERASE;
413
414  /* Update any new list package which isn't installed at the new list rev. */
415  if (pkg->newlistrev.present && !revsame(&pkg->instrev, &pkg->newlistrev))
416    return UPDATE;
417
418  return NONE;
419}
420
421/* Apply the private workstation rules to decide what to do with a
422 * package.
423 */
424static enum act decide_private(struct package *pkg)
425{
426  /* If the installed package state is the same as it was in the old
427   * list, then apply the public workstation rules to this package,
428   * since we were the last ones to touch it.
429   */
430  if (revsame(&pkg->instrev, &pkg->oldlistrev))
431    return decide_public(pkg);
432
433  /* Otherwise, there has been a local change of some sort to the
434   * package.  Update the package if it is installed and we have a
435   * new version, but don't touch it otherwise.
436   */
437  if (pkg->instrev.present && pkg->newlistrev.present
438      && revcmp(&pkg->newlistrev, &pkg->instrev) > 0)
439    return UPDATE;
440
441  return NONE;
442}
443
444/* Read the header from an RPM file and add an update transaction for it. */
445static void schedule_update(struct package *pkg, rpmTransactionSet rpmdep)
446{
447  Header h;
448  FD_t fd;
449
450  assert(pkg->filename != NULL);
451  pkg->filename = fudge_arch_in_filename(pkg->filename);
452  fd = fdOpen(pkg->filename, O_RDONLY, 0);
453  if (fd == NULL)
454    die("Can't read package file %s", pkg->filename);
455  if (rpmReadPackageHeader(fd, &h, NULL, NULL, NULL) != 0)
456    die("Invalid rpm header in file %s", pkg->filename);
457  if (rpmtransAddPackage(rpmdep, h, NULL, pkg->filename, 1, NULL) != 0)
458    die("Can't install or update package %s", pkg->pkgname);
459  fdClose(fd);
460  headerFree(h);
461}
462
463static void display_action(struct package *pkg, enum act action)
464{
465  switch (action)
466    {
467    case ERASE:
468      printf("Erase package %s\n", pkg->pkgname);
469      break;
470    case UPDATE:
471      if (pkg->instrev.present)
472        {
473          printf("Update package %s from rev ", pkg->pkgname);
474          printrev(&pkg->instrev);
475          printf(" to rev ");
476          printrev(&pkg->newlistrev);
477          printf("\n");
478        }
479      else
480        {
481          printf("Install package %s at rev ", pkg->pkgname);
482          printrev(&pkg->newlistrev);
483          printf("\n");
484        }
485      break;
486    default:
487      break;
488    }
489}
490
491/* Red Hat's kernel package doesn't take care of lilo.conf; the update
492 * agent does.  So we have to take care of it too.
493 */
494static void update_lilo(struct package *pkg)
495{
496  const char *name = "/etc/lilo.conf", *savename = "/etc/lilo.conf.rpmsave";
497  FILE *in, *out;
498  char *buf = NULL, *oldkname, *newkname, *newiname, *initrdcmd;
499  const char *p;
500  int bufsize = 0, status, replaced;
501  struct stat statbuf;
502
503  /* For now, only act on updates. */
504  if (!pkg->instrev.present || !pkg->newlistrev.present
505      || revsame(&pkg->instrev, &pkg->newlistrev))
506    return;
507
508  /* Figure out kernel names. */
509  easprintf(&oldkname, "/boot/vmlinuz-%s-%s", pkg->instrev.version,
510            pkg->instrev.release);
511  easprintf(&newkname, "/boot/vmlinuz-%s-%s", pkg->newlistrev.version,
512            pkg->newlistrev.release);
513  easprintf(&newiname, "/boot/initrd-%s-%s.img", pkg->newlistrev.version,
514            pkg->newlistrev.release);
515  easprintf(&initrdcmd, "/sbin/mkinitrd -f /boot/initrd-%s-%s.img %s-%s",
516            pkg->newlistrev.version, pkg->newlistrev.release,
517            pkg->newlistrev.version, pkg->newlistrev.release);
518
519  if (stat(name, &statbuf) == -1)
520    die("Can't stat lilo.conf for rewrite.");
521
522  /* This isn't very atomic, but it's what Red Hat's update agent does. */
523  if (rename(name, savename) == -1)
524    die("Can't rename /etc/lilo.conf to /etc/lilo.conf.rpmsave.");
525  in = fopen(savename, "r");
526  out = fopen(name, "w");
527  if (!in || !out)
528    {
529      rename(savename, name);
530      die("Can't open lilo.conf files for rewrite.");
531    }
532  fchmod(fileno(out), statbuf.st_mode & 0777);
533
534  /* Rewrite lilo.conf, changing the old kernel name to the new kernel
535   * name in "image" lines.  Update "initrd" following images we
536   * replace, if it exists, for the benefit of machines that need it.
537   */
538  replaced = 0;
539  while ((status = read_line(in, &buf, &bufsize)) == 0)
540    {
541      p = buf;
542      while (isspace(*p))
543        p++;
544      if (strncmp(p, "image", 5) == 0 && !isalpha(p[5]))
545        {
546          p = strstr(buf, oldkname);
547          if (p != NULL)
548            {
549              fprintf(out, "%.*s%s%s\n", p - buf, buf, newkname,
550                      p + strlen(oldkname));
551              replaced = 1;
552              continue;
553            }
554          else
555            replaced = 0;
556        }
557      else if (replaced && strncmp(p, "initrd", 6) == 0 && !isalpha(p[6]))
558        {
559          p = skip_to_value(p, "initrd");
560          if (p != NULL)
561            {
562              fprintf(out, "%.*s%s\n", p - buf, buf, newiname);
563              if (system(initrdcmd) != 0)
564                fprintf(stderr, "Error running %s", initrdcmd);
565              continue;
566            }
567        }
568      fprintf(out, "%s\n", buf);
569    }
570  fclose(in);
571  if (status == -1 || ferror(out) || fclose(out) == EOF)
572    {
573      rename(savename, name);
574      die("Error rewriting lilo.conf: %s", strerror(errno));
575    }
576
577  free(oldkname);
578  free(newkname);
579  free(newiname);
580  free(initrdcmd);
581  system("/sbin/lilo");
582}
583
584/* If filename has an arch string which is too high for this machine's
585 * architecture, replace it with a new filename containing this
586 * machine's architecture.  filename must be an allocated string which
587 * can be freed and replaced.
588 */
589static char *fudge_arch_in_filename(char *filename)
590{
591  static const char *arches[] = { "i386", "i486", "i586", "i686", NULL };
592  const char *p;
593  int i, j, len;
594  struct utsname buf;
595  char *newfile;
596
597  /* Find the beginning of the arch string in filename. */
598  p = find_back(filename, filename + strlen(filename), '.');
599  assert(p != NULL);
600  p = find_back(filename, p, '.');
601  assert(p != NULL);
602  p++;
603
604  /* Locate this architecture in the array.  If it's not one we recognize,
605   * or if it's the least common denominator, leave well enough alone.
606   */
607  for (i = 0; arches[i] != NULL; i++)
608    {
609      len = strlen(arches[i]);
610      if (strncmp(p, arches[i], len) == 0 && *(p + len) == '.')
611        break;
612    }
613  if (i == 0 || arches[i] == NULL)
614    return filename;
615
616  /* Locate this machine's architecture in the array.  If we don't
617   * recognize it, or if it's at least as high as the filename's
618   * architecture, don't touch anything. */
619  assert(uname(&buf) == 0);
620  for (j = 0; arches[j] != NULL; j++)
621    {
622      if (strcmp(buf.machine, arches[j]) == 0)
623        break;
624    }
625  if (j >= i)
626    return filename;
627
628  /* We have to downgrade the architecture of the filename.  Make a
629   * new string and free the old one.
630   */
631  easprintf(&newfile, "%.*s%s%s", p - filename, filename, arches[j],
632            p + strlen(arches[i]));
633  free(filename);
634  return newfile;
635}
636
637static void printrev(struct rev *rev)
638{
639  assert(rev->present);
640  if (rev->epoch != -1)
641    printf("%d:", rev->epoch);
642  printf("%s-%s", rev->version, rev->release);
643}
644
645/* Compare two present revisions to see which is greater.  Returns 1 if
646 * rev1 is greater, -1 if rev2 is greater, or 0 if they are the same.
647 */
648static int revcmp(struct rev *rev1, struct rev *rev2)
649{
650  int r;
651
652  assert(rev1->present && rev2->present);
653  if (rev1->epoch != rev2->epoch)
654    return (rev1->epoch > rev2->epoch) ? 1 : -1;
655  else if ((r = rpmvercmp(rev1->version, rev2->version)) != 0)
656    return r;
657  else
658    return rpmvercmp(rev1->release, rev2->release);
659}
660
661/* Compare two revisions (either of which might not be present) to see
662 * if they're the same.
663 */
664static int revsame(struct rev *rev1, struct rev *rev2)
665{
666  if (rev1->present != rev2->present)
667    return 0;
668  else if (!rev1->present && !rev2->present)
669    return 1;
670  else
671    return (revcmp(rev1, rev2) == 0);
672}
673
674static void freerev(struct rev *rev)
675{
676  assert(rev->present);
677  free(rev->version);
678  free(rev->release);
679}
680
681/* Get package name and version information from a path line.  It
682 * would be more robust to look inside the package, but that's too
683 * inefficient when the packages live in AFS.  So assume that the
684 * trailing component of the path name is
685 * "pkgname-version-release.arch.rpm".  The RPM name may be followed
686 * by an epoch number if the RPM has one.
687 *
688 * The values set in *pkgname, *version, and *release are malloc'd and
689 * become the responsibility of the caller.
690 */
691static void parse_line(const char *line, char **pkgname, int *epoch,
692                       char **version, char **release, char **filename)
693{
694  const char *end, *p;
695  const char *pkgstart, *pkgend, *verstart, *verend, *relstart, *relend;
696
697  /* See if there's whitespace, which would be followed by an epoch. */
698  end = line;
699  while (*end && !isspace(*end))
700    end++;
701  if (*end)
702    {
703      p = end;
704      while (isspace(*p))
705        p++;
706      *epoch = atoi(p);
707    }
708  else
709    *epoch = -1;
710
711  /* The package name starts after the last slash in the path, if any. */
712  pkgstart = find_back(line, end, '/');
713  pkgstart = (pkgstart == NULL) ? line : pkgstart + 1;
714
715  /* The release string ends at the second to last period after pkgstart. */
716  relend = strrchr(pkgstart, '.');
717  if (!relend)
718    die("Found malformed RPM path line %s", line);
719  relend = find_back(pkgstart, relend, '.');
720  if (!relend)
721    die("Found malformed RPM path line %s", line);
722
723  /* The release string starts after the dash before relend. */
724  relstart = find_back(pkgstart, relend, '-');
725  if (!relstart)
726    die("Found malformed RPM path line %s", line);
727  relstart++;
728
729  /* The version string ends at that dash. */
730  verend = relstart - 1;
731
732  /* The version string starts after the dash before verend. */
733  verstart = find_back(pkgstart, verend, '-');
734  if (!verstart)
735    die("Found malformed RPM path line %s", line);
736  verstart++;
737
738  /* The package name ends at that dash. */
739  pkgend = verstart - 1;
740
741  *pkgname = estrndup(pkgstart, pkgend - pkgstart);
742  *version = estrndup(verstart, verend - verstart);
743  *release = estrndup(relstart, relend - relstart);
744  if (filename)
745    *filename = estrndup(line, end - line);
746}
747
748struct package *get_package(struct package **table, const char *pkgname)
749{
750  struct package **pkgptr, *pkg;
751
752  /* Look through the hash bucket for a package with the right name. */
753  for (pkgptr = &table[hash(pkgname)]; *pkgptr; pkgptr = &(*pkgptr)->next)
754    {
755      pkg = *pkgptr;
756      if (strcmp(pkg->pkgname, pkgname) == 0)
757        return pkg;
758    }
759
760  /* Create a new package and chain it in. */
761  pkg = emalloc(sizeof(struct package));
762  pkg->pkgname = estrdup(pkgname);
763  pkg->filename = NULL;
764  pkg->oldlistrev.present = 0;
765  pkg->newlistrev.present = 0;
766  pkg->instrev.present = 0;
767  pkg->erase = 0;
768  pkg->next = NULL;
769  *pkgptr = pkg;
770  return pkg;
771}
772
773/* Algorithm by Peter J. Weinberger.  Uses a hard-wired modulus for now. */
774static unsigned int hash(const char *str)
775{
776  unsigned int hashval, g;
777
778  hashval = 0;
779  for (; *str; str++)
780    {
781      hashval = (hashval << 4) + *str;
782      if ((g = hashval & 0xf0000000) != 0)
783        {
784          hashval ^= g >> 24;
785          hashval ^= g;
786        }
787    }
788  return hashval % HASHSIZE;
789}
790
791/* Find c in the range start..end-1, or return NULL if it isn't there. */
792static const char *find_back(const char *start, const char *end, char c)
793{
794  const char *p;
795
796  for (p = end - 1; p >= start; p--)
797    {
798      if (*p == c)
799        return p;
800    }
801  return NULL;
802}
803
804/* Assuming p points to a string beginning with varname, return a
805 * pointer to the value part.
806 */
807static const char *skip_to_value(const char *p, const char *varname)
808{
809  p += strlen(varname);
810  while (isspace(*p))
811    p++;
812  if (*p != '=')
813    return NULL;
814  p++;
815  while (isspace(*p))
816    p++;
817  return p;
818}
819
820static void *emalloc(size_t size)
821{
822  void *ptr;
823
824  ptr = malloc(size);
825  if (!ptr)
826    die("malloc size %lu failed", (unsigned long) size);
827  return ptr;
828}
829
830static void *erealloc(void *ptr, size_t size)
831{
832  ptr = realloc(ptr, size);
833  if (!ptr)
834    die("realloc size %lu failed", (unsigned long) size);
835  return ptr;
836}
837
838static char *estrdup(const char *s)
839{
840  char *new_s;
841
842  new_s = emalloc(strlen(s) + 1);
843  strcpy(new_s, s);
844  return new_s;
845}
846
847static char *estrndup(const char *s, size_t n)
848{
849  char *new_s;
850
851  new_s = emalloc(n + 1);
852  memcpy(new_s, s, n);
853  new_s[n] = 0;
854  return new_s;
855}
856
857static int easprintf(char **ptr, const char *fmt, ...)
858{
859  va_list ap;
860  int ret;
861
862  va_start(ap, fmt);
863  ret = vasprintf(ptr, fmt, ap);
864  va_end(ap);
865  if (ret == -1)
866    die("asprintf malloc failed for format string %s", fmt);
867  return ret;
868}
869
870/* Read a line from a file into a dynamically allocated buffer,
871 * zeroing the trailing newline if there is one.  The calling routine
872 * may call read_line multiple times with the same buf and bufsize
873 * pointers; *buf will be reallocated and *bufsize adjusted as
874 * appropriate.  The initial value of *buf should be NULL.  After the
875 * calling routine is done reading lines, it should free *buf.  This
876 * function returns 0 if a line was successfully read, 1 if the file
877 * ended, and -1 if there was an I/O error.
878 */
879
880static int read_line(FILE *fp, char **buf, int *bufsize)
881{
882  char *newbuf;
883  int offset = 0, len;
884
885  if (*buf == NULL)
886    {
887      *buf = emalloc(128);
888      *bufsize = 128;
889    }
890
891  while (1)
892    {
893      if (!fgets(*buf + offset, *bufsize - offset, fp))
894        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
895      len = offset + strlen(*buf + offset);
896      if ((*buf)[len - 1] == '\n')
897        {
898          (*buf)[len - 1] = 0;
899          return 0;
900        }
901      offset = len;
902
903      /* Allocate more space. */
904      newbuf = erealloc(*buf, *bufsize * 2);
905      *buf = newbuf;
906      *bufsize *= 2;
907    }
908}
909
910static void die(const char *fmt, ...)
911{
912  va_list ap;
913
914  fprintf(stderr, "%s: ", progname);
915  va_start(ap, fmt);
916  vfprintf(stderr, fmt, ap);
917  va_end(ap);
918  fprintf(stderr, "\n");
919  exit(1);
920}
921
922static void usage()
923{
924  fprintf(stderr, "Usage: %s [-p] oldlist newlist\n", progname);
925  exit(1);
926}
Note: See TracBrowser for help on using the repository browser.