Compare commits

..

No commits in common. "master" and "2.1" have entirely different histories.
master ... 2.1

67 changed files with 203 additions and 292 deletions

2
.gitignore vendored
View file

@ -8,10 +8,10 @@ doc/man/*.html
doc/man/*.htm doc/man/*.htm
doc/man/*.texi doc/man/*.texi
doc/man/*.man doc/man/*.man
test/*
.*.swp .*.swp
doc/man/*.[0-9] doc/man/*.[0-9]
doc/*.xml doc/*.xml
doc/*/*.xml doc/*/*.xml
*.texi *.texi
*.fo *.fo
*.lock

View file

@ -1,12 +0,0 @@
stages:
- test
unit_tests:
stage: test
script:
- make test
shellcheck:
stage: test
script:
- make shellcheck

View file

@ -29,7 +29,7 @@ ASCIIDOC=asciidoc
DOCBOOKTOTEXI=docbook2x-texi DOCBOOKTOTEXI=docbook2x-texi
DOCBOOKTOMAN=docbook2x-man DOCBOOKTOMAN=docbook2x-man
XSLTPROC=xsltproc XSLTPROC=xsltproc
XSL=/usr/local/share/xsl/docbook/html/docbook.xsl XSL=/usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl
A2X=a2x A2X=a2x
prefix=/usr/packages/ccollect-git prefix=/usr/packages/ccollect-git
@ -41,7 +41,11 @@ manlink=/usr/local/man/man1
path_dir=/usr/local/bin path_dir=/usr/local/bin
path_destination=${path_dir}/${CCOLLECT_DEST} path_destination=${path_dir}/${CCOLLECT_DEST}
docs_archive_name=docs.tar
# where to publish
host=localhost
dir=/home/users/nico/privat/rechner/netz/seiten/www.nico.schottelius.org/src/software/ccollect
docdir=${dir}/documentation
# #
# Asciidoc will be used to generate other formats later # Asciidoc will be used to generate other formats later
@ -75,8 +79,6 @@ DOCBDOCS = ${DOCS:.text=.docbook}
DOC_ALL = ${HTMLDOCS} ${DBHTMLDOCS} ${TEXIDOCS} ${MANPDOCS} ${PDFDOCS} DOC_ALL = ${HTMLDOCS} ${DBHTMLDOCS} ${TEXIDOCS} ${MANPDOCS} ${PDFDOCS}
TEST_LOG_FILE = /tmp/ccollect/ccollect.log
# #
# End user targets # End user targets
# #
@ -87,8 +89,6 @@ all:
@echo "info: only generate Texinfo" @echo "info: only generate Texinfo"
@echo "man: only generate manpage{s}" @echo "man: only generate manpage{s}"
@echo "install: install ccollect to ${prefix}" @echo "install: install ccollect to ${prefix}"
@echo "shellcheck: shellcheck ccollect script"
@echo "test: run unit tests"
html: ${HTMLDOCS} html: ${HTMLDOCS}
htm: ${DBHTMLDOCS} htm: ${DBHTMLDOCS}
@ -175,11 +175,12 @@ t2:
# #
pub: pub:
git push git push
git push github
publish-doc: documentation publish-doc: documentation
@echo "Transferring files to ${host}"
@chmod a+r ${DOCS} ${DOC_ALL} @chmod a+r ${DOCS} ${DOC_ALL}
@tar cf ${docs_archive_name} ${DOCS} ${DOC_ALL} @tar c ${DOCS} ${DOC_ALL} | ssh ${host} "cd ${dir}; tar xv"
@echo "Documentation files are in ${docs_archive_name}"
# #
# Distribution # Distribution
@ -199,52 +200,9 @@ dist: distclean documentation
/tmp/ccollect: /tmp/ccollect:
mkdir -p /tmp/ccollect mkdir -p /tmp/ccollect
shellcheck: ./ccollect test: $(CCOLLECT_SOURCE) /tmp/ccollect
shellcheck -s sh -f gcc -x ./ccollect
test-nico: $(CCOLLECT_SOURCE) /tmp/ccollect
cd ./conf/sources/; for s in *; do CCOLLECT_CONF=../ ../../ccollect daily "$$s"; done cd ./conf/sources/; for s in *; do CCOLLECT_CONF=../ ../../ccollect daily "$$s"; done
touch /tmp/ccollect/$$(ls /tmp/ccollect | head -n1).ccollect-marker touch /tmp/ccollect/$$(ls /tmp/ccollect | head -n1).ccollect-marker
CCOLLECT_CONF=./conf ./ccollect -a daily CCOLLECT_CONF=./conf ./ccollect -a daily
touch /tmp/ccollect/$$(ls /tmp/ccollect | head -n1).ccollect-marker touch /tmp/ccollect/$$(ls /tmp/ccollect | head -n1).ccollect-marker
CCOLLECT_CONF=./conf ./ccollect -a -p daily CCOLLECT_CONF=./conf ./ccollect -a -p daily
test-dir-source:
mkdir -p /tmp/ccollect/source
cp -R -f ./* /tmp/ccollect/source
test-dir-destination:
mkdir -p /tmp/ccollect/backup
test-dir-destination-chint:
mkdir -p /tmp/ccollect/backup-chint
test-fixed-intervals: $(CCOLLECT_SOURCE) test-dir-source test-dir-destination test-dir-destination-chint
for s in ./test/conf/sources/*; do \
CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} daily "$$(basename "$$s")"; \
test "$$(ls -1 /tmp/ccollect/backup | wc -l)" -gt "0" || { cat ${TEST_LOG_FILE}; exit 1; }; \
done
CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} -a -v daily
test "$$(ls -1 /tmp/ccollect/backup | wc -l)" -gt "0" || { cat ${TEST_LOG_FILE}; exit 1; }
CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} -a -p daily
test "$$(ls -1 /tmp/ccollect/backup | wc -l)" -gt "0" || { cat ${TEST_LOG_FILE}; exit 1; }
@printf "\nFixed intervals test ended successfully\n"
test-interval-changing: $(CCOLLECT_SOURCE) test-dir-source test-dir-destination-chint
rm -rf /tmp/ccollect/backup-chint/*
test "$$(ls -1 /tmp/ccollect/backup-chint | wc -l)" -eq "0" || { cat ${TEST_LOG_FILE}; exit 1; }
printf "3" > ./test/conf/sources/local-with-interval/intervals/daily
for x in 1 2 3 4 5; do CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} daily local-with-interval; done
test "$$(ls -1 /tmp/ccollect/backup-chint | wc -l)" -eq "4" || { cat ${TEST_LOG_FILE}; exit 1; }
printf "5" > ./test/conf/sources/local-with-interval/intervals/daily
for x in 1 2 3 4 5 6 7; do CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} daily local-with-interval; done
test "$$(ls -1 /tmp/ccollect/backup-chint | wc -l)" -eq "6" || { cat ${TEST_LOG_FILE}; exit 1; }
printf "4" > ./test/conf/sources/local-with-interval/intervals/daily
for x in 1 2 3 4 5 6; do CCOLLECT_CONF=./test/conf ./ccollect -l ${TEST_LOG_FILE} daily local-with-interval; done
test "$$(ls -1 /tmp/ccollect/backup-chint | wc -l)" -eq "5" || { cat ${TEST_LOG_FILE}; exit 1; }
printf "3" > ./test/conf/sources/local-with-interval/intervals/daily
@printf "\nInterval changing test ended successfully\n"
test: test-fixed-intervals test-interval-changing
test -f "${TEST_LOG_FILE}"
@printf "\nTests ended successfully\n"

229
ccollect
View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# 2005-2013 Nico Schottelius (nico-ccollect at schottelius.org) # 2005-2013 Nico Schottelius (nico-ccollect at schottelius.org)
# 2016-2019 Darko Poljak (darko.poljak at gmail.com) # 2016 Darko Poljak (darko.poljak at gmail.com)
# #
# This file is part of ccollect. # This file is part of ccollect.
# #
@ -27,9 +27,9 @@ set -u
# #
# Standard variables (stolen from cconf) # Standard variables (stolen from cconf)
# #
__mydir="${0%/*}" __pwd="$(pwd -P)"
__abs_mydir="$(cd "$__mydir" && pwd -P)" __mydir="${0%/*}"; __abs_mydir="$(cd "$__mydir" && pwd -P)"
__myname=${0##*/} __myname=${0##*/}; __abs_myname="$__abs_mydir/$__myname"
# #
# where to find our configuration and temporary file # where to find our configuration and temporary file
@ -41,12 +41,11 @@ CPREEXEC="${CDEFAULTS}/pre_exec"
CPOSTEXEC="${CDEFAULTS}/post_exec" CPOSTEXEC="${CDEFAULTS}/post_exec"
CMARKER=".ccollect-marker" CMARKER=".ccollect-marker"
TMP="$(mktemp "/tmp/${__myname}.XXXXXX")" export TMP="$(mktemp "/tmp/${__myname}.XXXXXX")"
export TMP
CONTROL_PIPE="/tmp/${__myname}-control-pipe" CONTROL_PIPE="/tmp/${__myname}-control-pipe"
VERSION="2.10" VERSION="2.1"
RELEASE="2020-08-26" RELEASE="2017-03-22"
HALF_VERSION="ccollect ${VERSION}" HALF_VERSION="ccollect ${VERSION}"
FULL_VERSION="ccollect ${VERSION} (${RELEASE})" FULL_VERSION="ccollect ${VERSION} (${RELEASE})"
@ -77,9 +76,8 @@ LOCKFD=4
lock_flock() lock_flock()
{ {
# $1 = source to backup # $1 = source to backup
# shellcheck disable=SC2059
lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")" lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")"
eval "exec ${LOCKFD}> '${lockfile}'" eval "exec ${LOCKFD}> ${lockfile}"
flock -n ${LOCKFD} && return 0 || return 1 flock -n ${LOCKFD} && return 0 || return 1
} }
@ -87,7 +85,6 @@ lock_flock()
unlock_flock() unlock_flock()
{ {
# $1 = source to backup # $1 = source to backup
# shellcheck disable=SC2059
lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")" lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")"
eval "exec ${LOCKFD}>&-" eval "exec ${LOCKFD}>&-"
rm -f "${lockfile}" rm -f "${lockfile}"
@ -99,7 +96,6 @@ unlock_flock()
lock_mkdir() lock_mkdir()
{ {
# $1 = source to backup # $1 = source to backup
# shellcheck disable=SC2059
lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")" lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")"
mkdir "${lockfile}" && return 0 || return 1 mkdir "${lockfile}" && return 0 || return 1
@ -108,7 +104,6 @@ lock_mkdir()
unlock_mkdir() unlock_mkdir()
{ {
# $1 = source to backup # $1 = source to backup
# shellcheck disable=SC2059
lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")" lockfile="${LOCKDIR}/$(printf "${LOCKFILE_PATTERN}" "$1")"
rmdir "${lockfile}" rmdir "${lockfile}"
@ -117,7 +112,7 @@ unlock_mkdir()
# #
# determine locking tool: flock or mkdir # determine locking tool: flock or mkdir
# #
if command -v flock > /dev/null 2>&1 if $(which flock > /dev/null 2>&1)
then then
lockf="lock_flock" lockf="lock_flock"
unlockf="unlock_flock" unlockf="unlock_flock"
@ -142,7 +137,6 @@ LOGONLYERRORS=""
# catch signals # catch signals
# #
TRAPFUNC="rm -f \"${TMP}\"" TRAPFUNC="rm -f \"${TMP}\""
# shellcheck disable=SC2064
trap "${TRAPFUNC}" 1 2 15 trap "${TRAPFUNC}" 1 2 15
# #
@ -153,7 +147,12 @@ trap "${TRAPFUNC}" 1 2 15
# see: http://www.tldp.org/LDP/abs/html/intandnonint.html # see: http://www.tldp.org/LDP/abs/html/intandnonint.html
_is_interactive() _is_interactive()
{ {
[ -t 0 ] || [ -p /dev/stdin ] [ -t 0 -o -p /dev/stdin ]
}
add_name()
{
awk "{ print \"[${name}] \" \$0 }"
} }
# #
@ -166,29 +165,16 @@ delete_from_file()
file="$1"; shift file="$1"; shift
suffix="" # It will be set, if deleting incomplete backups. suffix="" # It will be set, if deleting incomplete backups.
[ $# -eq 1 ] && suffix="$1" && shift [ $# -eq 1 ] && suffix="$1" && shift
# dirs for deletion will be moved to this trash dir inside destination dir while read to_remove; do
# - for fast mv operation
trash="$(mktemp -d ".trash.XXXXXX")"
while read -r to_remove; do
mv "${to_remove}" "${trash}" ||
_exit_err "Moving ${to_remove} to ${trash} failed."
set -- "$@" "${to_remove}" set -- "$@" "${to_remove}"
if [ "${suffix}" ]; then if [ "${suffix}" ]; then
to_remove_no_suffix="$(echo "${to_remove}" | sed "s/$suffix\$//")" to_remove_no_suffix="$(echo ${to_remove} | sed "s/$suffix\$//")"
mv "${to_remove_no_suffix}" "${trash}" ||
_exit_err "Moving ${to_remove_no_suffix} to ${trash} failed."
set -- "$@" "${to_remove_no_suffix}" set -- "$@" "${to_remove_no_suffix}"
fi fi
done < "${file}" done < "${file}"
_techo "Removing $* in ${trash}..." _techo "Removing $@ ..."
empty_dir=".empty-dir" [ "${VVERBOSE}" ] && echo rm "$@"
mkdir "${empty_dir}" || _exit_err "Empty directory ${empty_dir} cannot be created." rm -rf "$@" || _exit_err "Removing $@ failed."
[ "${VVERBOSE}" ] && echo "Starting: rsync -a --delete ${empty_dir} ${trash}"
# rsync needs ending slash for directory content
rsync -a --delete "${empty_dir}/" "${trash}/" || _exit_err "Removing $* failed."
rmdir "${trash}" || _exit_err "Removing ${trash} directory failed"
rmdir "${empty_dir}" || _exit_err "Removing ${empty_dir} directory failed"
_techo "Removing $* in ${trash} finished."
} }
display_version() display_version()
@ -238,7 +224,7 @@ unlock()
# stdout version # stdout version
_techo_stdout() _techo_stdout()
{ {
echo "$(${DDATE}): $*" echo "$(${DDATE}): $@"
} }
# syslog version # syslog version
@ -266,16 +252,13 @@ _techo()
{ {
if [ "${LOGLEVEL}" = "a" ] if [ "${LOGLEVEL}" = "a" ]
then then
# name is exported before calling this function
# shellcheck disable=SC2154
set -- ${name:+"[${name}]"} "$@"
"${_techof}" "$@" "${_techof}" "$@"
fi fi
} }
_techo_err() _techo_err()
{ {
_techo "Error: $*" _techo "Error: $@"
} }
_exit_err() _exit_err()
@ -376,7 +359,7 @@ else
LOGLEVEL="a" LOGLEVEL="a"
fi fi
if [ "${LOGFILE}" ] || [ "${SYSLOG}" ] if [ "${LOGFILE}" -o "${SYSLOG}" ]
then then
if [ "${LOGONLYERRORS}" ] if [ "${LOGONLYERRORS}" ]
then then
@ -386,7 +369,8 @@ fi
# check that MAX_JOBS is natural number > 0 # check that MAX_JOBS is natural number > 0
# empty string means run all in parallel # empty string means run all in parallel
if ! echo "${MAX_JOBS}" | grep -q -E '^[1-9][0-9]*$|^$' echo "${MAX_JOBS}" | grep -q -E '^[1-9][0-9]*$|^$'
if [ "$?" -ne 0 ]
then then
_exit_err "Invalid max jobs value \"${MAX_JOBS}\"" _exit_err "Invalid max jobs value \"${MAX_JOBS}\""
fi fi
@ -419,22 +403,19 @@ if [ "${USE_ALL}" = 1 ]; then
( cd "${CSOURCES}" && ls -1 > "${TMP}" ) || \ ( cd "${CSOURCES}" && ls -1 > "${TMP}" ) || \
_exit_err "Listing of sources failed. Aborting." _exit_err "Listing of sources failed. Aborting."
while read -r tmp; do while read tmp; do
eval export "source_${no_sources}=\"${tmp}\"" eval export source_${no_sources}=\"${tmp}\"
no_sources=$((no_sources + 1)) no_sources=$((${no_sources}+1))
done < "${TMP}" done < "${TMP}"
else else
# #
# Get sources from command line # Get sources from command line
# #
while [ "$#" -ge 1 ]; do while [ "$#" -ge 1 ]; do
eval "arg=\"\$1\"" eval arg=\"\$1\"; shift
shift
# arg is assigned in the eval above eval export source_${no_sources}=\"${arg}\"
# shellcheck disable=SC2154 no_sources="$((${no_sources}+1))"
eval export "source_${no_sources}=\"${arg}\""
no_sources="$((no_sources + 1))"
done done
fi fi
@ -469,20 +450,14 @@ fi
if [ "${PARALLEL}" ]; then if [ "${PARALLEL}" ]; then
mkfifo "${CONTROL_PIPE}" mkfifo "${CONTROL_PIPE}"
# fd 5 is tied to control pipe # fd 5 is tied to control pipe
eval "exec 5<>'${CONTROL_PIPE}'" eval "exec 5<>${CONTROL_PIPE}"
TRAPFUNC="${TRAPFUNC}; rm -f \"${CONTROL_PIPE}\"" TRAPFUNC="${TRAPFUNC}; rm -f \"${CONTROL_PIPE}\""
# shellcheck disable=SC2064
trap "${TRAPFUNC}" 0 1 2 15 trap "${TRAPFUNC}" 0 1 2 15
# determine how much parallel jobs to prestart # determine how much parallel jobs to prestart
if [ "${MAX_JOBS}" ] if [ "${MAX_JOBS}" ] && [ "${MAX_JOBS}" -le "${no_sources}" ]
then then
if [ "${MAX_JOBS}" -le "${no_sources}" ] prestart="${MAX_JOBS}"
then
prestart="${MAX_JOBS}"
else
prestart="${no_sources}"
fi
else else
prestart=0 prestart=0
fi fi
@ -494,7 +469,7 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Get current source # Get current source
# #
eval export name=\"\$source_${source_no}\" eval export name=\"\$source_${source_no}\"
source_no=$((source_no + 1)) source_no=$((${source_no}+1))
# #
# Start ourself, if we want parallel execution # Start ourself, if we want parallel execution
@ -509,12 +484,12 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
then then
# run prestart child if pending # run prestart child if pending
{ "$0" "${INTERVAL}" "${name}"; printf '\n' >&5; } & { "$0" "${INTERVAL}" "${name}"; printf '\n' >&5; } &
prestart=$((prestart - 1)) prestart=$((${prestart} - 1))
continue continue
else else
# each time a child finishes we get a line from the pipe # each time a child finishes we get a line from the pipe
# and then launch another child # and then launch another child
while read -r line while read line
do do
{ "$0" "${INTERVAL}" "${name}"; printf '\n' >&5; } & { "$0" "${INTERVAL}" "${name}"; printf '\n' >&5; } &
# get out of loop so we can contnue with main loop # get out of loop so we can contnue with main loop
@ -550,7 +525,7 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Standard configuration checks # Standard configuration checks
# #
if [ ! -e "${backup}" ]; then if [ ! -e "${backup}" ]; then
_exit_err "Source \"${backup}\" does not exist." _exit_err "Source does not exist."
fi fi
# #
@ -568,7 +543,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# redefine trap to also unlock (rm lockfile) # redefine trap to also unlock (rm lockfile)
TRAPFUNC="${TRAPFUNC}; unlock \"${name}\"" TRAPFUNC="${TRAPFUNC}; unlock \"${name}\""
# shellcheck disable=SC2064
trap "${TRAPFUNC}" 1 2 15 trap "${TRAPFUNC}" 1 2 15
# #
@ -588,10 +562,10 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
for opt in verbose very_verbose summary exclude rsync_options \ for opt in verbose very_verbose summary exclude rsync_options \
delete_incomplete rsync_failure_codes \ delete_incomplete rsync_failure_codes \
mtime quiet_if_down ; do mtime quiet_if_down ; do
if [ -f "${backup}/${opt}" ] || [ -f "${backup}/no_${opt}" ]; then if [ -f "${backup}/${opt}" -o -f "${backup}/no_${opt}" ]; then
eval "c_$opt=\"${backup}/$opt\"" eval c_$opt=\"${backup}/$opt\"
else else
eval "c_$opt=\"${CDEFAULTS}/$opt\"" eval c_$opt=\"${CDEFAULTS}/$opt\"
fi fi
done done
@ -611,8 +585,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# #
# Sort by ctime (default) or mtime (configuration option) # Sort by ctime (default) or mtime (configuration option)
# #
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_mtime}" ] ; then if [ -f "${c_mtime}" ] ; then
TSORT="t" TSORT="t"
else else
@ -656,8 +628,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# #
# Exclude list # Exclude list
# #
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_exclude}" ]; then if [ -f "${c_exclude}" ]; then
set -- "$@" "--exclude-from=${c_exclude}" set -- "$@" "--exclude-from=${c_exclude}"
fi fi
@ -665,8 +635,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# #
# Output a summary # Output a summary
# #
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_summary}" ]; then if [ -f "${c_summary}" ]; then
set -- "$@" "--stats" set -- "$@" "--stats"
fi fi
@ -675,8 +643,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Verbosity for rsync, rm, and mkdir # Verbosity for rsync, rm, and mkdir
# #
VVERBOSE="" VVERBOSE=""
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_very_verbose}" ]; then if [ -f "${c_very_verbose}" ]; then
set -- "$@" "-vv" set -- "$@" "-vv"
VVERBOSE="-v" VVERBOSE="-v"
@ -687,21 +653,9 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# #
# Extra options for rsync provided by the user # Extra options for rsync provided by the user
# #
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_rsync_options}" ]; then if [ -f "${c_rsync_options}" ]; then
while read -r line; do while read line; do
# Trim line. set -- "$@" "${line}"
ln=$(echo "${line}" | awk '{$1=$1;print;}')
# Only if ln is non zero length string.
#
# If ln is empty then rsync '' DEST evaluates
# to transfer current directory to DEST which would
# with specific options destroy DEST content.
if [ -n "${ln}" ]
then
set -- "$@" "${ln}"
fi
done < "${c_rsync_options}" done < "${c_rsync_options}"
fi fi
@ -709,8 +663,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Check: source is up and accepting connections (before deleting old backups!) # Check: source is up and accepting connections (before deleting old backups!)
# #
if ! rsync "$@" "${source}" >/dev/null 2>"${TMP}" ; then if ! rsync "$@" "${source}" >/dev/null 2>"${TMP}" ; then
# variable is assigned using eval
# shellcheck disable=SC2154
if [ ! -f "${c_quiet_if_down}" ]; then if [ ! -f "${c_quiet_if_down}" ]; then
cat "${TMP}" cat "${TMP}"
fi fi
@ -730,77 +682,36 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# #
# Check incomplete backups (needs echo to remove newlines) # Check incomplete backups (needs echo to remove newlines)
# #
# shellcheck disable=SC2010
ls -1 | grep "${CMARKER}\$" > "${TMP}"; ret=$? ls -1 | grep "${CMARKER}\$" > "${TMP}"; ret=$?
if [ "$ret" -eq 0 ]; then if [ "$ret" -eq 0 ]; then
_techo "Incomplete backups: $(cat "${TMP}")" _techo "Incomplete backups: $(echo $(cat "${TMP}"))"
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "${c_delete_incomplete}" ]; then if [ -f "${c_delete_incomplete}" ]; then
delete_from_file "${TMP}" "${CMARKER}" & delete_from_file "${TMP}" "${CMARKER}"
fi fi
fi fi
#
# Include current time in name, not the time when we began to remove above
#
destination_name="${INTERVAL}.$(${CDATE}).$$-${source_no}"
export destination_name
destination_dir="${ddir}/${destination_name}"
export destination_dir
# #
# Check: maximum number of backups is reached? # Check: maximum number of backups is reached?
# #
# shellcheck disable=SC2010
count="$(ls -1 | grep -c "^${INTERVAL}\\.")" count="$(ls -1 | grep -c "^${INTERVAL}\\.")"
_techo "Existing backups: ${count} Total keeping backups: ${c_interval}" _techo "Existing backups: ${count} Total keeping backups: ${c_interval}"
if [ "${count}" -ge "${c_interval}" ]; then if [ "${count}" -ge "${c_interval}" ]; then
# Use oldest directory as new backup destination directory. remove="$((${count} - ${c_interval} + 1))"
# It need not to be deleted, rsync will sync its content. _techo "Removing ${remove} backup(s)..."
# shellcheck disable=SC2010
oldest_bak=$(ls -${TSORT}1r | grep "^${INTERVAL}\\." | head -n 1 || \
_exit_err "Listing oldest backup failed")
_techo "Using ${oldest_bak} for destination dir ${destination_dir}"
if mv "${oldest_bak}" "${destination_dir}"; then
# Touch dest dir so it is not sorted wrong in listings below.
ls_rm_exclude=$(basename "${destination_dir}")
# We have something to remove only if count > interval. ls -${TSORT}1r | grep "^${INTERVAL}\\." | head -n "${remove}" > "${TMP}" || \
remove="$((count - c_interval))" _exit_err "Listing old backups failed"
else
_techo_err "Renaming oldest backup ${oldest_bak} to ${destination_dir} failed, removing it."
remove="$((count - c_interval + 1))"
ls_rm_exclude=""
fi
if [ "${remove}" -gt 0 ]; then
_techo "Removing ${remove} backup(s)..."
if [ -z "${ls_rm_exclude}" ]; then delete_from_file "${TMP}"
# shellcheck disable=SC2010
ls -${TSORT}1r | grep "^${INTERVAL}\\." | head -n "${remove}" > "${TMP}" || \
_exit_err "Listing old backups failed"
else
# shellcheck disable=SC2010
ls -${TSORT}1r | grep -v "${ls_rm_exclude}" | grep "^${INTERVAL}\\." | head -n "${remove}" > "${TMP}" || \
_exit_err "Listing old backups failed"
fi
delete_from_file "${TMP}" &
fi
fi fi
# #
# Check for backup directory to clone from: Always clone from the latest one! # Check for backup directory to clone from: Always clone from the latest one!
# Exclude destination_dir from listing, it can be touched reused and renamed
# oldest existing destination directory.
# #
dest_dir_name=$(basename "${destination_dir}") last_dir="$(ls -${TSORT}p1 | grep '/$' | head -n 1)" || \
# shellcheck disable=SC2010
last_dir="$(ls -${TSORT}p1 | grep '/$' | grep -v "${dest_dir_name}" | head -n 1)" || \
_exit_err "Failed to list contents of ${ddir}." _exit_err "Failed to list contents of ${ddir}."
# #
@ -811,6 +722,12 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
_techo "Hard linking from ${last_dir}" _techo "Hard linking from ${last_dir}"
fi fi
#
# Include current time in name, not the time when we began to remove above
#
export destination_name="${INTERVAL}.$(${CDATE}).$$-${source_no}"
export destination_dir="${ddir}/${destination_name}"
# #
# Mark backup running and go back to original directory # Mark backup running and go back to original directory
# #
@ -824,11 +741,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
rsync "$@" "${source}" "${destination_dir}"; ret=$? rsync "$@" "${source}" "${destination_dir}"; ret=$?
_techo "Finished backup (rsync return code: $ret)." _techo "Finished backup (rsync return code: $ret)."
#
# export rsync return code, might be useful in post_exec
#
export rsync_return_code=$ret
# #
# Set modification time (mtime) to current time, if sorting by mtime is enabled # Set modification time (mtime) to current time, if sorting by mtime is enabled
# #
@ -838,10 +750,8 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Check if rsync exit code indicates failure. # Check if rsync exit code indicates failure.
# #
fail="" fail=""
# variable is assigned using eval
# shellcheck disable=SC2154
if [ -f "$c_rsync_failure_codes" ]; then if [ -f "$c_rsync_failure_codes" ]; then
while read -r code ; do while read code ; do
if [ "$ret" = "$code" ]; then if [ "$ret" = "$code" ]; then
fail=1 fail=1
fi fi
@ -861,16 +771,6 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
_techo "Warning: rsync failed with return code $ret." _techo "Warning: rsync failed with return code $ret."
fi fi
#
# Create symlink to newest backup
#
# shellcheck disable=SC2010
latest_dir="$(ls -${TSORT}p1 "${ddir}" | grep '/$' | head -n 1)" || \
_exit_err "Failed to list content of ${ddir}."
ln -snf "${ddir}/${latest_dir}" "${ddir}/current" || \
_exit_err "Failed to create 'current' symlink."
# #
# post_exec # post_exec
# #
@ -888,18 +788,15 @@ while [ "${source_no}" -lt "${no_sources}" ]; do
# Time calculation # Time calculation
# #
end_s="$(${SDATE})" end_s="$(${SDATE})"
full_seconds="$((end_s - begin_s))" full_seconds="$((${end_s} - ${begin_s}))"
hours="$((full_seconds / 3600))" hours="$((${full_seconds} / 3600))"
minutes="$(((full_seconds % 3600) / 60))" minutes="$(((${full_seconds} % 3600) / 60))"
seconds="$((full_seconds % 60))" seconds="$((${full_seconds} % 60))"
_techo "Backup lasted: ${hours}:${minutes}:${seconds} (h:m:s)" _techo "Backup lasted: ${hours}:${minutes}:${seconds} (h:m:s)"
unlock "${name}" unlock "${name}"
) | add_name
# wait for children (doing delete_from_file) if any still running
wait
) || exit
done done
# #

