1 /* utils.c -- various utility routines
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.
9 #include "context_foil.h"
10 #include "context_replace.h"
11 #include "context_read.h"
12 #include "context_find.h"
13 #include "print_help.h"
16 #include "h/signals.h"
21 #include "read_line.h"
23 extern char *mhdocdir
;
25 /* plurals gives the letter ess to indicate a plural noun, or an empty
26 * string as plurals+1 for the singular noun. Used by the PLURALS
28 const char plurals
[] = "s";
31 * We allocate space for messages (msgs array)
32 * this number of elements at a time.
36 /* Call malloc(3), exiting on NULL return. */
38 mh_xmalloc(size_t size
)
43 size
= 1; /* Some mallocs don't like 0. */
46 die("malloc failed, size wanted: %zu", size
);
51 /* Call realloc(3), exiting on NULL return. */
53 mh_xrealloc(void *ptr
, size_t size
)
57 /* Copy POSIX behaviour, coping with non-POSIX systems. */
60 return mh_xmalloc(1); /* Get a unique pointer. */
63 return mh_xmalloc(size
);
65 new = realloc(ptr
, size
);
67 die("realloc failed, size wanted: %zu", size
);
72 /* Call calloc(3), exiting on NULL return. */
74 mh_xcalloc(size_t nelem
, size_t elsize
)
78 if (!nelem
|| !elsize
)
79 return mh_xmalloc(1); /* Get a unique pointer. */
81 p
= calloc(nelem
, elsize
);
83 die("calloc failed, size wanted: %zu * %zu", nelem
, elsize
);
88 /* Duplicate a NUL-terminated string, exit on failure. */
90 mh_xstrdup(const char *src
)
95 n
= strlen(src
) + 1; /* Ignore possibility of overflow. */
103 * Return the present working directory, if the current directory does not
104 * exist, or is too long, make / the pwd.
110 static char curwd
[PATH_MAX
];
112 if (!getcwd (curwd
, PATH_MAX
)) {
113 inform("unable to determine working directory, continuing...");
114 if (!mypath
|| !*mypath
115 || (strcpy (curwd
, mypath
), chdir (curwd
)) == -1) {
117 if (chdir (curwd
) < 0) {
118 advise (curwd
, "chdir");
124 if ((cp
= curwd
+ strlen (curwd
) - 1) > curwd
&& *cp
== '/')
130 /* add returns a newly malloc'd string, exiting on failure. The order
131 * of the parameters is unusual. A NULL parameter is treated as an
132 * empty string. s1 is free'd. Use mh_xstrdup(s) rather than add(s,
133 * NULL), with FENDNULL() if s might be NULL.
135 * add(NULL, NULL) -> ""
136 * add(NULL, "foo") -> "foo"
137 * add("bar", NULL) -> "bar"
138 * add("bar", "foo") -> "foobar"
141 add (const char *s2
, char *s1
)
144 size_t len1
= 0, len2
= 0;
151 cp
= mh_xmalloc (len1
+ len2
+ 1);
153 /* Copy s1 and free it */
155 memcpy (cp
, s1
, len1
);
161 memcpy (cp
+ len1
, s2
, len2
);
163 /* Now NULL terminate the string */
164 cp
[len1
+ len2
] = '\0';
171 * Append an item to a comma separated list
174 addlist (char *list
, const char *item
)
177 list
= add (", ", list
);
179 return add (item
, list
);
184 * Check to see if a folder exists.
187 folder_exists(const char *folder
)
191 return stat(folder
, &st
) != -1;
196 * Check to see if a folder exists, if not, prompt the user to create
200 create_folder(char *folder
, int autocreate
, void (*done_callback
)(int))
206 if (stat (folder
, &st
) == -1) {
208 adios (folder
, "error on folder");
209 if (autocreate
== 0) {
210 /* ask before creating folder */
211 cp
= concat ("Create folder \"", folder
, "\"? ", NULL
);
212 if (!read_yes_or_no_if_tty (cp
))
215 } else if (autocreate
== -1) {
216 /* do not create, so exit */
219 if (!makedir (folder
))
220 die("unable to create folder %s", folder
);
226 * Return the number of digits in a nonnegative integer.
235 die("oops, num_digits called with negative value");
249 * Append a message arg to an array of them, resizing it if necessary.
250 * Really a simple vector-of-(char *) maintenance routine.
253 app_msgarg(struct msgs_array
*msgs
, char *cp
)
255 if(msgs
->size
>= msgs
->max
) {
256 msgs
->max
+= MAXMSGS
;
257 msgs
->msgs
= mh_xrealloc(msgs
->msgs
,
258 msgs
->max
* sizeof(*msgs
->msgs
));
260 msgs
->msgs
[msgs
->size
++] = cp
;
264 * Append a message number to an array of them, resizing it if necessary.
265 * Like app_msgarg, but with a vector-of-ints instead.
269 app_msgnum(struct msgnum_array
*msgs
, int msgnum
)
271 if (msgs
->size
>= msgs
->max
) {
272 msgs
->max
+= MAXMSGS
;
273 msgs
->msgnums
= mh_xrealloc(msgs
->msgnums
,
274 msgs
->max
* sizeof(*msgs
->msgnums
));
276 msgs
->msgnums
[msgs
->size
++] = msgnum
;
281 * Finds first occurrence of str in buf. buf is not a C string but a
282 * byte array of length buflen. str is a null-terminated C string.
283 * find_str() does not modify buf but passes back a non-const char *
284 * pointer so that the caller can modify it.
287 find_str (const char buf
[], size_t buflen
, const char *str
)
289 const size_t len
= strlen (str
);
292 for (i
= 0; i
+ len
<= buflen
; ++i
, ++buf
) {
293 if (! memcmp (buf
, str
, len
)) return (char *) buf
;
301 * Finds last occurrence of str in buf. buf is not a C string but a
302 * byte array of length buflen. str is a null-terminated C string.
303 * find_str() does not modify buf but passes back a non-const char *
304 * pointer so that the caller can modify it.
307 rfind_str (const char buf
[], size_t buflen
, const char *str
)
309 const size_t len
= strlen (str
);
312 for (i
= 0, buf
+= buflen
- len
; i
+ len
<= buflen
; ++i
, --buf
) {
313 if (! memcmp (buf
, str
, len
)) return (char *) buf
;
320 /* POSIX doesn't have strcasestr() so emulate it. */
322 nmh_strcasestr (const char *s1
, const char *s2
)
324 const size_t len
= strlen (s2
);
326 if (isupper ((unsigned char) s2
[0]) || islower ((unsigned char)s2
[0])) {
328 first
[0] = (char) toupper ((unsigned char) s2
[0]);
329 first
[1] = (char) tolower ((unsigned char) s2
[0]);
332 for (s1
= strpbrk (s1
, first
); s1
; s1
= strpbrk (++s1
, first
)) {
333 if (! strncasecmp (s1
, s2
, len
)) return (char *) s1
;
336 for (s1
= strchr (s1
, s2
[0]); s1
; s1
= strchr (++s1
, s2
[0])) {
337 if (! strncasecmp (s1
, s2
, len
)) return (char *) s1
;
345 /* truncpy copies at most size - 1 chars from non-NULL src to non-NULL,
346 * non-overlapping, dst, and ensures dst is NUL terminated. If size is
347 * zero then it aborts as dst cannot be NUL terminated.
349 * It's to be used when truncation is intended and correct, e.g.
350 * reporting a possibly very long external string back to the user. One
351 * of its advantages over strncpy(3) is it doesn't pad in the common
352 * case of no truncation. */
354 trunccpy(char *dst
, const char *src
, size_t size
)
357 inform("trunccpy: zero-length destination: \"%.20s\"",
362 if (strnlen(src
, size
) < size
) {
365 memcpy(dst
, src
, size
- 1);
366 dst
[size
- 1] = '\0';
371 /* has_prefix returns true if non-NULL s starts with non-NULL prefix. */
373 has_prefix(const char *s
, const char *prefix
)
375 while (*s
&& *s
== *prefix
) {
380 return *prefix
== '\0';
384 /* has_suffix returns true if non-NULL s ends with non-NULL suffix. */
386 has_suffix(const char *s
, const char *suffix
)
391 lsuf
= strlen(suffix
);
393 return lsuf
<= ls
&& !strcmp(s
+ ls
- lsuf
, suffix
);
397 /* has_suffix_c returns true if non-NULL string s ends with a c before the
398 * terminating NUL. */
400 has_suffix_c(const char *s
, int c
)
402 return *s
&& s
[strlen(s
) - 1] == c
;
406 /* trim_suffix_c deletes c from the end of non-NULL string s if it's
407 * present, shortening s by 1. Only one instance of c is removed. */
409 trim_suffix_c(char *s
, int c
)
420 /* to_lower runs all of s through tolower(3). */
426 for (b
= (unsigned char *)s
; (*b
= tolower(*b
)); b
++)
431 /* to_upper runs all of s through toupper(3). */
437 for (b
= (unsigned char *)s
; (*b
= toupper(*b
)); b
++)
443 nmh_init(const char *argv0
, bool read_context
, bool check_version
)
448 invo_name
= r1bindex ((char *) argv0
, '/');
450 if (setup_signal_handlers()) {
451 admonish("sigaction", "unable to set up signal handlers");
454 /* POSIX atexit() does not define any error conditions. */
455 if (atexit(remove_registered_files_atexit
)) {
456 admonish("atexit", "unable to register atexit function");
459 /* Read context, if supposed to. */
465 bool allow_version_check
= true;
466 bool check_older_version
= false;
467 if (!check_version
||
468 ((cp
= context_find ("Welcome")) && strcasecmp (cp
, "disable") == 0)) {
469 allow_version_check
= false;
470 } else if ((cp
= getenv ("MHCONTEXT")) != NULL
&& *cp
!= '\0') {
471 /* Context file comes from $MHCONTEXT, so only print the message
472 if the context file has an older version. If it does, or if it
473 doesn't have a version at all, update the version. */
474 check_older_version
= true;
477 /* Check to see if the user is running a different (or older, if
478 specified) version of nmh than they had run before, and notify them
480 if (allow_version_check
&& isatty (fileno (stdin
)) &&
481 isatty (fileno (stdout
)) && isatty (fileno (stderr
))) {
482 if (nmh_version_changed (check_older_version
)) {
483 printf ("==================================================="
484 "=====================\n");
485 printf ("Welcome to nmh version %s\n\n", VERSION
);
486 printf ("See the release notes in %s/NEWS\n\n",
488 print_intro (stdout
, 1);
489 printf ("\nThis message will not be repeated until "
490 "nmh is next updated.\n");
491 printf ("==================================================="
492 "=====================\n\n");
494 fputs ("Press enter to continue: ", stdout
);
500 if ((status
= context_foil(NULL
)) != OK
) {
501 advise("", "failed to create minimal profile/context");
505 /* Allow the user to set a locale in their profile. Otherwise, use the
506 "" string to pull it from their environment, see setlocale(3). */
507 if ((locale
= context_find ("locale")) == NULL
) {
511 if (! setlocale (LC_ALL
, locale
)) {
512 inform("setlocale failed, check your LC_ALL, LC_CTYPE, and LANG "
513 "environment variables, continuing...");
521 * Check stored version, and return 1 if out-of-date or non-existent.
522 * Because the output of "mhparam version" is prefixed with "nmh-",
523 * use that prefix here.
526 nmh_version_changed (int older
)
528 const char *const context_version
= context_find("Version");
531 /* Convert the version strings to floats and compare them. This will
532 break for versions with multiple decimal points, etc. */
533 const float current_version
= strtof (VERSION
, NULL
);
534 const float old_version
=
535 context_version
&& has_prefix(context_version
, "nmh-")
536 ? strtof (context_version
+ 4, NULL
)
539 if (context_version
== NULL
|| old_version
< current_version
) {
540 context_replace ("Version", "nmh-" VERSION
);
543 return old_version
< current_version
;
546 if (context_version
== NULL
|| strcmp(context_version
, "nmh-" VERSION
) != 0) {
547 context_replace ("Version", "nmh-" VERSION
);
555 /* contains8bit returns true if any byte from start onwards fails
556 * isascii(3), i.e. is outside [0, 0x7f]. If start is NULL it returns
557 * false. Bytes are examined until a NUL byte, or, if end is not NULL,
558 * whilst start is before end. */
560 contains8bit(const char *start
, const char *end
)
570 while (p
< end
&& (c
= (*p
++)))
571 if (!isascii((unsigned char)c
))
575 if (!isascii((unsigned char)c
))
584 * See if input has any 8-bit bytes.
587 scan_input (int fd
, int *eightbit
)
593 lseek(fd
, 0, SEEK_SET
);
595 while ((state
= read (fd
, buf
, sizeof buf
)) > 0) {
596 if (contains8bit (buf
, buf
+ state
)) {
602 return state
== NOTOK
? NOTOK
: OK
;
607 * Convert an int to a char string.
612 return m_strn(value
, 0);
617 * Convert an int to a char string, of limited width if > 0.
620 /* SIZE(n) includes NUL. n must just be digits, not an equation. */
621 #define SIZE(n) (sizeof STR(n))
624 m_strn(int value
, unsigned int width
)
626 /* Need to include space for negative sign. But don't use INT_MIN
627 because it could be a macro that would fool SIZE(n). */
628 static char buffer
[SIZE(-INT_MAX
)];
629 const int num_chars
= snprintf(buffer
, sizeof buffer
, "%d", value
);
631 return num_chars
> 0 && (width
== 0 || (unsigned int) num_chars
<= width
)