/*- * Copyright (c) 2014-2020 MongoDB, Inc. * Copyright (c) 2008-2014 WiredTiger, Inc. * All rights reserved. * * See the file LICENSE for redistribution information. */ #include "wt_internal.h" /* * File system interface for in-memory implementation. */ typedef struct { WT_FILE_SYSTEM iface; TAILQ_HEAD(__wt_fhhash_inmem, __wt_file_handle_inmem) fhhash[WT_HASH_ARRAY_SIZE]; TAILQ_HEAD(__wt_fh_inmem_qh, __wt_file_handle_inmem) fhqh; WT_SPINLOCK lock; } WT_FILE_SYSTEM_INMEM; static int __im_file_size(WT_FILE_HANDLE *, WT_SESSION *, wt_off_t *); /* * __im_handle_search -- * Return a matching handle, if one exists. */ static WT_FILE_HANDLE_INMEM * __im_handle_search(WT_FILE_SYSTEM *file_system, const char *name) { WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; uint64_t bucket, hash; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; hash = __wt_hash_city64(name, strlen(name)); bucket = hash % WT_HASH_ARRAY_SIZE; TAILQ_FOREACH (im_fh, &im_fs->fhhash[bucket], hashq) if (strcmp(im_fh->iface.name, name) == 0) break; return (im_fh); } /* * __im_handle_remove -- * Destroy an in-memory file handle. Should only happen on remove or shutdown. */ static int __im_handle_remove( WT_SESSION_IMPL *session, WT_FILE_SYSTEM *file_system, WT_FILE_HANDLE_INMEM *im_fh, bool force) { WT_FILE_HANDLE *fhp; WT_FILE_SYSTEM_INMEM *im_fs; uint64_t bucket; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; if (im_fh->ref != 0) { __wt_err(session, EBUSY, "%s: file-remove", im_fh->iface.name); if (!force) return (__wt_set_return(session, EBUSY)); } bucket = im_fh->name_hash % WT_HASH_ARRAY_SIZE; WT_FILE_HANDLE_REMOVE(im_fs, im_fh, bucket); /* Clean up private information. */ __wt_buf_free(session, &im_fh->buf); /* Clean up public information. */ fhp = (WT_FILE_HANDLE *)im_fh; __wt_free(session, fhp->name); __wt_free(session, im_fh); return (0); } /* * __im_fs_directory_list -- * Return the directory contents. */ static int __im_fs_directory_list(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *directory, const char *prefix, char ***dirlistp, uint32_t *countp) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; size_t dirallocsz, len; uint32_t count; char *name, **entries; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; *dirlistp = NULL; *countp = 0; dirallocsz = 0; len = strlen(directory); entries = NULL; __wt_spin_lock(session, &im_fs->lock); count = 0; TAILQ_FOREACH (im_fh, &im_fs->fhqh, q) { name = im_fh->iface.name; if (strncmp(name, directory, len) != 0 || (prefix != NULL && !WT_PREFIX_MATCH(name + len, prefix))) continue; WT_ERR(__wt_realloc_def(session, &dirallocsz, count + 1, &entries)); WT_ERR(__wt_strdup(session, name, &entries[count])); ++count; } *dirlistp = entries; *countp = count; err: __wt_spin_unlock(session, &im_fs->lock); if (ret == 0) return (0); if (entries != NULL) { while (count > 0) __wt_free(session, entries[--count]); __wt_free(session, entries); } WT_RET_MSG( session, ret, "%s: directory-list, prefix \"%s\"", directory, prefix == NULL ? "" : prefix); } /* * __im_fs_directory_list_free -- * Free memory returned by __im_fs_directory_list. */ static int __im_fs_directory_list_free( WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, char **dirlist, uint32_t count) { WT_SESSION_IMPL *session; WT_UNUSED(file_system); session = (WT_SESSION_IMPL *)wt_session; if (dirlist != NULL) { while (count > 0) __wt_free(session, dirlist[--count]); __wt_free(session, dirlist); } return (0); } /* * __im_fs_exist -- * Return if the file exists. */ static int __im_fs_exist(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *name, bool *existp) { WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); *existp = __im_handle_search(file_system, name) != NULL; __wt_spin_unlock(session, &im_fs->lock); return (0); } /* * __im_fs_remove -- * POSIX remove. */ static int __im_fs_remove( WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *name, uint32_t flags) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; WT_UNUSED(flags); im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); ret = ENOENT; if ((im_fh = __im_handle_search(file_system, name)) != NULL) ret = __im_handle_remove(session, file_system, im_fh, false); __wt_spin_unlock(session, &im_fs->lock); return (ret); } /* * __im_fs_rename -- * POSIX rename. */ static int __im_fs_rename(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *from, const char *to, uint32_t flags) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; uint64_t bucket; char *copy; WT_UNUSED(flags); im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); ret = ENOENT; if ((im_fh = __im_handle_search(file_system, from)) != NULL) { WT_ERR(__wt_strdup(session, to, ©)); __wt_free(session, im_fh->iface.name); im_fh->iface.name = copy; bucket = im_fh->name_hash % WT_HASH_ARRAY_SIZE; WT_FILE_HANDLE_REMOVE(im_fs, im_fh, bucket); im_fh->name_hash = __wt_hash_city64(to, strlen(to)); bucket = im_fh->name_hash % WT_HASH_ARRAY_SIZE; WT_FILE_HANDLE_INSERT(im_fs, im_fh, bucket); } err: __wt_spin_unlock(session, &im_fs->lock); return (ret); } /* * __im_fs_size -- * Get the size of a file in bytes, by file name. */ static int __im_fs_size(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *name, wt_off_t *sizep) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); /* Search for the handle, then get its size. */ if ((im_fh = __im_handle_search(file_system, name)) == NULL) ret = __wt_set_return(session, ENOENT); else *sizep = (wt_off_t)im_fh->buf.size; __wt_spin_unlock(session, &im_fs->lock); return (ret); } /* * __im_file_close -- * ANSI C close. */ static int __im_file_close(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session) { WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; im_fh = (WT_FILE_HANDLE_INMEM *)file_handle; im_fs = (WT_FILE_SYSTEM_INMEM *)file_handle->file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); --im_fh->ref; __wt_spin_unlock(session, &im_fs->lock); return (0); } /* * __im_file_lock -- * Lock/unlock a file. */ static int __im_file_lock(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, bool lock) { WT_UNUSED(file_handle); WT_UNUSED(wt_session); WT_UNUSED(lock); return (0); } /* * __im_file_read -- * POSIX pread. */ static int __im_file_read( WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, wt_off_t offset, size_t len, void *buf) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; size_t off; im_fh = (WT_FILE_HANDLE_INMEM *)file_handle; im_fs = (WT_FILE_SYSTEM_INMEM *)file_handle->file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); off = (size_t)offset; if (off < im_fh->buf.size) { len = WT_MIN(len, im_fh->buf.size - off); memcpy(buf, (uint8_t *)im_fh->buf.mem + off, len); } else ret = WT_ERROR; __wt_spin_unlock(session, &im_fs->lock); if (ret == 0) return (0); WT_RET_MSG(session, WT_ERROR, "%s: handle-read: failed to read %" WT_SIZET_FMT " bytes at " "offset %" WT_SIZET_FMT, file_handle->name, len, off); } /* * __im_file_size -- * Get the size of a file in bytes, by file handle. */ static int __im_file_size(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, wt_off_t *sizep) { WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; im_fh = (WT_FILE_HANDLE_INMEM *)file_handle; im_fs = (WT_FILE_SYSTEM_INMEM *)file_handle->file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); *sizep = (wt_off_t)im_fh->buf.size; __wt_spin_unlock(session, &im_fs->lock); return (0); } /* * __im_file_sync -- * In-memory sync. */ static int __im_file_sync(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session) { WT_UNUSED(file_handle); WT_UNUSED(wt_session); return (0); } /* * __im_file_write -- * POSIX pwrite. */ static int __im_file_write( WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, wt_off_t offset, size_t len, const void *buf) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; size_t off; im_fh = (WT_FILE_HANDLE_INMEM *)file_handle; im_fs = (WT_FILE_SYSTEM_INMEM *)file_handle->file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); off = (size_t)offset; WT_ERR(__wt_buf_grow(session, &im_fh->buf, off + len + 1024)); memcpy((uint8_t *)im_fh->buf.data + off, buf, len); if (off + len > im_fh->buf.size) im_fh->buf.size = off + len; err: __wt_spin_unlock(session, &im_fs->lock); if (ret == 0) return (0); WT_RET_MSG(session, ret, "%s: handle-write: failed to write %" WT_SIZET_FMT " bytes at " "offset %" WT_SIZET_FMT, file_handle->name, len, off); } /* * __im_file_open -- * POSIX fopen/open. */ static int __im_file_open(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session, const char *name, WT_FS_OPEN_FILE_TYPE file_type, uint32_t flags, WT_FILE_HANDLE **file_handlep) { WT_DECL_RET; WT_FILE_HANDLE *file_handle; WT_FILE_HANDLE_INMEM *im_fh; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; uint64_t bucket, hash; WT_UNUSED(file_type); WT_UNUSED(flags); im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; session = (WT_SESSION_IMPL *)wt_session; __wt_spin_lock(session, &im_fs->lock); /* * First search the file queue, if we find it, assert there's only a single reference, in-memory * only supports a single handle on any file, for now. */ im_fh = __im_handle_search(file_system, name); if (im_fh != NULL) { if (im_fh->ref != 0) WT_ERR_MSG(session, EBUSY, "%s: file-open: already open", name); im_fh->ref = 1; *file_handlep = (WT_FILE_HANDLE *)im_fh; __wt_spin_unlock(session, &im_fs->lock); return (0); } /* The file hasn't been opened before, create a new one. */ WT_ERR(__wt_calloc_one(session, &im_fh)); /* Initialize public information. */ file_handle = (WT_FILE_HANDLE *)im_fh; file_handle->file_system = file_system; WT_ERR(__wt_strdup(session, name, &file_handle->name)); /* Initialize private information. */ im_fh->ref = 1; hash = __wt_hash_city64(name, strlen(name)); bucket = hash % WT_HASH_ARRAY_SIZE; im_fh->name_hash = hash; WT_FILE_HANDLE_INSERT(im_fs, im_fh, bucket); file_handle->close = __im_file_close; file_handle->fh_lock = __im_file_lock; file_handle->fh_read = __im_file_read; file_handle->fh_size = __im_file_size; file_handle->fh_sync = __im_file_sync; file_handle->fh_write = __im_file_write; *file_handlep = file_handle; if (0) { err: __wt_free(session, im_fh); } __wt_spin_unlock(session, &im_fs->lock); return (ret); } /* * __im_terminate -- * Terminate an in-memory configuration. */ static int __im_terminate(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session) { WT_DECL_RET; WT_FILE_HANDLE_INMEM *im_fh, *im_fh_tmp; WT_FILE_SYSTEM_INMEM *im_fs; WT_SESSION_IMPL *session; session = (WT_SESSION_IMPL *)wt_session; im_fs = (WT_FILE_SYSTEM_INMEM *)file_system; WT_TAILQ_SAFE_REMOVE_BEGIN(im_fh, &im_fs->fhqh, q, im_fh_tmp) { WT_TRET(__im_handle_remove(session, file_system, im_fh, true)); } WT_TAILQ_SAFE_REMOVE_END __wt_spin_destroy(session, &im_fs->lock); __wt_free(session, im_fs); return (ret); } /* * __wt_os_inmemory -- * Initialize an in-memory configuration. */ int __wt_os_inmemory(WT_SESSION_IMPL *session) { WT_DECL_RET; WT_FILE_SYSTEM *file_system; WT_FILE_SYSTEM_INMEM *im_fs; u_int i; WT_RET(__wt_calloc_one(session, &im_fs)); /* Initialize private information. */ TAILQ_INIT(&im_fs->fhqh); for (i = 0; i < WT_HASH_ARRAY_SIZE; i++) TAILQ_INIT(&im_fs->fhhash[i]); WT_ERR(__wt_spin_init(session, &im_fs->lock, "in-memory I/O")); /* Initialize the in-memory jump table. */ file_system = (WT_FILE_SYSTEM *)im_fs; file_system->fs_directory_list = __im_fs_directory_list; file_system->fs_directory_list_free = __im_fs_directory_list_free; file_system->fs_exist = __im_fs_exist; file_system->fs_open_file = __im_file_open; file_system->fs_remove = __im_fs_remove; file_system->fs_rename = __im_fs_rename; file_system->fs_size = __im_fs_size; file_system->terminate = __im_terminate; /* Switch the file system into place. */ S2C(session)->file_system = (WT_FILE_SYSTEM *)im_fs; return (0); err: __wt_free(session, im_fs); return (ret); }