View file

@ -1,6 +1,6 @@
Summary: (pseudo) incremental backup with different exclude lists using hardlinks and rsync Summary: (pseudo) incremental backup with different exclude lists using hardlinks and rsync
Name: ccollect Name: ccollect
Version: 2.3 Version: 0.8.1
Release: 0 Release: 0
URL: http://www.nico.schottelius.org/software/ccollect URL: http://www.nico.schottelius.org/software/ccollect
Source0: http://www.nico.schottelius.org/software/ccollect/%{name}-%{version}.tar.bz2 Source0: http://www.nico.schottelius.org/software/ccollect/%{name}-%{version}.tar.bz2
@ -23,10 +23,10 @@ Only the inodes used by the hardlinks and the changed files need additional spac
%install %install
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
#Installing main ccollect and /etc directory #Installing main ccollect.sh and /etc directory
%__install -d 755 %buildroot%_bindir %__install -d 755 %buildroot%_bindir
%__install -d 755 %buildroot%_sysconfdir/%name %__install -d 755 %buildroot%_sysconfdir/%name
%__install -m 755 ccollect %buildroot%_bindir/ %__install -m 755 ccollect.sh %buildroot%_bindir/
#bin files from tools directory #bin files from tools directory
for t in $(ls tools/ccollect_*) ; do for t in $(ls tools/ccollect_*) ; do
@ -45,13 +45,19 @@ done
#Addition documentation and some config tools #Addition documentation and some config tools
%__install -d 755 %buildroot%_datadir/%name/tools %__install -d 755 %buildroot%_datadir/%name/tools
%__install -m 755 tools/called_from_remote_pre_exec %buildroot%_datadir/%name/tools
%__cp -pr tools/config-pre-* %buildroot%_datadir/%name/tools %__cp -pr tools/config-pre-* %buildroot%_datadir/%name/tools
%__install -m 755 tools/report_success %buildroot%_datadir/%name/tools %__install -m 755 tools/gnu-du-backup-size-compare.sh %buildroot%_datadir/%name/tools
%__install -m 755 tools/report_success.sh %buildroot%_datadir/%name/tools
%clean %clean
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
%post
%__ln_s %_bindir/ccollect.sh %_bindir/ccollect
%preun
unlink %_bindir/ccollect
%files %files
%defattr(-,root,root) %defattr(-,root,root)
%_bindir/ccollect* %_bindir/ccollect*

