diff --git a/.github/workflows/RaspberryPiOS_armhf.yml b/.github/workflows/RaspberryPiOS_armhf.yml
index 11b3289..04600d3 100644
--- a/.github/workflows/RaspberryPiOS_armhf.yml
+++ b/.github/workflows/RaspberryPiOS_armhf.yml
@@ -39,6 +39,7 @@ jobs:
deb [arch=armhf] http://ports.ubuntu.com/ ${FLAVOR}-updates universe
deb [arch=armhf] http://ports.ubuntu.com/ ${FLAVOR} multiverse
deb [arch=armhf] http://ports.ubuntu.com/ ${FLAVOR}-updates multiverse
+ deb [arch=armhf] http://ports.ubuntu.com/ ${FLAVOR}-security main restricted universe multiverse
deb [arch=armhf] http://ports.ubuntu.com/ ${FLAVOR}-backports main restricted universe multiverse
LIST
sudo sed -E -i 's/deb (http|file|mirror)/deb [arch=amd64,i386] \1/' /etc/apt/sources.list
diff --git a/Builder/Windows/XM8.vcxproj b/Builder/Windows/XM8.vcxproj
index c557ddc..b0de7be 100644
--- a/Builder/Windows/XM8.vcxproj
+++ b/Builder/Windows/XM8.vcxproj
@@ -388,8 +388,9 @@
-
-
+
+
+
@@ -441,7 +442,8 @@
-
+
+
diff --git a/Builder/Windows/XM8.vcxproj.filters b/Builder/Windows/XM8.vcxproj.filters
index e5b95af..18ed7fb 100644
--- a/Builder/Windows/XM8.vcxproj.filters
+++ b/Builder/Windows/XM8.vcxproj.filters
@@ -156,9 +156,12 @@
ソース ファイル\UI
-
- ソース ファイル\UI
-
+
+ ソース ファイル\UI
+
+
+ ソース ファイル\UI
+
ソース ファイル\UI
@@ -284,9 +287,12 @@
ヘッダー ファイル\UI
-
- ヘッダー ファイル\UI
-
+
+ ヘッダー ファイル\UI
+
+
+ ヘッダー ファイル\UI
+
ヘッダー ファイル\UI
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65bcc01..5db5e31 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,6 +37,10 @@ set(PROJECT_VERSION 1.7.8)
# Project Name
project(XM8 VERSION ${PROJECT_VERSION})
+if(APPLE)
+ enable_language(OBJC OBJCXX)
+endif()
+
include(CTest)
# Packaging options
@@ -116,6 +120,7 @@ set(SRCS
Source/UI/menu.cpp
Source/UI/menuitem.cpp
Source/UI/menulist.cpp
+ Source/UI/pathresolver.cpp
Source/UI/platform.cpp
Source/UI/setting.cpp
Source/UI/softkey.cpp
@@ -143,6 +148,10 @@ set(SRCS
Source/ePC-8801MA/vm/pc8801/pc8801.cpp
)
+if(APPLE)
+ list(APPEND SRCS Source/UI/pathresolver_mac.mm)
+endif()
+
set(BIN_TARGET "xm8")
add_executable(${BIN_TARGET} MACOSX_BUNDLE ${SRCS} Builder/macOS/AppIcon.icns)
target_compile_definitions(${BIN_TARGET} PRIVATE SDL _PC8801MA)
@@ -186,6 +195,9 @@ option(ENABLE_PACKAGING "Enable CPack packaging" ON)
# Link SDL2 and xBRZ to xm8 target using interface target from Dependencies.cmake
target_link_libraries(${BIN_TARGET} PRIVATE XM8::SDL xbrz)
+if(APPLE)
+ target_link_libraries(${BIN_TARGET} PRIVATE "-framework Foundation")
+endif()
if(BUILD_TESTING)
add_executable(clidisk_test
@@ -207,6 +219,28 @@ if(BUILD_TESTING)
target_compile_definitions(d88probe_test PRIVATE SDL _PC8801MA)
target_link_libraries(d88probe_test PRIVATE XM8::SDL)
add_test(NAME d88probe_test COMMAND d88probe_test)
+
+ add_executable(pathresolver_test
+ Tests/pathresolver_test.cpp
+ Source/UI/pathresolver.cpp
+ )
+ if(APPLE)
+ target_sources(pathresolver_test PRIVATE Source/UI/pathresolver_mac.mm)
+ target_link_libraries(pathresolver_test PRIVATE "-framework Foundation")
+ endif()
+ target_include_directories(pathresolver_test PRIVATE Source/UI)
+ add_test(NAME pathresolver_test COMMAND pathresolver_test)
+
+ if(APPLE)
+ add_executable(pathresolver_mac_test
+ Tests/pathresolver_mac_test.mm
+ Source/UI/pathresolver.cpp
+ Source/UI/pathresolver_mac.mm
+ )
+ target_include_directories(pathresolver_mac_test PRIVATE Source/UI)
+ target_link_libraries(pathresolver_mac_test PRIVATE "-framework Foundation")
+ add_test(NAME pathresolver_mac_test COMMAND pathresolver_mac_test)
+ endif()
endif()
# Packaging
diff --git a/Source/UI/diskmgr.cpp b/Source/UI/diskmgr.cpp
index 015dc7c..a7f3dcf 100644
--- a/Source/UI/diskmgr.cpp
+++ b/Source/UI/diskmgr.cpp
@@ -17,6 +17,7 @@
#include "upd765a.h"
#include "vm.h"
#include "d88probe.h"
+#include "pathresolver.h"
#include "diskmgr.h"
//
@@ -42,7 +43,8 @@ DiskManager::DiskManager()
name_list = NULL;
wp_list = NULL;
signal = 0;
- path[0] = '\0';
+ path[0] = '\0';
+ resolved_path[0] = '\0';
dir[0] = '\0';
state_path[0] = '\0';
next_bank = 0;
@@ -105,23 +107,28 @@ void DiskManager::SetVM(VM *v)
//
bool DiskManager::Probe(const char *filename, int *banks)
{
- return ProbeD88Image(filename, banks);
+ char resolved[_MAX_PATH * 3];
+ return ResolvePathForIO(filename, resolved, sizeof(resolved)) &&
+ ProbeD88Image(resolved, banks);
}
//
// Open()
// open disk
//
-bool DiskManager::Open(const char *filename, int bank)
-{
- char *ptr;
- char *last;
-
- // save path
- if (strlen(filename) >= sizeof(path)) {
- return false;
- }
- strcpy(path, filename);
+bool DiskManager::Open(const char *filename, int bank)
+{
+ char *ptr;
+ char *last;
+ char candidate[_MAX_PATH * 3];
+
+ // save path
+ if (filename == NULL || strlen(filename) >= sizeof(path) ||
+ ResolvePathForIO(filename, candidate, sizeof(candidate)) == false) {
+ return false;
+ }
+ strcpy(path, filename);
+ strcpy(resolved_path, candidate);
// save directory
strcpy(dir, path);
@@ -147,7 +154,11 @@ bool DiskManager::Open(const char *filename, int bank)
// reopen disk
//
bool DiskManager::Open(int bank)
-{
+{
+ if (ResolvePath() == false) {
+ return false;
+ }
+
// close
Close();
@@ -164,7 +175,7 @@ bool DiskManager::Open(int bank)
current_bank = bank;
// open
- vm->open_disk(drive, (_TCHAR*)path, current_bank);
+ vm->open_disk(drive, (_TCHAR*)resolved_path, current_bank);
// ready
ready = true;
@@ -379,12 +390,15 @@ int DiskManager::GetBanks()
// SetBank
// change disk bank
//
-bool DiskManager::SetBank(int bank)
-{
- if ((bank >= 0) && (bank < num_of_banks)) {
- // open (dummy to access file)
- vm->close_disk(drive);
- vm->open_disk(drive, (_TCHAR*)path, bank);
+bool DiskManager::SetBank(int bank)
+{
+ if ((bank >= 0) && (bank < num_of_banks)) {
+ if (ResolvePath() == false) {
+ return false;
+ }
+ // open (dummy to access file)
+ vm->close_disk(drive);
+ vm->open_disk(drive, (_TCHAR*)resolved_path, bank);
// close
vm->close_disk(drive);
@@ -406,12 +420,17 @@ void DiskManager::ProcessMgr()
{
if (next_timer > 0) {
next_timer--;
- if (next_timer == 0) {
- current_bank = next_bank;
- vm->open_disk(drive, (_TCHAR*)path, current_bank);
- }
- }
-}
+ if (next_timer == 0) {
+ current_bank = next_bank;
+ if (ResolvePath() == true) {
+ vm->open_disk(drive, (_TCHAR*)resolved_path, current_bank);
+ }
+ else {
+ Close();
+ }
+ }
+ }
+}
//
// Load()
@@ -449,15 +468,24 @@ void DiskManager::Load(FILEIO *fio)
//
void DiskManager::Save(FILEIO *fio)
{
- fio->Fwrite(path, 1, sizeof(path));
+ fio->Fwrite(path, 1, sizeof(path));
fio->FputBool(ready);
fio->FputInt32(current_bank);
fio->FputInt32(next_bank);
fio->FputInt32(next_timer);
}
-//
-// Analyze()
+//
+// ResolvePath()
+// resolve logical disk path for I/O
+//
+bool DiskManager::ResolvePath()
+{
+ return ResolvePathForIO(path, resolved_path, sizeof(resolved_path));
+}
+
+//
+// Analyze()
// analyze d88 header
//
bool DiskManager::Analyze()
@@ -470,14 +498,14 @@ bool DiskManager::Analyze()
int bank;
char *ptr;
- if (ProbeD88Image(path, &num_of_banks, &len) == false) {
+ if (ProbeD88Image(resolved_path, &num_of_banks, &len) == false) {
return false;
}
- if (fio.Fopen(path, FILEIO_READ_BINARY) == false) {
+ if (fio.Fopen(resolved_path, FILEIO_READ_BINARY) == false) {
return false;
}
- readonly = fio.IsProtected(path);
+ readonly = fio.IsProtected(resolved_path);
// malloc
name_list = (char*)SDL_malloc(len);
diff --git a/Source/UI/diskmgr.h b/Source/UI/diskmgr.h
index b3181da..ffdffae 100644
--- a/Source/UI/diskmgr.h
+++ b/Source/UI/diskmgr.h
@@ -81,6 +81,8 @@ class DiskManager
// save state
private:
+ bool ResolvePath();
+ // resolve path used for I/O
bool Analyze();
// analyze d88 header
int drive;
@@ -89,8 +91,10 @@ class DiskManager
// virtual machine
UPD765A *upd765a;
// fdc
- char path[_MAX_PATH * 3];
- // disk path
+ char path[_MAX_PATH * 3];
+ // disk path
+ char resolved_path[_MAX_PATH * 3];
+ // disk path used for I/O
char dir[_MAX_PATH * 3];
// disk dir
char state_path[_MAX_PATH * 3];
diff --git a/Source/UI/menu.cpp b/Source/UI/menu.cpp
index 4ba7efe..51e7e4b 100644
--- a/Source/UI/menu.cpp
+++ b/Source/UI/menu.cpp
@@ -3083,7 +3083,7 @@ void Menu::CmdFile(int id)
}
#endif // __ANDROID__
- if (platform->MakePath(file_target, name) == true) {
+ if (platform->MakePath(file_target, name, true) == true) {
MakeExpect(name);
strcpy(file_dir, file_target);
EnterFile();
@@ -3092,7 +3092,9 @@ void Menu::CmdFile(int id)
}
// normal file
- platform->MakePath(file_target, name);
+ if (platform->MakePath(file_target, name, false) == false) {
+ return;
+ }
// tape ?
if ((file_id == MENU_CMT_PLAY) || (file_id == MENU_CMT_REC)) {
diff --git a/Source/UI/pathresolver.cpp b/Source/UI/pathresolver.cpp
new file mode 100644
index 0000000..85f75ba
--- /dev/null
+++ b/Source/UI/pathresolver.cpp
@@ -0,0 +1,89 @@
+#include
+#include
+
+#if defined(__linux__) || defined(__APPLE__) || defined(__ANDROID__)
+#include
+#include
+#endif
+
+#include "pathresolver.h"
+
+#ifdef __APPLE__
+bool ResolveMacAlias(const char *path, char *resolved, size_t capacity);
+#endif
+
+bool ResolvePathForIO(const char *path, char *resolved, size_t capacity)
+{
+ char candidate[4096];
+
+ if (path == nullptr || resolved == nullptr || capacity == 0) {
+ return false;
+ }
+
+#ifdef __APPLE__
+ if (!ResolveMacAlias(path, candidate, sizeof(candidate))) {
+ return false;
+ }
+#else
+ const size_t input_length = std::strlen(path);
+ if (input_length >= sizeof(candidate)) {
+ return false;
+ }
+ std::memcpy(candidate, path, input_length + 1);
+#endif
+
+#if defined(__linux__) || defined(__APPLE__) || defined(__ANDROID__)
+ char canonical[4096];
+ if (realpath(candidate, canonical) != nullptr) {
+ const size_t length = std::strlen(canonical);
+ if (length >= capacity) {
+ return false;
+ }
+ std::memcpy(resolved, canonical, length + 1);
+ return true;
+ }
+
+ struct stat file_stat;
+ if (lstat(candidate, &file_stat) == 0 || errno != ENOENT) {
+ return false;
+ }
+#endif
+
+ const size_t output_length = std::strlen(candidate);
+ if (output_length >= capacity) {
+ return false;
+ }
+ std::memcpy(resolved, candidate, output_length + 1);
+ return true;
+}
+
+PathKind InspectPath(const char *path, char *resolved, size_t capacity)
+{
+ char local_path[4096];
+ char *target = resolved;
+ size_t target_capacity = capacity;
+
+ if (target == nullptr) {
+ target = local_path;
+ target_capacity = sizeof(local_path);
+ }
+ if (!ResolvePathForIO(path, target, target_capacity)) {
+ return PATH_KIND_UNAVAILABLE;
+ }
+
+#if defined(__linux__) || defined(__APPLE__) || defined(__ANDROID__)
+ struct stat file_stat;
+ if (stat(target, &file_stat) != 0) {
+ return PATH_KIND_UNAVAILABLE;
+ }
+ if (S_ISREG(file_stat.st_mode)) {
+ return PATH_KIND_FILE;
+ }
+ if (S_ISDIR(file_stat.st_mode)) {
+ return PATH_KIND_DIRECTORY;
+ }
+ return PATH_KIND_OTHER;
+#else
+ return PATH_KIND_OTHER;
+#endif
+}
diff --git a/Source/UI/pathresolver.h b/Source/UI/pathresolver.h
new file mode 100644
index 0000000..c5c3029
--- /dev/null
+++ b/Source/UI/pathresolver.h
@@ -0,0 +1,17 @@
+#ifndef PATHRESOLVER_H
+#define PATHRESOLVER_H
+
+#include
+
+enum PathKind {
+ PATH_KIND_UNAVAILABLE = 0,
+ PATH_KIND_FILE,
+ PATH_KIND_DIRECTORY,
+ PATH_KIND_OTHER
+};
+
+bool ResolvePathForIO(const char *path, char *resolved, size_t capacity);
+PathKind InspectPath(const char *path, char *resolved = nullptr,
+ size_t capacity = 0);
+
+#endif
diff --git a/Source/UI/pathresolver_mac.mm b/Source/UI/pathresolver_mac.mm
new file mode 100644
index 0000000..3b30f73
--- /dev/null
+++ b/Source/UI/pathresolver_mac.mm
@@ -0,0 +1,84 @@
+#import
+
+#include
+#include
+#include
+
+#include "pathresolver.h"
+
+namespace {
+
+bool CopyPath(const char *source, char *destination, size_t capacity)
+{
+ const size_t length = std::strlen(source);
+ if (length >= capacity) {
+ return false;
+ }
+ std::memcpy(destination, source, length + 1);
+ return true;
+}
+
+}
+
+bool ResolveMacAlias(const char *path, char *resolved, size_t capacity)
+{
+ if (path == nullptr || resolved == nullptr || capacity == 0) {
+ return false;
+ }
+
+ @autoreleasepool {
+ NSString *string = [NSString stringWithUTF8String:path];
+ if (string == nil) {
+ return false;
+ }
+
+ NSURL *url = [NSURL fileURLWithPath:string];
+ NSNumber *is_alias = nil;
+ NSError *error = nil;
+ if (![url getResourceValue:&is_alias
+ forKey:NSURLIsAliasFileKey
+ error:&error]) {
+ struct stat file_stat;
+ if (lstat(path, &file_stat) != 0 && errno == ENOENT) {
+ // Nonexistent paths must remain usable for file creation.
+ return CopyPath(path, resolved, capacity);
+ }
+ return false;
+ }
+ if (![is_alias boolValue]) {
+ return CopyPath(path, resolved, capacity);
+ }
+
+ NSMutableSet *visited = [NSMutableSet set];
+ for (int depth = 0; depth < 16; depth++) {
+ NSString *current_path = [url path];
+ if (current_path == nil || [visited containsObject:current_path]) {
+ return false;
+ }
+ [visited addObject:current_path];
+
+ url = [NSURL URLByResolvingAliasFileAtURL:url
+ options:(NSURLBookmarkResolutionWithoutUI |
+ NSURLBookmarkResolutionWithoutMounting)
+ error:&error];
+ if (url == nil || ![url isFileURL]) {
+ return false;
+ }
+
+ is_alias = nil;
+ error = nil;
+ if (![url getResourceValue:&is_alias
+ forKey:NSURLIsAliasFileKey
+ error:&error]) {
+ return false;
+ }
+ if (![is_alias boolValue]) {
+ const char *file_path = [url fileSystemRepresentation];
+ return file_path != nullptr &&
+ CopyPath(file_path, resolved, capacity);
+ }
+ }
+ }
+
+ return false;
+}
diff --git a/Source/UI/platform.cpp b/Source/UI/platform.cpp
index 804db15..6564ac6 100644
--- a/Source/UI/platform.cpp
+++ b/Source/UI/platform.cpp
@@ -20,6 +20,7 @@
#include "classes.h"
#include "app.h"
#include "converter.h"
+#include "pathresolver.h"
#include "platform.h"
#ifdef __ANDROID__
@@ -54,6 +55,19 @@
#define LOCALE_UTF8 "UTF-8"
// UTF-8
+static bool IsSupportedFile(char *path)
+{
+ char d88[] = ".d88";
+ char cmt[] = ".cmt";
+ char t88[] = ".t88";
+ char n80[] = ".n80";
+
+ return check_file_extension(path, d88) ||
+ check_file_extension(path, cmt) ||
+ check_file_extension(path, t88) ||
+ check_file_extension(path, n80);
+}
+
//
// Platform()
// constructor
@@ -79,6 +93,7 @@ Platform::Platform(App *a)
dir_handle = NULL;
dir_name[0] = '\0';
dir_name_utf8[0] = '\0';
+ find_dir[0] = '\0';
dir_up = false;
#endif // __linux__ || __APPLE__
}
@@ -298,6 +313,11 @@ const char* Platform::FindFirst(const char *dir, Uint32 *info)
#if defined(__linux__) || defined(__APPLE__)
DIR *dir_ret;
+ if (strlen(dir) >= sizeof(find_dir)) {
+ return NULL;
+ }
+ strcpy(find_dir, dir);
+
// Find ..
dir_up = FindUp(dir);
@@ -418,10 +438,9 @@ const char* Platform::FindNext(Uint32 *info)
#if defined(__linux__) || defined(__APPLE__)
struct dirent *entry;
- Converter *converter;
-
- // get converter
- converter = app->GetConverter();
+ char entry_path[_MAX_PATH * 3];
+ char resolved_path[_MAX_PATH * 3];
+ PathKind kind;
// find
for (;;) {
@@ -432,36 +451,41 @@ const char* Platform::FindNext(Uint32 *info)
return NULL;
}
- // check file extension
- if (entry->d_type == DT_REG) {
- // normal file
- if (check_file_extension(entry->d_name, _T(".d88")) == false &&
- check_file_extension(entry->d_name, _T(".cmt")) == false &&
- check_file_extension(entry->d_name, _T(".t88")) == false &&
- check_file_extension(entry->d_name, _T(".n80")) == false) {
- // unsupported file
- continue;
- }
- }
-
// check .
- if (entry->d_name[0] != '.') {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ if (strlen(find_dir) + strlen(entry->d_name) >= sizeof(entry_path)) {
+ continue;
+ }
+ strcpy(entry_path, find_dir);
+ strcat(entry_path, entry->d_name);
+ kind = InspectPath(entry_path, resolved_path, sizeof(resolved_path));
+ if (kind == PATH_KIND_DIRECTORY) {
break;
}
+ if (kind != PATH_KIND_FILE) {
+ continue;
+ }
+ if (IsSupportedFile(resolved_path) == false) {
+ continue;
+ }
+ break;
}
// name
strcpy(dir_name, entry->d_name);
// directory ?
- if (entry->d_type == DT_DIR) {
+ if (kind == PATH_KIND_DIRECTORY) {
if (dir_name[strlen(dir_name) - 1] != '/') {
strcat(dir_name, "/");
}
}
// type
- *info = (Uint32)entry->d_type;
+ *info = kind == PATH_KIND_DIRECTORY ? DT_DIR : DT_REG;
return dir_name;
#endif // __liunx__ || __APPLE__
@@ -474,39 +498,20 @@ const char* Platform::FindNext(Uint32 *info)
//
bool Platform::FindUp(const char *dir)
{
- struct dirent *entry;
- DIR *dir_ret;
+ char parent[_MAX_PATH * 3];
+ struct stat file_stat;
// root ?
if (strcmp(dir, "/") == 0) {
return false;
}
- // open directory
- dir_ret = opendir(dir);
- if (dir_ret == NULL) {
+ if (strlen(dir) + 2 >= sizeof(parent)) {
return false;
}
-
- // find loop
- for (;;) {
- entry = readdir(dir_ret);
- if (entry == NULL) {
- break;
- }
-
- // check '..'
- if (strcmp(entry->d_name, "..") == 0) {
- if (entry->d_type == DT_DIR) {
- closedir(dir_ret);
- return true;
- }
- }
- }
-
- // close and false
- closedir(dir_ret);
- return false;
+ strcpy(parent, dir);
+ strcat(parent, "..");
+ return stat(parent, &file_stat) == 0 && S_ISDIR(file_stat.st_mode);
}
#endif // __liunx__ || __APPLE__
@@ -545,7 +550,7 @@ bool Platform::IsDir(Uint32 info)
// MakePath()
// make path name from dir(UTF-8) and name(SHIFT-JIS)
//
-bool Platform::MakePath(char *dir, const char *name)
+bool Platform::MakePath(char *dir, const char *name, bool directory)
{
#if defined(_WIN32) && defined(UNICODE)
wchar_t wide_dir[MAX_PATH];
@@ -585,6 +590,8 @@ bool Platform::MakePath(char *dir, const char *name)
wcscat_s(wide_dir, SDL_arraysize(wide_dir), wide_name);
}
+ (void)directory;
+
// GetFullPathName
GetFullPathName(wide_dir, SDL_arraysize(wide_name), wide_name, &part);
@@ -602,34 +609,40 @@ bool Platform::MakePath(char *dir, const char *name)
#endif // _WIN32 && UNICODE
#if defined(__linux__) || defined(__APPLE__)
- Converter *converter;
struct stat filestat;
+ char joined[_MAX_PATH * 3];
+ char resolved[_MAX_PATH * 3];
+ size_t dir_length;
+ size_t name_length;
+
+ dir_length = strlen(dir);
+ name_length = strlen(name);
+ while (name_length > 0 &&
+ (name[name_length - 1] == '/' || name[name_length - 1] == '\\')) {
+ name_length--;
+ }
+ if (dir_length + name_length >= sizeof(joined)) {
+ return false;
+ }
+ memcpy(joined, dir, dir_length);
+ memcpy(joined + dir_length, name, name_length);
+ joined[dir_length + name_length] = '\0';
- // get converter
- converter = app->GetConverter();
-
- // SHIFT-JIS to UTF-8
- // converter->SjisToUtf(name, dir_name);
- strcpy(dir_name, name);
-
- // cat
- strcat(dir, dir_name);
+ if (directory == false) {
+ strcpy(dir, joined);
+ return true;
+ }
- // realpath
- if (realpath(dir, dir_name) == NULL) {
+ if (!ResolvePathForIO(joined, resolved, sizeof(resolved)) ||
+ realpath(resolved, dir_name) == NULL ||
+ stat(dir_name, &filestat) != 0 ||
+ !S_ISDIR(filestat.st_mode)) {
return false;
}
-
- // directory ?
- if (stat(dir_name, &filestat) == 0) {
- if (S_ISDIR(filestat.st_mode)) {
- if (dir_name[strlen(dir_name) - 1] != '/') {
- strcat(dir_name, "/");
- }
- }
+ if (dir_name[strlen(dir_name) - 1] != '/') {
+ strcat(dir_name, "/");
}
- // realpath to dir
strcpy(dir, dir_name);
return true;
diff --git a/Source/UI/platform.h b/Source/UI/platform.h
index 12e416c..419db95 100644
--- a/Source/UI/platform.h
+++ b/Source/UI/platform.h
@@ -35,7 +35,7 @@ class Platform
// find next file
bool IsDir(Uint32 info);
// check directory
- bool MakePath(char *dir, const char *name);
+ bool MakePath(char *dir, const char *name, bool directory);
// make path from dir(UTF-8) and name(SHIFT-JIS)
// file date and time
@@ -75,6 +75,8 @@ class Platform
// file name (shift-jis)
char dir_name_utf8[_MAX_PATH * 3];
// file name (utf8-mac)
+ char find_dir[_MAX_PATH * 3];
+ // directory currently being enumerated
bool dir_up;
// FindUp() result
#endif // __linux__ || __APPLE__
diff --git a/Source/UI/tapemgr.cpp b/Source/UI/tapemgr.cpp
index c62802e..1aa4036 100644
--- a/Source/UI/tapemgr.cpp
+++ b/Source/UI/tapemgr.cpp
@@ -14,6 +14,7 @@
#include "common.h"
#include "vm.h"
#include "app.h"
+#include "pathresolver.h"
#include "tapemgr.h"
//
@@ -31,6 +32,7 @@ TapeManager::TapeManager()
// path and dir
path[0] = '\0';
+ resolved_path[0] = '\0';
dir[0] = '\0';
state_path[0] = '\0';
nullstr[0] = '\0';
@@ -85,16 +87,16 @@ void TapeManager::SetVM(VM *v)
//
bool TapeManager::Play(const char *filename)
{
- // Eject
- Eject();
-
// open
if (Open(filename, false) == false) {
return false;
}
+ // Eject
+ Eject();
+
// virtual machine
- vm->play_tape((_TCHAR*)path);
+ vm->play_tape((_TCHAR*)resolved_path);
// mount ok
mount_play = true;
@@ -108,16 +110,16 @@ bool TapeManager::Play(const char *filename)
//
bool TapeManager::Rec(const char *filename)
{
- // Eject
- Eject();
-
// open
if (Open(filename, true) == false) {
return false;
}
+ // Eject
+ Eject();
+
// virtual machine
- vm->rec_tape((_TCHAR*)path);
+ vm->rec_tape((_TCHAR*)resolved_path);
// mount ok
mount_rec = true;
@@ -133,44 +135,49 @@ bool TapeManager::Open(const char *filename, bool rec)
{
char *ptr;
char *last;
+ char candidate_path[_MAX_PATH * 3];
+ char candidate_dir[_MAX_PATH * 3];
+ char candidate_resolved[_MAX_PATH * 3];
FILEIO fileio;
bool ret;
- // specify filename ?
- if (filename != NULL) {
- // save path
- if (strlen(filename) >= sizeof(path)) {
- return false;
- }
- strcpy(path, filename);
-
- // save directory
- strcpy(dir, path);
- ptr = dir;
- last = dir;
-
- // search last '\\' or '/'
- while (*ptr != '\0') {
- if ((*ptr == '\\') || (*ptr == '/')) {
- last = ptr;
- }
- ptr++;
+ const char *source = filename != NULL ? filename : path;
+ if (source[0] == '\0' || strlen(source) >= sizeof(candidate_path)) {
+ return false;
+ }
+ strcpy(candidate_path, source);
+ strcpy(candidate_dir, candidate_path);
+ ptr = candidate_dir;
+ last = candidate_dir;
+
+ // search last '\\' or '/'
+ while (*ptr != '\0') {
+ if ((*ptr == '\\') || (*ptr == '/')) {
+ last = ptr;
}
+ ptr++;
+ }
+ last[1] = '\0';
- // end mark
- last[1] = '\0';
+ if (ResolvePathForIO(candidate_path, candidate_resolved,
+ sizeof(candidate_resolved)) == false) {
+ return false;
}
// open test
if (rec == true) {
- ret = fileio.Fopen(path, FILEIO_READ_WRITE_NEW_BINARY);
+ ret = fileio.Fopen(candidate_resolved,
+ FILEIO_READ_WRITE_NEW_BINARY);
}
else {
- ret = fileio.Fopen(path, FILEIO_READ_BINARY);
+ ret = fileio.Fopen(candidate_resolved, FILEIO_READ_BINARY);
}
if (ret == true) {
// close immediately
fileio.Fclose();
+ strcpy(path, candidate_path);
+ strcpy(dir, candidate_dir);
+ strcpy(resolved_path, candidate_resolved);
}
return ret;
@@ -270,7 +277,7 @@ void TapeManager::Load(FILEIO *fio)
//
void TapeManager::Save(FILEIO *fio)
{
- fio->Fwrite(state_path, 1, sizeof(state_path));
+ fio->Fwrite(path, 1, sizeof(path));
fio->FputBool(mount_play);
fio->FputBool(mount_rec);
}
diff --git a/Source/UI/tapemgr.h b/Source/UI/tapemgr.h
index 1930fd7..c6bdedf 100644
--- a/Source/UI/tapemgr.h
+++ b/Source/UI/tapemgr.h
@@ -61,6 +61,8 @@ class TapeManager
// mount flag (rec)
char path[_MAX_PATH * 3];
// tape path
+ char resolved_path[_MAX_PATH * 3];
+ // tape path used for I/O
char dir[_MAX_PATH * 3];
// tape dir
char state_path[_MAX_PATH * 3];
diff --git a/Tests/pathresolver_mac_test.mm b/Tests/pathresolver_mac_test.mm
new file mode 100644
index 0000000..1e7ddc1
--- /dev/null
+++ b/Tests/pathresolver_mac_test.mm
@@ -0,0 +1,93 @@
+#import
+
+#include
+#include
+#include
+
+#include "pathresolver.h"
+
+namespace {
+
+int failures = 0;
+
+void Check(bool condition, const char *message)
+{
+ if (!condition) {
+ std::cerr << "FAIL: " << message << '\n';
+ failures++;
+ }
+}
+
+bool CreateAlias(NSURL *target, NSURL *alias)
+{
+ NSError *error = nil;
+ NSData *bookmark = [target bookmarkDataWithOptions:
+ NSURLBookmarkCreationSuitableForBookmarkFile
+ includingResourceValuesForKeys:nil
+ relativeToURL:nil
+ error:&error];
+ return bookmark != nil &&
+ [NSURL writeBookmarkData:bookmark toURL:alias options:0 error:&error];
+}
+
+bool MatchesCanonicalPath(const char *actual, NSURL *expected)
+{
+ char canonical[1024];
+ return realpath([[expected path] fileSystemRepresentation], canonical) !=
+ nullptr && std::strcmp(actual, canonical) == 0;
+}
+
+}
+
+int main()
+{
+ @autoreleasepool {
+ NSFileManager *manager = [NSFileManager defaultManager];
+ NSURL *root = [NSURL fileURLWithPath:
+ @"/tmp/xm8-pathresolver-mac-test" isDirectory:YES];
+ [manager removeItemAtURL:root error:nil];
+ Check([manager createDirectoryAtURL:root
+ withIntermediateDirectories:YES attributes:nil error:nil],
+ "create temporary directory");
+
+ NSURL *target = [root URLByAppendingPathComponent:@"target.d88"];
+ NSURL *moved = [root URLByAppendingPathComponent:@"moved.d88"];
+ NSURL *alias = [root URLByAppendingPathComponent:@"disk alias"];
+ NSURL *directory = [root URLByAppendingPathComponent:@"directory"
+ isDirectory:YES];
+ NSURL *directory_alias = [root URLByAppendingPathComponent:
+ @"directory alias"];
+
+ Check([manager createFileAtPath:[target path] contents:[NSData data]
+ attributes:nil], "create alias target");
+ Check([manager createDirectoryAtURL:directory
+ withIntermediateDirectories:YES attributes:nil error:nil],
+ "create directory alias target");
+ Check(CreateAlias(target, alias), "create file alias");
+ Check(CreateAlias(directory, directory_alias), "create directory alias");
+
+ char resolved[1024];
+ Check(InspectPath([[alias path] fileSystemRepresentation], resolved,
+ sizeof(resolved)) == PATH_KIND_FILE, "classify file alias");
+ Check(MatchesCanonicalPath(resolved, target), "resolve file alias");
+ Check(InspectPath([[directory_alias path] fileSystemRepresentation],
+ resolved, sizeof(resolved)) == PATH_KIND_DIRECTORY,
+ "classify directory alias");
+
+ Check([manager moveItemAtURL:target toURL:moved error:nil],
+ "move alias target");
+ Check(ResolvePathForIO([[alias path] fileSystemRepresentation],
+ resolved, sizeof(resolved)), "resolve moved alias");
+ Check(MatchesCanonicalPath(resolved, moved),
+ "follow moved alias target");
+ Check([manager removeItemAtURL:moved error:nil],
+ "remove alias target");
+ Check(InspectPath([[alias path] fileSystemRepresentation], resolved,
+ sizeof(resolved)) == PATH_KIND_UNAVAILABLE,
+ "reject broken alias");
+
+ [manager removeItemAtURL:root error:nil];
+ }
+
+ return failures == 0 ? 0 : 1;
+}
diff --git a/Tests/pathresolver_test.cpp b/Tests/pathresolver_test.cpp
new file mode 100644
index 0000000..581abc7
--- /dev/null
+++ b/Tests/pathresolver_test.cpp
@@ -0,0 +1,84 @@
+#include
+#include
+#include
+#include
+
+#if defined(__linux__) || defined(__APPLE__)
+#include
+#include
+#endif
+
+#include "pathresolver.h"
+
+namespace {
+
+int failures = 0;
+
+void Check(bool condition, const char *message)
+{
+ if (!condition) {
+ std::cerr << "FAIL: " << message << '\n';
+ failures++;
+ }
+}
+
+}
+
+int main()
+{
+#if defined(__linux__) || defined(__APPLE__)
+ char temporary[] = "/tmp/xm8-pathresolver-XXXXXX";
+ char *directory = mkdtemp(temporary);
+ Check(directory != nullptr, "create temporary directory");
+ if (directory == nullptr) {
+ return 1;
+ }
+
+ char file_path[1024];
+ char file_link[1024];
+ char dir_link[1024];
+ char broken_link[1024];
+ std::snprintf(file_path, sizeof(file_path), "%s/disk.d88", directory);
+ std::snprintf(file_link, sizeof(file_link), "%s/file-link", directory);
+ std::snprintf(dir_link, sizeof(dir_link), "%s/dir-link", directory);
+ std::snprintf(broken_link, sizeof(broken_link), "%s/broken-link", directory);
+
+ FILE *file = std::fopen(file_path, "wb");
+ Check(file != nullptr, "create regular file");
+ if (file != nullptr) {
+ std::fclose(file);
+ }
+ Check(symlink(file_path, file_link) == 0, "create file symlink");
+ Check(symlink(directory, dir_link) == 0, "create directory symlink");
+ Check(symlink("/path/that/does/not/exist", broken_link) == 0,
+ "create broken symlink");
+
+ char resolved[1024];
+ Check(InspectPath(file_path, resolved, sizeof(resolved)) == PATH_KIND_FILE,
+ "classify regular file");
+ Check(InspectPath(file_link, resolved, sizeof(resolved)) == PATH_KIND_FILE,
+ "classify file symlink");
+ char canonical[1024];
+ Check(realpath(file_path, canonical) != nullptr &&
+ std::strcmp(resolved, canonical) == 0, "resolve file symlink");
+ Check(InspectPath(dir_link, resolved, sizeof(resolved)) ==
+ PATH_KIND_DIRECTORY, "classify directory symlink");
+ Check(realpath(directory, canonical) != nullptr &&
+ std::strcmp(resolved, canonical) == 0, "resolve directory symlink");
+ Check(InspectPath(broken_link, resolved, sizeof(resolved)) ==
+ PATH_KIND_UNAVAILABLE, "reject broken symlink");
+
+ unlink(broken_link);
+ unlink(dir_link);
+ unlink(file_link);
+ unlink(file_path);
+ rmdir(directory);
+#else
+ char resolved[32];
+ Check(ResolvePathForIO("disk.d88", resolved, sizeof(resolved)),
+ "copy normal path");
+ Check(std::strcmp(resolved, "disk.d88") == 0, "preserve normal path");
+#endif
+
+ return failures == 0 ? 0 : 1;
+}