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

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