View file

@ -1,7 +1,7 @@
ccollect - Installing, Configuring and Using ccollect - Installing, Configuring and Using
============================================ ============================================
Nico Schottelius <nico-ccollect__@__schottelius.org> Nico Schottelius <nico-ccollect__@__schottelius.org>
2.10, for ccollect 2.10, Initial Version from 2006-01-13 2.1, for ccollect 2.1, Initial Version from 2006-01-13
:Author Initials: NS :Author Initials: NS
@ -26,7 +26,6 @@ Supported and tested operating systems and architectures
- Mac OS X 10.5 - Mac OS X 10.5
- NetBSD on alpha/amd64/i386/sparc/sparc64 - NetBSD on alpha/amd64/i386/sparc/sparc64
- OpenBSD on amd64 - OpenBSD on amd64
- Windows by installing Cygwin, OpenSSH and rsync
It *should* run on any Unix that supports `rsync` and has a POSIX-compatible It *should* run on any Unix that supports `rsync` and has a POSIX-compatible
bourne shell. If your platform is not listed above and you have it successfully bourne shell. If your platform is not listed above and you have it successfully

View file

@ -1 +0,0 @@
* Add options for stdout, file and syslog logging (Darko Poljak)

View file

@ -1 +0,0 @@
* Add 'current' symlink to backup destinations (Steffen Zieger)

