727 lines
21 KiB
Bash
727 lines
21 KiB
Bash
# Searches for entries (files and directories) in the specified local directory.
|
|
#
|
|
# $1 (path): The path to the directory on the local machine where the search for entries will be performed.
|
|
# $2 (type): Type entry (f: files, d: directories, ...).
|
|
function fileList() {
|
|
local path="${1-}"
|
|
local type="${2-}"
|
|
local args=(-mindepth 1 -maxdepth 1)
|
|
|
|
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
|
[[ -d "$path" ]] || { appError "Directory not found: $path"; return 1; }
|
|
|
|
if [[ -n "$type" ]]; then
|
|
case "$type" in
|
|
f|d|l|p|s|b|c) args+=(-type "$type") ;;
|
|
*) appError "Unsupported file type: $type"; return 1 ;;
|
|
esac
|
|
fi
|
|
|
|
find "$path" "${args[@]}" -printf '%f\n'
|
|
}
|
|
|
|
# Searches for entries (files or directories) in the specified directory on an FTP server.
|
|
#
|
|
# $1 (path): The path to the directory on the FTP server where the search for entries will be performed.
|
|
# $2 (type): Type entry (f: files, d: directories, ...).
|
|
function ftpFileList() {
|
|
local path="$1"
|
|
local type="$2"
|
|
local output error
|
|
|
|
run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$path/" --connect-timeout 10 \
|
|
|| { appError "$error"; return 1; }
|
|
|
|
case "$type" in
|
|
f) printf '%s\n' "$output" | grep -v '^d' | awk '{print $NF}' ;;
|
|
d) printf '%s\n' "$output" | grep '^d' | awk '{print $NF}' ;;
|
|
*) printf '%s\n' "$output" | awk '{print $NF}' ;;
|
|
esac
|
|
}
|
|
|
|
# Searches for entries (files or directories) in the specified directory on a remote server via SSH.
|
|
#
|
|
# $1 (path): The path to the directory on the remote server where the search for entries will be performed.
|
|
# $2 (type): Type entry (f: files, d: directories, ...).
|
|
function sshFileList() {
|
|
local path="$1"
|
|
local type="$2"
|
|
local typeArg="${type:+-type $type}"
|
|
local output error
|
|
|
|
run output error ssh -i "$sshKeyFile" "$sshUser@$sshHost" \
|
|
"find '${sshPath}${path}' -maxdepth 1 -mindepth 1 ${typeArg} -exec basename {} \\;" \
|
|
|| { appError "$error"; return 1; }
|
|
|
|
printf '%s\n' "$output"
|
|
}
|
|
|
|
# Searches for entries (files or directories) in the specified directory on external storage.
|
|
function storageFileList() {
|
|
local -A storageFunc=(
|
|
[ftp]=ftpFileList
|
|
[rsync]=ftpFileList
|
|
[ssh]=sshFileList
|
|
)
|
|
[[ -n "${storageFunc[$storageType]:-}" ]] || { appError "Unknown value storageType: $storageType"; return 1; }
|
|
|
|
"${storageFunc[$storageType]}" "$@"
|
|
}
|
|
|
|
# Cleans a specified directory on a remote server using rsync.
|
|
#
|
|
# $1 (path): The path to the directory to be cleaned on the remote server.
|
|
#
|
|
# The function creates a temporary empty directory on the local machine and syncs it with the remote directory
|
|
# using rsync with the `--delete` option, which removes any files on the remote side that are not present in
|
|
# the local directory. After completion, the temporary directory is deleted. An error message is displayed in case of failure.
|
|
function rsyncDirectoryClean() {
|
|
local path="$1"
|
|
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
|
|
|
local emptyPath="$appDataPath/$(uuidgen)"
|
|
local error
|
|
runError error mkdir -p "$emptyPath" || { appError "Failed to create temp directory: $error"; return 1; }
|
|
|
|
runError error rsync -r --delete --password-file="$rsyncPassFile" "$emptyPath/" rsync://"$rsyncUri$path/" \
|
|
|| { rm -rf "$emptyPath"; appError "Failed to clean remote directory: $error"; return 1; }
|
|
rm -rf "$emptyPath"
|
|
}
|
|
|
|
# Deletes a directory on an FTP server.
|
|
#
|
|
# $1 (path): The path to the directory on the FTP server to be deleted (in ftpPath).
|
|
function ftpDirectoryDelete() {
|
|
local path="$1"
|
|
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
|
|
|
local error
|
|
runError error curl -u "$ftpUser:$ftpPass" -Q "-RMD $ftpPath$path" "ftp://$ftpHost:$ftpPort" --connect-timeout 10 \
|
|
|| { appError "$error"; return 1; }
|
|
}
|
|
|
|
# Deletes a directory on a remote server via SSH (in sshPath).
|
|
#
|
|
# $1 (path): The path to the directory to be deleted.
|
|
function sshDirectoryDelete() {
|
|
local path="$1"
|
|
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
|
|
|
local error
|
|
runError error ssh -i "$sshKeyFile" "$sshUser@$sshHost" "rm -rf '${sshPath}${path}'" \
|
|
|| { appError "$error"; return 1; }
|
|
}
|
|
|
|
function fileAtomicWrite() {
|
|
local file="$1"
|
|
local content="$2"
|
|
local path tmp
|
|
path="$(dirname "$file")"
|
|
|
|
mkdir -p "$path"
|
|
tmp="$(mktemp "$path/.tmp.XXXXXX")"
|
|
printf '%s\n' "$content" > "$tmp"
|
|
mv -f "$tmp" "$file"
|
|
}
|
|
|
|
# Create a timestamped backup copy of a file if it exists.
|
|
# $1 (file): path to the file to back up.
|
|
# [$2] (suffix): custom suffix for the backup file (defaults to a timestamp .bak suffix).
|
|
function fileBackup() {
|
|
local file="$1"
|
|
if [[ -z "$file" ]]; then
|
|
appError "File not specified"
|
|
return 1
|
|
fi
|
|
if [[ ! -f "$file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local suffix="${2:-.$(date +%F_%H-%M-%S).bak}"
|
|
|
|
cp -p -- "$file" "$file$suffix" || {
|
|
appError "Failed to backup file: $file"
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
# Download remote file to a local path.
|
|
function fileDownload() {
|
|
local remoteFile="$1"
|
|
local localFile="$2"
|
|
local retries="${3:-5}"
|
|
local delay="${4:-3}"
|
|
|
|
[[ -n "$remoteFile" ]] || { appError "Remote file not specified"; return 1; }
|
|
[[ -n "$localFile" ]] || { appError "Local file not specified"; return 1; }
|
|
|
|
mkdir -p "$(dirname "$localFile")" || { appError "Failed to create folder"; return 1; }
|
|
|
|
local tmpFile="${localFile}.part"
|
|
local i rc curlError lastError
|
|
for ((i = 1; i <= retries; i++)); do
|
|
curlError=$(curl -kfsSL --http1.1 --max-redirs 10 --connect-timeout 30 --speed-time 60 --speed-limit 1024 -C - -o "$tmpFile" "$remoteFile" 2>&1)
|
|
rc=$?
|
|
if [[ $rc -eq 0 ]]; then
|
|
mv -f "$tmpFile" "$localFile" || { appError "Failed to move downloaded file"; return 1; }
|
|
return 0
|
|
fi
|
|
|
|
lastError="$curlError"
|
|
[[ $rc -ne 33 ]] || rm -f "$tmpFile" # rc=33: server doesn't support resume, restart from scratch
|
|
|
|
sleep "$delay"
|
|
done
|
|
|
|
lastError="${lastError//$'\r'/ }"
|
|
lastError="${lastError//$'\n'/ }"
|
|
|
|
appError "Failed to download file after $retries attempts | $lastError"
|
|
return 1
|
|
}
|
|
|
|
# Creates an archive from a source file or directory.
|
|
#
|
|
# Supported archive formats are selected by the target file extension:
|
|
# .zip, .tar.gz, or .tgz.
|
|
#
|
|
# $1 (source): Source file or directory to archive.
|
|
# $2 (target): Target archive file path.
|
|
#
|
|
# Returns:
|
|
# 0 on success, 1 on validation or archive creation failure.
|
|
function archiveCreate() {
|
|
local source="$1"
|
|
if [[ -z "$source" ]]; then
|
|
appError "Source path not specified"
|
|
return 1
|
|
fi
|
|
if [[ ! -e "$source" ]]; then
|
|
appError "Source path not found: $source"
|
|
return 1
|
|
fi
|
|
|
|
local target="$2"
|
|
if [[ -z "$target" ]]; then
|
|
appError "Target file not specified"
|
|
return 1
|
|
fi
|
|
|
|
local compressProgram="${3:-}"
|
|
|
|
local sourcePath sourceBase targetPath targetBase output rc
|
|
sourcePath=$(dirname -- "$source")
|
|
sourceBase=$(basename -- "$source")
|
|
targetPath=$(dirname -- "$target")
|
|
targetBase=$(basename -- "$target")
|
|
|
|
case "$targetBase" in
|
|
*.zip|*.tar.gz|*.tgz)
|
|
;;
|
|
*)
|
|
appError "Unknown type archive: $targetBase"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
mkdir -p -- "$targetPath" || {
|
|
appError "Failed to create directory: $targetPath"
|
|
return 1
|
|
}
|
|
|
|
case "$targetBase" in
|
|
*.zip)
|
|
if [[ -d "$source" ]]; then
|
|
output=$(cd "$source" && zip -qr "$target" . 2>&1)
|
|
else
|
|
output=$(cd "$sourcePath" && zip -qr "$target" "$sourceBase" 2>&1)
|
|
fi
|
|
rc=$?
|
|
[[ $rc -le 1 ]] || { appError "Error creating ZIP (rc=$rc)${output:+: $output}"; return 1; }
|
|
[[ $rc -ne 1 ]] || [[ -z "$output" ]] || appError "Warning creating ZIP${output:+: $output}"
|
|
;;
|
|
*.tar.gz|*.tgz)
|
|
local -a tarCompressFlags
|
|
if [[ -n "$compressProgram" ]]; then
|
|
tarCompressFlags=("--use-compress-program=$compressProgram")
|
|
else
|
|
tarCompressFlags=("-z")
|
|
fi
|
|
if [[ -d "$source" ]]; then
|
|
output=$(tar --warning=no-file-changed -c "${tarCompressFlags[@]}" -f "$target" -C "$source" . 2>&1)
|
|
else
|
|
output=$(tar --warning=no-file-changed -c "${tarCompressFlags[@]}" -f "$target" -C "$sourcePath" "$sourceBase" 2>&1)
|
|
fi
|
|
rc=$?
|
|
[[ $rc -le 1 ]] || { appError "Error creating TAR (rc=$rc)${output:+: $output}"; return 1; }
|
|
[[ $rc -ne 1 ]] || [[ -z "$output" ]] || appError "Warning creating TAR${output:+: $output}"
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
# Extracts an archive into a target directory.
|
|
#
|
|
# Supported archive formats are selected by the source file extension:
|
|
# .zip, .tar.gz, or .tgz.
|
|
#
|
|
# $1 (source): Source archive file path.
|
|
# $2 (target): Target directory where archive contents should be extracted.
|
|
#
|
|
# Returns:
|
|
# 0 on success, 1 on validation or extraction failure.
|
|
function archiveExtract() {
|
|
local source="$1"
|
|
if [[ -z "$source" ]]; then
|
|
appError "Source archive not specified"
|
|
return 1
|
|
fi
|
|
if [[ ! -f "$source" ]]; then
|
|
appError "Source archive not found: $source"
|
|
return 1
|
|
fi
|
|
|
|
local target="$2"
|
|
if [[ -z "$target" ]]; then
|
|
appError "Target directory not specified"
|
|
return 1
|
|
fi
|
|
|
|
local sourceBase output rc
|
|
sourceBase=$(basename -- "$source")
|
|
|
|
case "$sourceBase" in
|
|
*.zip|*.tar.gz|*.tgz)
|
|
;;
|
|
*)
|
|
appError "Unknown type archive: $sourceBase"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
mkdir -p -- "$target" || {
|
|
appError "Failed to create directory: $target"
|
|
return 1
|
|
}
|
|
|
|
case "$sourceBase" in
|
|
*.zip)
|
|
output=$(unzip -oq "$source" -d "$target" 2>&1)
|
|
rc=$?
|
|
if [[ $rc -ne 0 ]]; then
|
|
appError "Error extracting ZIP: $output"
|
|
return 1
|
|
fi
|
|
;;
|
|
*.tar.gz|*.tgz)
|
|
output=$(tar -xzf "$source" -C "$target" 2>&1)
|
|
rc=$?
|
|
if [[ $rc -ne 0 ]]; then
|
|
appError "Error extracting TAR: $output"
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
# Retrieves the size of the specified path (file or directory) in b/kb/mb/gb.
|
|
#
|
|
# $1 (path): The path to the file or directory whose size is to be retrieved.
|
|
# $2 (unit): Unit.
|
|
# $3 (precision): Precision.
|
|
function pathSize() {
|
|
local path="$1"
|
|
local unit="${2:-}"
|
|
local precision="${3:-0}"
|
|
local divisor="1"
|
|
case "${unit,,}" in
|
|
kb) divisor="1024" ;;
|
|
mb) divisor="1048576" ;;
|
|
gb) divisor="1073741824" ;;
|
|
esac
|
|
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
|
|
|
local output error bytes
|
|
if [[ -d "$path" ]]; then
|
|
run output error du -sb "$path" || { appError "$error"; return 1; }
|
|
bytes=$(printf '%s\n' "$output" | cut -f1)
|
|
elif [[ -f "$path" ]]; then
|
|
run output error stat -c %s "$path" || { appError "$error"; return 1; }
|
|
bytes="$output"
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
LC_NUMERIC=C awk -v bytes="$bytes" -v divisor="$divisor" -v precision="$precision" 'BEGIN { printf "%.*f\n", precision, bytes / divisor }'
|
|
}
|
|
|
|
function ftpPathSize() {
|
|
local path="$1"
|
|
local unit="${2:-}"
|
|
local precision="${3:-0}"
|
|
local divisor="1"
|
|
case "${unit,,}" in
|
|
kb) divisor="1024" ;;
|
|
mb) divisor="1048576" ;;
|
|
gb) divisor="1073741824" ;;
|
|
esac
|
|
|
|
local output error total
|
|
if run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$path/" --connect-timeout 10; then
|
|
total=0
|
|
local line file size
|
|
while IFS= read -r line; do
|
|
file=$(awk '{print $NF}' <<< "$line")
|
|
[[ -n "$file" && "$file" != "." && "$file" != ".." ]] || continue
|
|
|
|
if [[ "$line" =~ ^d ]]; then
|
|
size=$(ftpPathSize "$path/$file") || return 1
|
|
else
|
|
size=$(awk '{print $5}' <<< "$line")
|
|
[[ "$size" =~ ^[0-9]+$ ]] || continue
|
|
fi
|
|
(( total += size ))
|
|
done <<< "$output"
|
|
else
|
|
local parent name
|
|
parent=$(dirname "$path")
|
|
name=$(basename "$path")
|
|
run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$parent/" --connect-timeout 10 || {
|
|
appError "$error"; return 1
|
|
}
|
|
total=$(awk -v f="$name" '$NF == f {print $5}' <<< "$output")
|
|
[[ "$total" =~ ^[0-9]+$ ]] || { appError "Cannot determine size of: $path"; return 1; }
|
|
fi
|
|
|
|
LC_NUMERIC=C awk -v b="$total" -v d="$divisor" -v p="$precision" 'BEGIN { printf "%.*f\n", p, b/d }'
|
|
}
|
|
|
|
# Return the contents of a file if it exists and is non-empty, otherwise write the given value to it and return it.
|
|
# $1 (file): path to the file.
|
|
# $2 (value): value to write when the file is missing or empty.
|
|
function fileValueGetOrCreate() {
|
|
local file="${1-}"
|
|
local value="${2-}"
|
|
|
|
if [[ -z "$file" ]]; then
|
|
appError "File not specified"
|
|
return 1
|
|
fi
|
|
if [[ -s "$file" ]]; then
|
|
cat "$file"
|
|
return $?
|
|
fi
|
|
|
|
if [[ -z "$value" ]]; then
|
|
appError "Value not specified"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p -- "$(dirname -- "$file")" || {
|
|
appError "Failed to create folder for: $file"
|
|
return 1
|
|
}
|
|
|
|
install -o root -g root -m 600 /dev/null "$file" || {
|
|
appError "Failed to create file: $file"
|
|
return 1
|
|
}
|
|
|
|
printf '%s\n' "$value" > "$file" || {
|
|
appError "Failed to save value: $file"
|
|
return 1
|
|
}
|
|
|
|
printf '%s\n' "$value"
|
|
}
|
|
|
|
# Retrieves or generates a password and stores it in the specified file.
|
|
#
|
|
# $1: path to the file where the password should be stored.
|
|
# [$2+]: additional parameters are passed to the stringRandom function for generating the password (optional).
|
|
function filePasswordGetOrCreate() {
|
|
local file="${1-}"
|
|
if [[ -z "$file" ]]; then
|
|
appError "File not specified"
|
|
return 1
|
|
fi
|
|
shift || true
|
|
|
|
local value
|
|
if [[ -s "$file" ]]; then
|
|
cat "$file"
|
|
return $?
|
|
fi
|
|
value="$(strRandom "$@")" || {
|
|
appError "Failed to generate password"
|
|
return 1
|
|
}
|
|
|
|
fileValueGetOrCreate "$file" "$value"
|
|
}
|
|
|
|
# Get value by key from "KEY=VALUE" content or file (returns first match)
|
|
function configGet() {
|
|
local file="${1-}"
|
|
local key="${2-}"
|
|
local line value
|
|
|
|
if [[ -z "$file" ]]; then
|
|
appError "Config file not specified"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z "$key" ]]; then
|
|
appError "Config key not specified"
|
|
return 1
|
|
fi
|
|
|
|
[[ -f "$file" ]] || {
|
|
printf '\n'
|
|
return 0
|
|
}
|
|
|
|
line="$(awk -F= -v key="$key" '$1 == key { print; exit }' "$file")" || {
|
|
printf '\n';
|
|
return 0;
|
|
}
|
|
[[ "$line" == "$key="* ]] || {
|
|
printf '\n'
|
|
return 0
|
|
}
|
|
|
|
value="${line#*=}"
|
|
value="$(sed -E 's/[[:space:]]+$//' <<<"$value")"
|
|
|
|
printf '%s\n' "$value"
|
|
}
|
|
|
|
# Ensure key=value is present in file (remove previous key lines, then append).
|
|
function configSet() {
|
|
local file="${1-}"
|
|
local key="${2-}"
|
|
local value="${3-}"
|
|
local tmp
|
|
|
|
if [[ -z "$file" ]]; then
|
|
appError "Config file not specified"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z "$key" ]]; then
|
|
appError "Config key not specified"
|
|
return 1
|
|
fi
|
|
|
|
value="${value//$'\r'/ }"
|
|
value="${value//$'\n'/ }"
|
|
|
|
mkdir -p -- "$(dirname -- "$file")" || {
|
|
appError "Failed to create folder for: $file"
|
|
return 1
|
|
}
|
|
|
|
[[ -f "$file" ]] || install -o root -g root -m 600 /dev/null "$file" || {
|
|
appError "Failed to create file: $file"
|
|
return 1
|
|
}
|
|
|
|
tmp="$(mktemp)" || return 1
|
|
|
|
awk -F= -v key="$key" '$1 != key' "$file" > "$tmp" || true
|
|
|
|
if [[ -n "$value" ]]; then
|
|
printf '%s=%s\n' "$key" "$value" >> "$tmp" || {
|
|
rm -f -- "$tmp"
|
|
appError "Failed to write config value: $key"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
cat "$tmp" > "$file" || {
|
|
rm -f -- "$tmp"
|
|
appError "Failed to update config file: $file"
|
|
return 1
|
|
}
|
|
|
|
rm -f -- "$tmp"
|
|
}
|
|
|
|
# Get param from config or set via configSet if missing
|
|
function configGetOrSet() {
|
|
local file="${1-}"
|
|
local key="${2-}"
|
|
local value="${3-}"
|
|
local current
|
|
|
|
current="$(configGet "$file" "$key")" || return 1
|
|
if [[ -n "$current" ]]; then
|
|
printf '%s\n' "$current"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -z "$value" ]]; then
|
|
appError "Config value not specified: $key"
|
|
return 1
|
|
fi
|
|
|
|
configSet "$file" "$key" "$value" || return 1
|
|
printf '%s\n' "$value"
|
|
}
|
|
|
|
# Get param from config or create via configSet (+gen stringRandom if missing value) if missing
|
|
function configGetOrCreate() {
|
|
local file="${1-}"
|
|
local key="${2-}"
|
|
shift 2 || true
|
|
|
|
local value current
|
|
|
|
current="$(configGet "$file" "$key")" || return 1
|
|
if [[ -n "$current" ]]; then
|
|
printf '%s\n' "$current"
|
|
return 0
|
|
fi
|
|
|
|
value="$(strRandom "$@")" || {
|
|
appError "Failed to generate config value: $key"
|
|
return 1
|
|
}
|
|
|
|
configSet "$file" "$key" "$value" || return 1
|
|
printf '%s\n' "$value"
|
|
}
|
|
|
|
# Check that no line in a file exceeds the specified maximum length, printing offending lines to stderr.
|
|
# $1 (file): path to the file to check.
|
|
# $2 (max): maximum allowed line length in characters.
|
|
function fileCheckLineLength() {
|
|
local file="$1"
|
|
local max="$2"
|
|
local line len num=0 found=0
|
|
|
|
[[ -f "$file" ]] || { appError "File not found: $file"; return 1; }
|
|
[[ "$max" =~ ^[0-9]+$ ]] || { appError "Invalid max line length: $max"; return 1; }
|
|
|
|
while IFS= read -r line || [[ -n $line ]]; do
|
|
((num++))
|
|
len=${#line}
|
|
|
|
if (( len > max )); then
|
|
printf 'Line %d exceeds %d characters (%d)\n' "$num" "$max" "$len" >&2
|
|
found=1
|
|
fi
|
|
done < "$file"
|
|
|
|
return "$found"
|
|
}
|
|
|
|
function fileSignatureDiffList() {
|
|
local path="$1" type="$2" mask="$3"
|
|
[[ -n "$path" && -n "$type" && -n "$mask" ]] || { appError "Missing argument(s)"; return 1; }
|
|
[[ -d "$path" ]] || { appError "Path not found: $path"; return 1; }
|
|
|
|
find "$path" -type "$type" -printf '%m:%u:%g:%p\n' 2>/dev/null | grep -vE "$mask:"
|
|
return 0
|
|
}
|
|
|
|
function fileSignatureDiff() {
|
|
local path="$1" mask="$2" sign
|
|
[[ -n "$path" && -n "$mask" ]] || { appError "Missing argument(s)"; return 1; }
|
|
[[ -d "$path" ]] || { appError "Path not found: $path"; return 1; }
|
|
|
|
sign="$(stat -c '%a:%U:%G' "$path")"
|
|
[[ "$sign" == "$mask" ]] || printf "%s\n" "$sign:$path";
|
|
}
|
|
|
|
function fileSignatureAclDiff() {
|
|
local path="$1" mask="$2"
|
|
[[ -n "$path" && -n "$mask" ]] || { printDanger "Missing argument(s)"; return 1; }
|
|
[[ -d "$path" ]] || { printDanger "Path not found: $path"; return 1; }
|
|
|
|
local acl unexpected
|
|
acl=$(getfacl -p "$path" 2>/dev/null)
|
|
unexpected=$(grep -vE "^(#|\$|(default:)?((user|group|mask)::|other::---|$mask\$))" <<< "$acl" | paste -sd '|')
|
|
|
|
grep -qxF "$mask" <<< "$acl" || unexpected+="${unexpected:+|}missing:$mask"
|
|
grep -qxF "default:$mask" <<< "$acl" || unexpected+="${unexpected:+|}missing:default:$mask"
|
|
|
|
[[ -z "$unexpected" ]] || printf "%s\n" "$unexpected:$path"
|
|
}
|
|
|
|
function fileSignatureCheck() {
|
|
local output error
|
|
run output error fileSignatureDiff "$@" || return 1
|
|
[[ -z "$output" ]]
|
|
}
|
|
|
|
function fileSignatureAclCheck() {
|
|
local output error
|
|
run output error fileSignatureAclDiff "$@" || return 1
|
|
[[ -z "$output" ]]
|
|
}
|
|
|
|
# Checking the availability of a file (URL) for download.
|
|
# Check if a URL is accessible and print the final effective URL on success.
|
|
# $1 (url): the URL to check.
|
|
function urlAccessible() {
|
|
local url="$1" code final
|
|
if [[ -z "$url" ]]; then
|
|
appError "URL not specified"
|
|
return 1
|
|
fi
|
|
|
|
final=$(curl -ksSL --max-redirs 10 --range 0-0 -o /dev/null -w "%{url_effective} %{http_code}" "$url") || return 1
|
|
code="${final##* }" # http code
|
|
final="${final% *}" # final URL
|
|
if [[ "$code" =~ ^[23][0-9]{2}$ ]]; then
|
|
printf "%s" "$final"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Fetch and print the HTTP status code for the given URL.
|
|
# $1 (url): the URL to request.
|
|
function urlCode() {
|
|
local url="$1" code
|
|
if [[ -z "$url" ]]; then
|
|
appError "URL not specified"
|
|
return 1
|
|
fi
|
|
|
|
code=$(curl -ksSL --max-redirs 10 -o /dev/null -w "%{http_code}" "$url") || return 1
|
|
printf "%s" "$code"
|
|
return 0
|
|
}
|
|
|
|
# Derive a local filename from a URL, optionally using a custom base name.
|
|
# $1 (url): the source URL to extract the filename from.
|
|
# [$2] (localFileName): custom base name; if given, the URL extension is appended to it.
|
|
function urlLocalFileGen() {
|
|
local url="$1" code
|
|
if [[ -z "$url" ]]; then
|
|
appError "URL not specified"
|
|
return 1
|
|
fi
|
|
|
|
local localFileName="$2"
|
|
|
|
name="${url##*/}"
|
|
name="${name%%\?*}"
|
|
base="${name%%.*}"
|
|
ext="${name#*.}"
|
|
|
|
if [[ -z "$localFileName" ]]; then
|
|
printf '%s' "$name"
|
|
else
|
|
printf '%s' "$localFileName.$ext"
|
|
fi
|
|
}
|