# ------------------------------------------------------------------------
#	test.fileaccess.tcl
#-------------------------------------------------------------------------
#	$Log: $
#	2003-12-07 benny	Created
# ------------------------------------------------------------------------

# proc cacls
#
# A small proc to encapsulate the NT cacls command-line tool.  A "y"
# is piped into the command to cover a possible question for
# confirmation.  Only one "y", so only one file can be given.
#
# See the output of cacls /? on a command prompt for all I know about
# cacls.
#
# I'm not sure how portable this is to XP.  The tool is also probably
# localized on non-english systems.
#
# In theory the same effect could probably be achieved easier and more
# controlled with the Cygwin tools getfacl and setfacl (be sure to use
# the setting CYGWIN=ntsec).  Those tools seem somewhat incomplete
# though, in my installation I can't see how getfacl represents the
# result of "cacls $fname /E /P $user:N" (deny all access for $user).
proc cacls {fname args} {
	string trim [eval [list exec cacls [file nativename $fname]] $args <<y]
}

# proc getuser
#
# Parses the output of the "dir/q" command to find the owner of a
# file.  I haven't found a specialized tool for this and cacls doesn't
# give this info either.
#
# Example of dir/q output line:
# 2003-11-03  20:36                  598 OCTAVIAN\benny         filename.txt
proc getuser {fname} {
	set tryname $fname
	if {[file isdirectory $fname]} {
		set tryname [file dirname $fname]
	}
	set tail [file tail $tryname]
	set dirtext [exec cmd /c dir /q [file nativename $fname]]
	set owner ""
	foreach line [split $dirtext "\n"] {
		if {[string match -nocase "* $tail" $line]} {
			set attrs [string range $line \
					   0 end-[string length $tail]]
			regexp { [A-Z]+\\.*$} $attrs owner
			set owner [string trim $owner]
		}
	}
	if {"" == "$owner"} {
		error "getuser: Owner not found in output of dir/q"
	}
	return $owner
}

# proc test_read
#
# A test for readablity by actually reading the file. 
proc test_read {fname} {
	if {[catch {set ifs [open $fname r]}]} {
		return 0
	}
	set readfailed [catch {read $ifs}]
	return [expr {![catch {close $ifs}] && !$readfailed}]
}

# proc test_writ
#
# A test for writablity by actually writing the file. 
proc test_writ {fname} {
	if {[catch {set ofs [open $fname w]}]} {
		return 0
	}
	set writefailed [catch {puts $ofs "Hello"}]
	return [expr {![catch {close $ofs}] && !$writefailed}]
}

# proc test_access
#
# Test the readablilty and writability of a file.  Output to stdout,
# if a test does not agree with the parameters "read" or "writ".  This
# tests with [file readable/writable] as well as with [test_read] and
# test_writ].  If any differences between the test and the given
# parameters are detected, cacls is called at the end to output the
# actual rights of the file for diagnosis.
#
# See calls in [test] below.
proc test_access {fname read writ} {
	set problem 0
	foreach type {read writ} {
		if {[set $type] != [file ${type}able $fname]} {
			puts "[set $type] != \[file ${type}able $fname\]"
			set problem 1
		}
		if {[set $type] != [test_${type} $fname]} {
			puts "[set $type] != \[test_${type} $fname\]"
			set problem 1
		}
	}
	if {$problem} {
		puts "Actual rights are:"
		puts [cacls $fname]
	} else {
		puts "OK"
	}
}

# proc test
#
# A bunch of actual test scenarios. 
proc test {} {
	set fname test.dat

	# Create the test file.
	file delete $fname
	close [open $fname w]

	# Determine and print what we know about user and file owner.
	set owner [getuser $fname]
	set user $::env(USERDOMAIN)\\$::env(USERNAME)
	puts "Owner: $owner"
	puts "User: $user"

	# Clean out some ACLs well-known to be added automatically to
	# new files. Deny current user all access.
	catch {cacls $fname /E /R "Everyone"} result
	catch {cacls $fname /E /R $user} result
	catch {cacls $fname /E /R $owner} result
	cacls $fname /E /P $user:N

	# First test: Without more ACLs there should be no access.
	puts "\nTest no access"
	test_access $fname 0 0

	# Add GENERIC_READ ACL for current user.  Now there should be
	# only read access.
	puts "\nTest readable only"
	cacls $fname /E /P $user:N
	cacls $fname /E /G $user:R
	test_access $fname 1 0

	# Remove ACL for current user.  Deny access. Add GENERIC_WRITE
	# ACL.  Now there should be only write access.
	puts "\nTest writable only"
	catch {cacls $fname /E /R $user} result
	cacls $fname /E /P $user:N
	cacls $fname /E /G $user:W
	test_access $fname 0 1

	# Remove ACL for current user.  Deny access.  Add GENERIC_READ
	# ACL and GENERIC_WRITE ACL.  Now there should be both read
	# and write access.
	puts "\nTest read+write"
	catch {cacls $fname /E /R $user} result
	cacls $fname /E /P $user:N
	cacls $fname /E /G $user:R
	cacls $fname /E /G $user:W
	test_access $fname 1 1

	# Remove ACL for current user.  Deny access.  Add GENERIC_ALL
	# (full) ACL.  There should be both read and write access.
	puts "\nTest full access"
	catch {cacls $fname /E /R $user} result
	cacls $fname /E /P $user:N
	cacls $fname /E /G $user:F
	test_access $fname 1 1

	# Cleanup.
	file delete $fname
}

# ------------------------------------------------------------------------
#				eof
# ------------------------------------------------------------------------
