libfs/
common.rs

1/*
2 * Copyright © 2018, Steve Smith <tarkasteve@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License version
6 * 3 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 */
16
17
18use log::{debug, warn};
19use rustix::fs::{fsync, ftruncate};
20use rustix::io::{pread, pwrite};
21use std::cmp;
22use std::fs::{File, FileTimes};
23use std::io::{ErrorKind, Read, Write};
24use std::os::unix::fs::{fchown, MetadataExt};
25use std::path::Path;
26use xattr::FileExt;
27
28use crate::errors::{Result, Error};
29use crate::{Extent, XATTR_SUPPORTED, copy_sparse, probably_sparse, copy_file_bytes};
30
31fn copy_xattr(infd: &File, outfd: &File) -> Result<()> {
32    // FIXME: Flag for xattr.
33    if XATTR_SUPPORTED {
34        debug!("Starting xattr copy...");
35        for attr in infd.list_xattr()? {
36            if let Some(val) = infd.get_xattr(&attr)? {
37                debug!("Copy xattr {attr:?}");
38                outfd.set_xattr(attr, val.as_slice())?;
39            }
40        }
41    }
42    Ok(())
43}
44
45/// Copy file permissions. Will also copy
46/// [xattr](https://man7.org/linux/man-pages/man7/xattr.7.html)'s if
47/// possible.
48pub fn copy_permissions(infd: &File, outfd: &File) -> Result<()> {
49    let xr = copy_xattr(infd, outfd);
50    if let Err(e) = xr {
51        // FIXME: We don't have a way of detecting if the
52        // target FS supports XAttr, so assume any error is
53        // "Unsupported" for now.
54        warn!("Failed to copy xattrs from {infd:?}: {e}");
55    }
56
57    // FIXME: ACLs, selinux, etc.
58
59    let inmeta = infd.metadata()?;
60
61    debug!("Performing permissions copy");
62    outfd.set_permissions(inmeta.permissions())?;
63
64    Ok(())
65}
66
67/// Copy file timestamps.
68pub fn copy_timestamps(infd: &File, outfd: &File) -> Result<()> {
69    let inmeta = infd.metadata()?;
70
71    debug!("Performing timestamp copy");
72    let ftime = FileTimes::new()
73        .set_accessed(inmeta.accessed()?)
74        .set_modified(inmeta.modified()?);
75    outfd.set_times(ftime)?;
76
77    Ok(())
78}
79
80pub fn copy_owner(infd: &File, outfd: &File) -> Result<()> {
81    let inmeta = infd.metadata()?;
82    fchown(outfd, Some(inmeta.uid()), Some(inmeta.gid()))?;
83
84    Ok(())
85}
86
87pub(crate) fn read_bytes(fd: &File, buf: &mut [u8], off: usize) -> Result<usize> {
88    Ok(pread(fd, buf, off as u64)?)
89}
90
91pub(crate) fn write_bytes(fd: &File, buf: &mut [u8], off: usize) -> Result<usize> {
92    Ok(pwrite(fd, buf, off as u64)?)
93}
94
95/// Copy a block of bytes at an offset between files. Uses Posix pread/pwrite.
96pub(crate) fn copy_range_uspace(reader: &File, writer: &File, nbytes: usize, off: usize) -> Result<usize> {
97    // FIXME: For larger buffers we should use a pre-allocated thread-local?
98    let mut buf = vec![0; nbytes];
99
100    let mut written: usize = 0;
101    while written < nbytes {
102        let next = cmp::min(nbytes - written, nbytes);
103        let noff = off + written;
104
105        let rlen = match read_bytes(reader, &mut buf[..next], noff) {
106            Ok(0) => return Err(Error::InvalidSource("Source file ended prematurely.")),
107            Ok(len) => len,
108            Err(e) => return Err(e),
109        };
110
111        let _wlen = match write_bytes(writer, &mut buf[..rlen], noff) {
112            Ok(len) if len < rlen => {
113                return Err(Error::InvalidSource("Failed write to file."))
114            }
115            Ok(len) => len,
116            Err(e) => return Err(e),
117        };
118
119        written += rlen;
120    }
121    Ok(written)
122}
123
124/// Slightly modified version of io::copy() that only copies a set amount of bytes.
125pub(crate) fn copy_bytes_uspace(mut reader: &File, mut writer: &File, nbytes: usize) -> Result<usize> {
126    let mut buf = vec![0; nbytes];
127
128    let mut written = 0;
129    while written < nbytes {
130        let next = cmp::min(nbytes - written, nbytes);
131        let len = match reader.read(&mut buf[..next]) {
132            Ok(0) => return Err(Error::InvalidSource("Source file ended prematurely.")),
133            Ok(len) => len,
134            Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
135            Err(e) => return Err(e.into())
136        };
137        writer.write_all(&buf[..len])?;
138        written += len;
139    }
140    Ok(written)
141}
142
143/// Allocate file space on disk. Uses Posix ftruncate().
144pub fn allocate_file(fd: &File, len: u64) -> Result<()> {
145    Ok(ftruncate(fd, len)?)
146}
147
148/// Merge any contiguous extents in a list. See [merge_extents].
149pub fn merge_extents(extents: Vec<Extent>) -> Result<Vec<Extent>> {
150    let mut merged: Vec<Extent> = vec![];
151
152    let mut prev: Option<Extent> = None;
153    for e in extents {
154        match prev {
155            Some(p) => {
156                if e.start == p.end + 1 {
157                    // Current & prev are contiguous, merge & see what
158                    // comes next.
159                    prev = Some(Extent {
160                        start: p.start,
161                        end: e.end,
162                        shared: p.shared & e.shared,
163                    });
164                } else {
165                    merged.push(p);
166                    prev = Some(e);
167                }
168            }
169            // First iter
170            None => prev = Some(e),
171        }
172    }
173    if let Some(p) = prev {
174        merged.push(p);
175    }
176
177    Ok(merged)
178}
179
180
181/// Determine if two files are the same by examining their inodes.
182pub fn is_same_file(src: &Path, dest: &Path) -> Result<bool> {
183    let sstat = src.metadata()?;
184    let dstat = dest.metadata()?;
185    let same = (sstat.ino() == dstat.ino())
186        && (sstat.dev() == dstat.dev());
187
188    Ok(same)
189}
190
191/// Copy a file. This differs from [std::fs::copy] in that it looks
192/// for sparse blocks and skips them.
193pub fn copy_file(from: &Path, to: &Path) -> Result<u64> {
194    let infd = File::open(from)?;
195    let len = infd.metadata()?.len();
196
197    let outfd = File::create(to)?;
198    allocate_file(&outfd, len)?;
199
200    let total = if probably_sparse(&infd)? {
201        copy_sparse(&infd, &outfd)?
202    } else {
203        copy_file_bytes(&infd, &outfd, len)? as u64
204    };
205
206    Ok(total)
207}
208
209/// Sync an open file to disk. Uses `fsync(2)`.
210pub fn sync(fd: &File) -> Result<()> {
211    Ok(fsync(fd)?)
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use std::fs::read;
218    use std::ops::Range;
219    use tempfile::tempdir;
220
221    impl From<Range<u64>> for Extent {
222        fn from(r: Range<u64>) -> Self {
223            Extent {
224                start: r.start,
225                end: r.end,
226                shared: false,
227            }
228        }
229    }
230
231    #[test]
232    fn test_copy_bytes_uspace_large() {
233        let dir = tempdir().unwrap();
234        let from = dir.path().join("from.bin");
235        let to = dir.path().join("to.bin");
236        let size = 128 * 1024;
237        let data = "X".repeat(size);
238
239        {
240            let mut fd: File = File::create(&from).unwrap();
241            write!(fd, "{data}").unwrap();
242        }
243
244        {
245            let infd = File::open(&from).unwrap();
246            let outfd = File::create(&to).unwrap();
247            let written = copy_bytes_uspace(&infd, &outfd, size).unwrap();
248
249            assert_eq!(written, size);
250        }
251
252        assert_eq!(from.metadata().unwrap().len(), to.metadata().unwrap().len());
253
254        {
255            let from_data = read(&from).unwrap();
256            let to_data = read(&to).unwrap();
257            assert_eq!(from_data, to_data);
258        }
259    }
260
261    #[test]
262    fn test_copy_range_uspace_large() {
263        let dir = tempdir().unwrap();
264        let from = dir.path().join("from.bin");
265        let to = dir.path().join("to.bin");
266        let size = 128 * 1024;
267        let data = "X".repeat(size);
268
269        {
270            let mut fd: File = File::create(&from).unwrap();
271            write!(fd, "{data}").unwrap();
272        }
273
274        {
275            let infd = File::open(&from).unwrap();
276            let outfd = File::create(&to).unwrap();
277
278            let blocksize = size / 4;
279            let mut written = 0;
280
281            for off in (0..4).rev() {
282                written += copy_range_uspace(&infd, &outfd, blocksize, blocksize * off).unwrap();
283            }
284
285            assert_eq!(written, size);
286        }
287
288        assert_eq!(from.metadata().unwrap().len(), to.metadata().unwrap().len());
289
290        {
291            let from_data = read(&from).unwrap();
292            let to_data = read(&to).unwrap();
293            assert_eq!(from_data, to_data);
294        }
295    }
296
297    #[test]
298    fn test_extent_merge() -> Result<()> {
299        assert_eq!(merge_extents(vec!())?, vec!());
300        assert_eq!(merge_extents(
301            vec!((0..1).into()))?,
302            vec!((0..1).into()));
303
304        assert_eq!(merge_extents(
305            vec!((0..1).into(),
306                (10..20).into()))?,
307            vec!((0..1).into(),
308                (10..20).into()));
309        assert_eq!(merge_extents(
310            vec!((0..10).into(),
311                (11..20).into()))?,
312            vec!((0..20).into()));
313        assert_eq!(
314            merge_extents(
315                vec!((0..5).into(),
316                    (11..20).into(),
317                    (21..30).into(),
318                    (40..50).into()))?,
319            vec!((0..5).into(),
320                (11..30).into(),
321                (40..50).into())
322        );
323        assert_eq!(
324            merge_extents(vec!((0..5).into(),
325                (11..20).into(),
326                (21..30).into(),
327                (40..50).into(),
328                (51..60).into()))?,
329            vec!((0..5).into(),
330                (11..30).into(),
331                (40..60).into())
332        );
333        assert_eq!(
334            merge_extents(
335                vec!((0..10).into(),
336                    (11..20).into(),
337                    (21..30).into(),
338                    (31..50).into(),
339                    (51..60).into()))?,
340            vec!((0..60).into())
341        );
342        Ok(())
343    }
344
345
346    #[test]
347    fn test_copy_file() -> Result<()> {
348        let dir = tempdir()?;
349        let from = dir.path().join("file.bin");
350        let len = 32 * 1024 * 1024;
351
352        {
353            let mut fd = File::create(&from)?;
354            let data = "X".repeat(len);
355            write!(fd, "{data}").unwrap();
356        }
357
358        assert_eq!(len, from.metadata()?.len() as usize);
359
360        let to = dir.path().join("sparse.copy.bin");
361        crate::copy_file(&from, &to)?;
362
363        assert_eq!(len, to.metadata()?.len() as usize);
364
365        Ok(())
366    }
367}