xref: /freebsd/contrib/ncurses/ncurses/tinfo/write_entry.c (revision 68ad2b0d7af2a3571c4abac9afa712f9b09b721c)
1 /****************************************************************************
2  * Copyright 2018-2024,2025 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996-on                 *
34  ****************************************************************************/
35 
36 /*
37  *	write_entry.c -- write a terminfo structure onto the file system
38  */
39 
40 #include <curses.priv.h>
41 #include <hashed_db.h>
42 
43 #include <tic.h>
44 
45 MODULE_ID("$Id: write_entry.c,v 1.144 2025/12/28 00:33:30 tom Exp $")
46 
47 #if 1
48 #define TRACE_OUT(p) DEBUG(2, p)
49 #define TRACE_NUM(n) if (VALID_NUMERIC(Numbers[n])) { \
50 	TRACE_OUT(("put Numbers[%u]=%d", (unsigned) (n), Numbers[n])); }
51 #else
52 #define TRACE_OUT(p)		/*nothing */
53 #define TRACE_NUM(n)		/* nothing */
54 #endif
55 
56 /*
57  * FIXME: special case to work around Cygwin bug in link(), which updates
58  * the target file's timestamp.
59  */
60 #if HAVE_LINK && !USE_SYMLINKS && !MIXEDCASE_FILENAMES && defined(__CYGWIN__)
61 #define LINK_TOUCHES 1
62 #else
63 #define LINK_TOUCHES 0
64 #endif
65 
66 static int total_written;
67 static int total_parts;
68 static int total_size;
69 
70 static int make_db_root(const char *);
71 
72 #if !USE_HASHED_DB
73 static void
write_file(const char * filename,TERMTYPE2 * tp)74 write_file(const char *filename, TERMTYPE2 *tp)
75 {
76     char buffer[MAX_ENTRY_SIZE];
77     unsigned limit = sizeof(buffer);
78     unsigned offset = 0;
79 
80     if (_nc_write_object(tp, buffer, &offset, limit) == ERR) {
81 	_nc_warning("entry is larger than %u bytes", limit);
82     } else {
83 	FILE *fp = ((_nc_access(filename, W_OK) == 0)
84 		    ? safe_fopen(filename, BIN_W)
85 		    : NULL);
86 	size_t actual;
87 
88 	if (fp == NULL) {
89 	    _nc_syserr_abort("cannot open %s/%s: (errno %d) %s",
90 			     _nc_tic_dir(NULL),
91 			     filename,
92 			     errno,
93 			     strerror(errno));
94 	}
95 
96 	actual = fwrite(buffer, sizeof(char), (size_t) offset, fp);
97 	if (actual != offset) {
98 	    int myerr = ferror(fp) ? errno : 0;
99 	    if (myerr) {
100 		_nc_syserr_abort("error writing %s/%s: (errno %d) %s",
101 				 _nc_tic_dir(NULL),
102 				 filename,
103 				 myerr,
104 				 strerror(myerr));
105 	    } else {
106 		_nc_syserr_abort("error writing %s/%s: %u bytes vs actual %lu",
107 				 _nc_tic_dir(NULL),
108 				 filename,
109 				 offset,
110 				 (unsigned long) actual);
111 	    }
112 	} else {
113 	    fclose(fp);
114 	    DEBUG(1, ("Created %s", filename));
115 	}
116     }
117 }
118 
119 /*
120  * Check for access rights to destination directories
121  * Create any directories which don't exist.
122  *
123  * Note:  there's no reason to return the result of make_db_root(), since
124  * this function is called only in instances where that has to succeed.
125  */
126 static void
check_writeable(int code)127 check_writeable(int code)
128 {
129     static const char dirnames[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
130     static bool verified[sizeof(dirnames)];
131 
132     const char *s = NULL;
133 
134     if (code == 0 || (s = (strchr) (dirnames, code)) == NULL) {
135 	_nc_err_abort("Illegal terminfo subdirectory \"" LEAF_FMT "\"", UChar(code));
136     } else if (!verified[s - dirnames]) {
137 	char dir[sizeof(LEAF_FMT)];
138 	_nc_SPRINTF(dir, _nc_SLIMIT(sizeof(dir)) LEAF_FMT, UChar(code));
139 	if (make_db_root(dir) < 0) {
140 	    _nc_err_abort("%s/%s: (errno %d) %s",
141 			  _nc_tic_dir(NULL),
142 			  dir,
143 			  errno,
144 			  strerror(errno));
145 	} else {
146 	    verified[s - dirnames] = TRUE;
147 	}
148     }
149 }
150 #endif /* !USE_HASHED_DB */
151 
152 static int
make_db_path(char * dst,const char * src,size_t limit)153 make_db_path(char *dst, const char *src, size_t limit)
154 {
155     int rc = -1;
156     const char *top = _nc_tic_dir(NULL);
157 
158     if (src == top || _nc_is_abs_path(src)) {
159 	if (strlen(src) + 1 <= limit) {
160 	    _nc_STRCPY(dst, src, limit);
161 	    rc = 0;
162 	}
163     } else {
164 	size_t len_top = strlen(top);
165 	size_t len_src = strlen(src);
166 	if ((len_top + len_src + 6) <= limit) {
167 	    _nc_SPRINTF(dst, _nc_SLIMIT(limit) "%.*s/%.*s",
168 			(int) len_top, top,
169 			(int) len_src, src);
170 	    rc = 0;
171 	}
172     }
173 #if USE_HASHED_DB
174     if (rc == 0) {
175 	static const char suffix[] = DBM_SUFFIX;
176 	size_t have = strlen(dst);
177 	size_t need = strlen(suffix);
178 	if (have > need && strcmp(dst + (int) (have - need), suffix)) {
179 	    if (have + need <= limit) {
180 		_nc_STRCAT(dst, suffix, limit);
181 	    } else {
182 		rc = -1;
183 	    }
184 	} else if (_nc_is_dir_path(dst)) {
185 	    rc = -1;
186 	}
187     }
188 #endif
189     return rc;
190 }
191 
192 /*
193  * Make a database-root if it doesn't exist.
194  */
195 static int
make_db_root(const char * path)196 make_db_root(const char *path)
197 {
198     int rc;
199     char fullpath[PATH_MAX];
200 
201     FixupPathname(path);
202     if ((rc = make_db_path(fullpath, path, sizeof(fullpath))) == 0) {
203 #if USE_HASHED_DB
204 	DB *capdbp;
205 
206 	if ((capdbp = _nc_db_open(fullpath, TRUE)) == NULL) {
207 	    rc = -1;
208 	} else if (_nc_db_close(capdbp) < 0) {
209 	    rc = -1;
210 	}
211 #else
212 	struct stat statbuf;
213 
214 	rc = 0;
215 	if (!_nc_is_path_found(path, &statbuf)) {
216 	    rc = mkdir(path
217 #ifndef _NC_WINDOWS_NATIVE
218 		       ,0777
219 #endif
220 		);
221 	} else if (_nc_access(path, R_OK | W_OK | X_OK) < 0) {
222 	    rc = -1;		/* permission denied */
223 	} else if (!(S_ISDIR(statbuf.st_mode))) {
224 	    errno = ENOTDIR;
225 	    rc = -1;		/* not a directory */
226 	}
227 #endif
228     }
229     return rc;
230 }
231 
232 /*
233  * Set the write directory for compiled entries.
234  */
235 NCURSES_EXPORT(void)
_nc_set_writedir(const char * dir)236 _nc_set_writedir(const char *dir)
237 {
238     const char *destination;
239     char actual[PATH_MAX];
240     bool specific = (dir != NULL);
241 
242     if (!specific && use_terminfo_vars())
243 	dir = getenv("TERMINFO");
244 
245     if (dir != NULL)
246 	(void) _nc_tic_dir(dir);
247 
248     destination = _nc_tic_dir(NULL);
249     if (make_db_root(destination) < 0) {
250 	bool success = FALSE;
251 
252 	if (!specific) {
253 	    char *home = _nc_home_terminfo();
254 
255 	    if (home != NULL) {
256 		destination = home;
257 		if (make_db_root(destination) == 0)
258 		    success = TRUE;
259 	    }
260 	}
261 	if (!success) {
262 	    _nc_err_abort("%s: (errno %d) %s",
263 			  destination,
264 			  errno,
265 			  strerror(errno));
266 	}
267     }
268 
269     /*
270      * Note: because of this code, this logic should be exercised
271      * *once only* per run.
272      */
273 #if USE_HASHED_DB
274     make_db_path(actual, destination, sizeof(actual));
275 #else
276     FixupPathname2(destination, actual);
277     if (chdir(_nc_tic_dir(destination)) < 0
278 	|| getcwd(actual, sizeof(actual)) == NULL)
279 	_nc_err_abort("%s: not a directory", destination);
280 #endif
281     _nc_keep_tic_dir(actual);
282 }
283 
284 /*
285  *	Save the compiled version of a description in the filesystem.
286  *
287  *	make a copy of the name-list
288  *	break it up into first-name and all-but-last-name
289  *	creat(first-name)
290  *	write object information to first-name
291  *	close(first-name)
292  *      for each name in all-but-last-name
293  *	    link to first-name
294  *
295  *	Using 'time()' to obtain a reference for file timestamps is unreliable,
296  *	e.g., with NFS, because the filesystem may have a different time
297  *	reference.  We check for pre-existence of links by latching the first
298  *	timestamp from a file that we create.
299  *
300  *	The _nc_warning() calls will report a correct line number only if
301  *	_nc_curr_line is properly set before the write_entry() call.
302  */
303 
304 NCURSES_EXPORT(void)
_nc_write_entry(TERMTYPE2 * const tp)305 _nc_write_entry(TERMTYPE2 *const tp)
306 {
307 #if USE_HASHED_DB
308 
309     char buffer[MAX_ENTRY_SIZE + 1];
310     unsigned limit = sizeof(buffer);
311     unsigned offset = 0;
312 
313 #else /* !USE_HASHED_DB */
314 
315     struct stat statbuf;
316     char filename[PATH_MAX];
317     char linkname[PATH_MAX];
318 #if USE_SYMLINKS
319     char symlinkname[PATH_MAX];
320 #if !HAVE_LINK
321 #undef HAVE_LINK
322 #define HAVE_LINK 1
323 #endif
324 #endif /* USE_SYMLINKS */
325 
326     unsigned limit2 = sizeof(filename) - (2 + LEAF_LEN);
327     char saved = '\0';
328 
329     static int call_count;
330     static time_t start_time;	/* time at start of writes */
331 
332 #endif /* USE_HASHED_DB */
333 
334     char name_list[MAX_TERMINFO_LENGTH];
335     char *first_name, *other_names;
336     char *ptr;
337     char *term_names = tp->term_names;
338     size_t name_size = strlen(term_names);
339 
340     if (name_size == 0) {
341 	_nc_syserr_abort("no terminal name found.");
342     } else if (name_size >= sizeof(name_list) - 1) {
343 	_nc_syserr_abort("terminal name too long: %s", term_names);
344     }
345 
346     _nc_STRCPY(name_list, term_names, sizeof(name_list));
347     DEBUG(7, ("Name list = '%s'", name_list));
348 
349     first_name = name_list;
350 
351     ptr = &name_list[name_size - 1];
352     other_names = ptr + 1;
353 
354     while (ptr > name_list && *ptr != '|')
355 	ptr--;
356 
357     if (ptr != name_list) {
358 	*ptr = '\0';
359 
360 	for (ptr = name_list; *ptr != '\0' && *ptr != '|'; ptr++) {
361 	    /* EMPTY */ ;
362 	}
363 
364 	if (*ptr == '\0')
365 	    other_names = ptr;
366 	else {
367 	    *ptr = '\0';
368 	    other_names = ptr + 1;
369 	}
370     }
371 
372     DEBUG(7, ("First name = '%s'", first_name));
373     DEBUG(7, ("Other names = '%s'", other_names));
374 
375     _nc_set_type(first_name);
376 
377 #if USE_HASHED_DB
378     if (_nc_write_object(tp, buffer + 1, &offset, limit - 1) != ERR) {
379 	DB *capdb = _nc_db_open(_nc_tic_dir(NULL), TRUE);
380 	DBT key, data;
381 
382 	if (capdb != NULL) {
383 	    buffer[0] = 0;
384 
385 	    memset(&key, 0, sizeof(key));
386 	    key.data = term_names;
387 	    key.size = name_size;
388 
389 	    memset(&data, 0, sizeof(data));
390 	    data.data = buffer;
391 	    data.size = offset + 1;
392 
393 	    _nc_db_put(capdb, &key, &data);
394 
395 	    buffer[0] = 2;
396 
397 	    key.data = name_list;
398 	    key.size = strlen(name_list);
399 
400 	    _nc_STRCPY(buffer + 1,
401 		       term_names,
402 		       sizeof(buffer) - 1);
403 	    data.size = name_size + 1;
404 
405 	    total_size += (int) data.size;
406 	    total_parts++;
407 	    _nc_db_put(capdb, &key, &data);
408 
409 	    while (*other_names != '\0') {
410 		ptr = other_names++;
411 		assert(ptr < buffer + sizeof(buffer) - 1);
412 		while (*other_names != '|' && *other_names != '\0')
413 		    other_names++;
414 
415 		if (*other_names != '\0')
416 		    *(other_names++) = '\0';
417 
418 		key.data = ptr;
419 		key.size = strlen(ptr);
420 
421 		total_size += (int) data.size;
422 		total_parts++;
423 		_nc_db_put(capdb, &key, &data);
424 	    }
425 	}
426     }
427 #else /* !USE_HASHED_DB */
428     if (call_count++ == 0) {
429 	start_time = 0;
430     }
431 
432     if (strlen(first_name) >= limit2) {
433 	_nc_warning("terminal name too long.");
434 	saved = first_name[limit2];
435 	first_name[limit2] = '\0';
436     }
437 
438     _nc_SPRINTF(filename, _nc_SLIMIT(sizeof(filename))
439 		LEAF_FMT "/%.*s", UChar(first_name[0]),
440 		(int) (sizeof(filename) - (LEAF_LEN + 2)),
441 		first_name);
442 
443     if (saved)
444 	first_name[limit2] = saved;
445 
446     /*
447      * Has this primary name been written since the first call to
448      * write_entry()?  If so, the newer write will step on the older,
449      * so warn the user.
450      */
451     if (start_time > 0 &&
452 	_nc_is_path_found(filename, &statbuf)
453 	&& statbuf.st_mtime >= start_time) {
454 #if HAVE_LINK && !USE_SYMLINKS
455 	/*
456 	 * If the file has more than one link, the reason for the previous
457 	 * write could be that the current primary name used to be an alias for
458 	 * the previous entry.  In that case, unlink the file so that we will
459 	 * not modify the previous entry as we write this one.
460 	 */
461 	if (statbuf.st_nlink > 1) {
462 	    _nc_warning("name redefined.");
463 	    unlink(filename);
464 	} else {
465 	    _nc_warning("name multiply defined.");
466 	}
467 #else
468 	_nc_warning("name multiply defined.");
469 #endif
470     }
471 
472     check_writeable(first_name[0]);
473     write_file(filename, tp);
474 
475     if (start_time == 0) {
476 	if (!_nc_is_path_found(filename, &statbuf)
477 	    || (start_time = statbuf.st_mtime) == 0) {
478 	    _nc_syserr_abort("error obtaining time from %s/%s",
479 			     _nc_tic_dir(NULL), filename);
480 	}
481     }
482     while (*other_names != '\0') {
483 	ptr = other_names++;
484 	while (*other_names != '|' && *other_names != '\0')
485 	    other_names++;
486 
487 	if (*other_names != '\0')
488 	    *(other_names++) = '\0';
489 
490 	if (strlen(ptr) > sizeof(linkname) - (2 + LEAF_LEN)) {
491 	    _nc_warning("terminal alias %s too long.", ptr);
492 	    continue;
493 	}
494 	if (strchr(ptr, '/') != NULL) {
495 	    _nc_warning("cannot link alias %s.", ptr);
496 	    continue;
497 	}
498 
499 	check_writeable(ptr[0]);
500 	_nc_SPRINTF(linkname, _nc_SLIMIT(sizeof(linkname))
501 		    LEAF_FMT "/%.*s", UChar(ptr[0]),
502 		    (int) sizeof(linkname) - (2 + LEAF_LEN), ptr);
503 
504 	if (strcmp(filename, linkname) == 0) {
505 	    _nc_warning("self-synonym ignored");
506 	}
507 #if !LINK_TOUCHES
508 	else if (_nc_is_path_found(linkname, &statbuf) &&
509 		 statbuf.st_mtime < start_time) {
510 	    _nc_warning("alias %s multiply defined.", ptr);
511 	}
512 #endif
513 	else if (_nc_access(linkname, W_OK) == 0)
514 #if HAVE_LINK
515 	{
516 	    int code;
517 #if USE_SYMLINKS
518 #define MY_SIZE sizeof(symlinkname) - 1
519 	    if (first_name[0] == linkname[0]) {
520 		_nc_STRNCPY(symlinkname, first_name, MY_SIZE);
521 	    } else {
522 		_nc_STRCPY(symlinkname, "../", sizeof(symlinkname));
523 		_nc_STRNCPY(symlinkname + 3, filename, MY_SIZE - 3);
524 	    }
525 	    symlinkname[MY_SIZE] = '\0';
526 #endif /* USE_SYMLINKS */
527 #if HAVE_REMOVE
528 	    code = remove(linkname);
529 #else
530 	    code = unlink(linkname);
531 #endif
532 	    if (code != 0 && errno == ENOENT)
533 		code = 0;
534 #if USE_SYMLINKS
535 	    if (symlink(symlinkname, linkname) < 0)
536 #else
537 	    if (link(filename, linkname) < 0)
538 #endif /* USE_SYMLINKS */
539 	    {
540 		/*
541 		 * If there wasn't anything there, and we cannot
542 		 * link to the target because it is the same as the
543 		 * target, then the source must be on a filesystem
544 		 * that uses caseless filenames, such as Win32, etc.
545 		 */
546 		if (code == 0 && errno == EEXIST)
547 		    _nc_warning("can't link %s to %s", filename, linkname);
548 		else if (code == 0 && (errno == EPERM || errno == ENOENT))
549 		    write_file(linkname, tp);
550 		else {
551 #if MIXEDCASE_FILENAMES
552 		    _nc_syserr_abort("cannot link %s to %s", filename, linkname);
553 #else
554 		    _nc_warning("cannot link %s to %s (errno=%d)", filename,
555 				linkname, errno);
556 #endif
557 		}
558 	    } else {
559 		DEBUG(1, ("Linked %s", linkname));
560 	    }
561 	}
562 #else /* just make copies */
563 	    write_file(linkname, tp);
564 #endif /* HAVE_LINK */
565     }
566 #endif /* USE_HASHED_DB */
567 }
568 
569 static size_t
fake_write(char * dst,unsigned * offset,size_t limit,const char * src,size_t want,size_t size)570 fake_write(char *dst,
571 	   unsigned *offset,
572 	   size_t limit,
573 	   const char *src,
574 	   size_t want,
575 	   size_t size)
576 {
577     size_t have = (limit - *offset);
578 
579     want *= size;
580     if (have > 0) {
581 	if (want > have)
582 	    want = have;
583 	memcpy(dst + *offset, src, want);
584 	*offset += (unsigned) want;
585     } else {
586 	want = 0;
587     }
588     return (want / size);
589 }
590 
591 #define Write(buf, size, count) fake_write(buffer, offset, (size_t) limit, (char *) buf, (size_t) count, (size_t) size)
592 
593 #undef LITTLE_ENDIAN		/* BSD/OS defines this as a feature macro */
594 #define HI(x)			((x) / 256)
595 #define LO(x)			((x) % 256)
596 #define LITTLE_ENDIAN(p, x)	(p)[0] = (unsigned char)LO(x),  \
597                                 (p)[1] = (unsigned char)HI(x)
598 
599 #define WRITE_STRING(str) (Write(str, sizeof(char), strlen(str) + 1) == strlen(str) + 1)
600 
601 static int
compute_offsets(char ** Strings,size_t strmax,short * offsets)602 compute_offsets(char **Strings, size_t strmax, short *offsets)
603 {
604     int nextfree = 0;
605     size_t i;
606 
607     for (i = 0; i < strmax; i++) {
608 	if (Strings[i] == ABSENT_STRING) {
609 	    offsets[i] = -1;
610 	} else if (Strings[i] == CANCELLED_STRING) {
611 	    offsets[i] = -2;
612 	} else {
613 	    offsets[i] = (short) nextfree;
614 	    nextfree += (int) strlen(Strings[i]) + 1;
615 	    TRACE_OUT(("put Strings[%d]=%s(%d)", (int) i,
616 		       _nc_visbuf(Strings[i]), (int) nextfree));
617 	}
618     }
619     return nextfree;
620 }
621 
622 static size_t
convert_shorts(unsigned char * buf,short * Numbers,size_t count)623 convert_shorts(unsigned char *buf, short *Numbers, size_t count)
624 {
625     size_t i;
626     for (i = 0; i < count; i++) {
627 	if (Numbers[i] == ABSENT_NUMERIC) {	/* HI/LO won't work */
628 	    buf[2 * i] = buf[2 * i + 1] = 0377;
629 	} else if (Numbers[i] == CANCELLED_NUMERIC) {	/* HI/LO won't work */
630 	    buf[2 * i] = 0376;
631 	    buf[2 * i + 1] = 0377;
632 	} else {
633 	    LITTLE_ENDIAN(buf + 2 * i, Numbers[i]);
634 	    TRACE_OUT(("put Numbers[%u]=%d", (unsigned) i, Numbers[i]));
635 	}
636     }
637     return SIZEOF_SHORT;
638 }
639 
640 #if NCURSES_EXT_NUMBERS
641 static size_t
convert_16bit(unsigned char * buf,NCURSES_INT2 * Numbers,size_t count)642 convert_16bit(unsigned char *buf, NCURSES_INT2 *Numbers, size_t count)
643 {
644     size_t i, j;
645     size_t size = SIZEOF_SHORT;
646     for (i = 0; i < count; i++) {
647 	unsigned value = (unsigned) Numbers[i];
648 	TRACE_NUM(i);
649 	for (j = 0; j < size; ++j) {
650 	    *buf++ = value & 0xff;
651 	    value >>= 8;
652 	}
653     }
654     return size;
655 }
656 
657 static size_t
convert_32bit(unsigned char * buf,NCURSES_INT2 * Numbers,size_t count)658 convert_32bit(unsigned char *buf, NCURSES_INT2 *Numbers, size_t count)
659 {
660     size_t i, j;
661     size_t size = SIZEOF_INT2;
662     for (i = 0; i < count; i++) {
663 	unsigned value = (unsigned) Numbers[i];
664 	TRACE_NUM(i);
665 	for (j = 0; j < size; ++j) {
666 	    *buf++ = value & 0xff;
667 	    value >>= 8;
668 	}
669     }
670     return size;
671 }
672 #endif
673 
674 #define even_boundary(value) \
675 	    ((value) % 2 != 0 && Write(&zero, sizeof(char), 1) != 1)
676 
677 #if NCURSES_XNAMES
678 static unsigned
extended_Booleans(const TERMTYPE2 * tp)679 extended_Booleans(const TERMTYPE2 *tp)
680 {
681     unsigned result = 0;
682     unsigned i;
683 
684     for (i = 0; i < tp->ext_Booleans; ++i) {
685 	if (tp->Booleans[BOOLCOUNT + i] == TRUE)
686 	    result = (i + 1);
687     }
688     return result;
689 }
690 
691 static unsigned
extended_Numbers(const TERMTYPE2 * tp)692 extended_Numbers(const TERMTYPE2 *tp)
693 {
694     unsigned result = 0;
695     unsigned i;
696 
697     for (i = 0; i < tp->ext_Numbers; ++i) {
698 	if (tp->Numbers[NUMCOUNT + i] != ABSENT_NUMERIC)
699 	    result = (i + 1);
700     }
701     return result;
702 }
703 
704 static unsigned
extended_Strings(const TERMTYPE2 * tp)705 extended_Strings(const TERMTYPE2 *tp)
706 {
707     unsigned short result = 0;
708     unsigned short i;
709 
710     for (i = 0; i < tp->ext_Strings; ++i) {
711 	if (tp->Strings[STRCOUNT + i] != ABSENT_STRING)
712 	    result = (unsigned short) (i + 1);
713     }
714     return result;
715 }
716 
717 /*
718  * _nc_align_termtype() will extend entries that are referenced in a use=
719  * clause - discard the unneeded data.
720  */
721 static bool
extended_object(const TERMTYPE2 * tp)722 extended_object(const TERMTYPE2 *tp)
723 {
724     bool result = FALSE;
725 
726     if (_nc_user_definable) {
727 	result = ((extended_Booleans(tp)
728 		   + extended_Numbers(tp)
729 		   + extended_Strings(tp)) != 0);
730     }
731     return result;
732 }
733 #endif
734 
735 NCURSES_EXPORT(int)
_nc_write_object(TERMTYPE2 * tp,char * buffer,unsigned * offset,unsigned limit)736 _nc_write_object(TERMTYPE2 *tp, char *buffer, unsigned *offset, unsigned limit)
737 {
738     char *namelist;
739     size_t namelen, boolmax, nummax, strmax, numlen;
740     char zero = '\0';
741     size_t i;
742     int nextfree;
743     short offsets[MAX_ENTRY_SIZE / 2];
744     unsigned char buf[MAX_ENTRY_SIZE];
745     unsigned last_bool = BOOLWRITE;
746     unsigned last_num = NUMWRITE;
747     unsigned last_str = STRWRITE;
748 #if NCURSES_EXT_NUMBERS
749     bool need_ints = FALSE;
750     size_t (*convert_numbers) (unsigned char *, NCURSES_INT2 *, size_t);
751 #else
752 #define convert_numbers convert_shorts
753 #endif
754 
755 #if NCURSES_XNAMES
756     /*
757      * Normally we limit the list of values to exclude the "obsolete"
758      * capabilities.  However, if we are accepting extended names, add
759      * these as well, since they are used for supporting translation
760      * to/from termcap.
761      */
762     if (_nc_user_definable) {
763 	last_bool = BOOLCOUNT;
764 	last_num = NUMCOUNT;
765 	last_str = STRCOUNT;
766     }
767 #endif
768 
769     namelist = tp->term_names;
770     namelen = strlen(namelist) + 1;
771 
772     boolmax = 0;
773     for (i = 0; i < last_bool; i++) {
774 	if (tp->Booleans[i] == TRUE) {
775 	    boolmax = i + 1;
776 	}
777     }
778 
779     nummax = 0;
780     for (i = 0; i < last_num; i++) {
781 	if (tp->Numbers[i] != ABSENT_NUMERIC) {
782 	    nummax = i + 1;
783 #if NCURSES_EXT_NUMBERS
784 	    if (tp->Numbers[i] > MAX_OF_TYPE(NCURSES_COLOR_T)) {
785 		need_ints = TRUE;
786 	    }
787 #endif
788 	}
789     }
790 
791     strmax = 0;
792     for (i = 0; i < last_str; i++) {
793 	if (tp->Strings[i] != ABSENT_STRING)
794 	    strmax = i + 1;
795     }
796 
797     nextfree = compute_offsets(tp->Strings, strmax, offsets);
798 
799     /* fill in the header */
800 #if NCURSES_EXT_NUMBERS
801     if (need_ints) {
802 	convert_numbers = convert_32bit;
803 	LITTLE_ENDIAN(buf, MAGIC2);
804     } else {
805 	convert_numbers = convert_16bit;
806 	LITTLE_ENDIAN(buf, MAGIC);
807     }
808 #else
809     LITTLE_ENDIAN(buf, MAGIC);
810 #endif
811     namelen = Min(namelen, MAX_NAME_SIZE + 1);
812     LITTLE_ENDIAN(buf + 2, namelen);
813     LITTLE_ENDIAN(buf + 4, boolmax);
814     LITTLE_ENDIAN(buf + 6, nummax);
815     LITTLE_ENDIAN(buf + 8, strmax);
816     LITTLE_ENDIAN(buf + 10, nextfree);
817 
818     /* write out the header */
819     TRACE_OUT(("Header of %s @%d", namelist, *offset));
820     if (Write(buf, 12, 1) != 1
821 	|| Write(namelist, sizeof(char), namelen) != namelen) {
822 	return (ERR);
823     }
824 
825     for (i = 0; i < boolmax; i++) {
826 	if (tp->Booleans[i] == TRUE) {
827 	    buf[i] = TRUE;
828 	} else {
829 	    buf[i] = FALSE;
830 	}
831     }
832     if (Write(buf, sizeof(char), boolmax) != boolmax) {
833 	return (ERR);
834     }
835 
836     if (even_boundary(namelen + boolmax)) {
837 	return (ERR);
838     }
839 
840     TRACE_OUT(("Numerics begin at %04x", *offset));
841 
842     /* the numerics */
843     numlen = convert_numbers(buf, tp->Numbers, nummax);
844     if (Write(buf, numlen, nummax) != nummax) {
845 	return (ERR);
846     }
847 
848     TRACE_OUT(("String offsets begin at %04x", *offset));
849 
850     /* the string offsets */
851     convert_shorts(buf, offsets, strmax);
852     if (Write(buf, SIZEOF_SHORT, strmax) != strmax) {
853 	return (ERR);
854     }
855 
856     TRACE_OUT(("String table begins at %04x", *offset));
857 
858     /* the strings */
859     for (i = 0; i < strmax; i++) {
860 	if (VALID_STRING(tp->Strings[i])) {
861 	    if (!WRITE_STRING(tp->Strings[i])) {
862 		return (ERR);
863 	    }
864 	}
865     }
866 
867 #if NCURSES_XNAMES
868     if (extended_object(tp)) {
869 	unsigned ext_total = (unsigned) NUM_EXT_NAMES(tp);
870 	unsigned ext_usage = ext_total;
871 
872 	if (even_boundary(nextfree)) {
873 	    return (ERR);
874 	}
875 
876 	nextfree = compute_offsets(tp->Strings + STRCOUNT,
877 				   (size_t) tp->ext_Strings,
878 				   offsets);
879 	TRACE_OUT(("after extended string capabilities, nextfree=%d", nextfree));
880 
881 	if (tp->ext_Strings >= SIZEOF(offsets)) {
882 	    return (ERR);
883 	}
884 
885 	nextfree += compute_offsets(tp->ext_Names,
886 				    (size_t) ext_total,
887 				    offsets + tp->ext_Strings);
888 	TRACE_OUT(("after extended capnames, nextfree=%d", nextfree));
889 	strmax = tp->ext_Strings + ext_total;
890 	for (i = 0; i < tp->ext_Strings; ++i) {
891 	    if (VALID_STRING(tp->Strings[i + STRCOUNT])) {
892 		ext_usage++;
893 	    }
894 	}
895 	TRACE_OUT(("will write %u/%lu strings", ext_usage, (unsigned long) strmax));
896 
897 	/*
898 	 * Write the extended header
899 	 */
900 	LITTLE_ENDIAN(buf + 0, tp->ext_Booleans);
901 	LITTLE_ENDIAN(buf + 2, tp->ext_Numbers);
902 	LITTLE_ENDIAN(buf + 4, tp->ext_Strings);
903 	LITTLE_ENDIAN(buf + 6, ext_usage);
904 	LITTLE_ENDIAN(buf + 8, nextfree);
905 	TRACE_OUT(("WRITE extended-header @%d", *offset));
906 	if (Write(buf, 10, 1) != 1) {
907 	    return (ERR);
908 	}
909 
910 	TRACE_OUT(("WRITE %d booleans @%d", tp->ext_Booleans, *offset));
911 	if (tp->ext_Booleans
912 	    && Write(tp->Booleans + BOOLCOUNT, sizeof(char),
913 		     tp->ext_Booleans) != tp->ext_Booleans) {
914 	    return (ERR);
915 	}
916 
917 	if (even_boundary(tp->ext_Booleans)) {
918 	    return (ERR);
919 	}
920 
921 	TRACE_OUT(("WRITE %d numbers @%d", tp->ext_Numbers, *offset));
922 	if (tp->ext_Numbers) {
923 	    numlen = convert_numbers(buf, tp->Numbers + NUMCOUNT, (size_t) tp->ext_Numbers);
924 	    if (Write(buf, numlen, tp->ext_Numbers) != tp->ext_Numbers) {
925 		return (ERR);
926 	    }
927 	}
928 
929 	/*
930 	 * Convert the offsets for the ext_Strings and ext_Names tables,
931 	 * in that order.
932 	 */
933 	convert_shorts(buf, offsets, strmax);
934 	TRACE_OUT(("WRITE offsets @%d", *offset));
935 	if (Write(buf, SIZEOF_SHORT, strmax) != strmax) {
936 	    return (ERR);
937 	}
938 
939 	/*
940 	 * Write the string table after the offset tables so we do not
941 	 * have to do anything about alignment.
942 	 */
943 	for (i = 0; i < tp->ext_Strings; i++) {
944 	    if (VALID_STRING(tp->Strings[i + STRCOUNT])) {
945 		TRACE_OUT(("WRITE ext_Strings[%d]=%s", (int) i,
946 			   _nc_visbuf(tp->Strings[i + STRCOUNT])));
947 		if (!WRITE_STRING(tp->Strings[i + STRCOUNT])) {
948 		    return (ERR);
949 		}
950 	    }
951 	}
952 
953 	/*
954 	 * Write the extended names
955 	 */
956 	for (i = 0; i < ext_total; i++) {
957 	    TRACE_OUT(("WRITE ext_Names[%d]=%s", (int) i, tp->ext_Names[i]));
958 	    if (!WRITE_STRING(tp->ext_Names[i])) {
959 		return (ERR);
960 	    }
961 	}
962 
963     }
964 #endif /* NCURSES_XNAMES */
965 
966     total_written++;
967     total_parts++;
968     total_size = total_size + (int) (*offset + 1);
969     return (OK);
970 }
971 
972 /*
973  * Returns the total number of entries written by this process
974  */
975 NCURSES_EXPORT(int)
_nc_tic_written(void)976 _nc_tic_written(void)
977 {
978     TR(TRACE_DATABASE, ("_nc_tic_written %d entries, %d parts, %d size",
979 			total_written, total_parts, total_size));
980     return total_written;
981 }
982