--- /usr/share/rear/lib/global-functions.sh 2015-09-03 15:43:01.000000000 +0200 +++ /usr/share/rear/lib/global-functions.sh 2017-01-06 12:02:52.000000000 +0100 @@ -2,27 +2,14 @@ # # global functions for Relax-and-Recover # -# Relax-and-Recover 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 2 of the License, or -# (at your option) any later version. - -# Relax-and-Recover 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 Relax-and-Recover; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# +# This file is part of Relax-and-Recover, licensed under the GNU General +# Public License. Refer to the included COPYING for full text of license. function read_and_strip_file () { # extracts content from config files. In other words: strips the comments and new lines - if test -s "$1" ; then - sed -e '/^[[:space:]]/d;/^$/d;/^#/d' "$1" - fi + if test -s "$1" ; then + sed -e '/^[[:space:]]/d;/^$/d;/^#/d' "$1" + fi } function is_numeric () { @@ -34,27 +21,143 @@ fi } +# two explicit functions to be able to test explicitly for true and false (see issue #625) +# because "tertium non datur" (cf. https://en.wikipedia.org/wiki/Law_of_excluded_middle) +# does not hold for variables because variables could be unset or have empty value +# and to test if a variable is true or false its value is tested by that functions +# but the variable may not have a real value (i.e. be unset or have empty value): + +function is_true () { + # the argument is usually the value of a variable which needs to be tested + # only if there is explicitly a 'true' value then is_true returns true + # so that an unset variable or an empty value is not true: + case "$1" in + ([tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE] | 1) + return 0 ;; + esac + return 1 +} + +function is_false () { + # the argument is usually the value of a variable which needs to be tested + # only if there is explicitly a 'false' value then is_false returns true + # so that an unset variable or an empty value is not false + # caution: for unset or empty variables is_false is false + case "$1" in + ([fF] | [nN] | [nN][oO] | [fF][aA][lL][sS][eE] | 0) + return 0 ;; + esac + return 1 +} + ###### ### Functions for dealing with URLs ###### +# URL is the most common form of URI +# see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier +# where a generic URI is of the form +# scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] +# e.g. for BACKUP_URL=sshfs://user@host/G/rear/ +# url_scheme = 'sshfs' , url_host = 'user@host' , url_hostname = 'host' , url_username = 'user' , url_path = '/G/rear/' +# e.g. for BACKUP_URL=usb:///dev/sdb1 +# url_scheme = 'usb' , url_host = '' , url_hostname = '' , url_username = '' , url_path = '/dev/sdb1' +# FIXME: the ulr_* functions are not safe against special characters +# for example they break when the password contains spaces +# but on the other hand permitted characters for values in a URI +# are ASCII letters, digits, dot, hyphen, underscore, and tilde +# and any other character must be percent-encoded (in particular the +# characters : / ? # [ ] @ are reserved as delimiters of URI components +# and must be percent-encoded when used in the value of a URI component) +# so that what is missing is support for percent-encoded characters +# but user-friendly support for percent-encoded characters is not possible +# cf. http://bugzilla.opensuse.org/show_bug.cgi?id=561626#c7 -url_scheme() { +function url_scheme() { local url=$1 + # the scheme is the leading part up to '://' local scheme=${url%%://*} # rsync scheme does not have to start with rsync:// it can also be scp style + # see the comments in usr/share/rear/prep/RSYNC/default/100_check_rsync.sh echo $scheme | grep -q ":" && echo rsync || echo $scheme } -url_host() { +function url_host() { + local url=$1 + local url_without_scheme=${url#*//} + # the authority part is the part after the scheme (e.g. 'host' or 'user@host') + # i.e. after 'scheme://' all up to but excluding the next '/' + # which means it breaks if there is a username that contains a '/' + # which should not happen because a POSIX-compliant username + # should have only characters from the portable filename character set + # which is ASCII letters, digits, dot, hyphen, and underscore + # (a hostname must not contain a '/' see RFC 952 and RFC 1123) + local authority_part=${url_without_scheme%%/*} + # for backward compatibility the url_host function returns the whole authority part + # see https://github.com/rear/rear/issues/856 + # to get only hostname or username use the url_hostname and url_username functions + echo $authority_part +} + +function url_hostname() { local url=$1 - local host=${url#*//} - echo ${host%%/*} + local url_without_scheme=${url#*//} + local authority_part=${url_without_scheme%%/*} + # if authority_part contains a '@' we assume the 'user@host' format and + # then we remove the 'user@' part (i.e. all up to and including the last '@') + # so that it also works when the username contains a '@' + # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/ + # (a hostname must not contain a '@' see RFC 952 and RFC 1123) + local host_and_port=${authority_part##*@} + # if host_and_port contains a ':' we assume the 'host:port' format and + # then we remove the ':port' part (i.e. all from and including the last ':') + # so that it even works when the hostname contains a ':' (in spite of RFC 952 and RFC 1123) + echo ${host_and_port%:*} } -url_path() { +function url_username() { local url=$1 - local path=${url#*//} - echo /${path#*/} + local url_without_scheme=${url#*//} + local authority_part=${url_without_scheme%%/*} + # authority_part must contain a '@' when a username is specified + echo $authority_part | grep -q '@' || return 0 + # we remove the '@host' part (i.e. all from and including the last '@') + # so that it also works when the username contains a '@' + # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/ + # (a hostname must not contain a '@' see RFC 952 and RFC 1123) + local user_and_password=${authority_part%@*} + # if user_and_password contains a ':' we assume the 'user:password' format and + # then we remove the ':password' part (i.e. all from and including the first ':') + # so that it works when the password contains a ':' + # (a POSIX-compliant username should not contain a ':') + echo $user_and_password | grep -q ':' && echo ${user_and_password%%:*} || echo $user_and_password +} + +function url_password() { + local url=$1 + local url_without_scheme=${url#*//} + local authority_part=${url_without_scheme%%/*} + # authority_part must contain a '@' when a username is specified + echo $authority_part | grep -q '@' || return 0 + # we remove the '@host' part (i.e. all from and including the last '@') + # so that it also works when the username contains a '@' + # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/ + # (a hostname must not contain a '@' see RFC 952 and RFC 1123) + local user_and_password=${authority_part%@*} + # user_and_password must contain a ':' when a password is specified + echo $user_and_password | grep -q ':' || return 0 + # we remove the 'user:' part (i.e. all up to and including the first ':') + # so that it works when the password contains a ':' + # (a POSIX-compliant username should not contain a ':') + echo ${user_and_password#*:} +} + +function url_path() { + local url=$1 + local url_without_scheme=${url#*//} + # the path is all from and including the first '/' in url_without_scheme + # i.e. the whole rest after the authority part so that + # it may contain an optional trailing '?query' and '#fragment' + echo /${url_without_scheme#*/} } backup_path() { @@ -76,7 +179,7 @@ path="${TMP_DIR}/isofs${path}" fi ;; - (*) # nfs, cifs, usb, a.o. need a temporary mount-path + (*) # nfs, cifs, usb, a.o. need a temporary mount-path path="${BUILD_DIR}/outputfs/${NETFS_PREFIX}" ;; esac @@ -93,7 +196,7 @@ (file) # type file needs a local path (must be mounted by user) path="$path/${OUTPUT_PREFIX}" ;; - (*) # nfs, cifs, usb, a.o. need a temporary mount-path + (*) # nfs, cifs, usb, a.o. need a temporary mount-path path="${BUILD_DIR}/outputfs/${OUTPUT_PREFIX}" ;; esac @@ -129,7 +232,8 @@ ;; (cifs) if [ x"$options" = x"$defaultoptions" ];then - mount_cmd="mount $v -o $options,guest //$(url_host $url)$(url_path $url) $mountpoint" + # defaultoptions contains noatime which is not valid for cifs (issue #752) + mount_cmd="mount $v -o rw,guest //$(url_host $url)$(url_path $url) $mountpoint" else mount_cmd="mount $v -o $options //$(url_host $url)$(url_path $url) $mountpoint" fi @@ -137,19 +241,48 @@ (usb) mount_cmd="mount $v -o $options $(url_path $url) $mountpoint" ;; - (sshfs) - mount_cmd="sshfs $(url_host $url):$(url_path $url) $mountpoint -o $options" + (sshfs) + local authority=$( url_host $url ) + test "$authority" || Error "Cannot run 'sshfs' because no authority '[user@]host' found in URL '$url'." + local path=$( url_path $url ) + test "$path" || Error "Cannot run 'sshfs' because no path found in URL '$url'." + # ensure the fuse kernel module is loaded because sshfs is based on FUSE + lsmod | grep -q '^fuse' || modprobe $verbose fuse || Error "Cannot run 'sshfs' because 'fuse' kernel module is not loadable." + mount_cmd="sshfs $authority:$path $mountpoint -o $options" + ;; + (ftpfs) + local hostname=$( url_hostname $url ) + test "$hostname" || Error "Cannot run 'curlftpfs' because no hostname found in URL '$url'." + local path=$( url_path $url ) + test "$path" || Error "Cannot run 'curlftpfs' because no path found in URL '$url'." + local username=$( url_username $url ) + # ensure the fuse kernel module is loaded because ftpfs (via CurlFtpFS) is based on FUSE + lsmod | grep -q '^fuse' || modprobe $verbose fuse || Error "Cannot run 'curlftpfs' because 'fuse' kernel module is not loadable." + if test "$username" ; then + local password=$( url_password $url ) + if test "$password" ; then + # single quoting is a must for the password + mount_cmd="curlftpfs $verbose -o user='$username:$password' ftp://$hostname$path $mountpoint" + else + # also single quoting for the plain username so that it also works for non-POSIX-compliant usernames + # (a POSIX-compliant username should only contain ASCII letters, digits, dot, hyphen, and underscore) + mount_cmd="curlftpfs $verbose -o user='$username' ftp://$hostname$path $mountpoint" + fi + else + mount_cmd="curlftpfs $verbose ftp://$hostname$path $mountpoint" + fi + ;; + (davfs) + mount_cmd="mount $v -t davfs http://$(url_host $url)$(url_path $url) $mountpoint" ;; - (davfs) - mount_cmd="mount $v -t davfs http://$(url_host $url)$(url_path $url) $mountpoint" - ;; (*) mount_cmd="mount $v -t $(url_scheme $url) -o $options $(url_host $url):$(url_path $url) $mountpoint" ;; esac Log "Mounting with '$mount_cmd'" - $mount_cmd >&2 + # eval is required when mount_cmd contains single quoted stuff (e.g. see the above mount_cmd for curlftpfs) + eval $mount_cmd >&2 StopIfError "Mount command '$mount_cmd' failed." AddExitTask "umount -f $v '$mountpoint' >&2" @@ -182,7 +315,7 @@ # and delete only the just used cache #rm -rf /var/cache/davfs2/** rm -rf /var/cache/davfs2/*outputfs* - + ;; (var) local var=$(url_host $url) @@ -228,3 +361,4 @@ Log "Unmounting '$mountpoint' failed." return 1 } +