Decoded: chmod (coreutils)

[Back to Project Main Page]

Note: This page explores the design of command-line utilities. It is not a user guide.
[GNU Manual] [POSIX requirement] [Linux man] [FreeBSD man]

Logical flow of chmod command (coreutils)

Summary

chmod - change file mode bits

[Source] [Code Walkthrough]

Lines of code: 573
Principal syscall: fchmodat()
Support syscalls: fstatat()
Options: 27 (17 short, 10 long, does not include perm digits)

Descended from chmod introduced in Version 1 UNIX (1971)
Added to Fileutils in October 1992 [First version]
Number of revisions: 174

Helpers:
  • describe_change() - Converts change attempt results to human-readable text
  • mode_changed() - Boolean function to detect mode change using fstatat()
  • process_file() - Attempts mode change on a single file
  • process_files() - Attempts mode change on a group of files
External non-standard helpers:
  • error() - Outputs error message to standard error with possible process termination
  • get_root_dev_ino() - Get's the dev and inode number of root
  • mode_adjust() - Computes the new mode based on input changes (in Gnulib)
  • mode_compile() - Converts a string describing a mode to a mode_t
  • mode_create_from_ref() - Returns a mode from a reference file
  • quoteaf() - Converts an input file to a string
  • strmode() - Converts file mode from bit field to char array
  • X2REALLOC() - Macro for the same function to verify/create a memory area

Setup

At global scope, chmod.c does the following:

  • Defines Change_status enum to identify the result of each file chmod
  • Defines Verbosity enum identifies levels of feedback
  • Defines pseudo-characters for long options without actual letters
  • Initializes the long parsing options for getopt including: --changes, --recursive, --no-preserve-root, --preserve-root, --quiet, --reference, --silent, --verbose, --help, --version

It also traps all digits and capitals as short options with no arguments. The numbers are used to detect pids and the capitals force invalid argument handling during parse

main() initializes the following:

  • c - The current command line option letter we're parsing
  • diagnose_surpises - Flag to verify actual result is intended result
  • force_silent - Flag to prevent error feedback
  • mode - The initial input mode string from the user
  • mode_len - The length of the mode string
  • mode_alloc - The allocation size of the mode string
  • ok - The final return status. Note overloaded usage
  • preserve_root - Flag to limit recursion from root
  • recurse - Flag to enforce recursion checks
  • reference_file - The file name used as a mode reference

Parsing kicks off with the short options passed as a string literal:
"Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::0::1::2::3::4::5::6::7::"


Parsing

During parsing, we're collecting options and arguments to answer the following questions:

  • Which file(s) do we modify
  • What is the new mode?
  • Is the new mode provided directly or with a reference file
  • Are we preserving root?
  • Are we operating recursively
  • What amount of reporting should we provide?

The expected input mode format follows convention: [ugo][+-][rwx] or in decimal triple (ex: 755). The parser creates a comma-separated sequence of the requested mode changes and 'compiles' them to the mode_t type format expected by your system.

Parsing failures

These failure cases are explicitly checked:

  • Specifying a mode but also providing a reference file
  • Mismatch between actual and expected operand count
  • Cannot access input reference file
  • Cannot parse specified mode
  • Cannot access filesystem root while preserving and recursing

User specified parsing failures result in a short error message followed by the usage instructions. Access related parsing errors die with an error message.


Execution

chmod executes on all files sequentially and continues until all files are processed regardless of errors. The fchmod() syscall applies mode changes while the fstatat() syscall retrieves current file mode. There are four ways the change could occur:

  • The change succeeds
  • The mode already matches and no change is necessary
  • There is nothing to change (a symbolic link)
  • Something went wrong....

The last case is failure and could happen in several ways:

Failure cases:

  • Insufficient permissions
  • No file / bad link
  • The resuling change isn't the expected change

All failures at this stage output an error message to STDERR and return without displaying usage help

Extra comments

File access and retrieval is performed with the fts library. There is a quirk that attempting to access a top-level file for the first time will require two attempts to succeed.

The ok status variable is not global, but is reused within three nested scopes: main() -> process_files(), and process_file(). Consequently, the final return value depends on the value returned of the last file processed or a failure of the fts system.

The chmodat() used to change file modes is actually a wrapper for the real syscall. Here is where the wrapper leads to:

/* From Gnulib (openat.h) */
chmodat (int fd, char const *file, mode_t mode)
{
  return fchmodat (fd, file, mode, 0);
}

[Back to Project Main Page]