source: trunk/confman.in @ 587

Revision 587, 36.4 KB checked in by blee, 7 weeks ago (diff)

Implement confman touch for creating versioned files in working copies.

See #154

  • Property rc:mode set to 0555
  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!@BASH@
2#
3# Copyright (c) 2008, Christopher Cowart and contributors
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# * Redistributions of source code must retain the above copyright
10#   notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12#   notice, this list of conditions and the following disclaimer in the
13#   documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# $Id$
28#
29# confman help
30#       Print help and usage information
31
32if [ -r @pkgdatadir@/confmancommon.sh ] ; then
33    . @pkgdatadir@/confmancommon.sh
34else
35    echo "Can't find confmancommon.sh. Exiting." >&2
36    exit 17
37fi
38
39# Source the documentation library
40if [ -f $REPO_DOCS_CONFMAN ] ; then
41        . $REPO_DOCS_CONFMAN
42else
43    echo "Couldn't source the confman shell documentation library" >&2
44        exit 1
45fi
46
47# Set a default editor
48if [ -z ${EDITOR} ] ; then
49    EDITOR="vi"
50fi
51
52# Let's make sure that only you can read your working copy
53# Have to make this less restrictive due to NFS home dirs.
54${NFS_HACK:-false} && umask 022 || umask 027
55
56# This function implements the setup subcommand. It is intended to be called
57# with no arguments, and will error if that's not the case.
58function setup {
59    local response
60    local wcopy_lock
61
62    if conf_wcopy_is_locked; then
63        echo "Your working copy is locked." >&2
64        exit 1
65    else
66        conf_debug "Running setup"
67        if [ -n "$*" ] ; then
68            print_usage 1
69        elif [ -d ${WORK_PATH} ] ; then
70            echo "Looks like ${WORK_PATH} already exists."
71            echo "Start over by removing it? (y/N)"
72            read response
73            if [[ $response =~ ^[yY]([es]|[ES])? ]] ; then
74                rm -rf ${WORK_PATH}
75                setup
76            else
77                echo "Setup failed." >&4
78                return 1
79            fi
80        else
81            mkdir -p ${WORK_PATH}
82            wcopy_lock=$(conf_lock_wcopy)
83            conf_checkout_tree
84            conf_unlock_wcopy $wcopy_lock
85        fi
86    fi
87}
88
89function create {
90    local wcopy_lock
91    if [ -z "$*" ] ; then
92        print_usage 1
93    else
94        wcopy_lock=$(conf_lock_wcopy)
95        conf_create_modules "$@" || exit 1
96        conf_unlock_wcopy $wcopy_lock
97    fi
98}
99
100function update {
101    local wcopy_lock
102    local opt OPTARG OPTIND
103    local revision="HEAD" recipe update_all="false"
104    while getopts ":r:a" opt ; do
105        case $opt in
106            r)  revision="$OPTARG" ;;
107            a)  update_all="true" ;;
108            *)  echo "Invalid option '-${OPTARG}'." >&4
109                print_usage 1
110                ;;
111        esac
112    done
113    shift $(($OPTIND - 1))
114    recipe="$1"
115    wcopy_lock=$(conf_lock_wcopy)
116    $UPDATE_RELEVANT_ONLY && [ -z $recipe ] && recipe="$RECIPE_NAME"
117    if [ ! -r "$(conf_recipe_dir)/$recipe" ]; then
118        echo "Recipe \"$recipe\" does not exist or is not readable." >&2
119        exit 1
120    fi
121    if $update_all || [ -z $recipe ]; then
122        conf_update_tree -r $revision ${WORK_PATH} || exit 1
123    else
124        conf_update_relevant_tree $recipe -r $revision || exit 1
125    fi
126    conf_unlock_wcopy $wcopy_lock
127}
128
129function revert {
130    local wcopy_lock
131    if [ -z "$*" ] ; then
132        print_usage 1
133    else
134        wcopy_lock=$(conf_lock_wcopy)
135        conf_revert "$@"
136        conf_unlock_wcopy $wcopy_lock
137    fi
138}
139
140# Either by prompting or respecting -m/-F, returns a filename on stdout
141# with a log message in it
142# $1 contains a file with the output of svn status to display in interactive
143# log messages
144#   Returns 1 if an abort was requested by the user
145#   Returns 0 if the returned filename is good for a commit
146function get_log {
147    local status="$1"
148    local finished=false
149    local ignoreline="--This line, and those below, will be ignored--"
150    local orig_log logfile
151
152    # local seems to return true, overwriting the return of the nested
153    # call for && evaluation
154    logfile=$(conf_log_message) && finished=true
155
156    if $finished ; then
157        echo "$logfile"
158        return 0
159    fi
160
161    orig_log=$(conf_tmp_file)
162    printf "\n$ignoreline\n" >> "$logfile"
163    cat "$status" >> "$logfile"
164    cp "$logfile" "$orig_log"
165
166    # I apologize for the redirect, but stdout is being written into a
167    # variable, and at least vim (probably other editors too) gets cranky when
168    # stdout doesn't go to the user. Luckily, the fork in the common section
169    # leaves a copy of the original stdout in fd 3.
170    ${EDITOR} "$logfile" >&3
171
172    while diff "$logfile" "$orig_log" >/dev/null 2>&1 ; do
173        echo "Log message unchanged. (E)dit / (a)bort / (c)ontinue?" >&3
174        read response
175        case "$response" in
176            [cC]*)      break;;
177            [aA]*)      return 1;;
178            ""|[eE]*)   ${EDITOR} "$logfile" >&3;;
179        esac
180    done
181    rm -f "$orig_log"
182    sed_i_cmd -e "/${ignoreline}/,\$d" "$logfile" 2>/dev/null
183    echo "$logfile"
184    return 0
185}
186
187# Arguments are full paths to files within $WORK_PATH to check via svn status.
188#   Returns 1 if the files are unchanged
189#   Returns 0 if the files are changed and prints a filename to stdout
190#     with status info suitable for log message footers
191function get_status {
192    local status=$(conf_tmp_file)
193    local ABS_WORK=$(${readlink_cmd} -m "$WORK_PATH")
194
195    # Using a subshell to preserve CWD
196    (
197        cd "$WORK_PATH"
198        until [ -z "$1" ] ; do
199            @SVN@ status "${1#${ABS_WORK}/}" | egrep -v "^\?" >> "$status"
200            shift
201        done
202    )
203
204    if [ $(wc -l < "$status") -eq 0 ] ; then
205        rm -f "$status"
206        return 1
207    fi
208
209    echo "$status"
210    return 0
211}
212
213function commit_repo_only {
214    local msg status abswork
215    local opt OPTIND OPTARG
216    local wcopy_lock
217
218    wcopy_lock=$(conf_lock_wcopy)
219    abswork=$(conf_abswork)
220
221    while getopts ":m:F:" opt ; do
222        case $opt in
223            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
224            F)  LOG_FILE="$OPTARG" ;;
225            *)  echo "Invalid option '-${OPTARG}'." >&4
226                print_usage 1
227                ;;
228        esac
229    done
230    shift $(($OPTIND - 1))
231
232    [ -z "$1" ] || print_usage 1
233
234    if status=$(get_status "${abswork}"); then
235        nocommit=false
236        msg=$(get_log "$status") || conf_cleanExit
237        rm -f "$status"
238    fi
239
240    echo "Commit operation started" >&2
241
242    $nocommit || conf_commit_file $msg "${abswork}" || return $?
243
244    echo "Commit operation finished successfully" >&2
245
246    rm -f $msg
247    conf_unlock_wcopy $wcopy_lock
248}
249
250function commit_full {
251    local msg status modules module symlink abswork files statefile
252    local nocommit=true
253    local opt OPTIND OPTARG
254    local wcopy_lock system_lock
255
256    while getopts ":m:F:" opt ; do
257        case $opt in
258            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
259            F)  LOG_FILE="$OPTARG" ;;
260            *)  echo "Invalid option '-${OPTARG}'." >&4
261                print_usage 1
262                ;;
263        esac
264    done
265    shift $(($OPTIND - 1))
266
267    [ -z "$1" ] || print_usage 1
268
269    conf_require_recipe
270    system_lock=$(conf_lock_system)
271    wcopy_lock=$(conf_lock_wcopy)
272    abswork=$(conf_abswork)
273   
274    for module in $(conf_get_reverse_recipe); do
275        modules=${modules:+${modules} }${module}
276        for symlink in $(find ${WORK_PATH}/${module} \
277            -type l -exec ${readlink_cmd} -m {} \; -print)
278        do
279            symlink=$(conf_rel_path "${symlink}")
280            files="${files:+${files} }${abswork}/${symlink}"
281        done
282        files="${files:+${files} }${abswork}/${module}"
283    done
284   
285    $SUDO ${SUDO:+-v}
286
287    if status=$(get_status $modules $symlinks); then
288        nocommit=false
289        msg=$(get_log "$status") || conf_cleanExit
290        rm -f "$status"
291    fi
292   
293    $SUDO ${SUDO:+-v}
294   
295    echo "Commit operation started" >&2
296    $nocommit || conf_commit_file $msg $files || return $?
297    update
298
299    statefile=$(conf_tmp_file)
300    for layer in $(conf_get_reverse_recipe) ; do
301        echo "Rolling on $layer..."
302        conf_rollout $layer $statefile || return $?
303    done
304    for file in $SINGULARITIES ; do
305        conf_assemble_sing $file || return $?
306    done
307    conf_recordAction commit
308    conf_markclean
309    echo "Commit operation finished successfully" >&2
310    rm -f $msg
311    conf_unlock_wcopy $wcopy_lock
312    conf_unlock_system $system_lock
313}
314
315function commit {
316    if ${CONF_COMMIT_REPO_ONLY}; then
317        commit_repo_only "$@" || return $?
318    else
319        commit_full "$@" || return $?
320    fi
321}
322
323# Short name intentional, don't want collision with real install.
324function inst {
325    local file livefile
326    local msg status files
327    local nocommit=true
328    local system_lock wcopy_lock
329    local opt OPTIND OPTARG
330    local module
331    local statefile
332
333    while getopts ":m:F:" opt ; do
334        case $opt in
335            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
336            F)  LOG_FILE="$OPTARG" ;;
337            *)  echo "Invalid option '-${OPTARG}'." >&4
338                print_usage 1
339                ;;
340        esac
341    done
342    shift $(($OPTIND - 1))
343
344    [ -n "$1" ] || print_usage 1
345
346    system_lock=$(conf_lock_system)
347    wcopy_lock=$(conf_lock_wcopy)
348    conf_require_recipe
349   
350    for file in "$@"; do
351        files="${files:+${files} }$(${readlink_cmd} -m "$file")"
352    done
353   
354    $SUDO ${SUDO:+-v}
355
356    if status=$(get_status $files) ; then
357        nocommit=false
358        msg=$(get_log "$status") || conf_cleanExit
359        rm -f "$status"
360    fi
361    echo "Installation operation started." >&2
362    $nocommit || conf_commit_file "$msg" "$files" || return $?
363
364    update
365   
366    statefile=$(conf_tmp_file)
367    for layer in $(conf_get_reverse_recipe) ; do
368        for file in "$@"; do
369            module=$(conf_wfile_module $file)
370            if conf_wfile_is_singularity $file; then
371                conf_install $layer $statefile "${file%-$module}-${layer}"
372            else
373                conf_install $layer $statefile "$file"
374            fi
375        done
376    done
377    for file in $SINGULARITIES ; do
378        conf_assemble_sing $file || conf_cleanExit
379    done
380    conf_recordAction install
381    if ! conf_isclean ; then
382        echo "WARNING: Recent 'install' operations prevented a 'sync'" >&2
383        echo "Running a 'commit' is highly recommended." >&2
384    fi
385    echo "Installation operation succeeded." >&2
386    conf_unlock_wcopy $wcopy_lock
387    conf_unlock_system $system_lock
388}
389       
390
391function import {
392    local wcopy_lock
393
394    local item OPTIND flags
395    local force=false
396    while getopts "f" OPT; do
397        case "$OPT" in
398            f)
399                force=true
400                flags="-f"
401                ;;
402            *)
403                print_usage 1
404                ;;
405        esac
406    done
407    shift $(($OPTIND - 1))
408
409    conf_require_recipe
410    local module=$1
411    local response usefile suffix file layer symlink morefiles i
412    local mode=$DEFAULT_MODE_FILE
413    local owner=$DEFAULT_OWNER
414    local group=$DEFAULT_GROUP
415    local comment=$DEFAULT_COMMENT
416    shift
417
418    wcopy_lock=$(conf_lock_wcopy)
419
420    if [ -z "$module" ] || [ -z "$1" ] ; then
421        if ! conf_lock_is_recursive $wcopy_lock ; then
422            print_usage 1
423            return 1
424        fi
425        return 0
426    fi
427
428    if ! [ -d "${WORK_PATH}/${module}" ] ; then
429        echo "No such module: $module" >&2
430        return 1
431    fi
432
433    $SUDO ${SUDO:+-v}
434    if [ -r $1 ] ; then
435        file=`$ABSPATH $1`
436    else
437        # If we can't enter the parent directory, this will help us
438        # get the info we need.
439        file=`$SUDO $ABSPATH $1`
440    fi
441    shift
442
443    # See if we're importing a singularity
444    if [[ "$SINGULARITIES" =~ "$file" ]] ; then
445        suffix="-$module"
446    fi
447
448    if [ -f ${WORK_PATH}/${module}${file}${suffix} ] ; then
449        echo "$file already exists in your working copy of $module." \
450            "Skipping." >&4
451        import $flags $module "$@"
452        conf_unlock_wcopy $wcopy_lock
453        return
454    fi
455
456    if ! $force ; then
457        for layer in $(conf_get_recipe) ; do
458            if [ -f ${WORK_PATH}/${layer}${file}${suffix} ] ; then
459                echo "$file already exists in the ${layer}" \
460                    "module. Skipping." >&4
461                echo "Did you mean -f ?" >&4
462                import $flags $module "$@"
463                conf_unlock_wcopy $wcopy_lock
464                return
465            fi
466        done
467    fi
468
469    if $SUDO [ -L $file ] ; then
470        eval `$SUDO ${stat_cmd} "${stat_opts}" ${file}`
471        symlink=`${readlink_cmd} -m $file`
472        usefile=$(conf_tmp_file)
473        echo "# This is a symlink, please do not modify" > $usefile
474
475    elif $SUDO [ -f $file ] ; then
476        eval `$SUDO ${stat_cmd} "${stat_opts}" ${file}`
477        # Let's make our best guess on the comment character:
478        local tmpfile=$(conf_tmp_file)
479        local biggestcount=0
480        local count
481
482        # Let's see if we can read the file as ourself:
483        usefile=$(conf_tmp_file)
484        if [ ! -r $file ] ; then
485            $SUDO cat $file > $usefile
486        else
487            cat $file > $usefile
488        fi
489
490        # Put all non-alphanumerics into a file
491        awk '{print $1}' $usefile | egrep -o \
492            "^[^-_A-Za-z0-9]" > $tmpfile
493
494        for char in `cat $tmpfile | sort | uniq` ; do
495            count=`egrep -o "\\$char" $tmpfile | wc -l`
496            if [ $count -gt $biggestcount ] ; then
497                biggestcount=$count
498                comment="$char"
499            fi
500        done
501        rm $tmpfile
502
503        # Convert mode string to base 10:
504        mode=`echo "obase=10;ibase=8;$mode" | bc`
505        # And use a bitmask to subtract off all write-access
506        # 3949d = 7555o
507        mode=$(($mode & 3949))
508        # And back to an octet:
509        mode=`printf '%04o\n' $mode`
510    elif $SUDO [ -d "$file" ] ; then
511        # Create the directory in our working copy
512        if ! [ -d "${WORK_PATH}/${module}${file}" ] ; then
513            newdir "${WORK_PATH}/${module}${file}"
514        fi
515
516        # Now glob every possible file in the directory
517        morefiles=(${file}/* ${file}/.[^.]* ${file}/..?*)
518
519        # But filter out the ones that aren't files (e.g., ${file}/*,
520        # because there were no files and the glob was interpreted literally)
521        i=0
522        until [ $i -eq ${#morefiles[@]} ] ; do
523            if [ -f "${morefiles[$i]}" ] ; then
524                import $flags $module "${morefiles[$i]}"
525            fi
526            i=$(($i + 1))
527        done
528
529        import $flags $module "$@"
530        conf_unlock_wcopy $wcopy_lock
531        return
532    else
533        # Prompt for file owner
534        echo "Who should be the file's owner? [ $owner ]"
535        read response
536        if [ ! -z $response ] ; then
537            owner=$response
538        fi
539
540        # And file's group
541        echo "Who should be the file's group? [ $group ]"
542        read response
543        if [ ! -z $response ] ; then
544            group=$response
545        fi
546
547        # now, the permissions
548        echo "What should the file's permissions be? [ $mode ]"
549        read response
550        if [ ! -z $response ] ; then
551            mode=$response
552        fi
553
554        # now, the comment character
555        echo "What string starts comment lines? [ $comment ]"
556        read response
557        if [ ! -z $response ] ; then
558            comment=$response
559        fi
560    fi
561
562    if [ ! -d "${WORK_PATH}/${module}`dirname $file`" ] ; then
563        newdir "${WORK_PATH}/${module}`dirname $file`"
564    fi
565
566    # Time to generate the file
567    conf_gen_file $module "${file}${suffix}" $owner $group \
568        $mode "$comment" $usefile "$symlink"
569
570    # Removing temporary files
571    rm -f $usefile
572
573    # Are there more files to import?
574    if [ ! -z $1 ] ; then
575        import $flags $module "$@"
576    fi
577
578    conf_unlock_wcopy $wcopy_lock
579}
580
581function remove {
582    local wcopy_lock
583    if [ -z $1 ]; then
584        print_usage 1
585    fi
586
587    wcopy_lock=$(conf_lock_wcopy)
588    conf_rm_file "$@"
589    conf_unlock_wcopy $wcopy_lock
590}
591
592function newdir {
593    local dir="$1"
594    local response module realpath
595    local mode=$DEFAULT_MODE_DIRECTORY
596    local owner=$DEFAULT_OWNER
597    local group=$DEFAULT_GROUP
598    local comment="dir"
599    local workdir=`$ABSPATH .`
600    local wcopy_lock
601
602    if [ -z "$dir" ] ; then
603        print_usage 1
604    fi
605   
606    wcopy_lock=$(conf_lock_wcopy)
607    # Find the "real" directory's path if not already specified
608    if [[ ! $dir =~ ^/ ]] ; then
609        dir="${workdir}/${dir}"
610    fi
611    module=`echo ${dir#$WORK_PATH} | ${sed_cmd} -e 's:/([^/]+)/.*:\1:'`
612    realpath=${dir#${WORK_PATH}/${module}}
613   
614    local directories=`echo "$realpath" | ${sed_cmd} -e 's:/: :g'`
615    local fulldir=""
616   
617    for dir_name in $directories; do
618       fulldir="${fulldir}/${dir_name}"
619   
620       # If it exists on the filesystem, get its real attributes
621       if [ -d $fulldir ] ; then
622           eval `$SUDO ${stat_cmd} "${stat_opts}" ${fulldir}`
623           echo "Making directory $fulldir with ${owner}:${group}, $mode"
624           conf_mkdir "$WORK_PATH/$module/$fulldir" $owner $group $mode
625   
626       # Otherwise, prompt for attributes
627       else
628           # Prompt for file owner
629           echo "Who should be the directory's owner? [ $owner ]"
630           read response
631           if [ ! -z $response ] ; then
632               owner=$response
633           fi
634   
635           # And file's group
636           echo "Who should be the directory's group? [ $group ]"
637           read response
638           if [ ! -z $response ] ; then
639               group=$response
640           fi
641   
642           # now, the permissions
643           echo "What should the directory's permissions be? [ $mode ]"
644           read response
645           if [ ! -z $response ] ; then
646               mode=$response
647           fi
648           echo "Making directory $fulldir with ${owner}:${group}, $mode"
649           conf_mkdir "$WORK_PATH/$module/$fulldir" $owner $group $mode
650       fi
651    done
652
653    conf_unlock_wcopy $wcopy_lock
654}
655
656function cnf_touch {
657    local file="$1"
658
659    local owner="${DEFAULT_OWNER}"
660    local group="${DEFAULT_GROUP}"
661    local mode="${DEFAULT_MODE_FILE}"
662    local comment="${DEFAULT_COMMENT}"
663    local wcopy_lock
664    local livefile
665
666    if [ -z "${file}" ]; then
667        print_usage 1
668    fi
669   
670    wcopy_lock=$(conf_lock_wcopy)
671
672    # Resolve the absolute path to the file
673    file="$("${ABSPATH}" .)/${file}"
674
675    # If the filename doesn't begin with WORK_PATH, we can't operate here
676    if [ "${file#${WORK_PATH}}" = "${file}" ]; then
677        echo "Cannot operate on non-working-copy file: ${file}" >&4
678    fi
679
680    module=${file#${WORK_PATH}/}
681    module=${module%%/*}
682    livefile="/${file#${WORK_PATH}/*/}"
683
684    # If it exists on the live filesystem, pull the attributes from there
685    if [ -f "${livefile}" ]; then
686        eval `$SUDO ${stat_cmd} "${stat_opts}" "${livefile}"`
687
688        # TODO: Guess the comment character too
689
690        # Convert mode string to base 10:
691        mode=`echo "obase=10;ibase=8;${mode}" | bc`
692        # And use a bitmask to subtract off all write-access
693        # 3949d = 7555o
694        mode=$((${mode} & 3949))
695        # And back to an octet:
696        mode=`printf '%04o\n' ${mode}`
697
698        echo "Touching file ${livefile} with ${owner}:${group}, ${mode}, comment string ${comment}"
699        conf_gen_file "${module}" "${livefile}" "${owner}" "${group}" "${mode}" "${comment}" "" ""
700
701    # Otherwise, prompt for attributes
702    else
703        # Prompt for file owner
704        echo "Who should be the file's owner? [ ${owner} ]"
705        read response
706        if [ -n "${response}" ]; then
707            owner="${response}"
708        fi
709
710        # Prompt for file group
711        echo "Who should be the file's group? [ ${group} ]"
712        read response
713        if [ -n "${response}" ]; then
714            group="${response}"
715        fi
716
717        # Prompt for file mode
718        echo "What should the file's permissions be? [ ${mode} ]"
719        read response
720        if [ -n "${response}" ]; then
721            mode="${response}"
722        fi
723
724        # Prompt for file comment
725        echo "What string starts comment lines? [ ${comment} ]"
726        read response
727        if [ -n "${response}" ]; then
728            comment="${response}"
729        fi
730
731        echo "Touching file ${livefile} with ${owner}:${group}, ${mode}, comment string ${comment}"
732        conf_gen_file "${module}" "${livefile}" "${owner}" "${group}" "${mode}" "${comment}" "" ""
733    fi
734
735    conf_unlock_wcopy "${wcopy_lock}"
736}
737
738function list {
739        local file item
740
741        if [ -z $1 ] ; then
742                file=`$ABSPATH .`
743        else
744                file=`$ABSPATH $1`
745        fi
746        echo -e "--------------------------------------------------------"
747        echo -e "Mode\tOwner\tGroup\tComment\t\tFilename"
748        echo -e "--------------------------------------------------------"
749        if [ -f "$file" ] ; then
750                conf_list $file
751        elif [ -d "$file" ] ; then
752                for item in `ls -A $file | grep -v .svn` ; do
753                        conf_list "$file/$item"
754                done
755        fi
756}
757
758function status {
759        conf_status "$@"
760}
761
762function chowner {
763    local owner file wcopy_lock
764
765    local recursive item OPTIND
766    while getopts "R" opt ; do
767        case $opt in
768            R)
769            recursive=1
770            shift
771            ;;
772            *)
773            print_usage 1
774            ;;
775        esac
776    done
777
778    owner=$1
779    file=$2
780
781    wcopy_lock=$(conf_lock_wcopy)
782    conf_set_prop $file owner $owner
783
784    if [ ! -z $recursive ] && [ -d $file ] ; then
785        for item in $file/* ; do
786            chowner -R $owner $item
787        done
788    fi
789
790    conf_unlock_wcopy $wcopy_lock
791}
792
793function chgroup {
794    local group file wcopy_lock
795
796    local recursive item OPTIND
797    while getopts "R" opt ; do
798        case $opt in
799            R)
800            recursive=1
801            shift
802            ;;
803            *)
804            print_usage 1
805            ;;
806        esac
807    done
808
809    group=$1
810    file=$2
811
812    wcopy_lock=$(conf_lock_wcopy)
813    conf_set_prop $file group $group
814
815    if [ ! -z $recursive ] && [ -d $file ] ; then
816        for item in $file/* ; do
817            chgroup -R $group $item
818        done
819    fi
820
821    conf_unlock_wcopy $wcopy_lock
822}
823
824function chmode {
825    local recursive item OPTIND mode file wcopy_lock
826
827    while getopts "R" opt ; do
828        case $opt in
829            R)
830            recursive=1
831            shift
832            ;;
833            *)
834            print_usage 1
835            ;;
836        esac
837    done
838
839    mode=$1
840    file=$2
841
842    wcopy_lock=$(conf_lock_wcopy)
843    conf_set_prop $file mode $mode
844    if [ ! -z $recursive ] && [ -d $file ] ; then
845        for item in $file/* ; do
846            chmode -R $mode $item
847        done
848    fi
849
850    conf_unlock_wcopy $wcopy_lock
851}
852
853function chcom {
854    local wcopy_lock
855    local comment="$1"
856    local file=$2
857    wcopy_lock=$(conf_lock_wcopy)
858    conf_set_prop $file comment "$comment"
859    conf_unlock_wcopy $wcopy_lock
860}
861
862function chln {
863    local symlink="$1"
864    local file="$2"
865    local wcopy_lock
866
867    if [ $# -lt 2 ] ; then
868        print_usage 1
869    fi
870
871    wcopy_lock=$(conf_lock_wcopy)
872    conf_set_prop "$file" symlink "$symlink"
873    conf_unlock_wcopy $wcopy_lock
874}
875
876function checklook {
877        local module=$1
878        local chkdir=${WORK_PATH}/${REPO_CHECKPTS}/${module}
879        if [ -z $1 ] ; then
880                print_usage 1
881        elif [ -d $chkdir ] ; then
882                ls $chkdir
883        else
884                echo "There are no checkpoints for ${module}."
885        fi
886}
887
888function checknew {
889    local module=$1
890    local checkpoint=$2
891    local wcopy_lock
892    if [ -z $2 ] ; then
893        print_usage 1
894    else     
895        wcopy_lock=$(conf_lock_wcopy)
896        conf_new_checkpoint $module $checkpoint
897        conf_unlock_wcopy $wcopy_lock
898    fi
899}
900
901function checkclear {
902    local module=$1
903    local checkpoint=$2
904    local wcopy_lock
905    if [ -z $2 ] ; then
906        print_usage 1
907    else
908        wcopy_lock=$(conf_lock_wcopy)
909        conf_rm_checkpoint $module $checkpoint
910        conf_unlock_wcopy $wcopy_lock
911    fi
912}
913
914function rollback {
915    local module=$1
916    local checkpoint=$2
917    local clock=$3
918    local wcopy_lock system_lock statefile
919
920    if [ -z "$2" ] ; then
921        print_usage 1
922    else
923        conf_require_recipe
924        wcopy_lock=$(conf_lock_wcopy)
925        system_lock=$(conf_lock_system)
926        echo "Rolling $module back to $checkpoint $clock" >&2
927        conf_rollback $module $checkpoint $clock || conf_cleanExit
928        statefile=$(conf_tmp_file)
929        for layer in $(conf_get_reverse_recipe) ; do
930            echo "Rolling on $layer..."
931            conf_rollout $layer $statefile || conf_cleanExit
932        done
933
934        for file in $SINGULARITIES ; do
935            conf_assemble_sing $file || conf_cleanExit
936        done
937        echo "Rollback succeeded." >&2
938        conf_unlock_wcopy $wcopy_lock
939        conf_unlock_system $system_lock
940    fi
941}
942
943function rmmod {
944    local wcopy_lock
945    if [ -z "$*" ] ; then
946        print_usage 1
947    else
948        wcopy_lock=$(conf_lock_wcopy)
949        conf_remove_modules "$@" || exit 1
950        conf_unlock_wcopy $wcopy_lock
951    fi
952}
953
954function rename {
955    local oldmod=$1
956    local newmod=$2
957    local dir
958    local wcopy_lock
959    if [ -z "$oldmod" -o -z "$newmod" ]; then
960        print_usage 1
961    fi
962    wcopy_lock=$(conf_lock_wcopy)
963    conf_update_tree || exit 1
964    for dir in ${WORK_PATH}/{,${REPO_CHECKPTS}/}$oldmod; do
965        if ! [ -d "$dir" ]; then
966            echo "No such directory: $dir" >&4
967            exit 1
968        fi
969    done
970    for dir in ${WORK_PATH}/{,${REPO_CHECKPTS}/}$newmod; do
971        if [ -e "$dir" ]; then
972            echo "Directory already exists: $dir" >&4
973            exit 1
974        fi
975    done
976    conf_rename $oldmod $newmod || exit 1
977    conf_unlock_wcopy $wcopy_lock
978}
979
980function cnf_diff {
981        conf_diff "$@"
982}
983
984function cnf_log {
985        conf_log "$@"
986}
987
988function copy {
989    local src=$1
990    local dest=$2
991    local wcopy_lock
992    if [ -z $2 ]; then
993        print_usage 1   
994    fi
995    wcopy_lock=$(conf_lock_wcopy)
996    conf_cp $src $dest
997    conf_unlock_wcopy $wcopy_lock
998}
999
1000function move {
1001    local src=$1
1002    local dest=$2
1003    local wcopy_lock
1004    if [ -z $2 ]
1005    then
1006        print_usage 1
1007    fi
1008    wcopy_lock=$(conf_lock_wcopy)
1009    conf_mv $src $dest
1010    conf_unlock_wcopy $wcopy_lock
1011}
1012
1013function mklink {
1014    if [ $# -lt 2 ] ; then
1015        print_usage 1
1016    fi
1017
1018    local forced
1019    if [ $1 == "-f" ] ; then
1020        forced=true
1021        shift
1022        if [ $# -lt 2 ] ; then
1023            print_usage 1
1024        fi
1025    fi
1026
1027    local target=$1
1028    local link=$2
1029    local wcopy_lock
1030    wcopy_lock=$(conf_lock_wcopy)
1031    conf_ln $target $link $forced
1032    conf_unlock_wcopy $wcopy_lock
1033}
1034
1035function lsattr {
1036    if [ -z "$1" ] ; then
1037        conf_lsattr *
1038    else
1039        conf_lsattr "$@"
1040    fi
1041}
1042
1043function chattr {
1044    local attr="$1"
1045    shift
1046    local value="${1:-true}"
1047    shift
1048    if [ -z "$1" ] ; then print_usage 1 ; fi
1049    local wcopy_lock
1050
1051    wcopy_lock=$(conf_lock_wcopy)
1052    conf_chattr "$attr" "$value" "$@"
1053    conf_unlock_wcopy $wcopy_lock
1054}
1055
1056function rmattr {
1057    local attr="$1"
1058    shift
1059    local wcopy_lock
1060    if [ -z "$1" ] ; then print_usage 1 ; fi
1061
1062    wcopy_lock=$(conf_lock_wcopy)
1063    conf_rmattr "$attr" "$@"
1064    conf_unlock_wcopy $wcopy_lock
1065}
1066
1067function state {
1068    if conf_isclean ; then
1069        echo "This host is clean."
1070    else
1071        echo "This host may be dirty. Consider running a 'commit' operation"
1072    fi
1073}
1074
1075function audit {
1076    local wcopy_lock system_lock
1077    if [ -z "$*" ] ; then
1078        conf_require_recipe
1079        system_lock=$(conf_lock_system)
1080        wcopy_lock=$(conf_lock_wcopy)
1081        local tmproot=$(conf_tmp_dir)
1082        local live_root="${LIVE_ROOT}"
1083        local statefile
1084
1085        # Override LIVE_ROOT to commit into our tmporary directory.
1086        LIVE_ROOT="$tmproot"
1087
1088        conf_update_tree || conf_cleanExit
1089
1090        echo "Audit operation started" >&2
1091        statefile=$(conf_tmp_file)
1092        for layer in $(conf_get_reverse_recipe) ; do
1093            echo "Rolling on $layer..."
1094            conf_rollout $layer $statefile || return $?
1095        done >/dev/null
1096        for file in $SINGULARITIES ; do
1097            conf_assemble_sing $file || return $?
1098        done >/dev/null
1099        $SUDO diff -ru ${live_root}/ $tmproot | grep -v "^Only in"
1100        echo "Audit operation finished successfully" >&2
1101        conf_unlock_wcopy $wcopy_lock
1102        conf_unlock_system $system_lock
1103        $SUDO rm -rf $tmproot
1104    else
1105        print_usage 1
1106    fi
1107}
1108
1109function recipe_edit {
1110    local recipe wcopy_lock
1111    local new_recipe_file recipe_file
1112    local repeat msg status
1113    local opt OPTIND OPTARG
1114
1115    while getopts ":m:F:" opt ; do
1116        case $opt in
1117            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
1118            F)  LOG_FILE="$OPTARG" ;;
1119            *)  echo "Invalid option '-${OPTARG}'." >&4
1120                print_usage 1
1121                ;;
1122        esac
1123    done
1124    shift $(($OPTIND - 1))
1125
1126    if [ -z "$1" ] ; then
1127        conf_require_recipe
1128        recipe=$(conf_get_recipe_name)
1129    else
1130        recipe="$1"
1131    fi
1132
1133    wcopy_lock=$(conf_lock_wcopy)
1134    recipe_file=$(conf_recipe_dir)/${recipe}
1135
1136    if ! [ -f "$recipe_file" ] ; then
1137        echo "Recipe $recipe does not exist." >&4
1138        exit 1
1139    fi
1140
1141    new_recipe_file=$(conf_tmp_file)
1142
1143    cat "$recipe_file" > "$new_recipe_file"
1144    $EDITOR "$new_recipe_file"
1145    until conf_recipe_verify "$new_recipe_file" ; do
1146        echo -n "(E)dit / (c)ancel? "
1147        read repeat
1148        case "$repeat" in
1149            [Cc]*)  return 1;;
1150            *)      $EDITOR "$new_recipe_file";;
1151        esac
1152    done
1153
1154    cat "$new_recipe_file" > "$recipe_file"
1155
1156    if status=$(get_status "${recipe_file#${WORK_PATH}/}") ; then
1157        msg=$(get_log "$status") || return 1
1158        conf_commit_recipes "$msg" "$recipe"
1159    fi
1160    rm -f "$new_recipe_file"
1161    conf_unlock_wcopy $wcopy_lock
1162}
1163
1164function recipe_create {
1165    local recipe recipe_file opt OPTIND msg status wcopy_lock
1166    while getopts ":m:F:R:" opt ; do
1167        case $opt in
1168            R)  new_recipe_file="$OPTARG" ;;
1169            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
1170            F)  LOG_FILE="$OPTARG" ;;
1171            *)  echo "Invalid option '-${OPTARG}' for recipe create." >&4
1172                print_usage 1
1173                ;;
1174        esac
1175    done
1176    shift $(($OPTIND - 1))
1177
1178    eval recipe=$1
1179
1180    if [ -z "$recipe" ] ; then
1181        print_usage 1
1182    fi
1183
1184    wcopy_lock=$(conf_lock_wcopy)
1185    recipe_file=$(conf_recipe_dir)/${recipe}
1186    conf_recipe_create "$recipe" || return 1
1187
1188    if [ -z "$new_recipe_file" ] ; then
1189        if ! recipe_edit "$recipe" ; then
1190            conf_rm_file --force "${recipe_file}"
1191            conf_unlock_wcopy $wcopy_lock
1192            return 1
1193        fi
1194        conf_unlock_wcopy $wcopy_lock
1195        return 0
1196    fi
1197
1198    if conf_recipe_verify "$new_recipe_file" ; then
1199        cp "$new_recipe_file" "$recipe_file"
1200        if status=$(get_status "${recipe_file#${WORK_PATH}/}") ; then
1201            msg=$(get_log "$status") || return 1
1202            conf_commit_recipes "$msg" "$recipe"
1203        fi
1204        conf_unlock_wcopy $wcopy_lock
1205        return 0
1206    else
1207        conf_rm_file --force "${recipe_file}"
1208        conf_unlock_wcopy $wcopy_lock
1209        return 1
1210    fi
1211}
1212
1213function recipe_remove {
1214    local opt OPTIND OPTARG
1215    local msg status recipes recipe wcopy_lock
1216    local nocommit=true
1217    while getopts ":m:F:" opt ; do
1218        case $opt in
1219            m)  LOG_MESSAGE="$OPTARG"; LOG_MESSAGE_SET="true" ;;
1220            F)  LOG_FILE="$OPTARG" ;;
1221            *)  echo "Invalid option '-${OPTARG}'." >&4
1222                print_usage 1
1223                ;;
1224        esac
1225    done
1226    shift $(($OPTIND - 1))
1227
1228    wcopy_lock=$(conf_lock_wcopy)
1229
1230    conf_remove_recipes "$@" || exit 1
1231
1232    for recipe in "$@" ; do
1233        recipes="${recipes:+${recipes} }$(conf_recipe_dir)/${recipe}"
1234    done
1235
1236    if status=$(get_status $recipes) ; then
1237        nocommit=false
1238        msg=$(get_log "$status") || conf_cleanExit
1239        rm -f "$status"
1240    fi
1241
1242    $nocommit || conf_commit_recipes "$msg" "$@"
1243
1244    conf_unlock_wcopy $wcopy_lock
1245}
1246
1247function recipe_get {
1248    if ! conf_get_recipe_name ; then
1249        echo "This host's recipe is unset" >&2
1250        return 1
1251    fi
1252}
1253
1254function recipe_set {
1255    local wcopy_lock system_lock
1256    if [ -z "$1" ] ; then print_usage 1 ; fi
1257    local recipe="$1"
1258    wcopy_lock=$(conf_lock_wcopy)
1259    system_lock=$(conf_lock_system)
1260    if ! [ -f "$(conf_recipe_dir)/${recipe}" ] ; then
1261        echo "Error: recipe $recipe does not exist. Create it first!" >&2
1262        exit 1
1263    fi
1264    conf_set_recipe "$recipe"
1265    conf_unlock_system $system_lock
1266    conf_unlock_wcopy $wcopy_lock
1267}
1268
1269function recipe_print {
1270    local recipe="$1"
1271
1272    if [ -z "$recipe" ] ; then
1273        recipe=$(conf_get_recipe_name)
1274    fi
1275
1276    if ! [ -f "$(conf_recipe_dir)/${recipe}" ] ; then
1277        echo "Error: recipe $recipe does not exist." >&2
1278        exit 1
1279    fi
1280
1281    cat "$(conf_recipe_dir)/${recipe}"
1282}
1283   
1284function recipe_list {
1285    (
1286        cd $(conf_recipe_dir)
1287        ls
1288    )
1289}
1290
1291function recipe {
1292    local subcommand="$1"
1293    shift
1294    case "$subcommand" in
1295        edit)               recipe_edit "$@"    ;;
1296        create)             recipe_create "$@"  ;;
1297        remove|rem*|rm)     recipe_remove "$@"  ;;
1298        get)                recipe_get "$@"     ;;
1299        set)                recipe_set "$@"     ;;
1300        print)              recipe_print "$@"   ;;
1301        list)               recipe_list "$@"    ;;
1302        *)                  print_usage 1       ;;
1303    esac
1304}
1305
1306function print_version {
1307    echo "confman-${VERSION}"
1308}
1309
1310# Debug mode?
1311while getopts ":h:dv-" opt ; do
1312        case $opt in
1313                d) DEBUG="true" ;;
1314        h) print_help "$OPTARG" ; conf_cleanExit 0 ;;
1315        v) print_version ; conf_cleanExit 0 ;;
1316                *) echo "Invalid options '-${OPTARG}'." >&4
1317           print_usage 1
1318           ;;
1319        esac
1320done
1321
1322# Argument checking
1323if [ -n "$LOG_FILE" ] && ! [ -r "$LOG_FILE" ] ; then
1324    echo "Log file $LOG_FILE does not exist or is not readable." >&4
1325    conf_cleanExit 1
1326fi
1327
1328if $LOG_MESSAGE_SET && [ -n "$LOG_FILE" ] ; then
1329    echo "Cannot specify both a message with -m and a file with -F." >&4
1330    conf_cleanExit 1
1331fi
1332
1333# Dispatch the subcommand. This must happen last and in the global context.
1334eval subcommand=\$$OPTIND
1335shift $OPTIND
1336
1337trap "conf_interrupt_trap" HUP INT QUIT TERM
1338case $subcommand in
1339    help )                          print_help "$@" ; conf_cleanExit 0 ;;
1340    version )                       print_version; conf_cleanExit 0;;
1341    setup|se* )                     setup "$@" ;;
1342    status )                        status "$@" ;;
1343    state )                         state ;;
1344    create|cr* )                    create "$@" ;;
1345    update|u* )                     update "$@" ;;
1346    commit|com* )                   commit "$@" ;;
1347    import|im* )                    import "$@" ;;
1348    install|in* )                   inst "$@" ;;
1349    recipe|rec* )                   recipe "$@" ;;
1350    remove|rem*|rm )                remove "$@" ;;
1351    rev* )                          revert "$@" ;;
1352    ren* )                          rename "$@" ;;
1353    rmmod )                         rmmod "$@" ;;
1354    mkdir|mkd* )                    newdir "$@" ;;
1355    touch )                         cnf_touch "$@" ;;
1356    list|ls )                       list "$@" ;;
1357    link|ln )                       mklink "$@" ;;
1358    lsattr )                        lsattr "$@" ;;
1359    chattr )                        chattr "$@" ;;
1360    rmattr )                        rmattr "$@" ;;
1361    move|mv )                       move "$@" ;;
1362    copy|cp )                       copy "$@" ;;
1363    diff )                          cnf_diff "$@" ;;
1364    log )                           cnf_log "$@" ;;
1365    chmod )                         chmode "$@" ;;
1366    chown )                         chowner "$@" ;;
1367    chcom )                         chcom "$@" ;;
1368    chgrp )                         chgroup "$@" ;;
1369    chln )                          chln "$@" ;;
1370    checklook|checklist|chls|chlk ) checklook "$@" ;;
1371    checknew|chnw )                 checknew "$@" ;;
1372    checkclear|chcl|chrm )          checkclear "$@" ;;
1373    rollback|ro* )                  rollback "$@" ;;
1374    audit )                         audit "$@" ;;
1375    * )                             print_usage 1 ;;
1376esac
1377
1378conf_cleanExit 0
1379
1380# vim:ts=4:ft=sh
Note: See TracBrowser for help on using the repository browser.