Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix shared directory state between KILL, FILES, and _FILES$ #557

Merged
merged 1 commit into from
Oct 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 61 additions & 42 deletions internal/c/libqb/src/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

#include "libqb-common.h"

#include "error_handle.h"
#include "filepath.h"
#include "filesystem.h"

#include "../../libqb.h"

#include <algorithm>
#include <dirent.h>
#include <memory>
#include <sys/stat.h>
#include <unistd.h>
#ifdef QB64_WINDOWS
Expand Down Expand Up @@ -423,7 +425,7 @@ qbs *func__dir(qbs *qbsContext) {
/// @param path The directory path
/// @return True if the directory exists
int32_t func__direxists(qbs *path) {
if (new_error)
if (is_error_pending())
return QB_FALSE;

std::string pathName(reinterpret_cast<char *>(path->chr), path->len);
Expand All @@ -450,7 +452,7 @@ bool FS_FileExists(const char *path) {
/// @param path The file path to check for
/// @return True if the file exists
int32_t func__fileexists(qbs *path) {
if (new_error)
if (is_error_pending())
return QB_FALSE;

std::string pathName(reinterpret_cast<char *>(path->chr), path->len);
Expand All @@ -471,7 +473,7 @@ qbs *func__startdir() {
/// @brief Changes the current directory
/// @param str The directory path to change to
void sub_chdir(qbs *str) {
if (new_error)
if (is_error_pending())
return;

std::string pathName(reinterpret_cast<char *>(str->chr), str->len);
Expand Down Expand Up @@ -540,21 +542,34 @@ static inline bool FS_IsPatternMatching(const char *fileSpec, const char *fileNa
/// @return True if * or ? are found
static inline bool FS_HasPattern(const char *fileSpec) { return fileSpec != nullptr && (strchr(fileSpec, '*') || strchr(fileSpec, '?')); }

struct DirectoryContext {
DIR *directory;
char pattern[FS_PATHNAME_LENGTH_MAX];
char entry[FS_PATHNAME_LENGTH_MAX];

DirectoryContext() : directory(nullptr) {
pattern[0] = '\0';
entry[0] = '\0';
}

~DirectoryContext() {
if (directory)
closedir(directory);
}
};

/// @brief An MS BASIC PDS DIR$ style function
/// @param ctx The directory context
/// @param fileSpec This can be a path with wildcard for the final level (i.e. C:/Windows/*.* or /usr/lib/* etc.)
/// @return Returns a file or directory name matching fileSpec or an empty string when there is nothing left
static const char *FS_GetDirectoryEntryName(const char *fileSpec) {
static DIR *pDir = nullptr;
static char pattern[FS_PATHNAME_LENGTH_MAX];
static char entry[FS_PATHNAME_LENGTH_MAX];

entry[0] = '\0'; // set to an empty string
static const char *FS_GetDirectoryEntryName(DirectoryContext *ctx, const char *fileSpec) {
ctx->entry[0] = '\0'; // set to an empty string

if (!FS_IsStringEmpty(fileSpec)) {
// We got a filespec. Check if we have one already going and if so, close it
if (pDir) {
closedir(pDir);
pDir = nullptr;
if (ctx->directory) {
closedir(ctx->directory);
ctx->directory = nullptr;
}

char dirName[FS_PATHNAME_LENGTH_MAX]; // we only need this for opendir()
Expand All @@ -569,62 +584,63 @@ static const char *FS_GetDirectoryEntryName(const char *fileSpec) {

if (p) {
// Split the path and the filespec
strncpy(pattern, p + 1, FS_PATHNAME_LENGTH_MAX);
pattern[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
strncpy(ctx->pattern, p + 1, FS_PATHNAME_LENGTH_MAX);
ctx->pattern[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
auto len = std::min<size_t>((p - fileSpec) + 1, FS_PATHNAME_LENGTH_MAX - 1);
memcpy(dirName, fileSpec, len);
dirName[len] = '\0';
} else {
// No path. Use the current path
strncpy(pattern, fileSpec, FS_PATHNAME_LENGTH_MAX);
pattern[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
strncpy(ctx->pattern, fileSpec, FS_PATHNAME_LENGTH_MAX);
ctx->pattern[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
strcpy(dirName, "./");
}
} else {
// No pattern. Check if this is a file and simply return the name if it exists
if (FS_FileExists(fileSpec)) {
strncpy(entry, filepath_get_filename(fileSpec), FS_PATHNAME_LENGTH_MAX);
entry[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
strncpy(ctx->entry, filepath_get_filename(fileSpec), FS_PATHNAME_LENGTH_MAX);
ctx->entry[FS_PATHNAME_LENGTH_MAX - 1] = '\0';

return entry;
return ctx->entry;
}

// Else, We'll just assume it's a directory
strncpy(dirName, fileSpec, FS_PATHNAME_LENGTH_MAX);
dirName[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
strcpy(pattern, "*");
strcpy(ctx->pattern, "*");
}

pDir = opendir(dirName);
ctx->directory = opendir(dirName);
}

if (pDir) {
if (ctx->directory) {
for (;;) {
auto pDirent = readdir(pDir);
auto pDirent = readdir(ctx->directory);
if (!pDirent) {
closedir(pDir);
pDir = nullptr;
closedir(ctx->directory);
ctx->directory = nullptr;

break;
}

if (FS_IsPatternMatching(pattern, pDirent->d_name)) {
strncpy(entry, pDirent->d_name, FS_PATHNAME_LENGTH_MAX);
entry[FS_PATHNAME_LENGTH_MAX - 1] = '\0';
if (FS_IsPatternMatching(ctx->pattern, pDirent->d_name)) {
strncpy(ctx->entry, pDirent->d_name, FS_PATHNAME_LENGTH_MAX);
ctx->entry[FS_PATHNAME_LENGTH_MAX - 1] = '\0';

break;
}
}
}

return entry;
return ctx->entry;
}

/// @brief This mimics MS BASIC PDS 7.1 & VBDOS 1.0 DIR$() function
/// @param qbsFileSpec This can be a path with wildcard for the final level (i.e. C:/Windows/*.* or /usr/lib/* etc.)
/// @param passed Flags for optional parameters
/// @return Returns a qbs with the directory entry name or an empty string if there are no more entries
qbs *func__files(qbs *qbsFileSpec, int32_t passed) {
static DirectoryContext directoryContext;
static std::string directory;
std::string pathName;
const char *entry;
Expand All @@ -645,7 +661,7 @@ qbs *func__files(qbs *qbsFileSpec, int32_t passed) {
directory = "./";
}

entry = FS_GetDirectoryEntryName(fileSpec.c_str());
entry = FS_GetDirectoryEntryName(&directoryContext, fileSpec.c_str());
} else {
// Check if we've been called the first time without a filespec
if (directory.empty()) {
Expand All @@ -655,7 +671,7 @@ qbs *func__files(qbs *qbsFileSpec, int32_t passed) {
return qbsFinal;
}

entry = FS_GetDirectoryEntryName(nullptr); // get the next entry
entry = FS_GetDirectoryEntryName(&directoryContext, nullptr); // get the next entry
}

filepath_join(pathName, directory, entry);
Expand Down Expand Up @@ -781,7 +797,7 @@ static std::string FS_GetShortName(const char *path) {
void sub_files(qbs *str, int32_t passed) {
static qbs *strz = nullptr;

if (new_error)
if (is_error_pending())
return;

if (!strz)
Expand Down Expand Up @@ -819,7 +835,8 @@ void sub_files(qbs *str, int32_t passed) {
qbs_set(strz, qbs_new_txt_len(shortName.c_str(), shortName.size()));
qbs_print(strz, 1);

auto entry = FS_GetDirectoryEntryName(fileSpec.c_str()); // get the first entry
auto directoryContext = std::make_unique<DirectoryContext>();
auto entry = FS_GetDirectoryEntryName(directoryContext.get(), fileSpec.c_str()); // get the first entry
filepath_join(pathName, directory, entry);

if (FS_IsStringEmpty(entry)) {
Expand Down Expand Up @@ -854,7 +871,7 @@ void sub_files(qbs *str, int32_t passed) {
makefit(strz);
qbs_print(strz, 0);

entry = FS_GetDirectoryEntryName(nullptr); // get the next entry
entry = FS_GetDirectoryEntryName(directoryContext.get(), nullptr); // get the next entry
filepath_join(pathName, directory, entry);
} while (!FS_IsStringEmpty(entry));

Expand All @@ -872,13 +889,15 @@ void sub_files(qbs *str, int32_t passed) {
/// @brief Deletes files from disk
/// @param str The file(s) to delete (may contain wildcard at the final level)
void sub_kill(qbs *str) {
if (new_error)

if (is_error_pending())
return;

std::string directory, pathName, fileSpec(reinterpret_cast<char *>(str->chr), str->len);

filepath_split(filepath_fix_directory(fileSpec), directory, pathName); // split the file path
auto entry = FS_GetDirectoryEntryName(fileSpec.c_str()); // get the first entry

auto directoryContext = std::make_unique<DirectoryContext>();
auto entry = FS_GetDirectoryEntryName(directoryContext.get(), fileSpec.c_str()); // get the first entry

// Keep looking through the entries until we file a file
while (!FS_IsStringEmpty(entry)) {
Expand All @@ -887,7 +906,7 @@ void sub_kill(qbs *str) {
if (FS_FileExists(pathName.c_str()))
break;

entry = FS_GetDirectoryEntryName(nullptr); // get the next entry
entry = FS_GetDirectoryEntryName(directoryContext.get(), nullptr); // get the next entry
}

// Check if we have exhausted the entries without ever finding a file
Expand Down Expand Up @@ -917,15 +936,15 @@ void sub_kill(qbs *str) {
}
}

entry = FS_GetDirectoryEntryName(nullptr); // get the next entry
entry = FS_GetDirectoryEntryName(directoryContext.get(), nullptr); // get the next entry
filepath_join(pathName, directory, entry);
} while (!FS_IsStringEmpty(entry));
}

/// @brief Creates a new directory
/// @param str The directory path name to create
void sub_mkdir(qbs *str) {
if (new_error)
if (is_error_pending())
return;

std::string pathName(reinterpret_cast<char *>(str->chr), str->len);
Expand All @@ -949,7 +968,7 @@ void sub_mkdir(qbs *str) {
/// @param oldname The old file / directory name
/// @param newname The new file / directory name
void sub_name(qbs *oldname, qbs *newname) {
if (new_error)
if (is_error_pending())
return;

std::string pathNameOld(reinterpret_cast<char *>(oldname->chr), oldname->len), pathNameNew(reinterpret_cast<char *>(newname->chr), newname->len);
Expand Down Expand Up @@ -979,7 +998,7 @@ void sub_name(qbs *oldname, qbs *newname) {
/// @brief Deletes an empty directory
/// @param str The path name of the directory to delete
void sub_rmdir(qbs *str) {
if (new_error)
if (is_error_pending())
return;

std::string pathName(reinterpret_cast<char *>(str->chr), str->len);
Expand Down
Loading