Remove leftover backup files
[time-slider.git] / usr / share / time-slider / lib / time_slider / zfs.py~
diff --git a/usr/share/time-slider/lib/time_slider/zfs.py~ b/usr/share/time-slider/lib/time_slider/zfs.py~
deleted file mode 100755 (executable)
index b35e58d..0000000
+++ /dev/null
@@ -1,1049 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-
-import subprocess
-import re
-import threading
-from bisect import insort, bisect_left, bisect_right
-
-import util
-
-BYTESPERMB = 1048576
-
-# Commonly used command paths
-PFCMD = "/usr/bin/pfexec"
-ZFSCMD = "/usr/sbin/zfs"
-ZPOOLCMD = "/usr/sbin/zpool"
-
-
-class Datasets(Exception):
-    """
-    Container class for all zfs datasets. Maintains a centralised
-    list of datasets (generated on demand) and accessor methods. 
-    Also allows clients to notify when a refresh might be necessary.
-    """
-    # Class wide instead of per-instance in order to avoid duplication
-    filesystems = None
-    volumes = None
-    snapshots = None
-    
-    # Mutex locks to prevent concurrent writes to above class wide
-    # dataset lists.
-    _filesystemslock = threading.Lock()
-    _volumeslock = threading.Lock()
-    snapshotslock = threading.Lock()
-
-    def create_auto_snapshot_set(self, label, tag = None):
-        """
-        Create a complete set of snapshots as if this were
-        for a standard zfs-auto-snapshot operation.
-        
-        Keyword arguments:
-        label:
-            A label to apply to the snapshot name. Cannot be None.
-        tag:
-            A string indicating one of the standard auto-snapshot schedules
-            tags to check (eg. "frequent" for will map to the tag:
-            com.sun:auto-snapshot:frequent). If specified as a zfs property
-            on a zfs dataset, the property corresponding to the tag will 
-            override the wildcard property: "com.sun:auto-snapshot"
-            Default value = None
-        """
-        everything = []
-        included = []
-        excluded = []
-        single = []
-        recursive = []
-        finalrecursive = []
-
-        # Get auto-snap property in two passes. First with the schedule
-        # specific tag override value, then with the general property value
-        cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume",
-               "-o", "name,com.sun:auto-snapshot", "-s", "name"]
-        if tag:
-            overrideprop = "com.sun:auto-snapshot:" + tag
-            scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume",
-                    "-o", "name," + overrideprop, "-s", "name"]
-            outdata,errdata = util.run_command(scmd)
-            for line in outdata.rstrip().split('\n'):
-                line = line.split()
-                # Skip over unset values. 
-                if line[1] == "-":
-                    continue
-                # Add to everything list. This is used later
-                # for identifying parents/children of a given
-                # filesystem or volume.
-                everything.append(line[0])
-                if line[1] == "true":
-                    included.append(line[0])
-                elif line[1] == "false":
-                    excluded.append(line[0])
-        # Now use the general property. If no value
-        # was set in the first pass, we set it here.
-        outdata,errdata = util.run_command(cmd)
-        for line in outdata.rstrip().split('\n'):
-            line = line.split()
-            idx = bisect_right(everything, line[0])
-            if len(everything) == 0 or \
-               everything[idx-1] != line[0]:           
-                # Dataset is neither included nor excluded so far
-                if line[1] == "-":
-                    continue
-                everything.insert(idx, line[0])
-                if line[1] == "true":
-                    included.insert(0, line[0])
-                elif line[1] == "false":
-                    excluded.append(line[0])
-
-        # Now figure out what can be recursively snapshotted and what
-        # must be singly snapshotted. Single snapshot restrictions apply
-        # to those datasets who have a child in the excluded list.
-        # 'included' is sorted in reverse alphabetical order. 
-        for datasetname in included:
-            excludedchild = False
-            idx = bisect_right(everything, datasetname)
-            children = [name for name in everything[idx:] if \
-                        name.find(datasetname) == 0]
-            for child in children:
-                idx = bisect_left(excluded, child)
-                if idx < len(excluded) and excluded[idx] == child:
-                    excludedchild = True
-                    single.append(datasetname)
-                    break
-            if excludedchild == False:
-                # We want recursive list sorted in alphabetical order
-                # so insert instead of append to the list.
-                recursive.insert(0, datasetname)
-
-        for datasetname in recursive:
-            parts = datasetname.rsplit('/', 1)
-            parent = parts[0]
-            if parent == datasetname:
-                # Root filesystem of the Zpool, so
-                # this can't be inherited and must be
-                # set locally.
-                finalrecursive.append(datasetname)
-                continue
-            idx = bisect_right(recursive, parent)
-            if len(recursive) > 0 and \
-               recursive[idx-1] == parent:
-                # Parent already marked for recursive snapshot: so skip
-                continue
-            else:
-                finalrecursive.append(datasetname)
-
-        for name in finalrecursive:
-            dataset = ReadWritableDataset(name)
-            dataset.create_snapshot(label, True)
-        for name in single:
-            dataset = ReadWritableDataset(name)
-            dataset.create_snapshot(label, False)
-
-    def list_auto_snapshot_sets(self, tag = None):
-        """
-        Returns a list of zfs filesystems and volumes tagged with
-        the "com.sun:auto-snapshot" property set to "true", either
-        set locally or inherited. Snapshots are excluded from the
-        returned result.
-
-        Keyword Arguments:
-        tag:
-            A string indicating one of the standard auto-snapshot schedules
-            tags to check (eg. "frequent" will map to the tag:
-            com.sun:auto-snapshot:frequent). If specified as a zfs property
-            on a zfs dataset, the property corresponding to the tag will 
-            override the wildcard property: "com.sun:auto-snapshot"
-            Default value = None
-        """
-        #Get auto-snap property in two passes. First with the global
-        #value, then overriding with the label/schedule specific value
-
-        included = []
-        excluded = []
-
-        cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume",
-               "-o", "name,com.sun:auto-snapshot", "-s", "name"]
-        if tag:
-            overrideprop = "com.sun:auto-snapshot:" + tag
-            scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume",
-                    "-o", "name," + overrideprop, "-s", "name"]
-            outdata,errdata = util.run_command(scmd)
-            for line in outdata.rstrip().split('\n'):
-                line = line.split()
-                if line[1] == "true":
-                    included.append(line[0])
-                elif line[1] == "false":
-                    excluded.append(line[0])
-        outdata,errdata = util.run_command(cmd)
-        for line in outdata.rstrip().split('\n'):
-            line = line.split()
-            # Only set values that aren't already set. Don't override
-            try:
-                included.index(line[0])
-                continue
-            except ValueError:
-                try:
-                    excluded.index(line[0])
-                    continue
-                except ValueError:
-                    # Dataset is not listed in either list.
-                    if line[1] == "true":
-                        included.append(line[0])
-        return included
-
-    def list_filesystems(self, pattern = None):
-        """
-        List pattern matching filesystems sorted by name.
-        
-        Keyword arguments:
-        pattern -- Filter according to pattern (default None)
-        """
-        filesystems = []
-        # Need to first ensure no other thread is trying to
-        # build this list at the same time.
-        Datasets._filesystemslock.acquire()
-        if Datasets.filesystems == None:
-            Datasets.filesystems = []
-            cmd = [ZFSCMD, "list", "-H", "-t", "filesystem", \
-                   "-o", "name,mountpoint", "-s", "name"]
-            try:
-                p = subprocess.Popen(cmd,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.PIPE,
-                                     close_fds=True)
-                outdata,errdata = p.communicate()
-                err = p.wait()
-            except OSError, message:
-                raise RuntimeError, "%s subprocess error:\n %s" % \
-                                    (cmd, str(message))
-            if err != 0:
-                Datasets._filesystemslock.release()
-                raise RuntimeError, '%s failed with exit code %d\n%s' % \
-                                    (str(cmd), err, errdata)
-            for line in outdata.rstrip().split('\n'):
-                line = line.rstrip().split()
-                Datasets.filesystems.append([line[0], line[1]])
-        Datasets._filesystemslock.release()
-
-        if pattern == None:
-            filesystems = Datasets.filesystems[:]
-        else:
-            # Regular expression pattern to match "pattern" parameter.
-            regexpattern = ".*%s.*" % pattern
-            patternobj = re.compile(regexpattern)
-
-            for fsname,fsmountpoint in Datasets.filesystems:
-                patternmatchobj = re.match(patternobj, fsname)
-                if patternmatchobj != None:
-                    filesystems.append(fsname, fsmountpoint)
-        return filesystems
-
-    def list_volumes(self, pattern = None):
-        """
-        List pattern matching volumes sorted by name.
-        
-        Keyword arguments:
-        pattern -- Filter according to pattern (default None)
-        """
-        volumes = []
-        Datasets._volumeslock.acquire()
-        if Datasets.volumes == None:
-            Datasets.volumes = []
-            cmd = [ZFSCMD, "list", "-H", "-t", "volume", \
-                   "-o", "name", "-s", "name"]
-            try:
-                p = subprocess.Popen(cmd,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.PIPE,
-                                     close_fds=True)
-                outdata,errdata = p.communicate()
-                err = p.wait()
-            except OSError, message:
-                raise RuntimeError, "%s subprocess error:\n %s" % \
-                                    (cmd, str(message))
-            if err != 0:
-                Datasets._volumeslock.release()
-                raise RuntimeError, '%s failed with exit code %d\n%s' % \
-                                    (str(cmd), err, errdata)
-            for line in outdata.rstrip().split('\n'):
-                Datasets.volumes.append(line.rstrip())
-        Datasets._volumeslock.release()
-
-        if pattern == None:
-            volumes = Datasets.volumes[:]
-        else:
-            # Regular expression pattern to match "pattern" parameter.
-            regexpattern = ".*%s.*" % pattern
-            patternobj = re.compile(regexpattern)
-
-            for volname in Datasets.volumes:
-                patternmatchobj = re.match(patternobj, volname)
-                if patternmatchobj != None:
-                    volumes.append(volname)
-        return volumes
-
-    def list_snapshots(self, pattern = None):
-        """
-        List pattern matching snapshots sorted by creation date.
-        Oldest listed first
-        
-        Keyword arguments:
-        pattern -- Filter according to pattern (default None)
-        """
-        snapshots = []
-        Datasets.snapshotslock.acquire()
-        if Datasets.snapshots == None:
-            Datasets.snapshots = []
-            snaps = []
-            cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value,name", "creation"]
-            try:
-                p = subprocess.Popen(cmd,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.PIPE,
-                                     close_fds=True)
-                outdata,errdata = p.communicate()
-                err= p.wait()
-            except OSError, message:
-                Datasets.snapshotslock.release()
-                raise RuntimeError, "%s subprocess error:\n %s" % \
-                                    (cmd, str(message))
-            if err != 0:
-                Datasets.snapshotslock.release()
-                raise RuntimeError, '%s failed with exit code %d\n%s' % \
-                                    (str(cmd), err, errdata)
-            for dataset in outdata.rstrip().split('\n'):
-                if re.search("@", dataset):
-                    insort(snaps, dataset.split())
-            for snap in snaps:
-                Datasets.snapshots.append([snap[1], long(snap[0])])
-        if pattern == None:
-            snapshots = Datasets.snapshots[:]
-        else:
-            # Regular expression pattern to match "pattern" parameter.
-            regexpattern = ".*@.*%s" % pattern
-            patternobj = re.compile(regexpattern)
-
-            for snapname,snaptime in Datasets.snapshots:
-                patternmatchobj = re.match(patternobj, snapname)
-                if patternmatchobj != None:
-                    snapshots.append([snapname, snaptime])
-        Datasets.snapshotslock.release()
-        return snapshots
-
-    def list_cloned_snapshots(self):
-        """
-        Returns a list of snapshots that have cloned filesystems
-        dependent on them.
-        Snapshots with cloned filesystems can not be destroyed
-        unless dependent cloned filesystems are first destroyed.
-        """
-        cmd = [ZFSCMD, "list", "-H", "-o", "origin"]
-        outdata,errdata = util.run_command(cmd)
-        result = []
-        for line in outdata.rstrip().split('\n'):
-            details = line.rstrip()
-            if details != "-":
-                try:
-                    result.index(details)
-                except ValueError:
-                    result.append(details)
-        return result
-
-    def list_held_snapshots(self):
-        """
-        Returns a list of snapshots that have a "userrefs"
-        property value of greater than 0. Resul list is
-        sorted in order of creation time. Oldest listed first.
-        """
-        cmd = [ZFSCMD, "list", "-H",
-               "-t", "snapshot",
-               "-s", "creation",
-               "-o", "userrefs,name"]
-        outdata,errdata = util.run_command(cmd)
-        result = []
-        for line in outdata.rstrip().split('\n'):
-            details = line.split()
-            if details[0] != "0":
-                result.append(details[1])
-        return result
-
-    def refresh_snapshots(self):
-        """
-        Should be called when snapshots have been created or deleted
-        and a rescan should be performed. Rescan gets deferred until
-        next invocation of zfs.Dataset.list_snapshots()
-        """
-        # FIXME in future.
-        # This is a little sub-optimal because we should be able to modify
-        # the snapshot list in place in some situations and regenerate the 
-        # snapshot list without calling out to zfs(1m). But on the
-        # pro side, we will pick up any new snapshots since the last
-        # scan that we would be otherwise unaware of.
-        Datasets.snapshotslock.acquire()
-        Datasets.snapshots = None
-        Datasets.snapshotslock.release()
-
-
-class ZPool:
-    """
-    Base class for ZFS storage pool objects
-    """
-    def __init__(self, name):
-        self.name = name
-        self.health = self.__get_health()
-        self.__datasets = Datasets()
-        self.__filesystems = None
-        self.__volumes = None
-        self.__snapshots = None
-
-    def __get_health(self):
-        """
-        Returns pool health status: 'ONLINE', 'DEGRADED' or 'FAULTED'
-        """
-        cmd = [ZPOOLCMD, "list", "-H", "-o", "health", self.name]
-        outdata,errdata = util.run_command(cmd)
-        result = outdata.rstrip()
-        return result
-
-    def get_capacity(self):
-        """
-        Returns the percentage of total pool storage in use.
-        Calculated based on the "used" and "available" properties
-        of the pool's top-level filesystem because the values account
-        for reservations and quotas of children in their calculations,
-        giving a more practical indication of how much capacity is used
-        up on the pool.
-        """
-        if self.health == "FAULTED":
-            raise ZPoolFaultedError("Can not determine capacity of zpool: %s" \
-                                    "because it is in a FAULTED state" \
-                                    % (self.name))
-
-        cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", \
-               "used,available", self.name]
-        outdata,errdata = util.run_command(cmd)
-        _used,_available = outdata.rstrip().split('\n')
-        used = float(_used)
-        available = float(_available) 
-        return 100.0 * used/(used + available)
-
-    def get_available_size(self):
-        """
-        How much unused space is available for use on this Zpool.
-        Answer in bytes.
-        """
-        # zpool(1) doesn't report available space in
-        # units suitable for calulations but zfs(1)
-        # can so use it to find the value for the
-        # filesystem matching the pool.
-        # The root filesystem of the pool is simply
-        # the pool name.
-        poolfs = Filesystem(self.name)
-        avail = poolfs.get_available_size()
-        return avail
-
-    def get_used_size(self):
-        """
-        How much space is in use on this Zpool.
-        Answer in bytes
-        """
-        # Same as ZPool.get_available_size(): zpool(1)
-        # doesn't generate suitable out put so use
-        # zfs(1) on the toplevel filesystem
-        if self.health == "FAULTED":
-            raise ZPoolFaultedError("Can not determine used size of zpool: %s" \
-                                    "because it is in a FAULTED state" \
-                                    % (self.name))
-        poolfs = Filesystem(self.name)
-        used = poolfs.get_used_size()
-        return used
-
-    def list_filesystems(self):
-        """
-        Return a list of filesystems on this Zpool.
-        List is sorted by name.
-        """
-        if self.__filesystems == None:
-            result = []
-            # Provides pre-sorted filesystem list
-            for fsname,fsmountpoint in self.__datasets.list_filesystems():
-                if re.match(self.name, fsname):
-                    result.append([fsname, fsmountpoint])
-            self.__filesystems = result
-        return self.__filesystems
-
-    def list_volumes(self):
-        """
-        Return a list of volumes (zvol) on this Zpool
-        List is sorted by name
-        """
-        if self.__volumes == None:
-            result = []
-            regexpattern = "^%s" % self.name
-            patternobj = re.compile(regexpattern)
-            for volname in self.__datasets.list_volumes():
-                patternmatchobj = re.match(patternobj, volname)
-                if patternmatchobj != None:
-                    result.append(volname)
-            result.sort()
-            self.__volumes = result
-        return self.__volumes
-
-    def list_auto_snapshot_sets(self, tag = None):
-        """
-        Returns a list of zfs filesystems and volumes tagged with
-        the "com.sun:auto-snapshot" property set to "true", either
-        set locally or inherited. Snapshots are excluded from the
-        returned result. Results are not sorted.
-
-        Keyword Arguments:
-        tag:
-            A string indicating one of the standard auto-snapshot schedules
-            tags to check (eg. "frequent" will map to the tag:
-            com.sun:auto-snapshot:frequent). If specified as a zfs property
-            on a zfs dataset, the property corresponding to the tag will 
-            override the wildcard property: "com.sun:auto-snapshot"
-            Default value = None
-        """
-        result = []
-        allsets = self.__datasets.list_auto_snapshot_sets(tag)
-        if len(allsets) == 0:
-            return result
-
-        regexpattern = "^%s" % self.name
-        patternobj = re.compile(regexpattern)
-        for datasetname in allsets:
-            patternmatchobj = re.match(patternobj, datasetname)
-            if patternmatchobj != None:
-                result.append(datasetname)
-        return result
-
-    def list_snapshots(self, pattern = None):
-        """
-        List pattern matching snapshots sorted by creation date.
-        Oldest listed first
-           
-        Keyword arguments:
-        pattern -- Filter according to pattern (default None)   
-        """
-        # If there isn't a list of snapshots for this dataset
-        # already, create it now and store it in order to save
-        # time later for potential future invocations.
-        Datasets.snapshotslock.acquire()
-        if Datasets.snapshots == None:
-            self.__snapshots = None
-        Datasets.snapshotslock.release()
-        if self.__snapshots == None:
-            result = []
-            regexpattern = "^%s.*@"  % self.name
-            patternobj = re.compile(regexpattern)
-            for snapname,snaptime in self.__datasets.list_snapshots():
-                patternmatchobj = re.match(patternobj, snapname)
-                if patternmatchobj != None:
-                    result.append([snapname, snaptime])
-            # Results already sorted by creation time
-            self.__snapshots = result
-        if pattern == None:
-            return self.__snapshots
-        else:
-            snapshots = []
-            regexpattern = "^%s.*@.*%s" % (self.name, pattern)
-            patternobj = re.compile(regexpattern)
-            for snapname,snaptime in self.__snapshots:
-                patternmatchobj = re.match(patternobj, snapname)
-                if patternmatchobj != None:
-                    snapshots.append([snapname, snaptime])
-            return snapshots
-
-    def __str__(self):
-        return_string = "ZPool name: " + self.name
-        return_string = return_string + "\n\tHealth: " + self.health
-        try:
-            return_string = return_string + \
-                            "\n\tUsed: " + \
-                            str(self.get_used_size()/BYTESPERMB) + "Mb"
-            return_string = return_string + \
-                            "\n\tAvailable: " + \
-                            str(self.get_available_size()/BYTESPERMB) + "Mb"
-            return_string = return_string + \
-                            "\n\tCapacity: " + \
-                            str(self.get_capacity()) + "%"
-        except ZPoolFaultedError:
-            pass
-        return return_string
-
-
-class ReadableDataset:
-    """
-    Base class for Filesystem, Volume and Snapshot classes
-    Provides methods for read only operations common to all.
-    """
-    def __init__(self, name, creation = None):
-        self.name = name
-        self.__creationTime = creation
-        self.datasets = Datasets()
-
-    def __str__(self):
-        return_string = "ReadableDataset name: " + self.name + "\n"
-        return return_string
-
-    def get_creation_time(self):
-        if self.__creationTime == None:
-            cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "creation",
-                   self.name]
-            outdata,errdata = util.run_command(cmd)
-            self.__creationTime = long(outdata.rstrip())
-        return self.__creationTime
-
-    def exists(self):
-        """
-        Returns True if the dataset is still existent on the system.
-        False otherwise
-        """
-        # Test existance of the dataset by checking the output of a 
-        # simple zfs get command on the snapshot
-        cmd = [ZFSCMD, "get", "-H", "-o", "name", "type", self.name]
-        try:
-            p = subprocess.Popen(cmd,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE,
-                                 close_fds=True)
-            outdata,errdata = p.communicate()
-            err = p.wait()
-        except OSError, message:
-            raise RuntimeError, "%s subprocess error:\n %s" % \
-                            (command, str(message))
-        if err != 0:
-            # Doesn't exist
-            return False
-
-        result = outdata.rstrip()
-        if result == self.name:
-            return True
-        else:
-            return False
-
-    def get_used_size(self):
-        cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "used", self.name]
-        outdata,errdata = util.run_command(cmd)
-        return long(outdata.rstrip())
-
-    def get_user_property(self, prop, local=False):
-        if local == True:
-            cmd = [ZFSCMD, "get", "-s", "local", "-H", "-o", "value", prop, self.name]
-        else:
-            cmd = [ZFSCMD, "get", "-H", "-o", "value", prop, self.name]
-        outdata,errdata = util.run_command(cmd)
-        return outdata.rstrip()
-
-    def set_user_property(self, prop, value):
-        cmd = [PFCMD, ZFSCMD, "set", "%s=%s" % (prop, value), self.name]
-        outdata,errdata = util.run_command(cmd)
-    
-    def unset_user_property(self, prop):
-        cmd = [PFCMD, ZFSCMD, "inherit", prop, self.name]
-        outdata,errdata = util.run_command(cmd)
-
-class Snapshot(ReadableDataset):
-    """
-    ZFS Snapshot object class.
-    Provides information and operations specfic to ZFS snapshots
-    """    
-    def __init__(self, name, creation = None):
-        """
-        Keyword arguments:
-        name -- Name of the ZFS snapshot
-        creation -- Creation time of the snapshot if known (Default None)
-        """
-        ReadableDataset.__init__(self, name, creation)
-        self.fsname, self.snaplabel = self.__split_snapshot_name()
-        self.poolname = self.__get_pool_name()
-
-    def __get_pool_name(self):
-        name = self.fsname.split("/", 1)
-        return name[0]
-
-    def __split_snapshot_name(self):
-        name = self.name.split("@", 1)
-        # Make sure this is really a snapshot and not a
-        # filesystem otherwise a filesystem could get 
-        # destroyed instead of a snapshot. That would be
-        # really really bad.
-        if name[0] == self.name:
-            raise SnapshotError("\'%s\' is not a valid snapshot name" \
-                                % (self.name))
-        return name[0],name[1]
-
-    def get_referenced_size(self):
-        """
-        How much unique storage space is used by this snapshot.
-        Answer in bytes
-        """
-        cmd = [ZFSCMD, "get", "-H", "-p", \
-               "-o", "value", "referenced", \
-               self.name]
-        outdata,errdata = util.run_command(cmd)
-        return long(outdata.rstrip())
-
-    def list_children(self):
-        """Returns a recursive list of child snapshots of this snapshot"""
-        cmd = [ZFSCMD,
-               "list", "-t", "snapshot", "-H", "-r", "-o", "name",
-               self.fsname]
-        outdata,errdata = util.run_command(cmd)
-        result = []
-        for line in outdata.rstrip().split('\n'):
-            if re.search("@%s" % (self.snaplabel), line) and \
-                line != self.name:
-                    result.append(line)
-        return result
-
-    def has_clones(self):
-        """Returns True if the snapshot has any dependent clones"""
-        cmd = [ZFSCMD, "list", "-H", "-o", "origin,name"]
-        outdata,errdata = util.run_command(cmd)
-        for line in outdata.rstrip().split('\n'):
-            details = line.rstrip().split()
-            if details[0] == self.name and \
-                details[1] != '-':
-                return True
-        return False
-
-    def destroy(self, deferred=True):
-        """
-        Permanently remove this snapshot from the filesystem
-        Performs deferred destruction by default.
-        """
-        # Be sure it genuninely exists before trying to destroy it
-        if self.exists() == False:
-            return
-        if deferred == False:
-            cmd = [PFCMD, ZFSCMD, "destroy", self.name]
-        else:
-            cmd = [PFCMD, ZFSCMD, "destroy", "-d", self.name]
-
-        outdata,errdata = util.run_command(cmd)
-        # Clear the global snapshot cache so that a rescan will be
-        # triggered on the next call to Datasets.list_snapshots()
-        self.datasets.refresh_snapshots()
-
-    def hold(self, tag):
-        """
-        Place a hold on the snapshot with the specified "tag" string.
-        """
-        # FIXME - fails if hold is already held
-        # Be sure it genuninely exists before trying to place a hold
-        if self.exists() == False:
-            return
-
-        cmd = [PFCMD, ZFSCMD, "hold", tag, self.name]
-        outdata,errdata = util.run_command(cmd)
-
-    def holds(self):
-        """
-        Returns a list of user hold tags for this snapshot
-        """
-        cmd = [ZFSCMD, "holds", self.name]
-        results = []
-        outdata,errdata = util.run_command(cmd)
-
-        for line in outdata.rstrip().split('\n'):
-            if len(line) == 0:
-                continue
-            # The first line heading columns are  NAME TAG TIMESTAMP
-            # Filter that line out.
-            line = line.split()
-            if (line[0] != "NAME" and line[1] != "TAG"):
-                results.append(line[1])
-        return results
-
-    def release(self, tag,):
-        """
-        Release the hold on the snapshot with the specified "tag" string.
-        """
-        # FIXME raises exception if no hold exists.
-        # Be sure it genuninely exists before trying to destroy it
-        if self.exists() == False:
-            return
-
-        cmd = [PFCMD, ZFSCMD, "release", tag, self.name]
-
-        outdata,errdata = util.run_command(cmd)
-        # Releasing the snapshot might cause it get automatically
-        # deleted by zfs.
-        # Clear the global snapshot cache so that a rescan will be
-        # triggered on the next call to Datasets.list_snapshots()
-        self.datasets.refresh_snapshots()
-
-
-    def __str__(self):
-        return_string = "Snapshot name: " + self.name
-        return_string = return_string + "\n\tCreation time: " \
-                        + str(self.get_creation_time())
-        return_string = return_string + "\n\tUsed Size: " \
-                        + str(self.get_used_size())
-        return_string = return_string + "\n\tReferenced Size: " \
-                        + str(self.get_referenced_size())
-        return return_string
-
-
-class ReadWritableDataset(ReadableDataset):
-    """
-    Base class for ZFS filesystems and volumes.
-    Provides methods for operations and properties
-    common to both filesystems and volumes.
-    """
-    def __init__(self, name, creation = None):
-        ReadableDataset.__init__(self, name, creation)
-        self.__snapshots = None
-
-    def __str__(self):
-        return_string = "ReadWritableDataset name: " + self.name + "\n"
-        return return_string
-
-    def get_auto_snap(self, schedule = None):
-        if schedule:
-            cmd = [ZFSCMD, "get", "-H", "-o", "value", \
-               "com.sun:auto-snapshot", self.name]
-        cmd = [ZFSCMD, "get", "-H", "-o", "value", \
-               "com.sun:auto-snapshot", self.name]
-        outdata,errdata = util.run_command(cmd)
-        if outdata.rstrip() == "true":
-            return True
-        else:
-            return False
-
-    def get_available_size(self):
-        cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "available", \
-               self.name]
-        outdata,errdata = util.run_command(cmd)
-        return long(outdata.rstrip())
-
-    def create_snapshot(self, snaplabel, recursive = False):
-        """
-        Create a snapshot for the ReadWritable dataset using the supplied
-        snapshot label.
-
-        Keyword Arguments:
-        snaplabel:
-            A string to use as the snapshot label.
-            The bit that comes after the "@" part of the snapshot
-            name.
-        recursive:
-            Recursively snapshot childfren of this dataset.
-            Default = False
-        """
-        cmd = [PFCMD, ZFSCMD, "snapshot"]
-        if recursive == True:
-            cmd.append("-r")
-        cmd.append("%s@%s" % (self.name, snaplabel))
-        outdata,errdata = util.run_command(cmd, False)
-       if errdata:
-         print errdata
-        self.datasets.refresh_snapshots()
-
-    def list_children(self):
-        
-        # Note, if more dataset types ever come around they will
-        # need to be added to the filsystem,volume args below.
-        # Not for the forseeable future though.
-        cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem,volume",
-               "-o", "name", self.name]
-        outdata,errdata = util.run_command(cmd)
-        result = []
-        for line in outdata.rstrip().split('\n'):
-            if line.rstrip() != self.name:
-                result.append(line.rstrip())
-        return result
-
-
-    def list_snapshots(self, pattern = None):
-        """
-        List pattern matching snapshots sorted by creation date.
-        Oldest listed first
-           
-        Keyword arguments:
-        pattern -- Filter according to pattern (default None)   
-        """
-        # If there isn't a list of snapshots for this dataset
-        # already, create it now and store it in order to save
-        # time later for potential future invocations.
-        Datasets.snapshotslock.acquire()
-        if Datasets.snapshots == None:
-            self.__snapshots = None
-        Datasets.snapshotslock.release()
-        if self.__snapshots == None:
-            result = []
-            regexpattern = "^%s@" % self.name
-            patternobj = re.compile(regexpattern)
-            for snapname,snaptime in self.datasets.list_snapshots():
-                patternmatchobj = re.match(patternobj, snapname)
-                if patternmatchobj != None:
-                    result.append([snapname, snaptime])
-            # Results already sorted by creation time
-            self.__snapshots = result
-        if pattern == None:
-            return self.__snapshots
-        else:
-            snapshots = []
-            regexpattern = "^%s@.*%s" % (self.name, pattern)
-            patternobj = re.compile(regexpattern)
-            for snapname,snaptime in self.__snapshots:
-                patternmatchobj = re.match(patternobj, snapname)
-                if patternmatchobj != None:
-                    snapshots.append(snapname)
-            return snapshots
-
-    def set_auto_snap(self, include, inherit = False):
-        if inherit == True:
-            self.unset_user_property("com.sun:auto-snapshot")
-        else:
-            if include == True:
-                value = "true"
-            else:
-                value = "false"
-            self.set_user_property("com.sun:auto-snapshot", value)
-
-        return
-
-
-class Filesystem(ReadWritableDataset):
-    """ZFS Filesystem class"""
-    def __init__(self, name, mountpoint = None):
-        ReadWritableDataset.__init__(self, name)
-        self.__mountpoint = mountpoint
-
-    def __str__(self):
-        return_string = "Filesystem name: " + self.name + \
-                        "\n\tMountpoint: " + self.get_mountpoint() + \
-                        "\n\tMounted: " + str(self.is_mounted()) + \
-                        "\n\tAuto snap: " + str(self.get_auto_snap())
-        return return_string
-
-    def get_mountpoint(self):
-        if (self.__mountpoint == None):
-            cmd = [ZFSCMD, "get", "-H", "-o", "value", "mountpoint", \
-                   self.name]
-            outdata,errdata = util.run_command(cmd)
-            result = outdata.rstrip()
-            self.__mountpoint = result
-        return self.__mountpoint
-
-    def is_mounted(self):
-        cmd = [ZFSCMD, "get", "-H", "-o", "value", "mounted", \
-               self.name]
-        outdata,errdata = util.run_command(cmd)
-        result = outdata.rstrip()
-        if result == "yes":
-            return True
-        else:
-            return False
-
-    def list_children(self):
-        cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem", "-o", "name",
-               self.name]
-        outdata,errdata = util.run_command(cmd)
-        result = []
-        for line in outdata.rstrip().split('\n'):
-            if line.rstrip() != self.name:
-                result.append(line.rstrip())
-        return result
-
-
-class Volume(ReadWritableDataset):
-    """
-    ZFS Volume Class
-    This is basically just a stub and does nothing
-    unique from ReadWritableDataset parent class.
-    """
-    def __init__(self, name):
-        ReadWritableDataset.__init__(self, name)
-
-    def __str__(self):
-        return_string = "Volume name: " + self.name + "\n"
-        return return_string
-
-
-class ZFSError(Exception):
-    """Generic base class for ZPoolFaultedError and SnapshotError
-
-    Attributes:
-        msg -- explanation of the error
-    """
-    def __init__(self, msg):
-        self.msg = msg
-    def __str__(self):
-        return repr(self.msg)
-
-
-class ZPoolFaultedError(ZFSError):
-    """Exception raised for queries made against ZPools that
-       are in a FAULTED state
-
-    Attributes:
-        msg -- explanation of the error
-    """
-    def __init__(self, msg):
-        ZFSError.__init__(self, msg)
-
-
-class SnapshotError(ZFSError):
-    """Exception raised for invalid snapshot names provided to
-       Snapshot() constructor.
-
-    Attributes:
-        msg -- explanation of the error
-    """
-    def __init__(self, msg):
-        ZFSError.__init__(self, msg)
-
-
-def list_zpools():
-    """Returns a list of all zpools on the system"""
-    result = []
-    cmd = [ZPOOLCMD, "list", "-H", "-o", "name"]
-    outdata,errdata = util.run_command(cmd)
-    for line in outdata.rstrip().split('\n'):
-        result.append(line.rstrip())
-    return result
-
-
-if __name__ == "__main__":
-    for zpool in list_zpools():
-        pool = ZPool(zpool)
-        print pool
-        for filesys,mountpoint in pool.list_filesystems():
-            fs = Filesystem(filesys, mountpoint)
-            print fs
-            print "\tSnapshots:"
-            for snapshot, snaptime in fs.list_snapshots():
-                snap = Snapshot(snapshot, snaptime)
-                print "\t\t" + snap.name
-
-        for volname in pool.list_volumes():
-            vol = Volume(volname)
-            print vol
-            print "\tSnapshots:"
-            for snapshot, snaptime in vol.list_snapshots():
-                snap = Snapshot(snapshot, snaptime)
-                print "\t\t" + snap.name
-