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