source: trunk/athena/bin/getcluster/getcluster.c @ 20028

Revision 20028, 12.1 KB checked in by ghudson, 21 years ago (diff)
Ignore old clusterinfo records when AUTOUPDATE=true.
RevLine 
[19233]1#include <sys/types.h>
2#include <netinet/in.h>
3#include <arpa/inet.h>
[649]4#include <stdio.h>
[8517]5#include <stdlib.h>
[19233]6#include <stdarg.h>
7#include <string.h>
[649]8#include <ctype.h>
[10988]9#include <errno.h>
[11312]10#include <limits.h>
[13606]11#include <time.h>
[13627]12#include <unistd.h>
[10976]13#include <hesiod.h>
[649]14
[10229]15#ifndef INADDR_NONE
16#define INADDR_NONE ((unsigned long) -1)
17#endif
18
19/* Delay attaching new packs by up to four hours. */
20#define UPDATE_INTERVAL (3600 * 4)
21
[8517]22static void usage(void);
[19233]23static void die(const char *f, ...);
24static void shellenv(char **hp, const char *ws_version, int bourneshell);
[8527]25static void output_var(const char *var, const char *val, int bourneshell);
[8517]26static void upper(char *v);
[11312]27static char **readcluster(FILE *f);
28static char **merge(char **l1, char **l2);
[8517]29static int vercmp(const char *v1, const char *v2);
[11312]30static void *emalloc(size_t size);
[8517]31
[10976]32/* Make a hesiod cluster query for the machine you are on and produce a
33 * set of environment variable assignments for the C shell or the Bourne
34 * shell, depending on the '-b' flag
[11312]35 * If a localfile or a fallbackfile is specified, read cluster information
36 * from it as well. Variables in localfile override varibles obtained from
37 * Hesiod, and variables obtained from Hesiod override in fallbackfile.
38 * "Override" means that the presence of any instances of variable "foo"
39 * in one source will prevent any instances from a source being overridden.
40 * Example 1:
41 *   localfile: lpr myprinter
42 *   Hesiod:    syslib random-syspack-1 9.0
43 *              syslib random-syspack-2 9.1
44 *              syslib old-random-syspack
45 *   fallback:  syslib normal-syspack
[649]46 *
[11312]47 * lpr would be myprinter, and syslib would be negotiated among the three
48 * Hesiod entries.
49 *
50 * Example 2:
51 *   localfile: lpr myprinter
52 *              syslib new-spiffy-syspack
53 *   Hesiod:    syslib random-syspack-1 9.0
54 *              syslib random-syspack-2 9.1
55 *              syslib old-random-syspack
56 *   fallback:  syslib normal-syspack
57 *
58 * lpr would be myprinter, and syslib would be new-spiffy-syspack, regardless
59 * of the given version.
60 *
61 *
[10976]62 * If any stdio errors, truncate standard output to 0 and return an exit
63 * status.
[649]64 */
65
[8517]66int main(int argc, char **argv)
[649]67{
[19233]68  char **hp = NULL, **fp = NULL, **lp = NULL, **or1, **or2;
[10229]69  int debug = 0, bourneshell = 0, ch;
[19233]70  const char *fallbackfile = SYSCONFDIR "/cluster.fallback";
71  const char *localfile = SYSCONFDIR "/cluster.local";
72  const char *clusterfile = SYSCONFDIR "/cluster";
73  const char *hostname = NULL, *version;
74  char hostbuf[1024];
[11312]75  FILE *f;
[10976]76  void *hescontext;
[11312]77  extern int optind;
78  extern char *optarg;
[649]79
[19233]80  while ((ch = getopt(argc, argv, "bdl:f:h:")) != -1)
[10976]81    {
82      switch (ch)
83        {
84        case 'd':
85          debug = 1;
86          break;
87        case 'b':
88          bourneshell = 1;
89          break;
[11312]90        case 'f':
[19233]91          /* Deprecated option, for compatibility. */
[11312]92          fallbackfile = optarg;
93          break;
94        case 'l':
[19233]95          /* Deprecated option, for compatibility */
[11312]96          localfile = optarg;
97          break;
[19233]98        case 'h':
99          hostname = optarg;
100          break;
[10976]101        default:
102          usage();
103        }
[8402]104    }
[8517]105  argc -= optind;
106  argv += optind;
107
[19233]108  /* For compatibility, allow two arguments, but ignore the first one. */
109  if (argc != 1 && argc != 2)
[8517]110    usage();
[19233]111  version = (argc == 2) ? argv[1] : argv[0];
[8517]112
[19233]113  if (hostname == NULL)
[10976]114    {
[19233]115      f = fopen(clusterfile, "r");
[11392]116      if (f)
[10976]117        {
[19233]118          if (fgets(hostbuf, sizeof(hostbuf), f) == NULL)
119            die("Failed to read %s: %s", clusterfile, strerror(errno));
120          if (*hostbuf != '\0')
121            hostbuf[strlen(hostbuf) - 1] = '\0';
[11312]122          fclose(f);
123        }
[19233]124      else if (gethostname(hostbuf, sizeof(hostbuf)) != 0)
125        die("Can't get hostname: %s", strerror(errno));
126      hostname = hostbuf;
[10976]127    }
[11312]128
[19233]129  f = fopen(fallbackfile, "r");
130  if (f)
[10976]131    {
[19233]132      fp = readcluster(f);
133      fclose(f);
[11312]134    }
135
[19233]136  f = fopen(localfile, "r");
137  if (f)
138    {
139      lp = readcluster(f);
140      fclose(f);
141    }
142
[11312]143  if (debug)
144    {
145      /* Get clusterinfo records from standard input. */
146      hp = readcluster(stdin);
147    }
148  else
149    {
150      /* Get clusterinfo records from Hesiod. */
151      if (hesiod_init(&hescontext) != 0)
152        perror("hesiod_init");
153      else
[10976]154        {
[19233]155          hp = hesiod_resolve(hescontext, hostname, "cluster");
[11312]156          if (hp == NULL && errno != ENOENT)
157            perror("hesiod_resolve");
[10976]158        }
159    }
[8517]160
[11312]161  if (hp == NULL && lp == NULL && fp == NULL)
162    {
[19233]163      fprintf(stderr, "No cluster information available for %s\n", hostname);
[11312]164      return 2;
165    }
166
167  or1 = merge(lp, hp);
168  or2 = merge(or1, fp);
[19233]169  shellenv(or2, version, bourneshell);
[10976]170  if (!debug)
171    {
[11312]172      if (hp != NULL)
173        hesiod_free_list(hescontext, hp);
[10976]174      hesiod_end(hescontext);
175    }
[11312]176  /* We don't bother to free memory we know we allocated just before exiting;
177   * it's not worth the trouble. */
[10229]178  return (ferror(stdout)) ? 1 : 0;
[649]179}
180
[11312]181static char **merge(char **l1, char **l2)
182{
183  int size, point, i, j, ret, sizefroml1;
[13606]184  char **nl;
[11312]185  char var[256], compvar[256], dummy[256];
186
187  if (l1 == NULL)
188    return l2;
189  if (l2 == NULL)
190    return l1;
191
192  size = 1;
193  for (i = 0; l1[i] != NULL; i++)
194    size++;
195  for (i = 0; l2[i] != NULL; i++)
196    size++;
197
198  nl = emalloc(sizeof(char *) * size);
199  point = 0;
200
201  /* Copy l1 to nl. */
202  for (i = 0; l1[i] != NULL; i++)
203    {
204      ret = sscanf(l1[i], "%s %s", var, dummy);
205      if (ret == 2) /* Ignore invalid lines. */
206        {
207          nl[point] = l1[i];
208          point++;
209        }
210    }
211  sizefroml1 = point;
212
213  /* For each entry in l2, add it to nl if nothing in l1 has that var. */
214  for (i = 0; l2[i] != NULL; i++)
215    {
216      ret = sscanf(l2[i], "%s %s", var, dummy);
217      if (ret < 2)
218        continue; /* Ignore invalid lines. */
219      for (j = 0; j < sizefroml1; j++)
220        {
221          sscanf(nl[j], "%s", compvar);
222          if (strcmp(var, compvar) == 0)
223              break;
224        }
225      if (j == sizefroml1)
226        {
227          nl[point] = l2[i];
228          point++;
229        }
230    }
231  nl[point] = NULL;
232  return nl;
233}
234
[13606]235static void usage(void)
[11312]236{
[19233]237  die("Usage: getcluster [-h hostname] [-b] [-d] version");
[8517]238  exit(1);
239}
[649]240
[19233]241static void die(const char *fmt, ...)
242{
243  va_list ap;
244
245  va_start(ap, fmt);
246  vfprintf(stderr, fmt, ap);
247  putc('\n', stderr);
248  exit(1);
249}
250
[10229]251/* Cluster records will come in entries of the form:
[8517]252 *
[10229]253 *      variable value [version [flags]]
[8517]254 *
[10229]255 * There may be multiple records for the same variable, but not
256 * multiple entries with the same variable and version.  There may be
257 * at most one entry for a given variable that does not list a
[20028]258 * version.  Records with a version less than the current workstation
259 * version are ignored.
[8517]260 *
[10229]261 * Discard records if they have a version greater than the current
262 * workstation version and any of the following is true:
263 *      - They have 't' listed in the flags.
264 *      - AUTOUPDATE is false.
265 *      - The environment variable UPDATE_TIME doesn't exist or
266 *        specifies a time later than the current time.
267 * Set NEW_TESTING_RELEASE if any records are discarded for the first
268 * reason, NEW_PRODUCTION_RELEASE if any records are discarded for
269 * the second reason, and UPDATE_TIME if any entries specify a version
270 * greater than the current workstation version and are not discarded
271 * for the first two reasons.
272 *
273 * After discarding records, output the variable definition with the
274 * highest version number.  If there aren't any records left with
275 * version numbers and there's one with no version number, output
276 * that one. */
[19233]277static void shellenv(char **hp, const char *ws_version, int bourneshell)
[8517]278{
[10229]279  int *seen, count, i, j, output_time = 0, autoupdate = 0;
[8517]280  char var[80], val[80], vers[80], flags[80], compvar[80], compval[80];
[8527]281  char compvers[80], defaultval[80], new_production[80], new_testing[80];
[10229]282  char timebuf[32], *envp;
283  time_t update_time = -1, now;
284  unsigned long ip = INADDR_NONE;
[8517]285
[10229]286  time(&now);
287
288  /* Gather information from the environment.  UPDATE_TIME comes from
289   * the clusterinfo file we wrote out last time; AUTOUPDATE and ADDR
290   * come from rc.conf.  If neither file has been sourced by the
291   * caller, we just use defaults. */
292  envp = getenv("UPDATE_TIME");
293  if (envp)
294    update_time = atoi(envp);
295  envp = getenv("AUTOUPDATE");
296  if (envp && strcmp(envp, "true") == 0)
297    autoupdate = 1;
298  envp = getenv("ADDR");
299  if (envp)
300    ip = inet_addr(envp);
301
[8517]302  count = 0;
303  while (hp[count])
304    count++;
305
306  seen = (int *) calloc(count, sizeof(int));
307  if (seen == NULL)
308    exit(1);
309
[8527]310  strcpy(new_production, "0.0");
311  strcpy(new_testing, "0.0");
312
[10229]313  /* The outer loop is for the purpose of "considering each variable."
314   * We skip entries for variables which had a previous entry. */
[8517]315  for (i = 0; i < count; i++)
[8402]316    {
[8517]317      if (seen[i])
318        continue;
319      sscanf(hp[i], "%s", var);
320
321      /* Consider each entry for this variable (including hp[i]). */
322      strcpy(vers, "0.0");
323      *defaultval = 0;
324      for (j = i; j < count; j++)
[8402]325        {
[8517]326          *compvers = *flags = 0;
327          sscanf(hp[j], "%s %s %s %s", compvar, compval, compvers, flags);
328          if (strcmp(compvar, var) != 0)
[8402]329            continue;
[8517]330          seen[j] = 1;
331
[10229]332          /* If there's no version, keep this as the default value in
333           * case we don't come up with anything else to print.  If
334           * there is a version, discard it if it doesn't match the
335           * current workstation version and (a) we're not autoupdate,
336           * or (b) it's a testing version.  If we do consider the
337           * entry, and its version is greater than the current best
338           * version we have, update the current best version and its
[8517]339           * value. */
340          if (!*compvers)
[11312]341            strcpy(defaultval, compval);
[20028]342          else if (vercmp(compvers, ws_version) < 0)
343            continue;
[8517]344          else if (((autoupdate && !strchr(flags, 't')) ||
[8527]345                    (vercmp(compvers, ws_version) == 0)))
[8517]346            {
[10229]347              if (vercmp(compvers, ws_version) > 0)
348                {
349                  /* We want to take this value, but not necessarily
350                   * right away.  Accept the record only if we have an
351                   * update time which has already passed.  Make
352                   * a note that we should output the time
353                   * whether or not we discard the entry, since the
354                   * workstation is out of date either way. */
355                  output_time = 1;
356                  if (update_time == -1 || now < update_time)
357                    continue;
358                }
359
[8527]360              if (vercmp(compvers, vers) >= 0)
361                {
362                  strcpy(val, compval);
363                  strcpy(vers, compvers);
364                }
[8517]365            }
[8527]366          else
367            {
368              /* Discard this entry, but record most recent testing and
369               * production releases. */
370              if (strchr(flags, 't') && vercmp(compvers, new_testing) > 0)
371                strcpy(new_testing, compvers);
372              if (!strchr(flags, 't') && vercmp(compvers, new_production) > 0)
373                strcpy(new_production, compvers);
374            }
[8517]375        }
376      if (*vers != '0' || *defaultval)
377        {
378          if (*vers == '0')
379            strcpy(val, defaultval);
[8402]380          upper(var);
[8527]381          output_var(var, val, bourneshell);
[1446]382        }
[8517]383    }
[8402]384
[8527]385  if (vercmp(new_testing, ws_version) > 0)
386    output_var("NEW_TESTING_RELEASE", new_testing, bourneshell);
387  if (vercmp(new_production, ws_version) > 0)
388    output_var("NEW_PRODUCTION_RELEASE", new_production, bourneshell);
[10229]389  if (output_time)
390    {
391      /* If we have no time from the environment, make up one
392       * between now and UPDATE_INTERVAL seconds in the future. */
393      if (update_time == -1)
394        {
395          srand(ntohl(ip));
396          update_time = now + rand() % UPDATE_INTERVAL;
397        }
398      sprintf(timebuf, "%lu", update_time);
399      output_var("UPDATE_TIME", timebuf, bourneshell);
400    }
[8527]401  free(seen);
[649]402}
403
[8527]404static void output_var(const char *var, const char *val, int bourneshell)
405{
406  if (bourneshell)
[14871]407    printf("%s=%s ; export %s ;\n", var, val, var);
[8527]408  else
[14871]409    printf("setenv %s %s ;\n", var, val);
[8527]410}
411
[8517]412static void upper(char *v)
[649]413{
[8402]414  while(*v)
415    {
416      *v = toupper(*v);
417      v++;
418    }
[649]419}
[8402]420
[8517]421static int vercmp(const char *v1, const char *v2)
[8402]422{
[8517]423  int major1 = 0, minor1 = 0, major2 =0, minor2 = 0;
424
425  sscanf(v1, "%d.%d", &major1, &minor1);
426  sscanf(v2, "%d.%d", &major2, &minor2);
[10976]427  return (major1 != major2) ? (major1 - major2) : (minor1 - minor2);
[8402]428}
[11312]429
430static char **readcluster(FILE *f)
431{
432  char line[1024];
433  char **lp;
434  int nl, al;
435
436  nl = 0;
437  al = 10;
438  lp = emalloc(al * sizeof(char *));
439
440  lp[0] = NULL;
441  while (fgets(line, 1024, f) != NULL)
442    {
443      if (nl + 1 == al)
444        {
445          al = al * 2;
446          lp = realloc(lp, al * sizeof(char *));
447          if (lp == NULL)
448            {
449              fprintf(stderr, "Out of memory.");
450              exit(1);
451            }
452        }
453      lp[nl] = strdup(line);
454      if (lp[nl] == NULL)
455        {
456          fprintf(stderr, "Out of memory.");
457          exit(1);
458        }
459      nl++;
460    }
461  lp[nl] = NULL;
462
463  return lp;
464}
465
466static void *emalloc(size_t size)
467{
468  void *p;
469
470  p = malloc(size);
471  if (p == NULL)
472    {
473      fprintf(stderr, "Out of memory.\n");
474      exit(1);
475    }
476  return p;
477}
Note: See TracBrowser for help on using the repository browser.