~launchpad-pqm/launchpad/devel

8687.15.12 by Karl Fogel
Add the copyright header block to files under lib/lp/archivepublisher/
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
1102 by Canonical.com Patch Queue Manager
Lucille had some XXXs which should have been NOTEs
3
3691.212.1 by Malcolm Cleaton
Checkpoint
4
__all__ = ['DiskPoolEntry', 'DiskPool', 'poolify', 'unpoolify']
1102 by Canonical.com Patch Queue Manager
Lucille had some XXXs which should have been NOTEs
5
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
6
import os
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
7
import tempfile
3496.1.30 by Celso Providelo
partial review comments from kiko applied.
8
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
9
from canonical.librarian.utils import (
10
    copy_and_close,
11
    sha1_from_path,
12
    )
8426.7.1 by Julian Edwards
migrate archivepublisher to the lp tree.
13
from lp.archivepublisher import HARDCODED_COMPONENT_ORDER
11382.6.34 by Gavin Panella
Reformat imports in all files touched so far.
14
from lp.services.propertycache import cachedproperty
7675.332.1 by Celso Providelo
Dealing with MissingSymlinkInPool situation in DeathRow.
15
from lp.soyuz.interfaces.publishing import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
16
    MissingSymlinkInPool,
17
    NotInPool,
18
    PoolFileOverwriteError,
19
    )
3500.2.38 by Celso Providelo
Checkpoint version, don't use it tests are broken, will need more time to fix 'publishing from content class' and don't want to loose this changeset
20
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
21
3691.212.1 by Malcolm Cleaton
Checkpoint
22
def poolify(source, component):
23
    """Poolify a given source and component name."""
24
    if source.startswith("lib"):
25
        return os.path.join(component, source[:4], source)
26
    else:
27
        return os.path.join(component, source[:1], source)
28
29
30
def unpoolify(self, path):
31
    """Take a path and unpoolify it.
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
32
3691.212.1 by Malcolm Cleaton
Checkpoint
33
    Return a tuple of component, source, filename
1102 by Canonical.com Patch Queue Manager
Lucille had some XXXs which should have been NOTEs
34
    """
3691.212.1 by Malcolm Cleaton
Checkpoint
35
    p = path.split("/")
36
    if len(p) < 3 or len(p) > 4:
37
        raise ValueError("Path %s is not in a valid pool form" % path)
38
    if len(p) == 4:
39
        return p[0], p[2], p[3]
40
    return p[0], p[2], None
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
41
2890.2.4 by Daniel Silverstone
Make sure symlinks are relative and sanitised. Also ensure we split on tabs only on extra override input and split the distrorelease name slightly more carefully.
42
43
def relative_symlink(src_path, dst_path):
44
    """os.symlink replacement that creates relative symbolic links."""
45
    path_sep = os.path.sep
46
    src_path = os.path.normpath(src_path)
47
    dst_path = os.path.normpath(dst_path)
48
    src_path_elems = src_path.split(path_sep)
49
    dst_path_elems = dst_path.split(path_sep)
50
    if os.path.isabs(src_path):
51
        if not os.path.isabs(dst_path):
52
            dst_path = os.path.abspath(dst_path)
53
        common_prefix = os.path.commonprefix([src_path_elems, dst_path_elems])
54
        backward_elems = ['..'] * (len(dst_path_elems)-len(common_prefix)-1)
55
        forward_elems = src_path_elems[len(common_prefix):]
56
        src_path = path_sep.join(backward_elems + forward_elems)
57
    os.symlink(src_path, dst_path)
58
3691.212.1 by Malcolm Cleaton
Checkpoint
59
3691.212.2 by Malcolm Cleaton
More refactoring
60
class FileAddActionEnum:
61
    """Possible actions taken when adding a file.
62
63
    FILE_ADDED: we added the actual file to the disk
64
    SYMLINK_ADDED: we created a symlink to another copy of the same file
65
    NONE: no action was necessary or taken.
66
    """
67
    FILE_ADDED = "file_added"
68
    SYMLINK_ADDED = "symlink_added"
69
    NONE = "none"
70
71
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
72
class _diskpool_atomicfile:
73
    """Simple file-like object used by the pool to atomically move into place
74
    a file after downloading from the librarian.
75
76
    This class is designed to solve a very specific problem encountered in
77
    the publisher. Namely that should the publisher crash during the process
78
    of publishing a file to the pool, an empty or incomplete file would be
79
    present in the pool. Its mere presence would fool the publisher into
80
    believing it had already downloaded that file to the pool, resulting
81
    in failures in the apt-ftparchive stage.
82
83
    By performing a rename() when the file is guaranteed to have been
84
    fully written to disk (after the fd.close()) we can be sure that if
85
    the filename is present in the pool, it is definitely complete.
86
    """
87
88
    def __init__(self, targetfilename, mode, rootpath="/tmp"):
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
89
        # atomicfile implements the file object interface, but it is only
90
        # really used (or useful) for writing binary files, which is why we
91
        # keep the mode constructor argument but assert it's sane below.
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
92
        if mode == "w":
93
            mode = "wb"
94
        assert mode == "wb"
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
95
3496.1.44 by Celso Providelo
review comments from kiko (take I and II), missing publishing/pool redesign and tests
96
        assert not os.path.exists(targetfilename)
97
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
98
        self.targetfilename = targetfilename
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
99
        fd, name = tempfile.mkstemp(prefix="temp-download.", dir=rootpath)
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
100
        self.fd = os.fdopen(fd, mode)
101
        self.tempname = name
102
        self.write = self.fd.write
103
104
    def close(self):
105
        """Make the atomic move into place having closed the temp file."""
106
        self.fd.close()
3023.3.86 by Daniel Silverstone
Fix up permissions in pool files
107
        os.chmod(self.tempname, 0644)
3147.4.1 by Daniel Silverstone
Review response
108
        # Note that this will fail if the target and the temp dirs are on
109
        # different filesystems.
3023.3.60 by Daniel Silverstone
Ensure that files are published into the pool properly, or not at all
110
        os.rename(self.tempname, self.targetfilename)
111
2890.2.4 by Daniel Silverstone
Make sure symlinks are relative and sanitised. Also ensure we split on tabs only on extra override input and split the distrorelease name slightly more carefully.
112
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
113
class DiskPoolEntry:
3691.212.6 by Malcolm Cleaton
Addressed review comments.
114
    """Represents a single file in the pool, across all components.
115
116
    Creating a DiskPoolEntry performs disk reads, so don't create an
117
    instance of this class unless you need to know what's already on
118
    the disk for this file.
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
119
4162.4.3 by Celso Providelo
applying review comments [r=statik]
120
    'tempath' must be in the same filesystem as 'rootpath', it will be
121
    used to store the instalation candidate while it is being downloaded
122
    from the Librarian.
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
123
4162.4.3 by Celso Providelo
applying review comments [r=statik]
124
    Remaining files in the 'temppath' indicated installation failures and
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
125
    require manual removal after further investigation.
3691.212.6 by Malcolm Cleaton
Addressed review comments.
126
    """
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
127
    def __init__(self, rootpath, temppath, source, filename, logger):
3691.212.1 by Malcolm Cleaton
Checkpoint
128
        self.rootpath = rootpath
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
129
        self.temppath = temppath
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
130
        self.source = source
3691.212.1 by Malcolm Cleaton
Checkpoint
131
        self.filename = filename
132
        self.logger = logger
133
3691.212.6 by Malcolm Cleaton
Addressed review comments.
134
        self.file_component = None
3691.212.1 by Malcolm Cleaton
Checkpoint
135
        self.symlink_components = set()
2890.2.4 by Daniel Silverstone
Make sure symlinks are relative and sanitised. Also ensure we split on tabs only on extra override input and split the distrorelease name slightly more carefully.
136
3691.212.1 by Malcolm Cleaton
Checkpoint
137
        for component in HARDCODED_COMPONENT_ORDER:
138
            path = self.pathFor(component)
139
            if os.path.islink(path):
140
                self.symlink_components.add(component)
141
            elif os.path.isfile(path):
142
                assert not self.file_component
143
                self.file_component = component
144
        if self.symlink_components:
145
            assert self.file_component
146
3691.212.2 by Malcolm Cleaton
More refactoring
147
    def debug(self, *args, **kwargs):
148
        self.logger.debug(*args, **kwargs)
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
149
3691.212.2 by Malcolm Cleaton
More refactoring
150
    def pathFor(self, component):
151
        """Return the path for this file in the given component."""
152
        return os.path.join(self.rootpath,
153
                            poolify(self.source, component),
154
                            self.filename)
155
156
    def preferredComponent(self, add=None, remove=None):
157
        """Return the appropriate component for the real file.
158
3691.212.6 by Malcolm Cleaton
Addressed review comments.
159
        If add is passed, add it to the list before calculating.
160
        If remove is passed, remove it before calculating.
3691.212.2 by Malcolm Cleaton
More refactoring
161
        Thus, we can calcuate which component should contain the main file
162
        after the addition or removal we are working on.
3496.1.28 by Celso Providelo
Fix publishing core, add unit tests for several publishing cases, check potetial archive corruption (pair-programming with malcc)
163
        """
3691.212.2 by Malcolm Cleaton
More refactoring
164
        components = set()
165
        if self.file_component:
166
            components.add(self.file_component)
167
        components = components.union(self.symlink_components)
168
        if add is not None:
169
            components.add(add)
3691.212.6 by Malcolm Cleaton
Addressed review comments.
170
        if remove is not None and remove in components:
3691.212.2 by Malcolm Cleaton
More refactoring
171
            components.remove(remove)
172
173
        for component in HARDCODED_COMPONENT_ORDER:
174
            if component in components:
175
                return component
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
176
3691.212.2 by Malcolm Cleaton
More refactoring
177
    @cachedproperty
3691.212.1 by Malcolm Cleaton
Checkpoint
178
    def file_hash(self):
3691.212.2 by Malcolm Cleaton
More refactoring
179
        """Return the SHA1 sum of this file."""
180
        targetpath = self.pathFor(self.file_component)
181
        return sha1_from_path(targetpath)
182
183
    def addFile(self, component, sha1, contents):
184
        """See DiskPool.addFile."""
3691.212.6 by Malcolm Cleaton
Addressed review comments.
185
        assert component in HARDCODED_COMPONENT_ORDER
186
3691.212.2 by Malcolm Cleaton
More refactoring
187
        targetpath = self.pathFor(component)
3691.212.6 by Malcolm Cleaton
Addressed review comments.
188
        if not os.path.exists(os.path.dirname(targetpath)):
189
            os.makedirs(os.path.dirname(targetpath))
3496.1.44 by Celso Providelo
review comments from kiko (take I and II), missing publishing/pool redesign and tests
190
3691.212.2 by Malcolm Cleaton
More refactoring
191
        if self.file_component:
192
            # There's something on disk. Check hash.
193
            if sha1 != self.file_hash:
3691.212.6 by Malcolm Cleaton
Addressed review comments.
194
                raise PoolFileOverwriteError('%s != %s for %s' %
7726.2.1 by Celso Providelo
Fixing bug #324274 (misleading deathrow optimizations of file checksums). Also re-write doc/deathrow.txt to be more readable and sampledata-independent.
195
                    (sha1, self.file_hash,
196
                     self.pathFor(self.file_component)))
3691.212.2 by Malcolm Cleaton
More refactoring
197
198
            if (component == self.file_component
199
                or component in self.symlink_components):
200
                # The file is already here
201
                return FileAddActionEnum.NONE
202
            else:
7726.2.1 by Celso Providelo
Fixing bug #324274 (misleading deathrow optimizations of file checksums). Also re-write doc/deathrow.txt to be more readable and sampledata-independent.
203
                # The file is present in a different component,
204
                # make a symlink.
205
                relative_symlink(
206
                    self.pathFor(self.file_component), targetpath)
3691.212.2 by Malcolm Cleaton
More refactoring
207
                self.symlink_components.add(component)
208
                # Then fix to ensure the right component is linked.
209
                self._sanitiseLinks()
210
211
                return FileAddActionEnum.SYMLINK_ADDED
212
213
        # If we get to here, we want to write the file.
3691.212.6 by Malcolm Cleaton
Addressed review comments.
214
        assert not os.path.exists(targetpath)
215
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
216
        self.debug("Making new file in %s for %s/%s" %
3691.212.1 by Malcolm Cleaton
Checkpoint
217
                   (component, self.source, self.filename))
3496.1.28 by Celso Providelo
Fix publishing core, add unit tests for several publishing cases, check potetial archive corruption (pair-programming with malcc)
218
3691.212.2 by Malcolm Cleaton
More refactoring
219
        file_to_write = _diskpool_atomicfile(
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
220
            targetpath, "wb", rootpath=self.temppath)
3691.212.2 by Malcolm Cleaton
More refactoring
221
        contents.open()
222
        copy_and_close(contents, file_to_write)
3691.212.1 by Malcolm Cleaton
Checkpoint
223
        self.file_component = component
3691.212.2 by Malcolm Cleaton
More refactoring
224
        return FileAddActionEnum.FILE_ADDED
3496.1.28 by Celso Providelo
Fix publishing core, add unit tests for several publishing cases, check potetial archive corruption (pair-programming with malcc)
225
3691.212.1 by Malcolm Cleaton
Checkpoint
226
    def removeFile(self, component):
3691.93.1 by Christian Reis
Factor the death row processing into a separate script. Implement the DeathRow processor, with size calculation, some optimizations and properly restricting it to a specific distribution, and a process-death-row script, which has a dry run mode. Do minor cleanups in DAR.publish(), implementing a DR.isUnstable() method. Test DeathRow. Change DiskPool file removal code to return the filesize of the file removed. Implement Source/BinaryPackageFilePublishing.displayname, with tests. Clean up publish-distro as much as I can..
227
        """Remove a file from a given component; return bytes freed.
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
228
229
        This method handles three situations:
230
3691.212.1 by Malcolm Cleaton
Checkpoint
231
        1) Remove a symlink
232
233
        2) Remove the main file and there are no symlinks left.
234
235
        3) Remove the main file and there are symlinks left.
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
236
        """
3691.212.1 by Malcolm Cleaton
Checkpoint
237
        if not self.file_component:
7675.332.1 by Celso Providelo
Dealing with MissingSymlinkInPool situation in DeathRow.
238
            raise NotInPool(
239
                "File for removing %s %s/%s is not in pool, skipping." %
240
                (component, self.source, self.filename))
241
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
242
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
243
        # Okay, it's there, if it's a symlink then we need to remove
244
        # it simply.
3691.212.1 by Malcolm Cleaton
Checkpoint
245
        if component in self.symlink_components:
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
246
            self.debug("Removing %s %s/%s as it is a symlink"
3691.212.1 by Malcolm Cleaton
Checkpoint
247
                       % (component, self.source, self.filename))
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
248
            # ensure we are removing a symbolic link and
249
            # it is published in one or more components
3691.212.1 by Malcolm Cleaton
Checkpoint
250
            link_path = self.pathFor(component)
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
251
            assert os.path.islink(link_path)
3691.212.1 by Malcolm Cleaton
Checkpoint
252
            return self._reallyRemove(component)
253
7675.332.1 by Celso Providelo
Dealing with MissingSymlinkInPool situation in DeathRow.
254
        if component != self.file_component:
255
            raise MissingSymlinkInPool(
256
                "Symlink for %s/%s in %s is missing, skipping." %
257
                (self.source, self.filename, component))
3691.212.1 by Malcolm Cleaton
Checkpoint
258
259
        # It's not a symlink, this means we need to check whether we
260
        # have symlinks or not.
261
        if len(self.symlink_components) == 0:
7726.2.1 by Celso Providelo
Fixing bug #324274 (misleading deathrow optimizations of file checksums). Also re-write doc/deathrow.txt to be more readable and sampledata-independent.
262
            self.debug("Removing %s/%s from %s" %
3691.212.1 by Malcolm Cleaton
Checkpoint
263
                       (self.source, self.filename, component))
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
264
        else:
3691.92.1 by Malcolm Cleaton
Fix bug 57237
265
            # The target for removal is the real file, and there are symlinks
266
            # pointing to it. In order to avoid breakage, we need to first
267
            # shuffle the symlinks, so that the one we want to delete will
3691.212.2 by Malcolm Cleaton
More refactoring
268
            # just be one of the links, and becomes safe.
269
            targetcomponent = self.preferredComponent(remove=component)
3691.212.1 by Malcolm Cleaton
Checkpoint
270
            self._shufflesymlinks(targetcomponent)
271
272
        return self._reallyRemove(component)
273
274
    def _reallyRemove(self, component):
3691.93.1 by Christian Reis
Factor the death row processing into a separate script. Implement the DeathRow processor, with size calculation, some optimizations and properly restricting it to a specific distribution, and a process-death-row script, which has a dry run mode. Do minor cleanups in DAR.publish(), implementing a DR.isUnstable() method. Test DeathRow. Change DiskPool file removal code to return the filesize of the file removed. Implement Source/BinaryPackageFilePublishing.displayname, with tests. Clean up publish-distro as much as I can..
275
        """Remove file and return file size.
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
276
3691.212.1 by Malcolm Cleaton
Checkpoint
277
        Remove the file from the filesystem and from our data
3691.93.1 by Christian Reis
Factor the death row processing into a separate script. Implement the DeathRow processor, with size calculation, some optimizations and properly restricting it to a specific distribution, and a process-death-row script, which has a dry run mode. Do minor cleanups in DAR.publish(), implementing a DR.isUnstable() method. Test DeathRow. Change DiskPool file removal code to return the filesize of the file removed. Implement Source/BinaryPackageFilePublishing.displayname, with tests. Clean up publish-distro as much as I can..
278
        structures.
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
279
        """
3691.212.1 by Malcolm Cleaton
Checkpoint
280
        fullpath = self.pathFor(component)
3691.93.1 by Christian Reis
Factor the death row processing into a separate script. Implement the DeathRow processor, with size calculation, some optimizations and properly restricting it to a specific distribution, and a process-death-row script, which has a dry run mode. Do minor cleanups in DAR.publish(), implementing a DR.isUnstable() method. Test DeathRow. Change DiskPool file removal code to return the filesize of the file removed. Implement Source/BinaryPackageFilePublishing.displayname, with tests. Clean up publish-distro as much as I can..
281
        assert os.path.exists(fullpath)
282
3691.212.1 by Malcolm Cleaton
Checkpoint
283
        if component == self.file_component:
284
            # Deleting the master file is only allowed if there
285
            # are no symlinks left.
286
            assert not self.symlink_components
287
            self.file_component = None
288
        elif component in self.symlink_components:
289
            self.symlink_components.remove(component)
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
290
3691.93.1 by Christian Reis
Factor the death row processing into a separate script. Implement the DeathRow processor, with size calculation, some optimizations and properly restricting it to a specific distribution, and a process-death-row script, which has a dry run mode. Do minor cleanups in DAR.publish(), implementing a DR.isUnstable() method. Test DeathRow. Change DiskPool file removal code to return the filesize of the file removed. Implement Source/BinaryPackageFilePublishing.displayname, with tests. Clean up publish-distro as much as I can..
291
        size = os.lstat(fullpath).st_size
292
        os.remove(fullpath)
293
        return size
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
294
3691.212.1 by Malcolm Cleaton
Checkpoint
295
    def _shufflesymlinks(self, targetcomponent):
3496.1.30 by Celso Providelo
partial review comments from kiko applied.
296
        """Shuffle the symlinks for filename so that targetcomponent contains
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
297
        the real file and the rest are symlinks to the right place..."""
3691.212.1 by Malcolm Cleaton
Checkpoint
298
        if targetcomponent == self.file_component:
3691.87.2 by Malcolm Cleaton
Tidying up
299
            # We're already in the right place.
3691.87.1 by Malcolm Cleaton
Fix and re-enable diskpool sanitiseLinks (bug 55896)
300
            return
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
301
3691.212.1 by Malcolm Cleaton
Checkpoint
302
        if targetcomponent not in self.symlink_components:
3496.1.46 by Celso Providelo
Finishing poll review take II (sanitizeLinks dead)
303
            raise ValueError(
3691.212.1 by Malcolm Cleaton
Checkpoint
304
                "Target component '%s' is not a symlink for %s" %
305
                             (targetcomponent, self.filename))
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
306
3691.87.1 by Malcolm Cleaton
Fix and re-enable diskpool sanitiseLinks (bug 55896)
307
        self.debug("Shuffling symlinks so primary for %s is in %s" %
3691.212.1 by Malcolm Cleaton
Checkpoint
308
                   (self.filename, targetcomponent))
3691.87.1 by Malcolm Cleaton
Fix and re-enable diskpool sanitiseLinks (bug 55896)
309
3691.87.2 by Malcolm Cleaton
Tidying up
310
        # Okay, so first up, we unlink the targetcomponent symlink.
