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

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