bdrck 0.22.5

Generic common foundational utilities.
Documentation
// Copyright 2015 Axel Rasmussen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::fs::*;
use crate::testing::temp;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;

#[test]
fn test_path_bytes_round_trip() {
    crate::init().unwrap();

    let expected_path = PathBuf::from("/tmp/test_path");
    let bytes = path_to_bytes(expected_path.as_path()).unwrap();
    let path = path_from_bytes(bytes).unwrap();
    assert_eq!(expected_path, path);
}

#[test]
fn test_create_file() {
    crate::init().unwrap();

    let dir = temp::Dir::new("bdrck").unwrap();
    let mut file_path = dir.path().to_path_buf();
    file_path.push("test_file");
    assert_eq!(file_path.file_name().unwrap(), "test_file");
    assert!(!file_path.exists());
    create_file(file_path.as_path()).unwrap();
    assert!(file_path.exists());
    assert!(file_path.is_file());
}

#[test]
fn test_create_symlink() {
    crate::init().unwrap();

    const TEST_CONTENTS: &'static str = "this is a test";

    let dir = temp::Dir::new("bdrck").unwrap();

    let file_path = dir.path().join("test_file");
    let mut f = File::create(&file_path).unwrap();
    f.write_all(TEST_CONTENTS.as_bytes()).unwrap();
    f.flush().unwrap();

    let symlink_path = dir.path().join("test_symlink");
    create_symlink(&file_path, &symlink_path).unwrap();
    assert!(
        fs::symlink_metadata(&symlink_path)
            .unwrap()
            .file_type()
            .is_symlink()
    );
    let mut contents = String::new();
    let mut f = File::open(&symlink_path).unwrap();
    assert_eq!(
        TEST_CONTENTS.len(),
        f.read_to_string(&mut contents).unwrap()
    );
    assert_eq!(TEST_CONTENTS, contents.as_str());
}

#[test]
fn test_lookup_unknown_user_is_not_found() {
    crate::init().unwrap();

    let file = temp::File::new_file().unwrap();
    // Unknown user names drive lookup_uid through the NotFound error arm.
    let err = set_ownership_by_name(
        file.path(),
        "bdrck_nonexistent_user_xyz_0000",
        "root",
        /*fail_on_access_denied=*/ true,
        /*follow=*/ true,
    )
    .unwrap_err();
    // We expect an Error::NotFound — format-check the message, since Error
    // doesn't implement PartialEq.
    let msg = format!("{}", err);
    assert!(msg.contains("not found"), "unexpected error: {}", msg);
}

#[test]
fn test_lookup_unknown_group_is_not_found() {
    crate::init().unwrap();

    let file = temp::File::new_file().unwrap();
    let err = set_ownership_by_name(
        file.path(),
        "root",
        "bdrck_nonexistent_group_xyz_0000",
        true,
        true,
    )
    .unwrap_err();
    let msg = format!("{}", err);
    assert!(msg.contains("not found"), "unexpected error: {}", msg);
}

#[test]
fn test_lookup_name_with_nul_is_error() {
    crate::init().unwrap();

    let file = temp::File::new_file().unwrap();
    // NUL bytes in user / group names should flunk the CString::new conversion.
    assert!(set_ownership_by_name(file.path(), "foo\0bar", "root", true, true).is_err());
    assert!(set_ownership_by_name(file.path(), "root", "foo\0bar", true, true).is_err());
}

#[test]
fn test_set_ownership_soft_failure_on_access_denied() {
    crate::init().unwrap();

    // Running the test suite as non-root, chown-to-root should fail with
    // EPERM / EACCES. With fail_on_access_denied=false the function logs a
    // warning and still returns Ok.
    if nix_is_root() {
        // Skip if we actually are root — the chown would succeed.
        return;
    }
    let file = temp::File::new_file().unwrap();
    let result = set_ownership_by_name(
        file.path(),
        "root",
        "root",
        /*fail_on_access_denied=*/ false,
        /*follow=*/ true,
    );
    assert!(result.is_ok(), "expected soft success, got {:?}", result);
}

#[test]
fn test_set_ownership_hard_failure_on_access_denied() {
    crate::init().unwrap();

    if nix_is_root() {
        return;
    }
    let file = temp::File::new_file().unwrap();
    let result = set_ownership_by_name(
        file.path(),
        "root",
        "root",
        /*fail_on_access_denied=*/ true,
        /*follow=*/ true,
    );
    assert!(result.is_err(), "expected hard failure");
}

/// Returns true if the test process is running with uid 0.
fn nix_is_root() -> bool {
    // Safety: getuid() never fails and doesn't touch any Rust state.
    unsafe { libc::getuid() == 0 }
}

#[test]
fn test_set_permissions_mode() {
    use std::os::unix::fs::PermissionsExt;

    crate::init().unwrap();

    let temp_file = temp::File::new_file().unwrap();
    set_permissions_mode(temp_file.path(), 0o444).unwrap();
    assert_eq!(
        0o444,
        fs::metadata(temp_file.path()).unwrap().permissions().mode() & 0x1FF
    );
    set_permissions_mode(temp_file.path(), 0o666).unwrap();
    assert_eq!(
        0o666,
        fs::metadata(temp_file.path()).unwrap().permissions().mode() & 0x1FF
    );
}