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

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