• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Achiefs / fim / 14275166216

04 Apr 2025 10:13PM UTC coverage: 84.711% (-0.4%) from 85.125%
14275166216

push

github

web-flow
Merge pull request #194 from Achiefs/177-db-hash

Added Hash diff scanner

353 of 433 new or added lines in 12 files covered. (81.52%)

4 existing lines in 1 file now uncovered.

1435 of 1694 relevant lines covered (84.71%)

1.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.64
/src/hashscanner.rs
1
// Copyright (C) 2024, Achiefs.
2

3
use crate::db;
4
use crate::dbfile::*;
5
use crate::appconfig::AppConfig;
6
use crate::hashevent;
7
use crate::hashevent::HashEvent;
8
use crate::utils;
9

10
use walkdir::WalkDir;
11
use log::*;
12
use std::collections::HashSet;
13
use std::time::Duration;
14
use std::thread;
15
use tokio::runtime::Runtime;
16

17
#[cfg(test)]
18
mod test;
19

20
// ----------------------------------------------------------------------------
21

22
pub fn scan_path(cfg: AppConfig, root: String) {
1✔
23
    let db = db::DB::new(&cfg.hashscanner_file);
2✔
24
    for res in WalkDir::new(root) {
4✔
25
        let entry = res.unwrap();
1✔
26
        let metadata = entry.metadata().unwrap();
2✔
27
        let path = entry.path();
1✔
28
        if metadata.clone().is_file(){
1✔
29
            let dbfile = DBFile::new(cfg.clone(), path.to_str().unwrap(), None);
1✔
30
            db.insert_file(dbfile);
1✔
31
        }
32
    }
33
}
34

35
// ----------------------------------------------------------------------------
36

37
/// This function iterate over the files on `root` directory
38
/// If hash or permissions of a file change it should trigger a HashEvent
39
/// Just in case the first scan after reboot or a hash change between scans
40
/// It also updates the DBFile definition in the DB
41
pub async fn check_path(cfg: AppConfig, root: String, first_scan: bool) {
4✔
42
    let db = db::DB::new(&cfg.hashscanner_file);
2✔
43
    for res in WalkDir::new(root) {
4✔
44
        let entry = res.unwrap();
1✔
45
        let metadata = entry.metadata().unwrap();
1✔
46
        let path = entry.path();
1✔
47

48
        if metadata.clone().is_file(){
1✔
49
            let result = db.get_file_by_path(String::from(path.to_str().unwrap()));
1✔
50
            match result {
1✔
51
                Ok(dbfile) => {
1✔
52
                    let hash = dbfile.get_file_hash(cfg.clone());
2✔
53
                    let permissions = utils::get_unix_permissions(&dbfile.path);
2✔
54
                    if dbfile.hash != hash {
1✔
55
                        debug!("The file '{}' checksum has changed.", path.display());
3✔
56
                        let current_dbfile = db.update_file(cfg.clone(), dbfile.clone());
1✔
57
                        match current_dbfile {
1✔
58
                            Some(data) => {
1✔
59
                                let event = HashEvent::new(Some(dbfile), data, String::from(hashevent::WRITE));
1✔
60
                                event.process(cfg.clone()).await;
3✔
61
                            },
NEW
62
                            None => warn!("Could not update file checksum information in database, file: '{}'", path.display())
×
63
                        }
NEW
64
                    } else if dbfile.permissions != permissions {
×
NEW
65
                        debug!("The file '{}' permissions have changed.", path.display());
×
NEW
66
                        let current_dbfile = db.update_file(cfg.clone(), dbfile.clone());
×
NEW
67
                        match current_dbfile {
×
NEW
68
                            Some(data) => {
×
NEW
69
                                let event = HashEvent::new(Some(dbfile), data, String::from(hashevent::WRITE));
×
NEW
70
                                event.process(cfg.clone()).await;
×
71
                            },
NEW
72
                            None => warn!("Could not update file permissions information in database, file: '{}'", path.display())
×
73
                        }
74
                    }
75
                },
NEW
76
                Err(e) => {
×
NEW
77
                    if e.kind() == "DBFileNotFoundError" {
×
NEW
78
                        debug!("New file '{}' found in directory.", path.display());
×
NEW
79
                        let dbfile = DBFile::new(cfg.clone(), path.to_str().unwrap(), None);
×
NEW
80
                        db.insert_file(dbfile.clone());
×
81
                        // Only trigger new file event in case it is a first scan else monitor will notify.
NEW
82
                        if first_scan {
×
NEW
83
                            let event = HashEvent::new(None, dbfile, String::from(hashevent::CREATE));
×
NEW
84
                            event.process(cfg.clone()).await;
×
85
                        }
86
                    } else {
NEW
87
                        error!("Could not get file '{}' information from database, Error: {:?}", path.display(), e)
×
88
                    }
89
                }
90
            };
91
        }
92
    }
93
}
94

