1use std::fs::{self, File, Permissions};
6use std::io::{BufRead, BufReader, BufWriter, Write};
7use std::path::{Path, PathBuf};
8use std::os::unix::fs::PermissionsExt;
9
10use anyhow::Result;
11use zeroize::Zeroize;
12
13use psh::{PshStore, ZeroizingString};
14
15pub const DB_FILE: &str = ".psh.db";
17const DEBUG_DB_PATH: &str = "/tmp/psh.db";
18
19pub struct PshDb {
21 path: PathBuf,
22}
23
24impl PshDb {
25 pub fn new(path: &Path) -> Self {
34 let db_path =
35 if path.has_root() {
36 path.to_path_buf()
37 } else {
38 let mut db_path = home::home_dir()
39 .expect("User has no home directory");
40 db_path.push(path);
41 db_path
42 };
43
44 Self {
45 path: db_path,
46 }
47 }
48
49 fn tmp_path(&self) -> PathBuf {
50 let db_path = self.path.clone();
51 let mut db_tmp_path = db_path.into_os_string();
52 db_tmp_path.push(".tmp");
53 db_tmp_path.into()
54 }
55}
56
57impl Default for PshDb {
58 fn default() -> Self {
59 let mut db_path = home::home_dir()
60 .expect("User has no home directory");
61 db_path.push(DB_FILE);
62
63 if cfg!(debug_assertions) {
65 db_path = PathBuf::from(DEBUG_DB_PATH);
66 }
67
68 Self::new(&db_path)
69 }
70}
71
72impl PshStore for PshDb {
73 fn exists(&self) -> bool {
74 if self.path.exists() {
75 let metadata = fs::metadata(&self.path).unwrap();
76 if metadata.len() > 0 {
77 return true;
78 }
79 }
80 false
81 }
82
83 fn records(&self) -> Box<dyn Iterator<Item=ZeroizingString>> {
84 if self.exists() {
85 let db = File::open(&self.path).expect("Unable to open file for reading");
86 let reader = BufReader::new(db);
87 Box::new(PshDbIter { reader })
88 } else {
89 Box::new(PshDbIter { reader: std::io::empty() })
90 }
91 }
92
93 fn append(&mut self, record: &ZeroizingString) -> Result<()> {
94 let mut db = File::options().create(true).append(true).open(&self.path)?;
95 let user_only_perms = Permissions::from_mode(0o600);
96 db.set_permissions(user_only_perms)?;
97
98 let mut record = record.to_string();
99 record.push('\n');
100 db.write_all(record.as_bytes())?;
101 record.zeroize();
102
103 Ok(())
104 }
105
106 fn delete(&mut self, record: &ZeroizingString) -> Result<()> {
107 let db = File::open(&self.path)?;
108 let db_temp = File::create(self.tmp_path())?;
109 let user_only_perms = Permissions::from_mode(0o600);
110 db_temp.set_permissions(user_only_perms)?;
111
112 let mut reader = BufReader::new(&db);
113 let mut writer = BufWriter::new(&db_temp);
114
115 let mut buf = String::new();
116 loop {
117 match reader.read_line(&mut buf) {
118 Ok(0) => break,
119 Ok(_) => {
120 if **record != buf.trim() {
121 writeln!(writer, "{}", buf.trim())?;
122 }
123 buf.zeroize();
124 }
125 Err(e) => panic!("Failed to read from file: {}", e),
126 }
127 }
128 buf.zeroize();
129
130 fs::rename(self.tmp_path(), &self.path)?;
131
132 Ok(())
133 }
134}
135
136struct PshDbIter<T: BufRead> {
137 reader: T,
138}
139
140impl<T: BufRead> Iterator for PshDbIter<T> {
141 type Item = ZeroizingString;
142
143 fn next(&mut self) -> Option<Self::Item> {
144 let mut buf = String::new();
145 match self.reader.read_line(&mut buf) {
146 Ok(0) => None,
147 Ok(_) => {
148 let item = Some(ZeroizingString::new(buf.trim().to_string()));
149 buf.zeroize();
150 item
151 }
152 Err(e) => panic!("Failed to read from file: {}", e),
153 }
154 }
155}