diff --git a/Cargo.lock b/Cargo.lock index 360c9a7672..abd662daf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -669,6 +669,15 @@ name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -947,6 +956,7 @@ dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "grin_api 1.0.1", "grin_chain 1.0.1", @@ -3215,6 +3225,7 @@ dependencies = [ "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" diff --git a/servers/Cargo.toml b/servers/Cargo.toml index 476b3c670c..6b901a32a0 100644 --- a/servers/Cargo.toml +++ b/servers/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" [dependencies] hyper = "0.12" +fs2 = "0.4" futures = "0.1" http = "0.1" hyper-staticfile = "0.3" diff --git a/servers/src/common/types.rs b/servers/src/common/types.rs index 72db40a036..5b2c9e2676 100644 --- a/servers/src/common/types.rs +++ b/servers/src/common/types.rs @@ -48,6 +48,8 @@ pub enum Error { Pool(pool::PoolError), /// Invalid Arguments. ArgumentError(String), + /// Error originating from some I/O operation (likely a file on disk). + IOError(std::io::Error), } impl From for Error { @@ -60,7 +62,11 @@ impl From for Error { Error::Chain(e) } } - +impl From for Error { + fn from(e: std::io::Error) -> Error { + Error::IOError(e) + } +} impl From for Error { fn from(e: p2p::Error) -> Error { Error::P2P(e) diff --git a/servers/src/grin/server.rs b/servers/src/grin/server.rs index ac25eb3f7e..8ad1d1577d 100644 --- a/servers/src/grin/server.rs +++ b/servers/src/grin/server.rs @@ -16,9 +16,15 @@ //! the peer-to-peer server, the blockchain and the transaction pool) and acts //! as a facade. +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; use std::sync::Arc; use std::{thread, time}; +use fs2::FileExt; + use crate::api; use crate::api::TLSConfig; use crate::chain; @@ -59,6 +65,8 @@ pub struct Server { state_info: ServerStateInfo, /// Stop flag pub stop_state: Arc>, + /// Maintain a lock_file so we do not run multiple Grin nodes from same dir. + lock_file: Arc, } impl Server { @@ -102,8 +110,36 @@ impl Server { } } + // Exclusive (advisory) lock_file to ensure we do not run multiple + // instance of grin server from the same dir. + // This uses fs2 and should be safe cross-platform unless somebody abuses the file itself. + fn one_grin_at_a_time(config: &ServerConfig) -> Result, Error> { + let path = Path::new(&config.db_root); + fs::create_dir_all(path.clone())?; + let path = path.join("grin.lock"); + let lock_file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&path)?; + lock_file.try_lock_exclusive().map_err(|e| { + let mut stderr = std::io::stderr(); + writeln!( + &mut stderr, + "Failed to lock {:?} (grin server already running?)", + path + ) + .expect("Could not write to stderr"); + e + })?; + Ok(Arc::new(lock_file)) + } + /// Instantiates a new server associated with the provided future reactor. pub fn new(config: ServerConfig) -> Result { + // Obtain our lock_file or fail immediately with an error. + let lock_file = Server::one_grin_at_a_time(&config)?; + // Defaults to None (optional) in config file. // This translates to false here. let archive_mode = match config.archive_mode { @@ -264,6 +300,7 @@ impl Server { ..Default::default() }, stop_state, + lock_file, }) } @@ -451,6 +488,7 @@ impl Server { pub fn stop(&self) { self.p2p.stop(); self.stop_state.lock().stop(); + let _ = self.lock_file.unlock(); } /// Pause the p2p server.