summaryrefslogtreecommitdiff
path: root/morphlib/fsutils.py
blob: a3b73bf6d84d333f73dc33bf4747919b4c07b222 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# Copyright (C) 2012-2015  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import re


def create_image(runcmd, image_name, size): # pragma: no cover
    # FIXME a pure python implementation may be better
    runcmd(['dd', 'if=/dev/zero', 'of=' + image_name, 'bs=1',
            'seek=%d' % size, 'count=0'])


def partition_image(runcmd, image_name): # pragma: no cover
    # FIXME make this more flexible with partitioning options
    runcmd(['sfdisk', image_name], feed_stdin='1,,83,*\n')


def setup_device_mapping(runcmd, image_name): # pragma: no cover
    findstart = re.compile(r"start=\s+(\d+),")
    out = runcmd(['sfdisk', '-d', image_name])
    for line in out.splitlines():
        match = findstart.search(line)
        if match is None:
            continue
        start = int(match.group(1)) * 512
        if start != 0:
            break

    device = runcmd(['losetup', '--show', '-o', str(start), '-f', image_name])
    return device.strip()


def create_fs(runcmd, partition): # pragma: no cover
    runcmd(['mkfs.btrfs', '-L', 'baserock', partition])


def mount(runcmd, partition, mount_point, fstype=None): # pragma: no cover
    if not os.path.exists(mount_point):
        os.mkdir(mount_point)
    if not fstype:
        fstype = []
    else:
        fstype = ['-t', fstype]
    runcmd(['mount', partition, mount_point] + fstype)


def unmount(runcmd, mount_point): # pragma: no cover
    runcmd(['umount', mount_point])


def undo_device_mapping(runcmd, image_name): # pragma: no cover
    out = runcmd(['losetup', '-j', image_name])
    for line in out.splitlines():
        i = line.find(':')
        device = line[:i]
        runcmd(['losetup', '-d', device])


def invert_paths(tree_walker, paths):
    '''List paths from `tree_walker` that are not in `paths`.

    Given a traversal of a tree and a set of paths separated by os.sep,
    return the files and directories that are not part of the set of
    paths, culling directories that do not need to be recursed into,
    if the traversal supports this.

    `tree_walker` is expected to follow similar behaviour to `os.walk()`.

    This function will remove directores from the ones listed, to avoid
    traversing into these subdirectories, if it doesn't need to.

    As such, if a directory is returned, it is implied that its contents
    are also not in the set of paths.

    If the tree walker does not support culling the traversal this way,
    such as `os.walk(root, topdown=False)`, then the contents will also
    be returned.

    The purpose for this is to list the directories that can be made
    read-only, such that it would leave everything in paths writable.

    Each path in `paths` is expected to begin with the same path as
    yielded by the tree walker.

    '''

    def normpath(path):
        if path == '.':
            return path
        path = os.path.normpath(path)
        if not os.path.isabs(path):
            path = os.path.join('.', path)
        return path
    def any_paths_are_subpath_of(prefix):
        prefix = normpath(prefix)
        norm_paths = (normpath(path) for path in paths)
        return any(path[:len(prefix)] == prefix
                   for path in norm_paths)

    def path_is_listed(path):
        return any(normpath(path) == normpath(other)
                   for other in paths)

    for dirpath, dirnames, filenames in tree_walker:

        if path_is_listed(dirpath):
            # No subpaths need to be considered
            del dirnames[:]
            del filenames[:]
        elif any_paths_are_subpath_of(dirpath):
            # Subpaths may be marked, or may not, need to leave this
            # writable, so don't yield, but we don't cull.
            pass
        else:
            # not listed as a parent or an exact match, needs to be
            # yielded, but we don't need to consider subdirs, so can cull
            yield dirpath
            del dirnames[:]
            del filenames[:]

        for filename in filenames:
            fullpath = os.path.join(dirpath, filename)
            if path_is_listed(fullpath):
                pass
            else:
                yield fullpath