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

Revision 15738, 24.9 KB checked in by ghudson, 24 years ago (diff)
Move rpmupdate here from packs/update/os/linux.
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.1 2001-03-09 20:38:00 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 Header 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  Header h;
211  int offset;
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  for (offset = rpmdbFirstRecNum(db);
222       offset != 0;
223       offset = rpmdbNextRecNum(db, offset))
224    {
225      h = rpmdbGetRecord(db, offset);
226      if (h == NULL)
227        die("Failed to read database record\n");
228      headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &pkgname, NULL);
229      if (!headerGetEntry(h, RPMTAG_EPOCH, NULL, (void **) &epoch, NULL))
230        epoch = NULL;
231      headerGetEntry(h, RPMTAG_VERSION, NULL, (void **) &version, NULL);
232      headerGetEntry(h, RPMTAG_RELEASE, NULL, (void **) &release, NULL);
233
234      /* Two versions of the same package can be installed on a system
235       * with some coercion.  If so, make sure that instrev gets set to
236       * the highest one.
237       */
238      rev.present = 1;
239      rev.epoch = (epoch == NULL) ? -1 : *epoch;
240      rev.version = estrdup(version);
241      rev.release = estrdup(release);
242      pkg = get_package(pkgtab, pkgname);
243      if (!pkg->instrev.present)
244        pkg->instrev = rev;
245      else if (revcmp(&rev, &pkg->instrev) > 0)
246        {
247          freerev(&pkg->instrev);
248          pkg->instrev = rev;
249        }
250      else
251        freerev(&rev);
252
253      headerFree(h);
254    }
255
256  rpmdbClose(db);
257}
258
259static void perform_updates(struct package **pkgtab, int public, int dryrun,
260                            int hashmarks)
261{
262  int r, i, offset;
263  struct package *pkg;
264  rpmdb db;
265  rpmTransactionSet rpmdep;
266  rpmProblemSet probs = NULL;
267  struct rpmDependencyConflict *conflicts;
268  int nconflicts;
269  Header h;
270  enum act action;
271  char *pkgname;
272  struct notify_data ndata;
273
274  if (!dryrun)
275    {
276      if (rpmdbOpen(NULL, &db, O_RDWR, 0644))
277        die("Can't open RPM database for writing");
278      rpmdep = rpmtransCreateSet(db, NULL);
279    }
280
281  /* Decide what to do for each package.  Add updates to the
282   * transaction set.  Flag erasures for the next bit of code.
283   * If we're doing a dry run, say what we're going to do here.
284   */
285  for (i = 0; i < HASHSIZE; i++)
286    {
287      for (pkg = pkgtab[i]; pkg; pkg = pkg->next)
288        {
289          if (public)
290            action = decide_public(pkg);
291          else
292            action = decide_private(pkg);
293          if (dryrun)
294            display_action(pkg, action);
295          else if (action == UPDATE)
296            schedule_update(pkg, rpmdep);
297          else if (action == ERASE)
298            pkg->erase = 1;
299        }
300    }
301
302  if (dryrun)
303    return;
304
305  /* Walk through the database and add transactions to erase packages
306   * which we've flagged for erasure.  We do erasures this way
307   * (instead of remembering the offset in read_installed_versions())
308   * because the database offsets might have changed since we read the
309   * versions and because this way if a package we want to erase is
310   * installed at two different versions on the system, we will remove
311   * both packages.
312   */
313  for (offset = rpmdbFirstRecNum(db);
314       offset != 0;
315       offset = rpmdbNextRecNum(db, offset))
316    {
317      h = rpmdbGetRecord(db, offset);
318      headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &pkgname, NULL);
319      pkg = get_package(pkgtab, pkgname);
320      if (pkg->erase)
321        {
322          /* What we'd really like to do is display hashmarks while
323           * we're actually removing the package.  But librpm doesn't
324           * issue callbacks for erased RPMs, so we can't do that
325           * currently.  Instead, tell people what packages we're
326           * going to erase.
327           */
328          if (hashmarks)
329            printf("Scheduling removal of package %s\n", pkgname);
330          rpmtransRemovePackage(rpmdep, offset);
331        }
332      headerFree(h);
333    }
334
335  /* The transaction set is complete.  Check for dependency problems. */
336  if (rpmdepCheck(rpmdep, &conflicts, &nconflicts) != 0)
337    exit(1);
338  if (conflicts)
339    {
340      fprintf(stderr, "Update would break dependencies:\n");
341      printDepProblems(stderr, conflicts, nconflicts);
342      rpmdepFreeConflicts(conflicts, nconflicts);
343      exit(1);
344    }
345
346  ndata.hashmarks_flag = hashmarks;
347  ndata.hashmarks_printed = 0;
348  r = rpmRunTransactions(rpmdep, notify, &ndata, NULL, &probs, 0,
349                         RPMPROB_FILTER_OLDPACKAGE|RPMPROB_FILTER_REPLACEPKG);
350  if (r < 0)
351    die("Failed to run transactions\n");
352  else if (r > 0)
353    {
354      fprintf(stderr, "Update failed due to the following problems:\n");
355      rpmProblemSetPrint(stderr, probs);
356      exit(1);
357    }
358
359  rpmdbClose(db);
360}
361
362/* Callback function for rpmRunTransactions. */
363static void *notify(const Header h, const rpmCallbackType what,
364                    const unsigned long amount, const unsigned long total,
365                    const void *pkgKey, void *data)
366{
367  const char *filename = pkgKey;
368  struct notify_data *ndata = data;
369  int n;
370
371  switch (what)
372    {
373    case RPMCALLBACK_INST_OPEN_FILE:
374      ndata->fd = fdOpen(filename, O_RDONLY, 0);
375      return ndata->fd;
376
377    case RPMCALLBACK_INST_CLOSE_FILE:
378      fdClose(ndata->fd);
379      return NULL;
380
381    case RPMCALLBACK_INST_START:
382      if (ndata->hashmarks_flag)
383        {
384          ndata->hashmarks_printed = 0;
385          printf("%-28s", headerSprintf(h, "%{NAME}", rpmTagTable,
386                                        rpmHeaderFormats, NULL));
387          fflush(stdout);
388        }
389      return NULL;
390
391    case RPMCALLBACK_INST_PROGRESS:
392      if (ndata->hashmarks_flag)
393        {
394          n = (amount == total) ? 50 : 50.0 * amount / total;
395          for (; ndata->hashmarks_printed < n; ndata->hashmarks_printed++)
396            putchar('#');
397          if (amount == total)
398            putchar('\n');
399          fflush(stdout);
400        }
401      return NULL;
402
403    default:
404      return NULL;
405    }
406}
407
408/* Apply the public workstation rules to decide what to do with a
409 * package.
410 */
411static enum act decide_public(struct package *pkg)
412{
413  /* Erase any installed package which isn't in the new list. */
414  if (pkg->instrev.present && !pkg->newlistrev.present)
415    return ERASE;
416
417  /* Update any new list package which isn't installed at the new list rev. */
418  if (pkg->newlistrev.present && !revsame(&pkg->instrev, &pkg->newlistrev))
419    return UPDATE;
420
421  return NONE;
422}
423
424/* Apply the private workstation rules to decide what to do with a
425 * package.
426 */
427static enum act decide_private(struct package *pkg)
428{
429  /* If the installed package state is the same as it was in the old
430   * list, then apply the public workstation rules to this package,
431   * since we were the last ones to touch it.
432   */
433  if (revsame(&pkg->instrev, &pkg->oldlistrev))
434    return decide_public(pkg);
435
436  /* Otherwise, there has been a local change of some sort to the
437   * package.  Update the package if it is installed and we have a
438   * new version, but don't touch it otherwise.
439   */
440  if (pkg->instrev.present && pkg->newlistrev.present
441      && revcmp(&pkg->newlistrev, &pkg->instrev) > 0)
442    return UPDATE;
443
444  return NONE;
445}
446
447/* Read the header from an RPM file and add an update transaction for it. */
448static void schedule_update(struct package *pkg, rpmTransactionSet rpmdep)
449{
450  Header h;
451  FD_t fd;
452
453  assert(pkg->filename != NULL);
454  pkg->filename = fudge_arch_in_filename(pkg->filename);
455  fd = fdOpen(pkg->filename, O_RDONLY, 0);
456  if (fd == NULL)
457    die("Can't read package file %s", pkg->filename);
458  if (rpmReadPackageHeader(fd, &h, NULL, NULL, NULL) != 0)
459    die("Invalid rpm header in file %s", pkg->filename);
460  if (rpmtransAddPackage(rpmdep, h, NULL, pkg->filename, 1, NULL) != 0)
461    die("Can't install or update package %s", pkg->pkgname);
462  fdClose(fd);
463  headerFree(h);
464}
465
466static void display_action(struct package *pkg, enum act action)
467{
468  switch (action)
469    {
470    case ERASE:
471      printf("Erase package %s\n", pkg->pkgname);
472      break;
473    case UPDATE:
474      if (pkg->instrev.present)
475        {
476          printf("Update package %s from rev ", pkg->pkgname);
477          printrev(&pkg->instrev);
478          printf(" to rev ");
479          printrev(&pkg->newlistrev);
480          printf("\n");
481        }
482      else
483        {
484          printf("Install package %s at rev ", pkg->pkgname);
485          printrev(&pkg->newlistrev);
486          printf("\n");
487        }
488      break;
489    default:
490      break;
491    }
492}
493
494/* Red Hat's kernel package doesn't take care of lilo.conf; the update
495 * agent does.  So we have to take care of it too.
496 */
497static void update_lilo(struct package *pkg)
498{
499  const char *name = "/etc/lilo.conf", *savename = "/etc/lilo.conf.rpmsave";
500  FILE *in, *out;
501  char *buf = NULL, *oldkname, *newkname, *newiname, *initrdcmd;
502  const char *p;
503  int bufsize = 0, status, replaced;
504  struct stat statbuf;
505
506  /* For now, only act on updates. */
507  if (!pkg->instrev.present || !pkg->newlistrev.present
508      || revsame(&pkg->instrev, &pkg->newlistrev))
509    return;
510
511  /* Figure out kernel names. */
512  easprintf(&oldkname, "/boot/vmlinuz-%s-%s", pkg->instrev.version,
513            pkg->instrev.release);
514  easprintf(&newkname, "/boot/vmlinuz-%s-%s", pkg->newlistrev.version,
515            pkg->newlistrev.release);
516  easprintf(&newiname, "/boot/initrd-%s-%s.img", pkg->newlistrev.version,
517            pkg->newlistrev.release);
518  easprintf(&initrdcmd, "/sbin/mkinitrd -f /boot/initrd-%s-%s.img %s-%s",
519            pkg->newlistrev.version, pkg->newlistrev.release,
520            pkg->newlistrev.version, pkg->newlistrev.release);
521
522  if (stat(name, &statbuf) == -1)
523    die("Can't stat lilo.conf for rewrite.");
524
525  /* This isn't very atomic, but it's what Red Hat's update agent does. */
526  if (rename(name, savename) == -1)
527    die("Can't rename /etc/lilo.conf to /etc/lilo.conf.rpmsave.");
528  in = fopen(savename, "r");
529  out = fopen(name, "w");
530  if (!in || !out)
531    {
532      rename(savename, name);
533      die("Can't open lilo.conf files for rewrite.");
534    }
535  fchmod(fileno(out), statbuf.st_mode & 0777);
536
537  /* Rewrite lilo.conf, changing the old kernel name to the new kernel
538   * name in "image" lines.  Update "initrd" following images we
539   * replace, if it exists, for the benefit of machines that need it.
540   */
541  replaced = 0;
542  while ((status = read_line(in, &buf, &bufsize)) == 0)
543    {
544      p = buf;
545      while (isspace(*p))
546        p++;
547      if (strncmp(p, "image", 5) == 0 && !isalpha(p[5]))
548        {
549          p = strstr(buf, oldkname);
550          if (p != NULL)
551            {
552              fprintf(out, "%.*s%s%s\n", p - buf, buf, newkname,
553                      p + strlen(oldkname));
554              replaced = 1;
555              continue;
556            }
557          else
558            replaced = 0;
559        }
560      else if (replaced && strncmp(p, "initrd", 6) == 0 && !isalpha(p[6]))
561        {
562          p = skip_to_value(p, "initrd");
563          if (p != NULL)
564            {
565              fprintf(out, "%.*s%s\n", p - buf, buf, newiname);
566              if (system(initrdcmd) != 0)
567                fprintf(stderr, "Error running %s", initrdcmd);
568              continue;
569            }
570        }
571      fprintf(out, "%s\n", buf);
572    }
573  fclose(in);
574  if (status == -1 || ferror(out) || fclose(out) == EOF)
575    {
576      rename(savename, name);
577      die("Error rewriting lilo.conf: %s", strerror(errno));
578    }
579
580  free(oldkname);
581  free(newkname);
582  free(newiname);
583  free(initrdcmd);
584  system("/sbin/lilo");
585}
586
587/* If filename has an arch string which is too high for this machine's
588 * architecture, replace it with a new filename containing this
589 * machine's architecture.  filename must be an allocated string which
590 * can be freed and replaced.
591 */
592static char *fudge_arch_in_filename(char *filename)
593{
594  static const char *arches[] = { "i386", "i486", "i586", "i686", NULL };
595  const char *p;
596  int i, j, len;
597  struct utsname buf;
598  char *newfile;
599
600  /* Find the beginning of the arch string in filename. */
601  p = find_back(filename, filename + strlen(filename), '.');
602  assert(p != NULL);
603  p = find_back(filename, p, '.');
604  assert(p != NULL);
605  p++;
606
607  /* Locate this architecture in the array.  If it's not one we recognize,
608   * or if it's the least common denominator, leave well enough alone.
609   */
610  for (i = 0; arches[i] != NULL; i++)
611    {
612      len = strlen(arches[i]);
613      if (strncmp(p, arches[i], len) == 0 && *(p + len) == '.')
614        break;
615    }
616  if (i == 0 || arches[i] == NULL)
617    return filename;
618
619  /* Locate this machine's architecture in the array.  If we don't
620   * recognize it, or if it's at least as high as the filename's
621   * architecture, don't touch anything. */
622  assert(uname(&buf) == 0);
623  for (j = 0; arches[j] != NULL; j++)
624    {
625      if (strcmp(buf.machine, arches[j]) == 0)
626        break;
627    }
628  if (j >= i)
629    return filename;
630
631  /* We have to downgrade the architecture of the filename.  Make a
632   * new string and free the old one.
633   */
634  easprintf(&newfile, "%.*s%s%s", p - filename, filename, arches[j],
635            p + strlen(arches[i]));
636  free(filename);
637  return newfile;
638}
639
640static void printrev(struct rev *rev)
641{
642  assert(rev->present);
643  if (rev->epoch != -1)
644    printf("%d:", rev->epoch);
645  printf("%s-%s", rev->version, rev->release);
646}
647
648/* Compare two present revisions to see which is greater.  Returns 1 if
649 * rev1 is greater, -1 if rev2 is greater, or 0 if they are the same.
650 */
651static int revcmp(struct rev *rev1, struct rev *rev2)
652{
653  int r;
654
655  assert(rev1->present && rev2->present);
656  if (rev1->epoch != rev2->epoch)
657    return (rev1->epoch > rev2->epoch) ? 1 : -1;
658  else if ((r = rpmvercmp(rev1->version, rev2->version)) != 0)
659    return r;
660  else
661    return rpmvercmp(rev1->release, rev2->release);
662}
663
664/* Compare two revisions (either of which might not be present) to see
665 * if they're the same.
666 */
667static int revsame(struct rev *rev1, struct rev *rev2)
668{
669  if (rev1->present != rev2->present)
670    return 0;
671  else if (!rev1->present && !rev2->present)
672    return 1;
673  else
674    return (revcmp(rev1, rev2) == 0);
675}
676
677static void freerev(struct rev *rev)
678{
679  assert(rev->present);
680  free(rev->version);
681  free(rev->release);
682}
683
684/* Get package name and version information from a path line.  It
685 * would be more robust to look inside the package, but that's too
686 * inefficient when the packages live in AFS.  So assume that the
687 * trailing component of the path name is
688 * "pkgname-version-release.arch.rpm".  The RPM name may be followed
689 * by an epoch number if the RPM has one.
690 *
691 * The values set in *pkgname, *version, and *release are malloc'd and
692 * become the responsibility of the caller.
693 */
694static void parse_line(const char *line, char **pkgname, int *epoch,
695                       char **version, char **release, char **filename)
696{
697  const char *end, *p;
698  const char *pkgstart, *pkgend, *verstart, *verend, *relstart, *relend;
699
700  /* See if there's whitespace, which would be followed by an epoch. */
701  end = line;
702  while (*end && !isspace(*end))
703    end++;
704  if (*end)
705    {
706      p = end;
707      while (isspace(*p))
708        p++;
709      *epoch = atoi(p);
710    }
711  else
712    *epoch = -1;
713
714  /* The package name starts after the last slash in the path, if any. */
715  pkgstart = find_back(line, end, '/');
716  pkgstart = (pkgstart == NULL) ? line : pkgstart + 1;
717
718  /* The release string ends at the second to last period after pkgstart. */
719  relend = strrchr(pkgstart, '.');
720  if (!relend)
721    die("Found malformed RPM path line %s", line);
722  relend = find_back(pkgstart, relend, '.');
723  if (!relend)
724    die("Found malformed RPM path line %s", line);
725
726  /* The release string starts after the dash before relend. */
727  relstart = find_back(pkgstart, relend, '-');
728  if (!relstart)
729    die("Found malformed RPM path line %s", line);
730  relstart++;
731
732  /* The version string ends at that dash. */
733  verend = relstart - 1;
734
735  /* The version string starts after the dash before verend. */
736  verstart = find_back(pkgstart, verend, '-');
737  if (!verstart)
738    die("Found malformed RPM path line %s", line);
739  verstart++;
740
741  /* The package name ends at that dash. */
742  pkgend = verstart - 1;
743
744  *pkgname = estrndup(pkgstart, pkgend - pkgstart);
745  *version = estrndup(verstart, verend - verstart);
746  *release = estrndup(relstart, relend - relstart);
747  if (filename)
748    *filename = estrndup(line, end - line);
749}
750
751struct package *get_package(struct package **table, const char *pkgname)
752{
753  struct package **pkgptr, *pkg;
754
755  /* Look through the hash bucket for a package with the right name. */
756  for (pkgptr = &table[hash(pkgname)]; *pkgptr; pkgptr = &(*pkgptr)->next)
757    {
758      pkg = *pkgptr;
759      if (strcmp(pkg->pkgname, pkgname) == 0)
760        return pkg;
761    }
762
763  /* Create a new package and chain it in. */
764  pkg = emalloc(sizeof(struct package));
765  pkg->pkgname = estrdup(pkgname);
766  pkg->filename = NULL;
767  pkg->oldlistrev.present = 0;
768  pkg->newlistrev.present = 0;
769  pkg->instrev.present = 0;
770  pkg->erase = 0;
771  pkg->next = NULL;
772  *pkgptr = pkg;
773  return pkg;
774}
775
776/* Algorithm by Peter J. Weinberger.  Uses a hard-wired modulus for now. */
777static unsigned int hash(const char *str)
778{
779  unsigned int hashval, g;
780
781  hashval = 0;
782  for (; *str; str++)
783    {
784      hashval = (hashval << 4) + *str;
785      if ((g = hashval & 0xf0000000) != 0)
786        {
787          hashval ^= g >> 24;
788          hashval ^= g;
789        }
790    }
791  return hashval % HASHSIZE;
792}
793
794/* Find c in the range start..end-1, or return NULL if it isn't there. */
795static const char *find_back(const char *start, const char *end, char c)
796{
797  const char *p;
798
799  for (p = end - 1; p >= start; p--)
800    {
801      if (*p == c)
802        return p;
803    }
804  return NULL;
805}
806
807/* Assuming p points to a string beginning with varname, return a
808 * pointer to the value part.
809 */
810static const char *skip_to_value(const char *p, const char *varname)
811{
812  p += strlen(varname);
813  while (isspace(*p))
814    p++;
815  if (*p != '=')
816    return NULL;
817  p++;
818  while (isspace(*p))
819    p++;
820  return p;
821}
822
823static void *emalloc(size_t size)
824{
825  void *ptr;
826
827  ptr = malloc(size);
828  if (!ptr)
829    die("malloc size %lu failed", (unsigned long) size);
830  return ptr;
831}
832
833static void *erealloc(void *ptr, size_t size)
834{
835  ptr = realloc(ptr, size);
836  if (!ptr)
837    die("realloc size %lu failed", (unsigned long) size);
838  return ptr;
839}
840
841static char *estrdup(const char *s)
842{
843  char *new_s;
844
845  new_s = emalloc(strlen(s) + 1);
846  strcpy(new_s, s);
847  return new_s;
848}
849
850static char *estrndup(const char *s, size_t n)
851{
852  char *new_s;
853
854  new_s = emalloc(n + 1);
855  memcpy(new_s, s, n);
856  new_s[n] = 0;
857  return new_s;
858}
859
860static int easprintf(char **ptr, const char *fmt, ...)
861{
862  va_list ap;
863  int ret;
864
865  va_start(ap, fmt);
866  ret = vasprintf(ptr, fmt, ap);
867  va_end(ap);
868  if (ret == -1)
869    die("asprintf malloc failed for format string %s", fmt);
870  return ret;
871}
872
873/* Read a line from a file into a dynamically allocated buffer,
874 * zeroing the trailing newline if there is one.  The calling routine
875 * may call read_line multiple times with the same buf and bufsize
876 * pointers; *buf will be reallocated and *bufsize adjusted as
877 * appropriate.  The initial value of *buf should be NULL.  After the
878 * calling routine is done reading lines, it should free *buf.  This
879 * function returns 0 if a line was successfully read, 1 if the file
880 * ended, and -1 if there was an I/O error.
881 */
882
883static int read_line(FILE *fp, char **buf, int *bufsize)
884{
885  char *newbuf;
886  int offset = 0, len;
887
888  if (*buf == NULL)
889    {
890      *buf = emalloc(128);
891      *bufsize = 128;
892    }
893
894  while (1)
895    {
896      if (!fgets(*buf + offset, *bufsize - offset, fp))
897        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
898      len = offset + strlen(*buf + offset);
899      if ((*buf)[len - 1] == '\n')
900        {
901          (*buf)[len - 1] = 0;
902          return 0;
903        }
904      offset = len;
905
906      /* Allocate more space. */
907      newbuf = erealloc(*buf, *bufsize * 2);
908      *buf = newbuf;
909      *bufsize *= 2;
910    }
911}
912
913static void die(const char *fmt, ...)
914{
915  va_list ap;
916
917  fprintf(stderr, "%s: ", progname);
918  va_start(ap, fmt);
919  vfprintf(stderr, fmt, ap);
920  va_end(ap);
921  fprintf(stderr, "\n");
922  exit(1);
923}
924
925static void usage()
926{
927  fprintf(stderr, "Usage: %s [-p] oldlist newlist\n", progname);
928  exit(1);
929}
Note: See TracBrowser for help on using the repository browser.