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