Enhance displayFileSystemErrorMessage() to include details of the actual path (#3574)

* Enhance displayFileSystemErrorMessage() to include details of the actual path that generated the error message to make diagnostics easier when a file system issue is generated
* Add SQLITE_READONLY as a case to catch if the database file is read-only
This commit is contained in:
abraunegg 2025-12-22 12:53:33 +11:00 committed by GitHub
commit 6396864bfc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 266 additions and 90 deletions

View file

@ -723,6 +723,9 @@ class ApplicationConfig {
// Create a backup of the 'config' file if it does not exist
void createBackupConfigFile() {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
if (!getValueBool("dry_run")) {
// Is there a backup of the config file if the config file exists?
if (exists(applicableConfigFilePath)) {
@ -734,7 +737,7 @@ class ApplicationConfig {
configBackupFile.setAttributes(convertedPermissionValue);
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, configBackupFile, FsErrorSeverity.warning);
}
}
} else {
@ -1906,6 +1909,9 @@ class ApplicationConfig {
// Check the application configuration for any changes that need to trigger a --resync
// This function is only called if --resync is not present
bool applicationChangeWhereResyncRequired() {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Default is that no resync is required
bool resyncRequired = false;
@ -1972,11 +1978,11 @@ class ApplicationConfig {
} catch (FileException e) {
// filesystem error
failedToReadBackupConfig = true;
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, configBackupFile, FsErrorSeverity.warning);
} catch (std.exception.ErrnoException e) {
// filesystem error
failedToReadBackupConfig = true;
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, configBackupFile, FsErrorSeverity.warning);
}
scope(exit) {
@ -2172,6 +2178,9 @@ class ApplicationConfig {
// For each of the config files, update the hash data in the hash files
void updateHashContentsForConfigFiles() {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Are we in a --dry-run scenario?
if (!getValueBool("dry_run")) {
// Not a dry-run scenario, update the applicable files
@ -2185,7 +2194,7 @@ class ApplicationConfig {
configHashFile.setAttributes(convertedPermissionValue);
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, configHashFile, FsErrorSeverity.warning);
}
}
// Update 'sync_list' files
@ -2198,7 +2207,7 @@ class ApplicationConfig {
syncListHashFile.setAttributes(convertedPermissionValue);
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, syncListHashFile, FsErrorSeverity.warning);
}
}
} else {
@ -2209,6 +2218,9 @@ class ApplicationConfig {
// Create any required hash files for files that help us determine if the configuration has changed since last run
void createRequiredInitialConfigurationHashFiles() {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Does a 'config' file exist with a valid hash file
if (exists(applicableConfigFilePath)) {
if (!exists(configHashFile)) {
@ -2219,7 +2231,7 @@ class ApplicationConfig {
configHashFile.setAttributes(convertedPermissionValue);
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, configHashFile, FsErrorSeverity.warning);
}
}
// Generate the runtime hash for the 'config' file
@ -2236,7 +2248,7 @@ class ApplicationConfig {
syncListHashFile.setAttributes(convertedPermissionValue);
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, syncListHashFile, FsErrorSeverity.warning);
}
}
// Generate the runtime hash for the 'sync_list' file

View file

@ -446,6 +446,7 @@ final class ItemDatabase {
switch (exception.errorCode) {
case 7: // SQLITE_FULL
case 8: // SQLITE_READONLY
case 10: // SQLITE_SCHEMA
case 11: // SQLITE_CORRUPT
case 17: // SQLITE_IOERR

View file

@ -852,8 +852,18 @@ int main(string[] cliArgs) {
return EXIT_FAILURE;
}
// Change the working directory to the 'sync_dir' as configured
chdir(runtimeSyncDirectory);
// Try and change to the working directory to the 'sync_dir' as configured
try {
chdir(runtimeSyncDirectory);
// A FileSystem exception was thrown when attempting to change to the configured 'sync_dir'
} catch (FileException e) {
// Log error message
addLogEntry("FATAL: Unable to change to the configured local 'sync_dir' directory: " ~ runtimeSyncDirectory);
// A file system exception was generated
displayFileSystemErrorMessage(e.msg, strip(getFunctionName!({})), runtimeSyncDirectory, FsErrorSeverity.fatal);
// Use exit scopes to shutdown API as if we are unable to change to the 'sync_dir' we need to exit
return EXIT_FAILURE;
}
// Do we need to validate the runtimeSyncDirectory to check for the presence of a '.nosync' file
checkForNoMountScenario();

View file

@ -360,6 +360,9 @@ final class Monitor {
// Recursively add this path to be monitored
private void addRecursive(string dirname) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// skip non existing/disappeared items
if (!exists(dirname)) {
if (verboseLogging) {addLogEntry("Not adding non-existing/disappeared directory: " ~ dirname, ["verbose"]);}
@ -452,17 +455,24 @@ final class Monitor {
// Catch any FileException error which is generated
} catch (std.file.FileException e) {
// Standard filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, dirname);
return;
}
}
// Traverse directory to test if this should have an inotify watch added
private void traverseDirectory(string dirname) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Current path for error logging
string currentPath;
// Try and get all the directory entities for this path
try {
auto pathList = dirEntries(dirname, SpanMode.shallow, false);
foreach(DirEntry entry; pathList) {
currentPath = entry.name;
if (entry.isDir) {
if (debugLogging) {addLogEntry("Calling addRecursive() for this directory: " ~ entry.name, ["debug"]);}
addRecursive(entry.name);
@ -471,7 +481,7 @@ final class Monitor {
// Catch any FileException error which is generated
} catch (std.file.FileException e) {
// Standard filesystem error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
return;
} catch (Exception e) {
// Issue #1154 handling
@ -486,7 +496,7 @@ final class Monitor {
forceExit();
} else {
// some other error
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
return;
}
}

View file

@ -486,6 +486,9 @@ class OneDriveApi {
// Authenticate this client against Microsoft OneDrive API using one of the 3 authentication methods this client supports
bool authorise() {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Has the client been configured to use Intune SSO via Microsoft Identity Broker (microsoft-identity-broker) dbus session
if (appConfig.getValueBool("use_intune_sso")) {
// The client is configured to use Intune SSO via Microsoft Identity Broker dbus session
@ -697,7 +700,7 @@ class OneDriveApi {
char[] response;
// What URL should be presented to the user to access
string url = authUrl ~ "?client_id=" ~ clientId ~ authScope ~ redirectUrl;
// Configure automated authentication if --auth-files authUrl:responseUrl is being used
// Configure automated authentication if --auth-files authUrlFilePath:responseUrlFilePath is being used
string authFilesString = appConfig.getValueString("auth_files");
string authResponseString = appConfig.getValueString("auth_response");
@ -706,48 +709,60 @@ class OneDriveApi {
response = cast(char[]) authResponseString;
} else if (authFilesString != "") {
string[] authFiles = authFilesString.split(":");
string authUrl = authFiles[0];
string responseUrl = authFiles[1];
string authUrlFilePath = authFiles[0];
string responseUrlFilePath = authFiles[1];
try {
auto authUrlFile = File(authUrl, "w");
auto authUrlFile = File(authUrlFilePath, "w");
authUrlFile.write(url);
authUrlFile.close();
} catch (FileException exception) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, authUrlFilePath);
// Must force exit here, allow logging to be done
forceExit();
} catch (ErrnoException exception) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, authUrlFilePath);
// Must force exit here, allow logging to be done
forceExit();
}
// Log we are now waiting
addLogEntry("Client requires authentication before proceeding. Waiting for --auth-files elements to be available.");
while (!exists(responseUrl)) {
while (!exists(responseUrlFilePath)) {
Thread.sleep(dur!("msecs")(100));
}
// read response from provided from OneDrive
try {
response = cast(char[]) read(responseUrl);
response = cast(char[]) read(responseUrlFilePath);
} catch (OneDriveException exception) {
// exception generated
displayOneDriveErrorMessage(exception.msg, getFunctionName!({}));
displayOneDriveErrorMessage(exception.msg, thisFunctionName);
return false;
}
// try to remove old files
// try to remove auth files one at a time
try {
std.file.remove(authUrl);
std.file.remove(responseUrl);
std.file.remove(authUrlFilePath);
} catch (FileException exception) {
addLogEntry("Cannot remove files " ~ authUrl ~ " " ~ responseUrl);
addLogEntry("Cannot remove --auth-files elements - details below");
// There was a file system error - display the error message
displayFileSystemErrorMessage(exception.msg, thisFunctionName, authUrlFilePath);
return false;
}
try {
std.file.remove(responseUrlFilePath);
} catch (FileException exception) {
addLogEntry("Cannot remove --auth-files elements - details below");
// There was a file system error - display the error message
displayFileSystemErrorMessage(exception.msg, thisFunctionName, responseUrlFilePath);
return false;
}
} else {
@ -794,8 +809,10 @@ class OneDriveApi {
// Process Intune JSON response data
void processIntuneResponse(JSONValue intuneBrokerJSONData) {
// Use the provided JSON data and configure elements, save JSON data to disk for reuse
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Use the provided JSON data and configure elements, save JSON data to disk for reuse
long expiresOnMs = intuneBrokerJSONData["expiresOn"].integer();
// Convert to SysTime
SysTime expiryTime = SysTime.fromUnixTime(expiresOnMs / 1000);
@ -821,7 +838,7 @@ class OneDriveApi {
appConfig.intuneAccountDetailsFilePath.setAttributes(appConfig.returnSecureFilePermission());
} catch (FileException exception) {
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, appConfig.intuneAccountDetailsFilePath);
}
}
@ -1153,6 +1170,9 @@ class OneDriveApi {
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get_content
void downloadById(const(char)[] driveId, const(char)[] itemId, string saveToPath, long fileSize, JSONValue onlineHash, long resumeOffset = 0) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// We pass through to 'downloadFile()'
// - resumeOffset
// - onlineHash
@ -1166,7 +1186,7 @@ class OneDriveApi {
remove(saveToPath);
} catch (FileException exception) {
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, saveToPath);
}
}
}
@ -1190,7 +1210,7 @@ class OneDriveApi {
}
} catch (FileException exception) {
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, parentalPath);
}
}
@ -1242,6 +1262,10 @@ class OneDriveApi {
}
private void acquireToken(char[] postData) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Configure the response JSON
JSONValue response;
// Log what we are doing
@ -1266,7 +1290,7 @@ class OneDriveApi {
// There was a HTTP 5xx Server Side Error - retry
acquireToken(postData);
} else {
displayOneDriveErrorMessage(exception.msg, getFunctionName!({}));
displayOneDriveErrorMessage(exception.msg, thisFunctionName);
}
}
}
@ -1327,6 +1351,9 @@ class OneDriveApi {
// Process the authentication JSON
private void processAuthenticationJSON(JSONValue response) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// Use 'access_token' and set in the application configuration
appConfig.accessToken = "bearer " ~ strip(response["access_token"].str);
@ -1363,7 +1390,7 @@ class OneDriveApi {
appConfig.refreshTokenFilePath.setAttributes(appConfig.returnSecureFilePermission());
} catch (FileException exception) {
// display the error message
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
displayFileSystemErrorMessage(exception.msg, thisFunctionName, appConfig.refreshTokenFilePath);
}
}
}
@ -1708,12 +1735,14 @@ class OneDriveApi {
// Save the resume download data
private void saveResumeDownloadFile(string threadResumeDownloadFilePath, JSONValue resumeDownloadData) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
try {
std.file.write(threadResumeDownloadFilePath, resumeDownloadData.toString());
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, threadResumeDownloadFilePath);
}
}
@ -2036,11 +2065,10 @@ class OneDriveApi {
throw new OneDriveException(response.statusLine.code, response.statusLine.reason, response);
}
// A FileSystem exception was thrown
} catch (ErrnoException exception) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(exception.msg, callingFunction);
// A FileSystem exception was thrown from somewhere
} catch (FileException exception) {
// There was a file system error - display the error message
displayFileSystemErrorMessage(exception.msg, callingFunction, ""); // as we have no file path reference here, use a blank input
throw new OneDriveException(0, "There was a file system error during OneDrive request: " ~ exception.msg, response);
// A OneDriveError was thrown

View file

@ -3149,7 +3149,7 @@ class SyncEngine {
saveDatabaseItem(newDatabaseItem);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, newItemPath);
}
} else {
// we dont create the directory, but we need to track that we 'faked it'
@ -3497,7 +3497,7 @@ class SyncEngine {
}
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, existingItemPath);
}
}
@ -3853,12 +3853,12 @@ class SyncEngine {
} catch (FileException e) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, newItemPath);
downloadFailed = true;
} catch (ErrnoException e) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, newItemPath);
downloadFailed = true;
}
@ -4640,7 +4640,7 @@ class SyncEngine {
rmdirRecurse(path);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, path);
}
}
}
@ -5420,7 +5420,7 @@ class SyncEngine {
}
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, localFilePath);
}
} else {
// Directory does not exist locally, but it is in our database as a dbItem containing all the data was passed into this function
@ -7064,7 +7064,7 @@ class SyncEngine {
}
} catch (FileException e) {
// filesystem error
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, localFilePath);
}
} else {
// As this is a unique thread, the sessionFilePath for where we save the data needs to be unique
@ -7097,8 +7097,8 @@ class SyncEngine {
displayOneDriveErrorMessage(exception.msg, thisFunctionName);
}
} catch (FileException e) {
addLogEntry("DEBUG TO REMOVE: Modified file upload FileException Handling (Create the Upload Session)");
displayFileSystemErrorMessage(e.msg, thisFunctionName);
// Display filesystem exception error message
displayFileSystemErrorMessage(e.msg, thisFunctionName, threadUploadSessionFilePath);
}
// Do we have a valid session URL that we can use ?
@ -7121,8 +7121,8 @@ class SyncEngine {
displayOneDriveErrorMessage(exception.msg, thisFunctionName);
} catch (FileException e) {
addLogEntry("DEBUG TO REMOVE: Modified file upload FileException Handling (Perform the Upload using the session)");
displayFileSystemErrorMessage(e.msg, thisFunctionName);
// Display filesystem exception error message
displayFileSystemErrorMessage(e.msg, thisFunctionName, threadUploadSessionFilePath);
}
} else {
// Create session Upload URL failed
@ -7731,6 +7731,9 @@ class SyncEngine {
// Before we traverse this 'path', we need to make a last check to see if this was just excluded
bool skipFolderTraverse = skipBusinessSharedFolder(path);
// Current path for error logging
string currentPath;
if (!unwanted) {
// At this point, this path, we want to scan for new data as it is not excluded
if (isDir(path)) {
@ -7751,6 +7754,9 @@ class SyncEngine {
try {
auto directoryEntries = dirEntries(path, SpanMode.depth, false);
foreach (DirEntry child; directoryEntries) {
// set for error logging
currentPath = child.name;
// what sort of child is this?
if (isDir(child.name)) {
addLogEntry("Removing local directory: " ~ child.name);
@ -7766,7 +7772,7 @@ class SyncEngine {
attrIsDir(child.linkAttributes) ? rmdir(child.name) : safeRemove(child.name);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
}
}
}
@ -7785,14 +7791,14 @@ class SyncEngine {
rmdirRecurse(path);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, path);
}
}
}
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
// Display function processing time if configured to do so
if (appConfig.getValueBool("display_processing_time") && debugLogging) {
@ -7813,14 +7819,14 @@ class SyncEngine {
try {
auto directoryEntries = dirEntries(path, SpanMode.shallow, false);
foreach (DirEntry entry; directoryEntries) {
string thisPath = entry.name;
scanPathForNewData(thisPath);
currentPath = entry.name;
scanPathForNewData(entry.name);
}
// Clear directoryEntries
object.destroy(directoryEntries);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
// Display function processing time if configured to do so
if (appConfig.getValueBool("display_processing_time") && debugLogging) {
@ -7947,14 +7953,14 @@ class SyncEngine {
try {
auto directoryEntries = dirEntries(path, SpanMode.shallow, false);
foreach (DirEntry entry; directoryEntries) {
string thisPath = entry.name;
scanPathForNewData(thisPath);
currentPath = entry.name;
scanPathForNewData(entry.name);
}
// Clear directoryEntries
object.destroy(directoryEntries);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, currentPath);
// Display function processing time if configured to do so
if (appConfig.getValueBool("display_processing_time") && debugLogging) {
@ -9331,7 +9337,7 @@ class SyncEngine {
} catch (FileException e) {
// display the error message
addLogEntry("Uploading new file: " ~ fileToUpload ~ " ... failed!", ["info", "notify"]);
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, fileToUpload);
// OneDrive API Instance Cleanup - Shutdown API, free curl object and memory
uploadFileOneDriveApiInstance.releaseCurlEngine();
@ -9367,7 +9373,7 @@ class SyncEngine {
} catch (FileException e) {
// display the error message
addLogEntry("Uploading new file: " ~ fileToUpload ~ " ... failed!", ["info", "notify"]);
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, fileToUpload);
}
// Do we have a valid session URL that we can use ?
@ -9625,7 +9631,7 @@ class SyncEngine {
std.file.write(threadUploadSessionFilePath, uploadSessionData.toString());
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, threadUploadSessionFilePath);
}
// Display function processing time if configured to do so
@ -9844,13 +9850,13 @@ class SyncEngine {
} catch (std.exception.ErrnoException e) {
// There was a file system error - display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, newUploadSession["localPath"].str);
return uploadResponse;
}
} catch (ErrnoException e) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, uploadSessionData["localPath"].str);
uploadResponse = null;
return uploadResponse;
}
@ -11582,7 +11588,7 @@ class SyncEngine {
}
} catch (FileException e) {
// filesystem generated an error message - display error message
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, path);
} catch (OneDriveException e) {
if (e.httpStatusCode == 404) {
addLogEntry(e.msg);

View file

@ -64,6 +64,13 @@ shared static this() {
deviceName = Socket.hostName;
}
// To assist with filesystem severity issues, configure an enum that can be used
enum FsErrorSeverity {
warning,
error,
fatal
}
// Creates a safe backup of the given item, and only performs the function if not in a --dry-run scenario.
// If the path already ends with "-<deviceName>-safeBackup-####", the counter is incremented
// instead of appending another "-<deviceName>-safeBackup-".
@ -216,7 +223,7 @@ void safeRemove(const(char)[] path) {
return; // nothing to do
}
// Anything else is noteworthy (EISDIR, EACCES, etc.)
displayFileSystemErrorMessage(e.msg, thisFunctionName);
displayFileSystemErrorMessage(e.msg, thisFunctionName, to!string(path));
}
}
@ -547,6 +554,9 @@ bool retryInternetConnectivityTest(ApplicationConfig appConfig) {
// https://github.com/abraunegg/onedrive/issues/113
// returns true if file can be accessed
bool readLocalFile(string path) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
// What is the file size
if (getSize(path) != 0) {
try {
@ -561,7 +571,7 @@ bool readLocalFile(string path) {
}
} catch (std.file.FileException e) {
// Unable to read the file, log the error message
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, path);
return false;
}
return true;
@ -1000,29 +1010,126 @@ void handleClientUnauthorised(int httpStatusCode, JSONValue errorMessage) {
}
// Parse and display error message received from the local file system
void displayFileSystemErrorMessage(string message, string callingFunction) {
addLogEntry(); // used rather than writeln
addLogEntry("ERROR: The local file system returned an error with the following message:");
void displayFileSystemErrorMessage(string message, string callingFunction, string contextPath, FsErrorSeverity severity = FsErrorSeverity.error) {
// Separate this block from surrounding log output
addLogEntry();
auto errorArray = splitLines(message);
// Safely get the error message
string errorMessage = errorArray.length > 0 ? to!string(errorArray[0]) : "No error message available";
addLogEntry(" Error Message: " ~ errorMessage);
// Log the calling function
addLogEntry(" Calling Function: " ~ callingFunction);
// Header prefix for logging accuracy
string headerPrefix = severity == FsErrorSeverity.warning ? "WARNING"
: severity == FsErrorSeverity.fatal ? "FATAL"
: "ERROR";
// Filesystem logging header
addLogEntry(headerPrefix ~ ": The local file system returned an error with the following details:");
// Calling context (helps correlate where this came from)
if (!callingFunction.empty) {
addLogEntry(" Calling Function: " ~ callingFunction);
}
try {
// Safely check for disk space
ulong localActualFreeSpace = to!ulong(getAvailableDiskSpace("."));
if (localActualFreeSpace == 0) {
// Must force exit here, allow logging to be done
forceExit();
}
} catch (Exception e) {
// Handle exceptions from disk space check or type conversion
addLogEntry(" Exception in disk space check: " ~ e.msg);
}
// Path context (the *thing* we were operating on)
if (!contextPath.empty) {
addLogEntry(" Path: " ~ contextPath);
} else {
addLogEntry(" Path: (not available)");
}
// Primary error message (first line) + any additional lines
string errorMessage = message;
string[] errorLines;
try {
errorLines = splitLines(message);
} catch (Exception e) {
// splitLines should not fail, but never let logging throw
addLogEntry(" NOTE: Failed to split file system exception message into lines: " ~ e.msg);
}
// If we have lines to process
if (errorLines.length > 0) {
// First line: usually the most useful
errorMessage = to!string(errorLines[0]);
addLogEntry(" Error Message: " ~ errorMessage);
// Remaining lines (if any) often contain errno / path / syscall details
if (errorLines.length > 1) {
addLogEntry(" Error Details:");
foreach (i, line; errorLines[1 .. $]) {
// Avoid logging empty lines, but keep order
if (!line.empty) {
addLogEntry(" - " ~ to!string(line));
}
}
}
} else {
addLogEntry(" Error Message: No error message available");
}
// Disk space diagnostics (best-effort)
// We intentionally probe both the current directory and the target path directory when possible.
try {
// Always check the current working directory as a baseline
ulong freeCwd = to!ulong(getAvailableDiskSpace("."));
addLogEntry(" Disk Space (CWD): " ~ to!string(freeCwd) ~ " bytes available");
// If we have a context path, also check its parent directory when possible.
// We keep this conservative: if anything throws, just log the exception.
if (!contextPath.empty) {
string targetProbePath = contextPath;
// If it's a file path, probe the parent directory (where writes/renames happen).
// Avoid throwing if parentDir isn't available or contextPath is weird.
try {
// std.path.dirName handles both file/dir paths; if it returns ".", keep as-is.
import std.path : dirName;
auto parent = dirName(contextPath);
if (!parent.empty) targetProbePath = parent;
} catch (Exception e) {
addLogEntry(" NOTE: Failed to derive parent directory from path: " ~ e.msg);
}
ulong freeTarget = to!ulong(getAvailableDiskSpace(targetProbePath));
addLogEntry(" Disk Space (Path): " ~ to!string(freeTarget) ~ " bytes available (parent path: " ~ targetProbePath ~ ")");
// Preserve existing behaviour: if disk space check returns 0, force exit.
// (Assumes getAvailableDiskSpace returns 0 on a hard failure in your implementation.)
if (freeTarget == 0 || freeCwd == 0) {
// Must force exit here, allow logging to be done
forceExit();
}
} else {
// Preserve existing behaviour: if disk space check returns 0, force exit.
if (freeCwd == 0) {
forceExit();
}
}
} catch (Exception e) {
// Handle exceptions from disk space check or type conversion
addLogEntry(" NOTE: Exception during disk space check: " ~ e.msg);
}
// Add note for WARNING messages
if (headerPrefix == "WARNING") {
addLogEntry();
addLogEntry("NOTE: This error is non-fatal; the client will continue to operate, but this may affect future operations if not resolved");
addLogEntry();
}
// Add note for ERROR messages
if (headerPrefix == "ERROR") {
addLogEntry();
addLogEntry("NOTE: This error requires attention; the client may continue running, but functionality is impaired and the issue should be resolved.");
addLogEntry();
}
// Add note for FATAL messages
if (headerPrefix == "FATAL") {
addLogEntry();
addLogEntry("NOTE: This error is fatal; the client cannot continue and this issue must be corrected before retrying. The client will now attempt to exit in a safe and orderly manner.");
addLogEntry();
}
// Separate this block from surrounding log output
addLogEntry();
}
// Display the POSIX Error Message
@ -1911,6 +2018,8 @@ bool isBadCurlVersion(string curlVersion) {
// Set the timestamp of the provided path to ensure this is done in a consistent manner
void setLocalPathTimestamp(bool dryRun, string inputPath, SysTime newTimeStamp) {
// Set this function name
string thisFunctionName = format("%s.%s", strip(__MODULE__) , strip(getFunctionName!({})));
SysTime updatedModificationTime;
bool makeTimestampChange = false;
@ -1980,7 +2089,7 @@ void setLocalPathTimestamp(bool dryRun, string inputPath, SysTime newTimeStamp)
}
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, inputPath);
}
SysTime newAccessTime;
@ -1995,7 +2104,7 @@ void setLocalPathTimestamp(bool dryRun, string inputPath, SysTime newTimeStamp)
}
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
displayFileSystemErrorMessage(e.msg, thisFunctionName, inputPath);
}
}