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) |Debian 12 package|✔|✔|✔|✔| **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) |Debian Sid package|✔|✔|✔|✔| | | Fedora | [onedrive](https://koji.fedoraproject.org/koji/packageinfo?packageID=26044) |Fedora Rawhide package|✔|✔|✔|✔| | +| FreeBSD | [onedrive](https://www.freshports.org/net/onedrive) |FreeBSD package|❌|✔|❌|❌| | | Gentoo | [onedrive](https://gpo.zugaina.org/net-misc/onedrive) | No API Available |✔|✔|❌|❌| | | Homebrew | [onedrive](https://formulae.brew.sh/formula/onedrive) |Homebrew package |❌|✔|❌|❌| | | Linux Mint 20.x | [onedrive](https://community.linuxmint.com/software/view/onedrive) |Ubuntu 20.04 package |❌|✔|✔|✔| **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