#!/bin/sh

: <<SCRIPTHEADER
Description: Disk image files extender
Version: 0:2023.12.15
Copyright: GNU GPL (2018-2024) Narcis Garcia
Homepage: https://www.actiu.net/pro/
License: GNU GPL
 This program 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.
 .
 This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
SCRIPTHEADER

# Program development at programacio/projectes_publics/matromu/diskimg-resize

# Script template version: 1.3.7-1
# Copyright 2017-2020 (GNU GPL) Narcis Garcia
# Compatible with package managers integration: Dpkg/deb

# Program development ToDo:
#	- Allow to shrink, with a special warning (and require "YES" answer) about no partitions resizing then inconsistency.
#	  + Allow truncating with an additional confirmation.

# ProgramName: Brief and compact code name that needs to be unique in the software world. Will be used for filenames and some directories.
ProgramName="diskimg-resize"
# DependsOnSoftware: Space separated list of required programs. Each item's syntaxes: executable/package ExecutableOption1:ExecutableOption2/PackageOption1|PackageOption2...
DependsOnSoftware="tr:cat/coreutils|debianutils grep sed perl/perl-base fatresize fallocate/util-linux bc"
RecommendedSoftware=""
SuggestedSoftware=""
SystemConfigDir="/etc/${ProgramName}"
# ProgramInstaller: If integrated install/uninstall functions will be available (1) or not (0)
ProgramInstaller=1
# InstalledExpected: If help information drives user to install program before using it (1) or not (0)
InstalledExpected=0
# RootRequired: Prevent to run without superuser permissions? (1=Yes 0=No). This also determines program FHS location (sbin/ or bin/).
RootRequired=0
# EssentialAtBoot: Is this program necessary for a rescue boot before mounting /usr? (1=Yes 0=No). This determines program FHS location (1: /bin /sbin 0: /usr/bin /usr/sbin)
EssentialAtBoot=0


##### TEMPLATE FUNCTIONS from @-funcions 0:2019.05.17 #####

