/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define C_LUCY_FSFILEHANDLE #include "Lucy/Util/ToolSet.h" #include "charmony.h" #include #include // open, POSIX flags #ifdef CHY_HAS_UNISTD_H #include // close #endif #ifdef CHY_HAS_SYS_MMAN_H #include #elif defined(CHY_HAS_WINDOWS_H) #include #include #else #error "No support for memory mapped files" #endif #include "Lucy/Store/ErrorMessage.h" #include "Lucy/Store/FSFileHandle.h" #include "Lucy/Store/FileWindow.h" // Convert FileHandle flags to POSIX flags. static CFISH_INLINE int SI_posix_flags(uint32_t fh_flags) { int posix_flags = 0; if (fh_flags & FH_WRITE_ONLY) { posix_flags |= O_WRONLY; } if (fh_flags & FH_READ_ONLY) { posix_flags |= O_RDONLY; } if (fh_flags & FH_CREATE) { posix_flags |= O_CREAT; } if (fh_flags & FH_EXCLUSIVE) { posix_flags |= O_EXCL; } #ifdef O_LARGEFILE posix_flags |= O_LARGEFILE; #endif #ifdef _O_BINARY posix_flags |= _O_BINARY; #endif return posix_flags; } #define IS_64_BIT (CHY_SIZEOF_PTR == 8 ? 1 : 0) // Memory map a region of the file with shared (read-only) permissions. If // the requested length is 0, return NULL. If an error occurs, return NULL // and set the global error object. static CFISH_INLINE void* SI_map(FSFileHandle *self, FSFileHandleIVARS *ivars, int64_t offset, int64_t len); // Release a memory mapped region assigned by SI_map. static CFISH_INLINE bool SI_unmap(FSFileHandle *self, char *ptr, int64_t len); // 32-bit or 64-bit inlined helpers for FSFH_window. static CFISH_INLINE bool SI_window(FSFileHandle *self, FSFileHandleIVARS *ivars, FileWindow *window, int64_t offset, int64_t len); // Architecture- and OS- specific initialization for a read-only FSFileHandle. static CFISH_INLINE bool SI_init_read_only(FSFileHandle *self, FSFileHandleIVARS *ivars); // Windows-specific routine needed for closing read-only handles. #ifdef CHY_HAS_WINDOWS_H static CFISH_INLINE bool SI_close_win_handles(FSFileHandle *self); #endif FSFileHandle* FSFH_open(String *path, uint32_t flags) { FSFileHandle *self = (FSFileHandle*)Class_Make_Obj(FSFILEHANDLE); return FSFH_do_open(self, path, flags); } FSFileHandle* FSFH_do_open(FSFileHandle *self, String *path, uint32_t flags) { FH_do_open((FileHandle*)self, path, flags); FSFileHandleIVARS *const ivars = FSFH_IVARS(self); if (!path || !Str_Get_Size(path)) { ErrMsg_set("Missing required param 'path'"); CFISH_DECREF(self); return NULL; } // Attempt to open file. if (flags & FH_WRITE_ONLY) { char *path_ptr = Str_To_Utf8(path); ivars->fd = open(path_ptr, SI_posix_flags(flags), 0666); FREEMEM(path_ptr); if (ivars->fd == -1) { ivars->fd = 0; ErrMsg_set_with_errno("Attempt to open '%o' failed", path); CFISH_DECREF(self); return NULL; } if (flags & FH_EXCLUSIVE) { ivars->len = 0; } else { // Derive length. ivars->len = chy_lseek64(ivars->fd, INT64_C(0), SEEK_END); if (ivars->len == -1) { ErrMsg_set_with_errno("lseek64 on %o failed", path); CFISH_DECREF(self); return NULL; } else { int64_t check_val = chy_lseek64(ivars->fd, INT64_C(0), SEEK_SET); if (check_val == -1) { ErrMsg_set_with_errno("lseek64 on %o failed", path); CFISH_DECREF(self); return NULL; } } } } else if (flags & FH_READ_ONLY) { if (SI_init_read_only(self, ivars)) { // On 64-bit systems, map the whole file up-front. if (IS_64_BIT && ivars->len) { ivars->buf = (char*)SI_map(self, ivars, 0, ivars->len); if (!ivars->buf) { // An error occurred during SI_map, which has set // the global error object for us already. CFISH_DECREF(self); return NULL; } } } else { CFISH_DECREF(self); return NULL; } } else { ErrMsg_set("Must specify FH_READ_ONLY or FH_WRITE_ONLY to open '%o'", path); CFISH_DECREF(self); return NULL; } return self; } bool FSFH_Close_IMP(FSFileHandle *self) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); // On 64-bit systems, cancel the whole-file mapping. if (IS_64_BIT && (ivars->flags & FH_READ_ONLY) && ivars->buf != NULL) { if (!SI_unmap(self, ivars->buf, ivars->len)) { return false; } ivars->buf = NULL; } // Close system-specific handles. if (ivars->fd) { if (close(ivars->fd)) { ErrMsg_set_with_errno("Failed to close file"); return false; } ivars->fd = 0; } #if (defined(CHY_HAS_WINDOWS_H) && !defined(CHY_HAS_SYS_MMAN_H)) if (ivars->win_fhandle) { if (!SI_close_win_handles(self)) { return false; } } #endif return true; } bool FSFH_Write_IMP(FSFileHandle *self, const void *data, size_t len) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); if (len) { // Write data, track file length, check for errors. int64_t check_val = write(ivars->fd, data, len); ivars->len += check_val; if ((size_t)check_val != len) { if (check_val == -1) { ErrMsg_set_with_errno("Error when writing %u64 bytes", (uint64_t)len); } else { ErrMsg_set("Attempted to write %u64 bytes, but wrote %i64", (uint64_t)len, check_val); } return false; } } return true; } int64_t FSFH_Length_IMP(FSFileHandle *self) { return FSFH_IVARS(self)->len; } bool FSFH_Window_IMP(FSFileHandle *self, FileWindow *window, int64_t offset, int64_t len) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); const int64_t end = offset + len; if (!(ivars->flags & FH_READ_ONLY)) { ErrMsg_set("Can't read from write-only handle"); return false; } else if (offset < 0) { ErrMsg_set("Can't read from negative offset %i64", offset); return false; } else if (end > ivars->len) { ErrMsg_set("Tried to read past EOF: " "offset %i64 + request %i64 > len %i64", offset, len, ivars->len); return false; } else { return SI_window(self, ivars, window, offset, len); } } int FSFH_Set_FD_IMP(FSFileHandle *self, int fd) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); int old_fd = ivars->fd; ivars->fd = fd; return old_fd; } /********************************* 64-bit *********************************/ #if IS_64_BIT static CFISH_INLINE bool SI_window(FSFileHandle *self, FSFileHandleIVARS *ivars, FileWindow *window, int64_t offset, int64_t len) { UNUSED_VAR(self); FileWindow_Set_Window(window, ivars->buf + offset, offset, len); return true; } bool FSFH_Release_Window_IMP(FSFileHandle *self, FileWindow *window) { UNUSED_VAR(self); FileWindow_Set_Window(window, NULL, 0, 0); return true; } bool FSFH_Read_IMP(FSFileHandle *self, char *dest, int64_t offset, size_t len) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); const int64_t end = offset + (int64_t)len; if (ivars->flags & FH_WRITE_ONLY) { ErrMsg_set("Can't read from write-only filehandle"); return false; } if (offset < 0 || end < offset) { ErrMsg_set("Invalid offset and len (%i64, %u64)", offset, (uint64_t)len); return false; } else if (end > ivars->len) { ErrMsg_set("Tried to read past EOF: " "offset %i64 + request %u64 > len %i64", offset, (uint64_t)len, ivars->len); return false; } memcpy(dest, ivars->buf + offset, len); return true; } /********************************* 32-bit *********************************/ #else static CFISH_INLINE bool SI_window(FSFileHandle *self, FSFileHandleIVARS *ivars, FileWindow *window, int64_t offset, int64_t len) { // Release the previously mmap'd region, if any. FSFH_Release_Window_IMP(self, window); // Start map on a page boundary. Ensure that the window is at // least wide enough to view all the data spec'd in the original // request. const int64_t remainder = offset % ivars->page_size; const int64_t adjusted_offset = offset - remainder; const int64_t adjusted_len = len + remainder; char *const buf = (char*)SI_map(self, ivars, adjusted_offset, adjusted_len); if (len && buf == NULL) { return false; } else { FileWindow_Set_Window(window, buf, adjusted_offset, adjusted_len); } return true; } bool FSFH_Release_Window_IMP(FSFileHandle *self, FileWindow *window) { char *buf = FileWindow_Get_Buf(window); int64_t len = FileWindow_Get_Len(window); if (!SI_unmap(self, buf, len)) { return false; } FileWindow_Set_Window(window, NULL, 0, 0); return true; } #endif // IS_64_BIT vs. 32-bit /********************************* UNIXEN *********************************/ #ifdef CHY_HAS_SYS_MMAN_H static CFISH_INLINE bool SI_init_read_only(FSFileHandle *self, FSFileHandleIVARS *ivars) { UNUSED_VAR(self); // Open. char *path_ptr = Str_To_Utf8(ivars->path); ivars->fd = open(path_ptr, SI_posix_flags(ivars->flags), 0666); FREEMEM(path_ptr); if (ivars->fd == -1) { ivars->fd = 0; ErrMsg_set_with_errno("Can't open '%o'", ivars->path); return false; } // Derive len. ivars->len = chy_lseek64(ivars->fd, INT64_C(0), SEEK_END); if (ivars->len == -1) { ErrMsg_set_with_errno("lseek64 on %o failed", ivars->path); return false; } else { int64_t check_val = chy_lseek64(ivars->fd, INT64_C(0), SEEK_SET); if (check_val == -1) { ErrMsg_set_with_errno("lseek64 on %o failed", ivars->path); return false; } } // Get system page size. #if defined(_SC_PAGESIZE) ivars->page_size = sysconf(_SC_PAGESIZE); #elif defined(_SC_PAGE_SIZE) ivars->page_size = sysconf(_SC_PAGE_SIZE); #else #error "Can't determine system memory page size" #endif return true; } static CFISH_INLINE void* SI_map(FSFileHandle *self, FSFileHandleIVARS *ivars, int64_t offset, int64_t len) { UNUSED_VAR(self); void *buf = NULL; if (len) { // Read-only memory mapping. buf = mmap(NULL, (size_t)len, PROT_READ, MAP_SHARED, ivars->fd, offset); if (buf == (void*)(-1)) { ErrMsg_set_with_errno("mmap of offset %i64 and length %i64 " "(page size %i64) against '%o' failed", offset, len, ivars->page_size, ivars->path); return NULL; } } return buf; } static CFISH_INLINE bool SI_unmap(FSFileHandle *self, char *buf, int64_t len) { if (buf != NULL) { if (munmap(buf, (size_t)len)) { ErrMsg_set_with_errno("Failed to munmap '%o'", FSFH_IVARS(self)->path); return false; } } return true; } #if !IS_64_BIT bool FSFH_Read_IMP(FSFileHandle *self, char *dest, int64_t offset, size_t len) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); int64_t check_val; // Sanity check. if (offset < 0) { ErrMsg_set("Can't read from an offset less than 0 (%i64)", offset); return false; } // Read. check_val = chy_pread64(ivars->fd, dest, len, offset); if (check_val != (int64_t)len) { if (check_val == -1) { ErrMsg_set_with_errno("Tried to read %u64 bytes, got %i64", (uint64_t)len, check_val); } else { ErrMsg_set("Tried to read %u64 bytes, got %i64", (uint64_t)len, check_val); } return false; } return true; } #endif // !IS_64_BIT /********************************* WINDOWS **********************************/ #elif defined(CHY_HAS_WINDOWS_H) static CFISH_INLINE bool SI_init_read_only(FSFileHandle *self, FSFileHandleIVARS *ivars) { UNUSED_VAR(self); char *filepath = Str_To_Utf8(ivars->path); SYSTEM_INFO sys_info; // Get system page size. GetSystemInfo(&sys_info); ivars->page_size = sys_info.dwAllocationGranularity; // Open. ivars->win_fhandle = CreateFile( filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY | FILE_FLAG_OVERLAPPED, NULL ); FREEMEM(filepath); if (ivars->win_fhandle == INVALID_HANDLE_VALUE) { ErrMsg_set_with_win_error("CreateFile for %o failed", ivars->path); return false; } // Derive len. DWORD file_size_hi; DWORD file_size_lo = GetFileSize(ivars->win_fhandle, &file_size_hi); if (file_size_lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) { ErrMsg_set_with_win_error("GetFileSize for %o failed", ivars->path); return false; } ivars->len = ((uint64_t)file_size_hi << 32) | file_size_lo; // Init mapping handle. ivars->buf = NULL; if (ivars->len) { ivars->win_maphandle = CreateFileMapping(ivars->win_fhandle, NULL, PAGE_READONLY, 0, 0, NULL); if (ivars->win_maphandle == NULL) { ErrMsg_set_with_win_error("CreateFileMapping for %o failed", ivars->path); return false; } } return true; } static CFISH_INLINE void* SI_map(FSFileHandle *self, FSFileHandleIVARS *ivars, int64_t offset, int64_t len) { UNUSED_VAR(self); void *buf = NULL; if (len) { // Read-only memory map. uint64_t offs = (uint64_t)offset; DWORD file_offset_hi = offs >> 32; DWORD file_offset_lo = offs & 0xFFFFFFFF; size_t amount = (size_t)len; buf = MapViewOfFile(ivars->win_maphandle, FILE_MAP_READ, file_offset_hi, file_offset_lo, amount); if (buf == NULL) { ErrMsg_set_with_win_error("MapViewOfFile for %o failed", ivars->path); } } return buf; } static CFISH_INLINE bool SI_unmap(FSFileHandle *self, char *buf, int64_t len) { UNUSED_VAR(self); if (buf != NULL) { if (!UnmapViewOfFile(buf)) { ErrMsg_set_with_win_error("Failed to unmap '%o'", FSFH_IVARS(self)->path); return false; } } return true; } static CFISH_INLINE bool SI_close_win_handles(FSFileHandle *self) { FSFileHandleIVARS *ivars = FSFH_IVARS(self); // Close both standard handle and mapping handle. if (ivars->win_maphandle) { if (!CloseHandle(ivars->win_maphandle)) { ErrMsg_set_with_win_error("Failed to close file mapping handle"); return false; } ivars->win_maphandle = NULL; } if (ivars->win_fhandle) { if (!CloseHandle(ivars->win_fhandle)) { ErrMsg_set_with_win_error("Failed to close file handle"); return false; } ivars->win_fhandle = NULL; } return true; } #if !IS_64_BIT bool FSFH_Read_IMP(FSFileHandle *self, char *dest, int64_t offset, size_t len) { FSFileHandleIVARS *const ivars = FSFH_IVARS(self); BOOL check_val; DWORD got; OVERLAPPED read_op_state; uint64_t offs = (uint64_t)offset; read_op_state.hEvent = NULL; read_op_state.OffsetHigh = offs >> 32; read_op_state.Offset = offs & 0xFFFFFFFF; // Sanity check. if (offset < 0) { ErrMsg_set("Can't read from an offset less than 0 (%i64)", offset); return false; } // ReadFile() takes a DWORD (unsigned 32-bit integer) as a length // argument, so throw a sensible error rather than wrap around. if (len > UINT32_MAX) { ErrMsg_set("Can't read more than 4 GB (%u64)", (uint64_t)len); return false; } // Read. check_val = ReadFile(ivars->win_fhandle, dest, len, &got, &read_op_state); if (!check_val && GetLastError() == ERROR_IO_PENDING) { // Read has been queued by the OS and will soon complete. Wait for // it, since this is a blocking IO call from the point of the rest of // the library. check_val = GetOverlappedResult(ivars->win_fhandle, &read_op_state, &got, TRUE); } // Verify that the read has succeeded by now. if (!check_val) { ErrMsg_set_with_win_error("Failed to read %u64 bytes", (uint64_t)len); return false; } return true; } #endif // !IS_64_BIT #endif // CHY_HAS_SYS_MMAN_H vs. CHY_HAS_WINDOWS_H