aboutsummaryrefslogtreecommitdiff
path: root/util/path-util.c
blob: 5511c09b6a70da3c35d1de8f0e245a81a127f937 (plain)
/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#define _GNU_SOURCE

#include "path-util.h"
#include "compat.h"
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>

#include <talloc.h>

char *
notmuch_canonicalize_file_name (const char *path)
{
#if HAVE_CANONICALIZE_FILE_NAME
    return canonicalize_file_name (path);
#elif defined(PATH_MAX)
    char *resolved_path =  malloc (PATH_MAX + 1);
    if (resolved_path == NULL)
	return NULL;

    return realpath (path, resolved_path);
#else
#error undefined PATH_MAX _and_ missing canonicalize_file_name not supported
#endif
}

/* Call fsync() on a directory path. */
notmuch_status_t
sync_dir (const char *dir, char **status_string)
{
    int fd, r;

    fd = open (dir, O_RDONLY);
    if (fd == -1) {
	if (status_string)
	    IGNORE_RESULT (asprintf (status_string,
				     "Error: open %s: %s\n", dir, strerror (errno)));
	return NOTMUCH_STATUS_FILE_ERROR;
    }

    r = fsync (fd);
    if (r && status_string)
	IGNORE_RESULT (asprintf (status_string,
				 "Error: fsync %s: %s\n", dir, strerror (errno)));

    close (fd);

    return r == 0 ? NOTMUCH_STATUS_SUCCESS : NOTMUCH_STATUS_FILE_ERROR;
}

/*
 * Make the given directory and its parents as necessary, using the
 * given mode. Partial results are not cleaned up on errors.
 */
notmuch_status_t
mkdir_recursive (const void *ctx, const char *path, int mode,
		 char **status_string)
{
    notmuch_status_t status;
    struct stat st;
    int r;
    char *parent = NULL, *slash;

    /* First check the common case: directory already exists. */
    r = stat (path, &st);
    if (r == 0) {
	if (! S_ISDIR (st.st_mode)) {
	    if (status_string)
		IGNORE_RESULT (asprintf (status_string, "Error: '%s' is not a directory: %s\n",
					 path, strerror (EEXIST)));
	    return NOTMUCH_STATUS_FILE_ERROR;
	}

	return NOTMUCH_STATUS_SUCCESS;
    } else if (errno != ENOENT) {
	if (status_string)
	    IGNORE_RESULT (asprintf (status_string,
				     "Error: stat '%s': %s\n", path, strerror (errno)));
	return NOTMUCH_STATUS_FILE_ERROR;
    }

    /* mkdir parents, if any */
    slash = strrchr (path, '/');
    if (slash && slash != path) {
	parent = talloc_strndup (ctx, path, slash - path);
	if (! parent) {
	    if (status_string)
		IGNORE_RESULT (asprintf (status_string,
					 "Error: %s\n",
					 strerror (ENOMEM)));
	    return NOTMUCH_STATUS_FILE_ERROR;
	}

	status = mkdir_recursive (ctx, parent, mode, status_string);
	if (status)
	    return status;
    }

    if (mkdir (path, mode)) {
	if (status_string)
	    IGNORE_RESULT (asprintf (status_string,
				     "Error: mkdir '%s': %s\n",
				     path, strerror (errno)));
	return NOTMUCH_STATUS_FILE_ERROR;
    }

    if (parent) {
	status = sync_dir (parent, status_string);
	if (status)
	    return status;
    }
    return NOTMUCH_STATUS_SUCCESS;
}