Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/sh
- ############################################################ IDENT(1)
- #
- # $Title: Script to put file(s) on a remote cloud controller $
- #
- ############################################################ GLOBALS
- #
- # Program name
- #
- pgm="${0##*/}"
- #
- # Global exit status
- #
- SUCCESS=0
- FAILURE=1
- #
- # Command-line options
- #
- DEBUG=
- DRYRUN=
- PRESERVE=
- QUIET=
- RECURSE=
- TELNET=
- #
- # Miscellaneous
- #
- CONTROLLER=
- OUTPUT=
- PROMPT1="$( id -nu )@$( hostname ) expect> "
- PROMPT2="expect> "
- ############################################################ FUNCTIONS
- usage()
- {
- local optfmt="\t%-4s %s\n"
- exec >&2
- printf "Usage: %s [-dnpTv] file1 ... host:[file2]\n" "$pgm"
- printf " %s [-dnpTv] -r dir1 ... host:[dir2]\n" "$pgm"
- printf "\n"
- printf "OPTIONS:\n"
- printf "$optfmt" "-d" \
- "Debug. Can be specified multiple times."
- printf "$optfmt" "-n" \
- "Dry run. Show what would be written but don't do anything."
- printf "$optfmt" "-p" \
- "Preserve permissions of source file(s) when writing."
- printf "$optfmt" "-q" \
- "Quiet. Suppress lines printed for each file written."
- printf "$optfmt" "-r" \
- "Recursively copy the contents of directories."
- printf "$optfmt" "-T" \
- "Telnet to localhost port \`host' instead of \`connect host'."
- exit $FAILURE
- }
- replace()
- {
- local __string= __found=
- local OPTIND=1 OPTARG __flag __all=
- while getopts a __flag; do
- case "$__flag" in
- a) __all=1 ;;
- esac
- done
- shift $(( $OPTIND - 1 ))
- local __find="$1" __start="$2" __sub="$3" __var_to_set="$4"
- while [ "$__start" ]; do
- case "$__start" in *"$__find"*)
- __found=1
- __string="$__string${__start%%"$__find"*}$__sub"
- __start="${__start#*"$__find"}"
- [ "$__all" ] || break
- continue
- esac
- break
- done
- __string="$__string$__start"
- if [ "$__var_to_set" ]; then
- eval $__var_to_set=\"\$__string\"
- else
- echo "$__string"
- fi
- [ "$__found" ] # return status
- }
- shell_escape()
- {
- eval replace -a \"\'\" \"\${$1}\" \"\'\\\\\'\'\" \"$1\"
- }
- ############################################################ MAIN
- #
- # Process command-line options
- #
- while getopts dnpqrT flag; do
- case "$flag" in
- d) DEBUG=$(( $DEBUG + 1 )) ;;
- n) DRYRUN=1 ;;
- p) PRESERVE=1 ;;
- q) QUIET=1 ;;
- r) RECURSE=1 ;;
- T) TELNET=1 ;;
- \?|*) usage
- esac
- done
- shift $(( $OPTIND - 1 ))
- #
- # Validate number of arguments
- #
- [ $# -lt 2 ] && usage
- eval OUTPUT=\"\${$#}\"
- [ "$OUTPUT" ] || usage
- #
- # Validate arguments
- #
- case "$OUTPUT" in
- :*) echo "$pgm: Missing host: $OUTPUT" >&2
- usage ;;
- *:*) CONTROLLER="${OUTPUT%%:*}" OUTPUT="${OUTPUT#*:}" ;;
- *) echo "$pgm: Missing host: $OUTPUT" >&2
- usage
- esac
- [ "$CONTROLLER" ] || usage
- #
- # Local shell command (executed via Tcl/expect) to send data to awk decoder
- #
- # NB: Arguments provided are different from parent script. The last argument
- # has been moved to the first argument (making it easier to remove prior to
- # operating on remaining source arguments given the destination first).
- #
- exec 9<<EOF_SH_ENCODER
- ################################################## PARENT GLOBALS
- pgm='${pgm%%\'*}'
- SUCCESS=$SUCCESS
- FAILURE=$FAILURE
- DRYRUN=$DRYRUN
- RECURSE=$RECURSE
- EOF_SH_ENCODER
- exec 8<<'EOF_SH_ENCODER'
- ################################################## GLOBALS
- DEST="$1"
- local="$2"
- prefix="${DEST#*:}"
- : ${UNAME_s:=$( uname -s )}
- ################################################## EXPORTED FUNCTIONS
- putsrc_function='
- putsrc()
- {
- if [ "$DRYRUN" ]; then
- echo "b64source" "$@"
- else
- echo "begin-source" "$@"
- fi
- }
- ' # END-QUOTE
- eval "$putsrc_function"
- perms_function='
- perms()
- {
- local path="$1" fallback="$2"
- case "$UNAME_s" in
- FreeBSD) stat -f%Lp "$path" ;;
- *) stat -c%a "$path"
- esac
- local retval=$?
- [ $retval -eq $SUCCESS ] || echo "$fallback"
- return $retval
- }
- ' # END-QUOTE
- eval "$perms_function"
- encode_function='
- encode()
- {
- local item="$1" src="$2" mode
- #
- # Encode a directory
- #
- if [ -d "$item" ]; then
- putsrc D "$ARGN,$src"
- if [ "$DRYRUN" ]; then
- echo "b64dirent - $item"
- else
- mode=$( perms "$item" 644 )
- echo "begin-dirent $mode $item"
- fi
- return $SUCCESS
- fi
- #
- # Encode a file
- #
- if [ "$DRYRUN" ]; then
- echo "b64decode - $item"
- return $SUCCESS
- fi
- local retval
- if type b64encode > /dev/null 2>&1; then
- b64encode "$item" "$item"
- retval=$?
- else
- mode=$( perms "$item" 644 )
- echo "begin-base64 $mode $item"
- base64 "$item"
- retval=$?
- echo "===="
- fi
- return $retval
- }
- ' # END-QUOTE
- eval "$encode_function"
- ################################################## FUNCTIONS
- encode_file()
- {
- local file="$1" prefix="$2" src=
- putsrc F "$ARGN,$file"
- encode "$file" "$src"
- }
- encode_dir()
- {
- local localdir="$1" prefix="$2"
- if [ ! "$RECURSE" ]; then
- echo "$pgm: $localdir: Not a regular file" >&2
- return $FAILURE
- fi
- cd "$localdir" || return $?
- local retval=$SUCCESS
- find . \( -type f -or -type d \) -print0 | xargs -0r sh -c '
- : ${UNAME_s:=$( uname -s )}
- DRYRUN='"$DRYRUN"' ARGN='"$ARGN"'
- '"$perms_function"'
- '"$putsrc_function"'
- '"$encode_function"'
- localdir="$1" prefix="$2"
- shift 2 # localdir prefix
- while [ $# -gt 0 ]; do
- direntry="$1" src="$localdir"
- shift 1 # direntry
- [ -d "$direntry" ] || putsrc D "$ARGN,$src"
- encode "$direntry" "$src"
- done
- ' /bin/sh "$localdir" "$prefix" || retval=$?
- cd - > /dev/null 2>&1
- return $retval
- }
- ################################################## MAIN
- exec 3<&1
- shift 2 # DEST local
- ( ARGN=1
- for p in "$@"; do
- if [ -d "$p" ]; then
- encode_dir "$p" "$prefix"
- else
- encode_file "$p" "$prefix"
- fi
- ARGN=$(( $ARGN + 1 ))
- done
- ) 2>&1 >&3 | awk 'sub(/^/, "SEND_ERROR: ")'
- echo # expect(1) chomps the last newline
- EOF_SH_ENCODER
- SH_ENCODER_CMD=$( cat <&9; cat <&8 )
- #
- # awk(1) code for processing the input of local command (SH_ENCODER_CMD)
- #
- # NB: Tabs replaced with 1 or 4 spaces (prevents shell interpretation of tab)
- # NB: Newlines are removed (so make sure semi-colons are used appropriately)
- #
- exec 9<<EOF_AWK_DECODER
- ################################################## GLOBALS
- #
- # Globals available from invocation line:
- #
- # local True if operating locally, false if remotely
- # prefix Path prefix for file destination(s)
- # quiet Maps to \$QUIET from parent script GLOBALS
- #
- # Globals available from BEGIN { ... }:
- #
- # preserve True if permissions should be preserved
- # src_prefix Array for source->prefix mapping
- #
- ################################################## BEGIN
- BEGIN {
- preserve = ${PRESERVE:-0};
- delete src_prefix;
- }
- EOF_AWK_DECODER
- exec 8<<'EOF_AWK_DECODER'
- ################################################## FUNCTIONS
- function escape(str) {
- gsub(/'/, "&\\\\&&", str);
- return str;
- }
- function exists(path) {
- return system(sprintf("[ -e '%s' ]", escape(path))) == 0;
- }
- function isdir(path) {
- return system(sprintf("[ -d '%s' ]", escape(path))) == 0;
- }
- function chmod(mode, path) {
- return system(sprintf("chmod %s '%s'", mode, escape(path)));
- }
- function mkdir(path, mode, chdir)
- {
- cmd = "mkdir -p";
- if (chdir = escape(chdir))
- cmd = sprintf("cd '%s' 2>&- && %s", chdir, cmd);
- if (mode = escape(mode))
- cmd = sprintf("%s -m '%s'", cmd, mode);
- cmd = sprintf("%s '%s'", cmd, escape(path));
- return system(cmd);
- }
- function reset_output() {
- OUTPUT_CMD = OUTPUT_FILE = OUTPUT_MODE = "";
- dump = 0;
- }
- function target_perms(src_perms, isfile)
- {
- target_mode = "";
- # Validate quantity/quality of octal permissions
- if (match(src_perms, /[0-7][0-7][0-7]+$/))
- src_perms = substr(src_perms, RSTART);
- if (src_perms && preserve)
- target_mode = src_perms;
- else if (src_perms && isfile) {
- # Carry over executable bits
- n = split(src_perms, p, "");
- if (p[n--] ~ /^[1357]$/) target_mode = target_mode "o";
- if (p[n--] ~ /^[1357]$/) target_mode = target_mode "g";
- if (p[n--] ~ /^[1357]$/) target_mode = target_mode "u";
- if (target_mode) target_mode = target_mode "+x";
- }
- return target_mode;
- }
- function target_path(path)
- {
- target_prefix = INPUT_SRC ? src_prefix[INPUT_SRC] : "";
- if (!target_prefix) {
- target_prefix = prefix;
- sub("/+$", "", target_prefix);
- append = "";
- if (isdir(target_prefix) || prefix ~ "/$") {
- if (INPUT_TYPE == "D") {
- src_name = INPUT_SRC;
- sub(/^[[:digit:]]+,/, "", src_name);
- gsub("^[^/]*/", "", src_name);
- append = "/" src_name;
- }
- } else if (INPUT_TYPE == "F") {
- if (!exists(target_prefix)) {
- path = target_prefix;
- target_prefix = "";
- }
- }
- target_prefix = target_prefix append;
- src_prefix[INPUT_SRC] = target_prefix;
- }
- sub("^\\./", "", path);
- return target_prefix (target_prefix ? "/" : "") path;
- }
- function prepare_directory(path, perms)
- {
- tpath = target_path(path);
- sub("/\\.$", "", tpath);
- sprefix = src_prefix[INPUT_SRC];
- if (path == "." && !isdir(tpath)) {
- gsub("^[^/]*/+", "", tpath);
- sub("/+[^/]*$", "", sprefix);
- chdir = "";
- if (prefix ~ "/$" || isdir(sprefix))
- chdir = sprefix;
- result = mkdir(tpath, perms, chdir);
- } else if (isdir(tpath))
- return (preserve ? chmod(perms, path) : 0);
- else {
- result = mkdir(path, perms, chdir = sprefix);
- }
- return result;
- }
- function prepare_output(path, perms)
- {
- #
- # This function reads the following globals:
- #
- # INPUT_SRC Part of base64 preamble for dest calculations
- #
- # and creates the following globals for referencing the output:
- #
- # OUTPUT_FILE Path to output file to be written
- # OUTPUT_MODE chmod(1) mode to apply after writing file
- # OUTPUT_CMD Command to write file (from base64 encoding)
- #
- reset_output();
- OUTPUT_FILE = target_path(path);
- OUTPUT_MODE = target_perms(perms, isfile = 1);
- OUTPUT_CMD = sprintf("base64 -d - > '%s'",
- escape(OUTPUT_FILE));
- }
- function close_output()
- {
- success = (close(OUTPUT_CMD) == 0);
- close(OUTPUT_FILE);
- if (OUTPUT_FILE && success && !quiet) print OUTPUT_FILE;
- if (OUTPUT_MODE && preserve) chmod(OUTPUT_MODE, OUTPUT_FILE);
- reset_output();
- INPUT_TYPE = INPUT_SRC = "";
- }
- ################################################## MAIN
- # IPC/flow-control protocol
- match($0, /^SEND_USER: /) {
- print substr($0, local ? RSTART+RLENGTH : 0);
- }
- /^EOI: / { exit }
- # End of base64 encoded input
- /^====/ { close_output() }
- # base64 encoded input
- dump {
- sub(/\r$/, "");
- print | OUTPUT_CMD;
- }
- # Preamble for base64 encoded input
- match($0, /^(begin-|b64)source [A-Z] /) {
- INPUT_TYPE = substr($0, RSTART + RLENGTH - 2, 1);
- INPUT_SRC = substr($0, RSTART + RLENGTH);
- }
- # Preamble for directory creation (with or without perms)
- match($0, /^begin-dirent [0-7]+ /) {
- # Capture permissions info ([0-7]+) and directory path
- dir = substr($0, RSTART + RLENGTH);
- perms = substr($0, 14);
- sub(/\r$/, "", dir);
- sub(/ .*/, "", perms);
- prepare_directory(dir, perms);
- }
- # Start of base64 encoded input
- match($0, /^begin-base64 [0-7]+ /) {
- # Capture permissions info ([0-7]+) and destination file path
- file = substr($0, RSTART + RLENGTH);
- perms = substr($0, 14);
- sub(/\r$/, "", file);
- sub(/ .*/, "", perms);
- prepare_output(file, perms);
- dump = 1;
- }
- # DRYRUN processing (always on)
- match($0, /^b64(en|de)code - /) {
- file = substr($0, RSTART + RLENGTH);
- sub(/\r$/, "", file);
- prepare_output(file, perms = "");
- print OUTPUT_FILE;
- OUTPUT_FILE = "";
- }
- END { close_output() }
- EOF_AWK_DECODER
- AWK_DECODER_CMD=$(
- program_text='
- !/^[[:space:]]*(#|$)/ {
- gsub(/\t/, d > 1 ? " " : " ")
- printf "%s" (d > 1 ? "\n" : ""), $0
- } END { if (d < 2) printf "\n" }
- ' # END-QUOTE
- awk -v d="$DEBUG" "$program_text" <&9
- awk -v d="$DEBUG" "$program_text" <&8
- )
- #
- # Tcl/expect code for processing shell arguments ($@)
- #
- if [ "$TELNET" ]; then
- spawn_cmd="telnet localhost"
- else
- spawn_cmd="connect"
- fi
- exec 9<<EOF_EXPECT
- ################################################## PARENT GLOBALS
- set dryrun ${DRYRUN:-0}
- set quiet ${QUIET:-0}
- set recurse ${RECURSE:-0}
- set spawn_cmd "$spawn_cmd"
- set prompt1 "${PROMPT1%%\"*}"
- set prompt2 "${PROMPT2%%\"*}"
- EOF_EXPECT
- exec 8<<'EOF_EXPECT'
- ################################################## GLOBALS
- set timeout 300
- set sh_encoder_cmd [lindex $argv 0]
- set awk_decoder_cmd [lindex $argv 1]
- set argv [lreplace $argv 0 1]
- set argc [llength $argv]
- set argmax [expr {$argc - 1}]
- set lastarg [lindex $argv $argmax]
- set argv [lreplace $argv $argmax $argmax]
- set argc [llength $argv]
- set cpos [string first ":" $lastarg]
- set cc ""
- set prefix ""
- if {$cpos >= 0} {
- set cc [string range $lastarg 0 [expr {$cpos-1}]]
- set prefix [string range $lastarg [expr {$cpos+1}] end]
- }
- if {$prefix == ""} {
- set prefix "."
- }
- set awk_flags {}
- lappend awk_flags " -v local=0"
- lappend awk_flags " -v prefix='$prefix'"
- lappend awk_flags " -v quiet='$quiet'"
- ################################################## MAIN
- # Validate arguments
- if {$argc < 1} exit
- if {$cc == ""} exit
- # Prepare decoder for remote injection
- regsub -all {'} $awk_decoder_cmd {'\\''} awk_decoder_cmd
- regsub {^\s*} $awk_decoder_cmd {} awk_decoder_cmd
- regsub -all {\n} $awk_decoder_cmd {} awk_decoder_cmd
- fconfigure stdout -buffering none
- spawn {*}$spawn_cmd $cc
- # Wait for available/dangling command prompt
- expect -re {#\s+$} {
- # Change to custom command/continuation prompts
- send "PS1='$prompt1' PS2='$prompt2'\n"
- send "\n"
- }
- # Wait for customized command-prompt to inject awk(1) decoder
- expect -re [subst -nocommands -nobackslashes {$prompt1$}] {
- set awk_cmd "awk"
- append awk_cmd [join $awk_flags " "]
- append awk_cmd " '$awk_decoder_cmd'"
- #
- # Run awk decoder in sub-shell that prefixes all
- # + stdout messages with "SEND_USER: "
- # + stderr messages with "SEND_ERROR: "
- #
- # NB: Allows remotely-running awk(1) code to get I/O
- # past the AWK_TTY_CMD to user on appropriate fd
- #
- send "( exec 3<&1; $awk_cmd 2>&1 >&3 |"
- send " awk 'sub(/^/, \"SEND_ERROR: \")' >&2"
- send ") | awk 'sub(/^/, \"SEND_USER: \")'"
- # Send escaped newline to invoke continuation prompt
- send " \\\n"
- }
- # Wait for continuation prompt, start decoder, begin encoding
- expect -re [subst -nocommands -nobackslashes {$prompt2$}] {
- send "; exit\n" ; # exit shell after decoder completes
- send [exec sh -c $sh_encoder_cmd /bin/sh $prefix 1 {*}$argv]
- send "EOI: exiting.\n"
- }
- expect eof
- EOF_EXPECT
- EXPECT_CMD=$( cat <&9; cat <&8 )
- #
- # Local awk for providing user feedback on attached terminal
- #
- AWK_TTY_CMD='
- match($0, /^SEND_USER: /) { print substr($0, RSTART + RLENGTH) }
- match($0, /^SEND_ERROR: /) {
- print substr($0, RSTART + RLENGTH) > "/dev/stderr";
- }
- ' # END-QUOTE
- #
- # Interact with each path argument via expect(1)
- #
- debugfmt3="echo '\n%s\n\n' | expect -f- '\n%s\n' '\n%s\n' %s 2>&1"
- debugfmt2="$debugfmt3 | awk '\n%s\n'"
- if [ ${DEBUG:-0} -ge 3 ]; then
- # Replace single-quotes (runnable via copy/paste or pipe to sh)
- for var in EXPECT_CMD SH_ENCODER_CMD AWK_DECODER_CMD AWK_TTY_CMD; do
- shell_escape $var
- done
- printf "$debugfmt3\n" "$EXPECT_CMD" \
- "$SH_ENCODER_CMD" "$AWK_DECODER_CMD" "$*"
- elif [ ${DEBUG:-0} -ge 2 ]; then
- # Replace single-quotes (runnable via copy/paste or pipe to sh)
- for var in EXPECT_CMD SH_ENCODER_CMD AWK_DECODER_CMD AWK_TTY_CMD; do
- shell_escape $var
- done
- printf "$debugfmt2\n" "$EXPECT_CMD" \
- "$SH_ENCODER_CMD" "$AWK_DECODER_CMD" "$*" "$AWK_TTY_CMD"
- elif [ ${DEBUG:-0} -ge 1 ]; then
- echo "$EXPECT_CMD" |
- expect -f- "$SH_ENCODER_CMD" "$AWK_DECODER_CMD" "$@" 2>&1
- else
- echo "$EXPECT_CMD" |
- expect -f- "$SH_ENCODER_CMD" "$AWK_DECODER_CMD" "$@" 2>&1 |
- awk "$AWK_TTY_CMD"
- fi
- exit $SUCCESS
- ################################################################################
- # END
- ################################################################################
- #
- # $Copyright: 2015 Devin Teske. All rights reserved. $
- #
- # $Header$
- #
- ################################################################################
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement