from __future__ import with_statement
+from auto import HOME
+import errno as E
import itertools as I
import os as OS; ENV = OS.environ
###--------------------------------------------------------------------------
### Utilities.
-def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args):
+def fill_in_fields(fno_user, fno_passwd, fno_map,
+ user, passwd, args, defaults = None):
"""
Return a vector of filled-in fields.
is a sequence of (NAME, POS) pairs. The USER and PASSWD arguments give the
actual user name and password values; ARGS are the remaining arguments,
maybe in the form `NAME=VALUE'.
+
+ If DEFAULTS is given, it is a fully-filled-in sequence of records. The
+ user name and password are taken from the DEFAULTS if USER and PASSWD are
+ `None' on entry; they cannot be set from ARGS.
"""
## Prepare the result vector, and set up some data structures.
if fno_user >= n or fno_passwd >= n: ok = False
for k, i in fno_map:
fmap[k] = i
- rmap[i] = "`%s'" % k
+ if i in rmap: ok = False
+ else: rmap[i] = "`%s'" % k
if i >= n: ok = False
if not ok:
raise U.ExpectedError, \
## Prepare the new record's fields.
f = [None]*n
- f[fno_user] = user
- f[fno_passwd] = passwd
+ if user is not None: f[fno_user] = user
+ elif defaults is not None: f[no_user] = defaults[no_user]
+ else: raise U.ExpectedError, (500, "No user name given")
+ if passwd is not None: f[fno_passwd] = passwd
+ elif defaults is not None: f[no_passwd] = defaults[no_passwd]
+ else: raise U.ExpectedError, (500, "No password given")
for a in args:
if '=' in a:
raise U.ExpectedError, (400, "Field %s is already set" % rmap[i])
f[i] = v
- ## Check that the vector of fields is properly set up.
+ ## Check that the vector of fields is properly set up. Copy unset values
+ ## from the defaults if they're available.
for i in xrange(n):
- if f[i] is None:
- raise U.ExpectedError, (500, "Field %s is unset" % rmap[i])
+ if f[i] is not None: pass
+ elif defaults is not None: f[i] = defaults[i]
+ else: raise U.ExpectedError, (500, "Field %s is unset" % rmap[i])
## Done.
return f
is careful to do this).
"""
- def __init__(me, line, delim, fmap, *args, **kw):
+ def __init__(me, line, delim, fno_user, fno_passwd, fmap, *args, **kw):
"""
Initialize the record, splitting the LINE into fields separated by DELIM,
and setting attributes under control of FMAP.
line = line.rstrip('\n')
fields = line.split(delim)
me._delim = delim
+ me._fno_user = fno_user
+ me._fno_passwd = fno_passwd
me._fmap = fmap
me._raw = fields
for k, v in fmap.iteritems():
fields[v] = val
return me._delim.join(fields) + '\n'
+ def edit(me, args):
+ """
+ Modify the record as described by the ARGS.
+
+ Fields other than the user name and password can be modified.
+ """
+ ff = fill_in_fields(
+
class FlatFileBackend (object):
"""
Password storage in a flat passwd(5)-style file.
specified by the DELIM constructor argument.
The file is updated by writing a new version alongside, as `FILE.new', and
- renaming it over the old version. If a LOCK file is named then an
- exclusive fcntl(2)-style lock is taken out on `LOCKDIR/LOCK' (creating the
- file if necessary) during the update operation. Use of a lockfile is
- strongly recommended.
+ renaming it over the old version. If a LOCK is provided then this is done
+ while holding a lock. By default, an exclusive fcntl(2)-style lock is
+ taken out on `LOCKDIR/LOCK' (creating the file if necessary) during the
+ update operation, but subclasses can override the `dolocked' method to
+ provide alternative locking behaviour; the LOCK parameter is not
+ interpreted by any other methods. Use of a lockfile is strongly
+ recommended.
The DELIM constructor argument specifies the delimiter character used when
splitting lines into fields. The USER and PASSWD arguments give the field
## If there's a lockfile, then acquire it around the meat of this
## function; otherwise just do the job.
- if me._lock is None:
- doit()
- else:
- with U.lockfile(OS.path.join(CFG.LOCKDIR, me._lock), 5):
- doit()
+ if me._lock is None: doit()
+ else: me.dolocked(me._lock, doit)
+
+ def dolocked(me, lock, func):
+ """
+ Call FUNC with the LOCK held.
+
+ Subclasses can override this method in order to provide alternative
+ locking functionality.
+ """
+ try: OS.mkdir(CFG.LOCKDIR)
+ except OSError, e:
+ if e.errno != E.EEXIST: raise
+ with U.lockfile(OS.path.join(CFG.LOCKDIR, lock), 5):
+ func()
def _parse(me, line):
"""Convenience function for constructing a record."""