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

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