text: /*-
   * Public Domain 2014-2018 MongoDB, Inc.
   * Public Domain 2008-2014 WiredTiger, Inc.
   *
   * This is free and unencumbered software released into the public domain.
   *
   * Anyone is free to copy, modify, publish, use, compile, sell, or
   * distribute this software, either in source code form or as a compiled
   * binary, for any purpose, commercial or non-commercial, and by any
   * means.
   *
   * In jurisdictions that recognize copyright laws, the author or authors
   * of this software dedicate any and all copyright interest in the
   * software to the public domain. We make this dedication for the benefit
   * of the public at large and to the detriment of our heirs and
   * successors. We intend this dedication to be an overt act of
   * relinquishment in perpetuity of all present and future rights to this
   * software under copyright law.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   * OTHER DEALINGS IN THE SOFTWARE.
   */
  
  #include <string.h>
  #include <limits.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <assert.h>
  #include <pthread.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <sys/stat.h>
  #include <execinfo.h>
  
  #include <wiredtiger_ext.h>
  #include "queue.h"
  
  #define	FAIL_FS_GIGABYTE		 (1024 * 1024 * 1024)
  
  #define	FAIL_FS_ENV_ENABLE		"WT_FAIL_FS_ENABLE"
  #define	FAIL_FS_ENV_WRITE_ALLOW		"WT_FAIL_FS_WRITE_ALLOW"
  #define	FAIL_FS_ENV_READ_ALLOW		"WT_FAIL_FS_READ_ALLOW"
  
  /*
   * A "fail file system", that is, a file system extension that fails when we
   * want it to.  This is only used in test frameworks, this fact allows us to
   * simplify some error paths. This code is not portable to Windows, as it has
   * direct knowledge of file descriptors, environment variables and stack
   * traces.
   *
   * When the filesystem extension is configured, parameters can set how many
   * reads or writes can be allowed before failure. If this is not fine-grained
   * enough, an 'environment' configuration parameter can be specified. If that
   * is used, then on every file system read or write, environment variables are
   * checked that control when reading or writing should fail.
   */
  typedef struct {
  	WT_FILE_SYSTEM iface;
  	/*
  	 * WiredTiger performs schema and I/O operations in parallel, all file
  	 * system and file handle access must be thread-safe. This extension
  	 * uses a single, global file system lock.
  	 */
  	pthread_rwlock_t lock;                  /* Lock */
  	bool fail_enabled;
  	bool use_environment;
  	bool verbose;
  	int64_t read_ops;
  	int64_t write_ops;
  	int64_t allow_reads;
  	int64_t allow_writes;
  	/* Queue of file handles */
  	TAILQ_HEAD(fail_file_handle_qh, fail_file_handle) fileq;
  	WT_EXTENSION_API *wtext;                /* Extension functions */
  } FAIL_FILE_SYSTEM;
  
  typedef struct fail_file_handle {
  	WT_FILE_HANDLE iface;
  
  	/*
  	 * Track the system file descriptor for each file.
  	 */
  	FAIL_FILE_SYSTEM *fail_fs;              /* Enclosing file system */
  	TAILQ_ENTRY(fail_file_handle) q;        /* Queue of handles */
  	int fd;					/* System file descriptor */
  } FAIL_FILE_HANDLE;
  
  static int fail_file_close(WT_FILE_HANDLE *, WT_SESSION *);
  static void fail_file_handle_remove(WT_SESSION *, FAIL_FILE_HANDLE *);
  static int fail_file_lock(WT_FILE_HANDLE *, WT_SESSION *, bool);
  static int fail_file_read(
      WT_FILE_HANDLE *, WT_SESSION *, wt_off_t, size_t, void *);
  static int fail_file_size(WT_FILE_HANDLE *, WT_SESSION *, wt_off_t *);
  static int fail_file_sync(WT_FILE_HANDLE *, WT_SESSION *);
  static int fail_file_truncate(WT_FILE_HANDLE *, WT_SESSION *, wt_off_t);
  static int fail_file_write(
      WT_FILE_HANDLE *, WT_SESSION *, wt_off_t, size_t, const void *);
  static bool fail_fs_arg(
      const char *, WT_CONFIG_ITEM *, WT_CONFIG_ITEM *, int64_t *);
  static int fail_fs_directory_list(WT_FILE_SYSTEM *, WT_SESSION *,
      const char *, const char *, char ***, uint32_t *);
  static int fail_fs_directory_list_free(
      WT_FILE_SYSTEM *, WT_SESSION *, char **, uint32_t);
  static void fail_fs_env(const char *, int64_t *);
  static int fail_fs_exist(WT_FILE_SYSTEM *, WT_SESSION *, const char *, bool *);
  static int fail_fs_open(WT_FILE_SYSTEM *, WT_SESSION *,
      const char *, WT_FS_OPEN_FILE_TYPE, uint32_t, WT_FILE_HANDLE **);
  static int fail_fs_remove(
      WT_FILE_SYSTEM *, WT_SESSION *, const char *, uint32_t);
  static int fail_fs_rename(
      WT_FILE_SYSTEM *, WT_SESSION *, const char *, const char *, uint32_t);
  static int fail_fs_simulate_fail(
      FAIL_FILE_HANDLE *, WT_SESSION *, int64_t, const char *);
  static int fail_fs_size(
      WT_FILE_SYSTEM *, WT_SESSION *, const char *, wt_off_t *);
  static int fail_fs_terminate(WT_FILE_SYSTEM *, WT_SESSION *);
  
  /*
   * We use pthread functions for portable locking.
   * Assert on errors for simplicity.
   */
  static void
  fail_fs_allocate_lock(pthread_rwlock_t *lockp)
  {
  	assert(pthread_rwlock_init(lockp, NULL) == 0);
  }
  
  static void
  fail_fs_destroy_lock(pthread_rwlock_t *lockp)
  {
  	assert(pthread_rwlock_destroy(lockp) == 0);
  }
  
  static void
  fail_fs_lock(pthread_rwlock_t *lockp)
  {
  	assert(pthread_rwlock_wrlock(lockp) == 0);
  }
  
  static void
  fail_fs_unlock(pthread_rwlock_t *lockp)
  {
  	assert(pthread_rwlock_unlock(lockp) == 0);
  }
  
  /*
   * fail_file_close --
   *	ANSI C close.
   */
  static int
  fail_file_close(WT_FILE_HANDLE *file_handle, WT_SESSION *session)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	FAIL_FILE_SYSTEM *fail_fs;
  	int ret;
  
  	(void)session;						/* Unused */
  
  	fail_fh = (FAIL_FILE_HANDLE *)file_handle;
  	fail_fs = fail_fh->fail_fs;
  
  	/*
  	 * We don't actually open an fd when opening directories for flushing,
  	 * so ignore that case here.
  	 */
  	if (fail_fh->fd < 0)
  		return (0);
  	ret = close(fail_fh->fd);
  	fail_fh->fd = -1;
  	fail_fs_lock(&fail_fs->lock);
  	fail_file_handle_remove(session, fail_fh);
  	fail_fs_unlock(&fail_fs->lock);
  	return (ret);
  }
  
  /*
   * fail_file_handle_remove --
   *	Destroy an in-memory file handle. Should only happen on remove or
   *	shutdown. The file system lock must be held during this call.
   */
  static void
  fail_file_handle_remove(WT_SESSION *session, FAIL_FILE_HANDLE *fail_fh)
  {
  	FAIL_FILE_SYSTEM *fail_fs;
  
  	(void)session;						/* Unused */
  	fail_fs = fail_fh->fail_fs;
  
  	TAILQ_REMOVE(&fail_fs->fileq, fail_fh, q);
  
  	free(fail_fh->iface.name);
  	free(fail_fh);
  }
  
  /*
   * fail_file_lock --
   *	Lock/unlock a file.
   */
  static int
  fail_file_lock(WT_FILE_HANDLE *file_handle, WT_SESSION *session, bool lock)
  {
  	/* Locks are always granted. */
  	(void)file_handle;					/* Unused */
  	(void)session;						/* Unused */
  	(void)lock;						/* Unused */
  
  	return (0);
  }
  
  /*
   * fail_file_read --
   *	POSIX pread.
   */
  static int
  fail_file_read(WT_FILE_HANDLE *file_handle,
      WT_SESSION *session, wt_off_t offset, size_t len, void *buf)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	FAIL_FILE_SYSTEM *fail_fs;
  	WT_EXTENSION_API *wtext;
  	int64_t envint, read_ops;
  	int ret;
  	size_t chunk;
  	ssize_t nr;
  	uint8_t *addr;
  
  	fail_fh = (FAIL_FILE_HANDLE *)file_handle;
  	fail_fs = fail_fh->fail_fs;
  	wtext = fail_fs->wtext;
  	read_ops = 0;
  	ret = 0;
  
  	fail_fs_lock(&fail_fs->lock);
  
  	if (fail_fs->use_environment) {
  		fail_fs_env(FAIL_FS_ENV_ENABLE, &envint);
  		if (envint != 0) {
  			if (!fail_fs->fail_enabled) {
  				fail_fs->fail_enabled = true;
  				fail_fs_env(FAIL_FS_ENV_READ_ALLOW,
  				    &fail_fs->allow_reads);
  				fail_fs->read_ops = 0;
  			}
  			read_ops = ++fail_fs->read_ops;
  		} else
  			fail_fs->fail_enabled = false;
  	} else
  		read_ops = ++fail_fs->read_ops;
  
  	fail_fs_unlock(&fail_fs->lock);
  
  	if (fail_fs->fail_enabled && fail_fs->allow_reads != 0 &&
  	    read_ops % fail_fs->allow_reads == 0)
  		return (fail_fs_simulate_fail(
  		    fail_fh, session, read_ops, "read"));
  
  	/* Break reads larger than 1GB into 1GB chunks. */
  	for (addr = buf; len > 0; addr += nr, len -= (size_t)nr, offset += nr) {
  		chunk = (len < FAIL_FS_GIGABYTE) ? len : FAIL_FS_GIGABYTE;
  		if ((nr = pread(fail_fh->fd, addr, chunk, offset)) <= 0) {
  			(void)wtext->err_printf(wtext, session,
  			    "%s: handle-read: failed to read %" PRIuMAX
  			    " bytes at offset %" PRIuMAX ": %s",
  			    fail_fh->iface.name,
  			    (uintmax_t)len, (uintmax_t)offset,
  			    wtext->strerror(wtext, NULL, errno));
  			ret = (nr == 0 ? WT_ERROR : errno);
  			break;
  		}
  	}
  	return (ret);
  }
  
  /*
   * fail_file_size --
   *	Get the size of a file in bytes, by file handle.
   */
  static int
  fail_file_size(
      WT_FILE_HANDLE *file_handle, WT_SESSION *session, wt_off_t *sizep)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	struct stat statbuf;
  	int ret;
  
  	(void)session;						/* Unused */
  
  	fail_fh = (FAIL_FILE_HANDLE *)file_handle;
  	ret = 0;
  
  	if ((ret = fstat(fail_fh->fd, &statbuf)) != 0)
  		return (ret);
  	*sizep = statbuf.st_size;
  	return (0);
  }
  
  /*
   * fail_file_sync --
   *	Ensure the content of the file is stable. This is a no-op in our
   *	file system.
   */
  static int
  fail_file_sync(WT_FILE_HANDLE *file_handle, WT_SESSION *session)
  {
  	(void)file_handle;					/* Unused */
  	(void)session;						/* Unused */
  
  	return (0);
  }
  
  /*
   * fail_file_truncate --
   *	POSIX ftruncate.
   */
  static int
  fail_file_truncate(
      WT_FILE_HANDLE *file_handle, WT_SESSION *session, wt_off_t offset)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  
  	(void)session;						/* Unused */
  
  	fail_fh = (FAIL_FILE_HANDLE *)file_handle;
  	return (ftruncate(fail_fh->fd, offset));
  }
  
  /*
   * fail_file_write --
   *	POSIX pwrite.
   */
  static int
  fail_file_write(WT_FILE_HANDLE *file_handle, WT_SESSION *session,
      wt_off_t offset, size_t len, const void *buf)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	FAIL_FILE_SYSTEM *fail_fs;
  	WT_EXTENSION_API *wtext;
  	int64_t envint, write_ops;
  	int ret;
  	size_t chunk;
  	ssize_t nr;
  	const uint8_t *addr;
  
  	fail_fh = (FAIL_FILE_HANDLE *)file_handle;
  	fail_fs = fail_fh->fail_fs;
  	wtext = fail_fs->wtext;
  	write_ops = 0;
  	ret = 0;
  
  	fail_fs_lock(&fail_fs->lock);
  
  	if (fail_fs->use_environment) {
  		fail_fs_env(FAIL_FS_ENV_ENABLE, &envint);
  		if (envint != 0) {
  			if (!fail_fs->fail_enabled) {
  				fail_fs->fail_enabled = true;
  				fail_fs_env(FAIL_FS_ENV_WRITE_ALLOW,
  				    &fail_fs->allow_writes);
  				fail_fs->write_ops = 0;
  			}
  			write_ops = ++fail_fs->write_ops;
  		} else
  			fail_fs->fail_enabled = false;
  	} else
  		write_ops = ++fail_fs->write_ops;
  
  	fail_fs_unlock(&fail_fs->lock);
  
  	if (fail_fs->fail_enabled && fail_fs->allow_writes != 0 &&
  	    write_ops % fail_fs->allow_writes == 0)
  		return (fail_fs_simulate_fail(
  		    fail_fh, session, write_ops, "write"));
  
  	/* Break writes larger than 1GB into 1GB chunks. */
  	for (addr = buf; len > 0; addr += nr, len -= (size_t)nr, offset += nr) {
  		chunk = (len < FAIL_FS_GIGABYTE) ? len : FAIL_FS_GIGABYTE;
  		if ((nr = pwrite(fail_fh->fd, addr, chunk, offset)) <= 0) {
  			(void)wtext->err_printf(wtext, session,
  			    "%s: handle-write: failed to write %" PRIuMAX
  			    " bytes at offset %" PRIuMAX ": %s",
  			    fail_fh->iface.name,
  			    (uintmax_t)len, (uintmax_t)offset,
  			    wtext->strerror(wtext, NULL, errno));
  			ret = (nr == 0 ? WT_ERROR : errno);
  			break;
  		}
  	}
  	return (ret);
  }
  
  /*
   * fail_fs_arg --
   *      If the key matches, return the value interpreted as an integer.
   */
  static bool
  fail_fs_arg(const char *match, WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value,
      int64_t *argp)
  {
  	if (strncmp(match, key->str, key->len) == 0 &&
  	    match[key->len] == '\0' &&
  	    (value->type == WT_CONFIG_ITEM_BOOL ||
  	    value->type == WT_CONFIG_ITEM_NUM)) {
  		*argp = value->val;
  		return (true);
  	}
  	return (false);
  }
  
  /*
   * fail_fs_directory_list --
   *	Return a list of files in a given sub-directory.
   */
  static int
  fail_fs_directory_list(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, const char *directory,
      const char *prefix, char ***dirlistp, uint32_t *countp)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	FAIL_FILE_SYSTEM *fail_fs;
  	size_t len, prefix_len;
  	uint32_t allocated, count;
  	int ret;
  	char *name, **entries;
  	void *p;
  
  	(void)session;						/* Unused */
  
  	fail_fs = (FAIL_FILE_SYSTEM *)file_system;
  	ret = 0;
  	*dirlistp = NULL;
  	*countp = 0;
  
  	entries = NULL;
  	allocated = count = 0;
  	len = strlen(directory);
  	prefix_len = prefix == NULL ? 0 : strlen(prefix);
  
  	fail_fs_lock(&fail_fs->lock);
  	TAILQ_FOREACH(fail_fh, &fail_fs->fileq, q) {
  		name = fail_fh->iface.name;
  		if (strncmp(name, directory, len) != 0 ||
  		    (prefix != NULL && strncmp(name, prefix, prefix_len) != 0))
  			continue;
  
  		/*
  		 * Increase the list size in groups of 10, it doesn't
  		 * matter if the list is a bit longer than necessary.
  		 */
  		if (count >= allocated) {
  			allocated += 10;
  			if ((p = realloc(
  			    entries, allocated * sizeof(*entries))) == NULL) {
  				ret = ENOMEM;
  				goto err;
  			}
  			entries = p;
  			memset(entries + count, 0, 10 * sizeof(*entries));
  		}
  		entries[count++] = strdup(name);
  	}
  
  	*dirlistp = entries;
  	*countp = count;
  
  err:	fail_fs_unlock(&fail_fs->lock);
  	if (ret == 0)
  		return (0);
  
  	if (entries != NULL) {
  		while (count > 0)
  			free(entries[--count]);
  		free(entries);
  	}
  
  	return (ret);
  }
  
  /*
   * fail_fs_directory_list_free --
   *	Free memory allocated by fail_fs_directory_list.
   */
  static int
  fail_fs_directory_list_free(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, char **dirlist, uint32_t count)
  {
  	(void)file_system;					/* Unused */
  	(void)session;						/* Unused */
  
  	if (dirlist != NULL) {
  		while (count > 0)
  			free(dirlist[--count]);
  		free(dirlist);
  	}
  	return (0);
  }
  
  /*
   * fail_fs_env --
   *      If the name is in the environment, return its integral value.
   */
  static void
  fail_fs_env(const char *name, int64_t *valp)
  {
  	int64_t result;
  	char *s, *value;
  
  	result = 0;
  	if ((value = getenv(name)) != NULL) {
  		s = value;
  		if (strcmp(value, "true") == 0)
  			result = 1;
  		else if (strcmp(value, "false") != 0) {
  			result = strtoll(value, &s, 10);
  			if (*s != '\0')
  				result = 0;
  		}
  	}
  	*valp = result;
  }
  
  /*
   * fail_fs_exist --
   *	Return if the file exists.
   */
  static int
  fail_fs_exist(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, const char *name, bool *existp)
  {
  	(void)file_system;					/* Unused */
  	(void)session;						/* Unused */
  
  	*existp = (access(name, F_OK) == 0);
  	return (0);
  }
  
  /*
   * fail_fs_open --
   *	fopen for the fail file system.
   */
  static int
  fail_fs_open(WT_FILE_SYSTEM *file_system, WT_SESSION *session,
      const char *name, WT_FS_OPEN_FILE_TYPE file_type, uint32_t flags,
      WT_FILE_HANDLE **file_handlep)
  {
  	FAIL_FILE_HANDLE *fail_fh;
  	FAIL_FILE_SYSTEM *fail_fs;
  	WT_EXTENSION_API *wtext;
  	WT_FILE_HANDLE *file_handle;
  	int fd, open_flags, ret;
  
  	(void)session;						/* Unused */
  
  	*file_handlep = NULL;
  
  	fail_fh = NULL;
  	fail_fs = (FAIL_FILE_SYSTEM *)file_system;
  	fd = -1;
  	ret = 0;
  
  	if (fail_fs->verbose) {
  		wtext = fail_fs->wtext;
  		(void)wtext->msg_printf(wtext, session, "fail_fs: open: %s",
  		    name);
  	}
  
  	fail_fs_lock(&fail_fs->lock);
  
  	open_flags = 0;
  	if ((flags & WT_FS_OPEN_CREATE) != 0)
  		open_flags |= O_CREAT;
  	if ((flags & WT_FS_OPEN_EXCLUSIVE) != 0)
  		open_flags |= O_EXCL;
  	if ((flags & WT_FS_OPEN_READONLY) != 0)
  		open_flags |= O_RDONLY;
  	else
  		open_flags |= O_RDWR;
  
  	/*
  	 * Opening a file handle on a directory is only to support filesystems
  	 * that require a directory sync for durability.  This is a no-op
  	 * for this file system.
  	 */
  	if (file_type == WT_FS_OPEN_FILE_TYPE_DIRECTORY)
  		fd = -1;
  	else if ((fd = open(name, open_flags, 0666)) < 0) {
  		ret = errno;
  		goto err;
  	}
  
  	/* We create a handle structure for each open. */
  	if ((fail_fh = calloc(1, sizeof(FAIL_FILE_HANDLE))) == NULL) {
  		ret = ENOMEM;
  		goto err;
  	}
  
  	/* Initialize private information. */
  	fail_fh->fail_fs = fail_fs;
  	fail_fh->fd = fd;
  
  	/* Initialize public information. */
  	file_handle = (WT_FILE_HANDLE *)fail_fh;
  	if ((file_handle->name = strdup(name)) == NULL) {
  		ret = ENOMEM;
  		goto err;
  	}
  
  	/* Setup the function call table. */
  	file_handle->close = fail_file_close;
  	file_handle->fh_advise = NULL;
  	file_handle->fh_extend = NULL;
  	file_handle->fh_extend_nolock = NULL;
  	file_handle->fh_lock = fail_file_lock;
  	file_handle->fh_map = NULL;
  	file_handle->fh_map_discard = NULL;
  	file_handle->fh_map_preload = NULL;
  	file_handle->fh_unmap = NULL;
  	file_handle->fh_read = fail_file_read;
  	file_handle->fh_size = fail_file_size;
  	file_handle->fh_sync = fail_file_sync;
  	file_handle->fh_sync_nowait = NULL;
  	file_handle->fh_truncate = fail_file_truncate;
  	file_handle->fh_write = fail_file_write;
  
  	TAILQ_INSERT_HEAD(&fail_fs->fileq, fail_fh, q);
  
  	*file_handlep = file_handle;
  
  	if (0) {
  err:		if (fd != -1)
  			(void)close(fd);
  		free(fail_fh);
  	}
  
  	fail_fs_unlock(&fail_fs->lock);
  	return (ret);
  }
  
  /*
   * fail_fs_remove --
   *	POSIX remove.
   */
  static int
  fail_fs_remove(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, const char *name, uint32_t flags)
  {
  	(void)file_system;					/* Unused */
  	(void)session;						/* Unused */
  	(void)flags;						/* Unused */
  
  	return (unlink(name));
  }
  
  /*
   * fail_fs_rename --
   *	POSIX rename.
   */
  static int
  fail_fs_rename(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, const char *from, const char *to, uint32_t flags)
  {
  	(void)file_system;					/* Unused */
  	(void)session;						/* Unused */
  	(void)flags;						/* Unused */
  
  	return (rename(from, to));
  }
  
  /*
   * fail_fs_simulate_fail --
   *	Simulate a failure from this file system by reporting it
   *	and returning a non-zero return code.
   */
  static int
  fail_fs_simulate_fail(FAIL_FILE_HANDLE *fail_fh, WT_SESSION *session,
      int64_t nops, const char *opkind)
  {
  	FAIL_FILE_SYSTEM *fail_fs;
  	WT_EXTENSION_API *wtext;
  #ifdef __FreeBSD__
  	size_t btret, i;
  #else
  	int btret, i;
  #endif
  	void *bt[100];
  	char **btstr;
  
  	fail_fs = fail_fh->fail_fs;
  	if (fail_fs->verbose) {
  		wtext = fail_fs->wtext;
  		(void)wtext->msg_printf(wtext, session,
  		    "fail_fs: %s: simulated failure after %" PRId64
  		    " %s operations", fail_fh->iface.name, nops, opkind);
  #ifdef __FreeBSD__
  		btret = backtrace(bt, sizeof(bt) / sizeof(bt[0]));
  #else
  		btret = backtrace(bt, (int)(sizeof(bt) / sizeof(bt[0])));
  #endif
  		if ((btstr = backtrace_symbols(bt, btret)) != NULL) {
  			for (i = 0; i < btret; i++)
  				(void)wtext->msg_printf(wtext, session, "  %s",
  				    btstr[i]);
  			free(btstr);
  		}
  	}
  	return (EIO);
  }
  
  /*
   * fail_fs_size --
   *	Get the size of a file in bytes, by file name.
   */
  static int
  fail_fs_size(WT_FILE_SYSTEM *file_system,
      WT_SESSION *session, const char *name, wt_off_t *sizep)
  {
  	struct stat statbuf;
  	int ret;
  
  	(void)file_system;					/* Unused */
  	(void)session;						/* Unused */
  
  	ret = 0;
  	if ((ret = stat(name, &statbuf)) != 0)
  		return (ret);
  	*sizep = statbuf.st_size;
  	return (0);
  }
  
  /*
   * fail_fs_terminate --
   *	Discard any resources on termination
   */
  static int
  fail_fs_terminate(WT_FILE_SYSTEM *file_system, WT_SESSION *session)
  {
  	FAIL_FILE_HANDLE *fail_fh, *fail_fh_tmp;
  	FAIL_FILE_SYSTEM *fail_fs;
  
  	fail_fs = (FAIL_FILE_SYSTEM *)file_system;
  
  	TAILQ_FOREACH_SAFE(fail_fh, &fail_fs->fileq, q, fail_fh_tmp)
  		fail_file_handle_remove(session, fail_fh);
  
  	fail_fs_destroy_lock(&fail_fs->lock);
  	free(fail_fs);
  
  	return (0);
  }
  
  /*
   * wiredtiger_extension_init --
   *	WiredTiger fail filesystem extension.
   */
  int
  wiredtiger_extension_init(WT_CONNECTION *conn, WT_CONFIG_ARG *config)
  {
  	FAIL_FILE_SYSTEM *fail_fs;
  	WT_CONFIG_ITEM k, v;
  	WT_CONFIG_PARSER *config_parser;
  	WT_EXTENSION_API *wtext;
  	WT_FILE_SYSTEM *file_system;
  	int64_t argval;
  	int ret;
  
  	config_parser = NULL;
  	wtext = conn->get_extension_api(conn);
  	ret = 0;
  
  	if ((fail_fs = calloc(1, sizeof(FAIL_FILE_SYSTEM))) == NULL) {
  		(void)wtext->err_printf(wtext, NULL,
  		    "fail_file_system extension_init: %s",
  		    wtext->strerror(wtext, NULL, ENOMEM));
  		return (ENOMEM);
  	}
  	fail_fs->wtext = wtext;
  	file_system = (WT_FILE_SYSTEM *)fail_fs;
  
  	/* Get any configuration values. */
  	if ((ret = wtext->config_parser_open_arg(
  	    wtext, NULL, config, &config_parser)) != 0) {
  		(void)wtext->err_printf(wtext, NULL,
  		    "WT_EXTENSION_API.config_parser_open: config: %s",
  		    wtext->strerror(wtext, NULL, ret));
  		goto err;
  	}
  	while ((ret = config_parser->next(config_parser, &k, &v)) == 0) {
  		if (fail_fs_arg("environment", &k, &v, &argval)) {
  			fail_fs->use_environment = (argval != 0);
  			continue;
  		} else if (fail_fs_arg("verbose", &k, &v, &argval)) {
  			fail_fs->verbose = (argval != 0);
  			continue;
  		} else if (fail_fs_arg("allow_writes", &k, &v,
  		    &fail_fs->allow_writes))
  			continue;
  		else if (fail_fs_arg("allow_reads", &k, &v,
  		    &fail_fs->allow_reads))
  			continue;
  
  		(void)wtext->err_printf(wtext, NULL,
  		    "WT_CONFIG_PARSER.next: unexpected configuration "
  		    "information: %.*s=%.*s: %s",
  		    (int)k.len, k.str, (int)v.len, v.str,
  		    wtext->strerror(wtext, NULL, ret));
  		goto err;
  	}
  	if (ret != WT_NOTFOUND) {
  		(void)wtext->err_printf(wtext, NULL,
  		    "WT_CONFIG_PARSER.next: config: %s",
  		    wtext->strerror(wtext, NULL, ret));
  		goto err;
  	}
  	ret = config_parser->close(config_parser);
  	config_parser = NULL;
  	if (ret != 0) {
  		(void)wtext->err_printf(wtext, NULL,
  		    "WT_CONFIG_PARSER.close: config: %s",
  		    wtext->strerror(wtext, NULL, ret));
  		goto err;
  	}
  	if (fail_fs->allow_writes != 0 || fail_fs->allow_reads != 0)
  		fail_fs->fail_enabled = true;
  
  	fail_fs_allocate_lock(&fail_fs->lock);
  	/* Initialize the in-memory jump table. */
  	file_system->fs_directory_list = fail_fs_directory_list;
  	file_system->fs_directory_list_free = fail_fs_directory_list_free;
  	file_system->fs_exist = fail_fs_exist;
  	file_system->fs_open_file = fail_fs_open;
  	file_system->fs_remove = fail_fs_remove;
  	file_system->fs_rename = fail_fs_rename;
  	file_system->fs_size = fail_fs_size;
  	file_system->terminate = fail_fs_terminate;
  	if ((ret = conn->set_file_system(conn, file_system, NULL)) != 0) {
  		(void)wtext->err_printf(wtext, NULL,
  		    "WT_CONNECTION.set_file_system: %s",
  		    wtext->strerror(wtext, NULL, ret));
  		goto err;
  	}
  	return (0);
  
  err:	if (config_parser != NULL)
  		(void)config_parser->close(config_parser);
  	free(fail_fs);
  	return (ret);
  }
  