#!/usr/bin/wish -f

#    fstool: reads /etc/fstab and allows mounting & unmounting of volumes
#    By Bob Hepple, bhepple@bit.net.au http://www.bit.net.au/~bhepple

#    Can use jstools (version 4.x by Jay Sekora: js@aq.org
#    http://www.aq.org/~js/) otherwise no help or preferences settings
#    can be made.

#    Copyright (C) 1999  Bob Hepple

#    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 2 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; see the file COPYING.  If not, write to
#    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#    Boston, MA 02111-1307, USA.

#######################Standard initialisation###################

if {[info exists env(JSTOOLS_LIB)]} {
  set jstools_library $env(JSTOOLS_LIB)
  set jstools_pkg [file join $env(JSTOOLS_LIB) pkg]
} else {
  set jstools_library /usr/local/lib/jstools
  set jstools_pkg [file join $jstools_library pkg]
}

if {[file exists $jstools_library]} {
    set useJtools 1
} {
    set useJtools 0
}

#################################################################
# Subroutines: (see main at the end)
#################################################################

proc fstool:debug {} {
    global debug
   
    return $debug
}

proc fstool:useJtools {} {
    global useJtools

    return $useJtools
}

# simplified version of j:alert for when there's no jstools:
proc fstool:alert { msg } {

    if {[fstool:useJtools]} {
	j:alert -text $msg
    } {
	set w .alert
	
	set old_focus [focus]                 ;# so we can restore original focus
	toplevel .alert
	
	wm transient .alert .
	
	message .alert.msg -width 300 -anchor w -text $msg
	button .alert.b -text "OK" -command {destroy .alert}
	
	pack .alert.msg -side top -fill both -expand yes -padx 10 -pady 10
	pack .alert.b -side bottom -fill both
	
	update

	grab .alert
	focus .alert
	tkwait window .alert
    }
}

######################################################################
# jstools dependent:
######################################################################

proc fstool:mkmenus {} {
  frame .menu -borderwidth 2 -relief raised
  
  fstool:mkmenu:prefs
  fstool:mkmenu:help
  pack .menu -in . -side top -fill x
  
  tk_menuBar .menu .menu.prefs .menu.help
}

proc fstool:mkmenu:prefs {} {
  j:menu:menubutton .menu.prefs .menu.prefs.m {Prefs}
  pack .menu.prefs -in .menu -side left
  
  j:menu:commands .menu.prefs.m .t {
    j:cmd:global_pref_panel
    fstool:cmd:prefs
    -
    fstool:cmd:quit
  }
}

proc fstool:mkmenu:help {} {
  j:menu:menubutton .menu.help .menu.help.m {Help}
  pack .menu.help -in .menu -side right
  
  j:menu:docs .menu.help.m {
    {{Help on fstool} {fstool.jdoc}}
    -
    {{Help on jstools} {jstools.jdoc}}
  }
}

##############################################################################
# fstool:cmd:prefs - preferences panel
##############################################################################
proc fstool:cmd:prefs { w args } {
  global FSTOOL_PREFS
  
  set old_focus [focus]			;# so we can restore original focus
  
  set w .prefs
  toplevel $w
  wm title $w [= "Fstool Preferences"]

  frame $w.mountFile
  label $w.mountFile.l -text {Mount file:}
  entry $w.mountFile.e -width 20 \
    -textvariable FSTOOL_PREFS(mountFile)
  
  j:buttonbar $w.b -default save -buttons [format {
    { 
      save Save {
	fstool:destroyButtons
	fstool:readMountFile
	fstool:mkbuttons
        j:write_prefs -array FSTOOL_PREFS -file fstool-defaults
        destroy %s
      }
    } {
      done Cancel {
        destroy %s
      }
    }
  } $w $w]
  
  pack $w.mountFile.l $w.mountFile.e -in $w.mountFile -side left

  pack \
    $w.mountFile \
    [j:rule $w] \
    $w.b \
    -in $w -side top -fill x

  j:dialogue $w		;# position in centre of screen

  focus $w
  j:default_button $w.b.save $w.mountFile.e $w
  bind $w <Key-Tab> "focus $w.mountFile.e"
  tkwait window $w
  focus -force $old_focus		;# can't figure out a better way...
}

