#!/bin/sh set -e # Filters out UNKNOWN users and groups, prints a warning on stderr. filter_unknown() { CMD=$1 while read line; do # if the first n chars of $line equal "$CMD UNKNOWN "... if [ "$(printf %.$((9+${#CMD}))s "$line")" = "$CMD UNKNOWN " ]; then echo Bad "$2" for "$line" >&2 else echo "$line" fi done } filter_ignore() { if [ "$VCS" = darcs ]; then ignorefile=.darcsignore fi if [ "$VCS" = darcs ] && [ -e "$ignorefile" ]; then patternsfile="$( mktemp -t etckeeper-$VCS.XXXXXXXXXX )" grep -v '^[[:space:]]*\(#\|$\)' "$ignorefile" > "$patternsfile" || true grep -Evf "$patternsfile" rm -f "$patternsfile" unset patternsfile else cat - fi } shellquote() { # Single quotes text, escaping existing single quotes. sed -e "s/'/'\"'\"'/" -e "s/^/'/" -e "s/$/'/" } generate_metadata() { # This function generates the script commands to fix any file # ownerships that aren't owner=root, group=root, as well as to # store the permissions of files. # The script is produced on stdout. Errors go to stderr. # # The script can use a 'maybe' function, which only runs a command # if the file in its last argument exists. # We want files in the directory containing VCS data # but we want find to ignore the VCS files themselves. # # (Note that when using this, the find expression must end with # -print or -exec, else the excluded directories will actually be # printed!) NOVCS='. -path ./.git -prune -o -path ./.bzr -prune -o -path ./.hg -prune -o -path ./_darcs -prune -o' # Keep the sort order the same at all times. LC_COLLATE=C export LC_COLLATE if [ "$VCS" = git ] || [ "$VCS" = hg ]; then # These version control systems do not track directories, # so empty directories must be stored specially. find $NOVCS -type d -empty -print | sort | shellquote | sed -e "s/^/mkdir -p /" fi if [ "$VCS" = darcs ]; then # This version control system does not track symlinks, # so they must be stored specially. find $NOVCS -type l -print | sort | filter_ignore | while read link; do dest=$( readlink "$link" ) printf "ln -sf '%s' '%s'\n" "$(echo "$dest" | shellquote)" "$(echo "$link" | shellquote)" done fi # Store things that don't have the default user or group. # Store all file modes, in case the user has an unusual umask. find $NOVCS \( -type f -or -type d \) -print | sort | perl -ne ' BEGIN { $q=chr(39) } sub uidname { my $want=shift; if (exists $uidcache{$want}) { return $uidcache{$want}; } return $uidcache{$want}=scalar getpwuid($want); } sub gidname { my $want=shift; if (exists $gidcache{$want}) { return $gidcache{$want}; } return $gidcache{$want}=scalar getgrgid($want); } chomp; my @stat=stat($_); my $mode = $stat[2]; my $uid = $stat[4]; my $gid = $stat[5]; s/$q/$q"$q"$q/g; # escape single quotes s/^/$q/; s/$/$q/; if ($uid != $>) { printf "maybe chown %s %s\n", uidname($uid), $_; } if ($gid != $)) { printf "maybe chgrp %s %s\n", gidname($uid), $_; } printf "maybe chmod %04o %s\n", $mode & 07777, $_; ' # We don't handle xattrs. # Maybe check for getfattr/setfattr and use them if they're available? } if [ "$VCS" = git ] || [ "$VCS" = hg ] || [ "$VCS" = bzr ] || [ "$VCS" = darcs ]; then if [ -f .metadata ]; then # remove obsolete .metadata file # git allows fully deleting it at this point, other VCS # may not (the repo is locked for hg). if [ "$VCS" = git ]; then $VCS rm .metadata else rm -f .metadata fi fi echo "# Generated by etckeeper. Do not edit." > .etckeeper echo >> .etckeeper # Make sure the file is not readable by others, since it can leak # information about contents of non-readable directories in /etc. chmod 700 .etckeeper generate_metadata >> .etckeeper # stage the file as part of the current commit if [ "$VCS" = git ]; then # this will do nothing if the metadata file is unchanged. git add .etckeeper fi # hg, bzr and darcs add not done, they will automatically # include the file in the current commit fi