View file

@ -1 +0,0 @@
* Bugfix: empty rsync_options line causes destroying source (Darko Poljak)

View file

@ -1 +0,0 @@
* Bugfix: Fix parallel mode deadlock when max jobs > number of sources (Darko Poljak)

View file

@ -1,2 +0,0 @@
* Add Windows(Cygwin) as supported OS
* Add source name tag in log line

View file

@ -1 +0,0 @@
* Bugfix: exit in case of subshell error

View file

@ -1 +0,0 @@
* Improve performance, improve process of deletion of old backups

View file

@ -1 +0,0 @@
* Fix shellcheck reported issues

View file

@ -1 +0,0 @@
* Fix excluding destination dir from removal

View file

@ -1 +0,0 @@
* Make rsync return code available in post_exec (Steffen Zieger)

View file

@ -1 +1 @@
* Fix 'current' symlink (Jun Futagawa) * Add options for stdout, file and syslog logging (Darko Poljak)

View file

@ -1 +0,0 @@
5

View file

@ -1 +0,0 @@
4

View file

@ -1 +0,0 @@
4

View file

@ -1 +0,0 @@
2

View file

@ -1,3 +0,0 @@
#!/bin/sh
echo 'General post_exec executed.'

View file

@ -1,3 +0,0 @@
#!/bin/sh
echo 'General pre__exec executed.'

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup-chint

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1 +0,0 @@
.git

