]> diplodocus.org Git - nmh/blob - sbr/utils.c
c385b45b09999cb71ad1350428d9c54318e20a12
[nmh] / sbr / utils.c
1 /* utils.c -- various utility routines
2 *
3 * This code is Copyright (c) 2006, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include "h/mh.h"
9 #include "error.h"
10 #include "h/utils.h"
11 #include "h/signals.h"
12 #include "m_mktemp.h"
13 #include "makedir.h"
14 #include <fcntl.h>
15 #include <limits.h>
16 #include "read_line.h"
17
18 extern char *mhdocdir;
19
20 /* plurals gives the letter ess to indicate a plural noun, or an empty
21 * string as plurals+1 for the singular noun. Used by the PLURALS
22 * macro. */
23 const char plurals[] = "s";
24
25 /*
26 * We allocate space for messages (msgs array)
27 * this number of elements at a time.
28 */
29 #define MAXMSGS 256
30
31 /* Call malloc(3), exiting on NULL return. */
32 void *
33 mh_xmalloc(size_t size)
34 {
35 void *p;
36
37 if (size == 0)
38 size = 1; /* Some mallocs don't like 0. */
39 p = malloc(size);
40 if (!p)
41 die("malloc failed, size wanted: %zu", size);
42
43 return p;
44 }
45
46 /* Call realloc(3), exiting on NULL return. */
47 void *
48 mh_xrealloc(void *ptr, size_t size)
49 {
50 void *new;
51
52 /* Copy POSIX behaviour, coping with non-POSIX systems. */
53 if (size == 0) {
54 free(ptr);
55 return mh_xmalloc(1); /* Get a unique pointer. */
56 }
57 if (!ptr)
58 return mh_xmalloc(size);
59
60 new = realloc(ptr, size);
61 if (!new)
62 die("realloc failed, size wanted: %zu", size);
63
64 return new;
65 }
66
67 /* Call calloc(3), exiting on NULL return. */
68 void *
69 mh_xcalloc(size_t nelem, size_t elsize)
70 {
71 void *p;
72
73 if (!nelem || !elsize)
74 return mh_xmalloc(1); /* Get a unique pointer. */
75
76 p = calloc(nelem, elsize);
77 if (!p)
78 die("calloc failed, size wanted: %zu * %zu", nelem, elsize);
79
80 return p;
81 }
82
83 /* Duplicate a NUL-terminated string, exit on failure. */
84 char *
85 mh_xstrdup(const char *src)
86 {
87 size_t n;
88 char *dest;
89
90 n = strlen(src) + 1; /* Ignore possibility of overflow. */
91 dest = mh_xmalloc(n);
92 memcpy(dest, src, n);
93
94 return dest;
95 }
96
97 /*
98 * Return the present working directory, if the current directory does not
99 * exist, or is too long, make / the pwd.
100 */
101 char *
102 pwd(void)
103 {
104 char *cp;
105 static char curwd[PATH_MAX];
106
107 if (!getcwd (curwd, PATH_MAX)) {
108 inform("unable to determine working directory, continuing...");
109 if (!mypath || !*mypath
110 || (strcpy (curwd, mypath), chdir (curwd)) == -1) {
111 strcpy (curwd, "/");
112 if (chdir (curwd) < 0) {
113 advise (curwd, "chdir");
114 }
115 }
116 return curwd;
117 }
118
119 if ((cp = curwd + strlen (curwd) - 1) > curwd && *cp == '/')
120 *cp = '\0';
121
122 return curwd;
123 }
124
125 /* add returns a newly malloc'd string, exiting on failure. The order
126 * of the parameters is unusual. A NULL parameter is treated as an
127 * empty string. s1 is free'd. Use mh_xstrdup(s) rather than add(s,
128 * NULL), with FENDNULL() if s might be NULL.
129 *
130 * add(NULL, NULL) -> ""
131 * add(NULL, "foo") -> "foo"
132 * add("bar", NULL) -> "bar"
133 * add("bar", "foo") -> "foobar"
134 */
135 char *
136 add (const char *s2, char *s1)
137 {
138 char *cp;
139 size_t len1 = 0, len2 = 0;
140
141 if (s1)
142 len1 = strlen (s1);
143 if (s2)
144 len2 = strlen (s2);
145
146 cp = mh_xmalloc (len1 + len2 + 1);
147
148 /* Copy s1 and free it */
149 if (s1) {
150 memcpy (cp, s1, len1);
151 free (s1);
152 }
153
154 /* Copy s2 */
155 if (s2)
156 memcpy (cp + len1, s2, len2);
157
158 /* Now NULL terminate the string */
159 cp[len1 + len2] = '\0';
160
161 return cp;
162 }
163
164 /*
165 * addlist
166 * Append an item to a comma separated list
167 */
168 char *
169 addlist (char *list, const char *item)
170 {
171 if (list)
172 list = add (", ", list);
173
174 return add (item, list);
175 }
176
177 /*
178 * folder_exists
179 * Check to see if a folder exists.
180 */
181 int
182 folder_exists(const char *folder)
183 {
184 struct stat st;
185
186 return stat(folder, &st) != -1;
187 }
188
189 /*
190 * create_folder
191 * Check to see if a folder exists, if not, prompt the user to create
192 * it.
193 */
194 void
195 create_folder(char *folder, int autocreate, void (*done_callback)(int))
196 {
197 struct stat st;
198 extern int errno;
199 char *cp;
200
201 if (stat (folder, &st) == -1) {
202 if (errno != ENOENT)
203 adios (folder, "error on folder");
204 if (autocreate == 0) {
205 /* ask before creating folder */
206 cp = concat ("Create folder \"", folder, "\"? ", NULL);
207 if (!read_yes_or_no_if_tty (cp))
208 done_callback (1);
209 free (cp);
210 } else if (autocreate == -1) {
211 /* do not create, so exit */
212 done_callback (1);
213 }
214 if (!makedir (folder))
215 die("unable to create folder %s", folder);
216 }
217 }
218
219 /*
220 * num_digits
221 * Return the number of digits in a nonnegative integer.
222 */
223 int
224 num_digits (int n)
225 {
226 int ndigits = 0;
227
228 /* Sanity check */
229 if (n < 0)
230 die("oops, num_digits called with negative value");
231
232 if (n == 0)
233 return 1;
234
235 while (n) {
236 n /= 10;
237 ndigits++;
238 }
239
240 return ndigits;
241 }
242
243 /*
244 * Append a message arg to an array of them, resizing it if necessary.
245 * Really a simple vector-of-(char *) maintenance routine.
246 */
247 void
248 app_msgarg(struct msgs_array *msgs, char *cp)
249 {
250 if(msgs->size >= msgs->max) {
251 msgs->max += MAXMSGS;
252 msgs->msgs = mh_xrealloc(msgs->msgs,
253 msgs->max * sizeof(*msgs->msgs));
254 }
255 msgs->msgs[msgs->size++] = cp;
256 }
257
258 /*
259 * Append a message number to an array of them, resizing it if necessary.
260 * Like app_msgarg, but with a vector-of-ints instead.
261 */
262
263 void
264 app_msgnum(struct msgnum_array *msgs, int msgnum)
265 {
266 if (msgs->size >= msgs->max) {
267 msgs->max += MAXMSGS;
268 msgs->msgnums = mh_xrealloc(msgs->msgnums,
269 msgs->max * sizeof(*msgs->msgnums));
270 }
271 msgs->msgnums[msgs->size++] = msgnum;
272 }
273
274
275 /*
276 * Finds first occurrence of str in buf. buf is not a C string but a
277 * byte array of length buflen. str is a null-terminated C string.
278 * find_str() does not modify buf but passes back a non-const char *
279 * pointer so that the caller can modify it.
280 */
281 char *
282 find_str (const char buf[], size_t buflen, const char *str)
283 {
284 const size_t len = strlen (str);
285 size_t i;
286
287 for (i = 0; i + len <= buflen; ++i, ++buf) {
288 if (! memcmp (buf, str, len)) return (char *) buf;
289 }
290
291 return NULL;
292 }
293
294
295 /*
296 * Finds last occurrence of str in buf. buf is not a C string but a
297 * byte array of length buflen. str is a null-terminated C string.
298 * find_str() does not modify buf but passes back a non-const char *
299 * pointer so that the caller can modify it.
300 */
301 char *
302 rfind_str (const char buf[], size_t buflen, const char *str)
303 {
304 const size_t len = strlen (str);
305 size_t i;
306
307 for (i = 0, buf += buflen - len; i + len <= buflen; ++i, --buf) {
308 if (! memcmp (buf, str, len)) return (char *) buf;
309 }
310
311 return NULL;
312 }
313
314
315 /* POSIX doesn't have strcasestr() so emulate it. */
316 char *
317 nmh_strcasestr (const char *s1, const char *s2)
318 {
319 const size_t len = strlen (s2);
320
321 if (isupper ((unsigned char) s2[0]) || islower ((unsigned char)s2[0])) {
322 char first[3];
323 first[0] = (char) toupper ((unsigned char) s2[0]);
324 first[1] = (char) tolower ((unsigned char) s2[0]);
325 first[2] = '\0';
326
327 for (s1 = strpbrk (s1, first); s1; s1 = strpbrk (++s1, first)) {
328 if (! strncasecmp (s1, s2, len)) return (char *) s1;
329 }
330 } else {
331 for (s1 = strchr (s1, s2[0]); s1; s1 = strchr (++s1, s2[0])) {
332 if (! strncasecmp (s1, s2, len)) return (char *) s1;
333 }
334 }
335
336 return NULL;
337 }
338
339
340 /* truncpy copies at most size - 1 chars from non-NULL src to non-NULL,
341 * non-overlapping, dst, and ensures dst is NUL terminated. If size is
342 * zero then it aborts as dst cannot be NUL terminated.
343 *
344 * It's to be used when truncation is intended and correct, e.g.
345 * reporting a possibly very long external string back to the user. One
346 * of its advantages over strncpy(3) is it doesn't pad in the common
347 * case of no truncation. */
348 void
349 trunccpy(char *dst, const char *src, size_t size)
350 {
351 if (!size) {
352 inform("trunccpy: zero-length destination: \"%.20s\"",
353 src ? src : "null");
354 abort();
355 }
356
357 if (strnlen(src, size) < size) {
358 strcpy(dst, src);
359 } else {
360 memcpy(dst, src, size - 1);
361 dst[size - 1] = '\0';
362 }
363 }
364
365
366 /* has_prefix returns true if non-NULL s starts with non-NULL prefix. */
367 bool
368 has_prefix(const char *s, const char *prefix)
369 {
370 while (*s && *s == *prefix) {
371 s++;
372 prefix++;
373 }
374
375 return *prefix == '\0';
376 }
377
378
379 /* has_suffix returns true if non-NULL s ends with non-NULL suffix. */
380 bool
381 has_suffix(const char *s, const char *suffix)
382 {
383 size_t ls, lsuf;
384
385 ls = strlen(s);
386 lsuf = strlen(suffix);
387
388 return lsuf <= ls && !strcmp(s + ls - lsuf, suffix);
389 }
390
391
392 /* has_suffix_c returns true if non-NULL string s ends with a c before the
393 * terminating NUL. */
394 bool
395 has_suffix_c(const char *s, int c)
396 {
397 return *s && s[strlen(s) - 1] == c;
398 }
399
400
401 /* trim_suffix_c deletes c from the end of non-NULL string s if it's
402 * present, shortening s by 1. Only one instance of c is removed. */
403 void
404 trim_suffix_c(char *s, int c)
405 {
406 if (!*s)
407 return;
408
409 s += strlen(s) - 1;
410 if (*s == c)
411 *s = '\0';
412 }
413
414
415 /* to_lower runs all of s through tolower(3). */
416 void
417 to_lower(char *s)
418 {
419 unsigned char *b;
420
421 for (b = (unsigned char *)s; (*b = tolower(*b)); b++)
422 ;
423 }
424
425
426 /* to_upper runs all of s through toupper(3). */
427 void
428 to_upper(char *s)
429 {
430 unsigned char *b;
431
432 for (b = (unsigned char *)s; (*b = toupper(*b)); b++)
433 ;
434 }
435
436
437 int
438 nmh_init(const char *argv0, bool read_context, bool check_version)
439 {
440 int status = OK;
441 char *locale;
442
443 invo_name = r1bindex ((char *) argv0, '/');
444
445 if (setup_signal_handlers()) {
446 admonish("sigaction", "unable to set up signal handlers");
447 }
448
449 /* POSIX atexit() does not define any error conditions. */
450 if (atexit(remove_registered_files_atexit)) {
451 admonish("atexit", "unable to register atexit function");
452 }
453
454 /* Read context, if supposed to. */
455 if (read_context) {
456 char *cp;
457
458 context_read();
459
460 bool allow_version_check = true;
461 bool check_older_version = false;
462 if (!check_version ||
463 ((cp = context_find ("Welcome")) && strcasecmp (cp, "disable") == 0)) {
464 allow_version_check = false;
465 } else if ((cp = getenv ("MHCONTEXT")) != NULL && *cp != '\0') {
466 /* Context file comes from $MHCONTEXT, so only print the message
467 if the context file has an older version. If it does, or if it
468 doesn't have a version at all, update the version. */
469 check_older_version = true;
470 }
471
472 /* Check to see if the user is running a different (or older, if
473 specified) version of nmh than they had run before, and notify them
474 if so. */
475 if (allow_version_check && isatty (fileno (stdin)) &&
476 isatty (fileno (stdout)) && isatty (fileno (stderr))) {
477 if (nmh_version_changed (check_older_version)) {
478 printf ("==================================================="
479 "=====================\n");
480 printf ("Welcome to nmh version %s\n\n", VERSION);
481 printf ("See the release notes in %s/NEWS\n\n",
482 mhdocdir);
483 print_intro (stdout, 1);
484 printf ("\nThis message will not be repeated until "
485 "nmh is next updated.\n");
486 printf ("==================================================="
487 "=====================\n\n");
488
489 fputs ("Press enter to continue: ", stdout);
490 (void) read_line ();
491 putchar ('\n');
492 }
493 }
494 } else {
495 if ((status = context_foil(NULL)) != OK) {
496 advise("", "failed to create minimal profile/context");
497 }
498 }
499
500 /* Allow the user to set a locale in their profile. Otherwise, use the
501 "" string to pull it from their environment, see setlocale(3). */
502 if ((locale = context_find ("locale")) == NULL) {
503 locale = "";
504 }
505
506 if (! setlocale (LC_ALL, locale)) {
507 inform("setlocale failed, check your LC_ALL, LC_CTYPE, and LANG "
508 "environment variables, continuing...");
509 }
510
511 return status;
512 }
513
514
515 /*
516 * Check stored version, and return 1 if out-of-date or non-existent.
517 * Because the output of "mhparam version" is prefixed with "nmh-",
518 * use that prefix here.
519 */
520 int
521 nmh_version_changed (int older)
522 {
523 const char *const context_version = context_find("Version");
524
525 if (older) {
526 /* Convert the version strings to floats and compare them. This will
527 break for versions with multiple decimal points, etc. */
528 const float current_version = strtof (VERSION, NULL);
529 const float old_version =
530 context_version && has_prefix(context_version, "nmh-")
531 ? strtof (context_version + 4, NULL)
532 : 99999999;
533
534 if (context_version == NULL || old_version < current_version) {
535 context_replace ("Version", "nmh-" VERSION);
536 }
537
538 return old_version < current_version;
539 }
540
541 if (context_version == NULL || strcmp(context_version, "nmh-" VERSION) != 0) {
542 context_replace ("Version", "nmh-" VERSION);
543 return 1;
544 }
545
546 return 0;
547 }
548
549
550 /* contains8bit returns true if any byte from start onwards fails
551 * isascii(3), i.e. is outside [0, 0x7f]. If start is NULL it returns
552 * false. Bytes are examined until a NUL byte, or, if end is not NULL,
553 * whilst start is before end. */
554 bool
555 contains8bit(const char *start, const char *end)
556 {
557 const char *p;
558 char c;
559
560 if (!start)
561 return false;
562
563 p = start;
564 if (end) {
565 while (p < end && (c = (*p++)))
566 if (!isascii((unsigned char)c))
567 return true;
568 } else {
569 while ((c = (*p++)))
570 if (!isascii((unsigned char)c))
571 return true;
572 }
573
574 return false;
575 }
576
577
578 /*
579 * See if input has any 8-bit bytes.
580 */
581 int
582 scan_input (int fd, int *eightbit)
583 {
584 int state;
585 char buf[BUFSIZ];
586
587 *eightbit = 0;
588 lseek(fd, 0, SEEK_SET);
589
590 while ((state = read (fd, buf, sizeof buf)) > 0) {
591 if (contains8bit (buf, buf + state)) {
592 *eightbit = 1;
593 return OK;
594 }
595 }
596
597 return state == NOTOK ? NOTOK : OK;
598 }
599
600
601 /*
602 * Convert an int to a char string.
603 */
604 char *
605 m_str(int value)
606 {
607 return m_strn(value, 0);
608 }
609
610
611 /*
612 * Convert an int to a char string, of limited width if > 0.
613 */
614 #define STR(s) #s
615 /* SIZE(n) includes NUL. n must just be digits, not an equation. */
616 #define SIZE(n) (sizeof STR(n))
617
618 char *
619 m_strn(int value, unsigned int width)
620 {
621 /* Need to include space for negative sign. But don't use INT_MIN
622 because it could be a macro that would fool SIZE(n). */
623 static char buffer[SIZE(-INT_MAX)];
624 const int num_chars = snprintf(buffer, sizeof buffer, "%d", value);
625
626 return num_chars > 0 && (width == 0 || (unsigned int) num_chars <= width)
627 ? buffer
628 : "?";
629 }