#!/bin/sh
# Upgrade the rwfs partition if necessary.
# Parameters to save and restore during SOFT_RESET.
backup_parameters="
RemoteService.Enabled
RemoteService.ProxyServer
RemoteService.ProxyPort
RemoteService.ProxyLogin
RemoteService.ProxyPassword
RemoteService.ProxyAuth
RemoteService.ProxyDispatcherOnly
System.BoaPort
"
backup_files=
# reads the conf file for the paths to the three backup-files
# $BCK_PARAMETERS_FILE, $BCK_FILES_FILE $BCK_GROUPS_FILE
bck_conf=/etc/bck/fd_survive.conf
[ ! -r $bck_conf ] || . $bck_conf
# The three read files are created by factorydefaultsurvive.cgi which is a
# script that adds files and parameters to "backup_parameters",
# "backup_files and backup_groups" to be saved during a software reset.
[ ! -r "$BCK_PARAMETERS_FILE" ] ||
backup_parameters="$backup_parameters $(cat $BCK_PARAMETERS_FILE)"
[ ! -r "$BCK_FILES_FILE" ] ||
backup_files="$backup_files $(cat $BCK_FILES_FILE)"
backup_groups="
"
[ ! -r "$BCK_GROUPS_FILE" ] ||
backup_groups="$backup_groups $(cat $BCK_GROUPS_FILE)"
PHC="parhandclient --no-mapped"
BACKUP_DIR=/tmp
UEP=/usr/etc/param
UED=/usr/etc/defaultfiles
ED=/etc/dynamic
EDP=$ED/param
EDT=$BACKUP_DIR/$ED/tmp
EPP=/etc/parhand/portmanager
PHBCK=/etc/parameters_backup.txt
PHTMPBCK=/tmp/parameters_backup.txt
BCK_ARCHIVE=/tmp/bck.tar
BCK_PERMISSIONS_FILE=/tmp/backup_permissions
BCK_BACKUP_FILES_FILE=/tmp/bck_files
PREUPGRADE_SCRIPTS_DIR=/usr/lib/fsupgrade/pre-upgrade.d
SAVEFILES_DIR=/usr/lib/fsupgrade/upgrade-persistent.d
IV_ROLLBACK_DIR=/usr/local/fsupgrade-rollback
BCK_IGNORELIST_FILE=/tmp/bck_ignore.list
# Location for scripts to run after all other handling is done.
POST_UPGRADE_SCRIPTS_LOC=/etc/fsupgrade.d
# Location for scripts to run after all other handling is done
# when a reset has been made.
POST_UPGRADE_RESET_SCRIPTS_LOC=/etc/fsreset.d
SELF=${0##*/}
file_functions=./lib/rcscripts/sh/files.sh
[ -f $file_functions ] && . $file_functions ||
error "Failed to source helper '$file_functions'!"
info() {
echo "$SELF: $*">&2
echo "$SELF: $*">>/run/fsupgrade.log
}
warning() {
info "$*!"
}
error() {
warning "$*"
exit 1
}
make_dir() {
local f=make_dir
[ $# -eq 1 ] && [ "$1" ] || error "$f: wrong number of arguments"
[ -d $1 ] || mkdir -p $1 ||
error "$f: Failed to create directory '$1'"
}
files_functions=/lib/rcscripts/sh/files.sh
[ -f $files_functions ] && . $files_functions ||
error "Failed to source helper '$files_functions'!"
start_parhand_dyn() {
info "Starting parhand..."
if parhand /dev/null 50011 -d $EDP -daemon -upgrade; then
info "done"
else
warning "start_parhand_dyn: parhand failed"
return 1
fi
}
start_parhand() {
local _epp=
info "Starting parhand..."
[ ! -d $EPP ] || _epp="-d $EPP"
if parhand /dev/null 50011 -d $UEP -d $EDP $_epp -daemon -upgrade; then
info "done"
else
warning "start_parhand: parhand failed"
return 1
fi
}
# Stop a daemon. The method used to stop the daemon depends on the arguments:
#
# * Only one argument that contains slashes: stop all processes that are
# instances of that executable.
# * More than one argument: stop all processes that are started by the
# commandline specified by all arguments together.
stop_daemon() {
local _daemonpath _basename _pidfile
local _ssd=start-stop-daemon
[ $# -gt 0 ] || error "stop_daemon: wrong number of arguments"
_daemonpath=${1%% *}
_basename=${_daemonpath##*/}
_pidfile=/var/run/$_basename.pid
if [ -f "$_pidfile" ]; then
! $_ssd --stop --retry 5 --pidfile "$_pidfile" || return 0
# That didn't work. Remove the pidfile and try other methods.
rm -f "$_pidfile"
fi
# Get the absolute path to the daemon executable.
[ "$_daemonpath" != "$_basename" ] ||
_daemonpath=$(command which "$_basename" || :)
if [ "$_daemonpath" ]; then
$_ssd --stop --retry 5 --oknodo --exec "$_daemonpath"
else
warning "stop_daemon: daemon path missing"
return 1
fi
}
stop_parhand() {
info "Stopping parhand..."
if stop_daemon /bin/parhand; then
info "done"
else
warning "stop_parhand: Could not stop parhand, failed!"
return 1
fi
}
add_bckf() {
[ $# -le 1 ] || error "add_bckf: invalid arguments"
[ ! -e $1 ] || {
warning "+ saving '$1'"
printf "%s" "$1"
}
}
source_bckf() {
# Get list of files
local _conff=$UED/fsupgrade.conf
[ -f $_conff ] && [ -r $_conff ] ||
error "source_bckf: conf file '$_conff' not found"
. $_conff || error "source_bckf: failed to source '$_conff'"
}
# Returns UID/GID from given entry
# Input shall be in passwd/group file format
get_uid_gid_from_entry() {
# $1: variable name used to assign return value
# $2: line from /etc/passwd or /etc/group
local _f=get_uid_gid_from_entry
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
error "$_f: invalid argument(s)"
local _entry_id
_entry_id=${2#*:*:}
_entry_id=${_entry_id%%:*}
eval $1=\$_entry_id
}
# Returns user/group name of given entry
# Input shall be in passwd/group file format
get_user_grp_name_from_entry() {
# $1: variable name used to assign return value
# $2: a line from /etc/passwd or /etc/group
local _f=get_user_grp_name_from_entry
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
error "$_f: invalid argument(s)"
eval $1=\${2%%:*}
}
# Returns user/group entry with given user/group name
# Input shall be in passwd/group file format
get_user_grp_entry_with_name() {
# $1: variable name to assign return value on
# $2: string of /etc/passwd or /etc/group
# $3: user or group name to look for
local _f=get_user_grp_entry_with_name
[ $# -eq 3 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] ||
error "$_f: invalid argument(s)"
local _entry _found_entry=
local IFS='
'
for _entry in $2; do
case $_entry in
$3:*)
_found_entry=$_entry
break
;
esac
done
eval $1=\$_found_entry
}
# Returns user/group entry with given UID/GID.
# Input shall be in passwd/group file format
get_user_grp_entry_with_id() {
# $1: variable name to assign return value on
# $2: string of /etc/passwd or /etc/group
# $3: user or group name
local _f=get_user_grp_entry_with_id
[ $# -eq 3 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] ||
error "$_f: invalid argument(s)"
local _entry _found_entry=
local IFS='
'
for _entry in $2; do
case $_entry in
*:*:$3:*)
_found_entry=$_entry
break
;
esac
done
eval $1=\$_found_entry
}
# Returns GID of given user entry.
# Input shall be in passwd format
get_gid_of_user_entry() {
# $1: variable name used to assign return value
# $2: line from /etc/passwd or /etc/group
local _f=get_gid_of_user_entry
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] ||
error "$_f: invalid argument(s)"
local _entry_gid
_entry_gid=${2#*:*:*:}
_entry_gid=${_entry_gid%%:*}
eval $1=\$_entry_gid
}
# Returns the id that is 1 greater than the biggest id.
# Input shall be in passwd format
get_first_unused_id() {
# $1: variable name used to assign return value
# $2: string value of /etc/passwd or /etc/group file
# $3: smallest id that can be returned
local _f=get_first_unused_id
[ $# -eq 3 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] ||
error "$_f: invalid argument(s)"
local entry entry_id entry_name first_unused_id=$3
local IFS='
'
for entry in $2; do
get_user_grp_name_from_entry entry_name "$entry"
[ "$entry_name" != nobody ] && [ "$entry_name" != nogroup ] ||
continue
get_uid_gid_from_entry entry_id "$entry"
[ $first_unused_id -gt $entry_id ] ||
first_unused_id=$(($entry_id + 1))
done
eval $1=\$first_unused_id
}
# Merges two passwd or group files and assigns result to given variable.
pwd_grp_merge() {
# $1: type of file to merge, "passwd" or "group"
# $2: path to old passwd/group file
# $3: path to new passwd/group file
# $4: which entry to prefer in the event of a name collision, "old" or "new"
# $5: path to the merged file
# output: A temporary file with the merged result
local _f=pwd_grp_merge
[ $# -eq 5 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] && [ "$4" ] && [ "$5" ] ||
error "$_f: wrong number of arguments"
[ $1 = passwd ] || [ $1 = group ] ||
error "$_f: 2nd parameter shall be \"passwd\" or \"group\""
[ -f $2 ] && [ -r $2 ] || error "$_f: $2 does not exit or not readable"
[ -f $3 ] && [ -r $3 ] || error "$_f: $3 does not exit or not readable"
[ $4 = old ] || [ $4 = new ] ||
error "$_f: 4th parameter shall be 'old' or 'new'"
[ -f $5 ] && [ -r $5 ] || error "$_f: $5 does not exit or not readable"
local mergedentry mergedlines= newentry newentrywitholdid
local newentrywitholdname newfile new_grp newid newlines newname
local oldfile oldlines oldentry oldid oldname old_grp tmpmf unused_id
local prefer ucu_start_id=1000
local IFS='
'
filetype=$1
oldfile=$2
newfile=$3
prefer=$4
tmpmf=$5
# Check the format of the entries while reading the files,
# badly formatted lines get discarded.
oldlines=$(sed -rne '/^[^:]+:[^:]*:[0-9]+:.*/ p' $oldfile)
newlines=$(sed -rne '/^[^:]+:[^:]*:[0-9]+:.*/ p' $newfile)
for newentry in $newlines; do
get_uid_gid_from_entry newid "$newentry"
get_user_grp_name_from_entry newname "$newentry"
[ "$filetype" != passwd ] || {
get_gid_of_user_entry newgrp "$newentry"
# user in "users" will be merged later
[ $newgrp -ne 100 ] || continue
}
# get the same user from old file if available
get_user_grp_entry_with_name oldentry "$oldlines" "$newname"
if [ "$oldentry" ]; then
get_uid_gid_from_entry oldid "$oldentry"
if [ "$filetype" != passwd ] &&
{ [ "$newname" = root ] ||
[ "$newname" = admin ] ||
[ "$newname" = operator ] ||
[ "$newname" = viewer ] ||
[ "$newname" = users ] ||
[ "$newname" = ptz ] ||
[ "$newname" = nvrdevice ]; }; then
# UCUs are added to this group, always keep the user list from the old entry
if [ $prefer = new ]; then
mergedlines="$mergedlines${newentry%:*}:${oldentry##*:}
"
else
mergedlines="$mergedlines$oldentry
"
fi
else
if [ $newid -eq $oldid ] || [ $prefer = new ]; then
# UID/GID for this entry has not changed, or the caller prefers the new one
mergedlines="$mergedlines$newentry
"
else
# check if old UID/GUI is used for another entry in new file, try to keep old UID/GUI
get_user_grp_entry_with_id newentrywitholdid "$newlines" "$oldid"
if [ "$newentrywitholdid" ]; then
# same UID/GUI is used for another entry in new file, use new entry with updated UID/GUI
mergedlines="$mergedlines$newentry
"
else
# no entry with old UID/GUI exist, keep the old one
mergedlines="$mergedlines$oldentry
"
fi
fi
fi
else
# this user did not exist before
mergedlines="$mergedlines$newentry
"
fi
done
# keep old removed entries, if possible
for oldentry in $oldlines; do
get_uid_gid_from_entry oldid "$oldentry"
get_user_grp_name_from_entry oldname "$oldentry"
[ "$filetype" != passwd ] || {
get_gid_of_user_entry oldgrp "$oldentry"
# user in "users" will be merged later
[ $oldgrp -ne 100 ] || continue
}
get_user_grp_entry_with_name newentrywitholdname "$newlines" "$oldname"
if [ -z "$newentrywitholdname" ]; then
# old user is removed, if the UID is not used in new file keep the user
get_user_grp_entry_with_id newentrywitholdid "$newlines" "$oldid"
if [ -z "$newentrywitholdid" ]; then
# UID is availabe, keep user
mergedlines="$mergedlines$oldentry
"
else
info "Following entry in old file could not be merged due to conflict: $oldentry"
fi
fi
done
# keep all user created users (UCUs)
# merge all UCUs from old file, they belong to "users (100)" group
if [ "$filetype" = passwd ]; then
for oldentry in $oldlines; do
get_gid_of_user_entry oldgrp "$oldentry"
# merge only UCUs
if [ $oldgrp -eq 100 ]; then
get_uid_gid_from_entry oldid "$oldentry"
get_first_unused_id unused_id "$mergedlines" "$ucu_start_id"
oldentry=$(echo "$oldentry" | sed -e "s/$oldid/$unused_id/g")
mergedlines="$mergedlines$oldentry
"
fi
done
# merge all UCUs from new file
for newentry in $newlines; do
get_gid_of_user_entry newgrp "$newentry"
# merge only UCUs
if [ $newgrp -eq 100 ]; then
get_user_grp_name_from_entry newname "$newentry"
# check ift his user is already added from old file UCUs
get_user_grp_entry_with_name mergedentry "$mergedlines" "$newname"
if [ -z "$mergedentry" ]; then
get_uid_gid_from_entry newid "$newentry"
get_first_unused_id unused_id "$mergedlines" "$ucu_start_id"
newentry=$(echo "$newentry" | sed -e "s/$newid/$unused_id/g")
mergedlines="$mergedlines$newentry
"
fi
fi
done
fi
# change shell to /bin/false for non root user
[ "$filetype" != passwd ] &&
mergedlines=$(echo "$mergedlines" |
sed -e '/^[^(root)]/s/\/bin\/sh$/\/bin\/false/')
mergedlines=$(echo "$mergedlines" | sed -e '/^$/d' | sort -t : -k 3n)
# Write the result to the temporary merge file. This file will be
# replacing the original file safely and the parent function.
echo "$mergedlines" > $tmpmf ||
error "Failed write the temporary merge file '$tmpmf'"
}
# Prints the entry matching the login name.
# First argument is a login name, second is a shadow file.
get_line_from_shadow() {
local line
[ $# -eq 2 ] || error "get_line_from_shadow: missing argument"
for line in $1; do
[ ${line%%:*} != $2 ] || {
echo $line
return 0
}
done
return 1
}
# Merges two shadow files and prints the result in the same order as in
# the passwd file.
# If there are any conflicts, the entry from the old file stays.
# First argument is the current passwd file, second is the old shadow file,
# third is the new shadow file,
shadow_merge() {
# $1: Current passwd file
# $2: Old shadow file
# $3: New shadow file
# $4: Merged shadow file
# output: Merged content to the merged shadow file ($4)
[ $# -eq 4 ] || error "shadow_merge: missing argument"
[ -f $1 ] && [ -r $1 ] || error "$f: '$1' missing or not readable"
[ -f $2 ] && [ -r $2 ] || error "$f: '$2' missing or not readable"
[ -f $3 ] && [ -r $3 ] || error "$f: '$3' missing or not readable"
[ -f $4 ] && [ -r $4 ] || error "$f: '$4' missing or not readable"
local f=shadow_merge line passwd_lines cur_sf old_sf new_sf merged_sf
local IFS restore_IFS
cur_sf=$1
old_sf=$2
new_sf=$3
merged_sf=$4
IFS='
'
passwd_lines=$(sed -rne '/^[^:]+:[^:]*:[0-9]+:.*/ p' $cur_sf)
old_shadow_lines=$(sed -rne '/^[^:]+:[^:]+/ p' $old_sf)
new_shadow_lines=$(sed -rne '/^[^:]+:[^:]+/ p' $new_sf)
for line in $passwd_lines; do
login_name=${line%%:*}
get_line_from_shadow "$old_shadow_lines" $login_name ||
get_line_from_shadow "$new_shadow_lines" $login_name || {
# Generate an entry using the same values as
# pwconv if an entry is missing in both old and
# new shadow file,
restore_IFS=$IFS
IFS=:
set -- $line
IFS=$restore_IFS
[ $# -ge 2 ] || {
warning "$f: line split failed"
continue
}
echo $1:$2:10231:0:99999:7:::
}
done >$merged_sf ||
error "$f: failed to write the merged shadow file '$merged_sf'"
}
generate_new_preset_file() {
local f=generate_new_preset_file tmppf line _1st_part _last_part index
[ $# -eq 1 ] && [ "$1" ] || error "$f: missing argument"
[ -f $1 ] && [ -r $1 ] || error "$f: '$1' missing or not readable"
info "Parsing Preset file: $1"
tmppf=$(mktemp /tmp/ptz_preset.new.XXXXXX) ||
error "$f: temporary preset file creation failed"
while read line; do
_1st_part=${line%[[:blank:]]*}
_last_part=${line#$_1st_part}
_last_part=${_last_part##[[:blank:]]}
case $_last_part in
[0-9]_position|[0-9][0-9]_position)
# The preset file has already the new format.
rm -f $tmppf ||
error "$f: failed to remove temporary" "preset file '$tmppf'"
return 0
;
position)
# Needs changes.
index=${_1st_part#*PTZ.Preset.P}
index=${index%%.*}
echo $_1st_part ${index}_position >>$tmppf ||
error "$f: failed to update temporary" "file '$tmppf'"
;
*)
rm -f $tmppf ||
error "$f: failed to remove temporary" "file '$tmppf'"
error "$f: internal error; bad code" "'$_last_part' or bad line '$line'"
;
esac
done <$1
info "Replacing: $1"
mv -f $tmppf $1 || error "$f: failed to replace '$1' with '$tmppf'"
}
modify_preset_file() {
local f is_preset_file
if [ -d $EDT ]; then
for f in $EDT/*; do
[ -f $f ] && [ -r $f ] || continue
is_preset_file=${f#*ptz_preset_p}
[ "$is_preset_file" = $f ] ||
generate_new_preset_file $f ||
error "modify_preset_file: failed to parse '$f'"
done
fi
}
add_bckf_from_file() {
local line filelist= IFS="
"
if [ -r "$1" ]; then
while read line; do
case $line in
\#*)
continue
;
esac
filelist="$filelist $(add_bckf $line)"
done <$1
else
warning "Failed to read file '$1'"
fi
echo $filelist
}
set_owner_and_permissions() {
[ $# -eq 4 ] && [ "$1" ] && [ "$2" ] && [ "$3" ] && [ "$4" ] ||
error "set_owner_and_permissions: missing argument"
if [ -e $1 ]; then
chmod $2 $1 ||
warning "Failed to set permissions to '$2' on '$1'"
chown $3:$4 $1 ||
warning "Failed to set owner,group to '$3','$4' on '$1'"
else
warning "Skip restoring permissions for '$1', file does not exist"
fi
}
# Creates an archive of backup files and a seperate file containing the backup
# files original path.
archive_files() {
# $1: Space separated list of files to backup
# output: Status code, 0=SUCCESS/1=FAILURE
local files f self=archive_files
files=$1
info "$self: Saving files temporarily..."
touch $BCK_IGNORELIST_FILE
if tar cf $BCK_ARCHIVE -X $BCK_IGNORELIST_FILE $files; then
# Save a list of the backuped files, to be used at restore
echo $files >$BCK_BACKUP_FILES_FILE ||
warning "Failed to save list of backup files"
else
warning "Failed to create archive of backup files"
fi
rm $BCK_IGNORELIST_FILE && info "done" || {
warning "Failed to remove backup ignore list"
return 1
}
}
perform_upgrade_backup() {
local backup_files bck_list eraseparam extdr f filedevid grp jffsid oldtype
local prm rootdevid vfsu_d
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] || error "perform_upgrade_backup: missing argument"
jffsid=$1
oldtype=$2
if [ "$jffsid" != ONVIF_HARD_RESET ]; then
# Run pre-upgrade scripts
if [ -d $PREUPGRADE_SCRIPTS_DIR ]; then
for f in $PREUPGRADE_SCRIPTS_DIR/*; do
info "Pre-upgrade script '$f' ..." && JFFSID=$jffsid $f && info "done" ||
warning "Failed to execute pre upgrade script '$f'"
done
fi
fi
# Files for parameters not handled by parhand
source_bckf
case $jffsid in
ONVIF_HARD_RESET)
bck_list=$ONVIF_HARD_RESET_PERSISTENT_FILES
;
SOFT_RESET)
bck_list=$RESET_PERSISTENT_FILES
;
*)
bck_list=$UPGRADE_PERSISTENT_FILES
;
esac
for f in $bck_list; do
backup_files="$backup_files $(add_bckf $f)"
done
if [ -d $SAVEFILES_DIR ] && [ "$(ls -A $SAVEFILES_DIR)" ]; then
for f in $SAVEFILES_DIR/*; do
backup_files="$backup_files $(add_bckf_from_file $f)"
done
fi
make_dir /var/tmp || error "Failed to create /var/tmp"
make_dir /var/run/parhand || error "Failed to create /var/run/parhand"
if [ "$jffsid" = ONVIF_HARD_RESET ]; then
archive_files "$backup_files"
exit 0
fi
if [ "$jffsid" = SOFT_RESET ]; then
# Soft factory default
if start_parhand; then
info 'Saving parameters temporarily...'
exec 7>$PHTMPBCK || error "Could not open '$PHTMPBCK'"
for prm in $backup_parameters; do
$PHC get root.$prm - NAMEVALUE >&7 ||
warning "'$PHC get root.$prm' failed"
done
for grp in $backup_groups; do
$PHC getgroup root.$grp - NAMEVALUE >&7 ||
warning "'$PHC getgroup root.$grp' failed"
done
exec 7>&-
info "done"
else
warning "Failed to save parameters"
fi
stop_parhand || warning "Failed to stop parhand(1)"
extdr=/sbin/rc.extdevrestore
if [ -x $extdr ]; then
info "Resetting external device..."
$extdr
fi
else
# Upgrade
# Don't erase third part applications
eraseparam=--noiv
# Create directories for merging persistant conf files.
vfsu_d=/var/tmp/fsupgrade
make_dir $vfsu_d
# Save copies of the old files before update.
# These will be handled by scripts in
# POST_UPGRADE_SCRIPTS_LOC later.
for f in $UPGRADE_POST_UPGRADE_FILES; do
[ ! -r $f ] || cp $f $vfsu_d ||
warning "Failed to copy '$f' to '$vfsu_d'"
done
if [ -f $PHBCK ] && [ -w $PHBCK ]; then
info "Will use previously stored parameters..."
mv $PHBCK $PHTMPBCK || error "Failed to move '$PHBCK' to '$PHTMPBCK'"
info "done"
else
if start_parhand; then
info "Backup parameters..."
$PHC getgroup root $PHTMPBCK NAMEVALUE || :
info "done"
else
warning "Failed to backup parameters"
fi
stop_parhand || warning "Failed to stop parhand(2)"
fi
if [ -d $EDP ]; then
make_dir $EDT
for f in $EDP/*; do
[ -f $f ] && [ -r $f ] || continue
grep DO $f >$EDT/${f#$EDP/} 2>/dev/null
done
modify_preset_file
backup_files="$backup_files $ED"
fi
fi
if [ "$oldtype" = lfp ]; then
rootdevid=$(stat -c %d /) ||
warning "Could not determine device id mounted on /"
# We don't want to back up files which actually came from the
# rootfs rather than the old rwfs (due to /etc being mounted as
# an overlay); this is redundant and will interfere with fixing
# LFP uid/gids when restoring the files.
if [ "$rootdevid" ]; then
find $backup_files | xargs stat -c '%n %d' | while read -r f filedevid; do
[ "$filedevid" != "$rootdevid" ] || echo "${f#/}"
done >>$BCK_IGNORELIST_FILE ||
error "Failed to add rootfs files to ignore list"
fi
fi
archive_files "$backup_files"
}
convert_to_temp() {
local f=$2 tmpf self=convert_to_temp
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] || error "$self: missing or empty argument"
if [ -r $f ]; then
tmpf=$(mktemp "$f.new.XXXXXX") ||
error "$self: Failed to create a temporary file for '$f'"
cp $f $tmpf || error "$self: Failed to copy '$f' to '$tmpf'"
fsync $tmpf || error "$self: Failed to sync '$tmpf'"
eval $1=\$tmpf
else
warning "$self: File '$f' is missing or insufficient permissions"
fi
}
safe_merge() {
# $1: Primary file in the merge
# $2: Secondary file in the merge
# $3: Function that will perform the actual merge
# $@: Arguments for the function specified at $3
# ouput: Merged result in the file specified at $1
[ "$1" ] && [ "$2" ] && [ "$3" ] ||
error "safe_merge: invalid arguments"
local f=$1 fun=$3 fun_args self=safe_merge tmpf=$2 tmpmf
shift 3
if [ -r $f ] && [ -r $tmpf ]; then
tmpmf=$(mktemp "$f.mrg.XXXXXX") ||
error "$self: Failed to create a temporary merge file"
$fun $* $tmpmf ||
error "$self: Failed to call function '$fun' with arguments '$fun_args'"
fsynced_write_or_cleanup $tmpmf $f
#mv $tmpmf $f || error "$self: Failed to move file '$tmpmf' to '$f'"
#sync || warning "$self: Failed to sync the filesystem"
chmod 644 $f || error "$self: Failed to set permissions 644 to '$f'"
rm $tmpf || warning "$self: Failed to remove file '$tmpf'"
else
error "$self: Failed to merge '$f' and '$tmpf', missing one of the files"
mv $tmpf $f || error "$self: Failed to restore '$f'"
fi
}
do_pwconv() {
local name pass rest pf=/etc/passwd sf=/etc/shadow tmpsf=/etc/shadow.conv IFS=:
rm -f $tmpsf
while read -r name pass rest; do
[ "$pass" = x ] || echo "$name:$pass:10231:0:99999:7:::"
done <$pf >$tmpsf
# Install the new shadow file and clear all passwords from /etc/passwd.
fsynced_write_or_cleanup $tmpsf $sf
sed -i -e 's/^\([^:]*\):[^:]*/\1:x/' $pf
fsync $pf
}
perform_lfp_to_cvp_upgrade_restore() {
local _f=perform_lfp_to_cvp_upgrade_restore f local_backup_files mergedfile
local pf=/etc/passwd gf=/etc/group sf=/etc/shadow
local tmppf tmpgf tmpsf
local tmpdir etc_filelist common_filelist extra_filelist
local common_permissions extra_permissions iv_permissions
local cvp_passwd cvp_group perms usr grp mounted_iv=no
tmpdir=$(mktemp -d /tmp/tmp.fsupgrade.XXXXXX) ||
error "Could not create temporary directory"
etc_filelist=$tmpdir/etc_filelist
extra_filelist=$tmpdir/extra_filelist
common_filelist=$tmpdir/common_filelist
common_permissions=$tmpdir/common_permissions
extra_permissions=$tmpdir/extra_permissions
iv_permissions=$tmpdir/iv_permissions
convert_to_temp tmppf $pf
convert_to_temp tmpgf $gf
convert_to_temp tmpsf $sf
local_backup_files=$(tar tf $BCK_ARCHIVE) ||
error "Could not read file list from backup archive"
# tar may emit a trailing slash, whereas find won't.
local_backup_files=$(echo "$local_backup_files" | sed -e 's|/$||')
# Find which files are only in the backup and which files are common to
# both. The files only present in the backup will have old LFP uids and
# gids which will need to be changed after extraction.
find /etc | sed -e 's,^/,,' >$etc_filelist
# Having no files in common means we're already in serious trouble
# (e.g. /etc/passwd being missing from one of the systems). There being
# no extra files (files from the old system that don't exist on the
# new) is suspicious, but shouldn't fatally break anything.
printf %s "$local_backup_files" | grep -Fxf $etc_filelist >$common_filelist ||
error "System and backup archive have no files in common"
printf %s "$local_backup_files" | grep -Fxvf $etc_filelist >$extra_filelist ||
warning "No extra files from old system in backup archive"
while read f; do
# The permissions of a symbolic link itself don't matter and we
# assume that the permissions of the target are, or will be,
# correct. This silences errors from chown later, when trying
# to modify the permissions of files on the rootfs. (Which is
# read-only, so we wouldn't be able to do anything about it
# anyway.)
[ ! -h "$f" ] || continue
stat -c "%n %a %U %G" $f ||
warning "Saving permissions of file '$f' failed"
done <$common_filelist >$common_permissions
tar xf $BCK_ARCHIVE || error "Failed to unpack backup archive"
rm $BCK_ARCHIVE || warning "Failed to remove backup archive"
# Save the permissions for the extra files. Note that we do this
# _after_ extraction, so stat will pick up the old user and group names
# from the extracted /etc/group and /etc/passwd.
while read f; do
[ ! -h "$f" ] || continue
stat -c "%n %a %U %G" $f ||
warning "Saving permissions of file '$f' failed"
done <$extra_filelist >$extra_permissions
if ! grep -w /usr/local /proc/mounts; then
mount /usr/local || error "$_f: failed to mount /usr/local"
mounted_iv=yes
fi
find /usr/local | while read -r f; do
stat -c "%a %U %G %n" "$f" ||
warning "Saving permissions of file '$f' failed"
done >$iv_permissions
cvp_passwd=$(cat $tmppf)
cvp_group=$(cat $tmpgf)
# If there are any hashes in the passwd file, assume it is unconverted
# and generate a corresponding shadow file.
! grep -q -v '^[^:]*:x:' $pf || do_pwconv
safe_merge $pf $tmppf pwd_grp_merge passwd $pf $tmppf new
safe_merge $gf $tmpgf pwd_grp_merge group $gf $tmpgf new
safe_merge $sf $tmpsf shadow_merge $pf $sf $tmpsf
# Use the CVP permissions for files and directories common to both.
while read f; do
set_owner_and_permissions $f
done <$common_permissions
# The extra files get LFP permissions.
while read f; do
set_owner_and_permissions $f
done <$extra_permissions
! [ -e $IV_ROLLBACK_DIR ] || rm -rf $IV_ROLLBACK_DIR
mkdir -m 700 $IV_ROLLBACK_DIR || error "Couldn't create $IV_ROLLBACK_DIR"
# Write the CVP passwd/group files to /usr/local so that ownership of
# everything there can be fixed again in the event of a rollback. A
# factory default wiping these out won't matter; in that case there
# won't be any files there to fix anyway.
printf %s "$cvp_passwd" >$IV_ROLLBACK_DIR/passwd.tmp
printf %s "$cvp_group" >$IV_ROLLBACK_DIR/group.tmp
chmod 400 $IV_ROLLBACK_DIR/passwd.tmp $IV_ROLLBACK_DIR/group.tmp
fsynced_write_or_cleanup $IV_ROLLBACK_DIR/passwd.tmp $IV_ROLLBACK_DIR/passwd
fsynced_write_or_cleanup $IV_ROLLBACK_DIR/group.tmp $IV_ROLLBACK_DIR/group
# We need to be careful about changing permissions in /usr/local. If we
# handle all files in a loop as with /etc and we're interrupted (e.g.
# by a power cut), we'll roll back and be stuck with a /usr/local which
# is partially correct. To undo this, generate a script that rectifies
# this if we're interrupted. The script is run by a systemd service
# patched into the old firmware that runs on boot.
{
echo "#!/bin/sh"
while read perms usr grp f; do
echo "chmod $perms '$f' || STATUS=1"
echo "chown $usr:$grp '$f' || STATUS=1"
done <$iv_permissions
echo 'exit ${STATUS:-0}'
} >$IV_ROLLBACK_DIR/recover.sh.tmp
chmod 700 $IV_ROLLBACK_DIR/recover.sh.tmp
fsynced_write_or_cleanup $IV_ROLLBACK_DIR/recover.sh.tmp $IV_ROLLBACK_DIR/recover.sh
# Update /usr/local now.
$IV_ROLLBACK_DIR/recover.sh ||
warning "Failed to fix ownership of all files in /usr/local"
sync
# We're safe; remove the generated script.
rm -f $IV_ROLLBACK_DIR/recover.sh
rm -rf $tmpdir
if [ $mounted_iv = yes ]; then
umount /usr/local || error "$_f: failed to unmount /usr/local"
fi
}
perform_cvp_upgrade_restore() {
local f local_backup_files
local pf gf sf tmppf tmpgf tmpsf
local_backup_files=$(cat $BCK_BACKUP_FILES_FILE)
source_bckf
# Save ownerships and permissions from the new fimage before restoring
# the backed up files otherwise.
# NOTICE: This function have, previously, been failing to get the
# permissions of the shadow file since the shadow file is moved to a
# temporary file (see "convert_to_temp $sf"). Correcting this might
# cause issues since it will ensure the previous permissions.
rm -f $BCK_PERMISSIONS_FILE
exec 7>$BCK_PERMISSIONS_FILE
for f in $local_backup_files; do
if [ ! -h $f ] && echo $SKIP_NEW_PERMISSIONS | grep -Fqv $f; then
stat -c "%n %a %U %G" $f >&7 ||
warning "Saving permissions of file '$f' failed"
fi
done
exec 7>&-
# Convert the following files in to temporary files with the format
# "$1.new.XXXXXX"
pf=/etc/passwd
convert_to_temp tmppf $pf
gf=/etc/group
convert_to_temp tmpgf $gf
sf=/etc/shadow
convert_to_temp tmpsf $sf
err=$(tar xf $BCK_ARCHIVE 2>&1) || warning "Failed to unpack backup archive: $err"
rm $BCK_ARCHIVE || warning "Failed to remove backup archive"
safe_merge $pf $tmppf pwd_grp_merge passwd $pf $tmppf old
safe_merge $gf $tmpgf pwd_grp_merge group $gf $tmpgf old
safe_merge $sf $tmpsf shadow_merge $pf $sf $tmpsf
# Restore the new default permissions of the backuped files
while read f; do
set_owner_and_permissions $f
done <$BCK_PERMISSIONS_FILE
}
perform_upgrade_restore() {
local f jffsid local_backup_files mergedfile oldtype
local pf gf sf tmppf tmpgf tmpsf
[ $# -eq 2 ] && [ "$1" ] && [ "$2" ] || error "perform_upgrade_restore: missing argument"
jffsid=$1
oldtype=$2
if [ "$jffsid" = ONVIF_HARD_RESET ]; then
tar xf $BCK_ARCHIVE || warning "Restore failed"
rm $BCK_ARCHIVE || warning "Failed to remove backup archive"
exit 0
fi
if [ "$oldtype" = lfp ]; then
perform_lfp_to_cvp_upgrade_restore
else
perform_cvp_upgrade_restore
fi
if [ "$jffsid" != SOFT_RESET ]; then
rm -f $EDP/*
for f in $ED/*; do
[ ! -f "$f" ] || rm -f "$f"
done
if start_parhand_dyn; then
info 'Restoring dynamic parameters...'
if $PHC --nosync upgradedynparams $EDT; then
info "done"
else
warning "restoring dynamic parameters failed!"
fi
if $PHC sync; then
info "Synced restored dynamic parameters to file"
else
warning "Failed to sync restored dynamic parameters to file"
fi
else
warning "Failed to restore dynamic parameters"
fi
stop_parhand || warning "Failed to stop parhand(3)"
rm -rf $EDT
fi
if start_parhand; then
if [ -e $PHTMPBCK ]; then
info "Restoring temporarily saved parameters... "
if $PHC --nosync --restore setparams $PHTMPBCK; then
info "done"
else
warning "'$PHC --nosync --restore setparams $PHTMPBCK' failed!"
fi
if $PHC sync; then
info "Synced restored parameters to file"
else
warning "Failed to sync restored parameters to file"
fi
rm -f $PHTMPBCK || warning "removing '$PHTMPBCK' failed"
else
warning "Saved parameters are lost"
fi
else
warning "Failed to restore saved parameters"
fi
if [ -d $POST_UPGRADE_SCRIPTS_LOC ]; then
for file in $POST_UPGRADE_SCRIPTS_LOC/*; do
if [ -f "$file" ] && [ -x "$file" ]; then
info "Post upgrade script $file ..."
JFFSID=$jffsid $file && info "done" ||
warning "Failed to execute post upgrade script '$file'"
fi
done
fi
if [ "$jffsid" = SOFT_RESET ]; then
if [ -d $POST_UPGRADE_RESET_SCRIPTS_LOC ]; then
for file in $POST_UPGRADE_RESET_SCRIPTS_LOC/*; do
[ ! -f "$file" ] || [ ! -x "$file" ] || $file
done
fi
fi
stop_parhand || warning "Failed to stop parhand(4)"
df
}
perform_embsdk_restore() {
local rc_iv=/sbin/rc.ivrestore
case $1 in
ONVIF_HARD_RESET|SOFT_RESET)
;
'')
error "perform_embsdk_restore: jffsid is not set"
;
*)
# Make sure that any uploaded applications are restored
[ ! -x $rc_iv ] || $rc_iv || warning "$rc_iv failed!"
;
esac
}
[ $# -ge 2 ] || error "fsupgrade: missing argument"
jffsid=$1
rwfsold=$3
res=0
case $2 in
backup)
info "fsupgrade: BACKUP START '$jffsid'"
perform_upgrade_backup $jffsid cvp
res=$?
info "fsupgrade: BACKUP STOP '$jffsid'"
;
backuplfp)
info "fsupgrade: BACKUP START '$jffsid'"
perform_upgrade_backup $jffsid lfp
res=$?
info "fsupgrade: BACKUP STOP '$jffsid'"
;
restore)
info "fsupgrade: RESTORE START '$jffsid'"
perform_upgrade_restore $jffsid cvp
ret=$?
perform_embsdk_restore $jffsid
res=$(($ret + $?))
info "fsupgrade: RESTORE STOP '$jffsid'"
;
restorelfp)
info "fsupgrade: RESTORE START '$jffsid'"
perform_upgrade_restore $jffsid lfp
ret=$?
perform_embsdk_restore $jffsid
res=$(($ret + $?))
info "fsupgrade: RESTORE STOP '$jffsid'"
;
'')
error "Missing command"
;
esac
exit $res