proc fstool:cmd:quit { w args } {
    if {[fstool:useJtools]} {
	if {[j:confirm -text "Are you sure you want to quit?"]} {
	    exit 0
	}
    } {
	exit 0
    }
}

proc fstool:init {} {
    global jstools_library 
    global jstools_pkg
    global auto_path
    global tk_library
    global J_PREFS                        ;# general jstools user preferences
    global FSTOOL_PREFS                     ;# user preferences for fstool
    global FSTOOL_PATH
    global debug

    set debug 0

    if {[fstool:useJtools]} {
	# add the jstools library to the library search path:
	set auto_path [concat [list $jstools_pkg] [list $jstools_library] $auto_path ]

	j:jstools_init fstool                   ;# prefs, libraries, bindings...
    
	set FSTOOL_PATH "
    .
    [glob -nocomplain ~/tk/fstool]
    [glob -nocomplain ~/.tk/fstool]
    $jstools_library/fstool
    $tk_library/fstool
  "
	#  fstool:register_commands                ;# force auto-loading of all commands

	j:read_prefs -array FSTOOL_PREFS -file fstool-defaults {
	    {mountFile "/etc/fstab"}
	}

	j:command:register fstool:cmd:prefs {fstool Preferences...}
	j:command:register fstool:cmd:quit {Quit}
	fstool:mkmenus
    } {
	set FSTOOL_PREFS(mountFile) "/etc/fstab"
    }
}

######################################################################
# End of jstools dependent code
######################################################################

proc fstool:disable {} {
    global fileSystem numFileSystems
    . configure -cursor watch

    for {set i 1} {$i <= $numFileSystems} {incr i} {
	.mount$i configure -state disabled
    }
    .quit configure -state disabled
}

proc fstool:enable {} {
    global fileSystem numFileSystems
    . configure -cursor {}

    for {set i 1} {$i <= $numFileSystems} {incr i} {
	.mount$i configure -state normal
    }
    .quit configure -state normal
}
    
######################################################################
# Only root can give options -t -o etc to mount(1). Ordinary users must
# give only the mountpoint:
######################################################################
proc fstool:mount {button} {
    global fileSystem
    set uid [exec id -u]

    fstool:disable
    .status configure -text "Mounting, please wait ..."
    update idletasks
    if {$uid == 0} { # we are root
	set doOption 0
	set option $fileSystem($button,options)
	if {[string length $option] > 0} {set doOption 1}
	if $doOption {
	    if [catch \
		    {exec "mount" \
			 "-t" "$fileSystem($button,type)" \
			 "-o" "$option" \
			 "$fileSystem($button,device)" \
			 "$fileSystem($button,mountPoint)"} msg] {
		fstool:alert $msg
	    }
	} {
	    if [catch \
		    {exec "mount" \
			 "-t" "$fileSystem($button,type)" \
			 "$fileSystem($button,device)" \
			 "$fileSystem($button,mountPoint)"} msg] {
		fstool:alert $msg
	    }
	}
    } { # we are not root
	if [catch {exec "mount" $fileSystem($button,mountPoint)} msg] {
	    fstool:alert $msg
	}
    }
    .status configure -text ""
    fstool:enable
    fstool:check $button
}

proc fstool:unmount {button} {
    global fileSystem

    . configure -cursor watch
    .status configure -text "Unmounting, please wait ..."
    update idletasks
    if [catch {exec "umount" $fileSystem($button,mountPoint)} msg] {
	fstool:alert $msg
    }
    .status configure -text ""
    . configure -cursor {}
    fstool:check $button
}

proc fstool:check {button} {
    global fileSystem
    global fstool:debug

    # See if the device is mounted:
    catch {exec mount | grep "^$fileSystem($button,device)"} msg
    if {[fstool:debug]} {
	puts $msg
    }
    if [regexp "^$fileSystem($button,device)" $msg] {
	.mount$button select
	return $msg
    } {
	.mount$button deselect
	return 0
    }
}

proc fstool:toggle {button} {
    global mount$button

    # value is toggled _before_ we get here, hence reverse sense:
    # note the dereference [set mount$button] - pretty funky in tcl!
    # $$mount$button (nor even $$var) work as you would expect! Sux.
    if {[set mount$button]} {
	fstool:mount $button
    } {
	fstool:unmount $button
    }    
}