3691.212.1 by Malcolm Cleaton
Checkpoint
311
        targetpath = self.pathFor(targetcomponent)
3691.87.1 by Malcolm Cleaton
Fix and re-enable diskpool sanitiseLinks (bug 55896)
312
        os.remove(targetpath)
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
313
3691.87.2 by Malcolm Cleaton
Tidying up
314
        # Now we rename the source file into the target component.
3691.212.1 by Malcolm Cleaton
Checkpoint
315
        sourcepath = self.pathFor(self.file_component)
3496.1.44 by Celso Providelo
review comments from kiko (take I and II), missing publishing/pool redesign and tests
316
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
317
        # XXX cprov 2006-05-26: if it fails the symlinks are severely broken
3496.1.33 by Celso Providelo
Trace an issue in archive_removal related to corrupted symlinks.
318
        # or maybe we are writing them wrong. It needs manual fix !
319
        # Nonetheless, we carry on checking other candidates.
320
        # Use 'find -L . -type l' on pool to find out broken symlinks
321
        # Normally they only can be fixed by remove the broken links and
322
        # run a careful (-C) publication.
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
323
324
        # ensure targetpath doesn't exists and  the sourcepath exists
325
        # before rename them.
326
        assert not os.path.exists(targetpath)
327
        assert os.path.exists(sourcepath)
328
        os.rename(sourcepath, targetpath)
329
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
330
        # XXX cprov 2006-06-12: it may cause problems to the database, since
3691.113.1 by Malcolm Cleaton
Tidying XXX comments in archivepublisher
331
        # ZTM isn't handled properly in scripts/publish-distro.py. Things are
3496.1.45 by Celso Providelo
Postponed review issues and fixing pylint errors
332
        # commited mid-procedure & bare exception is caught.
333
3691.212.1 by Malcolm Cleaton
Checkpoint
334
        # Update the data structures.
335
        self.symlink_components.add(self.file_component)
336
        self.symlink_components.remove(targetcomponent)
337
        self.file_component = targetcomponent
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
338
3691.212.1 by Malcolm Cleaton
Checkpoint
339
        # Now we make the symlinks on the filesystem.
340
        for comp in self.symlink_components:
341
            newpath = self.pathFor(comp)
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
342
            try:
343
                os.remove(newpath)
344
            except OSError:
3691.87.2 by Malcolm Cleaton
Tidying up
345
                # Do nothing because it's almost certainly a not found.
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
346
                pass
2890.2.4 by Daniel Silverstone
Make sure symlinks are relative and sanitised. Also ensure we split on tabs only on extra override input and split the distrorelease name slightly more carefully.
347
            relative_symlink(targetpath, newpath)
1932 by Canonical.com Patch Queue Manager
Merge in publishing work from soyuz sprint. r=jamesh
348
3691.212.2 by Malcolm Cleaton
More refactoring
349
    def _sanitiseLinks(self):
3691.212.1 by Malcolm Cleaton
Checkpoint
350
        """Ensure the real file is in the most preferred component.
351
352
        If this file is in more than one component, ensure the real
353
        file is in the most preferred component and the other components
354
        use symlinks.
355
356
        It's important that the real file be in the most preferred
357
        component because partial mirrors may only take a subset of
3691.87.1 by Malcolm Cleaton
Fix and re-enable diskpool sanitiseLinks (bug 55896)
358
        components, and these partial mirrors must not have broken
359
        symlinks where they should have working files.
360
        """
3691.212.2 by Malcolm Cleaton
More refactoring
361
        component = self.preferredComponent()
362
        if not self.file_component == component:
363
            self._shufflesymlinks(component)
3691.212.1 by Malcolm Cleaton
Checkpoint
364
365
366
class DiskPool:
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
367
    """Scan a pool on the filesystem and record information about it.
368
4162.4.3 by Celso Providelo
applying review comments [r=statik]
369
    Its constructor receives 'rootpath', which is the pool path where the
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
370
    files will be installed, and the 'temppath', which is a temporary
4162.4.3 by Celso Providelo
applying review comments [r=statik]
371
    directory used to store the installation candidate from librarian.
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
372
373
    'rootpath' and 'temppath' must be in the same filesystem, see
374
    DiskPoolEntry for further information.
375
    """