Which ()
# Syntax as a function: $(Which "$Program")
# Descripcion: Arranged copy of the debianutils' "which" (2009) to not depend of debianutils and not output to stderr (RHEL does it)
# Notes:
#	- There is an alternative to check an executable with path;
#	  Example:
#	  if test -x /usr/bin/nano && ! test -d /usr/bin/nano ; then echo "TRUE" ; fi
#	- Bash and Dash have the similar internal command "type"
#	  https://lieeil.wordpress.com/2010/12/05/type-vs-which/
# Depends on functions: (none)
# Depends on other software: (none)
{
	set -ef
	
	puts() {
		printf '%s\n' "$*"
	}
	
	ALLMATCHES=0
	
#	while getopts a whichopts	getopts not supported in Maemo/BusyBox
#	do
#		case "$whichopts" in
#			a) ALLMATCHES=1 ;;
#			?) puts "Usage: $0 [-a] args"; return 2 ;;
#		esac
#	done
#	shift $(($OPTIND - 1))
	if [ "$1" = "-a" ] ; then
		ALLMATCHES=1
		shift
	fi
	
	if [ "$#" -eq 0 ]; then
		ALLRET=1
	else
		ALLRET=0
	fi
	case $PATH in
		(*[!:]:) PATH="$PATH:" ;;
	esac
	for PROGRAM in "$@"; do
		RET=1
		IFS_SAVE="$IFS"
		IFS=:
		case $PROGRAM in
			*/*)
				if [ -f "$PROGRAM" ] && [ -x "$PROGRAM" ]; then
					puts "$PROGRAM"
					RET=0
				fi
				;;
			*)
				for ELEMENT in $PATH; do
					if [ -z "$ELEMENT" ]; then
						ELEMENT=.
					fi
					if [ -f "$ELEMENT/$PROGRAM" ] && [ -x "$ELEMENT/$PROGRAM" ]; then
						puts "$ELEMENT/$PROGRAM"
						RET=0
						[ "$ALLMATCHES" -eq 1 ] || break
					fi
				done
				;;
		esac
		IFS="$IFS_SAVE"
		if [ "$RET" -ne 0 ]; then
			ALLRET=1
		fi
	done
	
	return "$ALLRET"
}

Is_IntegerNr ()
# Syntax as a function: Is_IntegerNr $Valor
# Description: Returns (exitcode 0) TRUE if specified value is an integer number, or FALSE otherwise. (IsNumeric/EsNumerico)
# Use example (without brackets []):
#	if Is_IntegerNr $Respuesta ; then echo "Correcto." ; fi
# Use example verifying also there is only 1 word:
#	if Is_IntegerNr "$Respuesta" ; then echo "Correcto." ; fi
# Notes:
#	- Only first parameter is evaluated. No matter next parameters contain.
# Depends on functions: (none)
# Depends on other software: (none)
{
	local TestValue="$1"
	TestValue="$(expr "$TestValue" : '[ ]*\(.*[^ ]\)[ ]*$')"	# Trim spaces
	if [ "$TestValue" = "" ] ; then TestValue='.' ; fi
	[ "$TestValue" -eq "$TestValue" ] > /dev/null 2>&1
	return $?
}

Dirname ()
# Independent version of GNU dirname
# Useful for Busybox and other minimalistic environments
# Notes:
#	- Only allows 1 path specified
#	- Does not allow any option (such as -z or --version)
# Depends on functions: (none)
# Depends on other software: (none)
{
	local Path="$1"
	local NrElement=0
	local TotalElements=0
	local CurrentElement=''
	local PreviousElement=''
	local Value=''
	
	if [ "$Path" != "" ] ; then
		if [ "$Path" != "/" ] ; then
			IFS="/" ; for CurrentElement in $Path ; do
				NrElement=$(($NrElement + 1))
				if [ $NrElement -gt 1 ] ; then
					if [ $NrElement -gt 2 ] ; then
						if [ "$Value" != "/" ] ; then Value="${Value}/" ; fi
					fi
					Value="${Value}${PreviousElement}"
				else
					if [ "$CurrentElement" = "" ] ; then Value="/" ; fi
				fi
				PreviousElement="$CurrentElement"
			done
			unset IFS
			if [ $NrElement -eq 1 ] && [ "$Value" = "" ] ; then Value="." ; fi
		else
			Value="/"
		fi
	else
		Value="."
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ReadlinkF ()
# Old compatibility replacement for modern "readlink -f"
# "canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist"
# Depends on functions: Dirname
# Depends on other software: grep, sed
{
	local Element="$1"
	local PreviousDir=''
	local CurrentElement=''
	local CurrentPath=''
	local TmpElement=''
	local BaseElement=''
	local ReadlinkResult=0
	local LastPath=''
	local CurStep=''
	local Value=''

	if [ "$Element" != "" ] ; then
		PreviousDir="$(pwd)"
		CurrentRelElement="$Element"
		if [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] && [ -d "$(Dirname "$CurrentRelElement")" ] ; then
			# Same as "readlink -f" behaviour: If element doesn't exist (but path yes), return as if existed.
			cd "$(Dirname "$CurrentRelElement")"
			Value="$(pwd | sed -e 's|/$||g')"
			Value="${Value}/$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"
		fi
		while [ "$(ls "$CurrentRelElement" 2>/dev/null)" != "" ] ; do	# A broken link is not detected with -e
			if [ -d "$(Dirname "$CurrentRelElement")" ] ; then
				cd "$(Dirname "$CurrentRelElement")"
				CurrentPath="$(pwd)"
			else
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
			fi
			BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
			if [ "$CurrentPath" = "/" ] ; then CurrentPath="" ; fi
			Value="${CurrentPath}/${BaseElement}"
			NextRelElement="$(readlink "$BaseElement" 2>/dev/null)"
			ReadlinkResult=$?
			if [ "$NextRelElement" != "" ] ; then
				CurrentRelElement="$NextRelElement"
			else
				if [ $ReadlinkResult -eq 0 ] || [ $ReadlinkResult -eq 1 ] ; then
					# Old readlink (such as v1.13.3 in Potato debianutils) returns exitcode 1 if element is not a link.
					CurrentRelElement=""
				else
					NextRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					if [ "$NextRelElement" != "" ] ; then
						CurrentRelElement="$NextRelElement"
					else
						CurrentRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					fi
				fi
			fi
			if [ "$CurrentRelElement" != "" ] && [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] ; then
				# Broken link
				BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
				Value="${CurrentPath}/${BaseElement}"
			fi
		done
		cd "$PreviousDir"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

Lowercase ()
# Description: Converts specified string to lower letter case, using found tools.
# Depends on functions: Which
# Depends on other software: grep, tr|awk|sed
{
	local OriginalString="$1"
	
	if [ "$OriginalString" != "" ] ; then
		# Busybox tr has no upper/lower conversion
		if [ "$(Which tr)" != "" ] && [ "$(tr --help 2>&1 | grep -e ':lower:')" != "" ] ; then
			printf '%s\n' "$OriginalString" | tr '[:upper:]' '[:lower:]'
		else
			if [ "$(Which awk)" != "" ] ; then
				printf '%s\n' "$OriginalString" | awk '{print tolower($0)}'
			else
				if [ "$(Which sed)" != "" ] && [ "$(sed --help 2>&1 | grep -e 'GNU')" != "" ] ; then
					printf '%s\n' "$OriginalString" | sed -re 's/([[:upper:]]?)/\L\1/g'
				fi
			fi
		fi
	fi
}

DependenciasFaltan ()
# Sintaxis como función: $(DependenciasFaltan "$ListaDependencias")
# Descripción:
#	Comprueba la disponibilidad de los programas especificados y
#	devuelve (stdout) una lista de los que faltan. Si se encuentra todo
#	no devuelve nada.
# Parámetros esperados:
#	$1	Lista separada por espacios de cada ejecutable y paquete respectivo,
#		separados entre si por "/"
#		Para varias opciones de ejecutable, separarlos entre ":"
#	$2	(opcional) Especifique "0" para evitar que a cada mención de paquete se [añada] el nombre del ejecutable que falta.
# Ejemplo1:	$(DependenciasFaltan "cat/coreutils grep/grep dpkg-deb/dpkg")
#		puede devolver "grep dpkg[dpkg-deb]" si faltan grep y dpkg-deb
# Ejemplo2:	$(DependenciasFaltan "insserv:update-rc.d/sysv-rc cat/coreutils")
#		puede devolver "sysv-rc[update-rc.d]" si tanto falta insserv como update-rc.d
# Depends on functions: Which
# Depends on other software: grep, sed
{
	local ListaDependencias="$1"
	local SenyalarEjecutable="$2"
	local DependenciaActual=''
	local EjecutablesActuales=''
	local EjecutableActual=''
	local EncontradoActual=''
	local PaqueteActual=''
	local Value=''
	
	for DependenciaActual in $ListaDependencias ; do
		EjecutablesActuales="$(printf '%s' "$DependenciaActual" | cut -f 1 -d '/')"
		PaqueteActual="$(printf '%s' "$DependenciaActual" | cut -f 2 -d '/')"
		EjecutablesActuales="$(printf '%s' "$EjecutablesActuales" | tr -s ':' ' ')"
		EncontradoActual=''
		for EjecutableActual in $EjecutablesActuales ; do
			if [ "$(Which "$EjecutableActual")" != "" ] ; then
				EncontradoActual="1"
			fi
		done
		if [ "$EncontradoActual" = "" ] ; then
			if [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual\[")" = "" ] || [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual ")" = "" ] ; then
				if [ "$EjecutableActual" = "$PaqueteActual" ] || [ "$EjecutableActual" != "$EjecutablesActuales" ] || [ "$SenyalarEjecutable" = "0" ] ; then
					Value="$Value $PaqueteActual"
				else
					Value="$Value $PaqueteActual[$EjecutableActual]"
				fi
			fi
		fi
	done
	Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

IniSectionContent ()
# Syntax as a function: $(IniSectionContent "$FileOrContent" "$SectionName")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	[section name] without brackets []. Example: "global"
#		specifying "[]" returns file content before any section declaration.
# Notes:
#	- Includes section header 
# Depends on functions:	(none)
# Depends on other software: grep, sed
{
	local FileOrContent="$1"
	local SectionName="$2"
	local LinesNr=0
	local Value=''
	
	if [ "$FileOrContent" != "" ] && [ "$SectionName" != "" ] ; then
		if [ -f "$FileOrContent" ] ; then
			Value="$(cat "$FileOrContent")"
		else
			Value="$FileOrContent"
		fi
		if [ "$SectionName" != "[]" ] ; then
			Value="$(printf '%s\n' "$Value" | sed -rne "/^[[:blank:]]*[[]${SectionName}[]]/,//p")"
			if [ "$Value" != "" ] ; then
				printf '%s\n' "$Value" | head -n 1	# Print before being filtered
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				# Omit current section header
				Value="$(printf '%s\n' "$Value" | tail -n $(($LinesNr - 1)))"
				Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q')"
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				if [ "$(printf '%s\n' "$Value" | tail -n 1 | grep -e '^ [[].*[]]' -e '^[[].*[]]')" != "" ] && [ $LinesNr -ge 2 ] ; then
					# Omit next section header
					Value="$(printf '%s\n' "$Value" | head -n $(($LinesNr - 1)))"
				else
					# Omit when desired section is empty and first line is next section's header
					Value="$(printf '%s\n' "$Value" | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
				fi
			fi
		else
			Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

IniVarValue ()
# Syntax as a function: $(IniVarValue "$FileOrContent" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Default (empty): no section consideration. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. Default; empty string.
#	$5	(optional or empty) Separator between variable name and assigned value. Default: "=". Examples: "=" ":="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value. Default (empty): No end mark. Example: ";"
#	$7	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# Notes:
#	- if section is not specified or empty, returns last match from whole file content.
#	- if section is "[]", queries only file content before any [section] declaration.
# Depends on functions: IniSectionContent
# Depends on other software: grep, sed
{
	local FileOrContent="$1"
	local VariableName="$2"
	local SectionName="$3"
	local NotFoundValue="$4"
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local LinesNr=0
	local SectionContent=''
	local SeparatorMask='IVVtmpbHckF2LMB4tmpWz2coasdb3tmpX7LuyGTvrW'
	local NotFoundKey='IVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	
	if [ "$FileOrContent" != "" ] ; then
		if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator='=' ; fi
		if [ "$SectionName" = "" ] ; then
			if [ -f "$FileOrContent" ] ; then
				SectionContent="$(cat "$FileOrContent")"
			else
				SectionContent="$FileOrContent"
			fi
		else
			SectionContent="$(IniSectionContent "$FileOrContent" "$SectionName")"
		fi
		Value="$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}" | tail -n 1 | sed -e "s|${NameValueSeparator}|${SeparatorMask}|")"
		if [ "$Value" != "" ] ; then
			# Variable found; let's separate value.
			Value="$(printf '%s\n' "$Value" | sed -e "s|.*${SeparatorMask}||")"
			Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			if [ "$EndVariableSymbol" != "" ] ; then
				Value="$(printf '%s\n' "$Value" | sed -e "s|${EndVariableSymbol}$||")"
				Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			fi
			if [ "$(printf '%s\n' "$Value" | grep -e '^"' | grep -e '"$')" != "" ] ; then
				# Double quotes
				Value="$(printf '%s\n' "$Value" | cut -f 2- -d '"' | sed -e 's|"$||')"
			else
				if [ "$(printf '%s\n' "$Value" | grep -e "^'" | grep -e "'$")" != "" ] ; then
					# Single quotes
					Value="$(printf '%s\n' "$Value" | cut -f 2- -d "'" | sed -e "s|'$||")"
				fi
			fi
		else
			Value="$NotFoundKey"
		fi
	else
		Value="$NotFoundKey"
	fi
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ -f "$DefaultsFile" ] ; then
			Value="$(IniVarValue "$DefaultsFile" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" '')"
		else
			Value="$NotFoundValue"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

SetIniVarValue ()
# Syntax as a sentence: SetIniVarValue "$File" "$VariableName" "$SectionName" "$NewValue" "$NameValueSeparator" "$PreComment"
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to write for the variable entry.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#		PreComment can contain escaped \n to be converted into line breaks.
# Pending: Process content separated by section (treat different variable as in different section)
#	- if section is not specified or empty, sets matches in the whole file.
#	- if section is "[]", sets variable only before any [section] declaration in the file.
# Notes:
#	- Some blank lines can be lost in resulting file content
# Depends on functions: Dirname IniSectionContent
# Depends on other software: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"	# Optional or empty (examples: "global" "")
	local NewValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"	# Optional or empty (example: ":=") Default is "="
	local PreComment="$6"	# Optional line to precede variable's line if it must be added. Comment mark should be included.
	local OldContent=''
	local Part1=''
	local SectionContent=''
	local Part2=''
	local Result=0
	local ResultN=0
	
	if [ ! -f "$File" ] ; then
		mkdir -p "$(Dirname "$File")"
		touch "$File"
	fi
	if [ -f "$File" ] ; then
		if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator="=" ; fi
		OldContent="$(cat "$File")"
		if [ "$SectionName" != "" ] ; then
			OldContentLinesNr=$(printf '%s\n' "$OldContent" | wc -l)
			if [ "$SectionName" = "[]" ] ; then
				SectionContent="$(printf '%s\n' "$OldContent" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^[[].*[]]' -ve '^ [[].*[]]')"
				Part1LinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
				cat /dev/null > "$File"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
					if [ "$SectionContent" != "" ] ; then
						printf '%s\n' "$SectionContent" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
					if [ "$PreComment" != "" ] ; then
						if [ "$(cat "$File" 2>/dev/null)" != "" ] && [ "$(cat "$File" | tail -n 1)" != "" ] ; then
							printf '%s\n' "" >> "$File"
							Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
						fi
						printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
					printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				else
					printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
				printf '%s\n' "$OldContent" | tail -n $(($OldContentLinesNr - $Part1LinesNr)) >> "$File"
			else
				if [ "$(printf '%s\n' "$OldContent" | grep -e "^[[]${SectionName}[]]" -e "^ [[]${SectionName}[]]")" != "" ] ; then
					# Case sensitive to match sed that cannot be insensitive with q/p commands
					Part1="$(printf '%s\n' "$OldContent" | sed -re "/^[[:blank:]]*[[]${SectionName}[]]/q")"
					Part1LinesNr=$(printf '%s\n' "$Part1" | wc -l)
					Part1LinesNr=$(($Part1LinesNr - 1))
					Part1="$(printf '%s\n' "$Part1" | grep -ive "^[[]${SectionName}[]]" -ive "^ [[]${SectionName}[]]")"
					SectionContent="$(IniSectionContent "$File" "$SectionName")"
					SectionLinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
					Part2="$(printf '%s\n' "$OldContent" | tail -n $(($OldContentLinesNr - $Part1LinesNr - $SectionLinesNr)))"
					cat /dev/null > "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					if [ "$Part1" != "" ] ; then
						printf '%s\n' "$Part1" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
					if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
						printf '%s\n' "$SectionContent" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
						if [ "$PreComment" != "" ] ; then
							printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
							Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
						fi
						printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					else
						printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
					if [ "$Part2" != "" ] ; then
						printf '%s\n' "$Part2" >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
				else
					printf '%s\n' "[${SectionName}]" >> "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					if [ "$PreComment" != "" ] ; then
						printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
						Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					fi
					printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
			fi
		else
			if [ "$(printf '%s\n' "$OldContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
				if [ "$PreComment" != "" ] ; then
					printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
				printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			else
				printf '%s\n' "$OldContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|i" > "$File"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
	fi
	return $ResultN
}

GetOrSetIniVarValue ()
# Syntax as a function: $(GetOrSetIniVarValue "$File" "$VariableName" "$SectionName" "$DefaultValue" "$NameValueSeparator" "$EndVariableSymbol" "$PreComment" "$ReadOnly" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value and to be written when needed. Example: ";"
#	$7	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#	$8	(optional or empty) "1" or "ro" if $File must not be modified
#	$9	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# Notes:
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- PreComment can contain escaped \n to be converted into line breaks.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on other software: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local PreComment="$7"
	local ReadOnly="$8"
	local DefaultsFile="$9"
	local NotFoundKey='GOSIVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	local Result=0
	local ResultN=0
	
	Value="$(IniVarValue "$File" "$VariableName" "$SectionName" "$NotFoundKey" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")"
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ "$ReadOnly" != "1" ] && [ "$ReadOnly" != "ro" ] ; then
			SetIniVarValue "$File" "$VariableName" "$SectionName" "${DefaultValue}${EndVariableSymbol}" "$NameValueSeparator" "$PreComment"
			Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		fi
		Value="$DefaultValue"
		if [ "$(printf '%s\n' "$Value" | grep -e '^"')" != "" ] ; then
			Value="$(printf '%s\n' "$Value" | cut -f 2 -d '"')"
		else
			# Only avoid EndVariableSymbol if it's not inside a quoted string
			Value="$(printf '%s\n' "$Value" | sed -e "s|${EndVariableSymbol}$||g" | sed -e 's| $||g')"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	return $ResultN
}


##### PROGRAM TEMPLATE BASE FUNCTIONS #####

LogProgram ()
# Syntax as a sentence: LogProgram $ThisLevel $Message
# Expected parameters:
#	$1	Level number of the message:
#		0=NothingToSay 1=Error 2=Warning 3=NormalInfo 4=DebugInfo
#		(same numbers in negative sign to not write date-time stamp)
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
{
	local ThisLevel="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local CallType=''
	local DatetimeStamp=''
	local AskedText=''
	local CurString=''
	
	if [ $ThisLevel -ge 0 ] ; then
		DatetimeStamp="$(date '+%Y-%m-%dT%T%z') "
	fi
	ThisLevel="$(printf '%s\n' "$ThisLevel" | sed -e 's|^-||g')"
	if [ "$*" != "" ] && [ $LogLevel -ge $ThisLevel ] && [ $ThisLevel -gt 0 ] ; then
		for CurString in "$@" ; do	# Simple $* sometimes results in comma-separated words
			if [ "$AskedText" != "" ] ; then AskedText="${AskedText} " ; fi
			AskedText="${AskedText}$(printf '%s\n' "$CurString" | sed -e 's|\\n|\n|g')"
		done
		if [ $ThisLevel -ge 4 ] ; then
			printf '%s\n' "$AskedText" 1>&2
		else
			printf '%s\n' "$AskedText"
		fi
		if [ "$MainLog" != "" ] ; then
			mkdir -p "$(Dirname "$MainLog")"
			printf '%s\n' "${DatetimeStamp}${CallType}${AskedText}" >> "$MainLog"
		fi
	fi
}

BreakingControls ()
{
	if [ $(id -g) -ne 0 ] && [ "$RootRequired" != "0" ] ; then
		printf '%s\n' "E: This program needs to be run with superuser ${ParO}root${ParC} permissions." 1>&2
		exit 45
	fi
}

DeleteIfNotMe ()
{
	local FileToDelete="$1"
	local Result=0
	
	if [ "$FileToDelete" != "" ] && [ "$FileToDelete" != "$MeCallFile" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeCallFile" ] && [ "$FileToDelete" != "$MeExecutable" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeExecutable" ] ; then
		rm -fr "$FileToDelete"
		Result=$?
	fi
	return $Result
}

FoundProgramScript ()
{
	local Value=""
	if [ -x "/bin/${ProgramName}" ] ; then Value="/bin/${ProgramName}" ; fi
	if [ -x "/sbin/${ProgramName}" ] ; then Value="/sbin/${ProgramName}" ; fi
	if [ -x "/usr/bin/${ProgramName}" ] ; then Value="/usr/bin/${ProgramName}" ; fi
	if [ -x "/usr/sbin/${ProgramName}" ] ; then Value="/usr/sbin/${ProgramName}" ; fi
	if [ -x "/usr/local/bin/${ProgramName}" ] ; then Value="/usr/local/bin/${ProgramName}" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}" ] ; then Value="/usr/local/sbin/${ProgramName}" ; fi
	if [ -x "/bin/${ProgramName}.sh" ] ; then Value="/bin/${ProgramName}.sh" ; fi
	if [ -x "/sbin/${ProgramName}.sh" ] ; then Value="/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/bin/${ProgramName}.sh" ] ; then Value="/usr/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/sbin/${ProgramName}.sh" ] ; then Value="/usr/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/bin/${ProgramName}.sh" ] ; then Value="/usr/local/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}.sh" ] ; then Value="/usr/local/sbin/${ProgramName}.sh" ; fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ScriptHeaderValue ()
# Note: it's absolutely case-sensitive
{
	local HeaderKey="$1"
	local VariableKey=""
	local Value=""
	
	if [ "$(cat "$MeExecutable" | grep -e '^:.*SCRIPTHEADER$')" != "" ] && [ "$(cat "$MeExecutable" | grep -e '^SCRIPTHEADER$')" != "" ] ; then
		Value="$(echo aaa$(cat "$MeExecutable" | sed -ne '/^:.*SCRIPTHEADER$/,//p' | sed -e '/^SCRIPTHEADER$/q' | grep -e "^${HeaderKey}:" | head -n 1 | cut -f 2- -d ':') | sed -e 's|^aaa||g' | sed -e 's|^ ||g')"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

MeInstalled ()
# Returns 1 if this script is placed as system installed path.
{
	local Value=0
	
	if [ "$(printf '%s\n' "$MeExecutable" | grep -e 'bin/')" != "" ] ; then
		Value=1
	fi
	printf '%s\n' "$Value"
}

Install_precp ()
# WARNING: Package manager cannot invoke this action before this script file is installed. Install_postcp() calls this with argument "postcp" if didn't run.
{
	local Mode="$1"
	local Mode2="$2"
	local Result=0
	local ResultN=0
	
	if [ $ResultN -eq 0 ] ; then
		Install_precp_Pre "$(Dirname "$MeExecutable")" "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	fi
	TheFoundProgramScript="$(FoundProgramScript)"
	if [ $ResultN -eq 0 ] && [ -x "$TheFoundProgramScript" ] ; then
		if [ "$Mode" != "postcp" ] ; then
			if [ "$PackageManager_Call" = "" ] && [ "$Mode" != "force" ] && [ "$Mode2" != "force" ] ; then
				if [ "$(IniVarValue "$(cat "$TheFoundProgramScript" | grep -e '^ProgramInstaller=' | head -n 1)" ProgramInstaller '' '' = '' '')" = "0" ] ; then
					printf '%s\n' "E: Program already installed. Use your package manager to un/install $(printf '%s\n' "$TheFoundProgramScript" | tr -s '/' '\n' | tail -n 1)." 1>&2
					Result=99 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
			fi
			if [ $ResultN -eq 0 ] ; then
				printf '%s\n' "Trying to uninstall previous $ProgramName"
				"$TheFoundProgramScript" uninstall "$@"
				Result=$?
				if [ $Result -eq 87 ] || [ $Result -eq 127 ] ; then # Unknown action
					rm "$TheFoundProgramScript"
					Result=$?
				fi
				if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				if [ -x "$TheFoundProgramScript" ] ; then
					printf '%s\n' "W: old executable $TheFoundProgramScript not removed." 1>&2
					printf '%s\n' "Actions recommended:"
					printf '%s\n' "	1. Retry un/installation"
					printf '%s\n' "	2. Remove old program manually if necessary"
				fi
				if [ $ResultN -eq 0 ] ; then
					# Important to do this after uninstalling previous version.
					Configuration "$@"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
					if [ -d "$SystemConfigDir" ] ; then
						printf '%s\n' "System level configurations directory is: $SystemConfigDir"
					fi
					printf '%s\n' "System level parameters can be at: $SystemConfigFile"
				fi
			fi
		fi
	fi
	if [ $ResultN -eq 0 ] ; then
		Install_precp_Post "$(Dirname "$MeExecutable")" "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	fi
	if [ $ResultN -eq 0 ] ; then
		# Old executables were a duplicity of program script.
		DeleteIfNotMe "/bin/${ProgramName}"
		DeleteIfNotMe "/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/bin/${ProgramName}"
		DeleteIfNotMe "/usr/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
		DeleteIfNotMe "/bin/${ProgramName}.sh"
		DeleteIfNotMe "/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}.sh"
		DeleteIfNotMe "$InitExecutableLink"
		DeleteIfNotMe "$ProgramExecutablePath"
	fi
	touch "${DirTemp}/${ProgramName}.install-precp.done"
	return $ResultN
}

Install_cp ()
{
	local SourceScript="$1"
	local Result=0
	local ResultN=0
	
	if [ $ResultN -eq 0 ] ; then
		if [ "$SourceScript" != "$ProgramExecutablePath" ] && [ "$SourceScript" != "$InitExecutableLink" ] ; then
			PreviousUmask="$(umask)"
			umask u=rwx,go=rx
			mkdir -p "$(Dirname "$ProgramExecutablePath")"
			umask "$PreviousUmask"
			cp "$SourceScript" "$ProgramExecutablePath"
			Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			chown root:root "$ProgramExecutablePath"
			chmod u=rwx,go=rx "$ProgramExecutablePath"
		else
			printf '%s\n' "E: Cannot install from destination path." 1>&2
			Result=94 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		fi
	fi
	return $ResultN
}

Install_postcp ()
{
	local Result=0
	local ResultN=0
	
	if [ ! -f "${DirTemp}/${ProgramName}.install-precp.done" ] ; then
		Install_precp postcp
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		rm -f "${DirTemp}/${ProgramName}.install-precp.done"
	fi
	if [ $ResultN -eq 0 ] ; then
		Install_postcp_More "$(Dirname "$MeExecutable")" "$@"	#"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	fi
	if [ $ResultN -eq 0 ] ; then
		# Writing system-wide defaults
		Configuration "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	fi
	return $ResultN
}

Uninstall_predel ()
{
	local Result=0
	local ResultN=0
	
	Uninstall_predel_More "$@"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	return $ResultN
}

Uninstall_del ()
{
#	DeleteIfNotMe "$ProgramExecutablePath"
#	DeleteIfNotMe "/bin/${ProgramName}"
#	DeleteIfNotMe "/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
	rm -f "/bin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/sbin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/bin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/sbin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/local/bin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/local/sbin/${ProgramName}"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/bin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/sbin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/bin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/sbin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/local/bin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "/usr/local/sbin/${ProgramName}.sh"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	rm -f "$ProgramExecutablePath"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	if [ $ResultN -eq 0 ] ; then
		printf '%s\n' "Program files removed."
	fi
}

Uninstall_postdel ()
{
	local Result=0
	local ResultN=0
	
	Uninstall_Postdeletion "$@"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	return $ResultN
}

Uninstall_purge ()
{
	local Result=0
	local ResultN=0
	
	rm -f "${DirTempX}/${ProgramName}.uninstall.tmp" "/var/tmp/${ProgramName}.uninstall.tmp"
	Uninstall_purge_MorePre "$@"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	if [ -f "$SystemConfigFile" ] ; then
		printf '%s\n' "Removing: $SystemConfigFile"
		rm "$SystemConfigFile"
	fi
	if [ -d "$SystemConfigDir" ] ; then
		printf '%s\n' "Removing: $SystemConfigDir"
		rm -r "$SystemConfigDir"
	fi
	if [ "$ChildsPluralName" != "" ] ; then
		rm -fr "$Childs_logs"
	fi
	rm -f "$MainLog"
	rm -f "/var/log/upstart/${ProgramName}."*
	Uninstall_purge_MorePost "$@"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	if [ $Result -eq 0 ] ; then
		printf '%s\n' "Configurations and logs cleaned."
	fi
	return $ResultN
}

GetSetLocalConfig ()
# Syntax as a function: $(GetSetLocalConfig "$ReadOnly" "$VariableName" "$SectionName" "$DefaultValue" "$PreComment")
# Description: Gets the most local value for a configuration (1:user 2:system 3:filedefault 4:specifieddefault) and optionally writes if nowhere found.
# Expected parameters:
#	$1	"1" to not write anything at files; "0" to save variable when not found.
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
# Notes:
#	- NOT USEFUL when default value can be invalid for unprivileged user, such as file/dir paths to write: [system.conf=]/etc/custom.list vs [user.conf=]~/custom.list
#	  NOT USEFUL for other config files but SystemConfigFile & UserConfigFile
#	  In these cases, it's convenient to use GetOrSetIniVarValue()
#	- USEFUL when need to use system-level value on user-level lack.
#	- $SystemDefaults is used as DefaultsFile. A found value here prevails over 4th parameter (DefaultValue)
#	- If DefaultValue is to be written (because no value found anywhere and ReadOnly==0), root user will write to $SystemConfigFile and others will write to $UserConfigFile
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- Separator between variable name and assigned value is assumed to be '='
#	- No end variable/assignation mark assumed.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on other software: grep
{
	local ReadOnly="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local PreComment="$5"
	local DefaultsFile="$SystemDefaults"
	local NotFoundKey=''
	local Value=''
	local Result=0
	local ResultN=0
	
	NotFoundKey="#GOSIVV$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d ' ')#"
	Value="$(IniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' '')"
	if [ "$Value" = "$NotFoundKey" ] ; then
		Value="$(IniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' "$DefaultsFile")"
		if [ "$Value" = "$NotFoundKey" ] ; then
			if [ $ReadOnly -eq 0 ] ; then
				if [ $(id -u) -eq 0 ] ; then
					SetIniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				else
					SetIniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
			fi
			Value="$DefaultValue"
			if [ "$(printf '%s\n' "$Value" | grep -e '^".*"$')" != "" ] ; then Value="$(printf '%s\n' "$Value" | cut -f 2 -d '"')" ; fi	#'
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s' "$Value" ; fi
	return $ResultN
}

Configuration ()
{
	local ReadOnly=0
	local BaseName=''
	local Result=0
	local ResultN=0

	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
	MeExecutable="$(ReadlinkF "$MeCallFile")"
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	BreakingControls
	if [ "$1" = "pmc" ] ; then
		PackageManager_Call="$2"
		PackageManager_Mode="$3"
		PackageManager_ForVersion="$4"
	else
		PackageManager_Call=''
	fi
	if [ "$RootRequired" = "0" ] ; then
		ProgramExecutablePath="/bin/${ProgramName}"
	else
		ProgramExecutablePath="/sbin/${ProgramName}"
	fi
	if [ "$EssentialAtBoot" != "1" ] ; then
		ProgramExecutablePath="/usr${ProgramExecutablePath}"
	fi
	Configuration_Saved_Pre "$@"
	Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	if [ ! -d "$SystemConfigDir" ] && [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigDir")" ] ; then
		mkdir -p "$SystemConfigDir"
		chmod u=rwX,go=rX "$SystemConfigDir"
	fi
	SystemDefaults="${SystemConfigDir}/system.defaults"	# This file is never written by program; only provided by package manager and updates
	SystemConfigFile="${SystemConfigDir}/system.conf"
	if [ -f "${SystemConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] && [ $(id -u) -eq 0 ] ; then
			mv "${SystemConfigDir}/default.conf" "$SystemConfigFile"
		else
			SystemConfigFile="${SystemConfigDir}/default.conf"
		fi
	fi
	BaseName="$(printf '%s\n' "$SystemConfigDir" | tr -s '/' '\n' | tail -n 1)"
	UserDir="$HOME"
	if [ "$(printf '%s\n' "$BaseName" | grep -e '^\.')" != "" ] ; then
		UserConfigDir="${UserDir}/${BaseName}"
	else
		UserConfigDir="${UserDir}/.${BaseName}"
	fi
	if [ ! -d "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
	if [ ! -w "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
	UserDefaults="${UserConfigDir}/user.defaults"	# This file is never written by program; only provided by package manager/skel
	UserConfigFile="${UserConfigDir}/user.conf"
	if [ $ReadOnly -eq 0 ] && [ ! -d "$UserConfigDir" ] ; then
		mkdir -p "$UserConfigDir"
	fi
	if [ -f "${UserConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] ; then
			mv "${UserConfigDir}/default.conf" "$UserConfigFile"
		else
			UserConfigFile="${UserConfigDir}/default.conf"
		fi
	fi
	if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigFile")" ] ; then
		if [ ! -f "$SystemConfigFile" ] ; then
			printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$SystemConfigFile"
			if [ "$RootRequired" = "0" ] ; then
				printf '%s\n' "# $ProgramName system-wide and user-default working parameters" >> "$SystemConfigFile"
			else
				printf '%s\n' "# $ProgramName working parameters" >> "$SystemConfigFile"
			fi
			printf '%s\n' "" >> "$SystemConfigFile"
		fi
	fi
	CurrentConfigDir="$SystemConfigDir"
	CurrentConfigFile="$SystemConfigFile"
	if [ $(id -u) -ne 0 ] && [ "$RootRequired" = "0" ] ; then
		CurrentConfigDir="$UserConfigDir"
		CurrentConfigFile="$UserConfigFile"
		if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$UserConfigFile")" ] ; then
			if [ ! -f "$UserConfigFile" ] ; then
				printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$UserConfigFile"
				printf '%s\n' "# $ProgramName user working parameters. This overrides configurations over $SystemConfigFile" >> "$UserConfigFile"
				printf '%s\n' "" >> "$UserConfigFile"
			fi
		fi
	fi
	MainLog="/var/log/${ProgramName}.log"
	if [ $(id -u) -ne 0 ] ; then
		MainLog="${UserConfigDir}/main.log"
	fi
	if [ "$LogLevel" != "4" ] ; then	# Debugging probably a nested call (exported LogLevel)
		LogLevel="$(GetSetLocalConfig "$ReadOnly" LogLevel '' 3 '# LogLevel: 0=Nothing 1=Errors 2=Warnings+E 3=Info+W+E 4=Debug')"
		if ! Is_IntegerNr "$LogLevel" ; then LogLevel=3 ; fi
	fi
	if [ $ReadOnly -eq 0 ] ; then
		Configuration_Saved_Post "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	else
		Configuration_Saved_Post ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
	fi
	return $ResultN
}


##### PROGRAM FUNCTIONS from @-funcions x.x.x #####

#[diskimg-resize]
RespostaLletra ()
# Sintaxi com a funcio: $(RespostaLletra $JaEspecificada)
# Descripció: Reads a keyboard-typed answer (ENTER must be pressed) and returns first answer letter (lower case if possible - not in Maemo)
# Paràmetres esperats:
#	$1	(opcional) Tractar aquesta cadena en comptes de demanar-la de teclat.
# Depends on functions: Lowercase
# Depends on software packages: (none)
{
	JaEspecificada="$1"
	if [ "$JaEspecificada" = "" ] ; then read JaEspecificada ; fi
	JaEspecificada="$(printf '%s' "$JaEspecificada" | cut -c 1)"
	JaEspecificada="$(Lowercase "$JaEspecificada")"
	if [ "$JaEspecificada" != "" ] ; then printf '%s\n' "$JaEspecificada" ; fi
}

NumeroResumit ()
# Sintaxi com a funció: $(NumeroResumit $Numero $Estil)
# Retorna el número humanament llegible, segons $Estil:
# 	"2" :	Amb factors de 1024 (10,5b; 10,5k; 10,5m; 10,5g)
# 	"2L" :	Amb factors de 1024 i notació llarga (10,5B; 10,5KiB; 10,5MiB; 10,5GiB)
# 	"1" :	Amb factors de 1000 (10,5B; 10,5K; 10,5M; 10,5G) (SI)
# 	"." :	Utilitzar el separador decimal "." en comptes del predeterminat ","
#	Si s'afegeix un espai, se separa el número de la notació amb un espai:
#		"2 "   10,5 g
#		"2L "  9,5 MiB
#		"1 "   8300,5 B
#	Si s'afegeix una "A", aleshores s'alineen les dades:
#		"A2"	   10,5g
#		"A2L"    9,5MiB
#		"A1"	 8300,5B
#	Si s'afegeix un "0", aleshores no s'expressen decimals (només l'enter arrodonit a la baixa):
#		"02"  10g
#		"02L" 9MiB
#		"01"  8300B
#	Tot es pot combinar:
#		"A02L "     9 MiB
#		"A01L "  8300 B
#		"2L."     9.5MiB
# Nota: el $Numero s'espera en bytes (octets)
# ToDo:
#	- Try to not depend on bc
# Depends on functions: (none)
# Depends on other software: grep, bc
{
	local Numero=$1
	local Estil="$2"
	local LlindarResum=2000
	local SimbolDecimal=","
	local EnterVisible=''
	local DecimalVisible=''
	local Sufix=''
	local Factor=0
	
	if [ "$(printf '%s' "$Estil" | grep "\.")" != "" ] ; then SimbolDecimal="." ; fi
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then
		Factor=1024
	else
		if [ "$(printf '%s' "$Estil" | grep "1")" != "" ] ; then
			Factor=1000
		else
			return 1
		fi
	fi
	EnterVisible="$Numero"
	Sufix="B"
	# Resum
	if [ $Numero -ge $LlindarResum ] || [ $(printf '%s\n' "$Numero" | sed -e 's|.....$||g') -ge $LlindarResum ] ; then
		EnterVisible="$(echo "$Numero / $Factor" | bc)"
		DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
		Numero=$(echo "$Numero / $Factor" | bc)
		Sufix="K"
		if [ $Numero -ge $LlindarResum ] ; then
			EnterVisible="$(echo "$Numero / $Factor" | bc)"
			DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
			Numero=$(echo "$Numero / $Factor" | bc)
			Sufix="M"
			if [ $Numero -ge $LlindarResum ] ; then
				EnterVisible="$(echo "$Numero / $Factor" | bc)"
				DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
				Numero=$(echo "$Numero / $Factor" | bc)
				Sufix="G"
				if [ $Numero -ge $LlindarResum ] ; then
					EnterVisible="$(echo "$Numero / $Factor" | bc)"
					DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
					Numero=$(echo "$Numero / $Factor" | bc)
					Sufix="T"
					if [ $Numero -ge $LlindarResum ] ; then
						EnterVisible="$(echo "$Numero / $Factor" | bc)"
						DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
						Numero=$(echo "$Numero / $Factor" | bc)
						Sufix="P"
						if [ $Numero -ge $LlindarResum ] ; then
							EnterVisible="$(echo "$Numero / $Factor" | bc)"
							DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
							Numero=$(echo "$Numero / $Factor" | bc)
							Sufix="E"
							if [ $Numero -ge $LlindarResum ] ; then
								EnterVisible="$(echo "$Numero / $Factor" | bc)"
								DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
								Numero=$(echo "$Numero / $Factor" | bc)
								Sufix="Z"
								if [ $Numero -ge $LlindarResum ] ; then
									EnterVisible="$(echo "$Numero / $Factor" | bc)"
									DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
									Numero=$(echo "$Numero / $Factor" | bc)
									Sufix="Y"
								fi
							fi
						fi
					fi
				fi
			fi
		fi
	fi
	# Decimal invisible
	if [ "$(printf '%s' "$Estil" | grep "0")" != "" ] ; then
		DecimalVisible=''
	else
		if [ "$DecimalVisible" = "" ] ; then DecimalVisible="0" ; fi
		DecimalVisible="$SimbolDecimal$DecimalVisible"
	fi
	# Notació llarga
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then  # factor binari, sufix en minuscula
		case "$Sufix" in
			"B" )	Sufix="b"	;;
			"K" )	Sufix="k"	;;
			"M" )	Sufix="m"	;;
			"G" )	Sufix="g"	;;
			"T" )	Sufix="t"	;;
			"P" )	Sufix="p"	;;
			"E" )	Sufix="e"	;;
			"Z" )	Sufix="z"	;;
			"Y" )	Sufix="y"	;;
		esac
		if [ "$(printf '%s' "$Estil" | grep "L")" != "" ] ; then
			case "$Sufix" in
				"b" )	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
						Sufix="B  "	# Amb aliniació
					else
						Sufix="B"
					fi
					;;
				"k" )	Sufix="KiB"	;;
				"m" )	Sufix="MiB"	;;
				"g" )	Sufix="GiB"	;;
				"t" )	Sufix="TiB"	;;
				"p" )	Sufix="PiB"	;;
				"e" )	Sufix="EiB"	;;
				"z" )	Sufix="ZiB"	;;
				"y" )	Sufix="YiB"	;;
			esac
		fi
	fi
	# Espai
	if [ "$(printf '%s' "$Estil" | grep -e ' ')" != "" ] ; then
		Sufix=" $Sufix"
	fi
	# Aliniació
	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
		while [ ${#EnterVisible} -lt 4 ] ; do
			EnterVisible=" $EnterVisible"
		done
	fi
	printf '%s\n' "$EnterVisible$DecimalVisible$Sufix"
}

FileSize_Apparent ()
# Syntax as a function: $(FileSize_Apparent "$FileSpec")
# Description: Returns (stdout) traditional size in bytes, of specified file
# Notes:
#	- Sparse holes are included. Not accounting whole blocks but file size pointer.
#	- If file is not found, nothing is returned.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local FileSpec="$1"
	
	if [ -f "$FileSpec" ] ; then
		stat -c %s "$FileSpec"
	fi
}

FileSize_Allocated ()
# Syntax as a function: $(FileSize_Allocated "$FileSpec")
# Description: Returns (stdout) sum size of allocated blocks, in bytes, of specified file
# Notes:
#	- Sparsed complete blocks are NOT included. Accounting only used blocks, and last one as if it were completelly full.
#	- WARNING: For sparse files it's currently not possible to account real unused segments of data in EACH partially used block.
#	- If file is not found, nothing is returned.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local FileSpec="$1"
	local SectorsNr=''
	local SectorSize=''
	
	if [ -f "$FileSpec" ] ; then
		SectorsNr=$(stat -c %b "$FileSpec")
		SectorSize=$(stat -c %B "$FileSpec")
		# Max resulting number of 8388607 TiB at 64-bit architecture
		echo $((SectorsNr * SectorSize))
	fi
}

FileSize_DataUsed ()
# Syntax as a function: $(FileSize_DataUsed "$FileSpec")
# Description: Returns (stdout) real data size in bytes, of specified file
# Notes:
#	- Sparsed complete blocks are NOT included. Last partially used block is accounted only used bytes.
#	- WARNING: For sparse files it's currently not possible to account real used segments of data in EACH partially used block.
#	  Sparse files they often have multiple blocks partially used with data, not only the last one.
#	- If file is not found, nothing is returned.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local FileSpec="$1"
	local SectorsNr=''
	local SectorSize=''
	local AllocatedSize=''
	local ApparentSize=''
	
	if [ -f "$FileSpec" ] ; then
		SectorsNr=$(stat -c %b "$FileSpec")
		SectorSize=$(stat -c %B "$FileSpec")
		# Max resulting number of 8388607 TiB at 64-bit architecture
		AllocatedSize=$((SectorsNr * SectorSize))
		ApparentSize=$(stat -c %s "$FileSpec")
		if [ $AllocatedSize -lt $ApparentSize ] ; then
			BlockSize=$(stat -c %o "$FileSpec")
			LastBytes=$((AllocatedSize % BlockSize))
			FullBlocksBytes=$((AllocatedSize / BlockSize * BlockSize))
			echo $((FullBlocksBytes + LastBytes))
		else
			echo $ApparentSize
		fi
	fi
}

FileSize_Max ()
# Syntax as a function: $(FileSize_Max "$FileSpec")
# Description: Returns (stdout) sum size, in bytes, of blocks that can be allocated to attend apparent size of specified file
# Notes:
#	- Sparsed holes are included. Accounting real disk allocation if file it were unsparsed.
#	- If file is not found, nothing is returned.
# Depends on functions: (none)
# Depends on software packages: sed
{
	local FileSpec="$1"
	local BlockSize=''
	local ApparentSize=''
	local LastBytes=''
	local FullBlocksBytes=''
	
	if [ -f "$FileSpec" ] ; then
		BlockSize=$(stat -c %o "$FileSpec")
		ApparentSize=$(stat -c %s "$FileSpec")
		LastBytes=$((ApparentSize % BlockSize))
		FullBlocksBytes=$((ApparentSize / BlockSize * BlockSize))
		if [ $LastBytes -eq 0 ] ; then
			echo $FullBlocksBytes
		else
			echo $((FullBlocksBytes + BlockSize))
		fi
	fi
}

#[/diskimg-resize]


##### SPECIFIC FUNCTIONS TO THIS SCRIPT #####

#[diskimg-resize]
ReportFileSizes ()
{
	local ImagefilePath="$1"
	local CurSizeB_Apparent=''
	local CurSizeH_Apparent=''
	local CurSizeB_Allocated=''
	local CurSizeH_Allocated=''
	local CurSizeB_Used=''
	local CurSizeH_Used=''
	
	ImagefileBasename="$(printf '%s\n' "$ImagefilePath" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
#	CurSizeMiB_Apparent="$(($(stat -c %s "$ImagefilePath") / 1024 / 1024))"
#	CurSizeB_Apparent="$(stat -c %s "$ImagefilePath")"
	CurSizeB_Apparent=$(FileSize_Apparent $ImagefilePath)
	CurSizeH_Apparent="$(NumeroResumit $CurSizeB_Apparent '2L ')"
	# "du" utility doesn't make an adecuate division by 1024 to result in -m
#	CurSizeB_Allocated="$(echo TrimAndSingle $(du -b "$ImagefilePath" | head -n 1) | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 1 -d ' ')"
#	# "du -b" shows apparent size
#	CurSizeB_Allocated="$(echo TrimAndSingle $(du --block-size=1 "$ImagefilePath" | head -n 1) | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 1 -d ' ')"
#	CurSizeMiB_Allocated=$(($CurSizeB_Allocated / 1024 / 1024))
#	CurSizeB_Allocated="$(echo TrimAndSingle $(du --block-size=1 "$ImagefilePath" | head -n 1) | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 1 -d ' ')"
	CurSizeB_Allocated=$(FileSize_Allocated "$ImagefilePath")
	CurSizeH_Allocated="$(NumeroResumit $CurSizeB_Allocated '2L ')"
	CurSizeB_Used=$(FileSize_DataUsed "$ImagefilePath")
	CurSizeH_Used="$(NumeroResumit $CurSizeB_Used '2L ')"
#	printf '%s\n' "$ImagefileBasename found with an apparent size of: $CurSizeH_Apparent ${ParO}${CurSizeH_Allocated} allocated${ParC}"
	printf '%s\n' "$ImagefileBasename found with an apparent size of: $CurSizeH_Apparent ${ParO}${CurSizeH_Used} used${ParC}"
}

ExtendFile ()
{
	local Mode="$1"
	local ImagefilePath="$2"
	local NewSizeMiB="$3"
	local AnswerCreate="$4"
	local CurSizeMiB_Apparent=''
	local CurSizeMiB_Used=''
	local NewSizePercent=''
	local MiB1=''
	local ActionWord='Extending'
	
	MiB1=$((1024 * 1024))
	if [ "$ImagefilePath" != "" ] ; then
		if [ "$NewSizeMiB" != "" ] ; then
			if [ "$NewSizeMiB" = "2T" ] || [ "$NewSizeMiB" = "2t" ] ; then
				NewSizeMiB=$((2 * 1024 * 1024))
				# It's technically enough with 2TiB-512bytes, but we must use MiB unit here as all partition tools support.
				NewSizeMiB=$(($NewSizeMiB - 1))
			fi
			if [ ! -e "$ImagefilePath" ] && [ ! -L "$ImagefilePath" ] ; then
				if [ "$AnswerCreate" = "" ] ; then
					printf '%s' "Disk image file not found. Do you want to create it [y/N]? "
					AnswerCreate="$(RespostaLletra)"
				fi
				if [ "$AnswerCreate" = "y" ] || [ "$AnswerCreate" = "s" ] ; then
					touch "$ImagefilePath"
					Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
			else
				AnswerCreate=''
			fi
			if [ -f "$ImagefilePath" ] ; then
				# Disk usage (not file size)
				# "du" utility doesn't make an adecuate division by 1024 to result in -m
				#CurSizeMiB_Allocated="$(echo TrimAndSingle $(du -b "$ImagefilePath" | head -n 1) | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 1 -d ' ')"
				# "du -b" shows apparent size
#				CurSizeMiB_Allocated="$(echo TrimAndSingle $(du --block-size=1 "$ImagefilePath" | head -n 1) | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 1 -d ' ')"
#				CurSizeMiB_Allocated=$(($CurSizeMiB_Allocated / 1024 / 1024))
				CurSizeMiB_Used="$(($(FileSize_DataUsed "$ImagefilePath") / 1024 / 1024))"
				# File size (apparent if sparse)
#				CurSizeMiB_Apparent="$(($(stat -c %s "$ImagefilePath") / 1024 / 1024))"
				CurSizeMiB_Apparent="$(($(FileSize_Apparent "$ImagefilePath") / 1024 / 1024))"
				if Is_IntegerNr "$CurSizeMiB_Apparent" ; then
					if [ "$AnswerCreate" = "" ] ; then
						ReportFileSizes "$ImagefilePath"
					else
						ActionWord='Creating'
					fi
					if [ "$(printf '%s\n' "$NewSizeMiB" | grep -e '%$')" != "" ] ; then
						NewSizePercent="$(printf '%s\n' "$NewSizeMiB" | cut -f 1 -d '%')"
						if Is_IntegerNr "$NewSizePercent" ; then
							NewSizeMiB=$(($CurSizeMiB_Apparent * $NewSizePercent / 100))
						fi
					fi
					if Is_IntegerNr "$NewSizeMiB" && [ $NewSizeMiB -gt 0 ] ; then
						if [ $NewSizeMiB -gt $CurSizeMiB_Used ] ; then
							if [ "$Mode" = "sparse" ] ; then
								# Sparse increase
								if [ $NewSizeMiB -gt $CurSizeMiB_Apparent ] ; then
									printf '%s\n' "$ActionWord file size to $NewSizeMiB MiB without spending space."
									dd if=/dev/zero "of=${ImagefilePath}" obs=$MiB1 seek=$NewSizeMiB count=0
									Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
									if [ $ResultN -eq 0 ] ; then
										printf '%s\n' "Result is:"
										ReportFileSizes "$ImagefilePath"
									fi
								else
									printf '%s\n' "E: Contained partitions resizing not implemented. Can't shrink to $NewSizeMiB MiB." 1>&2
									Result=91 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
								fi
							else
								# Whole raw to disk, and de-sparsify
								printf '%s\n' "$ActionWord file space use to $NewSizeMiB MiB."
								fallocate --length ${NewSizeMiB}M "$ImagefilePath"
								Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
								if [ $ResultN -eq 0 ] ; then
									printf '%s\n' "Result is:"
									ReportFileSizes "$ImagefilePath"
								fi
							fi
						else
							if [ $NewSizeMiB -eq $CurSizeMiB_Used ] ; then
								printf '%s\n' "E: New size must be greater than current used space." 1>&2
								Result=92 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
							else
								printf '%s\n' "E: Contained partitions resizing not implemented. Can't shrink to $NewSizeMiB MiB." 1>&2
								Result=91 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
							fi
						fi
					else
						if [ $NewSizeMiB -gt 0 ] ; then
							printf '%s\n' "E: Third parameter must be an integer number ${ParO}new RAW disk image size in MiB${ParC} or a final percent ${ParO}such as 200% to double size${ParC}." 1>&2
							Result=84 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
						else
							printf '%s\n' "E: Third parameter must be an integer number ${ParO}new RAW disk image size in MiB${ParC} in this case." 1>&2
							Result=84 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
						fi
					fi
				fi
			else
				printf '%s\n' "E: RAW disk image file not found." 1>&2
				Result=95 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		else
			if [ -e "$ImagefilePath" ] || [ -L "$ImagefilePath" ] ; then
				printf '%s\n' "E: Third parameter must be new RAW disk image size ${ParO}in MiB${ParC} or a final percent ${ParO}such as 200% to double size${ParC}." 1>&2
				Result=81 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			else
				printf '%s\n' "E: Third parameter must be new RAW disk image size ${ParO}in MiB${ParC}. You can specify \"2T\" instead for a maximized size of near 2TiB compatible with MBR." 1>&2
				Result=81 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
	else
		if [ -f "$Mode" ] ; then
			ReportFileSizes "$Mode"
		else
			printf '%s\n' "E: RAW disk image file not specified as second parameter." 1>&2
			Result=97 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		fi
	fi
}
#[/diskimg-resize]

##### TEMPLATE FUNCTIONS TO CUSTOMIZE #####

Install_precp_Pre ()
# Example: Install_precp_Pre /cdrom/program
# Will be run after uninstalling previous version (all before installing new program files).
# Useful for old programs migration/uninstall before doing anything about this one.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local Result=0
	local ResultN=0

	return $ResultN
}

Install_precp_Post ()
# Example: Install_precp_Post /cdrom/program
# Will be run after uninstalling previous version and before copying new program files.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local Result=0
	local ResultN=0

	return $ResultN
}

Install_postcp_More ()
# Example: Install_postcp_More /cdrom/service
# Will be run once program files are already installed.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local Result=0
	local ResultN=0

	return $ResultN
}

Uninstall_predel_More ()
# Will be run after stopping service and before program files removal.
{
	sleep 0
}

Uninstall_Postdeletion ()
# Will be run after program files removal.
{
	sleep 0
}

Uninstall_purge_MorePre ()
# Will be run after Uninstall_Postdeletion and just before removing config files/dirs and logs.
{
	local Result=0
	local ResultN=0

	return $ResultN
}

Uninstall_purge_MorePost ()
# Will be run just after removing config files/dirs and logs.
{
	local Result=0
	local ResultN=0

	return $ResultN
}

Configuration_Saved_Pre ()
# Preliminar configurations at beginning of Configuration(). Useful to migrate old configurations and data before assuming them for production.
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
}

Configuration_Saved_Post ()
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
}

ProgramHelp ()
{
	local SudoPrefix=''
	local SudoSuffix=''
	local InstallerActions=''
	local BaseName=''
	
	BaseName="$(printf '%s\n' "$MeExecutable" | tr -s '/' '\n' | tail -n 1)"
	printf '%s\n' "$(ScriptHeaderValue Description) ${ParO}${ProgramName}${ParC} $(ScriptHeaderValue Version)"
	ScriptHeaderValue Homepage
	printf '%s\n' ""
	if [ $(MeInstalled) -eq 1 ] || [ "$InstalledExpected" = "0" ] ; then
		if [ "$RootRequired" != "0" ] && [ $(id -u) -ne 0 ] ; then
			if [ "$(Which sudo)" != "" ] ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			if [ $(MeInstalled) -eq 1 ] ; then
				InstallerActions="|uninstall|purge"
			else
				InstallerActions="|install"
			fi
		fi
#[diskimg-resize]
#		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {${InstallerActions}}${SudoSuffix}"
		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {sparse|raw|FileSpec${InstallerActions}}${SudoSuffix}"
#[/diskimg-resize]
		printf '%s\n' ""
	else
		if [ $(id -u) -ne 0 ] ; then
			if [ "$(Which sudo)" != "" ] ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			printf '%s\n' "To install as a system program:"
			printf '%s\n' "${SudoPrefix}${BaseName} install${SudoSuffix}"
			printf '%s\n' ""
			printf '%s\n' "To remove the program:"
			printf '%s\n' "${SudoPrefix}${ProgramName} uninstall${SudoSuffix}"
		else
			printf '%s\n' "W: Program not installed. Use your package manager to install ${ProgramName}."
		fi
	fi
}


##### MAIN SCRIPT #####

Result=0 ; ResultN=0
ParO='(' ; ParC=')' ; Tab="$(printf '\t')"
PreviousDir="$(pwd)"
MeCallFile="$0"
PreviousDir="$(pwd)"
MeDir="$(Dirname "$MeCallFile")"
cd "$MeDir"
MeDir="$(pwd)"
cd "$PreviousDir"
MeCallFile="${MeDir}/$(printf '%s\n' "$MeCallFile" | tr -s '/' '\n' | tail -n 1)"
if [ "$LogLevel" = "" ] ; then LogLevel=3 ; fi	# A nested call can export LogLevel
LogProgram 4 '$' "$0" "$@"
#cd / # avoid blocking mount points from being unmounted - It's a problem to deal with user specified relative paths

Action="$1"
if [ $# -gt 0 ] ; then shift ; fi
#[diskimg-resize]
#Action="$(Lowercase "$Action")"
#[/diskimg-resize]
case "$Action" in
	"remove" ) Action="uninstall" ; ActionMode="remove" ;;
	"purge" ) Action="uninstall" ; ActionMode="purge" ;;
	"-h" ) Action="--help" ;;
	"-V" ) Action="--version" ;;
esac
case "$Action" in
	"install" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		if [ $ResultN -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				Result=93 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				ProgramHelp 1>&2
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			else
				LackDependencies="$(DependenciasFaltan "$DependsOnSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "E: Following software must be installed before this dependent program:" 1>&2
					printf '%s\n' "   $LackDependencies" 1>&2
					Result=53 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				fi
				LackDependencies="$(DependenciasFaltan "$RecommendedSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "W: Following software is also recommended for this program:" 1>&2
					printf '%s\n' "   $LackDependencies" 1>&2
				fi
			fi
		fi
		if [ $ResultN -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "E: Install actions need to be run with superuser ${ParO}root${ParC} permissions." 1>&2
			Result=45 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "precp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script.
				Install_precp "$@"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "cp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				printf '%s\n' "Installing $ProgramName"
				Install_cp "$MeExecutable" "$@"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postcp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Install_postcp "$@"
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				if [ $ResultN -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
					printf '%s\n' ""
					printf '%s\n' "For more options, run: ${ProgramName} --help"
				fi
			fi
		fi
		;;
	"uninstall" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		if [ "$ActionMode" = "remove" ] ; then
			if [ "$ProgramInstaller" = "1" ] || [ "$PackageManager_Call" != "" ] ; then
				printf '%s\n' "W: To uninstall $ProgramName you must use the action \"uninstall\"" 1>&2
				Result=80 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
			if [ $ResultN -eq 0 ] ; then
				Result=90 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				ProgramHelp 1>&2
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				Result=93 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				ProgramHelp 1>&2
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "E: Uninstall actions need to be run with superuser ${ParO}root${ParC} permissions." 1>&2
			Result=45 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "predel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_predel
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
				if [ $ResultN -eq 0 ] && [ "$PackageManager_Call" = "predel" ] ; then
					cp "$MeExecutable" "/var/tmp/${ProgramName}.uninstall.tmp"
					cp "$MeExecutable" "${DirTempX}/${ProgramName}.uninstall.tmp"	# Compatibility with old package script: postrm
					chmod u=rx,g=r,o= "/var/tmp/${ProgramName}.uninstall.tmp"
					chmod u=rx,g=r,o= "${DirTempX}/${ProgramName}.uninstall.tmp"
				fi
			fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "del" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_del
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postdel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_postdel
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		if [ $ResultN -eq 0 ] && [ "$ActionMode" = "purge" ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "purge" ] ; then
				Uninstall_purge
				Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
			fi
		fi
		;;
	"debug" )
		Configuration "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		export LogLevel=4
		"$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		;;
	"--version" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		printf '%s\n' "${ProgramName} $(ScriptHeaderValue Version)"
		ScriptHeaderValue Copyright
		ScriptHeaderValue Homepage
		;;
	"--help" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		ProgramHelp
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		;;
#[diskimg-resize]
	"raw" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		ExtendFile raw "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		;;
	"sparse" )
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		ExtendFile sparse "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		;;
#[/diskimg-resize]
	"" )
		printf '%s\n' "E: No action specified." 1>&2
		Result=79 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		ProgramHelp 1>&2
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		;;
	* )
#[diskimg-resize]
		if [ -f "$Action" ] ; then
			ReportFileSizes "$Action" "$@"
		else
#[/diskimg-resize]
		printf '%s\n' "E: Syntax error; Unknown action $Action" 1>&2
		Result=90 ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		Configuration ro "$@"
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
		ProgramHelp 1>&2
		Result=$? ; if [ $ResultN -eq 0 ] ; then ResultN=$Result ; fi
#[diskimg-resize]
		fi
#[/diskimg-resize]
		;;
esac

cd "$PreviousDir" 2>/dev/null
exit $ResultN
