blob: a1caf8020783c9773770f7027f55783b67775b64 [file] [log] [blame]
/* 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_FSFOLDER
#include "Lucy/Util/ToolSet.h"
#include "charmony.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#ifdef CHY_HAS_SYS_TYPES_H
#include <sys/types.h>
#endif
// For rmdir, (hard) link.
#ifdef CHY_HAS_UNISTD_H
#include <unistd.h>
#endif
// For mkdir, rmdir.
#ifdef CHY_HAS_DIRECT_H
#include <direct.h>
#endif
#include "Clownfish/CharBuf.h"
#include "Lucy/Store/ErrorMessage.h"
#include "Lucy/Store/FSFolder.h"
#include "Lucy/Store/CompoundFileReader.h"
#include "Lucy/Store/CompoundFileWriter.h"
#include "Lucy/Store/FSDirHandle.h"
#include "Lucy/Store/FSFileHandle.h"
#include "Lucy/Store/InStream.h"
#include "Lucy/Store/OutStream.h"
#include "Lucy/Util/IndexFileNames.h"
// Return a String containing a platform-specific absolute filepath.
static String*
S_fullpath(FSFolder *self, String *path);
// Return a String containing a platform-specific absolute filepath.
static char*
S_fullpath_ptr(FSFolder *self, String *path);
// Return true if the supplied path is a directory.
static bool
S_dir_ok(String *path);
// Create a directory, or set the global error object and return false.
static bool
S_create_dir(String *path);
// Return true unless the supplied path contains a slash.
static bool
S_is_local_entry(String *path);
// Return true if the supplied path is absolute.
static bool
S_is_absolute(String *path);
// Transform a possibly relative path into an absolute path.
static String*
S_absolutify(String *path);
// Rename a directory or file.
static bool
S_rename(const char *from_path, const char *to_path);
// Create a hard link.
static bool
S_hard_link(const char *from_path, const char *to_path);
// Delete a directory or file.
static bool
S_delete(const char *path);
FSFolder*
FSFolder_new(String *path) {
FSFolder *self = (FSFolder*)Class_Make_Obj(FSFOLDER);
return FSFolder_init(self, path);
}
FSFolder*
FSFolder_init(FSFolder *self, String *path) {
String *abs_path = S_absolutify(path);
Folder_init((Folder*)self, abs_path);
DECREF(abs_path);
return self;
}
void
FSFolder_Initialize_IMP(FSFolder *self) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
if (!S_dir_ok(ivars->path)) {
if (!S_create_dir(ivars->path)) {
RETHROW(INCREF(Err_get_error()));
}
}
}
bool
FSFolder_Check_IMP(FSFolder *self) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
return S_dir_ok(ivars->path);
}
FileHandle*
FSFolder_Local_Open_FileHandle_IMP(FSFolder *self, String *name,
uint32_t flags) {
String *fullpath = S_fullpath(self, name);
FSFileHandle *fh = FSFH_open(fullpath, flags);
if (!fh) { ERR_ADD_FRAME(Err_get_error()); }
DECREF(fullpath);
return (FileHandle*)fh;
}
bool
FSFolder_Local_MkDir_IMP(FSFolder *self, String *name) {
String *dir = S_fullpath(self, name);
bool result = S_create_dir(dir);
if (!result) { ERR_ADD_FRAME(Err_get_error()); }
DECREF(dir);
return result;
}
DirHandle*
FSFolder_Local_Open_Dir_IMP(FSFolder *self) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
DirHandle *dh = (DirHandle*)FSDH_open(ivars->path);
if (!dh) { ERR_ADD_FRAME(Err_get_error()); }
return dh;
}
bool
FSFolder_Local_Exists_IMP(FSFolder *self, String *name) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
if (Hash_Fetch(ivars->entries, name)) {
return true;
}
else if (!S_is_local_entry(name)) {
return false;
}
else {
struct stat stat_buf;
char *fullpath_ptr = S_fullpath_ptr(self, name);
bool retval = false;
if (stat(fullpath_ptr, &stat_buf) != -1) {
retval = true;
}
FREEMEM(fullpath_ptr);
return retval;
}
}
bool
FSFolder_Local_Is_Directory_IMP(FSFolder *self, String *name) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
// Check for a cached object, then fall back to a system call.
Obj *elem = Hash_Fetch(ivars->entries, name);
if (elem && Obj_is_a(elem, FOLDER)) {
return true;
}
else {
String *fullpath = S_fullpath(self, name);
bool result = S_dir_ok(fullpath);
DECREF(fullpath);
return result;
}
}
bool
FSFolder_Rename_IMP(FSFolder *self, String* from, String *to) {
// TODO: Update Folder entries.
char *from_path = S_fullpath_ptr(self, from);
char *to_path = S_fullpath_ptr(self, to);
bool retval = S_rename(from_path, to_path);
FREEMEM(from_path);
FREEMEM(to_path);
return retval;
}
bool
FSFolder_Hard_Link_IMP(FSFolder *self, String *from,
String *to) {
// TODO: Update Folder entries.
char *from_path = S_fullpath_ptr(self, from);
char *to_path = S_fullpath_ptr(self, to);
bool retval = S_hard_link(from_path, to_path);
FREEMEM(from_path);
FREEMEM(to_path);
return retval;
}
bool
FSFolder_Local_Delete_IMP(FSFolder *self, String *name) {
// TODO: Delete should only delete files. We should add RmDir for
// directories.
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
char *path = S_fullpath_ptr(self, name);
bool retval = S_delete(path);
DECREF(Hash_Delete(ivars->entries, name));
FREEMEM(path);
return retval;
}
void
FSFolder_Close_IMP(FSFolder *self) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
Hash_Clear(ivars->entries);
}
Folder*
FSFolder_Local_Find_Folder_IMP(FSFolder *self, String *name) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
Folder *subfolder = NULL;
if (!name || !Str_Get_Size(name)) {
// No entity can be identified by NULL or empty string.
return NULL;
}
else if (!S_is_local_entry(name)) {
return NULL;
}
else if (Str_Starts_With_Utf8(name, ".", 1)) {
// Don't allow access outside of the main dir.
return NULL;
}
else if (NULL != (subfolder = (Folder*)Hash_Fetch(ivars->entries, name))) {
if (Folder_is_a(subfolder, FOLDER)) {
return subfolder;
}
else {
return NULL;
}
}
String *fullpath = S_fullpath(self, name);
if (S_dir_ok(fullpath)) {
subfolder = (Folder*)FSFolder_new(fullpath);
if (!subfolder) {
DECREF(fullpath);
THROW(ERR, "Failed to open FSFolder at '%o'", fullpath);
}
// Try to open a CompoundFileReader. On failure, just use the
// existing folder.
String *cfmeta_file = SSTR_WRAP_C("cfmeta.json");
if (Folder_Local_Exists(subfolder, cfmeta_file)) {
CompoundFileReader *cf_reader = CFReader_open(subfolder);
if (cf_reader) {
DECREF(subfolder);
subfolder = (Folder*)cf_reader;
}
}
Hash_Store(ivars->entries, name, (Obj*)subfolder);
}
DECREF(fullpath);
return subfolder;
}
static String*
S_fullpath(FSFolder *self, String *path) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
String *fullpath = Str_newf("%o%s%o", ivars->path, CHY_DIR_SEP, path);
String *retval;
if (CHY_DIR_SEP[0] != '/') {
// Replace '/' with CHY_DIR_SEP.
StringIterator *iter = Str_Top(fullpath);
CharBuf *buf = CB_new(Str_Get_Size(fullpath));
int32_t cp;
while (STR_OOB != (cp = StrIter_Next(iter))) {
if (cp == '/') { cp = CHY_DIR_SEP[0]; }
CB_Cat_Char(buf, cp);
}
retval = CB_Yield_String(buf);
DECREF(buf);
DECREF(iter);
DECREF(fullpath);
}
else {
retval = fullpath;
}
return retval;
}
static char*
S_fullpath_ptr(FSFolder *self, String *path) {
FSFolderIVARS *const ivars = FSFolder_IVARS(self);
size_t folder_size = Str_Get_Size(ivars->path);
size_t path_size = Str_Get_Size(path);
size_t full_size = folder_size + 1 + path_size;
const char *folder_ptr = Str_Get_Ptr8(ivars->path);
const char *path_ptr = Str_Get_Ptr8(path);
char *buf = (char*)MALLOCATE(full_size + 1);
memcpy(buf, folder_ptr, folder_size);
buf[folder_size] = CHY_DIR_SEP[0];
memcpy(buf + folder_size + 1, path_ptr, path_size);
buf[full_size] = '\0';
if (CHY_DIR_SEP[0] != '/') {
for (size_t i = 0; i < full_size; ++i) {
if (buf[i] == '/') { buf[i] = CHY_DIR_SEP[0]; }
}
}
return buf;
}
static bool
S_dir_ok(String *path) {
bool retval = false;
char *path_ptr = Str_To_Utf8(path);
struct stat stat_buf;
if (stat(path_ptr, &stat_buf) != -1) {
if (stat_buf.st_mode & S_IFDIR) { retval = true; }
}
FREEMEM(path_ptr);
return retval;
}
static bool
S_create_dir(String *path) {
bool retval = true;
char *path_ptr = Str_To_Utf8(path);
if (-1 == chy_makedir(path_ptr, 0777)) {
ErrMsg_set_with_errno("Couldn't create directory '%o'", path);
retval = false;
}
FREEMEM(path_ptr);
return retval;
}
static bool
S_is_local_entry(String *path) {
return !Str_Contains_Utf8(path, "/", 1);
}
/***************************************************************************/
#if (defined(CHY_HAS_WINDOWS_H) && !defined(__CYGWIN__))
// Windows.h defines INCREF and DECREF, so we include it only at the end of
// this file and undef those symbols.
#undef INCREF
#undef DECREF
#include <windows.h>
static bool
S_is_absolute(String *path) {
int32_t code_point = Str_Code_Point_At(path, 0);
if (isalpha(code_point)) {
code_point = Str_Code_Point_At(path, 1);
if (code_point != ':') { return false; }
code_point = Str_Code_Point_At(path, 2);
}
return code_point == '\\' || code_point == '/';
}
static String*
S_absolutify(String *path) {
if (S_is_absolute(path)) { return Str_Clone(path); }
DWORD cwd_len = GetCurrentDirectory(0, NULL);
char *cwd = (char*)MALLOCATE(cwd_len);
DWORD res = GetCurrentDirectory(cwd_len, cwd);
if (res == 0 || res > cwd_len) {
THROW(ERR, "GetCurrentDirectory failed");
}
String *abs_path = Str_newf("%s\\%o", cwd, path);
FREEMEM(cwd);
return abs_path;
}
static bool
S_rename(const char *from_path, const char *to_path) {
// TODO: We should consider using MoveFileEx with
// MOVEFILE_REPLACE_EXISTING to better match POSIX semantics. But unlike
// POSIX, this allows to replace a file with a directory, breaking the
// tests.
if (MoveFileA(from_path, to_path) != 0) {
return true;
}
else {
ErrMsg_set_with_win_error("rename from '%s' to '%s' failed",
from_path, to_path);
return false;
}
}
static bool
S_hard_link(const char *from_path, const char *to_path) {
if (CreateHardLinkA(to_path, from_path, NULL) != 0) {
return true;
}
else {
ErrMsg_set_with_win_error("CreateHardLink for new file '%s' "
"from '%s' failed",
to_path, from_path);
return false;
}
}
static bool
S_delete(const char *path) {
if (RemoveDirectoryA(path) != 0) {
return true;
}
if (GetLastError() != ERROR_DIRECTORY) {
ErrMsg_set_with_win_error("removing '%s' failed", path);
return false;
}
// Open file with FILE_FLAG_DELETE_ON_CLOSE and close it immediately.
// In contrast to DeleteFile, this allows files opened with
// FILE_SHARE_DELETE to be eventually deleted.
DWORD share_mode = FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE;
DWORD flags = FILE_FLAG_DELETE_ON_CLOSE;
DWORD attrs = FILE_ATTRIBUTE_NORMAL;
HANDLE handle = CreateFileA(path, DELETE, share_mode, NULL, OPEN_EXISTING,
flags | attrs, NULL);
if (handle == INVALID_HANDLE_VALUE) {
ErrMsg_set_with_win_error("removing '%s' failed", path);
return false;
}
CloseHandle(handle);
return true;
}
#elif (defined(CHY_HAS_UNISTD_H))
static bool
S_is_absolute(String *path) {
return Str_Starts_With_Utf8(path, CHY_DIR_SEP, 1);
}
static String*
S_absolutify(String *path) {
if (S_is_absolute(path)) { return Str_Clone(path); }
char *cwd = NULL;
for (size_t buf_size = 256; buf_size <= 65536; buf_size *= 2) {
cwd = (char*)MALLOCATE(buf_size);
if (getcwd(cwd, buf_size)) { break; }
FREEMEM(cwd);
cwd = NULL;
if (errno != ERANGE) { THROW(ERR, "getcwd failed"); }
}
if (!cwd) { THROW(ERR, "getcwd failed"); }
String *abs_path = Str_newf("%s" CHY_DIR_SEP "%o", cwd, path);
FREEMEM(cwd);
return abs_path;
}
static bool
S_rename(const char *from_path, const char *to_path) {
if (rename(from_path, to_path) != 0) {
ErrMsg_set_with_errno("rename from '%s' to '%s' failed",
from_path, to_path);
return false;
}
else {
return true;
}
}
static bool
S_hard_link(const char *from_path, const char *to_path) {
if (link(from_path, to_path) != 0) {
ErrMsg_set_with_errno("hard link for new file '%s' from '%s' failed",
to_path, from_path);
return false;
}
else {
return true;
}
}
static bool
S_delete(const char *path) {
#ifdef CHY_REMOVE_ZAPS_DIRS
bool result = !remove(path);
#else
bool result = !rmdir(path) || !remove(path);
#endif
if (!result) {
ErrMsg_set_with_errno("removing '%s' failed", path);
}
return result;
}
#else
#error "Need either windows.h or unistd.h"
#endif /* CHY_HAS_UNISTD_H vs. CHY_HAS_WINDOWS_H */