# NFS RPC client -- RFC 1094

# XXX This is not yet complete.
# XXX Only GETATTR, SETTTR, LOOKUP and READDIR are supported.

# (See mountclient.py for some hints on how to write RPC clients in
# Python in general)

import rpc
from rpc import UDPClient, TCPClient
from mountclient import FHSIZE, MountPacker, MountUnpacker

NFS_PROGRAM = 100003
NFS_VERSION = 2

# enum stat
NFS_OK = 0
# (...many error values...)

# enum ftype
NFNON = 0
NFREG = 1
NFDIR = 2
NFBLK = 3
NFCHR = 4
NFLNK = 5


class NFSPacker(MountPacker):

	def pack_sattrargs(self, sa):
		file, attributes = sa
		self.pack_fhandle(file)
		self.pack_sattr(attributes)

	def pack_sattr(self, sa):
		mode, uid, gid, size, atime, mtime = sa
		self.pack_uint(mode)
		self.pack_uint(uid)
		self.pack_uint(gid)
		self.pack_uint(size)
		self.pack_timeval(atime)
		self.pack_timeval(mtime)

	def pack_diropargs(self, da):
		dir, name = da
		self.pack_fhandle(dir)
		self.pack_string(name)

	def pack_readdirargs(self, ra):
		dir, cookie, count = ra
		self.pack_fhandle(dir)
		self.pack_uint(cookie)
		self.pack_uint(count)

	def pack_timeval(self, tv):
		secs, usecs = tv
		self.pack_uint(secs)
		self.pack_uint(usecs)


class NFSUnpacker(MountUnpacker):

	def unpack_readdirres(self):
		status = self.unpack_enum()
		if status == NFS_OK:
			entries = self.unpack_list(self.unpack_entry)
			eof = self.unpack_bool()
			rest = (entries, eof)
		else:
			rest = None
		return (status, rest)

	def unpack_entry(self):
		fileid = self.unpack_uint()
		name = self.unpack_string()
		cookie = self.unpack_uint()
		return (fileid, name, cookie)

	def unpack_diropres(self):
		status = self.unpack_enum()
		if status == NFS_OK:
			fh = self.unpack_fhandle()
			fa = self.unpack_fattr()
			rest = (fh, fa)
		else:
			rest = None
		return (status, rest)

	def unpack_attrstat(self):
		status = self.unpack_enum()
		if status == NFS_OK:
			attributes = self.unpack_fattr()
		else:
			attributes = None
		return status, attributes

	def unpack_fattr(self):
		type = self.unpack_enum()
		mode = self.unpack_uint()
		nlink = self.unpack_uint()
		uid = self.unpack_uint()
		gid = self.unpack_uint()
		size = self.unpack_uint()
		blocksize = self.unpack_uint()
		rdev = self.unpack_uint()
		blocks = self.unpack_uint()
		fsid = self.unpack_uint()
		fileid = self.unpack_uint()
		atime = self.unpack_timeval()
		mtime = self.unpack_timeval()
		ctime = self.unpack_timeval()
		return (type, mode, nlink, uid, gid, size, blocksize, \
			rdev, blocks, fsid, fileid, atime, mtime, ctime)

	def unpack_timeval(self):
		secs = self.unpack_uint()
		usecs = self.unpack_uint()
		return (secs, usecs)


class NFSClient(UDPClient):

	def __init__(self, host):
		UDPClient.__init__(self, host, NFS_PROGRAM, NFS_VERSION)

	def addpackers(self):
		self.packer = NFSPacker()
		self.unpacker = NFSUnpacker('')

	def mkcred(self):
		if self.cred == None:
			self.cred = rpc.AUTH_UNIX, rpc.make_auth_unix_default()
		return self.cred

	def Getattr(self, fh):
		return self.make_call(1, fh, \
			self.packer.pack_fhandle, \
			self.unpacker.unpack_attrstat)

	def Setattr(self, sa):
		return self.make_call(2, sa, \
			self.packer.pack_sattrargs, \
			self.unpacker.unpack_attrstat)

	# Root() is obsolete

	def Lookup(self, da):
		return self.make_call(4, da, \
			self.packer.pack_diropargs, \
			self.unpacker.unpack_diropres)

	# ...

	def Readdir(self, ra):
		return self.make_call(16, ra, \
			self.packer.pack_readdirargs, \
			self.unpacker.unpack_readdirres)

	# Shorthand to get the entire contents of a directory
	def Listdir(self, dir):
		list = []
		ra = (dir, 0, 2000)
		while 1:
			(status, rest) = self.Readdir(ra)
			if status <> NFS_OK:
				break
			entries, eof = rest
			last_cookie = None
			for fileid, name, cookie in entries:
				list.append((fileid, name))
				last_cookie = cookie
			if eof or last_cookie == None:
				break
			ra = (ra[0], last_cookie, ra[2])
		return list

	
def test():
	import sys
	if sys.argv[1:]: host = sys.argv[1]
	else: host = ''
	if sys.argv[2:]: filesys = sys.argv[2]
	else: filesys = None
	from mountclient import UDPMountClient, TCPMountClient
	mcl = TCPMountClient(host)
	if filesys == None:
		list = mcl.Export()
		for item in list:
			print item
		return
	sf = mcl.Mnt(filesys)
	print sf
	fh = sf[1]
	if fh:
		ncl = NFSClient(host)
		as = ncl.Getattr(fh)
		print as
		list = ncl.Listdir(fh)
		for item in list: print item
		mcl.Umnt(filesys)
