cyfs_util/process/
cmd_exec.rs

1use super::process_lock::ProcessLock;
2use super::process_mutex::{ProcessMutex, CURRENT_PROC_LOCK, SERVICE_NAME};
3use clap::{App, Arg, ArgMatches};
4use std::error::Error;
5
6pub trait ProcessCmdFuncs: Send + Sync {
7    fn exit_process(&self, action: ProcessAction, code: i32) -> !;
8}
9
10struct DefaultProcessCmdFuncs;
11
12impl ProcessCmdFuncs for DefaultProcessCmdFuncs {
13    fn exit_process(&self, _action: ProcessAction, code: i32) -> ! {
14        std::process::exit(code)
15    }
16}
17
18static PROCESS_CMD_FUNCS: once_cell::sync::OnceCell<Box<dyn ProcessCmdFuncs>> =
19    once_cell::sync::OnceCell::new();
20
21pub fn get_process_cmd_funcs() -> &'static Box<dyn ProcessCmdFuncs> {
22    PROCESS_CMD_FUNCS.get_or_init(|| Box::new(DefaultProcessCmdFuncs {}))
23}
24
25pub fn set_process_cmd_funcs(
26    funcs: Box<dyn ProcessCmdFuncs>,
27) -> Result<(), Box<dyn ProcessCmdFuncs>> {
28    PROCESS_CMD_FUNCS.set(funcs)
29}
30
31#[derive(Debug, Eq, PartialEq, Clone, Copy)]
32pub enum ProcessAction {
33    Start,
34    Stop,
35    Status,
36    Install,
37}
38
39#[derive(Debug)]
40pub enum ProcessStatusCode {
41    NotExists = 0,
42    Running = 1,
43    RunningOther = 2,
44}
45
46impl ProcessStatusCode {
47    pub fn is_running_other(exit_code: i32) -> bool {
48        if exit_code == Self::RunningOther as i32 {
49            true
50        } else {
51            // compatible with old return values, which is -1 on windows and 255 on nix
52            if exit_code == -1 || exit_code == 255 {
53                true
54            } else {
55                false
56            }
57        }
58    }
59}
60pub fn prepare_args<'a, 'b>(args: App<'a, 'b>) -> App<'a, 'b> {
61    args.arg(
62        Arg::with_name("start")
63            .long("start")
64            .takes_value(false)
65            .help("Start service"),
66    )
67    .arg(
68        Arg::with_name("stop")
69            .long("stop")
70            .takes_value(false)
71            .help("Stop service that if service still running"),
72    )
73    .arg(
74        Arg::with_name("status")
75            .long("status")
76            .takes_value(false)
77            .help("Check service is running or not"),
78    )
79    .arg(
80        Arg::with_name("install")
81            .long("install")
82            .takes_value(false)
83            .help("Install service when first install or update"),
84    )
85    .arg(
86        Arg::with_name("fid")
87            .long("fid")
88            .takes_value(true)
89            .help("Current service package fid when check status"),
90    )
91    .arg(
92        Arg::with_name("console-log")
93            .long("console-log")
94            .takes_value(false)
95            .help("Use console log instead"),
96    )
97    .arg(
98        Arg::with_name("cmd-log")
99            .long("cmd-log")
100            .takes_value(false)
101            .help("Output full cmd log to {service-name}-{cmd} file"),
102    )
103}
104
105pub fn parse_cmd(service_name: &str, matches: &ArgMatches) -> (ProcessAction, Option<String>) {
106    let mut fid: Option<String> = None;
107    let mut cmd = ProcessAction::Start;
108
109    if matches.is_present("status") {
110        cmd = ProcessAction::Status;
111
112        match matches.value_of("fid") {
113            Some(v) => fid = Some(v.to_string()),
114            None => {
115                warn!(
116                    "fid param not specified with check status!!! service={}",
117                    service_name
118                );
119            }
120        }
121    } else if matches.is_present("stop") {
122        cmd = ProcessAction::Stop;
123    } else if matches.is_present("install") {
124        cmd = ProcessAction::Install;
125    }
126
127    (cmd, fid)
128}
129
130// 检测当前进程路径是不是匹配fid
131fn check_current_fid(fid: &str) -> Result<bool, Box<dyn Error>> {
132    let fid = fid.to_owned();
133
134    let path = match std::env::current_exe() {
135        Ok(v) => v,
136        Err(e) => {
137            let msg = format!("get current_exe failed! err={}", e);
138            error!("{}", msg);
139
140            return Err(Box::<dyn Error>::from(msg));
141        }
142    };
143
144    let path_str = match path.to_str() {
145        Some(v) => v.to_owned(),
146        None => {
147            let msg = format!("get path str failed! path={}", path.display());
148            error!("{}", msg);
149
150            return Err(Box::<dyn Error>::from(msg));
151        }
152    };
153
154    Ok(match path_str.find(&fid) {
155        Some(_) => {
156            debug!("fid found in exe path! fid={}, path={}", fid, path_str);
157            true
158        }
159        None => {
160            warn!("fid not found in exe path! fid={}, path={}", fid, path_str);
161            false
162        }
163    })
164}
165
166static PROCESS_LOCK: once_cell::sync::OnceCell<ProcessLock> = once_cell::sync::OnceCell::new();
167
168// 检查一个服务|APP的状态
169pub fn check_process_status(service_name: &str, fid: Option<&str>) -> ProcessStatusCode {
170    if ProcessMutex::new(service_name).acquire().is_some() {
171        ProcessStatusCode::NotExists
172    } else {
173        let mut proc = ProcessLock::new(service_name);
174        let exit_code = proc.check();
175        if exit_code > 0 {
176            info!(
177                "target process in running! service={}, pid={}",
178                service_name, exit_code
179            );
180
181            // 如果fid存在,那么检测是否是当前版本
182            if fid.is_some() {
183                match proc.check_fid(fid.unwrap()) {
184                    Ok(valid) => {
185                        if valid {
186                            ProcessStatusCode::Running
187                        } else {
188                            ProcessStatusCode::RunningOther
189                        }
190                    }
191                    Err(_e) => {
192                        // 检测出错,那么都认为是running
193                        ProcessStatusCode::Running
194                    }
195                }
196            } else {
197                ProcessStatusCode::Running
198            }
199        } else {
200            info!(
201                "target process mutex exists but plock not found! service={}, pid={}",
202                service_name, exit_code
203            );
204            ProcessStatusCode::NotExists
205        }
206    }
207}
208
209pub fn check_cmd_and_exec(service_name: &str) -> ProcessAction {
210    check_cmd_and_exec_ext(service_name, service_name)
211}
212
213pub fn check_cmd_and_exec_ext(service_name: &str, mutex_name: &str) -> ProcessAction {
214    let about = format!("{} ood service for cyfs system", service_name);
215    let app = App::new(&format!("{}", service_name))
216        .version(cyfs_base::get_version())
217        .about(&*about);
218
219    let app = prepare_args(app);
220    let matches = app.get_matches();
221
222    check_cmd_and_exec_with_args_ext(service_name, mutex_name, &matches)
223}
224
225pub fn check_cmd_and_exec_with_args(service_name: &str, matches: &ArgMatches) -> ProcessAction {
226    check_cmd_and_exec_with_args_ext(service_name, service_name, matches)
227}
228
229// 通过配置环境变量,让cmd输出日志
230const CYFS_CMD_LOG_KEY: &str = "CYFS_CMD_LOG";
231
232fn load_cmd_level_from_env() -> Option<String> {
233    match std::env::var(CYFS_CMD_LOG_KEY) {
234        Ok(val) => Some(val),
235        Err(_) => None,
236    }
237}
238
239/*
240stop
241> 0 pid 成功停止了正在运行的service
2420 service没有在运行
243< 0 失败
244*/
245pub fn check_cmd_and_exec_with_args_ext(
246    service_name: &str,
247    mutex_name: &str,
248    matches: &ArgMatches,
249) -> ProcessAction {
250    SERVICE_NAME.lock().unwrap().init(mutex_name);
251
252    let (cmd, fid) = parse_cmd(service_name, matches);
253
254    // 如果cmd=start,那么直接使用应用层自己的初始化日志逻辑
255    if cmd != ProcessAction::Start {
256        // 环境变量和命令行都可以控制cmd日志的开启
257        let cmd_log_level = load_cmd_level_from_env();
258
259        if cmd_log_level.is_some() || matches.is_present("cmd-log") {
260            let name = format!("{}-status", service_name);
261            let level = cmd_log_level.unwrap_or("trace".to_owned());
262
263            crate::init_log(&name, Some(&level));
264        } else {
265            let console_log = matches.is_present("console-log");
266            if console_log || (cmd != ProcessAction::Start && cmd != ProcessAction::Install) {
267                if let Err(e) = simple_logger::SimpleLogger::default().init() {
268                    println!("init simple logger error! {}", e);
269                }
270            } else {
271                // start和install模式交给service本身决定,一般使用文件日志
272            }
273        }
274    }
275    // 如果不是启动,那么需要确保几秒后退出
276    if cmd != ProcessAction::Start {
277        std::thread::spawn(move || {
278            std::thread::sleep(std::time::Duration::from_secs(10));
279            let code = ProcessStatusCode::NotExists as i32;
280            error!("process running out of time! now will exit with {}", code);
281            crate::flush_log();
282            get_process_cmd_funcs().exit_process(cmd, code);
283        });
284    }
285
286    match cmd {
287        ProcessAction::Stop => {
288            if *CURRENT_PROC_LOCK {
289                info!("process mutex not exists! service={}", service_name);
290                get_process_cmd_funcs().exit_process(cmd, 0);
291            }
292
293            let exit_code = try_stop_process(service_name);
294            get_process_cmd_funcs().exit_process(cmd, exit_code);
295        }
296        ProcessAction::Status => {
297            // 如果进程锁不存在,那么该进程一定不存在
298            if *CURRENT_PROC_LOCK {
299                info!("process mutex not exists! service={}", service_name);
300                get_process_cmd_funcs().exit_process(cmd, ProcessStatusCode::NotExists as i32);
301            }
302
303            let mut proc = ProcessLock::new(service_name);
304            let exit_code = proc.check();
305            let status;
306            if exit_code > 0 {
307                info!(
308                    "target process in running! service={}, pid={}",
309                    service_name, exit_code
310                );
311
312                if fid.is_some() {
313                    status = match proc.check_fid(&fid.unwrap()) {
314                        Ok(valid) => {
315                            if valid {
316                                ProcessStatusCode::Running
317                            } else {
318                                ProcessStatusCode::RunningOther
319                            }
320                        }
321                        Err(_e) => {
322                            // 检测出错,那么都认为是running
323                            ProcessStatusCode::Running
324                        }
325                    };
326                } else {
327                    status = ProcessStatusCode::Running;
328                }
329            } else {
330                info!(
331                    "target process not found! service={}, pid={}",
332                    service_name, exit_code
333                );
334                status = ProcessStatusCode::NotExists;
335            }
336
337            info!(
338                "check status return, service={}, status={:?}",
339                service_name, status
340            );
341            get_process_cmd_funcs().exit_process(cmd, status as i32);
342        }
343        ProcessAction::Install => {
344            return ProcessAction::Install;
345        }
346        _ => {
347            if !*CURRENT_PROC_LOCK {
348                let msg = format!("process mutex already exists! service={}", service_name);
349                warn!("{}", msg);
350                println!("{}", msg);
351
352                get_process_cmd_funcs().exit_process(cmd, 1);
353            }
354
355            let mut proc = ProcessLock::new(service_name);
356            if let Err(e) = proc.force_acquire() {
357                let msg = format!(
358                    "target process already in running! service={}, pid={}, err={:?}",
359                    service_name,
360                    proc.get_old_pid(),
361                    e
362                );
363                error!("{}", msg);
364                println!("{}", msg);
365
366                get_process_cmd_funcs().exit_process(cmd, 1);
367            }
368
369            if let Err(_) = PROCESS_LOCK.set(proc) {
370                unreachable!();
371            }
372
373            return ProcessAction::Start;
374        }
375    }
376}
377
378// 只通过进程锁检查一个进程是否存在
379pub fn check_process_mutex(service_name: &str) -> bool {
380    let mutex = ProcessMutex::new(&service_name);
381    let ret = match mutex.acquire() {
382        Some(g) => {
383            drop(g);
384            false
385        }
386        None => true,
387    };
388
389    ret
390}
391
392// 不检查进程锁,直接读取进程pid文件并尝试终止
393pub fn try_stop_process(service_name: &str) -> i32 {
394    let mut proc = ProcessLock::new(service_name);
395    let pid = proc.check();
396    if pid > 0 {
397        info!(
398            "target process in running! now will kill, service={}, pid={}",
399            service_name, pid
400        );
401
402        let exit_code = match proc.kill() {
403            true => pid as i32,
404            false => -1i32,
405        };
406
407        exit_code
408    } else {
409        info!(
410            "process in running but pid file not exists! {}",
411            service_name
412        );
413        -1
414    }
415}
416
417// 尝试获取指定的进程锁,如果成功则创建对应的pid文件
418pub fn try_enter_proc(service_name: &str) -> bool {
419    SERVICE_NAME.lock().unwrap().init(service_name);
420
421    if !*CURRENT_PROC_LOCK {
422        info!("process mutex already exists! service={}", service_name);
423        return false;
424    }
425
426    let mut proc = ProcessLock::new(service_name);
427    if let Err(e) = proc.force_acquire() {
428        error!(
429            "target process already in running! service={}, pid={}, err={:?}",
430            service_name,
431            proc.get_old_pid(),
432            e
433        );
434
435        return false;
436    }
437
438    if let Err(_) = PROCESS_LOCK.set(proc) {
439        unreachable!();
440    }
441
442    true
443}