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

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