http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.32.tar.gz
[xscreensaver] / driver / prefs.c
1 /* dotfile.c --- management of the ~/.xscreensaver file.
2  * xscreensaver, Copyright (c) 1998 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdlib.h>
18
19 #ifdef HAVE_UNISTD_H
20 # include <unistd.h>
21 #endif
22
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28
29 #include <X11/Xlib.h>
30 #include <X11/Xresource.h>
31
32 #ifndef VMS
33 # include <pwd.h>
34 #else /* VMS */
35 # include "vms-pwd.h"
36 #endif /* VMS */
37
38
39 /* This file doesn't need the Xt headers, so stub these types out... */
40 #undef XtPointer
41 #define XtAppContext void*
42 #define XtIntervalId void*
43 #define XtPointer    void*
44 #define Widget       void*
45
46
47 /* Just in case there's something pathological about stat.h... */
48 #ifndef  S_IRUSR
49 # define S_IRUSR 00400
50 #endif
51 #ifndef  S_IWUSR
52 # define S_IWUSR 00200
53 #endif
54 #ifndef  S_IXUSR
55 # define S_IXUSR 00100
56 #endif
57 #ifndef  S_IXGRP
58 # define S_IXGRP 00010
59 #endif
60 #ifndef  S_IXOTH
61 # define S_IXOTH 00001
62 #endif
63
64
65 #include "prefs.h"
66 #include "resources.h"
67
68
69 extern char *progname;
70 extern char *progclass;
71 extern const char *blurb (void);
72
73
74
75 static void get_screenhacks (saver_preferences *p);
76 static char *format_command (const char *cmd, Bool wrap_p);
77 static void merge_system_screenhacks (saver_preferences *p,
78                                       screenhack **system_list, int count);
79
80 static char *
81 chase_symlinks (const char *file)
82 {
83 # ifdef HAVE_REALPATH
84   if (file)
85     {
86       char buf [2048];
87       if (realpath (file, buf))
88         return strdup (buf);
89
90       sprintf (buf, "%s: realpath", blurb());
91       perror(buf);
92     }
93 # endif /* HAVE_REALPATH */
94   return 0;
95 }
96
97
98 static Bool
99 i_am_a_nobody (uid_t uid)
100 {
101   struct passwd *p;
102
103   p = getpwnam ("nobody");
104   if (! p) p = getpwnam ("noaccess");
105   if (! p) p = getpwnam ("daemon");
106
107   if (! p) /* There is no nobody? */
108     return False;
109
110   return (uid == p->pw_uid);
111 }
112
113
114 const char *
115 init_file_name (void)
116 {
117   static char *file = 0;
118
119   if (!file)
120     {
121       uid_t uid = getuid ();
122       struct passwd *p = getpwuid (uid);
123
124       if (i_am_a_nobody (uid))
125         /* If we're running as nobody, then use root's .xscreensaver file
126            (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
127            to be different -- if we didn't do this, then xscreensaver-demo
128            would appear to have no effect when the luser is running as root.)
129          */
130         uid = 0;
131
132       p = getpwuid (uid);
133
134       if (!p || !p->pw_name || !*p->pw_name)
135         {
136           fprintf (stderr, "%s: couldn't get user info of uid %d\n",
137                    blurb(), getuid ());
138           file = "";
139         }
140       else if (!p->pw_dir || !*p->pw_dir)
141         {
142           fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
143                    blurb(), (p->pw_name ? p->pw_name : "???"));
144           file = "";
145         }
146       else
147         {
148           const char *home = p->pw_dir;
149           const char *name = ".xscreensaver";
150           file = (char *) malloc(strlen(home) + strlen(name) + 2);
151           strcpy(file, home);
152           if (!*home || home[strlen(home)-1] != '/')
153             strcat(file, "/");
154           strcat(file, name);
155         }
156     }
157
158   if (file && *file)
159     return file;
160   else
161     return 0;
162 }
163
164
165 static const char *
166 init_file_tmp_name (void)
167 {
168   static char *file = 0;
169   if (!file)
170     {
171       const char *name = init_file_name();
172       const char *suffix = ".tmp";
173
174       char *n2 = chase_symlinks (name);
175       if (n2) name = n2;
176
177       if (!name || !*name)
178         file = "";
179       else
180         {
181           file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
182           strcpy(file, name);
183           strcat(file, suffix);
184         }
185
186       if (n2) free (n2);
187     }
188
189   if (file && *file)
190     return file;
191   else
192     return 0;
193 }
194
195
196 static const char * const prefs[] = {
197   "timeout",
198   "cycle",
199   "lock",
200   "lockVTs",
201   "lockTimeout",
202   "passwdTimeout",
203   "visualID",
204   "installColormap",
205   "verbose",
206   "timestamp",
207   "splash",
208   "splashDuration",
209   "demoCommand",
210   "prefsCommand",
211   "helpURL",
212   "loadURL",
213   "nice",
214   "fade",
215   "unfade",
216   "fadeSeconds",
217   "fadeTicks",
218   "captureStderr",
219   "captureStdout",              /* not saved -- obsolete */
220   "font",
221   "dpmsEnabled",
222   "dpmsStandby",
223   "dpmsSuspend",
224   "dpmsOff",
225   "grabDesktopImages",
226   "grabVideoFrames",
227   "chooseRandomImages",
228   "imageDirectory",
229   "",
230   "programs",
231   "",
232   "pointerPollTime",
233   "windowCreationTimeout",
234   "initialDelay",
235   "sgiSaverExtension",
236   "mitSaverExtension",
237   "xidleExtension",
238   "procInterrupts",
239   "overlayStderr",
240   "overlayTextBackground",      /* not saved -- X resources only */
241   "overlayTextForeground",      /* not saved -- X resources only */
242   "bourneShell",                /* not saved -- X resources only */
243   0
244 };
245
246 static char *
247 strip (char *s)
248 {
249   char *s2;
250   while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
251     s++;
252   for (s2 = s; *s2; s2++)
253     ;
254   for (s2--; s2 >= s; s2--) 
255     if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
256       *s2 = 0;
257     else
258       break;
259   return s;
260 }
261
262 \f
263 /* Reading
264  */
265
266 static int
267 handle_entry (XrmDatabase *db, const char *key, const char *value,
268               const char *filename, int line)
269 {
270   int i;
271   for (i = 0; prefs[i]; i++)
272     if (*prefs[i] && !strcasecmp(key, prefs[i]))
273       {
274         char *val = strdup(value);
275         char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
276         strcpy(spec, progclass);
277         strcat(spec, ".");
278         strcat(spec, prefs[i]);
279
280         XrmPutStringResource (db, spec, val);
281
282         free(spec);
283         free(val);
284         return 0;
285       }
286
287   fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
288           blurb(), filename, line, key);
289   return 1;
290 }
291
292
293 static int
294 parse_init_file (saver_preferences *p)
295 {
296   time_t write_date = 0;
297   const char *name = init_file_name();
298   int line = 0;
299   struct stat st;
300   FILE *in;
301   int buf_size = 1024;
302   char *buf;
303
304   if (!name) return 0;
305
306   if (stat(name, &st) != 0)
307     {
308       p->init_file_date = 0;
309       return 0;
310     }
311
312   in = fopen(name, "r");
313   if (!in)
314     {
315       char *buf = (char *) malloc(1024 + strlen(name));
316       sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
317       perror(buf);
318       free(buf);
319       return -1;
320     }
321
322   if (fstat (fileno(in), &st) == 0)
323     {
324       write_date = st.st_mtime;
325     }
326   else
327     {
328       char *buf = (char *) malloc(1024 + strlen(name));
329       sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
330       perror(buf);
331       free(buf);
332       return -1;
333     }
334
335   buf = (char *) malloc(buf_size);
336
337   while (fgets (buf, buf_size-1, in))
338     {
339       char *key, *value;
340       int L = strlen(buf);
341
342       line++;
343       while (L > 2 &&
344              (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
345               buf[L-2] == '\\'))        /* or line ended with backslash */
346         {
347           if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
348             {
349               buf[L-2] = 0;
350               L -= 2;
351             }
352           buf_size += 1024;
353           buf = (char *) realloc(buf, buf_size);
354           if (!buf) exit(1);
355
356           line++;
357           if (!fgets (buf + L, buf_size-L-1, in))
358             break;
359           L = strlen(buf);
360         }
361
362       /* Now handle other backslash escapes. */
363       {
364         int i, j;
365         for (i = 0; buf[i]; i++)
366           if (buf[i] == '\\')
367             {
368               switch (buf[i+1])
369                 {
370                 case 'n': buf[i] = '\n'; break;
371                 case 'r': buf[i] = '\r'; break;
372                 case 't': buf[i] = '\t'; break;
373                 default:  buf[i] = buf[i+1]; break;
374                 }
375               for (j = i+2; buf[j]; j++)
376                 buf[j-1] = buf[j];
377               buf[j-1] = 0;
378             }
379       }
380
381       key = strip(buf);
382
383       if (*key == '#' || *key == '!' || *key == ';' ||
384           *key == '\n' || *key == 0)
385         continue;
386
387       value = strchr (key, ':');
388       if (!value)
389         {
390           fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
391                   name, line, key);
392           continue;
393         }
394       else
395         {
396           *value++ = 0;
397           value = strip(value);
398         }
399
400       if (!p->db) abort();
401       handle_entry (&p->db, key, value, name, line);
402     }
403   fclose (in);
404   free(buf);
405
406   p->init_file_date = write_date;
407   return 0;
408 }
409
410
411 Bool
412 init_file_changed_p (saver_preferences *p)
413 {
414   const char *name = init_file_name();
415   struct stat st;
416
417   if (!name) return False;
418
419   if (stat(name, &st) != 0)
420     return False;
421
422   if (p->init_file_date == st.st_mtime)
423     return False;
424
425   return True;
426 }
427
428 \f
429 /* Writing
430  */
431
432 static int
433 tab_to (FILE *out, int from, int to)
434 {
435   int tab_width = 8;
436   int to_mod = (to / tab_width) * tab_width;
437   while (from < to_mod)
438     {
439       fprintf(out, "\t");
440       from = (((from / tab_width) + 1) * tab_width);
441     }
442   while (from < to)
443     {
444       fprintf(out, " ");
445       from++;
446     }
447   return from;
448 }
449
450 static char *
451 stab_to (char *out, int from, int to)
452 {
453   int tab_width = 8;
454   int to_mod = (to / tab_width) * tab_width;
455   while (from < to_mod)
456     {
457       *out++ = '\t';
458       from = (((from / tab_width) + 1) * tab_width);
459     }
460   while (from < to)
461     {
462       *out++ = ' ';
463       from++;
464     }
465   return out;
466 }
467
468 static int
469 string_columns (const char *string, int length, int start)
470 {
471   int tab_width = 8;
472   int col = start;
473   const char *end = string + length;
474   while (string < end)
475     {
476       if (*string == '\n')
477         col = 0;
478       else if (*string == '\t')
479         col = (((col / tab_width) + 1) * tab_width);
480       else
481         col++;
482       string++;
483     }
484   return col;
485 }
486
487
488 static void
489 write_entry (FILE *out, const char *key, const char *value)
490 {
491   char *v = strdup(value ? value : "");
492   char *v2 = v;
493   char *nl = 0;
494   int col;
495   Bool programs_p = (!strcmp(key, "programs"));
496   int tab = (programs_p ? 32 : 16);
497   Bool first = True;
498
499   fprintf(out, "%s:", key);
500   col = strlen(key) + 1;
501
502   if (strlen(key) > 14)
503     col = tab_to (out, col, 20);
504
505   while (1)
506     {
507       if (!programs_p)
508         v2 = strip(v2);
509       nl = strchr(v2, '\n');
510       if (nl)
511         *nl = 0;
512
513       if (first && programs_p)
514         {
515           col = tab_to (out, col, 77);
516           fprintf (out, " \\\n");
517           col = 0;
518         }
519
520       if (first)
521         first = False;
522       else
523         {
524           col = tab_to (out, col, 75);
525           fprintf (out, " \\n\\\n");
526           col = 0;
527         }
528
529       if (!programs_p)
530         col = tab_to (out, col, tab);
531
532       if (programs_p &&
533           string_columns(v2, strlen (v2), col) + col > 75)
534         {
535           int L = strlen (v2);
536           int start = 0;
537           int end = start;
538           while (start < L)
539             {
540               while (v2[end] == ' ' || v2[end] == '\t')
541                 end++;
542               while (v2[end] != ' ' && v2[end] != '\t' &&
543                      v2[end] != '\n' && v2[end] != 0)
544                 end++;
545               if (string_columns (v2 + start, (end - start), col) >= 74)
546                 {
547                   col = tab_to (out, col, 75);
548                   fprintf(out, "   \\\n");
549                   col = tab_to (out, 0, tab + 2);
550                   while (v2[start] == ' ' || v2[start] == '\t')
551                     start++;
552                 }
553
554               col = string_columns (v2 + start, (end - start), col);
555               while (start < end)
556                 fputc(v2[start++], out);
557             }
558         }
559       else
560         {
561           fprintf (out, "%s", v2);
562           col += string_columns(v2, strlen (v2), col);
563         }
564
565       if (nl)
566         v2 = nl + 1;
567       else
568         break;
569     }
570
571   fprintf(out, "\n");
572   free(v);
573 }
574
575 int
576 write_init_file (saver_preferences *p, const char *version_string,
577                  Bool verbose_p)
578 {
579   int status = -1;
580   const char *name = init_file_name();
581   const char *tmp_name = init_file_tmp_name();
582   char *n2 = chase_symlinks (name);
583   struct stat st;
584   int i, j;
585
586   /* Kludge, since these aren't in the saver_preferences struct as strings...
587    */
588   char *visual_name;
589   char *programs;
590   Bool overlay_stderr_p;
591   char *stderr_font;
592   FILE *out;
593
594   if (!name) goto END;
595
596   if (n2) name = n2;
597
598   if (verbose_p)
599     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
600
601   unlink (tmp_name);
602   out = fopen(tmp_name, "w");
603   if (!out)
604     {
605       char *buf = (char *) malloc(1024 + strlen(name));
606       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
607       perror(buf);
608       free(buf);
609       goto END;
610     }
611
612   /* Give the new .xscreensaver file the same permissions as the old one;
613      except ensure that it is readable and writable by owner, and not
614      executable.  Extra hack: if we're running as root, make the file
615      be world-readable (so that the daemon, running as "nobody", will
616      still be able to read it.)
617    */
618   if (stat(name, &st) == 0)
619     {
620       mode_t mode = st.st_mode;
621       mode |= S_IRUSR | S_IWUSR;                /* read/write by user */
622       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);   /* executable by none */
623
624       if (getuid() == (uid_t) 0)                /* read by group/other */
625         mode |= S_IRGRP | S_IROTH;
626
627       if (fchmod (fileno(out), mode) != 0)
628         {
629           char *buf = (char *) malloc(1024 + strlen(name));
630           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
631                    tmp_name, (unsigned int) mode);
632           perror(buf);
633           free(buf);
634           goto END;
635         }
636     }
637
638   /* Kludge, since these aren't in the saver_preferences struct... */
639   visual_name = get_string_resource ("visualID", "VisualID");
640   programs = 0;
641   overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
642   stderr_font = get_string_resource ("font", "Font");
643
644   i = 0;
645   {
646     char *ss;
647     char **hack_strings = (char **)
648       calloc (p->screenhacks_count, sizeof(char *));
649
650     for (j = 0; j < p->screenhacks_count; j++)
651       {
652         hack_strings[j] = format_hack (p->screenhacks[j], True);
653         i += strlen (hack_strings[j]);
654         i += 2;
655       }
656
657     ss = programs = (char *) malloc(i + 10);
658     *ss = 0;
659     for (j = 0; j < p->screenhacks_count; j++)
660       {
661         strcat (ss, hack_strings[j]);
662         free (hack_strings[j]);
663         ss += strlen(ss);
664         *ss++ = '\n';
665         *ss = 0;
666       }
667   }
668
669   {
670     struct passwd *pw = getpwuid (getuid ());
671     char *whoami = (pw && pw->pw_name && *pw->pw_name
672                     ? pw->pw_name
673                     : "<unknown>");
674     time_t now = time ((time_t *) 0);
675     char *timestr = (char *) ctime (&now);
676     char *nl = (char *) strchr (timestr, '\n');
677     if (nl) *nl = 0;
678     fprintf (out,
679              "# %s Preferences File\n"
680              "# Written by %s %s for %s on %s.\n"
681              "# http://www.jwz.org/xscreensaver/\n"
682              "\n",
683              progclass, progname, version_string, whoami, timestr);
684   }
685
686   for (j = 0; prefs[j]; j++)
687     {
688       char buf[255];
689       const char *pr = prefs[j];
690       enum pref_type { pref_str, pref_int, pref_bool, pref_time
691       } type = pref_str;
692       const char *s = 0;
693       int i = 0;
694       Bool b = False;
695       Time t = 0;
696
697       if (pr && !*pr)
698         {
699           fprintf(out, "\n");
700           continue;
701         }
702
703 # undef CHECK
704 # define CHECK(X) else if (!strcmp(pr, X))
705       if (!pr || !*pr)          ;
706       CHECK("timeout")          type = pref_time, t = p->timeout;
707       CHECK("cycle")            type = pref_time, t = p->cycle;
708       CHECK("lock")             type = pref_bool, b = p->lock_p;
709 # if 0 /* #### not ready yet */
710       CHECK("lockVTs")          type = pref_bool, b = p->lock_vt_p;
711 # else
712       CHECK("lockVTs")          continue;  /* don't save */
713 # endif
714       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
715       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
716       CHECK("visualID")         type = pref_str,  s =    visual_name;
717       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
718       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
719       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
720       CHECK("splash")           type = pref_bool, b = p->splash_p;
721       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
722       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
723       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
724       CHECK("helpURL")          type = pref_str,  s = p->help_url;
725       CHECK("loadURL")          type = pref_str,  s = p->load_url_command;
726       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
727       CHECK("fade")             type = pref_bool, b = p->fade_p;
728       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
729       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
730       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
731       CHECK("captureStderr")    type = pref_bool, b = p->capture_stderr_p;
732       CHECK("captureStdout")    continue;  /* don't save */
733       CHECK("font")             type = pref_str,  s =    stderr_font;
734
735       CHECK("dpmsEnabled")      type = pref_bool, b = p->dpms_enabled_p;
736       CHECK("dpmsStandby")      type = pref_time, t = p->dpms_standby;
737       CHECK("dpmsSuspend")      type = pref_time, t = p->dpms_suspend;
738       CHECK("dpmsOff")          type = pref_time, t = p->dpms_off;
739
740       CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
741       CHECK("grabVideoFrames")   type =pref_bool, b = p->grab_video_p;
742       CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
743       CHECK("imageDirectory")    type =pref_str,  s = p->image_directory;
744
745       CHECK("programs")         type = pref_str,  s =    programs;
746       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
747       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
748       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
749       CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
750       CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
751       CHECK("xidleExtension")   type = pref_bool, b = p->use_xidle_extension;
752       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
753       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
754       CHECK("overlayTextBackground") continue;  /* don't save */
755       CHECK("overlayTextForeground") continue;  /* don't save */
756       CHECK("bourneShell")      continue;
757       else                      abort();
758 # undef CHECK
759
760       switch (type)
761         {
762         case pref_str:
763           break;
764         case pref_int:
765           sprintf(buf, "%d", i);
766           s = buf;
767           break;
768         case pref_bool:
769           s = b ? "True" : "False";
770           break;
771         case pref_time:
772           {
773             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
774             if (sec >= 60)
775               {
776                 min += (sec / 60);
777                 sec %= 60;
778               }
779             if (min >= 60)
780               {
781                 hour += (min / 60);
782                 min %= 60;
783               }
784             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
785             s = buf;
786           }
787           break;
788         default:
789           abort();
790           break;
791         }
792       write_entry (out, pr, s);
793     }
794
795   fprintf(out, "\n");
796
797   if (visual_name) free(visual_name);
798   if (stderr_font) free(stderr_font);
799   if (programs) free(programs);
800
801   if (fclose(out) == 0)
802     {
803       time_t write_date = 0;
804
805       if (stat(tmp_name, &st) == 0)
806         {
807           write_date = st.st_mtime;
808         }
809       else
810         {
811           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
812           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
813           perror(buf);
814           unlink (tmp_name);
815           free(buf);
816           goto END;
817         }
818
819       if (rename (tmp_name, name) != 0)
820         {
821           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
822           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
823                   blurb(), tmp_name, name);
824           perror(buf);
825           unlink (tmp_name);
826           free(buf);
827           goto END;
828         }
829       else
830         {
831           p->init_file_date = write_date;
832
833           /* Since the .xscreensaver file is used for IPC, let's try and make
834              sure that the bits actually land on the disk right away. */
835           sync ();
836
837           status = 0;    /* wrote and renamed successfully! */
838         }
839     }
840   else
841     {
842       char *buf = (char *) malloc(1024 + strlen(name));
843       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
844       perror(buf);
845       free(buf);
846       unlink (tmp_name);
847       goto END;
848     }
849
850  END:
851   if (n2) free (n2);
852   return status;
853 }
854
855 \f
856 /* Parsing the resource database
857  */
858
859 void
860 free_screenhack (screenhack *hack)
861 {
862   if (hack->visual) free (hack->visual);
863   if (hack->name) free (hack->name);
864   free (hack->command);
865   memset (hack, 0, sizeof(*hack));
866   free (hack);
867 }
868
869 static void
870 free_screenhack_list (screenhack **list, int count)
871 {
872   int i;
873   if (!list) return;
874   for (i = 0; i < count; i++)
875     if (list[i])
876       free_screenhack (list[i]);
877   free (list);
878 }
879
880
881 /* Populate `saver_preferences' with the contents of the resource database.
882    Note that this may be called multiple times -- it is re-run each time
883    the ~/.xscreensaver file is reloaded.
884
885    This function can be very noisy, since it issues resource syntax errors
886    and so on.
887  */
888 void
889 load_init_file (saver_preferences *p)
890 {
891   static Bool first_time = True;
892   
893   screenhack **system_default_screenhacks = 0;
894   int system_default_screenhack_count = 0;
895
896   if (first_time)
897     {
898       /* Get the programs resource before the .xscreensaver file has been
899          parsed and merged into the resource database for the first time:
900          this is the value of *programs from the app-defaults file.
901          Then clear it out so that it will be parsed again later, after
902          the init file has been read.
903        */
904       get_screenhacks (p);
905       system_default_screenhacks = p->screenhacks;
906       system_default_screenhack_count = p->screenhacks_count;
907       p->screenhacks = 0;
908       p->screenhacks_count = 0;
909     }
910
911   if (parse_init_file (p) != 0)         /* file might have gone away */
912     if (!first_time) return;
913
914   first_time = False;
915
916   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
917   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
918   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
919   p->lock_p         = get_boolean_resource ("lock", "Boolean");
920   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
921   p->fade_p         = get_boolean_resource ("fade", "Boolean");
922   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
923   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
924   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
925   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
926   p->nice_inferior  = get_integer_resource ("nice", "Nice");
927   p->splash_p       = get_boolean_resource ("splash", "Boolean");
928   p->capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
929
930   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
931   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
932   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
933   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
934   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
935   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
936   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
937   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
938                                                        "Time");
939
940   p->dpms_enabled_p  = get_boolean_resource ("dpmsEnabled", "Boolean");
941   p->dpms_standby    = 1000 * get_minutes_resource ("dpmsStandby", "Time");
942   p->dpms_suspend    = 1000 * get_minutes_resource ("dpmsSuspend", "Time");
943   p->dpms_off        = 1000 * get_minutes_resource ("dpmsOff",     "Time");
944
945   p->grab_desktop_p  = get_boolean_resource ("grabDesktopImages",  "Boolean");
946   p->grab_video_p    = get_boolean_resource ("grabVideoFrames",    "Boolean");
947   p->random_image_p  = get_boolean_resource ("chooseRandomImages", "Boolean");
948   p->image_directory = get_string_resource  ("imageDirectory",
949                                              "ImageDirectory");
950
951   p->shell = get_string_resource ("bourneShell", "BourneShell");
952
953   p->demo_command = get_string_resource("demoCommand", "URL");
954   p->prefs_command = get_string_resource("prefsCommand", "URL");
955   p->help_url = get_string_resource("helpURL", "URL");
956   p->load_url_command = get_string_resource("loadURL", "LoadURL");
957
958
959   /* If "*splash" is unset, default to true. */
960   {
961     char *s = get_string_resource ("splash", "Boolean");
962     if (s)
963       free (s);
964     else
965       p->splash_p = True;
966   }
967
968   /* If "*grabDesktopImages" is unset, default to true. */
969   {
970     char *s = get_string_resource ("grabDesktopImages", "Boolean");
971     if (s)
972       free (s);
973     else
974       p->grab_desktop_p = True;
975   }
976
977   p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
978   p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
979                                                      "Boolean");
980   p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
981                                                      "Boolean");
982   p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
983
984   /* Throttle the various timeouts to reasonable values.
985    */
986   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
987   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
988   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
989   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
990   if (p->notice_events_timeout <= 0)
991     p->notice_events_timeout = 10000;                            /* 10 secs */
992   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
993     p->fade_p = False;
994   if (! p->fade_p) p->unfade_p = False;
995
996   /* The DPMS settings may have the value 0.
997      But if they are negative, or are a range less than 10 seconds,
998      reset them to sensible defaults.  (Since that must be a mistake.)
999    */
1000   if (p->dpms_standby != 0 &&
1001       p->dpms_standby < 10 * 1000)
1002     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
1003   if (p->dpms_suspend != 0 &&
1004       p->dpms_suspend < 10 * 1000)
1005     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
1006   if (p->dpms_off != 0 &&
1007       p->dpms_off < 10 * 1000)
1008     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
1009
1010   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
1011       p->dpms_suspend == 0 &&
1012       p->dpms_off     == 0)
1013     p->dpms_enabled_p = False;
1014
1015
1016   p->watchdog_timeout = p->cycle * 0.6;
1017   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
1018   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
1019
1020   get_screenhacks (p);
1021
1022   if (system_default_screenhack_count)  /* note: first_time is also true */
1023     {
1024       merge_system_screenhacks (p, system_default_screenhacks,
1025                                 system_default_screenhack_count);
1026       free_screenhack_list (system_default_screenhacks,
1027                             system_default_screenhack_count);
1028       system_default_screenhacks = 0;
1029       system_default_screenhack_count = 0;
1030     }
1031
1032   if (p->debug_p)
1033     {
1034       p->xsync_p = True;
1035       p->verbose_p = True;
1036       p->timestamp_p = True;
1037       p->initial_delay = 0;
1038     }
1039 }
1040
1041
1042 /* If there are any hacks in the system-wide defaults that are not in
1043    the ~/.xscreensaver file, add the new ones to the end of the list.
1044    This does *not* actually save the file.
1045  */
1046 static void
1047 merge_system_screenhacks (saver_preferences *p,
1048                           screenhack **system_list, int system_count)
1049 {
1050   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1051      so fuck it. */
1052
1053   int made_space = 0;
1054   int i;
1055   for (i = 0; i < system_count; i++)
1056     {
1057       int j;
1058       Bool matched_p = False;
1059
1060       for (j = 0; j < p->screenhacks_count; j++)
1061         {
1062           char *name;
1063           if (!system_list[i]->name)
1064             system_list[i]->name = make_hack_name (system_list[i]->command);
1065
1066           name = p->screenhacks[j]->name;
1067           if (!name)
1068             name = make_hack_name (p->screenhacks[j]->command);
1069
1070           matched_p = !strcasecmp (name, system_list[i]->name);
1071
1072           if (name != p->screenhacks[j]->name)
1073             free (name);
1074
1075           if (matched_p)
1076             break;
1077         }
1078
1079       if (!matched_p)
1080         {
1081           /* We have an entry in the system-wide list that is not in the
1082              user's .xscreensaver file.  Add it to the end.
1083              Note that p->screenhacks is a single malloc block, not a
1084              linked list, so we have to realloc it.
1085            */
1086           screenhack *oh = system_list[i];
1087           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1088
1089           if (made_space == 0)
1090             {
1091               made_space = 10;
1092               p->screenhacks = (screenhack **)
1093                 realloc (p->screenhacks,
1094                          (p->screenhacks_count + made_space) 
1095                          * sizeof(screenhack));
1096               if (!p->screenhacks) abort();
1097             }
1098
1099           nh->enabled_p = oh->enabled_p;
1100           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1101           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1102           nh->command   = oh->command ? strdup(oh->command) : 0;
1103
1104           p->screenhacks[p->screenhacks_count++] = nh;
1105           made_space--;
1106
1107 #if 0
1108           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1109                    (nh->name ? nh->name : make_hack_name (nh->command)));
1110 #endif
1111         }
1112     }
1113 }
1114
1115
1116 \f
1117 /* Parsing the programs resource.
1118  */
1119
1120 screenhack *
1121 parse_screenhack (const char *line)
1122 {
1123   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1124   const char *s;
1125
1126   h->enabled_p = True;
1127
1128   while (isspace(*line)) line++;                /* skip whitespace */
1129   if (*line == '-')                             /* handle "-" */
1130     {
1131       h->enabled_p = False;
1132       line++;
1133       while (isspace(*line)) line++;            /* skip whitespace */
1134     }
1135
1136   s = line;                                     /* handle "visual:" */
1137   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1138     line++;
1139   if (*line != ':')
1140     line = s;
1141   else
1142     {
1143       h->visual = (char *) malloc (line-s+1);
1144       strncpy (h->visual, s, line-s);
1145       h->visual[line-s] = 0;
1146       if (*line == ':') line++;                 /* skip ":" */
1147       while (isspace(*line)) line++;            /* skip whitespace */
1148     }
1149
1150   if (*line == '"')                             /* handle "name" */
1151     {
1152       line++;
1153       s = line;
1154       while (*line && *line != '"')
1155         line++;
1156       h->name = (char *) malloc (line-s+1);
1157       strncpy (h->name, s, line-s);
1158       h->name[line-s] = 0;
1159       if (*line == '"') line++;                 /* skip "\"" */
1160       while (isspace(*line)) line++;            /* skip whitespace */
1161     }
1162
1163   h->command = format_command (line, False);    /* handle command */
1164   return h;
1165 }
1166
1167
1168 static char *
1169 format_command (const char *cmd, Bool wrap_p)
1170 {
1171   int tab = 30;
1172   int col = tab;
1173   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1174   const char *in = cmd;
1175   char *out = cmd2;
1176   while (*in)
1177     {
1178       /* shrink all whitespace to one space, for the benefit of the "demo"
1179          mode display.  We only do this when we can easily tell that the
1180          whitespace is not significant (no shell metachars).
1181        */
1182       switch (*in)
1183         {
1184         case '\'': case '"': case '`': case '\\':
1185           /* Metachars are scary.  Copy the rest of the line unchanged. */
1186           while (*in)
1187             *out++ = *in++, col++;
1188           break;
1189
1190         case ' ': case '\t':
1191           /* Squeeze all other whitespace down to one space. */
1192           while (*in == ' ' || *in == '\t')
1193             in++;
1194           *out++ = ' ', col++;
1195           break;
1196
1197         default:
1198           /* Copy other chars unchanged. */
1199           *out++ = *in++, col++;
1200           break;
1201         }
1202     }
1203
1204   *out = 0;
1205
1206   /* Strip trailing whitespace */
1207   while (out > cmd2 && isspace (out[-1]))
1208     *(--out) = 0;
1209
1210   return cmd2;
1211 }
1212
1213
1214 /* Returns a new string describing the shell command.
1215    This may be just the name of the program, capitalized.
1216    It also may be something from the resource database (gotten
1217    by looking for "hacks.XYZ.name", where XYZ is the program.)
1218  */
1219 char *
1220 make_hack_name (const char *shell_command)
1221 {
1222   char *s = strdup (shell_command);
1223   char *s2;
1224   char res_name[255];
1225
1226   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1227     if (isspace (*s2))
1228       {
1229         *s2 = 0;
1230         break;
1231       }
1232
1233   s2 = strrchr (s, '/');        /* if pathname, take last component */
1234   if (s2)
1235     {
1236       s2 = strdup (s2+1);
1237       free (s);
1238       s = s2;
1239     }
1240
1241   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1242     s[50] = 0;
1243
1244   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1245   s2 = get_string_resource (res_name, res_name);
1246   if (s2)
1247     return s2;
1248
1249   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1250     if (*s2 >= 'A' && *s2 <= 'Z')
1251       return s;
1252
1253   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1254     s[0] -= 'a'-'A';
1255   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1256     s[1] -= 'a'-'A';
1257   return s;
1258 }
1259
1260
1261 char *
1262 format_hack (screenhack *hack, Bool wrap_p)
1263 {
1264   int tab = 32;
1265   int size = (2 * (strlen(hack->command) +
1266                    (hack->visual ? strlen(hack->visual) : 0) +
1267                    (hack->name ? strlen(hack->name) : 0) +
1268                    tab));
1269   char *h2 = (char *) malloc (size);
1270   char *out = h2;
1271   char *s;
1272   int col = 0;
1273
1274   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1275
1276   if (hack->visual && *hack->visual)            /* write visual name */
1277     {
1278       if (hack->enabled_p) *out++ = ' ';
1279       *out++ = ' ';
1280       strcpy (out, hack->visual);
1281       out += strlen (hack->visual);
1282       *out++ = ':';
1283       *out++ = ' ';
1284     }
1285
1286   *out = 0;
1287   col = string_columns (h2, strlen (h2), 0);
1288
1289   if (hack->name && *hack->name)                /* write pretty name */
1290     {
1291       int L = (strlen (hack->name) + 2);
1292       if (L + col < tab)
1293         out = stab_to (out, col, tab - L - 2);
1294       else
1295         *out++ = ' ';
1296       *out++ = '"';
1297       strcpy (out, hack->name);
1298       out += strlen (hack->name);
1299       *out++ = '"';
1300       *out = 0;
1301
1302       col = string_columns (h2, strlen (h2), 0);
1303       if (wrap_p && col >= tab)
1304         {
1305           out = stab_to (out, col, 77);
1306           *out += strlen(out);
1307         }
1308       else
1309         *out++ = ' ';
1310
1311       if (out >= h2+size) abort();
1312     }
1313
1314   *out = 0;
1315   col = string_columns (h2, strlen (h2), 0);
1316   out = stab_to (out, col, tab);                /* indent */
1317
1318   if (out >= h2+size) abort();
1319   s = format_command (hack->command, wrap_p);
1320   strcpy (out, s);
1321   out += strlen (s);
1322   free (s);
1323   *out = 0;
1324
1325   return h2;
1326 }
1327
1328
1329 static void
1330 get_screenhacks (saver_preferences *p)
1331 {
1332   int i = 0;
1333   int start = 0;
1334   int end = 0;
1335   int size;
1336   char *d;
1337
1338   d = get_string_resource ("monoPrograms", "MonoPrograms");
1339   if (d && !*d) { free(d); d = 0; }
1340   if (!d)
1341     d = get_string_resource ("colorPrograms", "ColorPrograms");
1342   if (d && !*d) { free(d); d = 0; }
1343
1344   if (d)
1345     {
1346       fprintf (stderr,
1347        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1348         see the manual for details.\n", blurb());
1349       free(d);
1350     }
1351
1352   d = get_string_resource ("programs", "Programs");
1353
1354   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1355   p->screenhacks = 0;
1356   p->screenhacks_count = 0;
1357
1358   if (!d || !*d)
1359     return;
1360
1361   size = strlen (d);
1362
1363
1364   /* Count up the number of newlines (which will be equal to or larger than
1365      the number of hacks.)
1366    */
1367   i = 0;
1368   for (i = 0; d[i]; i++)
1369     if (d[i] == '\n')
1370       i++;
1371   i++;
1372
1373   p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1374
1375   /* Iterate over the lines in `d' (the string with newlines)
1376      and make new strings to stuff into the `screenhacks' array.
1377    */
1378   p->screenhacks_count = 0;
1379   while (start < size)
1380     {
1381       /* skip forward over whitespace. */
1382       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1383         start++;
1384
1385       /* skip forward to newline or end of string. */
1386       end = start;
1387       while (d[end] != 0 && d[end] != '\n')
1388         end++;
1389
1390       /* null terminate. */
1391       d[end] = 0;
1392
1393       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1394       if (p->screenhacks_count >= i)
1395         abort();
1396
1397       start = end+1;
1398     }
1399
1400   if (p->screenhacks_count == 0)
1401     {
1402       free (p->screenhacks);
1403       p->screenhacks = 0;
1404     }
1405 }