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