uu_chcon/
chcon.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5// spell-checker:ignore (vars) RFILE
6#![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    // By default, do not preserve root.
341    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    /// Do not traverse any symbolic links.
387    RecursiveButDoNotFollowSymLinks,
388    /// Traverse every symbolic link to a directory encountered.
389    RecursiveAndFollowAllDirSymLinks,
390    /// If a command line argument is a symbolic link to a directory, traverse it.
391    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    // SAFETY: If `entry.fts_statp` is not null, then is is assumed to be valid.
526    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                    // This happens e.g., with "chcon -R --preserve-root ... /"
542                    // and with "chcon -RH --preserve-root ... symlink-to-root".
543                    root_dev_ino_warn(&file_full_name);
544
545                    // Tell fts not to traverse into this hierarchy.
546                    let _ignored = fts.set(fts_sys::FTS_SKIP);
547
548                    // Ensure that we do not process "/" on the second visit.
549                    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            // For a top-level file or directory, this FTS_NS (stat failed) indicator is determined
569            // at the time of the initial fts_open call. With programs like chmod, chown, and chgrp,
570            // that modify permissions, it is possible that the file in question is accessible when
571            // control reaches this point. So, if this is the first time we've seen the FTS_NS for
572            // this file, tell fts_read to stat it "again".
573            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                // If the file doesn't have a context, and we're not setting all of the context
641                // components, there isn't really an obvious default. Thus, we just give up.
642                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(()) // Nothing to change.
706            } 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/// Call `lstat()` to get the device and inode numbers for `/`.
743#[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
768/// When `fts_read` returns [`fts_sys::FTS_DC`] to indicate a directory cycle, it may or may not indicate
769/// a real problem.
770/// When a program like chgrp performs a recursive traversal that requires traversing symbolic links,
771/// it is *not* a problem.
772/// However, when invoked with "-P -R", it deserves a warning.
773/// The `fts_options` parameter records the options that control this aspect of fts behavior,
774/// so test that.
775fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool {
776    // When dereferencing no symlinks, or when dereferencing only those listed on the command line
777    // and we're not processing a command-line argument, then a cycle is a serious problem.
778    ((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}