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

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