Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From qsu-0.0.2 To qsu-0.0.3
2023-10-25
| ||
14:27 | Remove CbOrigin. Use AppErr for argp errors. check-in: 2190365c01 user: jan tags: trunk | |
2023-10-23
| ||
12:29 | Update index.md. check-in: 0d49c4abb4 user: jan tags: qsu-0.0.3, trunk | |
12:20 | Release maintenance. check-in: ef926ac904 user: jan tags: trunk | |
2023-10-19
| ||
03:59 | Make a note about staticrocket. check-in: a54c7f6793 user: jan tags: trunk | |
03:32 | Category cleanup. check-in: 83c296e121 user: jan tags: qsu-0.0.2, trunk | |
03:29 | Cleanup. check-in: 00799d58af user: jan tags: trunk | |
Changes to .efiles.
1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | + - - - + + + - - - - - - - + + + + + + + + + + + | Cargo.toml README.md www/index.md www/design-notes.md www/changelog.md src/err.rs src/lib.rs src/lumberjack.rs src/rt.rs |
Changes to Cargo.toml.
1 2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | - + + - + - - - + + + - + - + - - - + | [package] name = "qsu" |
︙ | |||
77 78 79 80 81 82 83 | 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | - + - + - + | [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [[example]] name = "hellosvc" |
Changes to examples/err/mod.rs.
︙ | |||
28 29 30 31 32 33 34 35 36 | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | + + + + + + + + + + + + + + + + | } impl From<qsu::Error> for Error { fn from(err: qsu::Error) -> Self { Error::Qsu(err.to_string()) } } /* /// Convenience converter used to pass an application-defined errors from the /// qsu inner runtime back out from the qsu runtime. impl From<Error> for qsu::Error { fn from(err: Error) -> qsu::Error { qsu::Error::app(err) } } */ impl From<Error> for qsu::AppErr { fn from(err: Error) -> qsu::AppErr { qsu::AppErr::new(err) } } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : |
Changes to examples/hellosvc-rocket.rs.
1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | - - + + + + + - - + + + + + - + - + + - + + - + + - + + | #[macro_use] extern crate rocket; mod argp; mod err; mod procres; use qsu::{ |
︙ | |||
91 92 93 94 95 96 97 | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | - + | .expect("Unable to determine default service name"); // Parse, and process, command line arguments. let mut argsproc = argp::AppArgsProc {}; let ap = ArgParser::new(&svcname, &mut argsproc); ap.proc(|| { let handler = Box::new(MyService {}); |
︙ |
Changes to examples/hellosvc-tokio.rs.
1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | - - + + + + - + + - + | //! Simple service that does nothing other than log/trace every N seconds. mod argp; mod err; mod procres; use std::time::{Duration, Instant}; use qsu::{ |
︙ | |||
67 68 69 70 71 72 73 | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | - + + | } } } Ok(()) } |
︙ | |||
91 92 93 94 95 96 97 | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | - + | .expect("Unable to determine default service name"); // Parse, and process, command line arguments. let mut argsproc = argp::AppArgsProc {}; let ap = ArgParser::new(&svcname, &mut argsproc); ap.proc(|| { let handler = Box::new(MyService {}); |
Changes to examples/hellosvc.rs.
1 2 3 4 5 6 7 8 9 10 11 12 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | - - + + + + - + + - + | //! Simple service that does nothing other than log/trace every N seconds. mod argp; mod err; mod procres; use std::{ thread, time::{Duration, Instant} }; use qsu::{ |
︙ | |||
70 71 72 73 74 75 76 | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | - + + | thread::sleep(std::time::Duration::from_secs(1)); } Ok(()) } |
︙ | |||
94 95 96 97 98 99 100 | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | - + | .expect("Unable to determine default service name"); // Parse, and process, command line arguments. let mut argsproc = argp::AppArgsProc {}; let ap = ArgParser::new(&svcname, &mut argsproc); ap.proc(|| { let handler = Box::new(MyService {}); |
Changes to src/argp.rs.
1 2 3 4 5 6 7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | - + + + + + + + + + + + + + | //! Helpers for integrating clap into an application using _qsu_. use clap::{builder::Str, Arg, ArgAction, ArgMatches, Args, Command}; use crate::{ installer::{self, RegSvc}, lumberjack::LogLevel, |
︙ | |||
26 27 28 29 30 31 32 | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | - + - - + - - + | let cli = if let Some(subcmd) = rm_subcmd { let sub = mk_rm_cmd(subcmd, svcname); cli.subcommand(sub) } else { cli }; |
︙ | |||
75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | + + + + + + | #[arg(long, value_enum, hide(true), value_name = "LEVEL")] trace_level: Option<LogLevel>, #[arg(long, value_enum, hide(true), value_name = "FNAME")] trace_file: Option<String> } /// Create a `clap` [`Command`] object that accepts service registration /// arguments. /// /// It is recommended that applications use the higher-level `ArgParser` /// instead, but this call exists in case applications need finer grained /// control. pub fn mk_inst_cmd(cmd: &str, svcname: &str) -> Command { let namearg = Arg::new("svcname") .short('n') .long("name") .action(ArgAction::Set) .value_name("SVCNAME") .default_value(Str::from(svcname.to_string())) |
︙ | |||
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | + + + + + + + + + + + + + | } /// Deregister service. #[derive(Debug, Args)] struct DeregSvcArgs {} /// Create a `clap` [`Command`] object that accepts service deregistration /// arguments. /// /// It is recommended that applications use the higher-level `ArgParser` /// instead, but this call exists in case applications need finer grained /// control. pub fn mk_rm_cmd(cmd: &str, svcname: &str) -> Command { let namearg = Arg::new("svcname") .short('n') .long("name") .action(ArgAction::Set) .value_name("SVCNAME") .default_value(svcname.to_string()) .help("Name of service to remove"); let cli = Command::new(cmd.to_string()).arg(namearg); DeregSvcArgs::augment_args(cli) } /// Parsed service deregistration arguments. pub struct DeregSvc { pub svcname: String } impl DeregSvc { pub fn from_cmd_match(matches: &ArgMatches) -> Self { let svcname = matches.get_one::<String>("svcname").unwrap().to_owned(); Self { svcname } } } /// Run service. #[derive(Debug, Args)] struct RunSvcArgs {} /// Create a `clap` [`Command`] object that accepts service running arguments. /// /// It is recommended that applications use the higher-level `ArgParser` /// instead, but this call exists in case applications need finer grained /// control. pub fn mk_run_cmd(cmd: &str, svcname: &str) -> Command { let namearg = Arg::new("svcname") .short('n') .long("name") .action(ArgAction::Set) .value_name("SVCNAME") .default_value(svcname.to_string()) |
︙ | |||
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + | RunApp(RunCtx), /// Nothing to do (service was probably registered/deregistred). Quit } /// Parsed service running arguments. pub struct RunSvc { pub svcname: String } impl RunSvc { pub fn from_cmd_match(matches: &ArgMatches) -> Self { let svcname = matches.get_one::<String>("svcname").unwrap().to_owned(); Self { svcname } } } /// Allow application to customise behavior of an [`ArgParser`] instance. pub trait ArgsProc { /// Callback allowing application to configure service installation argument /// parser. |
︙ | |||
345 346 347 348 349 350 351 | 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 | + + + + + + + + + + + + + + + - + - + | Ok(ArgpRes::RunApp(RunCtx::new(&self.svcname))) } } } /// Process command line arguments. /// /// Calling this method will initialize the command line parser, parse the /// command line, using the associated [`ArgsProc`] as appropriate to modify /// the argument parser, and then take the appropriate action: /// /// - If a service registration was requested, the service will be registered /// and then the function will return. /// - If a service deregistration was requested, the service will be /// deregistered and then the function will return. /// - If a service run was requested, then set up the service subsystem and /// launch the server application under it. /// - If an application-defined subcommand was called, then process it using /// [`ArgsProc::proc_other()`] and then exit. /// - If none of the above subcommands where issued, then run the server /// application as a foreground process. /// |
︙ |
Changes to src/err.rs.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |
|
︙ | |||
137 138 139 140 141 142 143 144 | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | #[cfg(windows)] impl From<windows_service::Error> for Error { fn from(err: windows_service::Error) -> Self { Error::SubSystem(err.to_string()) } } /// An error type used to store an application-specific error. /// /// Application call-backs return this type for the `Err()` case in order to /// allow the errors to be passed back to the application call that started the /// service runtime. #[repr(transparent)] #[derive(Debug)] pub struct AppErr(Box<dyn Any + Send + 'static>); impl AppErr { pub fn new<E>(e: E) -> Self where E: Send + 'static { Self(Box::new(e)) } /// Attempt to unpack and cast the inner error type. /// /// If it can't be downcast to `E`, `AppErr` will be returned in the `Err()` /// case. /// /// ``` /// use qsu::AppErr; /// /// enum MyErr { /// Something(String) /// } /// let apperr = AppErr::new(MyErr::Something("hello".into())); /// /// let Ok(e) = apperr.try_into_inner::<MyErr>() else { /// panic!("Unexpectedly not MyErr"); /// }; /// ``` pub fn try_into_inner<E: 'static>(self) -> Result<E, AppErr> { match self.0.downcast::<E>() { Ok(e) => Ok(*e), Err(e) => Err(AppErr(e)) } } /// Unwrap application-specific error from an [`Error`](crate::err::Error). /// /// ``` /// use qsu::AppErr; /// /// enum MyErr { /// Something(String) /// } /// let apperr = AppErr::new(MyErr::Something("hello".into())); /// /// let MyErr::Something(e) = apperr.unwrap_inner::<MyErr>() else { /// panic!("Unexpectedly not MyErr::Something"); /// }; /// assert_eq!(e, "hello"); /// ``` /// /// # Panic /// Panics if the inner type is not castable to `E`. pub fn unwrap_inner<E: 'static>(self) -> E { let Ok(e) = self.0.downcast::<E>() else { panic!("Unable to downcast to error type E"); }; *e } } /// Origin of an application callback error. #[derive(Debug)] pub enum CbOrigin { /// The application error occurred in the service handler's `init()` /// callback. Init, /// The application error occurred in the service handler's `run()` /// callback. Run, /// The application error occurred in the service handler's `shutdown()`. /// callback. Shutdown } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : |
Changes to src/installer/winsvc.rs.
1 2 3 4 5 6 7 8 9 10 11 12 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | + - + + | use std::{cell::RefCell, ffi::OsString, thread, time::Duration}; use windows_service::{ service::{ ServiceAccess, ServiceDependency, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceState, ServiceType }, service_manager::{ServiceManager, ServiceManagerAccess} }; use crate::{ err::Error, rt::winsvc::{ |
︙ | |||
123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | + + + | key.set_value("Environment", &envs)?; } //println!("==> Service installation successful"); let mut params = create_service_params(svcname)?; // Just so the uninstaller will accept this service params.set_value("Installer", &"qsu")?; if let Some(wd) = ctx.workdir { params.set_value("WorkDir", &wd)?; } if let Some(ll) = ctx.log_level { params.set_value("LogLevel", &ll.to_string())?; } |
︙ | |||
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | // Mark status as success so the scopeguards won't attempt to reverse the // changes. *status.borrow_mut() = true; Ok(()) } /// Deregister a system service. /// /// # Constraints /// Uninstalling the wrong service can have spectacularly bad side-effects, so /// qsu goes to some lengths to ensure that only services it installed itself /// can be uninstalled. /// /// Before attempting an actual uninstallation, this function will verify that /// under the service's `Parameters` subkey there is an `Installer` key with /// the value `qsu`. pub fn uninstall(svcname: &str) -> Result<(), Error> { // Only allow uninstallation of services that have an Installer=qsu key in // its Parameters subkey. if let Ok(params) = get_service_params_subkey(svcname) { if let Ok(val) = params.get_value::<String, &str>("Installer") { if val != "qsu" { Err(Error::missing( "Refusing to uninstall service that doesn't appear to be installed \ by qsu" ))?; } } else { Err(Error::missing( "Service Parameters does not have a Installer key." ))?; } } else { Err(Error::missing("Service does not have a Parameters subkey."))?; } let manager_access = ServiceManagerAccess::CONNECT; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?; let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE; let service = service_manager.open_service(&svcname, service_access)?; |
︙ |
Changes to src/lib.rs.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - - - - - - - + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + + - - - + - + - - - - - - |
|
Changes to src/lumberjack.rs.
︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | + + + + + + + + + + + + | #[cfg(windows)] WinEvtLog { svcname: String } } /// Logging and tracing initialization. pub struct LumberJack { init: bool, log_out: LogOut, log_level: LogLevel, trace_level: LogLevel, //log_file: Option<PathBuf>, trace_file: Option<PathBuf> } impl Default for LumberJack { /// Create a default log/trace initialization. /// /// This will set the `log` log level to the value of the `LOG_LEVEL` /// environment variable, or default to `warm` (if either not set or /// invalid). /// /// The `tracing` trace level will use the environment variable `TRACE_LEVEL` /// in a similar manner, but defaults to `off`. /// /// If the environment variable `TRACE_FILE` is set the value will be the /// used as the file name to write the trace logs to. fn default() -> Self { let log_level = if let Ok(level) = std::env::var("LOG_LEVEL") { if let Ok(level) = level.parse::<LogLevel>() { level } else { LogLevel::Warn } |
︙ | |||
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | + + + + + + + + + + + + + + + + + - + | LogLevel::Off } } else { LogLevel::Off }; Self { init: true, log_out: Default::default(), log_level, trace_level, //log_file: None, trace_file } } } impl LumberJack { /// Create a [`LumberJack::default()`] object. pub fn new() -> Self { Self::default() } /// Do not initialize logging/tracing. /// /// This is useful when running tests. pub fn noinit() -> Self { Self { init: false, ..Default::default() } } pub fn set_init(mut self, flag: bool) -> Self { self.init = flag; self } /// Load logging/tracing information from a service Parameters subkey. #[cfg(windows)] pub fn from_winsvc(svcname: &str) -> Result<Self, Error> { |
︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - + + + + | } else { this }; Ok(this) } /// Set the `log` logging level. pub fn log_level(mut self, level: LogLevel) -> Self { self.log_level = level; self } /// Set the `tracing` log level. pub fn trace_level(mut self, level: LogLevel) -> Self { self.trace_level = level; self } /// Set a file to which `tracing` log entries are written (rather than to /// write to console). pub fn trace_file<P>(mut self, fname: P) -> Self where P: AsRef<Path> { self.trace_file = Some(fname.as_ref().to_path_buf()); self } /// Commit requested settings to `log` and `tracing`. pub fn init(self) -> Result<(), Error> { if self.init { |
︙ |
Deleted src/nosvc.rs.
| - - - - - - - - - - - - - - - - - - - |
|
Added src/rt.rs.