3 * utils.c -- various utility routines
5 * This code is Copyright (c) 2006, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
15 extern int setup_signal_handlers();
18 extern void remove_registered_files_atexit();
20 extern char *mhdocdir
;
23 * We allocate space for messages (msgs array)
24 * this number of elements at a time.
28 /* Call malloc(3), exiting on NULL return. */
29 void *mh_xmalloc(size_t size
)
34 size
= 1; /* Some mallocs don't like 0. */
37 adios(NULL
, "malloc failed, size wanted: %zu", size
);
42 /* Call realloc(3), exiting on NULL return. */
43 void *mh_xrealloc(void *ptr
, size_t size
)
47 /* Copy POSIX behaviour, coping with non-POSIX systems. */
52 return mh_xmalloc(1); /* Get a unique pointer. */
55 return mh_xmalloc(size
);
57 new = realloc(ptr
, size
);
59 adios(NULL
, "realloc failed, size wanted: %zu", size
);
64 /* Call calloc(3), exiting on NULL return. */
65 void *mh_xcalloc(size_t nelem
, size_t elsize
)
69 if (!nelem
|| !elsize
)
70 return mh_xmalloc(1); /* Get a unique pointer. */
72 p
= calloc(nelem
, elsize
);
74 adios(NULL
, "calloc failed, size wanted: %zu * %zu", nelem
, elsize
);
79 /* Duplicate a NUL-terminated string, exit on failure. */
80 char *mh_xstrdup(const char *src
)
85 n
= strlen(src
) + 1; /* Ignore possibility of overflow. */
92 /* Call free(3), if ptr isn't NULL. */
93 void mh_xfree(void *ptr
)
96 free(ptr
); /* Some very old platforms can't cope with NULL. */
100 * Return the present working directory, if the current directory does not
101 * exist, or is too long, make / the pwd.
107 static char curwd
[PATH_MAX
];
109 if (!getcwd (curwd
, PATH_MAX
)) {
110 admonish (NULL
, "unable to determine working directory");
111 if (!mypath
|| !*mypath
112 || (strcpy (curwd
, mypath
), chdir (curwd
)) == -1) {
114 if (chdir (curwd
) < 0) {
115 advise (curwd
, "chdir");
121 if ((cp
= curwd
+ strlen (curwd
) - 1) > curwd
&& *cp
== '/')
128 * add -- If "s1" is NULL, this routine just creates a
129 * -- copy of "s2" into newly malloc'ed memory.
131 * -- If "s1" is not NULL, then copy the concatenation
132 * -- of "s1" and "s2" (note the order) into newly
133 * -- malloc'ed memory. Then free "s1".
136 add (const char *s2
, char *s1
)
139 size_t len1
= 0, len2
= 0;
146 cp
= mh_xmalloc (len1
+ len2
+ 1);
148 /* Copy s1 and free it */
150 memcpy (cp
, s1
, len1
);
156 memcpy (cp
+ len1
, s2
, len2
);
158 /* Now NULL terminate the string */
159 cp
[len1
+ len2
] = '\0';
166 * Append an item to a comma separated list
169 addlist (char *list
, const char *item
)
172 list
= add (", ", list
);
174 return add (item
, list
);
179 * Check to see if a folder exists.
181 int folder_exists(const char *folder
)
186 if (stat (folder
, &st
) == -1) {
187 /* The folder either doesn't exist, or we hit an error. Either way
192 /* We can see a folder with the right name */
202 * Check to see if a folder exists, if not, prompt the user to create
205 void create_folder(char *folder
, int autocreate
, void (*done_callback
)(int))
211 if (stat (folder
, &st
) == -1) {
213 adios (folder
, "error on folder");
214 if (autocreate
== 0) {
215 /* ask before creating folder */
216 cp
= concat ("Create folder \"", folder
, "\"? ", NULL
);
217 if (!read_yes_or_no_if_tty (cp
))
220 } else if (autocreate
== -1) {
221 /* do not create, so exit */
224 if (!makedir (folder
))
225 adios (NULL
, "unable to create folder %s", folder
);
231 * Return the number of digits in a nonnegative integer.
240 adios (NULL
, "oops, num_digits called with negative value");
254 * Append a message arg to an array of them, resizing it if necessary.
255 * Really a simple vector-of-(char *) maintenance routine.
258 app_msgarg(struct msgs_array
*msgs
, char *cp
)
260 if(msgs
->size
>= msgs
->max
) {
261 msgs
->max
+= MAXMSGS
;
262 msgs
->msgs
= mh_xrealloc(msgs
->msgs
,
263 msgs
->max
* sizeof(*msgs
->msgs
));
265 msgs
->msgs
[msgs
->size
++] = cp
;
269 * Append a message number to an array of them, resizing it if necessary.
270 * Like app_msgarg, but with a vector-of-ints instead.
274 app_msgnum(struct msgnum_array
*msgs
, int msgnum
)
276 if (msgs
->size
>= msgs
->max
) {
277 msgs
->max
+= MAXMSGS
;
278 msgs
->msgnums
= mh_xrealloc(msgs
->msgnums
,
279 msgs
->max
* sizeof(*msgs
->msgnums
));
281 msgs
->msgnums
[msgs
->size
++] = msgnum
;
284 /* Open a form or components file */
286 open_form(char **form
, char *def
)
290 if ((in
= open (etcpath (*form
), O_RDONLY
)) == NOTOK
)
291 adios (*form
, "unable to open form file");
293 if ((in
= open (etcpath (def
), O_RDONLY
)) == NOTOK
)
294 adios (def
, "unable to open default components file");
302 * Finds first occurrence of str in buf. buf is not a C string but a
303 * byte array of length buflen. str is a null-terminated C string.
304 * find_str() does not modify buf but passes back a non-const char *
305 * pointer so that the caller can modify it.
308 find_str (const char buf
[], size_t buflen
, const char *str
) {
309 const size_t len
= strlen (str
);
312 for (i
= 0; i
+ len
<= buflen
; ++i
, ++buf
) {
313 if (! memcmp (buf
, str
, len
)) return (char *) buf
;
321 * Finds last occurrence of str in buf. buf is not a C string but a
322 * byte array of length buflen. str is a null-terminated C string.
323 * find_str() does not modify buf but passes back a non-const char *
324 * pointer so that the caller can modify it.
327 rfind_str (const char buf
[], size_t buflen
, const char *str
) {
328 const size_t len
= strlen (str
);
331 for (i
= 0, buf
+= buflen
- len
; i
+ len
<= buflen
; ++i
, --buf
) {
332 if (! memcmp (buf
, str
, len
)) return (char *) buf
;
339 /* POSIX doesn't have strcasestr() so emulate it. */
341 nmh_strcasestr (const char *s1
, const char *s2
) {
342 const size_t len
= strlen (s2
);
344 if (isupper ((unsigned char) s2
[0]) || islower ((unsigned char)s2
[0])) {
346 first
[0] = (char) toupper ((unsigned char) s2
[0]);
347 first
[1] = (char) tolower ((unsigned char) s2
[0]);
350 for (s1
= strpbrk (s1
, first
); s1
; s1
= strpbrk (++s1
, first
)) {
351 if (! strncasecmp (s1
, s2
, len
)) return (char *) s1
;
354 for (s1
= strchr (s1
, s2
[0]); s1
; s1
= strchr (++s1
, s2
[0])) {
355 if (! strncasecmp (s1
, s2
, len
)) return (char *) s1
;
364 nmh_init(const char *argv0
, int read_context
) {
365 if (! setlocale(LC_ALL
, "")) {
366 admonish(NULL
, "setlocale failed, check your LC_ALL, LC_CTYPE, and "
367 "LANG environment variables");
370 invo_name
= r1bindex ((char *) argv0
, '/');
372 if (setup_signal_handlers()) {
373 admonish("sigaction", "unable to set up signal handlers");
376 /* POSIX atexit() does not define any error conditions. */
377 if (atexit(remove_registered_files_atexit
)) {
378 admonish("atexit", "unable to register atexit function");
381 /* Read context, if supposed to. */
383 int allow_version_check
= 1;
384 int check_older_version
= 0;
389 if (read_context
!= 1 ||
390 ((cp
= context_find ("Welcome")) && strcasecmp (cp
, "disable") == 0)) {
391 allow_version_check
= 0;
392 } else if ((cp
= getenv ("MHCONTEXT")) != NULL
&& *cp
!= '\0') {
393 /* Context file comes from $MHCONTEXT, so only print the message
394 if the context file has an older version. If it does, or if it
395 doesn't have a version at all, update the version. */
396 check_older_version
= 1;
399 /* Check to see if the user is running a different (or older, if
400 specified) version of nmh than they had run bfore, and notify them
401 if so. But only if read_context was set to a value to enable. */
402 if (allow_version_check
&& isatty (fileno (stdin
)) &&
403 isatty (fileno (stdout
)) && isatty (fileno (stderr
))) {
404 if (nmh_version_changed (check_older_version
)) {
405 printf ("==================================================="
406 "=====================\n");
407 printf ("Welcome to nmh version %s\n\n", VERSION
);
408 printf ("See the release notes in %s/NEWS\n\n",
410 print_intro (stdout
, 1);
411 printf ("\nThis message will not be repeated until "
412 "nmh is next updated.\n");
413 printf ("==================================================="
414 "=====================\n\n");
416 fputs ("Press enter to continue: ", stdout
);
424 int status
= context_foil(NULL
);
426 advise("", "failed to create minimal profile/context");
434 * Check stored version, and return 1 if out-of-date or non-existent.
435 * Because the output of "mhparam version" is prefixed with "nmh-",
436 * use that prefix here.
439 nmh_version_changed (int older
) {
440 const char *const context_version
= context_find("Version");
443 /* Convert the version strings to floats and compare them. This will
444 break for versions with multiple decimal points, etc. */
445 const float current_version
= strtof (VERSION
, NULL
);
446 const float old_version
=
447 context_version
&& strncmp (context_version
, "nmh-", 4) == 0
448 ? strtof (context_version
+ 4, NULL
)
451 if (context_version
== NULL
|| old_version
< current_version
) {
452 context_replace ("Version", "nmh-" VERSION
);
455 return old_version
< current_version
? 1 : 0;
457 if (context_version
== NULL
|| strcmp(context_version
, "nmh-" VERSION
) != 0) {
458 context_replace ("Version", "nmh-" VERSION
);
468 /* Returns copy of argument str with all characters converted to upper
469 case, and trimmed whitespace (see cpytrim()) . */
471 upcase (const char *str
) {
472 char *up
= cpytrim (str
);
475 for (cp
= up
; *cp
; ++cp
) { *cp
= toupper ((unsigned char) *cp
); }
482 * Scan for any 8-bit characters. Return 1 if they exist.
484 * Scan up until the given endpoint (but not the actual endpoint itself).
485 * If the endpoint is NULL, scan until a '\0' is reached.
489 contains8bit(const char *start
, const char *end
)
494 while (*start
!= '\0' && (!end
|| (start
< end
)))
495 if (! isascii((unsigned char) *start
++))
503 * See if input has any 8-bit bytes.
506 scan_input (int fd
, int *eightbit
) {
511 lseek (fd
, (off_t
) 0, SEEK_SET
);
513 while ((state
= read (fd
, buf
, sizeof buf
)) > 0) {
514 if (contains8bit (buf
, buf
+ state
)) {
520 return state
== NOTOK
? NOTOK
: OK
;