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
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Colin Warren <me@zv.ms>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

/* last synced with: unlink (GNU coreutils) 8.21 */

// spell-checker:ignore (ToDO) lstat IFLNK IFMT IFREG

#[macro_use]
extern crate uucore;

use getopts::Options;
use libc::{lstat, stat, unlink};
use libc::{S_IFLNK, S_IFMT, S_IFREG};
use std::ffi::CString;
use std::io::{Error, ErrorKind};

static NAME: &str = "unlink";
static VERSION: &str = env!("CARGO_PKG_VERSION");

pub fn uumain(args: impl uucore::Args) -> i32 {
    let args = args.collect_str();

    let mut opts = Options::new();

    opts.optflag("h", "help", "display this help and exit");
    opts.optflag("V", "version", "output version information and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => crash!(1, "invalid options\n{}", f),
    };

    if matches.opt_present("help") {
        println!("{} {}", NAME, VERSION);
        println!();
        println!("Usage:");
        println!("  {} [FILE]... [OPTION]...", NAME);
        println!();
        println!("{}", opts.usage("Unlink the file at [FILE]."));
        return 0;
    }

    if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
        return 0;
    }

    if matches.free.is_empty() {
        crash!(
            1,
            "missing operand\nTry '{0} --help' for more information.",
            NAME
        );
    } else if matches.free.len() > 1 {
        crash!(
            1,
            "extra operand: '{1}'\nTry '{0} --help' for more information.",
            NAME,
            matches.free[1]
        );
    }

    let c_string = CString::new(matches.free[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0.

    let st_mode = {
        #[allow(deprecated)]
        let mut buf: stat = unsafe { std::mem::uninitialized() };
        let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) };

        if result < 0 {
            crash!(
                1,
                "Cannot stat '{}': {}",
                matches.free[0],
                Error::last_os_error()
            );
        }

        buf.st_mode & S_IFMT
    };

    let result = if st_mode != S_IFREG && st_mode != S_IFLNK {
        Err(Error::new(
            ErrorKind::Other,
            "Not a regular file or symlink",
        ))
    } else {
        let result = unsafe { unlink(c_string.as_ptr()) };

        if result < 0 {
            Err(Error::last_os_error())
        } else {
            Ok(())
        }
    };

    match result {
        Ok(_) => (),
        Err(e) => {
            crash!(1, "cannot unlink '{0}': {1}", matches.free[0], e);
        }
    }

    0
}