3691.212.2 by Malcolm Cleaton
More refactoring
376
    results = FileAddActionEnum
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
377
378
    def __init__(self, rootpath, temppath, logger):
3691.212.1 by Malcolm Cleaton
Checkpoint
379
        self.rootpath = rootpath
380
        if not rootpath.endswith("/"):
381
            self.rootpath += "/"
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
382
383
        self.temppath = temppath
4162.4.3 by Celso Providelo
applying review comments [r=statik]
384
        if not temppath.endswith("/"):
385
            self.temppath += "/"
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
386
3691.212.1 by Malcolm Cleaton
Checkpoint
387
        self.entries = {}
388
        self.logger = logger
389
3691.212.2 by Malcolm Cleaton
More refactoring
390
    def _getEntry(self, sourcename, file):
391
        """Return a new DiskPoolEntry for the given sourcename and file."""
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
392
        return DiskPoolEntry(
393
            self.rootpath, self.temppath, sourcename, file, self.logger)
3691.212.1 by Malcolm Cleaton
Checkpoint
394
395
    def pathFor(self, comp, source, file=None):
3691.212.2 by Malcolm Cleaton
More refactoring
396
        """Return the path for the given pool folder or file.
397
398
        If file is none, the path to the folder containing all packages
399
        for the given component and source package name will be returned.
400
401
        If file is specified, the path to the specific package file will
402
        be returned.
403
        """
3691.212.1 by Malcolm Cleaton
Checkpoint
404
        path = os.path.join(
405
            self.rootpath, poolify(source, comp))
406
        if file:
407
            return os.path.join(path, file)
408
        return path
409
3691.212.2 by Malcolm Cleaton
More refactoring
410
    def addFile(self, component, sourcename, filename, sha1, contents):
411
        """Add a file with the given contents to the pool.
412
413
        Component, sourcename and filename are used to calculate the
414
        on-disk location.
415
416
        sha1 is used to compare with the existing file's checksum, if
417
        a file already exists for any component.
418
419
        contents is a file-like object containing the contents we want
420
        to write.
421
422
        There are four possible outcomes:
423
        - If the file doesn't exist in the pool for any component, it will
424
        be written from the given contents and results.ADDED_FILE will be
425
        returned.
426
427
        - If the file already exists in the pool, in this or any other
428
        component, the checksum of the file on disk will be calculated and
429
        compared with the checksum provided. If they fail to match,
430
        PoolFileOverwriteError will be raised.
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
431
3691.212.2 by Malcolm Cleaton
More refactoring
432
        - If the file already exists but not in this component, and the
433
        checksum test above passes, a symlink will be added, and
434
        results.SYMLINK_ADDED will be returned. Also, the symlinks will be
435
        checked and sanitised, to ensure the real copy of the file is in the
436
        most preferred component, according to HARDCODED_COMPONENT_ORDER.
437
438
        - If the file already exists and is already in this component,
439
        either as a file or a symlink, and the checksum check passes,
440
        results.NONE will be returned and nothing will be done.
441
        """
442
        entry = self._getEntry(sourcename, filename)
443
        return entry.addFile(component, sha1, contents)
4162.4.1 by Celso Providelo
Fix bug #110351 (lost doffiles in archive breaks dsync/mirroring). Adding a temporary directory in the archive-tree for storing files while they are downloaded from librarian.
444
3691.212.1 by Malcolm Cleaton
Checkpoint
445
    def removeFile(self, component, sourcename, filename):
3691.212.2 by Malcolm Cleaton
More refactoring
446
        """Remove the specified file from the pool.
447
448
        There are three possible outcomes:
449
        - If the specified file does not exist, NotInPool will be raised.
450
451
        - If the specified file exists and is a symlink, or is the only
452
        copy of the file in the pool, it will simply be deleted, and its
453
        size will be returned.
454
455
        - If the specified file is a real file and there are symlinks
456
        referencing it, the symlink in the next most preferred component
457
        will be deleted, and the file will be moved to replace it. The
458
        size of the deleted symlink will be returned.
459
        """
460
        entry = self._getEntry(sourcename, filename)
3691.212.1 by Malcolm Cleaton
Checkpoint
461
        return entry.removeFile(component)