diff -r 000000000000 -r 008c75a5e08d librazor/uri-io.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librazor/uri-io.c Mon Jul 04 10:48:18 2016 +0100 @@ -0,0 +1,1177 @@ +/* + * Copyright (C) 2008 Kristian Høgsberg + * Copyright (C) 2008 Red Hat, Inc + * Copyright (C) 2009, 2011, 2012, 2014, 2016 J. Ali Harlow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef MSWIN_API +#include +#include +#endif +#if HAVE_SYS_MMAN_H +#include +#endif +#include + +#include "razor.h" +#include "types/types.h" +#include "razor-internal.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define strcmp0(s1, s2) ((s1) && (s2) ? strcmp(s1, s2) : \ + (s1) ? 1 : (s2) ? -1 : 0) + +#define OPEN_FILE_USED (1U<<0) +#define OPEN_FILE_MMAPPED (1U<<1) + +struct open_file { + void *addr; + size_t length; + uint32_t flags; +}; + +struct array open_files = { 0, }; + +void *razor_file_get_contents(const char *filename, size_t *length, int private, + struct razor_error **error) +{ + int fd; + struct stat st; + void *addr = NULL; + size_t nb; + ssize_t res; + struct open_file *of, *ofend; + + fd = open(filename, O_RDONLY | O_BINARY); + if (fd < 0) { + razor_set_error_posix(error, filename); + return NULL; + } + + if (fstat(fd, &st) < 0) { + razor_set_error_posix(error, filename); + close(fd); + return NULL; + } + + *length = st.st_size; + + ofend = open_files.data + open_files.size; + for (of = open_files.data; of < ofend; of++) + if (!(of->flags & OPEN_FILE_USED)) + break; + if (of == ofend) { + of = array_add(&open_files, sizeof *of); + of->flags = 0; + } + +#if HAVE_SYS_MMAN_H + if (!private) { + addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + addr = NULL; + else + of->flags = OPEN_FILE_USED | OPEN_FILE_MMAPPED; + } +#endif /* HAVE_SYS_MMAN_H */ + if (!addr) { + addr = malloc(st.st_size); + if (addr) { + of->flags = OPEN_FILE_USED; + nb = 0; + while(nb < st.st_size) { + res = read(fd, addr + nb, st.st_size - nb); + if (res <= 0) { + razor_set_error_posix(error, filename); + free(addr); + addr = NULL; + break; + } + nb += res; + } + } else + razor_set_error(error, RAZOR_POSIX_ERROR, ENOMEM, NULL, + "Not enough memory"); + } + close(fd); + + of->addr = addr; + of->length = st.st_size; + + return addr; +} + +int razor_file_free_contents(void *addr, size_t length) +{ +#if HAVE_SYS_MMAN_H + int mmapped; +#endif + struct open_file *of, *ofend; + + ofend = open_files.data + open_files.size; + for (of = open_files.data; of < ofend; of++) + if ((of->flags & OPEN_FILE_USED) && of->addr == addr) + break; + + if (of == ofend) + return -2; + + length = of->length; +#if HAVE_SYS_MMAN_H + mmapped = of->flags & OPEN_FILE_MMAPPED; +#endif + of->flags &= ~OPEN_FILE_USED; + + for (of = open_files.data; of < ofend; of++) + if (of->flags & OPEN_FILE_USED) + break; + + if (of == ofend) { + array_release(&open_files); + array_init(&open_files); + } + +#if HAVE_SYS_MMAN_H + if (mmapped) + return munmap(addr, length); +#endif + + free(addr); + return 0; +} + +int razor_file_mkdir(const char *path, mode_t mode, struct razor_error **error) +{ + int retval, code; + struct stat buf; + + retval = mkdir(path, mode); + + if (retval) { + /* + * Ignore the case of a pre-existing directory and give + * an explicit error message if there is something other + * than a directory already at path. + */ + code = errno; + if (code != EEXIST || stat(path, &buf)) + razor_set_error(error, RAZOR_POSIX_ERROR, code, path, + strerror(code)); + else if (!S_ISDIR(buf.st_mode)) + razor_set_error(error, RAZOR_POSIX_ERROR, code, path, + "Not a directory"); + } + + return retval; +} + +int razor_file_unlink(const char *path, struct razor_error **error) +{ + int retval; + + retval = unlink(path); + + if (retval) + razor_set_error_posix(error, path); + + return retval; +} + +int razor_file_open(const char *path, int flags, mode_t mode, + struct razor_error **error) +{ + int retval; + + retval = open(path, flags, mode); + + if (retval < 0) + razor_set_error_posix(error, path); + + return retval; +} + +int razor_file_move(const char *path, const char *dest, + struct razor_error **error) +{ + int retval = 0; + +#ifdef MSWIN_API + wchar_t *oldbuf, *newbuf; + const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING; + + newbuf = razor_utf8_to_utf16(dest, -1); + oldbuf = razor_utf8_to_utf16(path, -1); + + /* + * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will + * cover every case we care about _except_ replacing an empty + * directory with a file. Calling RemoveDirectory() will deal + * with this case while having no effect in all other cases. + */ + (void)RemoveDirectoryW(newbuf); + + if (!MoveFileExW(oldbuf, newbuf, flags)) { + razor_set_error_mswin(error, newbuf, GetLastError()); + retval = -1; + } + + free(newbuf); + free(oldbuf); +#else + int code; + const char *object; + + if (rename(path, dest)) { + if (error) { + code = errno; + if (access(path, F_OK) < 0) + object = path; + else + object = dest; + razor_set_error(error, RAZOR_POSIX_ERROR, code, object, + strerror(code)); + } + retval = -1; + } +#endif + + return retval; +} + +#ifndef MSWIN_API +static char *absolute_path(const char *path) +{ + int len; + char *result, *subpath, *p, *s, *t; + + result = realpath(path, NULL); + + if (!result && errno == ENOENT) { + p = strdup(path); + s = strrchr(p, '/'); + + while (s) { + if (s == p) { + result = strdup("/"); + break; + } + + *s = '\0'; + subpath = realpath(p, NULL); + + if (subpath) { + *s = '/'; + len = strlen(subpath); + result = malloc(len + strlen(s) + 1); + memcpy(result, subpath, len); + strcpy(result + len, s); + free(subpath); + break; + } else if (errno != ENOENT) + break; + + t = strrchr(p, '/'); + *s = '/'; + s = t; + } + + if (!s) + result = realpath(".", NULL); + + free(p); + } + + return result; +} +#endif + +int razor_file_is_directory(const char *path, struct razor_error **error) +{ + struct stat st; + + if (stat(path, &st) < 0) { + razor_set_error_posix(error, path); + return -1; + } + + return !!S_ISDIR(st.st_mode); +} + +char *razor_file_mkdtemp_near(const char *path, const char *template, + struct razor_error **error) +{ + char *retval; + +#ifdef MSWIN_API + if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\' + && strchr(path+3,'\\')) { + /* We have a UNC path: \\servername\sharename... */ + const char *sharename, *root; + int disklen; + + sharename = strchr(path+3,'\\')+1; + root = strchr(sharename,'\\'); + if (root) + disklen = root - path; + else + disklen = strlen(path); + + retval = malloc(disklen + 1 + strlen(template) + 1); + memcpy(retval, path, disklen); + retval[disklen] = '\\'; + strcpy(retval + disklen + 1, template); + } else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') && + path[1]==':') { + retval = malloc(3 + strlen(template) + 1); + retval[0] = path[0]; + retval[1] = ':'; + retval[2] = '\\'; + strcpy(retval + 3, template); + } else { + DWORD n; + wchar_t *buf; + char *dir; + + n = GetCurrentDirectoryW(0, NULL); + buf = malloc(n * sizeof(wchar_t)); + + if (GetCurrentDirectoryW(n, buf)) { + dir = razor_utf16_to_utf8(buf, n - 1); + free(buf); + retval = razor_file_mkdtemp_near(dir, template, error); + free(dir); + return retval; + } else { + retval = malloc(3 + strlen(template) + 1); + retval[0] = 'C'; + retval[1] = ':'; + retval[2] = '\\'; + strcpy(retval + 3, template); + } + + free(buf); + } +#else + /* + * Find the mount point (assuming we can write to the + * whole filesystem). Otherwise stop at the first + * unwritable directory and take one step back. + */ + char *s, *abspath, saved; + int len, can_step_back = 0; + dev_t filesystem; + struct stat buf; + + abspath = absolute_path(path); + if (!abspath) { + razor_set_error_posix(error, path); + return NULL; + } + + if (stat(abspath, &buf) < 0) { + if (errno == ENOENT) + filesystem = 0; + else { + razor_set_error_posix(error, abspath); + free(abspath); + return NULL; + } + } else + filesystem = buf.st_dev; + + len = strlen(abspath); + while(len > 1 && (s = strrchr(abspath, '/'))) { + if (s == abspath) { + saved = s[1]; + s[1] = '\0'; + len = s + 1 - abspath; + } else { + s[0] = '\0'; + len = s - abspath; + } + + if (stat(abspath, &buf) < 0) { + if (errno == ENOENT) + continue; + else { + razor_set_error_posix(error, abspath); + free(abspath); + return NULL; + } + } else if (!filesystem) + filesystem = buf.st_dev; + + if (buf.st_dev != filesystem || access(abspath, W_OK)) { + if (can_step_back) { + if (s == abspath) + s[1] = saved; + else + s[0] = '/'; + } + len = strlen(abspath); + break; + } else + can_step_back = 1; + } + + if (len == 1) + len = 0; /* Avoid an unslightly double slash. */ + retval = malloc(len + 1 + strlen(template) + 1); + memcpy(retval, abspath, len); + retval[len] = '/'; + strcpy(retval + len + 1, template); + + free(abspath); +#endif + + if (!mkdtemp(retval)) { + int err = errno; + +#ifdef EACCES + if (err == EACCES) { + char *s = strdup(template); + +#ifndef MSWIN_API + if (stat(".", &buf) < 0) { + razor_set_error_posix(error, "."); + free(s); + free(retval); + return NULL; + } + if (buf.st_dev != filesystem) + /* + * Don't use a different filesystem. It will + * only fail later on (in rename) and cause + * an unhelpful error message (EXDEV). + */ + free(s); + else +#endif + if (mkdtemp(s)) { + free(retval); + retval = s; + return retval; + } else + free(s); + } +#endif + + razor_set_error(error, RAZOR_POSIX_ERROR, err, retval, + strerror(err)); + + free(retval); + retval = NULL; + } + + return retval; +} + +struct open_dir { + uint32_t flags; +#ifdef MSWIN_API + _WDIR *dp; + wchar_t *path2; +#else + DIR *dp; + char *path; +#endif +}; + +#define OPEN_DIR_USED (1U<<0) + +struct array open_dirs = { 0, }; + +void *razor_file_opendir(const char *path, struct razor_error **error) +{ + struct open_dir *od, *odend; + + odend = open_dirs.data + open_dirs.size; + for (od = open_dirs.data; od < odend; od++) + if (!(od->flags & OPEN_DIR_USED)) + break; + if (od == odend) { + od = array_add(&open_dirs, sizeof *od); + od->flags = 0; + } + +#ifdef MSWIN_API + od->path2 = razor_utf8_to_utf16(path, -1); + od->dp = _wopendir(od->path2); +#else + od->path = strdup(path); + od->dp = opendir(od->path); +#endif + + if (!od->dp) { +#ifdef MSWIN_API + razor_set_error_mswin(error, od->path2, GetLastError()); + free(od->path2); +#else + razor_set_error_posix(error, od->path); + free(od->path); +#endif + return NULL; + } + + od->flags = OPEN_DIR_USED; + return od; +} + +char *razor_file_readdir(void *dp, struct razor_error **error) +{ + struct open_dir *od = dp, *odend; +#ifdef MSWIN_API + struct _wdirent *dirp; + char *path; +#else + struct dirent *dirp; +#endif + + odend = open_dirs.data + open_dirs.size; + if (dp < open_dirs.data || od >= odend || !(od->flags & OPEN_DIR_USED)) + return (char *)-1; + + errno = 0; + +#ifdef MSWIN_API + while((dirp = _wreaddir(od->dp))) { + path = razor_utf16_to_utf8(dirp->d_name, -1); + if (strcmp(path, ".") && strcmp(path, "..")) + return path; + else + free(path); + } +#else + while((dirp = readdir(od->dp))) + if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) + return strdup(dirp->d_name); +#endif + + if (errno) { +#ifdef MSWIN_API + razor_set_error_mswin(error, od->path2, GetLastError()); +#else + razor_set_error_posix(error, od->path); +#endif + } + + return NULL; +} + +int razor_file_closedir(void *dp, struct razor_error **error) +{ + struct open_dir *od = dp, *odend; + int retval; + + odend = open_dirs.data + open_dirs.size; + if (dp < open_dirs.data || od >= odend || !(od->flags & OPEN_DIR_USED)) + return -2; + +#ifdef MSWIN_API + /* + * I can't find documentation to state that _wclosedir() + * returns -1 on failure, so be paranoid. + */ + retval = _wclosedir(od->dp) ? -1 : 0; +#else + retval = closedir(od->dp); +#endif + + if (retval) { +#ifdef MSWIN_API + razor_set_error_mswin(error, od->path2, GetLastError()); +#else + razor_set_error_posix(error, od->path); +#endif + } + +#ifdef MSWIN_API + free(od->path2); +#else + free(od->path); +#endif + + od->flags &= ~OPEN_DIR_USED; + + for (od = open_dirs.data; od < odend; od++) + if (od->flags & OPEN_DIR_USED) + break; + + if (od == odend) { + array_release(&open_dirs); + array_init(&open_dirs); + } + + return retval; +} + +struct razor_uri_vtable_entry { + struct razor_uri_vtable vtable; + char *scheme; + struct array open_files, open_directories; +}; + +static struct array razor_uri_vtable_entries; + +static struct razor_uri_vtable_entry * + razor_uri_get_vtable_entry(const char *scheme) +{ + struct razor_uri_vtable_entry *entry, *eend, *fallback = NULL; + + eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size; + for(entry = razor_uri_vtable_entries.data; entry < eend; entry++) { + if (!strcmp0(entry->scheme, scheme)) + return entry; + else if (!entry->scheme) + fallback = entry; + } + + return fallback; +} + +int razor_uri_mkdir(const char *uri, mode_t mode, struct razor_error **error) +{ + int retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return -1; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return -1; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_mkdir(path, mode, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.mkdir) { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else + retval = entry->vtable.mkdir(uri, mode, error); + } + + return retval; +} + +int razor_uri_unlink(const char *uri, struct razor_error **error) +{ + int retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return -1; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return -1; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_unlink(path, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.unlink) { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else + retval = entry->vtable.unlink(uri, error); + } + + return retval; +} + +int razor_uri_open(const char *uri, int flags, mode_t mode, + struct razor_error **error) +{ + int retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return -1; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return -1; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_open(path, flags, mode, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.open) { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else + retval = entry->vtable.open(uri, flags, mode, error); + } + + return retval; +} + +int razor_uri_move(const char *src_uri, const char *dst_uri, + struct razor_error **error) +{ + int retval; + char *src_path, *dst_path; + struct razor_uri src_ru, dst_ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&src_ru, src_uri, error) || + razor_uri_parse(&dst_ru, dst_uri, error)) + return -1; + + src_path = razor_path_from_parsed_uri(&src_ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!src_path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&src_ru); + return -1; + } + + dst_path = razor_path_from_parsed_uri(&dst_ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!dst_path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&dst_ru); + razor_uri_destroy(&src_ru); + free(src_path); + return -1; + } + + if (src_path && dst_path) + retval = razor_file_move(src_path, dst_path, error); + else { + if (!strcmp(src_ru.scheme, dst_ru.scheme)) + entry = razor_uri_get_vtable_entry(src_ru.scheme); + else + entry = NULL; + if (entry && entry->vtable.move) + retval = entry->vtable.move(src_uri, dst_uri, error); + else if (strcmp(src_ru.scheme, dst_ru.scheme)) { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + dst_uri, "Cross-scheme URI move"); + } else { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + src_path ? dst_uri : src_uri, + "No URI handler installed"); + } + } + + razor_uri_destroy(&src_ru); + razor_uri_destroy(&dst_ru); + free(src_path); + free(dst_path); + + return retval; +} + +void *razor_uri_get_contents(const char *uri, size_t *length, int private, + struct razor_error **error) +{ + void *retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return NULL; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return NULL; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_get_contents(path, length, private, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.get_contents) { + retval = NULL; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else { + retval = entry->vtable.get_contents(uri, length, + private, error); + if (retval) + ptr_array_add(&entry->open_files, retval); + } + } + + return retval; +} + +int razor_uri_free_contents(void *addr, size_t length) +{ + int retval, of; + struct razor_uri_vtable_entry *entry, *eend; + + if (!addr) + return 0; + + retval = razor_file_free_contents(addr, length); + + if (retval != -2) + return retval; + + eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size; + for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) { + of = ptr_array_find(&entry->open_files, addr); + if (of >= 0) { + if (entry->vtable.free_contents) + retval = entry->vtable.free_contents(addr, + length); + ptr_array_remove_index(&entry->open_files, of); + break; + } + } + + return retval; +} + +int razor_uri_is_directory(const char *uri, struct razor_error **error) +{ + int retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return -1; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return -1; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_is_directory(path, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.is_directory) { + retval = -1; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else + retval = entry->vtable.is_directory(uri, error); + } + + return retval; +} + +char *razor_uri_mkdtemp_near(const char *uri, const char *template, + struct razor_error **error) +{ + char *retval, *s; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return NULL; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return NULL; + } + + if (path) { + razor_uri_destroy(&ru); + s = razor_file_mkdtemp_near(path, template, error); + retval = razor_path_to_uri(s); + free(s); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.mkdtemp_near) { + retval = NULL; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else + retval = entry->vtable.mkdtemp_near(uri, template, + error); + } + + return retval; +} + +void *razor_uri_opendir(const char *uri, struct razor_error **error) +{ + void *retval; + char *path; + struct razor_uri ru; + struct razor_uri_vtable_entry *entry; + struct razor_error *tmp_error = NULL; + + if (razor_uri_parse(&ru, uri, error)) + return NULL; + + path = razor_path_from_parsed_uri(&ru, &tmp_error); + + if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI)) + razor_error_free(tmp_error); + else if (!path) { + razor_propagate_error(error, tmp_error, NULL); + razor_uri_destroy(&ru); + return NULL; + } + + if (path) { + razor_uri_destroy(&ru); + retval = razor_file_opendir(path, error); + free(path); + } else { + entry = razor_uri_get_vtable_entry(ru.scheme); + razor_uri_destroy(&ru); + if (!entry || !entry->vtable.opendir) { + retval = NULL; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, + uri, "No URI handler installed"); + } else { + retval = entry->vtable.opendir(uri, error); + if (retval) + ptr_array_add(&entry->open_directories, retval); + } + } + + return retval; +} + +char *razor_uri_readdir(void *dir, struct razor_error **error) +{ + int od; + char *retval; + struct razor_uri_vtable_entry *entry, *eend; + + retval = razor_file_readdir(dir, error); + + if (retval != (char *)-1) + return retval; + + eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size; + for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) { + od = ptr_array_find(&entry->open_directories, dir); + if (od >= 0) { + if (entry->vtable.readdir) + retval = entry->vtable.readdir(dir, error); + break; + } + } + + if (retval == (char *)-1) { + retval = NULL; + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_FAILED, NULL, + "Invalid directory handle"); + } + + return retval; +} + +int razor_uri_closedir(void *dir, struct razor_error **error) +{ + int od; + int retval; + struct razor_uri_vtable_entry *entry, *eend; + + retval = razor_file_closedir(dir, error); + + if (retval != -2) + return retval; + + eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size; + for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) { + od = ptr_array_find(&entry->open_directories, dir); + if (od >= 0) { + if (entry->vtable.closedir) + retval = entry->vtable.closedir(dir, error); + break; + } + } + + if (retval == -2) + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_FAILED, NULL, + "Invalid directory handle"); + + return retval; +} + +RAZOR_EXPORT int razor_uri_set_vtable(const char *scheme, + struct razor_uri_vtable *vtable, struct razor_error **error) +{ + struct razor_uri_vtable_entry *entry, *eend; + + if (!strcmp0(scheme, "file")) { + /* + * There's no fundamental reason why we couldn't support + * this, but it's a lot of work without any obvious need. + */ + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_FAILED, scheme, + "Can't override file scheme handler"); + return -1; + } + + if (vtable->structure_size < sizeof(struct razor_uri_vtable)) { + /* + * A future version of the API might add vfuncs to the + * table (which we ignore), but won't remove any. + */ + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_FAILED, scheme, + "Invalid vtable structure size"); + return -1; + } + + eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size; + for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) { + if (!strcmp0(entry->scheme, scheme)) + break; + } + + if (entry == eend) { + if (!vtable) + return 0; + entry = array_add(&razor_uri_vtable_entries, sizeof *entry); + entry->scheme = scheme ? strdup(scheme) : NULL; + array_init(&entry->open_files); + array_init(&entry->open_directories); + } else if (entry->open_files.size || entry->open_directories.size) { + razor_set_error(error, RAZOR_GENERAL_ERROR, + RAZOR_GENERAL_ERROR_FAILED, scheme, + "URI handler busy"); + return -1; + } + + if (vtable) { + entry->vtable = *vtable; + entry->vtable.structure_size = sizeof(struct razor_uri_vtable); + } else { + free(entry->scheme); + if (entry + 1 < eend) + memmove(entry, entry + 1, eend - (entry + 1)); + array_set_size(&razor_uri_vtable_entries, + razor_uri_vtable_entries.size - sizeof *entry); + } + + return 0; +}