1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
extern crate nc;

use nc::Errno;

use crate::expand_env;

/// Options for `ln()`.
pub struct LnOptions {
    /// Expand environment path.
    pub expand_env: bool,

    /// Remove existing destination files.
    pub force: bool,

    /// Make hard links instead of symbolic links.
    pub physical: bool,

    /// Dereferences `target` that is a symbolic link.
    pub logical: bool,

    /// Make symbolic links instead of hard links.
    pub symbolic: bool,
}

impl Default for LnOptions {
    fn default() -> LnOptions {
        LnOptions {
            expand_env: true,
            force: false,
            physical: true,
            logical: false,
            symbolic: false,
        }
    }
}

/// Make links between files.
/// ```
/// extern crate shell_rs as shell;
/// use shell::{ln, LnOptions};
///
/// let options = LnOptions {
///     logical: true,
///     symbolic: true,
///     ..Default::default()
/// };
/// let ret = ln("/etc/issue", "/tmp/issue", &options);
/// assert_eq!(ret, Ok(()));
/// ```
pub fn ln<T: AsRef<str>>(src: T, target: T, options: &LnOptions) -> Result<(), Errno> {
    let src = if options.expand_env {
        expand_env(src)
    } else {
        src.as_ref().to_string()
    };

    let logical_src: String = if options.logical {
        let mut stat = nc::stat_t::default();
        let _ret = nc::newfstatat(nc::AT_FDCWD, &src, &mut stat, 0)?;
        if stat.st_mode == nc::S_IFLNK {
            let mut buf: Vec<u8> = Vec::with_capacity(nc::PATH_MAX as usize);
            let read_size = nc::readlinkat(nc::AT_FDCWD, &src, &mut buf)?;
            let mut real_src = String::from_utf8_lossy(&buf).into_owned();
            real_src.truncate((read_size + 1) as usize);
            real_src
        } else {
            src
        }
    } else {
        src
    };

    let target = if options.expand_env {
        expand_env(target)
    } else {
        target.as_ref().to_string()
    };

    if options.force {
        let _ret = nc::unlink(&target);
    }

    if options.symbolic {
        return nc::symlink(&logical_src, &target);
    } else {
        return nc::link(&logical_src, &target);
    }
}