+ dba_array_start(dba->pages);
+ if (dba_array_next(dba->pages) == NULL) {
+ if (unlink(MANDOC_DB) == -1)
+ say(MANDOC_DB, "&unlink");
+ return;
+ }
+
+ /*
+ * Build the database in a temporary file,
+ * then atomically move it into place.
+ */
+
+ if (dba_write(MANDOC_DB "~", dba) != -1) {
+ if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "&rename");
+ unlink(MANDOC_DB "~");
+ }
+ return;
+ }
+
+ /*
+ * We lack write permission and cannot replace the database
+ * file, but let's at least check whether the data changed.
+ */
+
+ (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
+ if (mkdtemp(tfn) == NULL) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&%s", tfn);
+ return;
+ }
+
+ (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
+ if (dba_write(tfn, dba) == -1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(tfn, "&dba_write");
+ goto out;
+ }
+
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork cmp");
+ return;
+ case 0:
+ execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
+ say("", "&exec cmp");
+ exit(0);
+ default:
+ break;
+ }
+ if (waitpid(child, &status, 0) == -1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait cmp");
+ } else if (WIFSIGNALED(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "cmp died from signal %d", WTERMSIG(status));
+ } else if (WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB,
+ "Data changed, but cannot replace database");
+ }
+
+out:
+ *strrchr(tfn, '/') = '\0';
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork rm");
+ return;
+ case 0:
+ execlp("rm", "rm", "-rf", tfn, (char *)NULL);
+ say("", "&exec rm");
+ exit((int)MANDOCLEVEL_SYSERR);
+ default:
+ break;
+ }
+ if (waitpid(child, &status, 0) == -1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait rm");
+ } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "%s: Cannot remove temporary directory", tfn);
+ }
+}
+
+static int
+set_basedir(const char *targetdir, int report_baddir)
+{
+ static char startdir[PATH_MAX];
+ static int getcwd_status; /* 1 = ok, 2 = failure */
+ static int chdir_status; /* 1 = changed directory */
+ char *cp;
+
+ /*
+ * Remember the original working directory, if possible.
+ * This will be needed if the second or a later directory
+ * on the command line is given as a relative path.
+ * Do not error out if the current directory is not
+ * searchable: Maybe it won't be needed after all.
+ */
+ if (0 == getcwd_status) {
+ if (NULL == getcwd(startdir, sizeof(startdir))) {
+ getcwd_status = 2;
+ (void)strlcpy(startdir, strerror(errno),
+ sizeof(startdir));
+ } else
+ getcwd_status = 1;
+ }
+
+ /*
+ * We are leaving the old base directory.
+ * Do not use it any longer, not even for messages.
+ */
+ *basedir = '\0';
+
+ /*
+ * If and only if the directory was changed earlier and
+ * the next directory to process is given as a relative path,
+ * first go back, or bail out if that is impossible.
+ */
+ if (chdir_status && '/' != *targetdir) {
+ if (2 == getcwd_status) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "getcwd: %s", startdir);
+ return 0;
+ }
+ if (-1 == chdir(startdir)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&chdir %s", startdir);
+ return 0;
+ }
+ }
+
+ /*
+ * Always resolve basedir to the canonicalized absolute
+ * pathname and append a trailing slash, such that
+ * we can reliably check whether files are inside.
+ */
+ if (NULL == realpath(targetdir, basedir)) {
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&%s: realpath", targetdir);
+ }
+ return 0;
+ } else if (-1 == chdir(basedir)) {
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&chdir");
+ }
+ return 0;
+ }
+ chdir_status = 1;
+ cp = strchr(basedir, '\0');
+ if ('/' != cp[-1]) {
+ if (cp - basedir >= PATH_MAX - 1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "Filename too long");
+ return 0;
+ }
+ *cp++ = '/';
+ *cp = '\0';
+ }
+ return 1;
+}
+
+static void
+say(const char *file, const char *format, ...)
+{
+ va_list ap;
+ int use_errno;
+
+ if ('\0' != *basedir)
+ fprintf(stderr, "%s", basedir);
+ if ('\0' != *basedir && '\0' != *file)
+ fputc('/', stderr);
+ if ('\0' != *file)
+ fprintf(stderr, "%s", file);
+
+ use_errno = 1;
+ if (NULL != format) {
+ switch (*format) {
+ case '&':
+ format++;
+ break;
+ case '\0':
+ format = NULL;
+ break;
+ default:
+ use_errno = 0;
+ break;
+ }
+ }
+ if (NULL != format) {
+ if ('\0' != *basedir || '\0' != *file)
+ fputs(": ", stderr);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ }
+ if (use_errno) {
+ if ('\0' != *basedir || '\0' != *file || NULL != format)
+ fputs(": ", stderr);
+ perror(NULL);
+ } else
+ fputc('\n', stderr);