Skip to content

Instantly share code, notes, and snippets.

@rindeal
Last active August 27, 2024 21:44
Show Gist options
  • Save rindeal/bc41ec6232fc80da575104ddb32c19ed to your computer and use it in GitHub Desktop.
Save rindeal/bc41ec6232fc80da575104ddb32c19ed to your computer and use it in GitHub Desktop.
A robust Bash script for copying file attributes (ownership, permissions, and SELinux context) from a source file to one or more destination files.

File Attributes Copier

A robust Bash script for copying file attributes (ownership, permissions, and SELinux context) from a source file to one or more destination files.

Features

  • Copies ownership (user and group)
  • Copies permissions (read, write, execute)
  • Copies SELinux security context (if SELinux is enabled)
  • Provides detailed, color-coded logging
  • Handles multiple destination files
  • Robust error checking and reporting

Requirements

  • Bash (version 4.0 or later recommended)
  • Standard Unix utilities: chown, chmod
  • SELinux utilities (optional): getenforce, chcon

Installation

  1. Download the script file:

    wget https://gist.githubusercontent.com/rindeal/bc41ec6232fc80da575104ddb32c19ed/raw/cp-attr.sh
  2. Make the script executable:

    chmod +x cp-attr.sh

Usage

./cp-attr.sh <source_path> <destination_path1> [<destination_path2> ...]
  • <source_path>: The file whose attributes you want to copy
  • <destination_path1>, <destination_path2>, etc.: One or more files to which you want to apply the attributes

Example

./cp-attr.sh /path/to/source/file.txt /path/to/dest1.txt /path/to/dest2.txt

This command will copy the attributes of file.txt to both dest1.txt and dest2.txt.

Output

The script provides detailed, color-coded output for each operation:

  • Blue [INFO]: General information
  • Green [SUCCESS]: Successful operations
  • Yellow [WARNING]: Non-critical issues
  • Red [ERROR]: Critical failures

Example output:

[INFO   ] SELINUX            | Enabled. Will copy security context.
[INFO   ] /path/to/dest1.txt | Starting attribute copy from '/path/to/source/file.txt'
[SUCCESS] /path/to/dest1.txt | change ownership: changed ownership of '/path/to/dest1.txt' from user1:group1 to user2:group2
[SUCCESS] /path/to/dest1.txt | change permissions: mode of '/path/to/dest1.txt' changed from 0644 (rw-r--r--) to 0755 (rwxr-xr-x)
[SUCCESS] /path/to/dest1.txt | change security context: changed security context of '/path/to/dest1.txt'
[SUCCESS] /path/to/dest1.txt | All attributes copied successfully

[INFO   ] /path/to/dest2.txt | Starting attribute copy from '/path/to/source/file.txt'
[SUCCESS] /path/to/dest2.txt | change ownership: changed ownership of '/path/to/dest2.txt' from user1:group1 to user2:group2
[ERROR  ] /path/to/dest2.txt | Failed to change permissions
[ERROR  ] /path/to/dest2.txt | chmod: changing permissions of '/path/to/dest2.txt': Operation not permitted
[WARNING] /path/to/dest2.txt | Some operations failed during attribute copy

[WARNING] SCRIPT             | Some operations failed. Please review the log for details.

Error Handling

  • The script will continue processing all specified destination files even if operations on one file fail.
  • A summary of overall success or failure is provided at the end of the script execution.
  • Detailed error messages are displayed for any failed operations.

Limitations

  • Requires appropriate permissions to modify file attributes
  • SELinux context copying only works on systems with SELinux enabled
  • Does not handle symbolic links specially (attributes are applied to the link itself, not the target)

License

This project is licensed under the GPL-3.0 License.

#!/usr/bin/env bash
# SPDX-FileCopyrightText: ANNO DOMINI 2024 Jan Chren ~rindeal <dev.rindeal gmail.com>
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
# Homepage: https://gist.github.com/rindeal/bc41ec6232fc80da575104ddb32c19ed
# Enable strict mode
set -euo pipefail
IFS=$'\n\t'
LANG='C'
# ANSI color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
SELINUX_ENABLED=false
SOURCE_PATH=""
# Logging functions
log() {
local level="$1"
local path="$2"
local message="$3"
local color=""
case "$level" in
INFO) color="$BLUE" ;;
SUCCESS) color="$GREEN" ;;
WARNING) color="$YELLOW" ;;
ERROR) color="$RED" ;;
esac
printf "${color}[%-7s]${NC} %-40s | %s\n" "$level" "$path" "$message"
}
log_info() { log "INFO" "$1" "$2"; }
log_success() { log "SUCCESS" "$1" "$2"; }
log_warning() { log "WARNING" "$1" "$2" >&2; }
log_error() { log "ERROR" "$1" "$2" >&2; }
# Function to execute command and log output
exec_and_log() {
local dest="$1"
local operation="$2"
shift 2
local cmd=("$@")
local output
local status=0
output=$("${cmd[@]}" 2>&1) || status=$?
if [[ $status -eq 0 ]]; then
while IFS= read -r line; do
log_success "$dest" "$operation: $line"
done <<< "$output"
return 0
else
log_error "$dest" "Failed to $operation"
while IFS= read -r line; do
log_error "$dest" "$line"
done <<< "$output"
return 1
fi
}
# Function to print usage
print_usage() {
log_info "USAGE" "$0 <source_path> <destination_path1> [<destination_path2> ...]"
log_info "DESCRIPTION" "Copies ownership, permissions, and security context from source to destination(s)."
}
# Check if at least two arguments are provided
if (( $# < 2 )); then
log_error "ARGS" "At least two arguments are required."
print_usage
exit 1
fi
SOURCE_PATH="$1"
shift
# Check if source path exists
if [[ ! -e "$SOURCE_PATH" ]]; then
log_error "$SOURCE_PATH" "Source path does not exist."
exit 1
fi
# Check if SELinux is enabled (only once per script execution)
if command -v getenforce &>/dev/null && [[ "$(getenforce)" != "Disabled" ]]; then
SELINUX_ENABLED=true
log_info "SELINUX" "Enabled. Will copy security context."
else
log_info "SELINUX" "Not enabled. Skipping security context copy."
fi
# Function to copy attributes
copy_attributes() {
local src="$1"
local dest="$2"
local success=true
log_info "$dest" "Starting attribute copy from '$src'"
# Copy ownership
exec_and_log "$dest" "change ownership" chown --verbose --preserve-root --reference="$src" "$dest" || success=false
# Copy permissions
exec_and_log "$dest" "change permissions" chmod --verbose --reference="$src" "$dest" || success=false
# Copy security context (if SELinux is enabled)
if $SELINUX_ENABLED; then
exec_and_log "$dest" "change security context" chcon --verbose --reference="$src" "$dest" || success=false
fi
if $success; then
log_success "$dest" "All attributes copied successfully"
else
log_warning "$dest" "Some operations failed during attribute copy"
fi
echo
$success
}
# Process each destination path
overall_success=true
for dest_path in "$@"; do
if [[ -e "$dest_path" ]]; then
copy_attributes "$SOURCE_PATH" "$dest_path" || overall_success=false
else
log_error "$dest_path" "Destination path does not exist. Skipping."
overall_success=false
fi
done
if $overall_success; then
log_success "SCRIPT" "All operations completed successfully."
else
log_warning "SCRIPT" "Some operations failed. Please review the log for details."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment