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