95
// ----------------------------------------------------------------------------
96

97
/// This function update the DB in case files were removed from given path
98
/// In case changes were detected, it trigger hashEvents on first scan after reboot
99
pub async fn update_db(cfg: AppConfig, root: String, first_scan: bool) {
4✔
100
    let db = db::DB::new(&cfg.hashscanner_file);
2✔
101

102
    let db_list = db.get_file_list(root.clone());
2✔
103
    let path_list = utils::get_fs_list(root);
1✔
104

105
    let path_set: HashSet<_> = path_list.iter().collect();
2✔
106
    let diff: Vec<_> = db_list.iter().filter(|item| !path_set.contains(&item.path)).collect();
4✔
107

108
    for file in diff {
4✔
109
        let dbfile = DBFile {
110
            id: file.id.clone(),
1✔
111
            timestamp: file.timestamp.clone(),
1✔
112
            hash: file.hash.clone(),
1✔
113
            path: file.path.clone(),
1✔
114
            size: file.size,
1✔
115
            permissions: file.permissions
1✔
116
        };
117
        let result = db.delete_file(dbfile.clone());
1✔
118
        match result {
1✔
119
            Ok(_v) => {
1✔
120
                // Only trigger delete file event in case it is a first scan else monitor will notify.
121
                if first_scan {
1✔
122
                    let event = HashEvent::new(None, dbfile, String::from(hashevent::REMOVE));
1✔
123
                    event.process(cfg.clone()).await;
2✔
124
                }
125
                debug!("File {} deleted from databse", file.path)
2✔
126
            },
NEW
127
            Err(e) => error!("Could not delete file {} from database, error: {:?}", file.path, e)
×
128
        }
129
    }
130
}
131

132
// ----------------------------------------------------------------------------
133

134
#[cfg(not(tarpaulin_include))]
135
pub fn scan(cfg: AppConfig) {
136
    let db = db::DB::new(&cfg.hashscanner_file);
137
    let rt = Runtime::new().unwrap();
138
    let interval = cfg.clone().hashscanner_interval;
139
    let mut first_scan = true;
140
    debug!("Starting file scan to create hash database.");
141

142
    let config_paths = match cfg.clone().engine.as_str() {
143
        "audit" => cfg.clone().audit,
144
        _ => cfg.clone().monitor,
145
    };
146

147
    loop{
148

149
        for element in config_paths.clone() {
150
            let path = String::from(element["path"].as_str().unwrap());
151
            if db.is_empty() {
152
                scan_path(cfg.clone(), path.clone());
153
            } else {
154
                rt.block_on(check_path(cfg.clone(), path.clone(), first_scan));
155
                rt.block_on(update_db(cfg.clone(), path.clone(), first_scan));
156
                first_scan = false;
157
            }
158
            debug!("Path '{}' scanned all files are hashed in DB.", path.clone());
159
        }
160

161
        debug!("Sleeping HashScanner thread for {} minutes", interval.clone());
162
        thread::sleep(Duration::from_secs(interval.try_into().unwrap()));
163
    }
164

165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc