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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
//! Program to get/set extended ACL's.
//!
//! To read an ACL from myfile and write it to stdout as JSON:
//! exacl myfile
//!
//! To set the ACL for myfile from JSON passed via stdin (complete replacement):
//! exacl --set myfile
//!
//! To get/set the ACL of a symlink itself, instead of the file it points to,
//! use the -s option.
//!
//! To get/set the default ACL (on Linux), use the -d option.
use exacl::{getfacl, setfacl, AclEntry, AclOption};
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use clap::Parser;
#[derive(clap::Parser)]
#[command(name = "exacl", about = "Read or write a file's ACL.")]
#[allow(clippy::struct_excessive_bools)]
struct Opt {
/// Set file's ACL.
#[arg(long)]
set: bool,
/// Get or set the access ACL.
#[arg(short = 'a', long)]
access: bool,
/// Get or set the default ACL.
#[arg(short = 'd', long)]
default: bool,
/// Get or set the ACL of a symlink itself.
#[arg(short = 's', long)]
symlink: bool,
/// Format of input or output.
#[arg(value_enum, short = 'f', long, default_value = "json")]
format: Format,
/// Input files
#[arg()]
files: Vec<PathBuf>,
}
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
#[value(rename_all = "lower")]
enum Format {
Json,
Std,
}
const EXIT_SUCCESS: i32 = 0;
const EXIT_FAILURE: i32 = 1;
fn main() {
env_logger::init();
let opt = Opt::parse();
let mut options = AclOption::empty();
if opt.access {
options |= AclOption::ACCESS_ACL;
}
if opt.default {
options |= AclOption::DEFAULT_ACL;
}
if opt.symlink {
options |= AclOption::SYMLINK_ACL;
}
let exit_code = if opt.set {
set_acl(&opt.files, options, opt.format)
} else {
get_acl(&opt.files, options, opt.format)
};
process::exit(exit_code);
}
fn get_acl(paths: &[PathBuf], options: AclOption, format: Format) -> i32 {
for path in paths {
if let Err(err) = dump_acl(path, options, format) {
eprintln!("{err}");
return EXIT_FAILURE;
}
}
EXIT_SUCCESS
}
fn set_acl(paths: &[PathBuf], options: AclOption, format: Format) -> i32 {
let entries = match read_input(format) {
Some(entries) => entries,
None => return EXIT_FAILURE,
};
if let Err(err) = setfacl(paths, &entries, options) {
eprintln!("{err}");
return EXIT_FAILURE;
}
EXIT_SUCCESS
}
fn dump_acl(path: &Path, options: AclOption, format: Format) -> io::Result<()> {
let entries = getfacl(path, options)?;
match format {
#[cfg(feature = "serde")]
Format::Json => {
serde_json::to_writer(io::stdout(), &entries)?;
println!(); // add newline
}
#[cfg(not(feature = "serde"))]
Format::Json => {
panic!("serde not supported");
}
Format::Std => exacl::to_writer(io::stdout(), &entries)?,
};
Ok(())
}
fn read_input(format: Format) -> Option<Vec<AclEntry>> {
let reader = io::BufReader::new(io::stdin());
let entries: Vec<AclEntry> = match format {
// Read JSON format.
#[cfg(feature = "serde")]
Format::Json => match serde_json::from_reader(reader) {
Ok(entries) => entries,
Err(err) => {
eprintln!("JSON parser error: {err}");
return None;
}
},
#[cfg(not(feature = "serde"))]
Format::Json => {
panic!("serde not supported");
}
// Read Std format.
Format::Std => match exacl::from_reader(reader) {
Ok(entries) => entries,
Err(err) => {
eprintln!("Std parser error: {err}");
return None;
}
},
};
Some(entries)
}
|