View file

@ -1 +0,0 @@
/tmp/ccollect/source

View file

@ -1 +0,0 @@
/tmp/ccollect/backup

View file

@ -1,5 +0,0 @@
#!/bin/cat
######################################################################
Source post_exec executed.
######################################################################

View file

@ -1,5 +0,0 @@
#!/bin/cat
######################################################################
Source pre_exec executed.
######################################################################

View file

@ -1 +0,0 @@
/tmp/ccollect/source

18
test/exec.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/sh
host="home.schottelius.org"
host=""
set -x
pcmd()
{
echo "$#", "$@"
if [ "$host" ]; then
ssh "$host" "$@"
else
$@
fi
}
#pcmd ls /
#pcmd cd /; ls "/is not there"
pcmd cd / && ls

1
test/local.sh Executable file
View file

@ -0,0 +1 @@
CCOLLECT_CONF=./conf ./ccollect.sh daily -v local1

1
test/remote.sh Executable file
View file

@ -0,0 +1 @@
CCOLLECT_CONF=./conf ./ccollect.sh daily -v remote1

23
test/return-value.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/sh
ls /surely-not-existent$$ 2>/dev/null
if [ "$?" -ne 0 ]; then
echo "$?"
fi
ls /surely-not-existent$$ 2>/dev/null
ret=$?
if [ "$ret" -ne 0 ]; then
echo "$ret"
fi
# if is true, ls is fales
if [ "foo" = "foo" ]; then
ls /surely-not-existent$$ 2>/dev/null
fi
# but that's still the return of ls and not of fi
echo $?

