diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 2a1d8c892..b95d9718a 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -28,6 +28,7 @@ AThings
aufxw
aur
autoclean
+automake
autoprocess
autoupdate
avmkfdiitirnrenzljwc
@@ -129,6 +130,7 @@ getenforce
gfortran
GFree
Gibi
+gmake
gmodule
GObject
gpg
@@ -188,6 +190,7 @@ libglib
libgobject
liblphobos
libm
+libinotify
libnotify
libsqlite
Lighttpd
@@ -206,6 +209,7 @@ ltmain
Lyncredible
makepkg
mangleof
+maxfiles
mayne
mbr
memtest
@@ -271,6 +275,7 @@ phlibi
phobos
pidx
pixbuf
+pkgconf
pki
pkolmann
podman
diff --git a/Makefile.in b/Makefile.in
index 07b1a22c1..2abb35571 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -25,6 +25,7 @@ systemdsystemunitdir = @systemdsystemunitdir@
curl_LIBS = @curl_LIBS@
sqlite_LIBS = @sqlite_LIBS@
notify_LIBS = @notify_LIBS@
+bsd_inotify_LIBS = @bsd_inotify_LIBS@
COMPLETIONS = @COMPLETIONS@
BASH_COMPLETION_DIR = @BASH_COMPLETION_DIR@
ZSH_COMPLETION_DIR = @ZSH_COMPLETION_DIR@
@@ -107,16 +108,22 @@ onedrive: $(SOURCES)
else \
echo $(version) > version ; \
fi
- $(DC) $(DCFLAGS) $(addprefix -L,$(curl_LIBS)) $(addprefix -L,$(sqlite_LIBS)) $(addprefix -L,$(notify_LIBS)) -L-ldl $(SOURCES) -of$@
+ $(DC) $(DCFLAGS) $(addprefix -L,$(curl_LIBS)) $(addprefix -L,$(sqlite_LIBS)) $(addprefix -L,$(notify_LIBS)) $(addprefix -L,$(bsd_inotify_LIBS)) -L-ldl $(SOURCES) -of$@
install: all
- $(INSTALL) -D onedrive $(DESTDIR)$(bindir)/onedrive
- $(INSTALL) -D -m 0644 onedrive.1 $(DESTDIR)$(mandir)/man1/onedrive.1
- $(INSTALL) -D -m 0644 contrib/logrotate/onedrive.logrotate $(DESTDIR)$(sysconfdir)/logrotate.d/onedrive
+ mkdir -p $(DESTDIR)$(bindir)
+ $(INSTALL) onedrive $(DESTDIR)$(bindir)/onedrive
+ mkdir -p $(DESTDIR)$(mandir)/man1
+ $(INSTALL) -m 0644 onedrive.1 $(DESTDIR)$(mandir)/man1/onedrive.1
+ mkdir -p $(DESTDIR)$(sysconfdir)/logrotate.d
+ $(INSTALL) -m 0644 contrib/logrotate/onedrive.logrotate $(DESTDIR)$(sysconfdir)/logrotate.d/onedrive
mkdir -p $(DESTDIR)$(docdir)
- $(INSTALL) -D -m 0644 $(DOCFILES) $(DESTDIR)$(docdir)
+ for file in $(DOCFILES); do \
+ $(INSTALL) -m 0644 $$file $(DESTDIR)$(docdir); \
+ done
ifeq ($(HAVE_SYSTEMD),yes)
- $(INSTALL) -d -m 0755 $(DESTDIR)$(systemduserunitdir) $(DESTDIR)$(systemdsystemunitdir)
+ mkdir -p $(DESTDIR)$(systemduserunitdir)
+ mkdir -p $(DESTDIR)$(systemdsystemunitdir)
ifeq ($(RHEL),1)
$(INSTALL) -m 0644 $(system_unit_files) $(DESTDIR)$(systemdsystemunitdir)
$(INSTALL) -m 0644 $(user_unit_files) $(DESTDIR)$(systemdsystemunitdir)
@@ -126,14 +133,17 @@ else
endif
else
ifeq ($(RHEL_VERSION),6)
- install -D contrib/init.d/onedrive.init $(DESTDIR)/etc/init.d/onedrive
- install -D contrib/init.d/onedrive_service.sh $(DESTDIR)$(bindir)/onedrive_service.sh
+ $(INSTALL) contrib/init.d/onedrive.init $(DESTDIR)/etc/init.d/onedrive
+ $(INSTALL) contrib/init.d/onedrive_service.sh $(DESTDIR)$(bindir)/onedrive_service.sh
endif
endif
ifeq ($(COMPLETIONS),yes)
- $(INSTALL) -D -m 0644 contrib/completions/complete.zsh $(DESTDIR)$(ZSH_COMPLETION_DIR)/_onedrive
- $(INSTALL) -D -m 0644 contrib/completions/complete.bash $(DESTDIR)$(BASH_COMPLETION_DIR)/onedrive
- $(INSTALL) -D -m 0644 contrib/completions/complete.fish $(DESTDIR)$(FISH_COMPLETION_DIR)/onedrive.fish
+ mkdir -p $(DESTDIR)$(ZSH_COMPLETION_DIR)
+ $(INSTALL) -m 0644 contrib/completions/complete.zsh $(DESTDIR)$(ZSH_COMPLETION_DIR)/_onedrive
+ mkdir -p $(DESTDIR)$(BASH_COMPLETION_DIR)
+ $(INSTALL) -m 0644 contrib/completions/complete.bash $(DESTDIR)$(BASH_COMPLETION_DIR)/onedrive
+ mkdir -p $(DESTDIR)$(FISH_COMPLETION_DIR)
+ $(INSTALL) -m 0644 contrib/completions/complete.fish $(DESTDIR)$(FISH_COMPLETION_DIR)/onedrive.fish
endif
uninstall:
diff --git a/configure b/configure
index 7c0fcd12d..a5d430d70 100755
--- a/configure
+++ b/configure
@@ -595,6 +595,7 @@ bashcompdir
COMPLETIONS
NOTIFICATIONS
notify_LIBS
+bsd_inotify_LIBS
notify_CFLAGS
HAVE_SYSTEMD
systemduserunitdir
@@ -678,6 +679,7 @@ sqlite_CFLAGS
sqlite_LIBS
notify_CFLAGS
notify_LIBS
+bsd_inotify_LIBS
bashcompdir'
@@ -1309,23 +1311,21 @@ Optional Packages:
Directory for fish completion files
Some influential environment variables:
- DC D compiler executable
- DCFLAGS flags for D compiler
- PKG_CONFIG path to pkg-config utility
- PKG_CONFIG_PATH
- directories to add to pkg-config's search path
+ DC D compiler executable
+ DCFLAGS flags for D compiler
+ PKG_CONFIG path to pkg-config utility
+ PKG_CONFIG_PATH directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
- path overriding pkg-config's built-in search path
- curl_CFLAGS C compiler flags for curl, overriding pkg-config
- curl_LIBS linker flags for curl, overriding pkg-config
- sqlite_CFLAGS
- C compiler flags for sqlite, overriding pkg-config
- sqlite_LIBS linker flags for sqlite, overriding pkg-config
- notify_CFLAGS
- C compiler flags for notify, overriding pkg-config
- notify_LIBS linker flags for notify, overriding pkg-config
- bashcompdir value of completionsdir for bash-completion, overriding
- pkg-config
+ path overriding pkg-config's built-in search path
+ curl_CFLAGS C compiler flags for curl, overriding pkg-config
+ curl_LIBS linker flags for curl, overriding pkg-config
+ sqlite_CFLAGS C compiler flags for sqlite, overriding pkg-config
+ sqlite_LIBS linker flags for sqlite, overriding pkg-config
+ notify_CFLAGS C compiler flags for GUI notifications, overriding pkg-config
+ notify_LIBS linker flags for GUI notifications, overriding pkg-config
+ bsd_inotify_LIBS linker flags for inotify support on BSD
+ bashcompdir value of completionsdir for bash-completion, overriding
+ pkg-config
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
@@ -2509,6 +2509,24 @@ else
fi
NOTIFICATIONS=$enable_notifications
+# Check if Linux or BSD Platform
+case "$(uname -s)" in
+ Linux)
+ bsd_inotify_LIBS=""
+ ;;
+ FreeBSD)
+ bsd_inotify_LIBS="-L/usr/local/lib -linotify"
+ ;;
+ OpenBSD)
+ bsd_inotify_LIBS=""
+ ;;
+ *)
+ bsd_inotify_LIBS=""
+ ;;
+esac
+
+# Export the variable for use in the script
+export bsd_inotify_LIBS
# Check whether --enable-completions was given.
if test "${enable_completions+set}" = set; then :
diff --git a/docs/install.md b/docs/install.md
index f600cb79d..ebd20c338 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -18,6 +18,7 @@ Only the current release version or greater is supported. Earlier versions are n
| Debian 12 | [onedrive](https://packages.debian.org/bookworm/source/onedrive) ||✔|✔|✔|✔| **Note:** Do not install from Debian Package Repositories as the package is obsolete and is not supported
For a supported application version, it is recommended that for Debian 12 that you install from OpenSuSE Build Service using the Debian Package Install [Instructions](ubuntu-package-install.md) |
| Debian Sid | [onedrive](https://packages.debian.org/sid/onedrive) ||✔|✔|✔|✔| |
| Fedora | [onedrive](https://koji.fedoraproject.org/koji/packageinfo?packageID=26044) ||✔|✔|✔|✔| |
+| FreeBSD | [onedrive](https://www.freshports.org/net/onedrive) ||❌|✔|❌|❌| |
| Gentoo | [onedrive](https://gpo.zugaina.org/net-misc/onedrive) | No API Available |✔|✔|❌|❌| |
| Homebrew | [onedrive](https://formulae.brew.sh/formula/onedrive) | |❌|✔|❌|❌| |
| Linux Mint 20.x | [onedrive](https://community.linuxmint.com/software/view/onedrive) | |❌|✔|✔|✔| **Note:** Do not install from Linux Mint Repositories as the package is obsolete and is not supported
For a supported application version, it is recommended that for Linux Mint that you install from OpenSuSE Build Service using the Ubuntu Package Install [Instructions](ubuntu-package-install.md) |
@@ -59,7 +60,7 @@ curl -fsS https://dlang.org/install.sh | bash -s ldc
```text
sudo pacman -S git make pkg-config curl sqlite ldc
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo pacman -S libnotify
```
@@ -76,11 +77,22 @@ sudo dnf groupinstall 'Development Tools'
sudo dnf install libcurl-devel sqlite-devel
curl -fsS https://dlang.org/install.sh | bash -s dmd
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo dnf install libnotify-devel
```
+### Dependencies: FreeBSD
+```text
+pkg install bash bash-completion gmake pkgconf autoconf automake logrotate libinotify git sqlite3 ldc
+```
+For GUI notifications the following is also necessary:
+```text
+pkg install libnotify
+```
+> [!NOTE]
+> Install the required FreeBSD packages as 'root' unless you have installed 'sudo'
+
### Dependencies: Gentoo
```text
sudo emerge app-portage/layman
@@ -88,7 +100,7 @@ sudo layman -a dlang
```
Add ebuild from contrib/gentoo to a local overlay to use.
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo emerge x11-libs/libnotify
```
@@ -113,7 +125,7 @@ sudo apt install build-essential
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl
curl -fsS https://dlang.org/install.sh | bash -s dmd
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo apt install libnotify-dev
```
@@ -134,7 +146,7 @@ These instructions were validated using:
sudo apt install build-essential
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl ldc
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo apt install libnotify-dev
```
@@ -145,7 +157,7 @@ sudo zypper addrepo https://download.opensuse.org/repositories/devel:languages:D
sudo zypper refresh
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo zypper install libnotify-devel
```
@@ -156,7 +168,7 @@ sudo zypper addrepo https://download.opensuse.org/repositories/devel:languages:D
sudo zypper refresh
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo zypper install libnotify-devel
```
@@ -166,19 +178,20 @@ sudo zypper install libnotify-devel
sudo zypper refresh
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
```
-For notifications the following is also necessary:
+For GUI notifications the following is also necessary:
```text
sudo zypper install libnotify-devel
```
## Compilation & Installation
### High Level Steps
-1. Install the platform dependencies for your Linux OS
-2. Activate your DMD or LDC compiler
-3. Clone the GitHub repository, run configure and make, then install
-4. Deactivate your DMD or LDC compiler
+1. Install the platform dependencies for your platform
+2. Activate your DMD or LDC compiler if required
+3. Clone the GitHub repository,
+4. Run the 'configure' command then build the application and install it
+5. Deactivate your DMD or LDC compiler if required
-### Building using DMD Reference Compiler
+### Linux: Building the application using the DMD Reference Compiler
Before cloning and compiling, if you have installed DMD via curl for your OS, you will need to activate DMD as per example below:
```text
Run `source ~/dlang/dmd-2.088.0/activate` in your shell to use dmd-2.088.0.
@@ -198,6 +211,17 @@ make clean; make;
sudo make install
```
+### FreeBSD: Building the application using FreeBSD version of LDC
+```text
+git clone https://github.com/abraunegg/onedrive.git
+cd onedrive
+./configure
+gmake clean; gmake;
+gmake install
+```
+> [!NOTE]
+> Install the application as 'root' unless you have installed 'sudo'
+
### Build options
#### GUI Notification Support
GUI notification support can be enabled using the `configure` switch `--enable-notifications`.
@@ -230,7 +254,7 @@ as far as possible automatically, but can be overridden by passing
> For successful compilation of this application, it's crucial that the build environment is equipped with a minimum of 1GB of memory and an additional 1GB of swap space. To verify your system's swap space availability, you can use the `swapon` command. Ensuring these requirements are met is vital for the application's compilation process.
> [!NOTE]
-> The 'configure' step will detect the correct version of LDC to be used when compiling the client under ARMHF and ARM64 cpu architectures.
+> The 'configure' step will detect the correct version of LDC to be used when compiling the client under ARMHF and ARM64 CPU architectures.
```text
git clone https://github.com/abraunegg/onedrive.git
diff --git a/src/main.d b/src/main.d
index 2bf126860..e106d7cbd 100644
--- a/src/main.d
+++ b/src/main.d
@@ -867,11 +867,9 @@ int main(string[] cliArgs) {
performFileSystemMonitoring = true;
// What are the current values for the platform we are running on
- // Max number of open files /proc/sys/fs/file-max
- string maxOpenFiles = strip(readText("/proc/sys/fs/file-max"));
+ string maxOpenFiles = strip(getMaxOpenFiles());
// What is the currently configured maximum inotify watches that can be used
- // /proc/sys/fs/inotify/max_user_watches
- string maxInotifyWatches = strip(readText("/proc/sys/fs/inotify/max_user_watches"));
+ string maxInotifyWatches = strip(getMaxInotifyWatches());
// Start the monitor process
addLogEntry("OneDrive synchronisation interval (seconds): " ~ to!string(appConfig.getValueLong("monitor_interval")));
@@ -1238,6 +1236,54 @@ int main(string[] cliArgs) {
}
}
+// Retrieves the maximum number of open files allowed by the system
+string getMaxOpenFiles() {
+ version (Linux) {
+ try {
+ // Read max open files from procfs on Linux
+ return strip(readText("/proc/sys/fs/file-max"));
+ } catch (Exception e) {
+ return "Unknown (Error reading /proc/sys/fs/file-max)";
+ }
+ } else version (FreeBSD) {
+ try {
+ // Read max open files using sysctl on FreeBSD
+ return strip(executeShell("sysctl -n kern.maxfiles").output);
+ } catch (Exception e) {
+ return "Unknown (sysctl error)";
+ }
+ } else version (OpenBSD) {
+ try {
+ // Read max open files using sysctl on OpenBSD
+ return strip(executeShell("sysctl -n kern.maxfiles").output);
+ } catch (Exception e) {
+ return "Unknown (sysctl error)";
+ }
+ } else {
+ return "Unsupported platform";
+ }
+}
+
+// Retrieves the maximum inotify watches allowed (Linux) or a placeholder for other platforms
+string getMaxInotifyWatches() {
+ version (Linux) {
+ try {
+ // Read max inotify watches from procfs on Linux
+ return strip(readText("/proc/sys/fs/inotify/max_user_watches"));
+ } catch (Exception e) {
+ return "Unknown (Error reading /proc/sys/fs/inotify/max_user_watches)";
+ }
+ } else version (FreeBSD) {
+ // FreeBSD uses kqueue instead of inotify, no direct equivalent
+ return "N/A (uses kqueue)";
+ } else version (OpenBSD) {
+ // OpenBSD uses kqueue instead of inotify, no direct equivalent
+ return "N/A (uses kqueue)";
+ } else {
+ return "Unsupported platform";
+ }
+}
+
// Print error message when --sync or --monitor has not been used and no valid 'no-sync' operation was requested
void printMissingOperationalSwitchesError() {
// notify the user that --sync or --monitor were missing
diff --git a/src/monitor.d b/src/monitor.d
index f5db7a741..aab1b0a66 100644
--- a/src/monitor.d
+++ b/src/monitor.d
@@ -20,6 +20,7 @@ import std.regex;
import std.stdio;
import std.string;
import std.conv;
+import core.sync.mutex;
// What other modules that we have created do we need to import?
import config;
@@ -57,13 +58,19 @@ class MonitorBackgroundWorker {
int wd = inotify_add_watch(fd, toStringz(pathname), mask);
if (wd < 0) {
if (errno() == ENOSPC) {
- // Get the current value
- ulong maxInotifyWatches = to!int(strip(readText("/proc/sys/fs/inotify/max_user_watches")));
- addLogEntry("The user limit on the total number of inotify watches has been reached.");
- addLogEntry("Your current limit of inotify watches is: " ~ to!string(maxInotifyWatches));
- addLogEntry("It is recommended that you change the max number of inotify watches to at least double your existing value.");
- addLogEntry("To change the current max number of watches to " ~ to!string((maxInotifyWatches * 2)) ~ " run:");
- addLogEntry("EXAMPLE: sudo sysctl fs.inotify.max_user_watches=" ~ to!string((maxInotifyWatches * 2)));
+ version (Linux) {
+ // Read max inotify watches from procfs on Linux
+ ulong maxInotifyWatches = to!int(strip(readText("/proc/sys/fs/inotify/max_user_watches")));
+ addLogEntry("The user limit on the total number of inotify watches has been reached.");
+ addLogEntry("Your current limit of inotify watches is: " ~ to!string(maxInotifyWatches));
+ addLogEntry("It is recommended that you change the max number of inotify watches to at least double your existing value.");
+ addLogEntry("To change the current max number of watches to " ~ to!string((maxInotifyWatches * 2)) ~ " run:");
+ addLogEntry("EXAMPLE: sudo sysctl fs.inotify.max_user_watches=" ~ to!string((maxInotifyWatches * 2)));
+ } else {
+ // some other platform
+ addLogEntry("The user limit on the total number of inotify watches has been reached.");
+ addLogEntry("Please seek support from your distribution on how to increase the max number of inotify watches to at least double your existing value.");
+ }
}
if (errno() == 13) {
if (verboseLogging) {addLogEntry("WARNING: inotify_add_watch failed - permission denied: " ~ pathname, ["verbose"]);}
@@ -85,6 +92,11 @@ class MonitorBackgroundWorker {
}
shared int removeInotifyWatch(int wd) {
+ assert(fd > 0, "File descriptor 'fd' is invalid.");
+ assert(wd > 0, "Watch descriptor 'wd' is invalid.");
+ // Debug logging of the inotify watch being removed
+ if (debugLogging) {addLogEntry("Attempting to remove inotify watch: fd=" ~ fd.to!string ~ ", wd=" ~ wd.to!string, ["debug"]);}
+ // return the value of performing the action
return inotify_rm_watch(fd, wd);
}
@@ -263,6 +275,9 @@ final class Monitor {
private string[int] cookieToPath;
// buffer to receive the inotify events
private void[] buffer;
+
+ // Mutex to support thread safe access of inotify watch descriptors
+ private Mutex inotifyMutex;
// Configure function delegates
void delegate(string path) onDirCreated;
@@ -273,12 +288,14 @@ final class Monitor {
// List of paths that were moved, not deleted
bool[string] movedNotDeleted;
+ // An array of actions
ActionHolder actionHolder;
// Configure the class variable to consume the application configuration including selective sync
this(ApplicationConfig appConfig, ClientSideFiltering selectiveSync) {
this.appConfig = appConfig;
this.selectiveSync = selectiveSync;
+ inotifyMutex = new Mutex(); // Define a Mutex for thread-safe access
}
// The destructor should only clean up resources owned directly by this instance
@@ -313,7 +330,6 @@ final class Monitor {
// Start monitoring
workerTid = spawn(&startMonitorJob, worker, thisTid);
-
initialised = true;
}
@@ -328,11 +344,16 @@ final class Monitor {
return;
initialised = false;
// Release all resources
- removeAll();
- // Notify the worker that the monitor has been shutdown
- worker.interrupt();
- send(false);
- wdToDirName = null;
+ synchronized(inotifyMutex) {
+ // Interrupt the worker to allow removal of inotify watch descriptors
+ worker.interrupt();
+ // Remove all the inotify watch descriptors
+ removeAll();
+ // Notify the worker that the monitor has been shutdown
+ worker.interrupt();
+ send(false);
+ wdToDirName = null;
+ }
}
// Recursively add this path to be monitored
@@ -445,7 +466,12 @@ final class Monitor {
// Remove a watch descriptor
private void removeAll() {
- string[int] copy = wdToDirName.dup;
+ string[int] copy;
+ synchronized(inotifyMutex) {
+ copy = wdToDirName.dup; // Make a thread-safe copy
+ }
+
+ // Loop through the watch descriptors and remove
foreach (wd, path; copy) {
remove(wd);
}
@@ -453,10 +479,13 @@ final class Monitor {
private void remove(int wd) {
assert(wd in wdToDirName);
- int ret = worker.removeInotifyWatch(wd);
- if (ret < 0) throw new MonitorException("inotify_rm_watch failed");
- if (verboseLogging) {addLogEntry("Monitored directory removed: " ~ to!string(wdToDirName[wd]), ["verbose"]);}
- wdToDirName.remove(wd);
+
+ synchronized(inotifyMutex) {
+ int ret = worker.removeInotifyWatch(wd);
+ if (ret < 0) throw new MonitorException("inotify_rm_watch failed");
+ if (verboseLogging) {addLogEntry("Monitored directory removed: " ~ to!string(wdToDirName[wd]), ["verbose"]);}
+ wdToDirName.remove(wd);
+ }
}
// Remove the watch descriptors associated to the given path