#!/bin/bash
#Common Interface for HDFS/FS of queueing abstraction

#Global variables
#cache of the current queue
#declare -a _lander_cached_queue=()
#current queue length
#_lander_qsize=0
lander_cached_queue_var=""
lander_queue_len_var=""
#disambiguator: different hosts/processes
_lander_queue_salt="$(hostname -s).$$"


#in case lander.conf doesn't have it (for old installs)
LANDER_RAW_FILE_REGEX_DEFAULT='[0-9]{8}-[0-9]{6}-[0-9]+.*'

# Set _lander_qsize to the number of files waiting in the queue
# Caches the current queue state.
#lander_queue_size(queue_uri)
#+where queue_uri can be for example:
#+	/nfs/lander/raw/
#+	/nfs/lander/raw/landeri2
#+  hdfs://namenode.usc.edu/rawinput
lander_queue_size() {
    if [ "${#_lander_cached_queue[*]}" == 0 ]; then
        local queue_id=$1 #could be ""
        local raw_regex=${LANDER_RAW_FILE_REGEX:-$LANDER_RAW_FILE_REGEX_DEFAULT}
        local files=$(lander_queue_files_$LANDER_QUEUE_TYPE $queue_id 2>/dev/null | 
            egrep "\/$raw_regex")
        [ -z "$files" ] && { _lander_cached_queue=(); _lander_qsize=0; return 1; }
        # sort files by name (but ignore the directories)
        local fnkey=$(echo "/x$files" 2>/dev/null | head -1 | tr '/' ' ' | wc -w) #should be num of '/' + 1
        _lander_cached_queue=( $(echo "$files" | sort -t '/' -k $fnkey) )
        _lander_qsize=${#_lander_cached_queue[*]}
    fi
    _lander_qsize="${#_lander_cached_queue[*]}"
    [ "$_lander_qsize" == 0 ] && return 1
    return 0
}
#lander_queue_files_fs(queue_uri)
lander_queue_files_fs() {
    local queue_uri=$1
    [ -d "$queue_uri" ] || {
        echo "ERROR: lander_queue_files_fs called without queue_uri"
        return 1
    }
    #xxx pass through regex here?
    find "$queue_uri/data" -type f -print
}
lander_queue_files_hdfs() {
    local queue_id=$1 #could be ""
    $LANDER_HDFS -ls $queue_id | sed -n "s%^.*  */%/%gp "
}
#clear cached state of the queue
#lander_queue_flush_cache(queue_uri="")
#empty queue_uri means ALL queues
lander_queue_flush_cache() {
    _lander_cached_queue=()
    _lander_qsize=0
}

# lander_queue_file_older <qid> <filename> <ageseconds>
# Check if the file's modtime is at least ageseconds in the past
lander_queue_file_older() {
    lander_queue_file_older_$LANDER_QUEUE_TYPE $@
}
lander_queue_file_older_fs() {
    local qid=$1 #ignored
    local filename=$2 ageseconds=$3 modtime=
    modtime=$(stat -c%Y $filename) || return 2
    local now=$(date +%s)
    (( now - modtime > ageseconds ))
}
lander_queue_file_older_hdfs() {
    local qid=$1 #ignored
    local filename=$2 ageseconds=$3 modtime_ms=
    modtime_ms=$($LANDER_HDFS -stat %Y $filename) || return 1
    local now=$(date +%s)
    (( now - modtime_ms/1000 > ageseconds ))
}

# Pop one file from the head of the queue, cache the current queue state.
# Sets 'reserved_local' to the file name on the local FS.
# Returns true if success or false if the queue is empty. 
#
# we want to be extremely careful not to read/write to shared fs if it
# can be helped.  So all our processing components read/write to /tmp
# and then copy results to shared fs, while retaining the result
# locally.
lander_queue_reserve_top() {
    local _queue_uri=$1 _localtmp=$2
    while true; do
	    #use queue_size not queue_size_hdfs, for caching
	    lander_queue_size $_queue_uri || \
            { reserved_local=""; return 1; }
        local firstinq=${_lander_cached_queue[0]} #xxx this should differentiate by _queue_uri 
        [ -z "$firstinq" ] && { reserved_local=""; return 1; }
        _lander_cached_queue=( "${_lander_cached_queue[@]:1}" )
        _lander_qsize="${#_lander_cached_queue[*]}"
        lander_queue_reserve_file_$LANDER_QUEUE_TYPE $_queue_uri $_localtmp $firstinq && return 0
    done
    return 1
}
lander_queue_reserve_file_hdfs() {
    local queue_uri=$1 localtmp=$2 firstinq=$3

    local file=$(basename $firstinq)
    local reservation="$LANDER_QUEUE_RESERVED_HDFS/$file-$_lander_queue_salt"
    #don't need to check next seq num, since files are renamed from _copyin_ prefix
    $LANDER_HDFS -mv $firstinq $reservation 2>/dev/null || return 1
    $LANDER_HDFS -test -e $reservation 2>/dev/null      || return 1
    mkdir -p $localtmp/$lander 2>/dev/null
    reserved_local="$localtmp/$lander/$file"
    $LANDER_HDFS -get $reservation $reserved_local || {
        echo "ERR: error hdfs -get $reservation $reserved_local" >&2
        return 1
    }
    return 0
}
lander_queue_reserve_file_fs() {
    local queue_uri=$1 localtmp=$2 firstinq=$3
    local file=$(basename $firstinq)
    local reservation="$queue_uri/temp/$file-$_lander_queue_salt"
    #check if next seq number has been written
    local r= seqnum=${file%%\.*} #kill all extensions
    seqnum=${seqnum%-[a-z]*}
    seqnum=${seqnum##*-}
    local number="^[0-9][0-9]*$"
    if [[ $seqnum =~ $number ]]; then
        local nseqlen=-${#seqnum}
        local nextseqnum="000000000000$(( 10#$seqnum+1 ))"
        nextseqnum=${nextseqnum:$nseqlen}
        if ! [ -e $queue_uri/data/*-$nextseqnum* 2>/dev/null ]; then
            while true; do
                lander_queue_file_older_fs $queue_uri $firstinq $LANDER_MINFILEAGE
                r=$?
                [ "$r" == "0" ] && break
                [ "$r" == "2" ] && return 1 #file gone
                # $r==1: need to sleep on it
                sleep 60
            done
            if ! [ -e $queue_uri/*-$nextseqnum* 2>/dev/null ]; then
                echo "WARN: working on $firstinq, while $nextseqnum hasn't been delivered" >&2
            fi
        fi
    else
        echo "WARN: couldn't extract seq number from $firstinq" >&2
    fi
    
    mv $firstinq $reservation 2>/dev/null || return 1
	# sometimes mv exit status is zero, but move in fact
	#+fails (probably because of NFS attribute caching), 
	#+so we have to check for existence of the target.
	[ -e "$reservation" ] || return 1
	#mkdir -p $localtmp 2>/dev/null
	reserved_local="$localtmp/$file"
	ln $reservation $reserved_local 2>/dev/null \
	    || cp -p $reservation $reserved_local \
	    || { echo "Error copying $reservation to $reserved_local"; return 1; }
    return 0
}


# Put previously reserved with reserve_top_hdfs() file back into the queue.
# This can be useful e.g. if the file is to be processed by a different agent.
lander_queue_unreserve() {
    lander_queue_unreserve_$LANDER_QUEUE_TYPE $@
}
lander_queue_unreserve_hdfs() {
    local queue=$1 file=$2
    local f=$(basename $file)
    local reservation="$LANDER_QUEUE_RESERVED_HDFS/$f-$_lander_queue_salt"

    $LANDER_HDFS -mv $reservation $queue/$f || {
	    echo "Cannot move $reservation back to $queue/$f" >&2
	    return 1
    }
    rm -f $file
    lander_queue_flush_cache
    return 0
}
lander_queue_unreserve_fs() {
    local queue=$1 file=$2
    local f=$(basename $file)
    local reservation="$queue/temp/$f-$_lander_queue_salt"
    mv $reservation $queue/$f || {
	    echo "Cannot move $reservation back to $queue/$f" >&2
	    return 1
    }
    lander_queue_flush_cache
    rm -f $file
    return 0
}

lander_queue_wipe() {
    local $queue=$1
    lander_queue_wipe_$LANDER_QUEUE_TYPE $queue
}

lander_queue_wipe_hdfs() {
    local queue=$1
    local raw_regex=${LANDER_RAW_FILE_REGEX:-$LANDER_RAW_FILE_REGEX_DEFAULT}
    local files=$( $LANDER_HDFS -ls $queue/ | 
	                     awk '{print $8}' |  egrep "\/$raw_regex" )
    local f
    for f in $files; do
        $LANDER_HDFS -rm "$f"
    done >& /dev/null
}

lander_queue_wipe_fs() {
    local queue=$1
    local raw_regex=${LANDER_RAW_FILE_REGEX:-$LANDER_RAW_FILE_REGEX_DEFAULT}
    find $queue/data -regextype posix-extended \
	     -regex ".*\/$raw_regex" -type f -exec rm -f '{}' \;
}

# The file has been entirely processed, remove it from HDFS, as well as from
# local tmp.
lander_queue_release() {
    local queue_id=$1 file=$2
    lander_queue_release_$LANDER_QUEUE_TYPE $queue_id $file
    /bin/rm -f $file
}
lander_queue_release_hdfs() {
    local queue_id=$1 file=$2
    local f=$(basename $file)
    local reservation="$LANDER_QUEUE_RESERVED_HDFS/$f-$_lander_queue_salt"
    $LANDER_HDFS -rm $reservation >& /dev/null || {
        echo "Could not remove reservation $reservation" >&2
	    return 1
    }
    return 0
}
lander_queue_release_fs() {
    local queue_id=$1 file=$2
    local f=$(basename $file)
    local reservation="$queue_id/temp/$f-$_lander_queue_salt"
    /bin/rm -rf $reservation
    return 0
}


lander_user_allowed() {
    local _uname=$1 _dtype=$2
    local _group=${LANDER_GROUPS[$_dtype]}
    if [ -z "$_group" ]; then
        _group=${LANDER_GROUPS[raw]}
        echo "ERROR: group for $_dtype is not set, defaulting to ($_group)">&2
    fi
    groups "$_uname" 2>/dev/null | grep -q "\b$_group\b" && return 0
    return 1
}

lander_user_inquota_fs() {
    local _uname=$1
    while read _user _quota _usage _rest; do
        [ -n "${_user%%#*}" ] || continue
        if [ "$_user" == "$_uname" ]; then
            [ $_quota -gt $_usage ] && return 0
            return 1
        fi
    done < <(grep -v '^#' $LANDER_QUOTAFILE)
    return 1
}

#sets global lander_derived_queues
lander_queue_list_derived_queues() { lander_queue_list_derived_queues_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_list_derived_queues_fs() {
    local _from_qid=$1 _quota_check=$2 
    local _DF="$_from_qid/.derived_queues" #directory with symlinks
    #from_quid is a directory
    
    lander_derived_queues=
    [ -d "$_DF" ] || return 1 #no derived queues

    local _l 
    
    for _l in $_from_qid/.derived_queues/*; do
        [ -h "$_l" ] || continue
        local _dq=$(readlink $_l)
        [ -d "$_dq" ] || continue
        local _uname=$(stat -c%U  "$_dq")
        if [ -n "$_quota_check" ]; then
                    lander_user_inquota_fs "$_uname" || continue
        fi
        lander_derived_queues+="$_dq "
    done
    [ -z "$lander_derived_queues" ] && return 1
    return 0
}

lander_queue_list_parent_queues() { lander_queue_list_parent_queues_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_list_parent_queues_fs() {
       local _q=$1
       lander_parent_queues=

    local _l
    for _l in $_q/.parent_queues/*; do
        [ -h "$_l" ] || continue
        local _dq=$(readlink $_l)
        [ -d "$_dq" ] || continue
        lander_parent_queues+="$_dq "
    done
    [ -z "$lander_parent_queues" ] && return 1
    return 0
}

lander_queue_is_active() { lander_queue_is_active_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_is_active_fs() {
    local _q=$1
    [ -e "$_q/.deliveryenabled" ] || return 1
    return 0
}

lander_queue_activate() { lander_queue_activate_$LANDER_QUEUE_TYPE $@; }
lander_queue_activate_fs() { 
    local _q=$1
    touch $_q/.deliveryenabled
}

lander_queue_deactivate() { lander_queue_deactivate_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_deactivate_fs() {
    local _q=$1
    rm -f $_q/.deliveryenabled
}

lander_queue_enqueue() { lander_queue_enqueue_$LANDER_QUEUE_TYPE $@; }
lander_queue_enqueue_fs() {
    local _to_qid=$1 _filename=$2
    #first try ln
    local _f=$(basename "$_filename")
    ln "$_filename" "$_to_qid/data/" || {
        cp "$_filename" "$_to_qid/temp/copyin-$_f" &&
            mv "$_to_qid/temp/copyin-$_f" "$_to_qid/data/$_f"
    } || {
        echo "ERROR: failed to enqueue $_f to $_to_qid" >&2
        return 1
    }
    lander_enqueue_file="$_to_qid/$_f"
    return 0
}

#sets up and uses lander_queue_cache_<type>
lander_queue_distribute_local() { lander_queue_distribute_local_$LANDER_QUEUE_TYPE $@; }
lander_queue_distribute_local_fs() {
    local _from_qid=$1 _filename=$2 _to_qid=$3 _qlist=
    if [ ! -z "$_to_qid" ]; then
        _qlist=$_to_qid
    else
        local _cache_var="lander_queue_cache_$_data_type" _cache
        #xxx caching can/should be moved into lander_distribute_local
        eval _cache=\$$_cache_var
        [ "$_cache" == "null" ] && return 0
        if [ -n "$_cache" ]; then
            lander_derived_queues=$_cache #xxx this cache needs to expire (either by time or by usage count)
        else
            lander_list_derived_queues_fs $_from_qid $_data_type true
            eval $_cache_var=\$lander_derived_queues
            [ -z $lander_derived_queues ] && {
                eval $_cache_var=null
                return 0
            }
        fi
    
        for _to_qid in $lander_derived_queues; do
            #check if it's active
            lander_queue_is_active_fs "$_to_qid" && _qlist+="$_to_qid "
        done
        [ -z "$_qlist" ] && return 0
    fi
    #xxx use tmpdir tied up to $_to_qid?
    #xxx e.g. $_to_qid/../tmp, so host/{yuri,johnh,...,tmp}
    #xxx this way each type can be on a separate partition
    lander_file_copylocal "$_filename" $LANDER_TMPDIR_U || return 1
    local _ret=1 #we signal success if just one delivery is OK
    local _tf="$LANDER_TMPDIR_U/$(basename $_filename)"
    for _to_qid in $_qlist; do
        lander_queue_enqueue_fs "$_to_qid" "$_tf" && _ret=0
        #failure can happen if the dest queue goes bad, nothing to be done
    done
    rm -f "$_tf"
    return $_ret
}

lander_raw_cat() { ${LANDER_COMPRESS_RAW_CAT:-cat} "$@"; }

lander_usr_zip() { ${LANDER_COMPRESS_USR_COMPRESS:-cat} "$@"; }

lander_process_host() {
    # HOST ANON: needs to be done even if there are no host-only data users
    #+get key ids: softlinks may be changed while the script
    #+is still going, so we've got to use the real keys
    local _inpfile=$1 _outdir=$2
    local _base=${_inpfile##*/} _localdir=${_inpfile%/*} _key_id=
    
    lander_fetch_key host $_localdir || return 1 #sets lander_local_keyfile to local name of the key

    local _real_key=$lander_local_keyfile
    local _anon_opt="$LANDER_SCRUBBER_OPT_HOST -s $_real_key"
    # separate key number
    _key_id=${_real_key##*/}
    _key_id=${_key_id#*-}
    local _process_out=$_outdir/${_base/-?./-h${_key_id}.} #xxx add proper extension handling
    if lander_raw_cat "$_inpfile" |
        $LANDER_SCRUBBER -l $LANDER_SCRUBBER_LOGFILE $_anon_opt |
        lander_usr_zip >"$_process_out"; then
        # Scrubbing worked
        chgrp ${LANDER_GROUPS[host]} "$_process_out"
        chmod 0660 "$_process_out"
        return 0
    fi
    return 1
}

#sets global lander_local_keyfile
lander_fetch_key() {
    local _keyname=$1 _locald=$2
    local _keydir=
    case $_keyname in
        ( host )
            _keydir=$LANDER_HOSTKEYDIR ;;
        ( full )
            _keydir=$LANDER_FULLKEYDIR ;;
        ( * )
            echo "ERROR: Unexpected keyname for key setup: $_keyname" >&2
            return 1
        ;;
    esac
    local _keyfile=$(lander_file_ls "$_keydir/$_keyname-[0-9]*" | sort -r | head -1 2> /dev/null )

    [ -n "$_keyfile" ] || {
        lander_local_keyfile=
        return 1
    }
    lander_local_keyfile=$_locald/$(basename $_keyfile)
    [ -e "$lander_local_keyfile" ] && return 0

    lander_file_copyremote $_keyfile $lander_local_keyfile.tmp || {
	lander_local_keyfile=
	return 1
    }
    mv -f $lander_local_keyfile.tmp $lander_local_keyfile
}

lander_process_full() {
    # FULL ANON: this is conditional (if there are users) and dependend on the success
    #+of the previous step as it takes $outH as input get key ids: softlinks may 
    #+be changed while the script is still going, so we've got to use the real keys.
    local _inpfile=$1 _outdir=$2
    local _base=${_inpfile##*/} _localdir=${_inpfile%/*} _key_id=
    
    lander_fetch_key full $_localdir || return 1 #sets lander_local_keyfile to local name of the key

    local _real_key=$lander_local_keyfile
    local _anon_opt="$LANDER_SCRUBBER_OPT_FULL -s $_real_key"
    # separate key number
    _key_id=${_real_key##*/}
    _key_id=${_key_id##*-}
    local _process_out=$_outdir/${_base/./-f${_key_id}.} #xxx add proper extension handling

    if lander_raw_cat "$_inpfile" |
        $LANDER_SCRUBBER -l $LANDER_SCRUBBER_LOGFILE $_anon_opt |
        lander_usr_zip >"$_process_out"; then
        # Scrubbing worked
        chgrp ${LANDER_GROUPS[full]} "$_process_out"
        chmod 0660 "$_process_out"
        return 0
    fi
    return 1
}

lander_process_raw() {
    local _inpfile=$1 _outdir=$2
    local _base=${_inpfile##*/}
    local _process_out=$_outdir/${_base/./-r.}
    ln "$_inpfile" "$_process_out" || return 1 #this is on local fs
    chgrp ${LANDER_GROUPS[raw]} $_process_out
    chmod 0660 "$_process_out"
    return 0
}

lander_create_watch() {
    lander_create_watch_$LANDER_QUEUE_TYPE $@
}

lander_create_watch_fs() {
    local _file=$1
    touch $_file
}

lander_create_watch_hdfs() {
    local _file=$1
    $LANDER_HDFS -touchz $_file
}

lander_remove_watch() {
    lander_remove_watch_$LANDER_QUEUE_TYPE $@
}

lander_remove_watch_fs() {
    local _file=$1
    rm -f $_file
}

lander_remove_watch_hdfs() {
    local _file=$1
    $LANDER_HDFS -rm -f $_file
}

#sets lander_diskfree_avail, lander_diskfree_dused, lander_diskfree_total
lander_diskfree() { lander_diskfree_$LANDER_QUEUE_TYPE "$@"; }
lander_diskfree_fs() {
    local _path=$1 _fs
    read _fs lander_diskfree_total lander_diskfree_dused lander_diskfree_avail rest < <(df -k $_path | tail -1)
    #sometimes filesystem is listed on a separate line
    if [ -z "${_fs/[0-9]*/}" ]; then
        lander_diskfree_avail=$lander_diskfree_dused
        lander_diskfree_dused=$lander_diskfree_total
        lander_diskfree_total=$_fs
    fi
    [ -z "$lander_diskfree_avail" -o \
      -z "$lander_diskfree_dused" -o \
      -z "$lander_diskfree_total" ] && {
        echo "ERROR:df failed"
        return 1
    }
    #convert to bytes
    lander_diskfree_avail=$((lander_diskfree_avail*1024))
    lander_diskfree_dused=$((lander_diskfree_dused*1024))
    lander_diskfree_total=$((lander_diskfree_total*1024))
    return 0
}

lander_catfile() { lander_catfile_$LANDER_QUEUE_TYPE "$@"; }
lander_catfile_fs() {
    local _f=$1
    cat "$_f"
}

lander_file_older() { lander_file_older_$LANDER_QUEUE_TYPE $@; }
lander_file_older_fs() {
    local _file=$1 _seconds=$2
    local _tm=$(stat -c %Y $_file)
    local _now=$(date +%s)
    (( _tm + _seconds < _now ))
}
lander_file_ls() { lander_file_ls_$LANDER_QUEUE_TYPE "$@"; }

lander_file_ls_fs() {
    local _d="$@"
    ls -1 $_d
}

lander_file_ls_hdfs() {
    local _d="$@"
    $LANDER_HDFS -ls $_d 2>/dev/null | sed -n "s%^.*  */%/%gp "
}

lander_file_rm() { lander_file_rm_$LANDER_QUEUE_TYPE $@; }
lander_file_rm_fs() {
    /bin/rm -rf $@
}
lander_file_rm_hdfs() {
    $LANDER_HDFS -rm -rf -skipTrash $@
}

lander_file_copyremote() {
    lander_file_copyremote_$LANDER_QUEUE_TYPE $@
}
lander_file_copyremote_fs() {
    local _src=$1 _dst=$2
    #first try linking
    ln $_src $_dst 2>/dev/null ||
        cp -p $_src $_dst
}
lander_file_copyremote_hdfs() {
    local _src=$1 _dst=$2
    $LANDER_HDFS -get -p $_src $_dst 2>/dev/null
}

lander_file_copylocal() { lander_file_copylocal_$LANDER_QUEUE_TYPE "$@"; }
lander_file_copylocal_fs() {
    local _src=$1 _dst=$2
    #first try linking
    ln $_src $_dst 2>/dev/null ||
        cp -p $_src $_dst
}
lander_file_copylocal_hdfs() {
    local _src=$1 _dst=$2
    $LANDER_HDFS -put -p $_src $_dst 2>/dev/null
}

#xxx cannot replace destination
lander_file_move() {
    lander_file_move_$LANDER_QUEUE_TYPE $@
}
lander_file_move_fs() {
    local _src=$1 _dst=$2
    mv -f $_src $_dst
}

lander_file_exists() { lander_file_exists_$LANDER_QUEUE_TYPE $@; }
lander_file_exists_fs() {
    local _file=$1
    [ -e "$_file" ]
}

lander_queue_create() { lander_queue_create_$LANDER_QUEUE_TYPE $@; }
lander_queue_create_fs() {
    local _t=$1 _q=$2 _p
    if [ ${_q::1} == "/" ]; then
        #full path
        _p=$_q
    else
    	_p=$LANDER_USERQDIR/$_t/$_q
    fi

    mkdir -p "$_p"/{data,temp,fail,.derived_queues,.parent_queues} || return 1

    local _g="${LANDER_GROUPS[$_t]}"
	[ -n "$_g" ] || _g="${LANDER_GROUPS[main]}"

    chgrp -R "$_g" "$_p"
    chmod -R g+rx,o-rwx "$_p"
    chmod g+rwx,o-rwx "$_p/data"
    chmod g+rwx,o-rwx,a+t "$_p/.derived_queues"
    echo "Created $_p"
}

lander_queue_list() { lander_queue_list_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_list_fs() {
    local _user=$1 _type=$2 _depth= _path=
    if [ -z "$_type" ]; then
        _depth=2
        _path=$LANDER_TOPDIR_U/$_user/queues
    else
        _depth=1
        _path=$LANDER_TOPDIR_U/$_user/queues/$_type
    fi
    local _qlist=$(find $_path -mindepth $_depth -maxdepth $_depth -type d 2>/dev/null) || return 1
    echo "$_qlist"
}

lander_queue_delete() { lander_queue_delete_$LANDER_QUEUE_TYPE "$@"; }
lander_queue_delete_fs() {
	local _qp=$1
	if [ -d "$_qp" ]; then
		rm -rf "$_qp"
	else
        echo "Queue $_qp does not exist" >&2
        return 1
    fi
}

lander_queue_derive() { lander_queue_derive_$LANDER_QUEUE_TYPE $@; }
lander_queue_derive_fs() {
    local _sq=$1 _dq=$2
    [ -d "$_sq/.derived_queues" ] || {
        echo "ERROR: Queue $_sq is not properly setup" >&2
        return 1
    }
    [ -d "$_dq/.parent_queues" ] || {
        echo "ERROR: Queue $_dq is not properly setup" >&2
        return 1
    }
    local _dh=$(echo $_dq | md5sum | sed 's%\s.*%%')
    ln -fns "$_dq" "$_sq/.derived_queues/$_dh"

    local _sh=$(echo $_sq | md5sum | sed 's%\s.*%%')
    ln -fns "$_sq" "$_dq/.parent_queues/$_sh"
}

lander_queue_underive() { lander_queue_underive_$LANDER_QUEUE_TYPE $@; }
lander_queue_underive_fs() {
    local _sq=$1 _dq=$2
    [ -d "$_sq/.derived_queues" ] || {
        echo "ERROR: Queue $_sq is not properly setup" >&2
        return 1
    }
    [ -d "$_dq/.parent_queues" ] || {
        echo "ERROR: Queue $_dq is not properly setup" >&2
        return 1
    }
    local _ret=0
    local _dh=$(echo $_dq | md5sum | sed 's%\s.*%%')
    local _dl="$_sq/.derived_queues/$_dh"
    if [ -h "$_dl" ]; then
    	rm -f $_dl
    else
        echo "ERROR: Queue $_dq doesn't seem to be connected from $_sq" >&2
        _ret=1
    fi

    local _sh=$(echo $_sq | md5sum | sed 's%\s.*%%')
    local _sl="$_dq/.parent_queues/$_sh"
    if [ -h "$_sl" ]; then
    	rm -f "$_sl"
    else
        echo "ERROR: Queue $_sq desn't seem to be back-referenced at $_dq" >&2
        _ret=$((_ret+2))
    fi
    return $_ret
}
