Index: /trunk/source/attach/attach
===================================================================
--- /trunk/source/attach/attach (revision 53)
+++ /trunk/source/attach/attach (revision 53)
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import getopt
+import hesiod
+
+usage = """Usage: attach [options] filesystem ... [options] filesystem ...
+       attach -l filesystem
+       attach -H host
+       attach
+"""
+
+if len(sys.argv) > 1 and sys.argv[1] == '-Padd':
+	os.execv('/usr/local/bin/attach-add.py', sys.argv[2:])
+
+try:
+	optlist, args = getopt.getopt(sys.argv[1:], 'vqplvngazhrwm:Mxet:o:NSOLH', ['verbose', 'quiet', 'printpath', 'lookup', 'map', 'nomap', 'remap', 'noremap', 'zephyr', 'nozephyr', 'readonly', 'write', 'mountpoint=', 'master', 'noexplicit', 'explicit', 'type', 'mountoptions', 'nosetuid', 'nosuid', 'setuid', 'suid', 'override', 'lock', 'host=']);
+except getopt.GetoptError:
+	sys.stderr.write(usage)
+	sys.exit(1)
+
+print_usage = False
+noise_level = 'verbose'
+for o, a in optlist:
+	if o == '-l' or o == '--lookup':
+		noise_level = 'lookup'
+	if o == '-v' or o == '--verbose':
+		noise_level = 'verbose'
+	if o == '-q' or o == '--quiet':
+		noise_level = 'quiet'
+	if o == '-p' or o == '--printpath':
+		noise_level = 'path'
+	if o == '-m' or o == '--mountpoint':
+		if a[0:5] == '/mit/':
+			sys.stderr.write('Sorry. The MacAthena attach program does not support alternate mount points within /mit\n')
+			sys.exit(1)
+		elif len(args) != 1:
+			print_usage = True
+		else:
+			filsys = hesiod.FilsysLookup(args[0])
+			if filsys:
+				os.symlink(filsys.getFilsys()[0]['location'], a)
+				sys.exit(0)
+			else:
+				sys.stderr.write('%s: Locker unknown' % args[0])
+				sys.exit(2)
+	if o == '-H' or o == '--host':
+		sys.stderr.write('Sorry. The MacAthena attach program does not keep an attachtab\n')
+		sys.exit(1)
+
+exit_code = 0
+
+for arg in args:
+	try:
+		os.stat('/mit/%s' % arg)
+		os.system('/usr/bin/aklog /mit/%s' % arg)
+	except OSError:
+		sys.stderr.write('%s: Locker unknown\n' % arg)
+		exit_code = 2
+	if noise_level == 'verbose':
+		filsys = hesiod.FilsysLookup(args[0])
+		print 'attach: %s attached to /mit/%s for filesystem %s' % (filsys.getFilsys()[0]['location'], arg, arg)
+	elif noise_level == 'path':
+		print '/mit/%s' % arg
+	elif noise_level == 'lookup':
+		print '%s resolves to:' % arg
+		os.system('hesinfo %s filsys' % arg)
+
+sys.exit(exit_code)
Index: /trunk/source/add/attach-add.py
===================================================================
--- /trunk/source/add/attach-add.py (revision 53)
+++ /trunk/source/add/attach-add.py (revision 53)
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import getopt
+
+usage = """Usage: add [-vfrpwbq] [-P $athena_path] [-a attachflags] [lockername ...]
+       add [-dfrb] [-P $athena_path] pathname ...
+"""
+
+if '-a' in sys.argv[1:]:
+	(add_options, attach_options) = sys.argv[1:].split('-a')
+else:
+	add_options = sys.argv[1:]
+	attach_options = []
+
+try:
+	optlist, args = getopt.getopt(add_options, 'frwpP:abqh');
+except getopt.GetoptError:
+	sys.stderr.write(usage)
+	sys.exit(1)
+
+front = False
+remove_locker = False
+shell = 'csh'
+for o, a in optlist:
+	if o == '-f': front = True
+	if o == '-r': remove_locker = True
+	if o == '-b': shell = 'bash'
+
+if os.environ.has_key('PATH'):
+	path = os.environ['PATH'].split(':')
+else: path = []
+
+if os.environ.has_key('MANPATH'):
+	manpath = os.environ['MANPATH'].split(':')
+else: manpath = []
+
+if os.environ.has_key('INFOPATH'):
+	infopath = os.environ['INFOPATH'].split(':')
+else: infopath = []
+
+for arg in args:
+	if '/' == arg[0] or '.' == arg[0]:
+		if remove_locker: path.remove(arg)
+		elif front: path = [arg] + path
+		else: path.append(arg)
+	else:
+		locker = '/mit/%s' % arg
+		
+		bin_pipe = os.popen('/usr/local/bin/athdir %s bin' % locker)
+		new_bin = bin_pipe.read().strip()
+		if bin_pipe.close() != None:
+			if not os.access(locker, os.F_OK):
+				sys.stderr.write("%s: Locker unknown\n" % arg)
+			continue
+		if new_bin in path:
+			path.remove(new_bin)
+		if front: path.insert(0, new_bin)
+		elif not remove_locker: path.append(new_bin)
+		
+		man_pipe = os.popen('/usr/local/bin/athdir %s man' % locker)
+		new_man = man_pipe.read().strip()
+		if man_pipe.close() == None:
+			if new_man in manpath:
+				manpath.remove(new_man)
+			if front: manpath.insert(0, new_man)
+			elif not remove_locker: manpath.append(new_man)
+		
+		info_pipe = os.popen('/usr/local/bin/athdir %s info' % locker)
+		new_info = info_pipe.read().strip()
+		if info_pipe.close() == None:
+			if new_info in infopath:
+				infopath.remove(new_info)
+			if front: infopath.insert(0, new_info)
+			elif not remove_locker: infopath.append(new_info)
+
+if shell == 'bash':
+	print 'PATH="%s"; export PATH; MANPATH="%s"; export MANPATH; INFOPATH="%s"; export INFOPATH' % (':'.join(path), ':'.join(manpath), ':'.join(infopath))
+elif shell == 'csh':
+	print 'setenv PATH "%s"; setenv MANPATH "%s"; setenv INFOPATH "%s"' % (':'.join(path), ':'.join(manpath), ':'.join(infopath))
Index: /trunk/source/pyhesiodfs/hesiod.py
===================================================================
--- /trunk/source/pyhesiodfs/hesiod.py (revision 38)
+++ /trunk/source/pyhesiodfs/hesiod.py (revision 38)
@@ -0,0 +1,61 @@
+import DNS
+
+DNS.DiscoverNameServers()
+
+dnsreq = DNS.Request(qtype="txt")
+
+class HesiodParseError(Exception):
+    pass
+
+class HesiodLookup:
+    """A generic Hesiod lookup"""
+    def __init__(self, name, type, realm="athena.mit.edu"):
+        if "@" in name:
+            name, realm = name.rsplit("@", 1)
+        self.dnsname = ("%s.%s.ns.%s" % (name, type, realm))
+        self.dnsresult = dnsreq.req(name=self.dnsname)
+        self.parseRecords()
+    def parseRecords(self):
+        self.entries = []
+        for answer in self.dnsresult.answers:
+            if answer['name'] == self.dnsname:
+                if isinstance(answer['data'],list):
+                    self.entries.extend(answer['data'])
+                else:
+                    self.entries.append(answer['data'])
+    def getRawEntries(self):
+        return self.entries
+
+class FilsysLookup(HesiodLookup):
+    def __init__(self, name, realm="athena.mit.edu"):
+        HesiodLookup.__init__(self, name, "filsys", realm)
+    def parseRecords(self):
+        HesiodLookup.parseRecords(self)
+        self.filsysPointers = []
+        if len(self.entries) > 1:
+            multiRecords = True
+        else:
+            multiRecords = False
+        for entry in self.entries:
+            priority = 0
+            if multiRecords:
+                entry, priority = entry.rsplit(" ", 1)
+                priority = int(priority)
+            parts = entry.split(" ")
+            type = parts[0]
+            if type == 'AFS':
+                self.filsysPointers.append({'type': type, 'location': parts[1], 'mode': parts[2], 'mountpoint': parts[3], 'priority': priority})
+            elif type == 'NFS':
+                self.filsysPointers.append({'type': type, 'remote_location': parts[1], 'server': parts[2], 'mode': parts[3], 'mountpoint': parts[4], 'priority': priority})
+            elif type == 'ERR':
+                parts = entry.split(" ", 1)
+                self.filsysPointers.append({'type': type, 'message': parts[1], 'priority': priority})
+            elif type == 'UFS':
+                self.filsysPointers.append({'type': type, 'device': parts[1], 'mode': parts[2], 'mountpoint': parts[3], 'priority': priority})
+            elif type == 'LOC':
+                self.filsysPointers.append({'type': type, 'location': parts[1], 'mode': parts[2], 'mountpoint': parts[3], 'priority': priority})
+            else:
+                raise HesiodParseError("Unknown filsys type: "+type)
+        self.filsysPointers.sort(key=lambda x: x['priority'])
+    def getFilsys(self):
+        return self.filsysPointers
Index: /trunk/source/pyhesiodfs/pyHesiodFS.py
===================================================================
--- /trunk/source/pyhesiodfs/pyHesiodFS.py (revision 41)
+++ /trunk/source/pyhesiodfs/pyHesiodFS.py (revision 41)
@@ -0,0 +1,140 @@
+#!/usr/bin/python2.5
+
+#    pyHesiodFS:
+#    Copyright (C) 2007  Quentin Smith <quentin@mit.edu>
+#    "Hello World" pyFUSE example:
+#    Copyright (C) 2006  Andrew Straw  <strawman@astraw.com>
+#
+#    This program can be distributed under the terms of the GNU LGPL.
+#    See the file COPYING.
+#
+
+import sys, os, stat, errno
+import fuse
+from fuse import Fuse
+
+import hesiod
+
+if not hasattr(fuse, '__version__'):
+    raise RuntimeError, \
+        "your fuse-py doesn't know of fuse.__version__, probably it's too old."
+
+fuse.fuse_python_api = (0, 2)
+
+hello_path = '/README.txt'
+hello_str = """This is the pyhesiodfs FUSE autmounter. To access a Hesiod filsys, just access
+%(mountpoint)s/name.
+
+If you're using the Finder, try pressing Cmd+Shift+G and then entering
+%(mountpoint)s/name"""
+
+class MyStat(fuse.Stat):
+    def __init__(self):
+        self.st_mode = 0
+        self.st_ino = 0
+        self.st_dev = 0
+        self.st_nlink = 0
+        self.st_uid = 0
+        self.st_gid = 0
+        self.st_size = 0
+        self.st_atime = 0
+        self.st_mtime = 0
+        self.st_ctime = 0
+
+class PyHesiodFS(Fuse):
+
+    def __init__(self, *args, **kwargs):
+        Fuse.__init__(self, *args, **kwargs)
+        self.fuse_args.add("allow_other", True)
+        self.fuse_args.add("noappledouble", True)
+        self.fuse_args.add("noapplexattr", True)
+        self.fuse_args.add("fsname", "pyHesiodFS")
+        self.fuse_args.add("volname", "MIT")
+        self.mounts = {}
+    
+    def getattr(self, path):
+        st = MyStat()
+        if path == '/':
+            st.st_mode = stat.S_IFDIR | 0755
+            st.st_nlink = 2
+        elif path == hello_path:
+            st.st_mode = stat.S_IFREG | 0444
+            st.st_nlink = 1
+            st.st_size = len(hello_str)
+        elif '/' not in path[1:]:
+            if self.findLocker(path[1:]):
+                st.st_mode = stat.S_IFLNK | 0777
+                st.st_nlink = 1
+                st.st_size = len(self.findLocker(path[1:]))
+            else:
+                return -errno.ENOENT
+        else:
+            return -errno.ENOENT
+        return st
+
+    def getCachedLockers(self):
+        return self.mounts.keys()
+
+    def findLocker(self, name):
+        """Lookup a locker in hesiod and return its path"""
+        if name in self.mounts:
+            return self.mounts[name]
+        else:
+            filsys = hesiod.FilsysLookup(name)
+            # FIXME check if the first locker is valid
+            if len(filsys.getFilsys()) >= 1:
+                pointers = filsys.getFilsys()
+                pointer = pointers[0]
+                if pointer['type'] != 'AFS' and pointer['type'] != 'LOC':
+                    print >>sys.stderr, "Unknown locker type "+pointer.type+" for locker "+name+" ("+repr(pointer)+" )"
+                    return None
+                else:
+                    self.mounts[name] = pointer['location']
+                    print >>sys.stderr, "Mounting "+name+" on "+pointer['location']
+                    return pointer['location']
+            else:
+                print >>sys.stderr, "Couldn't find filsys for "+name
+                return None
+
+    def readdir(self, path, offset):
+        for r in  ['.', '..', hello_path[1:]]+self.getCachedLockers():
+            yield fuse.Direntry(r)
+            
+    def readlink(self, path):
+        return self.findLocker(path[1:])
+
+    def open(self, path, flags):
+        if path != hello_path:
+            return -errno.ENOENT
+        accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
+        if (flags & accmode) != os.O_RDONLY:
+            return -errno.EACCES
+
+    def read(self, path, size, offset):
+        if path != hello_path:
+            return -errno.ENOENT
+        slen = len(hello_str)
+        if offset < slen:
+            if offset + size > slen:
+                size = slen - offset
+            buf = hello_str[offset:offset+size]
+        else:
+            buf = ''
+        return buf
+
+def main():
+    global hello_str
+    usage="""
+pyHesiodFS
+
+""" + Fuse.fusage
+    server = PyHesiodFS(version="%prog " + fuse.__version__,
+                     usage=usage,
+                     dash_s_do='setsingle')
+
+    server.parse(errex=1)
+    hello_str = hello_str % {'mountpoint': server.parse(errex=1).mountpoint}
+    server.main()
+
+if __name__ == '__main__':
+    main()
Index: /trunk/source/pyhesiodfs/configure.in
===================================================================
--- /trunk/source/pyhesiodfs/configure.in (revision 39)
+++ /trunk/source/pyhesiodfs/configure.in (revision 39)
@@ -0,0 +1,5 @@
+AC_INIT()
+
+AC_PROG_CC
+
+AC_OUTPUT(edu.mit.sipb.mit-automounter.plist)
Index: /trunk/source/pyhesiodfs/setup.py
===================================================================
--- /trunk/source/pyhesiodfs/setup.py (revision 38)
+++ /trunk/source/pyhesiodfs/setup.py (revision 38)
@@ -0,0 +1,8 @@
+from distutils.core import setup
+setup(name='pyHesiodFS',
+      version='1.0',
+      author='Quentin Smith',
+      author_email='pyhesiodfs@mit.edu',
+      py_modules=['hesiod'],
+      scripts=['pyHesiodFS.py'],
+      data_files=[('/Library/LaunchDaemons', ('edu.mit.sipb.mit-automounter.plist',))])
Index: /trunk/source/pyhesiodfs/edu.mit.sipb.mit-automounter.plist.in
===================================================================
--- /trunk/source/pyhesiodfs/edu.mit.sipb.mit-automounter.plist.in (revision 39)
+++ /trunk/source/pyhesiodfs/edu.mit.sipb.mit-automounter.plist.in (revision 39)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>Label</key>
+	<string>edu.mit.sipb.mit-automounter</string>
+	<key>OnDemand</key>
+	<false/>
+	<key>ProgramArguments</key>
+	<array>
+		<string>@prefix@/bin/pyHesiodFS.py</string>
+		<string>-f</string>
+		<string>/mit</string>
+	</array>
+	<key>RunAtLoad</key>
+	<true/>
+	<key>StandardErrorPath</key>
+	<string>@prefix@/var/log/mit-automounter.log</string>
+</dict>
+</plist>