# This is a bit precious. It starts by assuming that the input filename
# is absolute.
proc fstool:noLink { fileName } {
    # eliminate sybolic links from a file name (mount(1) seems to convert
    # to the real device name):
    while {[string compare [file type $fileName] {link}] == 0} {
	set newFileName [file readlink $fileName]
	if {[string compare [file pathtype $newFileName] {absolute}] != 0} {
	    set newFileName [file join [file dirname $fileName] $newFileName]
	}
	set fileName $newFileName
    }
    return $fileName
}

# Assumes /etc/fstab is in the form:
#      0          1      2        3   4   5
# device mountpoint [ type [ option [ x [ y ]]]]
# device could be an NFS mount point and not a file

proc fstool:readMountFile {} {
    global fileSystem numFileSystems FSTOOL_PREFS

    set f [open $FSTOOL_PREFS(mountFile) r]
    set numFileSystems 0
    while {[gets $f line] >= 0} {
	regsub -all "\t" $line " " x
	regsub -all " +" $x " " line
	set tokens [split $line " 	"]
	if {[llength $tokens] < 2} {
	    continue
	}
	# Eliminate things that can't be unmounted:
	if {[string compare [lindex $tokens 1] "/" ] == 0 || \
		[string compare [lindex $tokens 1] "swap" ] == 0 || \
		[string compare [lindex $tokens 1] "none" ] == 0 || \
		[string compare [lindex $tokens 1] "/proc" ] == 0 || \
		! [regexp {^/}  [lindex $tokens 1]] } {
	    continue
	}
	incr numFileSystems
	set device [lindex $tokens 0]
	if {[regexp {^/} $device]} { # Probably not an NFS mount point like tikka:/
	    set device [fstool:noLink $device]
	}
	set fileSystem($numFileSystems,device) 		$device
	set fileSystem($numFileSystems,mountPoint) 	[lindex $tokens 1]
	set fileSystem($numFileSystems,type) 		[lindex $tokens 2]
	set options [lindex $tokens 3]
	# remove options that only make sense in /etc/fstab and not in
	# a mount(1) command: process noXXX options before XXX
	foreach option {nouser user noauto auto} {
	    while {[regsub "(.*)$option,*(.*)" $options {\1\2} options]} {}
	}
	set fileSystem($numFileSystems,options) $options
    }
    close $f

    if {[fstool:debug]} {
	# for debugging: check that we parsed the file right:
	for {set i 1} {$i <= $numFileSystems} {incr i} {
	    puts "$i: DEVICE=$fileSystem($i,device) MOUNT=$fileSystem($i,mountPoint) TYPE=$fileSystem($i,type) OPTIONS=$fileSystem($i,options)"
	}
    }
}

proc fstool:mkbuttons {} {
    global numFileSystems fileSystem
    for {set i 1} {$i <= $numFileSystems} {incr i} {
	checkbutton .mount$i \
	    -text "$fileSystem($i,mountPoint) ($fileSystem($i,device))" \
	    -command "fstool:toggle $i" -anchor w
    }
    button .quit -text "Quit" -command {fstool:cmd:quit 1 1}
    label .status -text ""

    for {set i 1} {$i <= $numFileSystems} {incr i} {
	pack .mount$i -in . -fill x
    }
    pack .quit .status -in . -anchor w -fill x
    
    for {set i 1} {$i <= $numFileSystems} {incr i} {
	fstool:check $i
    }
}

proc fstool:destroyButtons {} {
    global numFileSystems

    for {set i 1} {$i <= $numFileSystems} {incr i} {
	destroy .mount$i
    }
    destroy .quit .status
}

##################################################################
# MAIN PROGRAM (but see initialisation at start
##################################################################

fstool:init

fstool:readMountFile

if {[fstool:debug]} {
    puts "$numFileSystems mountable devices"
}

if {$numFileSystems == 0} {
    puts "No mountable file systems in /etc/fstab"
    exit
}

fstool:mkbuttons

# For emacs:
# Local Variables:
# mode: tcl
# End:
