2 use std
::path
::{Path, PathBuf}
;
4 use std
::time
::Duration
;
6 use crypto
::digest
::Digest
;
7 use crypto
::sha2
::Sha512Trunc256
;
11 use std
::os
::unix
::io
::AsRawFd
;
15 pub struct ChunkStore
{
16 name
: String
, // used for error reporting
19 hasher
: Sha512Trunc256
,
24 const HEX_CHARS
: &'
static [u8; 16] = b
"0123456789abcdef";
26 pub fn digest_to_hex(digest
: &[u8]) -> String
{
28 let mut buf
= Vec
::<u8>::with_capacity(digest
.len()*2);
30 for i
in 0..digest
.len() {
31 buf
.push(HEX_CHARS
[(digest
[i
] >> 4) as usize]);
32 buf
.push(HEX_CHARS
[(digest
[i
] & 0xf) as usize]);
35 unsafe { String::from_utf8_unchecked(buf) }
38 fn digest_to_prefix(digest
: &[u8]) -> PathBuf
{
40 let mut buf
= Vec
::<u8>::with_capacity(3+1+2+1);
42 buf
.push(HEX_CHARS
[(digest
[0] as usize) >> 4]);
43 buf
.push(HEX_CHARS
[(digest
[0] as usize) &0xf]);
44 buf
.push(HEX_CHARS
[(digest
[1] as usize) >> 4]);
47 buf
.push(HEX_CHARS
[(digest
[1] as usize) & 0xf]);
48 buf
.push(HEX_CHARS
[(digest
[2] as usize) >> 4]);
51 let path
= unsafe { String::from_utf8_unchecked(buf)}
;
59 fn chunk_dir
<P
: AsRef
<Path
>>(path
: P
) -> PathBuf
{
61 let mut chunk_dir
: PathBuf
= PathBuf
::from(path
.as_ref());
62 chunk_dir
.push(".chunks");
67 pub fn create
<P
: Into
<PathBuf
>>(name
: &str, path
: P
) -> Result
<Self, Error
> {
69 let base
: PathBuf
= path
.into();
70 let chunk_dir
= Self::chunk_dir(&base
);
72 if let Err(err
) = std
::fs
::create_dir(&base
) {
73 bail
!("unable to create chunk store '{}' at {:?} - {}", name
, base
, err
);
76 if let Err(err
) = std
::fs
::create_dir(&chunk_dir
) {
77 bail
!("unable to create chunk store '{}' subdir {:?} - {}", name
, chunk_dir
, err
);
82 let mut l1path
= chunk_dir
.clone();
83 l1path
.push(format
!("{:03x}",i
));
84 if let Err(err
) = std
::fs
::create_dir(&l1path
) {
85 bail
!("unable to create chunk store '{}' subdir {:?} - {}", name
, l1path
, err
);
89 Self::open(name
, base
)
92 pub fn open
<P
: Into
<PathBuf
>>(name
: &str, path
: P
) -> Result
<Self, Error
> {
94 let base
: PathBuf
= path
.into();
95 let chunk_dir
= Self::chunk_dir(&base
);
97 if let Err(err
) = std
::fs
::metadata(&chunk_dir
) {
98 bail
!("unable to open chunk store '{}' at {:?} - {}", name
, chunk_dir
, err
);
101 let mut lockfile_path
= base
.clone();
102 lockfile_path
.push(".lock");
104 // make sure only one process/thread/task can use it
105 let lockfile
= tools
::open_file_locked(
106 lockfile_path
, Duration
::from_secs(10))?
;
109 name
: name
.to_owned(),
112 hasher
: Sha512Trunc256
::new(),
114 mutex
: Mutex
::new(false)
118 pub fn touch_chunk(&mut self, digest
:&[u8]) -> Result
<(), Error
> {
120 // fixme: nix::sys::stat::utimensat
121 let mut chunk_path
= self.chunk_dir
.clone();
122 let prefix
= digest_to_prefix(&digest
);
123 chunk_path
.push(&prefix
);
124 let digest_str
= digest_to_hex(&digest
);
125 chunk_path
.push(&digest_str
);
127 std
::fs
::metadata(&chunk_path
)?
;
131 fn sweep_old_files(&self, handle
: &mut nix
::dir
::Dir
) -> Result
<(), Error
> {
133 let rawfd
= handle
.as_raw_fd();
135 let now
= unsafe { libc::time(std::ptr::null_mut()) }
;
137 for entry
in handle
.iter() {
138 let entry
= match entry
{
140 Err(_
) => continue /* ignore */,
142 let file_type
= match entry
.file_type() {
143 Some(file_type
) => file_type
,
144 None
=> bail
!("unsupported file system type on chunk store '{}'", self.name
),
146 if file_type
!= nix
::dir
::Type
::File { continue; }
148 let filename
= entry
.file_name();
149 if let Ok(stat
) = nix
::sys
::stat
::fstatat(rawfd
, filename
, nix
::fcntl
::AtFlags
::AT_SYMLINK_NOFOLLOW
) {
150 let age
= now
- stat
.st_atime
;
151 println
!("FOUND {} {:?}", age
/(3600*24), filename
);
152 if age
/(3600*24) >= 2 {
153 println
!("UNLINK {} {:?}", age
/(3600*24), filename
);
154 let res
= unsafe { libc::unlinkat(rawfd, filename.as_ptr(), 0) }
;
156 let err
= nix
::Error
::last();
157 bail
!("unlink chunk {:?} failed on store '{}' - {}", filename
, self.name
, err
);
165 pub fn sweep_used_chunks(&mut self) -> Result
<(), Error
> {
167 use nix
::fcntl
::OFlag
;
168 use nix
::sys
::stat
::Mode
;
171 let base_handle
= match Dir
::open(
172 &self.chunk_dir
, OFlag
::O_RDONLY
, Mode
::empty()) {
174 Err(err
) => bail
!("unable to open store '{}' chunk dir {:?} - {}",
175 self.name
, self.chunk_dir
, err
),
178 let base_fd
= base_handle
.as_raw_fd();
181 let l1name
= PathBuf
::from(format
!("{:03x}", i
));
182 let mut l1_handle
= match nix
::dir
::Dir
::openat(
183 base_fd
, &l1name
, OFlag
::O_RDONLY
, Mode
::empty()) {
185 Err(err
) => bail
!("unable to open store '{}' dir {:?}/{:?} - {}",
186 self.name
, self.chunk_dir
, l1name
, err
),
189 let l1_fd
= l1_handle
.as_raw_fd();
191 for l1_entry
in l1_handle
.iter() {
192 let l1_entry
= match l1_entry
{
193 Ok(l1_entry
) => l1_entry
,
194 Err(_
) => continue /* ignore errors? */,
196 let file_type
= match l1_entry
.file_type() {
197 Some(file_type
) => file_type
,
198 None
=> bail
!("unsupported file system type on chunk store '{}'", self.name
),
200 if file_type
!= nix
::dir
::Type
::Directory { continue; }
202 let l2name
= l1_entry
.file_name();
203 if l2name
.to_bytes_with_nul()[0] == b'
.' { continue; }
205 let mut l2_handle
= match Dir
::openat(
206 l1_fd
, l2name
, OFlag
::O_RDONLY
, Mode
::empty()) {
209 "unable to open store '{}' dir {:?}/{:?}/{:?} - {}",
210 self.name
, self.chunk_dir
, l1name
, l2name
, err
),
212 self.sweep_old_files(&mut l2_handle
)?
;
218 pub fn insert_chunk(&mut self, chunk
: &[u8]) -> Result
<(bool
, [u8; 32]), Error
> {
221 self.hasher
.input(chunk
);
223 let mut digest
= [0u8; 32];
224 self.hasher
.result(&mut digest
);
225 //println!("DIGEST {}", digest_to_hex(&digest));
227 let mut chunk_path
= self.chunk_dir
.clone();
228 let prefix
= digest_to_prefix(&digest
);
229 chunk_path
.push(&prefix
);
230 let digest_str
= digest_to_hex(&digest
);
231 chunk_path
.push(&digest_str
);
233 let lock
= self.mutex
.lock();
235 if let Ok(metadata
) = std
::fs
::metadata(&chunk_path
) {
236 if metadata
.is_file() {
237 return Ok((true, digest
));
239 bail
!("Got unexpected file type for chunk {}", digest_str
);
243 let mut chunk_dir
= self.chunk_dir
.clone();
244 chunk_dir
.push(&prefix
);
246 if let Err(_
) = std
::fs
::create_dir(&chunk_dir
) { /* ignore */ }
248 let mut tmp_path
= chunk_path
.clone();
249 tmp_path
.set_extension("tmp");
250 let mut f
= std
::fs
::File
::create(&tmp_path
)?
;
253 if let Err(err
) = std
::fs
::rename(&tmp_path
, &chunk_path
) {
254 if let Err(_
) = std
::fs
::remove_file(&tmp_path
) { /* ignore */ }
255 bail
!("Atomic rename failed for chunk {} - {}", digest_str
, err
);
258 println
!("PATH {:?}", chunk_path
);
265 pub fn relative_path(&self, path
: &Path
) -> PathBuf
{
267 let mut full_path
= self.base
.clone();
268 full_path
.push(path
);
272 pub fn base_path(&self) -> PathBuf
{
280 fn test_chunk_store1() {
282 if let Err(_e
) = std
::fs
::remove_dir_all(".testdir") { /* ignore */ }
284 let chunk_store
= ChunkStore
::open(".testdir");
285 assert
!(chunk_store
.is_err());
287 let mut chunk_store
= ChunkStore
::create(".testdir").unwrap();
288 let (exists
, _
) = chunk_store
.insert_chunk(&[0u8, 1u8]).unwrap();
291 let (exists
, _
) = chunk_store
.insert_chunk(&[0u8, 1u8]).unwrap();
295 let chunk_store
= ChunkStore
::create(".testdir");
296 assert
!(chunk_store
.is_err());