diff --git a/.gitignore b/.gitignore index 3279758..20aa26f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__/ smart-runner.conf smart-runner.json smart-runner.log +.smart-runner.lock diff --git a/README.md b/README.md index 38ac0fe..d4fd8f8 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,11 @@ S.M.A.R.T Runner is a Python script designed to automate the scheduling and exec - **Parallel Testing:** Specify how many disks to test simultaneously for both short and long tests. - **Offset Timing:** Configure waiting periods between tests to avoid overloading the system or testing too frequently. - **Logging:** Keep track of tests, results, and any errors in a specified log file. -- **Email Notifications:** Receive email alerts for test failures or potential disk issues. +- **Email Notifications:** Receive email alerts for test failures. - **Configurable SMTP:** Set up custom SMTP server settings for sending out email notifications. - **Easy Cron Integration:** Run the script as a daily cron job for hands-off operation. - **Intelligent Disk Detection:** Automatically detect and handle disks based on mount points if specified. +- **Lock File:** Prevent multiple instances of the script from running simultaneously. ## Setup Requirements @@ -39,7 +40,7 @@ The script is configured via a `smart-runner.conf` file. Here's an explanation o | Section | Option | Description | | ---------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `disks` | (list) | List the devices or mounts to test. Devices are listed as `/dev/sdX`. If a mount is given the disk will automatically be resolved. | -| `database` | `file` | Path to the file where test schedules and results are stored. | +| `database` | `file` | Path to the file where test dates are stored. | | `short` | `enabled` | Set to `true` to enable short SMART tests. | | | `frequency_days` | How often (in days) to run a short test on each disk. | | | `disks_per_run` | Number of disks to test in parallel for short tests. Set to 0 for all. | diff --git a/smart-runner.py b/smart-runner.py index 1499fe8..e4a616c 100755 --- a/smart-runner.py +++ b/smart-runner.py @@ -8,6 +8,7 @@ Database, Disk, Email, + Lock, Logger, Message, NotEnabledException, @@ -20,7 +21,6 @@ TooSoonException, ) from sys import exit -from traceback import print_exc def sendTestFailureEmail(config, logger, test, disk): @@ -55,6 +55,7 @@ def sendTestFailureEmail(config, logger, test, disk): def main(): try: + Lock() args = Args() config = Config(configFile=args.config) db = Database(dbFile=config.database.file) @@ -175,7 +176,6 @@ def main(): except Exception as e: print("Error: {}".format(e)) - print_exc(e) exit(1) diff --git a/smart_runner/__init__.py b/smart_runner/__init__.py index 5689ab9..28db387 100644 --- a/smart_runner/__init__.py +++ b/smart_runner/__init__.py @@ -4,5 +4,6 @@ from .database import * from .disk import * from .email import * +from .lock import * from .logger import * from .test import * diff --git a/smart_runner/lock.py b/smart_runner/lock.py new file mode 100644 index 0000000..2f76c97 --- /dev/null +++ b/smart_runner/lock.py @@ -0,0 +1,32 @@ +from atexit import register +from os import getcwd, remove + +LOCK_FILE_PATH = getcwd() + "/.smart-runner.lock" + + +class Lock: + def __init__(self): + if self.isLocked(): + raise RuntimeError("smart-runner is already running") + + self.createLock() + + # Register the removeLock function to be called on exit + register(self.removeLock) + + def createLock(self): + with open(LOCK_FILE_PATH, "w") as lockFile: + lockFile.write("") + + def removeLock(self): + try: + remove(LOCK_FILE_PATH) + except FileNotFoundError: + pass # If the file is already removed or doesn't exist, ignore the error + + def isLocked(self): + try: + with open(LOCK_FILE_PATH, "r") as lockFile: + return True + except FileNotFoundError: + return False