/* * 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 #if HAVE_LIBARCHIVE #include #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) #if HAVE_LIBARCHIVE static void * razor_archive_get_file_contents(const char *filename, int fd, const char *path, size_t *length, struct razor_error **error) { int r; void *addr; const void *buf; size_t size; off_t offset; struct archive *a; struct archive_entry *entry; a = archive_read_new(); archive_read_support_compression_all(a); archive_read_support_format_all(a); r = archive_read_open_fd(a, fd, 10240); if (r) { razor_set_error(error, RAZOR_POSIX_ERROR, archive_errno(a), filename, archive_error_string(a)); archive_read_finish(a); return NULL; } for (;;) { r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; else if (r < ARCHIVE_WARN) { razor_set_error(error, RAZOR_POSIX_ERROR, archive_errno(a), filename, archive_error_string(a)); archive_read_close(a); archive_read_finish(a); return NULL; } /* * TODO: Unicode support. Might need to wait for libarchive v4. */ if (!strcmp(archive_entry_pathname(entry), path)) { addr = malloc(archive_entry_size(entry)); if (!addr) { archive_read_close(a); archive_read_finish(a); razor_set_error(error, RAZOR_POSIX_ERROR, ENOMEM, NULL, "Not enough memory"); return NULL; } for(;;) { r = archive_read_data_block(a, &buf, &size, &offset); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) { razor_set_error(error, RAZOR_POSIX_ERROR, archive_errno(a), path, archive_error_string(a)); free(addr); addr = NULL; break; } memcpy((char *)addr + offset, buf, size); } archive_read_close(a); archive_read_finish(a); return addr; } } archive_read_close(a); archive_read_finish(a); razor_set_error(error, RAZOR_POSIX_ERROR, ENOENT, path, "No such file or directory in archive"); return NULL; } static void * razor_file_get_contents_archive(const char *filename, size_t *length, struct razor_error **error) { int fd; char *path, *slash, *s; void *addr; path = strdup(filename); slash = strrchr(path, '/'); while (slash) { *slash = '\0'; fd = open(path, O_RDONLY | O_BINARY); if (fd >= 0) { addr = razor_archive_get_file_contents(path, fd, slash + 1, length, error); free(path); close(fd); return addr; } else if (errno != ENOTDIR) { free(path); razor_set_error_posix(error, filename); return NULL; } s = strrchr(path, '/'); *slash = '/'; slash = s; } /* Impossible */ free(path); razor_set_error(error, RAZOR_POSIX_ERROR, ENOTDIR, filename, strerror(ENOTDIR)); return NULL; } #endif #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, size; ssize_t res; struct open_file *of, *ofend; fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { #if HAVE_LIBARCHIVE if (errno != ENOTDIR) { razor_set_error_posix(error, filename); return NULL; } addr = razor_file_get_contents_archive(filename, &size, error); if (!addr) return NULL; #else razor_set_error_posix(error, filename); return NULL; #endif } else { if (fstat(fd, &st) < 0) { razor_set_error_posix(error, filename); close(fd); return NULL; } size = 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 (!addr && !private) { addr = mmap(NULL, 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(size); if (addr) { nb = 0; while(nb < size) { res = read(fd, addr + nb, size - nb); if (res <= 0) { razor_set_error_posix(error, filename); free(addr); addr = NULL; break; } nb += res; } if (addr) of->flags = OPEN_FILE_USED; } else razor_set_error(error, RAZOR_POSIX_ERROR, ENOMEM, NULL, "Not enough memory"); } if (fd >= 0) close(fd); of->addr = addr; of->length = size; if (addr) *length = 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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); if (s) { retval = razor_path_to_uri(s); free(s); } else retval = NULL; 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; } RAZOR_EXPORT 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; }