1#![cfg(target_os = "linux")]
7#![allow(clippy::upper_case_acronyms)]
8
9use clap::builder::ValueParser;
10use uucore::error::{UResult, USimpleError, UUsageError};
11use uucore::translate;
12use uucore::{display::Quotable, format_usage, show_error, show_warning};
13
14use clap::{Arg, ArgAction, ArgMatches, Command};
15use selinux::{OpaqueSecurityContext, SecurityContext};
16
17use std::borrow::Cow;
18use std::ffi::{CStr, CString, OsStr, OsString};
19use std::os::raw::c_int;
20use std::path::{Path, PathBuf};
21use std::{fs, io};
22
23mod errors;
24mod fts;
25
26use errors::*;
27
28pub mod options {
29 pub static HELP: &str = "help";
30 pub static VERBOSE: &str = "verbose";
31
32 pub static REFERENCE: &str = "reference";
33
34 pub static USER: &str = "user";
35 pub static ROLE: &str = "role";
36 pub static TYPE: &str = "type";
37 pub static RANGE: &str = "range";
38
39 pub static RECURSIVE: &str = "recursive";
40
41 pub mod sym_links {
42 pub static FOLLOW_ARG_DIR_SYM_LINK: &str = "follow-arg-dir-sym-link";
43 pub static FOLLOW_DIR_SYM_LINKS: &str = "follow-dir-sym-links";
44 pub static NO_FOLLOW_SYM_LINKS: &str = "no-follow-sym-links";
45 }
46
47 pub mod dereference {
48 pub static DEREFERENCE: &str = "dereference";
49 pub static NO_DEREFERENCE: &str = "no-dereference";
50 }
51
52 pub mod preserve_root {
53 pub static PRESERVE_ROOT: &str = "preserve-root";
54 pub static NO_PRESERVE_ROOT: &str = "no-preserve-root";
55 }
56}
57
58#[uucore::main]
59pub fn uumain(args: impl uucore::Args) -> UResult<()> {
60 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
61
62 let options = match parse_command_line(&matches) {
63 Ok(r) => r,
64 Err(r) => {
65 if let Error::CommandLine(r) = r {
66 return Err(r.into());
67 }
68
69 return Err(UUsageError::new(libc::EXIT_FAILURE, format!("{r}.\n")));
70 }
71 };
72
73 let context = match &options.mode {
74 CommandLineMode::ReferenceBased { reference } => {
75 let result = match SecurityContext::of_path(reference, true, false) {
76 Ok(Some(context)) => Ok(context),
77
78 Ok(None) => {
79 let err = io::Error::from_raw_os_error(libc::ENODATA);
80 Err(Error::from_io1(
81 translate!("chcon-op-getting-security-context"),
82 reference,
83 err,
84 ))
85 }
86
87 Err(r) => Err(Error::from_selinux(
88 translate!("chcon-op-getting-security-context"),
89 r,
90 )),
91 };
92
93 match result {
94 Err(r) => {
95 return Err(USimpleError::new(
96 libc::EXIT_FAILURE,
97 format!("{}.", report_full_error(&r)),
98 ));
99 }
100
101 Ok(file_context) => SELinuxSecurityContext::File(file_context),
102 }
103 }
104
105 CommandLineMode::ContextBased { context } => {
106 let c_context = match os_str_to_c_string(context) {
107 Ok(context) => context,
108
109 Err(_r) => {
110 return Err(USimpleError::new(
111 libc::EXIT_FAILURE,
112 translate!("chcon-error-invalid-context", "context" => context.quote()),
113 ));
114 }
115 };
116
117 if SecurityContext::from_c_str(&c_context, false).check() == Some(false) {
118 return Err(USimpleError::new(
119 libc::EXIT_FAILURE,
120 translate!("chcon-error-invalid-context", "context" => context.quote()),
121 ));
122 }
123
124 SELinuxSecurityContext::String(Some(c_context))
125 }
126
127 CommandLineMode::Custom { .. } => SELinuxSecurityContext::String(None),
128 };
129
130 let root_dev_ino = if options.preserve_root && options.recursive_mode.is_recursive() {
131 match get_root_dev_ino() {
132 Ok(r) => Some(r),
133
134 Err(r) => {
135 return Err(USimpleError::new(
136 libc::EXIT_FAILURE,
137 format!("{}.", report_full_error(&r)),
138 ));
139 }
140 }
141 } else {
142 None
143 };
144
145 let results = process_files(&options, &context, root_dev_ino);
146 if results.is_empty() {
147 return Ok(());
148 }
149
150 for result in &results {
151 show_error!("{}.", report_full_error(result));
152 }
153 Err(libc::EXIT_FAILURE.into())
154}
155
156pub fn uu_app() -> Command {
157 let cmd = Command::new(uucore::util_name())
158 .version(uucore::crate_version!())
159 .about(translate!("chcon-about"))
160 .override_usage(format_usage(&translate!("chcon-usage")))
161 .infer_long_args(true);
162 uucore::clap_localization::configure_localized_command(cmd)
163 .args_override_self(true)
164 .disable_help_flag(true)
165 .arg(
166 Arg::new(options::dereference::DEREFERENCE)
167 .long(options::dereference::DEREFERENCE)
168 .overrides_with(options::dereference::NO_DEREFERENCE)
169 .help(translate!("chcon-help-dereference"))
170 .action(ArgAction::SetTrue),
171 )
172 .arg(
173 Arg::new(options::dereference::NO_DEREFERENCE)
174 .short('h')
175 .long(options::dereference::NO_DEREFERENCE)
176 .help(translate!("chcon-help-no-dereference"))
177 .action(ArgAction::SetTrue),
178 )
179 .arg(
180 Arg::new("help")
181 .long("help")
182 .help(translate!("help"))
183 .action(ArgAction::Help),
184 )
185 .arg(
186 Arg::new(options::preserve_root::PRESERVE_ROOT)
187 .long(options::preserve_root::PRESERVE_ROOT)
188 .overrides_with(options::preserve_root::NO_PRESERVE_ROOT)
189 .help(translate!("chcon-help-preserve-root"))
190 .action(ArgAction::SetTrue),
191 )
192 .arg(
193 Arg::new(options::preserve_root::NO_PRESERVE_ROOT)
194 .long(options::preserve_root::NO_PRESERVE_ROOT)
195 .help(translate!("chcon-help-no-preserve-root"))
196 .action(ArgAction::SetTrue),
197 )
198 .arg(
199 Arg::new(options::REFERENCE)
200 .long(options::REFERENCE)
201 .value_name("RFILE")
202 .value_hint(clap::ValueHint::FilePath)
203 .conflicts_with_all([options::USER, options::ROLE, options::TYPE, options::RANGE])
204 .help(translate!("chcon-help-reference"))
205 .value_parser(ValueParser::os_string()),
206 )
207 .arg(
208 Arg::new(options::USER)
209 .short('u')
210 .long(options::USER)
211 .value_name("USER")
212 .value_hint(clap::ValueHint::Username)
213 .help(translate!("chcon-help-user"))
214 .value_parser(ValueParser::os_string()),
215 )
216 .arg(
217 Arg::new(options::ROLE)
218 .short('r')
219 .long(options::ROLE)
220 .value_name("ROLE")
221 .help(translate!("chcon-help-role"))
222 .value_parser(ValueParser::os_string()),
223 )
224 .arg(
225 Arg::new(options::TYPE)
226 .short('t')
227 .long(options::TYPE)
228 .value_name("TYPE")
229 .help(translate!("chcon-help-type"))
230 .value_parser(ValueParser::os_string()),
231 )
232 .arg(
233 Arg::new(options::RANGE)
234 .short('l')
235 .long(options::RANGE)
236 .value_name("RANGE")
237 .help(translate!("chcon-help-range"))
238 .value_parser(ValueParser::os_string()),
239 )
240 .arg(
241 Arg::new(options::RECURSIVE)
242 .short('R')
243 .long(options::RECURSIVE)
244 .help(translate!("chcon-help-recursive"))
245 .action(ArgAction::SetTrue),
246 )
247 .arg(
248 Arg::new(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK)
249 .short('H')
250 .requires(options::RECURSIVE)
251 .overrides_with_all([
252 options::sym_links::FOLLOW_DIR_SYM_LINKS,
253 options::sym_links::NO_FOLLOW_SYM_LINKS,
254 ])
255 .help(translate!("chcon-help-follow-arg-dir-symlink"))
256 .action(ArgAction::SetTrue),
257 )
258 .arg(
259 Arg::new(options::sym_links::FOLLOW_DIR_SYM_LINKS)
260 .short('L')
261 .requires(options::RECURSIVE)
262 .overrides_with_all([
263 options::sym_links::FOLLOW_ARG_DIR_SYM_LINK,
264 options::sym_links::NO_FOLLOW_SYM_LINKS,
265 ])
266 .help(translate!("chcon-help-follow-dir-symlinks"))
267 .action(ArgAction::SetTrue),
268 )
269 .arg(
270 Arg::new(options::sym_links::NO_FOLLOW_SYM_LINKS)
271 .short('P')
272 .requires(options::RECURSIVE)
273 .overrides_with_all([
274 options::sym_links::FOLLOW_ARG_DIR_SYM_LINK,
275 options::sym_links::FOLLOW_DIR_SYM_LINKS,
276 ])
277 .help(translate!("chcon-help-no-follow-symlinks"))
278 .action(ArgAction::SetTrue),
279 )
280 .arg(
281 Arg::new(options::VERBOSE)
282 .short('v')
283 .long(options::VERBOSE)
284 .help(translate!("chcon-help-verbose"))
285 .action(ArgAction::SetTrue),
286 )
287 .arg(
288 Arg::new("FILE")
289 .action(ArgAction::Append)
290 .value_hint(clap::ValueHint::FilePath)
291 .num_args(1..)
292 .value_parser(ValueParser::os_string()),
293 )
294}
295
296#[derive(Debug)]
297struct Options {
298 verbose: bool,
299 preserve_root: bool,
300 recursive_mode: RecursiveMode,
301 affect_symlink_referent: bool,
302 mode: CommandLineMode,
303 files: Vec<PathBuf>,
304}
305
306fn parse_command_line(matches: &ArgMatches) -> Result<Options> {
307 let verbose = matches.get_flag(options::VERBOSE);
308
309 let (recursive_mode, affect_symlink_referent) = if matches.get_flag(options::RECURSIVE) {
310 if matches.get_flag(options::sym_links::FOLLOW_DIR_SYM_LINKS) {
311 if matches.get_flag(options::dereference::NO_DEREFERENCE) {
312 return Err(Error::ArgumentsMismatch(translate!(
313 "chcon-error-recursive-no-dereference-require-p"
314 )));
315 }
316
317 (RecursiveMode::RecursiveAndFollowAllDirSymLinks, true)
318 } else if matches.get_flag(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK) {
319 if matches.get_flag(options::dereference::NO_DEREFERENCE) {
320 return Err(Error::ArgumentsMismatch(translate!(
321 "chcon-error-recursive-no-dereference-require-p"
322 )));
323 }
324
325 (RecursiveMode::RecursiveAndFollowArgDirSymLinks, true)
326 } else {
327 if matches.get_flag(options::dereference::DEREFERENCE) {
328 return Err(Error::ArgumentsMismatch(translate!(
329 "chcon-error-recursive-dereference-require-h-or-l"
330 )));
331 }
332
333 (RecursiveMode::RecursiveButDoNotFollowSymLinks, false)
334 }
335 } else {
336 let no_dereference = matches.get_flag(options::dereference::NO_DEREFERENCE);
337 (RecursiveMode::NotRecursive, !no_dereference)
338 };
339
340 let preserve_root = matches.get_flag(options::preserve_root::PRESERVE_ROOT);
342
343 let mut files = matches.get_many::<OsString>("FILE").unwrap_or_default();
344
345 let mode = if let Some(path) = matches.get_one::<OsString>(options::REFERENCE) {
346 CommandLineMode::ReferenceBased {
347 reference: PathBuf::from(path),
348 }
349 } else if matches.contains_id(options::USER)
350 || matches.contains_id(options::ROLE)
351 || matches.contains_id(options::TYPE)
352 || matches.contains_id(options::RANGE)
353 {
354 CommandLineMode::Custom {
355 user: matches.get_one::<OsString>(options::USER).map(Into::into),
356 role: matches.get_one::<OsString>(options::ROLE).map(Into::into),
357 the_type: matches.get_one::<OsString>(options::TYPE).map(Into::into),
358 range: matches.get_one::<OsString>(options::RANGE).map(Into::into),
359 }
360 } else if let Some(context) = files.next() {
361 CommandLineMode::ContextBased {
362 context: context.into(),
363 }
364 } else {
365 return Err(Error::MissingContext);
366 };
367
368 let files: Vec<_> = files.map(PathBuf::from).collect();
369 if files.is_empty() {
370 return Err(Error::MissingFiles);
371 }
372
373 Ok(Options {
374 verbose,
375 preserve_root,
376 recursive_mode,
377 affect_symlink_referent,
378 mode,
379 files,
380 })
381}
382
383#[derive(Debug, Copy, Clone)]
384enum RecursiveMode {
385 NotRecursive,
386 RecursiveButDoNotFollowSymLinks,
388 RecursiveAndFollowAllDirSymLinks,
390 RecursiveAndFollowArgDirSymLinks,
392}
393
394impl RecursiveMode {
395 fn is_recursive(self) -> bool {
396 match self {
397 Self::NotRecursive => false,
398
399 Self::RecursiveButDoNotFollowSymLinks
400 | Self::RecursiveAndFollowAllDirSymLinks
401 | Self::RecursiveAndFollowArgDirSymLinks => true,
402 }
403 }
404
405 fn fts_open_options(self) -> c_int {
406 match self {
407 Self::NotRecursive | Self::RecursiveButDoNotFollowSymLinks => fts_sys::FTS_PHYSICAL,
408
409 Self::RecursiveAndFollowAllDirSymLinks => fts_sys::FTS_LOGICAL,
410
411 Self::RecursiveAndFollowArgDirSymLinks => {
412 fts_sys::FTS_PHYSICAL | fts_sys::FTS_COMFOLLOW
413 }
414 }
415 }
416}
417
418#[derive(Debug)]
419enum CommandLineMode {
420 ReferenceBased {
421 reference: PathBuf,
422 },
423 ContextBased {
424 context: OsString,
425 },
426 Custom {
427 user: Option<OsString>,
428 role: Option<OsString>,
429 the_type: Option<OsString>,
430 range: Option<OsString>,
431 },
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
435struct DeviceAndINode {
436 device_id: u64,
437 inode: u64,
438}
439
440#[cfg(unix)]
441impl From<fs::Metadata> for DeviceAndINode {
442 fn from(md: fs::Metadata) -> Self {
443 use std::os::unix::fs::MetadataExt;
444
445 Self {
446 device_id: md.dev(),
447 inode: md.ino(),
448 }
449 }
450}
451
452impl TryFrom<&libc::stat> for DeviceAndINode {
453 type Error = Error;
454
455 #[allow(clippy::useless_conversion)]
456 fn try_from(st: &libc::stat) -> Result<Self> {
457 let device_id = u64::try_from(st.st_dev).map_err(|_r| Error::OutOfRange)?;
458 let inode = u64::try_from(st.st_ino).map_err(|_r| Error::OutOfRange)?;
459 Ok(Self { device_id, inode })
460 }
461}
462
463fn process_files(
464 options: &Options,
465 context: &SELinuxSecurityContext,
466 root_dev_ino: Option<DeviceAndINode>,
467) -> Vec<Error> {
468 let fts_options = options.recursive_mode.fts_open_options();
469 let mut fts = match fts::FTS::new(options.files.iter(), fts_options) {
470 Ok(fts) => fts,
471 Err(err) => return vec![err],
472 };
473
474 let mut errors = Vec::default();
475 loop {
476 match fts.read_next_entry() {
477 Ok(true) => {
478 if let Err(err) = process_file(options, context, &mut fts, root_dev_ino) {
479 errors.push(err);
480 }
481 }
482
483 Ok(false) => break,
484
485 Err(err) => {
486 errors.push(err);
487 break;
488 }
489 }
490 }
491 errors
492}
493
494fn process_file(
495 options: &Options,
496 context: &SELinuxSecurityContext,
497 fts: &mut fts::FTS,
498 root_dev_ino: Option<DeviceAndINode>,
499) -> Result<()> {
500 let mut entry = fts.last_entry_ref().unwrap();
501
502 let file_full_name = entry.path().map(PathBuf::from).ok_or_else(|| {
503 Error::from_io(
504 translate!("chcon-op-file-name-validation"),
505 io::ErrorKind::InvalidInput.into(),
506 )
507 })?;
508
509 let fts_access_path = entry.access_path().ok_or_else(|| {
510 let err = io::ErrorKind::InvalidInput.into();
511 Error::from_io1(
512 translate!("chcon-op-file-name-validation"),
513 &file_full_name,
514 err,
515 )
516 })?;
517
518 let err = |s, k: io::ErrorKind| Error::from_io1(s, &file_full_name, k.into());
519
520 let fts_err = |s| {
521 let r = io::Error::from_raw_os_error(entry.errno());
522 Err(Error::from_io1(s, &file_full_name, r))
523 };
524
525 let file_dev_ino: DeviceAndINode = if let Some(st) = entry.stat() {
527 st.try_into()?
528 } else {
529 return Err(err(
530 translate!("chcon-op-getting-meta-data"),
531 io::ErrorKind::InvalidInput,
532 ));
533 };
534
535 let mut result = Ok(());
536
537 match entry.flags() {
538 fts_sys::FTS_D => {
539 if options.recursive_mode.is_recursive() {
540 if root_dev_ino_check(root_dev_ino, file_dev_ino) {
541 root_dev_ino_warn(&file_full_name);
544
545 let _ignored = fts.set(fts_sys::FTS_SKIP);
547
548 let _ignored = fts.read_next_entry();
550
551 return Err(err(
552 translate!("chcon-op-modifying-root-path"),
553 io::ErrorKind::PermissionDenied,
554 ));
555 }
556
557 return Ok(());
558 }
559 }
560
561 fts_sys::FTS_DP => {
562 if !options.recursive_mode.is_recursive() {
563 return Ok(());
564 }
565 }
566
567 fts_sys::FTS_NS => {
568 if entry.level() == 0 && entry.number() == 0 {
574 entry.set_number(1);
575 let _ignored = fts.set(fts_sys::FTS_AGAIN);
576 return Ok(());
577 }
578
579 result = fts_err(translate!("chcon-op-accessing"));
580 }
581
582 fts_sys::FTS_ERR => result = fts_err(translate!("chcon-op-accessing")),
583
584 fts_sys::FTS_DNR => result = fts_err(translate!("chcon-op-reading-directory")),
585
586 fts_sys::FTS_DC => {
587 if cycle_warning_required(options.recursive_mode.fts_open_options(), &entry) {
588 emit_cycle_warning(&file_full_name);
589 return Err(err(
590 translate!("chcon-op-reading-cyclic-directory"),
591 io::ErrorKind::InvalidData,
592 ));
593 }
594 }
595
596 _ => {}
597 }
598
599 if entry.flags() == fts_sys::FTS_DP
600 && result.is_ok()
601 && root_dev_ino_check(root_dev_ino, file_dev_ino)
602 {
603 root_dev_ino_warn(&file_full_name);
604 result = Err(err(
605 translate!("chcon-op-modifying-root-path"),
606 io::ErrorKind::PermissionDenied,
607 ));
608 }
609
610 if result.is_ok() {
611 if options.verbose {
612 println!(
613 "{}",
614 translate!("chcon-verbose-changing-context", "util_name" => uucore::util_name(), "file" => file_full_name.quote())
615 );
616 }
617
618 result = change_file_context(options, context, fts_access_path);
619 }
620
621 if !options.recursive_mode.is_recursive() {
622 let _ignored = fts.set(fts_sys::FTS_SKIP);
623 }
624 result
625}
626
627fn change_file_context(
628 options: &Options,
629 context: &SELinuxSecurityContext,
630 path: &Path,
631) -> Result<()> {
632 match &options.mode {
633 CommandLineMode::Custom {
634 user,
635 role,
636 the_type,
637 range,
638 } => {
639 let err0 = || -> Result<()> {
640 let op = translate!("chcon-op-applying-partial-context");
643 let err = io::ErrorKind::InvalidInput.into();
644 Err(Error::from_io1(op, path, err))
645 };
646
647 let file_context =
648 match SecurityContext::of_path(path, options.affect_symlink_referent, false) {
649 Ok(Some(context)) => context,
650
651 Ok(None) => return err0(),
652 Err(r) => {
653 return Err(Error::from_selinux(
654 translate!("chcon-op-getting-security-context"),
655 r,
656 ));
657 }
658 };
659
660 let c_file_context = match file_context.to_c_string() {
661 Ok(Some(context)) => context,
662
663 Ok(None) => return err0(),
664 Err(r) => {
665 return Err(Error::from_selinux(
666 translate!("chcon-op-getting-security-context"),
667 r,
668 ));
669 }
670 };
671
672 let se_context =
673 OpaqueSecurityContext::from_c_str(c_file_context.as_ref()).map_err(|_r| {
674 let err = io::ErrorKind::InvalidInput.into();
675 Error::from_io1(translate!("chcon-op-creating-security-context"), path, err)
676 })?;
677
678 type SetValueProc = fn(&OpaqueSecurityContext, &CStr) -> selinux::errors::Result<()>;
679
680 let list: &[(&Option<OsString>, SetValueProc)] = &[
681 (user, OpaqueSecurityContext::set_user),
682 (role, OpaqueSecurityContext::set_role),
683 (the_type, OpaqueSecurityContext::set_type),
684 (range, OpaqueSecurityContext::set_range),
685 ];
686
687 for (new_value, set_value_proc) in list {
688 if let Some(new_value) = new_value {
689 let c_new_value = os_str_to_c_string(new_value).map_err(|_r| {
690 let err = io::ErrorKind::InvalidInput.into();
691 Error::from_io1(translate!("chcon-op-creating-security-context"), path, err)
692 })?;
693
694 set_value_proc(&se_context, &c_new_value).map_err(|r| {
695 Error::from_selinux(translate!("chcon-op-setting-security-context-user"), r)
696 })?;
697 }
698 }
699
700 let context_string = se_context.to_c_string().map_err(|r| {
701 Error::from_selinux(translate!("chcon-op-getting-security-context"), r)
702 })?;
703
704 if c_file_context.as_ref().to_bytes() == context_string.as_ref().to_bytes() {
705 Ok(()) } else {
707 SecurityContext::from_c_str(&context_string, false)
708 .set_for_path(path, options.affect_symlink_referent, false)
709 .map_err(|r| {
710 Error::from_selinux(translate!("chcon-op-setting-security-context"), r)
711 })
712 }
713 }
714
715 CommandLineMode::ReferenceBased { .. } | CommandLineMode::ContextBased { .. } => {
716 if let Some(c_context) = context.to_c_string()? {
717 SecurityContext::from_c_str(c_context.as_ref(), false)
718 .set_for_path(path, options.affect_symlink_referent, false)
719 .map_err(|r| {
720 Error::from_selinux(translate!("chcon-op-setting-security-context"), r)
721 })
722 } else {
723 let err = io::ErrorKind::InvalidInput.into();
724 Err(Error::from_io1(
725 translate!("chcon-op-setting-security-context"),
726 path,
727 err,
728 ))
729 }
730 }
731 }
732}
733
734#[cfg(unix)]
735pub(crate) fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
736 use std::os::unix::ffi::OsStrExt;
737
738 CString::new(s.as_bytes())
739 .map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into()))
740}
741
742#[cfg(unix)]
744fn get_root_dev_ino() -> Result<DeviceAndINode> {
745 fs::symlink_metadata("/")
746 .map(DeviceAndINode::from)
747 .map_err(|r| Error::from_io1("std::fs::symlink_metadata", "/", r))
748}
749
750fn root_dev_ino_check(root_dev_ino: Option<DeviceAndINode>, dir_dev_ino: DeviceAndINode) -> bool {
751 root_dev_ino == Some(dir_dev_ino)
752}
753
754fn root_dev_ino_warn(dir_name: &Path) {
755 if dir_name.as_os_str() == "/" {
756 show_warning!(
757 "{}",
758 translate!("chcon-warning-dangerous-recursive-root", "option" => options::preserve_root::NO_PRESERVE_ROOT)
759 );
760 } else {
761 show_warning!(
762 "{}",
763 translate!("chcon-warning-dangerous-recursive-dir", "dir" => dir_name.to_string_lossy(), "option" => options::preserve_root::NO_PRESERVE_ROOT)
764 );
765 }
766}
767
768fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool {
776 ((fts_options & fts_sys::FTS_PHYSICAL) != 0)
779 && (((fts_options & fts_sys::FTS_COMFOLLOW) == 0) || entry.level() != 0)
780}
781
782fn emit_cycle_warning(file_name: &Path) {
783 show_warning!(
784 "{}",
785 translate!("chcon-warning-circular-directory", "file" => file_name.to_string_lossy())
786 );
787}
788
789#[derive(Debug)]
790enum SELinuxSecurityContext<'t> {
791 File(SecurityContext<'t>),
792 String(Option<CString>),
793}
794
795impl SELinuxSecurityContext<'_> {
796 fn to_c_string(&self) -> Result<Option<Cow<'_, CStr>>> {
797 match self {
798 Self::File(context) => context
799 .to_c_string()
800 .map_err(|r| Error::from_selinux("SELinuxSecurityContext::to_c_string()", r)),
801
802 Self::String(context) => Ok(context.as_deref().map(Cow::Borrowed)),
803 }
804 }
805}