ncurses 5.0
[ncurses.git] / ncurses / tinfo / write_entry.c
1 /****************************************************************************
2  * Copyright (c) 1998 Free Software Foundation, Inc.                        *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34
35
36 /*
37  *      write_entry.c -- write a terminfo structure onto the file system
38  */
39
40 #include <curses.priv.h>
41
42 #include <sys/stat.h>
43
44 #include <tic.h>
45 #include <term_entry.h>
46
47 #ifndef S_ISDIR
48 #define S_ISDIR(mode) ((mode & S_IFMT) == S_IFDIR)
49 #endif
50
51 #if 0
52 #define TRACE_OUT(p) DEBUG(2, p)
53 #else
54 #define TRACE_OUT(p) /*nothing*/
55 #endif
56
57 MODULE_ID("$Id: write_entry.c,v 1.47 1999/07/10 20:29:22 tom Exp $")
58
59 static int total_written;
60
61 static int write_object(FILE *, TERMTYPE *);
62
63 static void write_file(char *filename, TERMTYPE *tp)
64 {
65         FILE *fp = (_nc_access(filename, W_OK) == 0) ? fopen(filename, "wb") : 0;
66         if (fp == 0) {
67                 perror(filename);
68                 _nc_syserr_abort("can't open %s/%s", _nc_tic_dir(0), filename);
69         }
70         DEBUG(1, ("Created %s", filename));
71
72         if (write_object(fp, tp) == ERR) {
73                 _nc_syserr_abort("error writing %s/%s", _nc_tic_dir(0), filename);
74         }
75         fclose(fp);
76 }
77
78 /*
79  *      make_directory(char *path)
80  *
81  *      Make a directory if it doesn't exist.
82  */
83 static int make_directory(const char *path)
84 {
85 int     rc;
86 struct  stat    statbuf;
87 char    fullpath[PATH_MAX];
88 const char *destination = _nc_tic_dir(0);
89
90         if (path == destination || *path == '/') {
91                 if (strlen(path) + 1 > sizeof(fullpath))
92                         return(-1);
93                 (void)strcpy(fullpath, path);
94         } else {
95                 if (strlen(destination) + strlen(path) + 2 > sizeof(fullpath))
96                         return(-1);
97                 (void)sprintf(fullpath, "%s/%s", destination, path);
98         }
99
100         if ((rc = stat(path, &statbuf)) < 0) {
101                 rc = mkdir(path, 0777);
102         } else {
103                 if (_nc_access(path, R_OK|W_OK|X_OK) < 0) {
104                         rc = -1;        /* permission denied */
105                 } else if (!(S_ISDIR(statbuf.st_mode))) {
106                         rc = -1;        /* not a directory */
107                 }
108         }
109         return rc;
110 }
111
112 void  _nc_set_writedir(char *dir)
113 /* set the write directory for compiled entries */
114 {
115     const char *destination;
116     char actual[PATH_MAX];
117
118     if (dir != 0)
119         (void) _nc_tic_dir(dir);
120     else if (getenv("TERMINFO") != NULL)
121         (void) _nc_tic_dir(getenv("TERMINFO"));
122
123     destination = _nc_tic_dir(0);
124     if (make_directory(destination) < 0)
125     {
126         char    *home = _nc_home_terminfo();
127
128         if (home != 0) {
129             destination = home;
130             if (make_directory(destination) < 0)
131                 _nc_err_abort("%s: permission denied (errno %d)",
132                         destination, errno);
133         }
134     }
135
136     /*
137      * Note: because of this code, this logic should be exercised
138      * *once only* per run.
139      */
140     if (chdir(_nc_tic_dir(destination)) < 0
141      || getcwd(actual, sizeof(actual)) == 0)
142         _nc_err_abort("%s: not a directory", destination);
143     _nc_keep_tic_dir(strdup(actual));
144 }
145
146 /*
147  *      check_writeable(char code)
148  *
149  *      Miscellaneous initialisations
150  *
151  *      Check for access rights to destination directories
152  *      Create any directories which don't exist.
153  *      Note: there's no reason to return the result of make_directory(), since
154  *      this function is called only in instances where that has to succeed.
155  *
156  */
157
158 static void check_writeable(int code)
159 {
160 static const char dirnames[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
161 static bool verified[sizeof(dirnames)];
162
163 char            dir[2];
164 char            *s;
165
166         if (code == 0 || (s = strchr(dirnames, code)) == 0)
167             _nc_err_abort("Illegal terminfo subdirectory \"%c\"", code);
168
169         if (verified[s-dirnames])
170             return;
171
172         dir[0] = code;
173         dir[1] = '\0';
174         if (make_directory(dir) < 0) {
175                 _nc_err_abort("%s/%s: permission denied", _nc_tic_dir(0), dir);
176         }
177
178         verified[s-dirnames] = TRUE;
179 }
180
181 /*
182  *      _nc_write_entry()
183  *
184  *      Save the compiled version of a description in the filesystem.
185  *
186  *      make a copy of the name-list
187  *      break it up into first-name and all-but-last-name
188  *      creat(first-name)
189  *      write object information to first-name
190  *      close(first-name)
191  *      for each name in all-but-last-name
192  *          link to first-name
193  *
194  *      Using 'time()' to obtain a reference for file timestamps is unreliable,
195  *      e.g., with NFS, because the filesystem may have a different time
196  *      reference.  We check for pre-existence of links by latching the first
197  *      timestamp from a file that we create.
198  *
199  *      The _nc_warning() calls will report a correct line number only if
200  *      _nc_curr_line is properly set before the write_entry() call.
201  */
202
203 void _nc_write_entry(TERMTYPE *const tp)
204 {
205 struct stat     statbuf;
206 char            name_list[MAX_TERMINFO_LENGTH];
207 char            *first_name, *other_names;
208 char            *ptr;
209 char            filename[PATH_MAX];
210 char            linkname[PATH_MAX];
211 #if USE_SYMLINKS
212 char            symlinkname[PATH_MAX];
213 #endif /* USE_SYMLINKS */
214 static int      call_count;
215 static time_t   start_time;             /* time at start of writes */
216
217         if (call_count++ == 0) {
218                 start_time = 0;
219         }
220
221         (void) strcpy(name_list, tp->term_names);
222         DEBUG(7, ("Name list = '%s'", name_list));
223
224         first_name = name_list;
225
226         ptr = &name_list[strlen(name_list) - 1];
227         other_names = ptr + 1;
228
229         while (ptr > name_list  &&  *ptr != '|')
230                 ptr--;
231
232         if (ptr != name_list) {
233                 *ptr = '\0';
234
235                 for (ptr = name_list; *ptr != '\0' && *ptr != '|'; ptr++)
236                         continue;
237
238                 if (*ptr == '\0')
239                         other_names = ptr;
240                 else {
241                         *ptr = '\0';
242                         other_names = ptr + 1;
243                 }
244         }
245
246         DEBUG(7, ("First name = '%s'", first_name));
247         DEBUG(7, ("Other names = '%s'", other_names));
248
249         _nc_set_type(first_name);
250
251         if (strlen(first_name) > sizeof(filename)-3)
252                 _nc_warning("terminal name too long.");
253
254         sprintf(filename, "%c/%s", first_name[0], first_name);
255
256         /*
257          * Has this primary name been written since the first call to
258          * write_entry()?  If so, the newer write will step on the older,
259          * so warn the user.
260          */
261         if (start_time > 0 &&
262             stat(filename, &statbuf) >= 0
263             && statbuf.st_mtime >= start_time)
264         {
265                 _nc_warning("name multiply defined.");
266         }
267
268         check_writeable(first_name[0]);
269         write_file(filename, tp);
270
271         if (start_time == 0) {
272                 if (stat(filename, &statbuf) < 0
273                  || (start_time = statbuf.st_mtime) == 0) {
274                         _nc_syserr_abort("error obtaining time from %s/%s",
275                                 _nc_tic_dir(0), filename);
276                 }
277         }
278         while (*other_names != '\0') {
279                 ptr = other_names++;
280                 while (*other_names != '|'  &&  *other_names != '\0')
281                         other_names++;
282
283                 if (*other_names != '\0')
284                         *(other_names++) = '\0';
285
286                 if (strlen(ptr) > sizeof(linkname)-3) {
287                         _nc_warning("terminal alias %s too long.", ptr);
288                         continue;
289                 }
290                 if (strchr(ptr, '/') != 0) {
291                         _nc_warning("cannot link alias %s.", ptr);
292                         continue;
293                 }
294
295                 check_writeable(ptr[0]);
296                 sprintf(linkname, "%c/%s", ptr[0], ptr);
297
298                 if (strcmp(filename, linkname) == 0) {
299                         _nc_warning("self-synonym ignored");
300                 }
301                 else if (stat(linkname, &statbuf) >= 0  &&
302                                                 statbuf.st_mtime < start_time)
303                 {
304                         _nc_warning("alias %s multiply defined.", ptr);
305                 }
306                 else if (_nc_access(linkname, W_OK) == 0)
307 #if HAVE_LINK
308                 {
309                         int code;
310 #if USE_SYMLINKS
311                         strcpy(symlinkname, "../");
312                         strncat(symlinkname, filename, sizeof(symlinkname) - 4);
313                         symlinkname[sizeof(symlinkname) - 1] = '\0';
314 #endif /* USE_SYMLINKS */
315 #if HAVE_REMOVE
316                         code = remove(linkname);
317 #else
318                         code = unlink(linkname);
319 #endif
320                         if (code != 0 && errno == ENOENT)
321                                 code = 0;
322 #if USE_SYMLINKS
323                         if (symlink(symlinkname, linkname) < 0)
324 #else
325                         if (link(filename, linkname) < 0)
326 #endif /* USE_SYMLINKS */
327                         {
328                             /*
329                              * If there wasn't anything there, and we cannot
330                              * link to the target because it is the same as the
331                              * target, then the source must be on a filesystem
332                              * that uses caseless filenames, such as Win32, etc.
333                              */
334                             if (code == 0 && errno == EEXIST)
335                                 _nc_warning("can't link %s to %s", filename, linkname);
336                             else if (code == 0 && errno == EPERM)
337                                 write_file(linkname, tp);
338                             else
339                                 _nc_syserr_abort("can't link %s to %s", filename, linkname);
340                         }
341                         else
342                         {
343                             DEBUG(1, ("Linked %s", linkname));
344                         }
345                 }
346 #else /* just make copies */
347                 write_file(linkname, tp);
348 #endif /* HAVE_LINK */
349         }
350 }
351
352 #undef LITTLE_ENDIAN    /* BSD/OS defines this as a feature macro */
353 #define HI(x)                   ((x) / 256)
354 #define LO(x)                   ((x) % 256)
355 #define LITTLE_ENDIAN(p, x)     (p)[0] = LO(x), (p)[1] = HI(x)
356
357 #define WRITE_STRING(str) (fwrite(str, sizeof(char), strlen(str) + 1, fp) == strlen(str) + 1)
358
359 static int compute_offsets(char **Strings, int strmax, short *offsets)
360 {
361     size_t nextfree = 0;
362     int i;
363
364     for (i = 0; i < strmax; i++) {
365         if (Strings[i] == ABSENT_STRING) {
366             offsets[i] = -1;
367         } else if (Strings[i] == CANCELLED_STRING) {
368             offsets[i] = -2;
369         } else {
370             offsets[i] = nextfree;
371             nextfree += strlen(Strings[i]) + 1;
372             TRACE_OUT(("put Strings[%d]=%s(%d)", i, _nc_visbuf(Strings[i]), nextfree));
373         }
374     }
375     return nextfree;
376 }
377
378 static void convert_shorts(unsigned char *buf, short *Numbers, int count)
379 {
380     int i;
381     for (i = 0; i < count; i++) {
382         if (Numbers[i] == -1) {         /* HI/LO won't work */
383             buf[2*i] = buf[2*i + 1] = 0377;
384         } else if (Numbers[i] == -2) {  /* HI/LO won't work */
385             buf[2*i] = 0376;
386             buf[2*i + 1] = 0377;
387         } else {
388             LITTLE_ENDIAN(buf + 2*i, Numbers[i]);
389             TRACE_OUT(("put Numbers[%d]=%d", i, Numbers[i]));
390         }
391     }
392 }
393
394 #define even_boundary(value) \
395             ((value) % 2 != 0 && fwrite(&zero, sizeof(char), 1, fp) != 1)
396
397 static int write_object(FILE *fp, TERMTYPE *tp)
398 {
399 char            *namelist;
400 size_t          namelen, boolmax, nummax, strmax;
401 char            zero = '\0';
402 size_t          i;
403 short           nextfree;
404 short           offsets[MAX_ENTRY_SIZE/2];
405 unsigned char   buf[MAX_ENTRY_SIZE];
406
407         namelist = tp->term_names;
408         namelen = strlen(namelist) + 1;
409
410         /*
411          * BOOLWRITE, etc., are less than BOOLCOUNT because we store some
412          * values internally.
413          */
414         boolmax = 0;
415         for (i = 0; i < BOOLWRITE; i++) {
416             if (tp->Booleans[i])
417                 boolmax = i+1;
418         }
419
420         nummax = 0;
421         for (i = 0; i < NUMWRITE; i++) {
422             if (tp->Numbers[i] != ABSENT_NUMERIC)
423                 nummax = i+1;
424         }
425
426         strmax = 0;
427         for (i = 0; i < STRWRITE; i++) {
428             if (tp->Strings[i] != ABSENT_STRING)
429                 strmax = i+1;
430         }
431
432         nextfree = compute_offsets(tp->Strings, strmax, offsets);
433
434         /* fill in the header */
435         LITTLE_ENDIAN(buf,    MAGIC);
436         LITTLE_ENDIAN(buf+2,  min(namelen, MAX_NAME_SIZE + 1));
437         LITTLE_ENDIAN(buf+4,  boolmax);
438         LITTLE_ENDIAN(buf+6,  nummax);
439         LITTLE_ENDIAN(buf+8,  strmax);
440         LITTLE_ENDIAN(buf+10, nextfree);
441
442         /* write out the header */
443         TRACE_OUT(("Header of %s @%ld", namelist, ftell(fp)));
444         if (fwrite(buf, 12, 1, fp) != 1
445             ||  fwrite(namelist, sizeof(char), namelen, fp) != namelen
446             ||  fwrite(tp->Booleans, sizeof(char), boolmax, fp) != boolmax)
447             return(ERR);
448
449         if (even_boundary(namelen+boolmax))
450             return(ERR);
451
452         TRACE_OUT(("Numerics begin at %04lx", ftell(fp)));
453
454         /* the numerics */
455         convert_shorts(buf, tp->Numbers, nummax);
456         if (fwrite(buf, 2, nummax, fp) != nummax)
457             return(ERR);
458
459         TRACE_OUT(("String offsets begin at %04lx", ftell(fp)));
460
461         /* the string offsets */
462         convert_shorts(buf, offsets, strmax);
463         if (fwrite(buf, 2, strmax, fp) != strmax)
464             return(ERR);
465
466         TRACE_OUT(("String table begins at %04lx", ftell(fp)));
467
468         /* the strings */
469         for (i = 0; i < strmax; i++)
470             if (VALID_STRING(tp->Strings[i]))
471                 if (!WRITE_STRING(tp->Strings[i]))
472                     return(ERR);
473
474 #if NCURSES_XNAMES
475         if (NUM_EXT_NAMES(tp)) {
476             unsigned extcnt = NUM_EXT_NAMES(tp);
477
478             if (even_boundary(nextfree))
479                 return(ERR);
480
481             nextfree = compute_offsets(tp->Strings + STRCOUNT, tp->ext_Strings, offsets);
482             TRACE_OUT(("after extended string capabilities, nextfree=%d", nextfree));
483             nextfree += compute_offsets(tp->ext_Names, extcnt, offsets + tp->ext_Strings);
484             TRACE_OUT(("after extended capnames, nextfree=%d", nextfree));
485             strmax = tp->ext_Strings + extcnt;
486
487             /*
488              * Write the extended header
489              */
490             LITTLE_ENDIAN(buf+0, tp->ext_Booleans);
491             LITTLE_ENDIAN(buf+2, tp->ext_Numbers);
492             LITTLE_ENDIAN(buf+4, tp->ext_Strings);
493             LITTLE_ENDIAN(buf+6, strmax);
494             LITTLE_ENDIAN(buf+8, nextfree);
495             TRACE_OUT(("WRITE extended-header @%ld", ftell(fp)));
496             if (fwrite(buf, 10, 1, fp) != 1)
497                 return(ERR);
498
499             TRACE_OUT(("WRITE %d booleans @%ld", tp->ext_Booleans, ftell(fp)));
500             if (tp->ext_Booleans
501              && fwrite(tp->Booleans + BOOLCOUNT, sizeof(char), tp->ext_Booleans, fp) != tp->ext_Booleans)
502                 return(ERR);
503
504             if (even_boundary(tp->ext_Booleans))
505                 return(ERR);
506
507             TRACE_OUT(("WRITE %d numbers @%ld", tp->ext_Numbers, ftell(fp)));
508             if (tp->ext_Numbers) {
509                 convert_shorts(buf, tp->Numbers + NUMCOUNT, tp->ext_Numbers);
510                 if (fwrite(buf, 2, tp->ext_Numbers, fp) != tp->ext_Numbers)
511                     return(ERR);
512             }
513
514             /*
515              * Convert the offsets for the ext_Strings and ext_Names tables,
516              * in that order.
517              */
518             convert_shorts(buf, offsets, strmax);
519             TRACE_OUT(("WRITE offsets @%ld", ftell(fp)));
520             if (fwrite(buf, 2, strmax, fp) != strmax)
521                 return(ERR);
522
523             /*
524              * Write the string table after the offset tables so we do not
525              * have to do anything about alignment.
526              */
527             for (i = 0; i < tp->ext_Strings; i++) {
528                 if (VALID_STRING(tp->Strings[i+STRCOUNT])) {
529                     TRACE_OUT(("WRITE ext_Strings[%d]=%s", i, _nc_visbuf(tp->Strings[i+STRCOUNT])));
530                     if (!WRITE_STRING(tp->Strings[i+STRCOUNT]))
531                         return(ERR);
532                 }
533             }
534
535             /*
536              * Write the extended names
537              */
538             for (i = 0; i < extcnt; i++) {
539                 TRACE_OUT(("WRITE ext_Names[%d]=%s", i, tp->ext_Names[i]));
540                 if (!WRITE_STRING(tp->ext_Names[i]))
541                     return(ERR);
542             }
543
544         }
545 #endif /* NCURSES_XNAMES */
546
547         total_written++;
548         return(OK);
549 }
550
551 /*
552  * Returns the total number of entries written by this process
553  */
554 int _nc_tic_written(void)
555 {
556         return total_written;
557 }