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