Add a framework for source host backends.
[matthijs/upstream/backupninja.git] / lib / backend.in
diff --git a/lib/backend.in b/lib/backend.in
new file mode 100644 (file)
index 0000000..0c130f2
--- /dev/null
@@ -0,0 +1,162 @@
+# -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*-
+# vim: set filetype=sh sw=3 sts=3 expandtab autoindent:
+
+defined_backends=()
+available_backends=()
+
+function init_backends () {
+   for backend in "${defined_backends[@]}"; do
+      . "$libdirectory/backends/$backend"
+      if "backend_${backend}_init"; then
+         append available_backends "$backend"
+         debug "Backend '$backend' is available on this system"
+      else
+         debug "Backend '$backend' is not available on this system"
+      fi
+   done
+}
+
+function init_source_hosts () {
+   # Use our first argument as the name of a configuration parameter to
+   # load the hosts from, or use the "hosts" parameter if nothing was
+   # passed. If the parameter is empty, use "localhost".
+   getconf_words "hosts" "local:"
+   # Allow the default backend variable to be overridden in this
+   # handler's configuration, but fall back to the global default
+   # backend. We use printconf instead of getconf to prevent the global
+   # $default_backend from getting clobbered.
+   local_default_backend=`printconf "default_backend" $default_backend`
+
+   # Check to see if the default backend is valid (don't check
+   # availability yet, though)
+   if [ -n "$local_default_backend" ] && ! backend_defined "$local_default_backend"; then
+      fatal "Default backend '$local_default_backend' does not exist"
+   fi
+
+   local hostspec
+   for hostspec in "${hosts[@]}"; do
+
+      # Find the backend
+      local backend=`backend_for_hostspec "$hostspec"`
+      if [ -z "$backend" ]; then
+         if [ -z "$local_default_backend" ]; then
+            fatal "No default backend set and no explicit backend in hostspec '$hostspec'"
+         else
+            backend=$local_default_backend
+         fi
+      fi
+
+      # Check if the backend is valid and available
+      if ! backend_defined "$backend"; then
+         fatal "Backend '$backend' does not exist"
+      elif ! backend_available "$backend"; then
+         fatal "Backend '$backend' is not available on this system"
+      fi
+
+      # Check if the host is available
+      local reason
+      local host=`host_for_hostspec "$hostspec"`
+      if ! "backend_${backend}_host_available" "$host" reason; then
+         fatal "Host '$hostspec' is not available: $reason"
+      fi
+
+      # Everything is ok, we can run on this host!
+   done;
+}
+
+# Run the function pointed to by $1 for each of the source hosts defined
+# in the configuration. Sets up the current backend and source host
+# variables, so source_run & friends can be used. Also sets up the $root
+# variable to point to the current source host's root filesystem (with
+# no trailing /).
+# This function should only be used after calling init_source_hosts.
+function run_for_source_hosts () {
+   # $hosts will have been filled by init_source_hosts
+   for hostspec in "${hosts[@]}"; do
+      current_backend=`backend_for_hostspec "$hostspec"`
+      current_host=`host_for_hostspec "$hostspec"`
+      # Set the current source's root.
+      root=`"backend_${current_backend}_host_root" "$current_host"`
+
+      info "Running on $current_backend:$current_host"
+
+      # Run the actual backup handler
+      "$1"
+   done
+}
+
+# Run the given command on the current source host.
+# The command should be $1, with $2, $3, etc. being its arguments.
+source_run () {
+   "backend_${current_backend}_run" "$@"
+}
+
+function backend_available () {
+   in_array "$1" "${available_backends[@]}"
+}
+
+function backend_defined () {
+   in_array "$1" "${defined_backends[@]}"
+}
+
+function backend_for_hostspec () {
+   echo "$1" | sed 's/^\(\([^:]*\):\)\?\(.*\)$/\2/'
+}
+
+function host_for_hostspec () {
+   echo "$1" | sed 's/^\(\([^:]*\):\)\?\(.*\)$/\3/'
+}
+
+
+# Replaces escape sequences in $1 with the proper values for the current
+# backend.
+# The result is put on stdout.  If $2 is empty, values for the host are
+# replaced. The following values are replaced:
+#
+# %%  Literal %
+# %h  The short hostname (as returned by hostname -s)
+# %H  The full hostname (as returned by hostname --fqdn)
+# %n  The vserver name, or empty for the host
+# %N  The vserver name, or "host" for the host
+# %v  The vserver root directory, or empty for the host
+#
+# Note that the given vserver must be running!
+#
+function interpolate() {
+   path=$1
+
+   expr=''
+   # Allow backends to supply extra vars
+   vars=`"backend_${current_backend}_interpolate_vars"`
+   vars="h H s S r $vars"
+   for var in $vars; do
+      # Do indirect lookup of the value to replace
+      val=`interpolate_value "$var"`
+      # Escape slashes, backslashes and ampersands, since those have
+      # special meaning for sed (note that we need to double the \
+      # twice, since it has special meaning to bash in the val=
+      # assignment, but also to sed.
+      val=`echo "$val" | sed 's#[/&\\\\]#\\\\&#g'`
+      # Add replacement pattern. The first part checks that there is
+      # an odd number of percent signs before the variable, so
+      # double % is properly left alone.
+      expr="${expr}s/\(\(^\|[^%]\)\(%%\)*\)%$var/\1$val/g;"
+   done
+   # Finally replace literal % signs
+   expr="${expr}s/%%/%/g"
+
+   # Do the actual interpolation
+   echo $path | sed "$expr"
+}
+
+function interpolate_value () {
+   case "$1" in
+      h) `source_run hostname -s`;;
+      H) `source_run hostname --fqdn`;;
+      s) echo "$current_host";;
+      S) echo "$current_backend:$current_host";;
+      r) echo "$root";;
+      # Not one of the defaults, ask the backend
+      *) "backend_${current_backend}_interpolate_value" "$current_host" "$1"
+   esac
+}