tiny_web/sys/
init.rs

1use std::{
2    env,
3    fs::read_to_string,
4    io::ErrorKind,
5    net::{IpAddr, Ipv4Addr, SocketAddr},
6    str::FromStr,
7    sync::Arc,
8};
9
10use toml::{Table, Value};
11
12use crate::fnv1a_64;
13use tiny_web_macro::fnv1a_64 as m_fnv1a_64;
14
15use super::{log::Log, route::Route};
16
17/// Responsible for the IP address that should be accepted.
18///
19/// # Values
20///
21/// * `Any` - Accepts any IP address;
22/// * `IpAddr: IpAddr` - Accepts a specific IP address;
23/// * `UDS` - Accepts only from Unix Domain Socket.
24#[derive(Debug, Clone)]
25pub(crate) enum AcceptAddr {
26    /// Accepts any IP address.
27    Any,
28    /// Accepts a specific IP address;
29    IpAddr(IpAddr),
30    /// Accepts only from Unix Domain Socket.
31    #[cfg(not(target_family = "windows"))]
32    Uds,
33}
34
35/// Responsible for the IP address.
36///
37/// # Values
38///
39/// * `SocketAddr` - Accepts any socket address;
40/// * `UDS: String` - Accepts only from Unix Domain Socket.
41#[derive(Debug, Clone)]
42pub(crate) enum Addr {
43    /// Accepts any IP address.
44    SocketAddr(SocketAddr),
45    /// Accepts only from Unix Domain Socket.
46    #[cfg(not(target_family = "windows"))]
47    Uds(String),
48}
49
50/// Responsible for database configuration data.
51///
52/// # Values
53///
54/// * `engine: DBEngine` - Engine of database;
55/// * `host: String` - Host of database;
56/// * `port: Option<u16>` - Port of database;
57/// * `name: String` - Name of database;
58/// * `user: Option<String>` - Database user;
59/// * `pwd: Option<String>` - Password of database user;
60/// * `sslmode: bool` - Use for sslmode=require when connecting to the database;
61/// * `max: SysCount` - The number of connections that will be used in the pool;
62#[derive(Debug, Clone)]
63pub struct DBConfig {
64    /// Host of database.
65    pub host: String,
66    /// Port of database.
67    pub port: Option<u16>,
68    /// Name of database.
69    pub name: String,
70    /// Database user.
71    pub user: Option<String>,
72    /// Password of database user.
73    pub pwd: Option<String>,
74    /// Use for sslmode=require when connecting to the database
75    pub sslmode: bool,
76    /// The number of connections that will be used in the pool
77    pub max: usize,
78}
79
80/// Describes the server configuration.
81#[derive(Debug, Clone)]
82pub(crate) struct Config {
83    pub is_default: bool,
84    /// Name server from env!("CARGO_PKG_NAME") primary project.
85    pub name: String,
86    /// Description server from env!("CARGO_PKG_DESCRIPTION") primary project.
87    pub desc: String,
88    /// Server version from env!("CARGO_PKG_VERSION") primary project.
89    pub version: String,
90    /// Default language.
91    pub lang: Arc<String>,
92    /// Number of work processes in async operations.
93    pub max: usize,
94    /// The address from which we accept working connections.
95    pub bind_accept: Arc<AcceptAddr>,
96    /// The address of the server that binds clients.
97    pub bind: Addr,
98    /// The address from which we accept connections for managing the server.
99    pub rpc_accept: AcceptAddr,
100    /// IP address from which to bind connections for managing the server.
101    pub rpc: Arc<Addr>,
102    /// Session key
103    pub session: Arc<String>,
104    /// Salt for a crypto functions.
105    pub salt: Arc<String>,
106    /// Database configuration.
107    pub db: Arc<DBConfig>,
108    /// Stop signal
109    pub stop_signal: i64,
110    /// Status signal
111    pub status_signal: i64,
112    /// Default controller for request "/" or default class or default action
113    pub action_index: Arc<Route>,
114    /// Default controller for 404 Not Found
115    pub action_not_found: Arc<Route>,
116    /// Default controller for error_route
117    pub action_err: Arc<Route>,
118}
119
120impl Config {
121    fn default(args: InitArgs) -> Config {
122        let num_cpus = num_cpus::get();
123        let bind_accept = AcceptAddr::Any;
124        let bind = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12500));
125        let rpc_accept = AcceptAddr::IpAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
126        let rpc = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12501));
127
128        let db = DBConfig {
129            host: String::new(),
130            port: None,
131            name: String::new(),
132            user: None,
133            pwd: None,
134            sslmode: false,
135            max: 0,
136        };
137        let log = "tiny.log".to_owned();
138        Log::set_path(log.clone());
139
140        let lang = if let Some(lang) = args.lang { lang } else { "en".to_owned() };
141
142        Config {
143            is_default: true,
144            name: args.name.to_owned(),
145            desc: args.desc.to_owned(),
146            version: args.version.to_owned(),
147            lang: Arc::new(lang),
148            max: num_cpus,
149            bind_accept: Arc::new(bind_accept),
150            bind,
151            rpc_accept,
152            rpc: Arc::new(rpc),
153            session: Arc::new("tinysession".to_owned()),
154            salt: Arc::new("salt".to_owned()),
155            db: Arc::new(db),
156            stop_signal: m_fnv1a_64!("stopsalt"),
157            status_signal: m_fnv1a_64!("statussalt"),
158            action_index: Arc::new(Route::default_index()),
159            action_not_found: Arc::new(Route::default_not_found()),
160            action_err: Arc::new(Route::default_err()),
161        }
162    }
163}
164
165/// Responsible for running mode of server.
166///
167/// # Values
168///
169/// * `Start` - Start the server;
170/// * `Stop` - Stop the server;
171/// * `Status` - Get status from server;
172/// * `Help` - Display a short help on starting the server;
173/// * `Go` - Start the server in interactive mode.
174#[derive(Debug, Clone, PartialEq)]
175pub(crate) enum Mode {
176    /// Start the server.
177    Start,
178    /// Stop the server.
179    Stop,
180    /// Get status from server.
181    Status,
182    /// Display a short help on starting the server.
183    Help,
184    /// Start the server in interactive mode.
185    Go,
186}
187
188/// Describes the server configuration:
189///
190/// # Values
191///
192/// * `mode: Mode` - Running mode of server;
193/// * `conf: Config` - Server configuration;
194/// * `exe_path: String` - The full path to the folder where this executable file is located;
195/// * `exe_file: String` - The full path to this executable file;
196/// * `conf_file: String` - The full path to configuration file;
197/// * `root_path: String` - The full path to the folder where the server was started.
198#[derive(Debug, Clone)]
199pub(crate) struct Init {
200    /// Running mode of server.
201    pub mode: Mode,
202    /// Server configuration.
203    pub conf: Config,
204    /// The full path to the folder where this executable file is located.
205    pub exe_path: Arc<String>,
206    /// The full path to this executable file.
207    pub exe_file: String,
208    /// The full path to the folder where the server was started.
209    pub root_path: Arc<String>,
210}
211
212/// Startup parameters
213#[derive(Debug, Clone)]
214struct InitArgs<'a> {
215    path: Option<String>, // -r start path for searching log file
216    lang: Option<String>, // -l Default lang
217
218    file: Option<&'a str>,
219    check_salt: bool,
220    name: &'a str,
221    version: &'a str,
222    desc: &'a str,
223    allow_no_config: bool,
224}
225
226impl Init {
227    /// Initializes the server configuration
228    /// If the server is running in release mode, the configuration file must be in the same folder as the program.
229    /// If the server is running in debug mode, the configuration file must be in the user's current folder.
230    pub fn new(name: &str, version: &str, desc: &str, allow_no_config: bool) -> Option<Init> {
231        let exe_file = Init::get_current_exe()?;
232
233        #[cfg(not(debug_assertions))]
234        let exe_path = match exe_file.rfind('/') {
235            Some(i) => exe_file[..i].to_owned(),
236            None => {
237                Log::stop(16, Some(exe_file));
238                return None;
239            }
240        };
241        #[cfg(debug_assertions)]
242        let exe_path = match env::current_dir() {
243            Ok(path) => match path.to_str() {
244                Some(path) => path.to_owned(),
245                None => {
246                    Log::stop(16, Some(format!("{:?}", path)));
247                    return None;
248                }
249            },
250            Err(e) => {
251                Log::stop(16, Some(e.to_string()));
252                return None;
253            }
254        };
255
256        let mut args = env::args();
257
258        let conf;
259        let mode;
260        let conf_file;
261        let root_path;
262
263        args.next();
264
265        let mut iar = InitArgs {
266            path: None,
267            lang: None,
268            file: None,
269            check_salt: false,
270            name,
271            version,
272            desc,
273            allow_no_config,
274        };
275
276        // Check first parameter
277        match args.next() {
278            // first parameter is empty
279            None => {
280                mode = Mode::Help;
281                (conf_file, root_path) = Init::check_path(&exe_path, allow_no_config)?;
282                iar.file = conf_file.as_deref();
283
284                conf = Init::load_conf(iar)?;
285            }
286            // first parameter is not empty
287            Some(arg) => {
288                match arg.as_str() {
289                    "start" => mode = Mode::Start,
290                    "stop" => mode = Mode::Stop,
291                    "status" => mode = Mode::Status,
292                    "go" => mode = Mode::Go,
293                    _ => mode = Mode::Help,
294                };
295                if mode != Mode::Help {
296                    iar.check_salt = true;
297
298                    while let Some(command) = args.next() {
299                        if command == "-r" {
300                            match args.next() {
301                                Some(value) => {
302                                    iar.path = Some(value);
303                                }
304                                None => break,
305                            }
306                        } else if command == "-l" {
307                            match args.next() {
308                                Some(value) => {
309                                    if value.len() != 2 {
310                                        Log::warning(81, Some(value));
311                                    } else {
312                                        iar.lang = Some(value);
313                                    }
314                                }
315                                None => break,
316                            }
317                        }
318                    }
319
320                    if let Some(path) = &iar.path {
321                        let file = format!("{}/tiny.toml", path);
322                        root_path = match file.rfind('/') {
323                            Some(i) => file[..i].to_owned(),
324                            None => {
325                                Log::stop(16, Some(file));
326                                return None;
327                            }
328                        };
329                        iar.file = Some(&root_path);
330                    } else {
331                        (conf_file, root_path) = Init::check_path(&exe_path, allow_no_config)?;
332                        iar.file = conf_file.as_deref();
333                    }
334                    conf = Init::load_conf(iar)?;
335                } else {
336                    root_path = String::new();
337                    conf = Config::default(iar);
338                }
339            }
340        };
341
342        Some(Init {
343            mode,
344            conf,
345            exe_file,
346            exe_path: Arc::new(exe_path),
347            root_path: Arc::new(root_path),
348        })
349    }
350
351    /// Get the path to the current executable
352    pub fn get_current_exe() -> Option<String> {
353        let exe = match env::current_exe() {
354            Ok(e) => match e.to_str() {
355                Some(e) => {
356                    if &e[..2] == r"\\" {
357                        if &e[..4] == r"\\?\" {
358                            e[4..].replace('\\', "/")
359                        } else {
360                            Log::stop(12, Some(e.to_string()));
361                            return None;
362                        }
363                    } else {
364                        e.replace('\\', "/")
365                    }
366                }
367                None => {
368                    Log::stop(11, Some(e.to_string_lossy().to_string()));
369                    return None;
370                }
371            },
372            Err(e) => {
373                Log::stop(10, Some(e.to_string()));
374                return None;
375            }
376        };
377        Some(exe)
378    }
379
380    /// Try to read configuration file
381    ///
382    /// # Parameters
383    ///
384    /// * `path: &str` - path to file
385    ///
386    /// # Return
387    ///
388    /// * `Option::None` - file not found;
389    /// * `Option::Some((String, String, String))` - success read configuration file:
390    ///   * `turple.0` - path to configuration file
391    ///   * `turple.1` - root folder
392    fn check_path(path: &str, allow_no_config: bool) -> Option<(Option<String>, String)> {
393        let file = format!("{}/tiny.toml", path);
394        match read_to_string(&file) {
395            Ok(_) => Some((Some(file), path.to_owned())),
396            Err(e) => match e.kind() {
397                ErrorKind::NotFound => {
398                    if allow_no_config {
399                        Some((None, path.to_owned()))
400                    } else {
401                        Log::stop(15, Some(format!("{}. Error: {}", &file, e)));
402                        None
403                    }
404                }
405                _ => {
406                    Log::stop(14, Some(format!("{}. Error: {}", &file, e)));
407                    None
408                }
409            },
410        }
411    }
412
413    /// Responsable for parsing data from configuration file.
414    ///
415    /// # Parameters
416    ///
417    /// * `iar: InitArgs` - Startup parameters
418    ///
419    /// # Return
420    ///
421    /// `Option<Config>` - Option of parsed configuration:
422    ///   * `None` - Configuration contains errors;
423    ///   * `Some(Config)` - is ok.
424    fn load_conf(args: InitArgs) -> Option<Config> {
425        let file = match args.file {
426            Some(file) => file,
427            None => {
428                if args.allow_no_config {
429                    return Some(Config::default(args));
430                } else {
431                    Log::stop(14, None);
432                    return None;
433                }
434            }
435        };
436
437        let text = match read_to_string(file) {
438            Ok(text) => text,
439            Err(e) => {
440                Log::stop(14, Some(format!("{}. Error: {}", &file, e)));
441                return None;
442            }
443        };
444
445        let text = match text.parse::<Table>() {
446            Ok(v) => v,
447            Err(e) => {
448                Log::stop(18, Some(e.to_string()));
449                return None;
450            }
451        };
452
453        let num_cpus = num_cpus::get();
454        let mut num_connections = num_cpus * 3;
455        let mut lang = "en".to_owned();
456        let mut max = num_cpus;
457        let mut bind_accept = AcceptAddr::Any;
458        let mut bind = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12500));
459        let mut rpc_accept = AcceptAddr::IpAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
460        let mut rpc = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12501));
461        let mut session = "tinysession".to_owned();
462        let mut salt = String::new();
463        let mut stop_signal = 0;
464        let mut status_signal = 0;
465
466        let mut db = DBConfig {
467            host: String::new(),
468            port: None,
469            name: String::new(),
470            user: None,
471            pwd: None,
472            sslmode: false,
473            max: num_connections,
474        };
475        let mut action_index = Route::default_index();
476        let mut action_not_found = Route::default_not_found();
477        let mut action_err = Route::default_err();
478
479        if let Some(value) = text.get("log") {
480            if let Value::String(val) = value {
481                Log::set_path(val.clone());
482            } else {
483                Log::warning(61, Some(value.to_string()));
484            }
485        };
486
487        for (key, value) in text {
488            match key.as_str() {
489                "lang" => {
490                    if let Value::String(val) = value {
491                        if val.len() != 2 {
492                            Log::warning(51, Some(val));
493                        } else {
494                            lang = val;
495                        }
496                    } else {
497                        Log::warning(51, Some(value.to_string()));
498                    }
499                }
500                "max" => match value {
501                    Value::String(s) => {
502                        if &s != "auto" {
503                            Log::warning(52, Some(s));
504                        }
505                    }
506                    Value::Integer(i) => match usize::try_from(i) {
507                        Ok(v) => {
508                            if v > 0 {
509                                max = v;
510                                num_connections = v * 3;
511                            } else {
512                                Log::warning(52, Some(v.to_string()));
513                            }
514                        }
515                        Err(e) => {
516                            Log::warning(52, Some(format!("{} {}", i, e)));
517                        }
518                    },
519                    _ => {
520                        Log::warning(52, Some(value.to_string()));
521                    }
522                },
523                "bind_from" => {
524                    if let Value::String(val) = value {
525                        if val.is_empty() {
526                            #[cfg(not(target_family = "windows"))]
527                            {
528                                bind_accept = AcceptAddr::Uds;
529                            }
530                            #[cfg(target_family = "windows")]
531                            {
532                                Log::warning(53, None);
533                            }
534                        } else if val == "any" {
535                            bind_accept = AcceptAddr::Any;
536                        } else {
537                            match IpAddr::from_str(&val) {
538                                Ok(ip) => bind_accept = AcceptAddr::IpAddr(ip),
539                                Err(e) => {
540                                    Log::warning(53, Some(format!("{} ({})", e, val)));
541                                }
542                            };
543                        }
544                    } else {
545                        Log::warning(53, Some(value.to_string()));
546                    }
547                }
548                "bind" => {
549                    if let Value::String(val) = value {
550                        if val.contains(':') {
551                            match SocketAddr::from_str(&val) {
552                                Ok(s) => bind = Addr::SocketAddr(s),
553                                Err(e) => {
554                                    Log::warning(54, Some(format!("{} ({})", e, val)));
555                                }
556                            }
557                        } else {
558                            #[cfg(target_family = "windows")]
559                            {
560                                Log::warning(54, Some(val));
561                            }
562                            #[cfg(not(target_family = "windows"))]
563                            if val.is_empty() || &val[..1] != "/" {
564                                Log::warning(54, None);
565                            } else {
566                                bind = Addr::Uds(val);
567                            }
568                        }
569                    } else {
570                        Log::warning(54, Some(value.to_string()));
571                    }
572                }
573                "rpc_from" => {
574                    if let Value::String(val) = value {
575                        if val.is_empty() {
576                            #[cfg(not(target_family = "windows"))]
577                            {
578                                rpc_accept = AcceptAddr::Uds;
579                            }
580                            #[cfg(target_family = "windows")]
581                            {
582                                Log::warning(53, None);
583                            }
584                        } else if val == "any" {
585                            rpc_accept = AcceptAddr::Any;
586                        } else {
587                            match IpAddr::from_str(&val) {
588                                Ok(ip) => rpc_accept = AcceptAddr::IpAddr(ip),
589                                Err(e) => {
590                                    Log::warning(55, Some(format!("{} ({})", e, val)));
591                                }
592                            };
593                        }
594                    } else {
595                        Log::warning(55, Some(value.to_string()));
596                    }
597                }
598                "rpc" => {
599                    if let Value::String(val) = value {
600                        if val.contains(':') {
601                            match SocketAddr::from_str(&val) {
602                                Ok(s) => rpc = Addr::SocketAddr(s),
603                                Err(e) => {
604                                    Log::warning(56, Some(format!("{} ({})", e, val)));
605                                }
606                            }
607                        } else {
608                            #[cfg(target_family = "windows")]
609                            {
610                                Log::warning(56, Some(val));
611                            }
612                            #[cfg(not(target_family = "windows"))]
613                            if val.is_empty() || &val[..1] != "/" {
614                                Log::warning(56, None);
615                            } else {
616                                rpc = Addr::Uds(val);
617                            }
618                        }
619                    } else {
620                        Log::warning(56, Some(value.to_string()));
621                    }
622                }
623                "session" => {
624                    if let Value::String(val) = value {
625                        session = val;
626                    } else {
627                        Log::warning(71, Some(value.to_string()));
628                    }
629                }
630                "salt" => {
631                    if let Value::String(val) = value {
632                        salt = val;
633                        stop_signal = fnv1a_64(format!("stop{}", &salt).as_bytes());
634                        status_signal = fnv1a_64(format!("status{}", &salt).as_bytes());
635                    } else {
636                        Log::warning(62, Some(value.to_string()));
637                    }
638                }
639                "db_host" => {
640                    if let Value::String(val) = value {
641                        if !val.is_empty() {
642                            db.host = val;
643                        }
644                    } else {
645                        Log::warning(63, Some(value.to_string()));
646                    }
647                }
648                "db_port" => {
649                    if let Value::Integer(i) = value {
650                        match u16::try_from(i) {
651                            Ok(v) => {
652                                if v > 0 {
653                                    db.port = Some(v);
654                                } else {
655                                    Log::warning(57, Some(v.to_string()));
656                                }
657                            }
658                            Err(e) => {
659                                Log::warning(57, Some(format!("{} ({})", e, i)));
660                            }
661                        }
662                    } else {
663                        Log::warning(57, Some(value.to_string()));
664                    }
665                }
666                "db_name" => {
667                    if let Value::String(val) = value {
668                        db.name = val;
669                    } else {
670                        Log::warning(64, Some(value.to_string()));
671                    }
672                }
673                "db_user" => {
674                    if let Value::String(val) = value {
675                        db.user = Some(val);
676                    } else {
677                        Log::warning(65, Some(value.to_string()));
678                    }
679                }
680                "db_pwd" => {
681                    if let Value::String(val) = value {
682                        db.pwd = Some(val);
683                    } else {
684                        Log::warning(66, Some(value.to_string()));
685                    }
686                }
687                "sslmode" => {
688                    if let Value::Boolean(val) = value {
689                        db.sslmode = val;
690                    } else {
691                        Log::warning(67, Some(value.to_string()));
692                    }
693                }
694                "db_max" => match value {
695                    Value::String(s) => {
696                        if &s == "auto" {
697                            db.max = num_connections;
698                        } else {
699                            Log::warning(58, Some(s));
700                        }
701                    }
702                    Value::Integer(i) => match usize::try_from(i) {
703                        Ok(v) => {
704                            if v > 0 {
705                                db.max = v;
706                            } else {
707                                Log::warning(58, Some(v.to_string()));
708                            }
709                        }
710                        Err(e) => {
711                            Log::warning(58, Some(format!("{} {}", i, e)));
712                        }
713                    },
714                    _ => {
715                        Log::warning(58, Some(value.to_string()));
716                    }
717                },
718                "action_index" => {
719                    if let Value::String(val) = &value {
720                        if let Some(route) = Route::parse(val) {
721                            action_index = route;
722                        } else {
723                            Log::warning(75, Some(value.to_string()));
724                        }
725                    } else {
726                        Log::warning(74, Some(value.to_string()));
727                    }
728                }
729                "action_not_found" => {
730                    if let Value::String(val) = &value {
731                        if let Some(route) = Route::parse(val) {
732                            action_not_found = route;
733                        } else {
734                            Log::warning(77, Some(value.to_string()));
735                        }
736                    } else {
737                        Log::warning(76, Some(value.to_string()));
738                    }
739                }
740                "action_err" => {
741                    if let Value::String(val) = &value {
742                        if let Some(route) = Route::parse(val) {
743                            action_err = route;
744                        } else {
745                            Log::warning(79, Some(value.to_string()));
746                        }
747                    } else {
748                        Log::warning(78, Some(value.to_string()));
749                    }
750                }
751                _ => {}
752            }
753        }
754
755        if db.host.is_empty() {
756            Log::stop(59, None);
757            return None;
758        }
759        if args.check_salt && salt.is_empty() {
760            Log::stop(50, None);
761            return None;
762        }
763        if let Some(l) = args.lang {
764            lang = l;
765        }
766
767        let conf = Config {
768            is_default: false,
769            name: args.name.to_owned(),
770            desc: args.desc.to_owned(),
771            version: args.version.to_owned(),
772            lang: Arc::new(lang),
773            max,
774            bind_accept: Arc::new(bind_accept),
775            bind,
776            rpc_accept,
777            rpc: Arc::new(rpc),
778            session: Arc::new(session),
779            salt: Arc::new(salt),
780            db: Arc::new(db),
781            stop_signal,
782            status_signal,
783            action_index: Arc::new(action_index),
784            action_not_found: Arc::new(action_not_found),
785            action_err: Arc::new(action_err),
786        };
787        Some(conf)
788    }
789}