summaryrefslogtreecommitdiff
path: root/morphlib/fsutils.py
blob: 6d65117160a95ba0bf902a721a9689a73e9b28bc (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
139
140
141
142
143
144
# Copyright (C) 2012-2014  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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

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 is_subpath(prefix, path):
        prefix_components = prefix.split(os.sep)
        path_components = path.split(os.sep)
        return path_components[:len(prefix_components)] == prefix_components

    for dirpath, dirnames, filenames in tree_walker:

        if any(p == dirpath for p in paths): # pragma: no cover
            # Dir is an exact match for a path
            # don't recurse any further
            # Don't yield it, since we don't return listed paths
            continue
        dn_copy = list(dirnames)
        for subdir in dn_copy:
            subdirpath = os.path.join(dirpath, subdir)

            if any(p == subdirpath for p in paths):
                # Subdir is an exact match for a path
                # Don't recurse into it, so remove from list
                # Don't yield it, since we don't return listed paths
                dirnames.remove(subdir)
            elif any(is_subpath(subdirpath, p) for p in paths):
                # This directory is a parent directory of one
                # of our paths
                # Recurse into it, so don't remove it from the list
                # Don't yield it, since we don't return listed paths
                pass
            else:
                # This directory is neither one marked for writing,
                # nor a parent of a file marked for writing
                # Don't recurse, so remove it from the list
                # Yield it, since we return listed paths
                dirnames.remove(subdir)
                yield subdirpath

        for filename in filenames:
            fullpath = os.path.join(dirpath, filename)
            if any(is_subpath(p, fullpath) for p in paths):
                # The file path is a child of one of the paths
                # or is equal.
                # Don't yield because either it is one of the specified
                # paths, or is a file in a directory specified by a path
                pass
            else:
                yield fullpath