View file

@ -0,0 +1,29 @@
#!/bin/sh
#
# 2009 Nico Schottelius (nico-ccollect at schottelius.org)
#
# This file is part of ccollect.
#
# ccollect is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ccollect is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ccollect. If not, see <http://www.gnu.org/licenses/>.
#
#
# Test the ccollect tools suite
#
set -x
tmp="$(mktemp /tmp/ccollect-tools.XXXXXXXXXXX)"
rm -rf "${tmp}"

44
test/test-ccollect1.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/sh
#
# Nico Schottelius <nico-linux //@// schottelius.org>
# Date: 27-Jan-2007
# Last Modified: -
# Description:
#
ccollect=../ccollect.sh
testdir="$(dirname $0)/test-backups"
confdir="$(dirname $0)/test-config"
source="$(hostname)"
source_source="/tmp"
interval="taeglich"
# backup destination
mkdir -p "$testdir"
source_dest="$(cd "$testdir"; pwd -P)"
# configuration
mkdir -p "${confdir}/sources/${source}"
ln -s "$source_dest" "${confdir}/sources/${source}/destination"
echo "$source_source" > "${confdir}/sources/${source}/source"
touch "${confdir}/sources/${source}/summary"
touch "${confdir}/sources/${source}/verbose"
mkdir -p "${confdir}/defaults/intervals/"
echo 3 > "${confdir}/defaults/intervals/$interval"
# create backups
CCOLLECT_CONF="$confdir" "$ccollect" "$interval" -p -a
touch "${source_source}/$(date +%s)-$$.1982"
CCOLLECT_CONF="$confdir" "$ccollect" "$interval" -p -a
touch "${source_source}/$(date +%s)-$$.42"
CCOLLECT_CONF="$confdir" "$ccollect" "$interval" -p -a
du -sh "$testdir"
du -shl "$testdir"
echo "Delete $testdir and $confdir after test"