librazor/uri-io.c
author J. Ali Harlow <ali@juiblex.co.uk>
Mon Jul 04 10:48:18 2016 +0100 (2016-07-04)
changeset 475 008c75a5e08d
child 476 48e45439fd9a
permissions -rw-r--r--
Switch to a URI-based API
     1 /*
     2  * Copyright (C) 2008  Kristian Høgsberg <krh@redhat.com>
     3  * Copyright (C) 2008  Red Hat, Inc
     4  * Copyright (C) 2009, 2011, 2012, 2014, 2016  J. Ali Harlow <ali@juiblex.co.uk>
     5  *
     6  * This program is free software; you can redistribute it and/or modify
     7  * it under the terms of the GNU General Public License as published by
     8  * the Free Software Foundation; either version 2 of the License, or
     9  * (at your option) any later version.
    10  *
    11  * This program is distributed in the hope that it will be useful,
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  * GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License along
    17  * with this program; if not, write to the Free Software Foundation, Inc.,
    18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    19  */
    20 
    21 #include "config.h"
    22 
    23 #include <limits.h>
    24 #include <string.h>
    25 #include <sys/types.h>
    26 #include <sys/stat.h>
    27 #include <stdlib.h>
    28 #include <stdio.h>
    29 #include <stdint.h>
    30 #include <errno.h>
    31 #include <unistd.h>
    32 #include <fcntl.h>
    33 #include <dirent.h>
    34 #ifdef MSWIN_API
    35 #include <windows.h>
    36 #include <direct.h>
    37 #endif
    38 #if HAVE_SYS_MMAN_H
    39 #include <sys/mman.h>
    40 #endif
    41 #include <assert.h>
    42 
    43 #include "razor.h"
    44 #include "types/types.h"
    45 #include "razor-internal.h"
    46 
    47 #ifndef O_BINARY
    48 #define O_BINARY	0
    49 #endif
    50 
    51 #define strcmp0(s1, s2)		((s1) && (s2) ? strcmp(s1, s2) : \
    52 				 (s1) ? 1 : (s2) ? -1 : 0)
    53 
    54 #define OPEN_FILE_USED		(1U<<0)
    55 #define OPEN_FILE_MMAPPED	(1U<<1)
    56 
    57 struct open_file {
    58 	void *addr;
    59 	size_t length;
    60 	uint32_t flags;
    61 };
    62 
    63 struct array open_files = { 0, };
    64 
    65 void *razor_file_get_contents(const char *filename, size_t *length, int private,
    66 			      struct razor_error **error)
    67 {
    68 	int fd;
    69 	struct stat st;
    70 	void *addr = NULL;
    71 	size_t nb;
    72 	ssize_t res;
    73 	struct open_file *of, *ofend;
    74 
    75 	fd = open(filename, O_RDONLY | O_BINARY);
    76 	if (fd < 0) {
    77 		razor_set_error_posix(error, filename);
    78 		return NULL;
    79 	}
    80 
    81 	if (fstat(fd, &st) < 0) {
    82 		razor_set_error_posix(error, filename);
    83 		close(fd);
    84 		return NULL;
    85 	}
    86 
    87 	*length = st.st_size;
    88 
    89 	ofend = open_files.data + open_files.size;
    90 	for (of = open_files.data; of < ofend; of++)
    91 		if (!(of->flags & OPEN_FILE_USED))
    92 			break;
    93 	if (of == ofend) {
    94 		of = array_add(&open_files, sizeof *of);
    95 		of->flags = 0;
    96 	}
    97 
    98 #if HAVE_SYS_MMAN_H
    99 	if (!private) {
   100 		addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
   101 		if (addr == MAP_FAILED)
   102 			addr = NULL;
   103 		else
   104 			of->flags = OPEN_FILE_USED | OPEN_FILE_MMAPPED;
   105 	}
   106 #endif	/* HAVE_SYS_MMAN_H */
   107 	if (!addr) {
   108 		addr = malloc(st.st_size);
   109 		if (addr) {
   110 			of->flags = OPEN_FILE_USED;
   111 			nb = 0;
   112 			while(nb < st.st_size) {
   113 				res = read(fd, addr + nb, st.st_size - nb);
   114 				if (res <= 0) {
   115 					razor_set_error_posix(error, filename);
   116 					free(addr);
   117 					addr = NULL;
   118 					break;
   119 				}
   120 				nb += res;
   121 			}
   122 		} else
   123 			razor_set_error(error, RAZOR_POSIX_ERROR, ENOMEM, NULL,
   124 					"Not enough memory");
   125 	}
   126 	close(fd);
   127 
   128 	of->addr = addr;
   129 	of->length = st.st_size;
   130 
   131 	return addr;
   132 }
   133 
   134 int razor_file_free_contents(void *addr, size_t length)
   135 {
   136 #if HAVE_SYS_MMAN_H
   137 	int mmapped;
   138 #endif
   139 	struct open_file *of, *ofend;
   140 
   141 	ofend = open_files.data + open_files.size;
   142 	for (of = open_files.data; of < ofend; of++)
   143 		if ((of->flags & OPEN_FILE_USED) && of->addr == addr)
   144 			break;
   145 
   146 	if (of == ofend)
   147 		return -2;
   148 
   149 	length = of->length;
   150 #if HAVE_SYS_MMAN_H
   151 	mmapped = of->flags & OPEN_FILE_MMAPPED;
   152 #endif
   153 	of->flags &= ~OPEN_FILE_USED;
   154 
   155 	for (of = open_files.data; of < ofend; of++)
   156 		if (of->flags & OPEN_FILE_USED)
   157 			break;
   158 
   159 	if (of == ofend) {
   160 		array_release(&open_files);
   161 		array_init(&open_files);
   162 	}
   163 
   164 #if HAVE_SYS_MMAN_H
   165 	if (mmapped)
   166 		return munmap(addr, length);
   167 #endif
   168 
   169 	free(addr);
   170 	return 0;
   171 }
   172 
   173 int razor_file_mkdir(const char *path, mode_t mode, struct razor_error **error)
   174 {
   175 	int retval, code;
   176 	struct stat buf;
   177 
   178 	retval = mkdir(path, mode);
   179 
   180 	if (retval) {
   181 		/*
   182 		 * Ignore the case of a pre-existing directory and give
   183 		 * an explicit error message if there is something other
   184 		 * than a directory already at path.
   185 		 */
   186 		code = errno;
   187 		if (code != EEXIST || stat(path, &buf))
   188 			razor_set_error(error, RAZOR_POSIX_ERROR, code, path,
   189 					strerror(code));
   190 		else if (!S_ISDIR(buf.st_mode))
   191 			razor_set_error(error, RAZOR_POSIX_ERROR, code, path,
   192 					"Not a directory");
   193 	}
   194 
   195 	return retval;
   196 }
   197 
   198 int razor_file_unlink(const char *path, struct razor_error **error)
   199 {
   200 	int retval;
   201 
   202 	retval = unlink(path);
   203 
   204 	if (retval)
   205 		razor_set_error_posix(error, path);
   206 
   207 	return retval;
   208 }
   209 
   210 int razor_file_open(const char *path, int flags, mode_t mode,
   211 		    struct razor_error **error)
   212 {
   213 	int retval;
   214 
   215 	retval = open(path, flags, mode);
   216 
   217 	if (retval < 0)
   218 		razor_set_error_posix(error, path);
   219 
   220 	return retval;
   221 }
   222 
   223 int razor_file_move(const char *path, const char *dest,
   224 		    struct razor_error **error)
   225 {
   226 	int retval = 0;
   227 
   228 #ifdef MSWIN_API
   229 	wchar_t *oldbuf, *newbuf;
   230 	const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
   231 
   232 	newbuf = razor_utf8_to_utf16(dest, -1);
   233 	oldbuf = razor_utf8_to_utf16(path, -1);
   234 
   235 	/*
   236 	 * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
   237 	 * cover every case we care about _except_ replacing an empty
   238 	 * directory with a file. Calling RemoveDirectory() will deal
   239 	 * with this case while having no effect in all other cases.
   240 	 */
   241 	(void)RemoveDirectoryW(newbuf);
   242 
   243 	if (!MoveFileExW(oldbuf, newbuf, flags)) {
   244 		razor_set_error_mswin(error, newbuf, GetLastError());
   245 		retval = -1;
   246 	}
   247 
   248 	free(newbuf);
   249 	free(oldbuf);
   250 #else
   251 	int code;
   252 	const char *object;
   253 
   254 	if (rename(path, dest)) {
   255 		if (error) {
   256 			code = errno;
   257 			if (access(path, F_OK) < 0)
   258 				object = path;
   259 			else
   260 				object = dest;
   261 			razor_set_error(error, RAZOR_POSIX_ERROR, code, object, 
   262 					strerror(code));
   263 		}
   264 		retval = -1;
   265 	}
   266 #endif
   267 
   268 	return retval;
   269 }
   270 
   271 #ifndef MSWIN_API
   272 static char *absolute_path(const char *path)
   273 {
   274 	int len;
   275 	char *result, *subpath, *p, *s, *t;
   276 
   277 	result = realpath(path, NULL);
   278 
   279 	if (!result && errno == ENOENT) {
   280 		p = strdup(path);
   281 		s = strrchr(p, '/');
   282 
   283 		while (s) {
   284 			if (s == p) {
   285 				result = strdup("/");
   286 				break;
   287 			}
   288 
   289 			*s = '\0';
   290 			subpath = realpath(p, NULL);
   291 
   292 			if (subpath) {
   293 				*s = '/';
   294 				len = strlen(subpath);
   295 				result = malloc(len + strlen(s) + 1);
   296 				memcpy(result, subpath, len);
   297 				strcpy(result + len, s);
   298 				free(subpath);
   299 				break;
   300 			} else if (errno != ENOENT)
   301 				break;
   302 
   303 			t = strrchr(p, '/');
   304 			*s = '/';
   305 			s = t;
   306 		}
   307 
   308 		if (!s)
   309 			result = realpath(".", NULL);
   310 
   311 		free(p);
   312 	}
   313 
   314 	return result;
   315 }
   316 #endif
   317 
   318 int razor_file_is_directory(const char *path, struct razor_error **error)
   319 {
   320 	struct stat st;
   321 
   322 	if (stat(path, &st) < 0) {
   323 		razor_set_error_posix(error, path);
   324 		return -1;
   325 	}
   326 
   327 	return !!S_ISDIR(st.st_mode);
   328 }
   329 
   330 char *razor_file_mkdtemp_near(const char *path, const char *template,
   331 			      struct razor_error **error)
   332 {
   333 	char *retval;
   334 
   335 #ifdef MSWIN_API
   336 	if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\'
   337 	    && strchr(path+3,'\\')) {
   338 		/* We have a UNC path: \\servername\sharename... */
   339 		const char *sharename, *root;
   340 		int disklen;
   341 
   342 		sharename = strchr(path+3,'\\')+1;
   343 		root = strchr(sharename,'\\');
   344 		if (root)
   345 		    disklen = root - path;
   346 		else
   347 		    disklen = strlen(path);
   348 
   349 		retval = malloc(disklen + 1 + strlen(template) + 1);
   350 		memcpy(retval, path, disklen);
   351 		retval[disklen] = '\\';
   352 		strcpy(retval + disklen + 1, template);
   353 	} else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') &&
   354 		    path[1]==':') {
   355 		retval = malloc(3 + strlen(template) + 1);
   356 		retval[0] = path[0];
   357 		retval[1] = ':';
   358 		retval[2] = '\\';
   359 		strcpy(retval + 3, template);
   360 	} else {
   361 		DWORD n;
   362 		wchar_t *buf;
   363 		char *dir;
   364 
   365 		n = GetCurrentDirectoryW(0, NULL);
   366 		buf = malloc(n * sizeof(wchar_t));
   367 
   368 		if (GetCurrentDirectoryW(n, buf)) {
   369 			dir = razor_utf16_to_utf8(buf, n - 1);
   370 			free(buf);
   371 			retval = razor_file_mkdtemp_near(dir, template, error);
   372 			free(dir);
   373 			return retval;
   374 		} else {
   375 			retval = malloc(3 + strlen(template) + 1);
   376 			retval[0] = 'C';
   377 			retval[1] = ':';
   378 			retval[2] = '\\';
   379 			strcpy(retval + 3, template);
   380 		}
   381 
   382 		free(buf);
   383 	}
   384 #else
   385 	/*
   386 	 * Find the mount point (assuming we can write to the
   387 	 * whole filesystem). Otherwise stop at the first
   388 	 * unwritable directory and take one step back.
   389 	 */
   390 	char *s, *abspath, saved;
   391 	int len, can_step_back = 0;
   392 	dev_t filesystem;
   393 	struct stat buf;
   394 
   395 	abspath = absolute_path(path);
   396 	if (!abspath) {
   397 		razor_set_error_posix(error, path);
   398 		return NULL;
   399 	}
   400 
   401 	if (stat(abspath, &buf) < 0) {
   402 		if (errno == ENOENT)
   403 			filesystem = 0;
   404 		else {
   405 			razor_set_error_posix(error, abspath);
   406 			free(abspath);
   407 			return NULL;
   408 		}
   409 	} else
   410 		filesystem = buf.st_dev;
   411 
   412 	len = strlen(abspath);
   413 	while(len > 1 && (s = strrchr(abspath, '/'))) {
   414 		if (s == abspath) {
   415 			saved = s[1];
   416 			s[1] = '\0';
   417 			len = s + 1 - abspath;
   418 		} else {
   419 			s[0] = '\0';
   420 			len = s - abspath;
   421 		}
   422 
   423 		if (stat(abspath, &buf) < 0) {
   424 			if (errno == ENOENT)
   425 				continue;
   426 			else {
   427 			    razor_set_error_posix(error, abspath);
   428 			    free(abspath);
   429 			    return NULL;
   430 			}
   431 		} else if (!filesystem)
   432 			filesystem = buf.st_dev;
   433 
   434 		if (buf.st_dev != filesystem || access(abspath, W_OK)) {
   435 			if (can_step_back) {
   436 				if (s == abspath)
   437 					s[1] = saved;
   438 				else
   439 					s[0] = '/';
   440 			}
   441 			len = strlen(abspath);
   442 			break;
   443 		} else
   444 			can_step_back = 1;
   445 	}
   446 
   447 	if (len == 1)
   448 		len = 0;	/* Avoid an unslightly double slash. */
   449 	retval = malloc(len + 1 + strlen(template) + 1);
   450 	memcpy(retval, abspath, len);
   451 	retval[len] = '/';
   452 	strcpy(retval + len + 1, template);
   453 
   454 	free(abspath);
   455 #endif
   456 
   457 	if (!mkdtemp(retval)) {
   458 		int err = errno;
   459 
   460 #ifdef EACCES
   461 		if (err == EACCES) {
   462 			char *s = strdup(template);
   463 
   464 #ifndef MSWIN_API
   465 			if (stat(".", &buf) < 0) {
   466 				razor_set_error_posix(error, ".");
   467 				free(s);
   468 				free(retval);
   469 				return NULL;
   470 			}
   471 			if (buf.st_dev != filesystem)
   472 				/*
   473 				 * Don't use a different filesystem. It will
   474 				 * only fail later on (in rename) and cause
   475 				 * an unhelpful error message (EXDEV).
   476 				 */
   477 				free(s);
   478 			else
   479 #endif
   480 			if (mkdtemp(s)) {
   481 				free(retval);
   482 				retval = s;
   483 				return retval;
   484 			} else
   485 				free(s);
   486 		}
   487 #endif
   488 
   489 		razor_set_error(error, RAZOR_POSIX_ERROR, err, retval,
   490 				strerror(err));
   491 
   492 		free(retval);
   493 		retval = NULL;
   494 	}
   495 
   496 	return retval;
   497 }
   498 
   499 struct open_dir {
   500 	uint32_t flags;
   501 #ifdef MSWIN_API
   502 	_WDIR *dp;
   503 	wchar_t *path2;
   504 #else
   505 	DIR *dp;
   506 	char *path;
   507 #endif
   508 };
   509 
   510 #define OPEN_DIR_USED		(1U<<0)
   511 
   512 struct array open_dirs = { 0, };
   513 
   514 void *razor_file_opendir(const char *path, struct razor_error **error)
   515 {
   516 	struct open_dir *od, *odend;
   517 
   518 	odend = open_dirs.data + open_dirs.size;
   519 	for (od = open_dirs.data; od < odend; od++)
   520 		if (!(od->flags & OPEN_DIR_USED))
   521 			break;
   522 	if (od == odend) {
   523 		od = array_add(&open_dirs, sizeof *od);
   524 		od->flags = 0;
   525 	}
   526 
   527 #ifdef MSWIN_API
   528 	od->path2 = razor_utf8_to_utf16(path, -1);
   529 	od->dp = _wopendir(od->path2);
   530 #else
   531 	od->path = strdup(path);
   532 	od->dp = opendir(od->path);
   533 #endif
   534 
   535 	if (!od->dp) {
   536 #ifdef MSWIN_API
   537 		razor_set_error_mswin(error, od->path2, GetLastError());
   538 		free(od->path2);
   539 #else
   540 		razor_set_error_posix(error, od->path);
   541 		free(od->path);
   542 #endif
   543 		return NULL;
   544 	}
   545 
   546 	od->flags = OPEN_DIR_USED;
   547 	return od;
   548 }
   549 
   550 char *razor_file_readdir(void *dp, struct razor_error **error)
   551 {
   552 	struct open_dir *od = dp, *odend;
   553 #ifdef MSWIN_API
   554 	struct _wdirent *dirp;
   555 	char *path;
   556 #else
   557 	struct dirent *dirp;
   558 #endif
   559 
   560 	odend = open_dirs.data + open_dirs.size;
   561 	if (dp < open_dirs.data || od >= odend || !(od->flags & OPEN_DIR_USED))
   562 		return (char *)-1;
   563 
   564 	errno = 0;
   565 
   566 #ifdef MSWIN_API
   567 	while((dirp = _wreaddir(od->dp))) {
   568 		path = razor_utf16_to_utf8(dirp->d_name, -1);
   569 		if (strcmp(path, ".") && strcmp(path, ".."))
   570 			return path;
   571 		else
   572 			free(path);
   573 	}
   574 #else
   575 	while((dirp = readdir(od->dp)))
   576 		if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, ".."))
   577 			return strdup(dirp->d_name);
   578 #endif
   579 
   580 	if (errno) {
   581 #ifdef MSWIN_API
   582 		razor_set_error_mswin(error, od->path2, GetLastError());
   583 #else
   584 		razor_set_error_posix(error, od->path);
   585 #endif
   586 	}
   587 
   588 	return NULL;
   589 }
   590 
   591 int razor_file_closedir(void *dp, struct razor_error **error)
   592 {
   593 	struct open_dir *od = dp, *odend;
   594 	int retval;
   595 
   596 	odend = open_dirs.data + open_dirs.size;
   597 	if (dp < open_dirs.data || od >= odend || !(od->flags & OPEN_DIR_USED))
   598 		return -2;
   599 
   600 #ifdef MSWIN_API
   601 	/*
   602 	 * I can't find documentation to state that _wclosedir()
   603 	 * returns -1 on failure, so be paranoid.
   604 	 */
   605 	retval = _wclosedir(od->dp) ? -1 : 0;
   606 #else
   607 	retval = closedir(od->dp);
   608 #endif
   609 
   610 	if (retval) {
   611 #ifdef MSWIN_API
   612 		razor_set_error_mswin(error, od->path2, GetLastError());
   613 #else
   614 		razor_set_error_posix(error, od->path);
   615 #endif
   616 	}
   617 
   618 #ifdef MSWIN_API
   619 	free(od->path2);
   620 #else
   621 	free(od->path);
   622 #endif
   623 
   624 	od->flags &= ~OPEN_DIR_USED;
   625 
   626 	for (od = open_dirs.data; od < odend; od++)
   627 		if (od->flags & OPEN_DIR_USED)
   628 			break;
   629 
   630 	if (od == odend) {
   631 		array_release(&open_dirs);
   632 		array_init(&open_dirs);
   633 	}
   634 
   635 	return retval;
   636 }
   637 
   638 struct razor_uri_vtable_entry {
   639 	struct razor_uri_vtable vtable;
   640 	char *scheme;
   641 	struct array open_files, open_directories;
   642 };
   643 
   644 static struct array razor_uri_vtable_entries;
   645 
   646 static struct razor_uri_vtable_entry *
   647   razor_uri_get_vtable_entry(const char *scheme)
   648 {
   649 	struct razor_uri_vtable_entry *entry, *eend, *fallback = NULL;
   650 
   651 	eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size;
   652 	for(entry = razor_uri_vtable_entries.data; entry < eend; entry++) {
   653 		if (!strcmp0(entry->scheme, scheme))
   654 			return entry;
   655 		else if (!entry->scheme)
   656 			fallback = entry;
   657 	}
   658 
   659 	return fallback;
   660 }
   661 
   662 int razor_uri_mkdir(const char *uri, mode_t mode, struct razor_error **error)
   663 {
   664 	int retval;
   665 	char *path;
   666 	struct razor_uri ru;
   667 	struct razor_uri_vtable_entry *entry;
   668 	struct razor_error *tmp_error = NULL;
   669 
   670 	if (razor_uri_parse(&ru, uri, error))
   671 		return -1;
   672 
   673 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   674 
   675 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   676 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   677 		razor_error_free(tmp_error);
   678 	else if (!path) {
   679 		razor_propagate_error(error, tmp_error, NULL);
   680 		razor_uri_destroy(&ru);
   681 		return -1;
   682 	}
   683 
   684 	if (path) {
   685 		razor_uri_destroy(&ru);
   686 		retval = razor_file_mkdir(path, mode, error);
   687 		free(path);
   688 	} else {
   689 		entry = razor_uri_get_vtable_entry(ru.scheme);
   690 		razor_uri_destroy(&ru);
   691 		if (!entry || !entry->vtable.mkdir) {
   692 			retval = -1;
   693 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   694 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   695 					uri, "No URI handler installed");
   696 		} else
   697 			retval = entry->vtable.mkdir(uri, mode, error);
   698 	}
   699 
   700 	return retval;
   701 }
   702 
   703 int razor_uri_unlink(const char *uri, struct razor_error **error)
   704 {
   705 	int retval;
   706 	char *path;
   707 	struct razor_uri ru;
   708 	struct razor_uri_vtable_entry *entry;
   709 	struct razor_error *tmp_error = NULL;
   710 
   711 	if (razor_uri_parse(&ru, uri, error))
   712 		return -1;
   713 
   714 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   715 
   716 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   717 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   718 		razor_error_free(tmp_error);
   719 	else if (!path) {
   720 		razor_propagate_error(error, tmp_error, NULL);
   721 		razor_uri_destroy(&ru);
   722 		return -1;
   723 	}
   724 
   725 	if (path) {
   726 		razor_uri_destroy(&ru);
   727 		retval = razor_file_unlink(path, error);
   728 		free(path);
   729 	} else {
   730 		entry = razor_uri_get_vtable_entry(ru.scheme);
   731 		razor_uri_destroy(&ru);
   732 		if (!entry || !entry->vtable.unlink) {
   733 			retval = -1;
   734 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   735 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   736 					uri, "No URI handler installed");
   737 		} else
   738 			retval = entry->vtable.unlink(uri, error);
   739 	}
   740 
   741 	return retval;
   742 }
   743 
   744 int razor_uri_open(const char *uri, int flags, mode_t mode,
   745 		   struct razor_error **error)
   746 {
   747 	int retval;
   748 	char *path;
   749 	struct razor_uri ru;
   750 	struct razor_uri_vtable_entry *entry;
   751 	struct razor_error *tmp_error = NULL;
   752 
   753 	if (razor_uri_parse(&ru, uri, error))
   754 		return -1;
   755 
   756 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   757 
   758 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   759 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   760 		razor_error_free(tmp_error);
   761 	else if (!path) {
   762 		razor_propagate_error(error, tmp_error, NULL);
   763 		razor_uri_destroy(&ru);
   764 		return -1;
   765 	}
   766 
   767 	if (path) {
   768 		razor_uri_destroy(&ru);
   769 		retval = razor_file_open(path, flags, mode, error);
   770 		free(path);
   771 	} else {
   772 		entry = razor_uri_get_vtable_entry(ru.scheme);
   773 		razor_uri_destroy(&ru);
   774 		if (!entry || !entry->vtable.open) {
   775 			retval = -1;
   776 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   777 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   778 					uri, "No URI handler installed");
   779 		} else
   780 			retval = entry->vtable.open(uri, flags, mode, error);
   781 	}
   782 
   783 	return retval;
   784 }
   785 
   786 int razor_uri_move(const char *src_uri, const char *dst_uri,
   787 		   struct razor_error **error)
   788 {
   789 	int retval;
   790 	char *src_path, *dst_path;
   791 	struct razor_uri src_ru, dst_ru;
   792 	struct razor_uri_vtable_entry *entry;
   793 	struct razor_error *tmp_error = NULL;
   794 
   795 	if (razor_uri_parse(&src_ru, src_uri, error) ||
   796 	    razor_uri_parse(&dst_ru, dst_uri, error))
   797 		return -1;
   798 
   799 	src_path = razor_path_from_parsed_uri(&src_ru, &tmp_error);
   800 
   801 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   802 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   803 		razor_error_free(tmp_error);
   804 	else if (!src_path) {
   805 		razor_propagate_error(error, tmp_error, NULL);
   806 		razor_uri_destroy(&src_ru);
   807 		return -1;
   808 	}
   809 
   810 	dst_path = razor_path_from_parsed_uri(&dst_ru, &tmp_error);
   811 
   812 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   813 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   814 		razor_error_free(tmp_error);
   815 	else if (!dst_path) {
   816 		razor_propagate_error(error, tmp_error, NULL);
   817 		razor_uri_destroy(&dst_ru);
   818 		razor_uri_destroy(&src_ru);
   819 		free(src_path);
   820 		return -1;
   821 	}
   822 
   823 	if (src_path && dst_path)
   824 		retval = razor_file_move(src_path, dst_path, error);
   825 	else {
   826 		if (!strcmp(src_ru.scheme, dst_ru.scheme))
   827 			entry = razor_uri_get_vtable_entry(src_ru.scheme);
   828 		else
   829 			entry = NULL;
   830 		if (entry && entry->vtable.move)
   831 			retval = entry->vtable.move(src_uri, dst_uri, error);
   832 		else if (strcmp(src_ru.scheme, dst_ru.scheme)) {
   833 			retval = -1;
   834 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   835 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   836 					dst_uri, "Cross-scheme URI move");
   837 		} else {
   838 			retval = -1;
   839 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   840 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   841 					src_path ? dst_uri : src_uri,
   842 					"No URI handler installed");
   843 		}
   844 	}
   845 
   846 	razor_uri_destroy(&src_ru);
   847 	razor_uri_destroy(&dst_ru);
   848 	free(src_path);
   849 	free(dst_path);
   850 
   851 	return retval;
   852 }
   853 
   854 void *razor_uri_get_contents(const char *uri, size_t *length, int private,
   855 			     struct razor_error **error)
   856 {
   857 	void *retval;
   858 	char *path;
   859 	struct razor_uri ru;
   860 	struct razor_uri_vtable_entry *entry;
   861 	struct razor_error *tmp_error = NULL;
   862 
   863 	if (razor_uri_parse(&ru, uri, error))
   864 		return NULL;
   865 
   866 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   867 
   868 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   869 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   870 		razor_error_free(tmp_error);
   871 	else if (!path) {
   872 		razor_propagate_error(error, tmp_error, NULL);
   873 		razor_uri_destroy(&ru);
   874 		return NULL;
   875 	}
   876 
   877 	if (path) {
   878 		razor_uri_destroy(&ru);
   879 		retval = razor_file_get_contents(path, length, private, error);
   880 		free(path);
   881 	} else {
   882 		entry = razor_uri_get_vtable_entry(ru.scheme);
   883 		razor_uri_destroy(&ru);
   884 		if (!entry || !entry->vtable.get_contents) {
   885 			retval = NULL;
   886 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   887 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   888 					uri, "No URI handler installed");
   889 		} else {
   890 			retval = entry->vtable.get_contents(uri, length,
   891 							    private, error);
   892 			if (retval)
   893 				ptr_array_add(&entry->open_files, retval);
   894 		}
   895 	}
   896 
   897 	return retval;
   898 }
   899 
   900 int razor_uri_free_contents(void *addr, size_t length)
   901 {
   902 	int retval, of;
   903 	struct razor_uri_vtable_entry *entry, *eend;
   904 
   905 	if (!addr)
   906 		return 0;
   907 
   908 	retval = razor_file_free_contents(addr, length);
   909 
   910 	if (retval != -2)
   911 		return retval;
   912 
   913 	eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size;
   914 	for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) {
   915 		of = ptr_array_find(&entry->open_files, addr);
   916 		if (of >= 0) {
   917 			if (entry->vtable.free_contents)
   918 				retval = entry->vtable.free_contents(addr,
   919 								     length);
   920 			ptr_array_remove_index(&entry->open_files, of);
   921 			break;
   922 		}
   923 	}
   924 
   925 	return retval;
   926 }
   927 
   928 int razor_uri_is_directory(const char *uri, struct razor_error **error)
   929 {
   930 	int retval;
   931 	char *path;
   932 	struct razor_uri ru;
   933 	struct razor_uri_vtable_entry *entry;
   934 	struct razor_error *tmp_error = NULL;
   935 
   936 	if (razor_uri_parse(&ru, uri, error))
   937 		return -1;
   938 
   939 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   940 
   941 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   942 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   943 		razor_error_free(tmp_error);
   944 	else if (!path) {
   945 		razor_propagate_error(error, tmp_error, NULL);
   946 		razor_uri_destroy(&ru);
   947 		return -1;
   948 	}
   949 
   950 	if (path) {
   951 		razor_uri_destroy(&ru);
   952 		retval = razor_file_is_directory(path, error);
   953 		free(path);
   954 	} else {
   955 		entry = razor_uri_get_vtable_entry(ru.scheme);
   956 		razor_uri_destroy(&ru);
   957 		if (!entry || !entry->vtable.is_directory) {
   958 			retval = -1;
   959 			razor_set_error(error, RAZOR_GENERAL_ERROR,
   960 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
   961 					uri, "No URI handler installed");
   962 		} else
   963 			retval = entry->vtable.is_directory(uri, error);
   964 	}
   965 
   966 	return retval;
   967 }
   968 
   969 char *razor_uri_mkdtemp_near(const char *uri, const char *template,
   970 			     struct razor_error **error)
   971 {
   972 	char *retval, *s;
   973 	char *path;
   974 	struct razor_uri ru;
   975 	struct razor_uri_vtable_entry *entry;
   976 	struct razor_error *tmp_error = NULL;
   977 
   978 	if (razor_uri_parse(&ru, uri, error))
   979 		return NULL;
   980 
   981 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
   982 
   983 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
   984 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
   985 		razor_error_free(tmp_error);
   986 	else if (!path) {
   987 		razor_propagate_error(error, tmp_error, NULL);
   988 		razor_uri_destroy(&ru);
   989 		return NULL;
   990 	}
   991 
   992 	if (path) {
   993 		razor_uri_destroy(&ru);
   994 		s = razor_file_mkdtemp_near(path, template, error);
   995 		retval = razor_path_to_uri(s);
   996 		free(s);
   997 		free(path);
   998 	} else {
   999 		entry = razor_uri_get_vtable_entry(ru.scheme);
  1000 		razor_uri_destroy(&ru);
  1001 		if (!entry || !entry->vtable.mkdtemp_near) {
  1002 			retval = NULL;
  1003 			razor_set_error(error, RAZOR_GENERAL_ERROR,
  1004 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
  1005 					uri, "No URI handler installed");
  1006 		} else
  1007 			retval = entry->vtable.mkdtemp_near(uri, template,
  1008 							    error);
  1009 	}
  1010 
  1011 	return retval;
  1012 }
  1013 
  1014 void *razor_uri_opendir(const char *uri, struct razor_error **error)
  1015 {
  1016 	void *retval;
  1017 	char *path;
  1018 	struct razor_uri ru;
  1019 	struct razor_uri_vtable_entry *entry;
  1020 	struct razor_error *tmp_error = NULL;
  1021 
  1022 	if (razor_uri_parse(&ru, uri, error))
  1023 		return NULL;
  1024 
  1025 	path = razor_path_from_parsed_uri(&ru, &tmp_error);
  1026 
  1027 	if (razor_error_matches(tmp_error, RAZOR_GENERAL_ERROR,
  1028 	    RAZOR_GENERAL_ERROR_UNSUPPORTED_URI))
  1029 		razor_error_free(tmp_error);
  1030 	else if (!path) {
  1031 		razor_propagate_error(error, tmp_error, NULL);
  1032 		razor_uri_destroy(&ru);
  1033 		return NULL;
  1034 	}
  1035 
  1036 	if (path) {
  1037 		razor_uri_destroy(&ru);
  1038 		retval = razor_file_opendir(path, error);
  1039 		free(path);
  1040 	} else {
  1041 		entry = razor_uri_get_vtable_entry(ru.scheme);
  1042 		razor_uri_destroy(&ru);
  1043 		if (!entry || !entry->vtable.opendir) {
  1044 			retval = NULL;
  1045 			razor_set_error(error, RAZOR_GENERAL_ERROR,
  1046 					RAZOR_GENERAL_ERROR_UNSUPPORTED_URI,
  1047 					uri, "No URI handler installed");
  1048 		} else {
  1049 			retval = entry->vtable.opendir(uri, error);
  1050 			if (retval)
  1051 				ptr_array_add(&entry->open_directories, retval);
  1052 		}
  1053 	}
  1054 
  1055 	return retval;
  1056 }
  1057 
  1058 char *razor_uri_readdir(void *dir, struct razor_error **error)
  1059 {
  1060 	int od;
  1061 	char *retval;
  1062 	struct razor_uri_vtable_entry *entry, *eend;
  1063 
  1064 	retval = razor_file_readdir(dir, error);
  1065 
  1066 	if (retval != (char *)-1)
  1067 		return retval;
  1068 
  1069 	eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size;
  1070 	for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) {
  1071 		od = ptr_array_find(&entry->open_directories, dir);
  1072 		if (od >= 0) {
  1073 			if (entry->vtable.readdir)
  1074 				retval = entry->vtable.readdir(dir, error);
  1075 			break;
  1076 		}
  1077 	}
  1078 
  1079 	if (retval == (char *)-1) {
  1080 		retval = NULL;
  1081 		razor_set_error(error, RAZOR_GENERAL_ERROR,
  1082 				RAZOR_GENERAL_ERROR_FAILED, NULL,
  1083 				"Invalid directory handle");
  1084 	}
  1085 
  1086 	return retval;
  1087 }
  1088 
  1089 int razor_uri_closedir(void *dir, struct razor_error **error)
  1090 {
  1091 	int od;
  1092 	int retval;
  1093 	struct razor_uri_vtable_entry *entry, *eend;
  1094 
  1095 	retval = razor_file_closedir(dir, error);
  1096 
  1097 	if (retval != -2)
  1098 		return retval;
  1099 
  1100 	eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size;
  1101 	for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) {
  1102 		od = ptr_array_find(&entry->open_directories, dir);
  1103 		if (od >= 0) {
  1104 			if (entry->vtable.closedir)
  1105 				retval = entry->vtable.closedir(dir, error);
  1106 			break;
  1107 		}
  1108 	}
  1109 
  1110 	if (retval == -2)
  1111 		razor_set_error(error, RAZOR_GENERAL_ERROR,
  1112 				RAZOR_GENERAL_ERROR_FAILED, NULL,
  1113 				"Invalid directory handle");
  1114 
  1115 	return retval;
  1116 }
  1117 
  1118 RAZOR_EXPORT int razor_uri_set_vtable(const char *scheme,
  1119   struct razor_uri_vtable *vtable, struct razor_error **error)
  1120 {
  1121 	struct razor_uri_vtable_entry *entry, *eend;
  1122 
  1123 	if (!strcmp0(scheme, "file")) {
  1124 		/*
  1125 		 * There's no fundamental reason why we couldn't support
  1126 		 * this, but it's a lot of work without any obvious need.
  1127 		 */
  1128 		razor_set_error(error, RAZOR_GENERAL_ERROR,
  1129 				RAZOR_GENERAL_ERROR_FAILED, scheme,
  1130 				"Can't override file scheme handler");
  1131 		return -1;
  1132 	}
  1133 
  1134 	if (vtable->structure_size < sizeof(struct razor_uri_vtable)) {
  1135 		/*
  1136 		 * A future version of the API might add vfuncs to the
  1137 		 * table (which we ignore), but won't remove any.
  1138 		 */
  1139 		razor_set_error(error, RAZOR_GENERAL_ERROR,
  1140 				RAZOR_GENERAL_ERROR_FAILED, scheme,
  1141 				"Invalid vtable structure size");
  1142 		return -1;
  1143 	}
  1144 
  1145 	eend = razor_uri_vtable_entries.data + razor_uri_vtable_entries.size;
  1146 	for (entry = razor_uri_vtable_entries.data; entry < eend; entry++) {
  1147 		if (!strcmp0(entry->scheme, scheme))
  1148 			break;
  1149 	}
  1150 
  1151 	if (entry == eend) {
  1152 		if (!vtable)
  1153 			return 0;
  1154 		entry = array_add(&razor_uri_vtable_entries, sizeof *entry);
  1155 		entry->scheme = scheme ? strdup(scheme) : NULL;
  1156 		array_init(&entry->open_files);
  1157 		array_init(&entry->open_directories);
  1158 	} else if (entry->open_files.size || entry->open_directories.size) {
  1159 		razor_set_error(error, RAZOR_GENERAL_ERROR,
  1160 				RAZOR_GENERAL_ERROR_FAILED, scheme,
  1161 				"URI handler busy");
  1162 		return -1;
  1163 	}
  1164 
  1165 	if (vtable) {
  1166 		entry->vtable = *vtable;
  1167 		entry->vtable.structure_size = sizeof(struct razor_uri_vtable);
  1168 	} else {
  1169 		free(entry->scheme);
  1170 		if (entry + 1 < eend)
  1171 			memmove(entry, entry + 1, eend - (entry + 1));
  1172 		array_set_size(&razor_uri_vtable_entries,
  1173 			       razor_uri_vtable_entries.size - sizeof *entry);
  1174 	}
  1175 
  1176 	return 0;
  1177 }