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

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