Buildout -- Software for automating application assembly

Support for removing of symlinks

Reported by Radim Novotny on 2007-09-23
20
This bug affects 3 people
Affects Status Importance Assigned to Milestone
Buildout
Undecided
Unassigned

Bug Description

_uninstall method of the Buildout class does not support removing of symbolic links pointing to directory.
I'm writing recipe which creates symbolic links to other locations and this recipe fails on reinstall. _uninstall method should be changed to:
(first check if f is file or link and then check if f is directory. Symlink pointing to directory is treated as directory)

    def _uninstall(self, installed):
        for f in installed.split('\n'):
            if not f:
                continue
            f = self._buildout_path(f)
            if os.path.isfile(f) or os.path.islink(f):
                os.remove(f)
            elif os.path.isdir(f):
                shutil.rmtree(f)

To rephrase Radim, symbolic links pointing to directories are not properly removed by buildout. This is because in _uninstall, removed filesystem items are first checked for whether they are directories. An os.path.isdir(link) check for a link pointing to a directory returns True, similarly to check on a link pointing to a file returns True for an isfile(link) check.

Buildout checks first for whether the filesystem item to be removed returns True for isdir(). This is wrong, because buildout then tries to rmtree(link), which causes havoc.

Instead, in _uninstall, the check for file & link should be first.The issue can thus simply be fixed by reversing the checks.

This bug is misnamed; it should be "fix removal of symlinks pointing to a directory" or something like that.

patch attached

Kazuhiko (kazuhiko) wrote :

+1 for this change.

BTW, if 'chmod(path, 0600)' trick in buildout.rmtree ALREADY succeeded on the symlink to a directory, the destination directory had 0600 permission and its contents can never be deleted. i.e. deletion of the content raises an exception and buildout.rmtree is called, but chmod(path, 0600) still fails because its parent directory has no 'x' permission.

So I propose that chmod 2nd argument in biuldout.rmtree should be 0700, at least for directories.

Michael Hudson-Doyle (mwhudson) wrote :

I've just been bitten by this :/

FYI, there's been a new zc.buildout release. Sadly, it contains no fix to this bug, but I have forked zc.buildout 1.6.0 (and applied the bug fix to it) at github; see https://github.com/koodaamo/zc.buildout

Kazuhiko: are you saying the mode change to 0700 in buildout.rmtree should be done also? In other words, even if the code already has the fix for bug #144228 ?

BTW, note that the version of the forked package at github is now "1.6.0-fix-lp144228", not 1.6.0...

Kazuhiko (kazuhiko) wrote :

Petri:
I just looked at your code and it should be good to prevent this issue.
My proposal,"mode change to 0700 in buildout.rmtree", is required to save the case where the directory was *already* polluted by this issue, i.e. the the mode of the destination directory of a symlink was alreay changed to 0600 that raises exception when buildout tries to delete its contents.

So if we only care further buildout results that are created the buildout with this fix, it is not mandatory. But if we care existing buildout results that are created by buildout without this fix, it will make some people happy.

Domen Kožar (ielectric+) wrote :

If you provide a patch with a test, I'll pull the changes :-)

Thanks Kazuhiko. Domen, what should we patch & provide a test for now? While there's a 1.7 beta release, I can only see a 1.6 branch at https://github.com/buildout/buildout ...?

BTW, should we copy this issue to the github issue tracker as well? I would really like to see this bug fixed, but I am rather a novice what it comes to writing buildout tests.

Jim Fulton (jim-zope) wrote :

The 1.6.x branch is miss-named. It should have been named
"1". I'll rename it.

In the mean time, I suggest creating a pull request against master.

Someone can then cherry-pick this to apply it to 1 if desired.

I have forked master and applied the fix.

I've not applied Kazuhiko's further suggestion (see above): afaik, it runs only when rmtree is called with a link pointing to a directory. As that will no longer be case with the fix applied, the modified code will not run (for the linked directories rmtree has previously left at mode 0600) any more. Please do correct me if I'm mistaken.

It then occurred to me to checked whether rmtree is called from elsewhere in buildout.py. I found another case that will potentially cause trouble exactly the same way. See code starting at line 1271, in the Options._call method:

https://github.com/buildout/buildout/blob/master/src/zc/buildout/buildout.py#L1272

Unless anyone objects, I will fix that as well and then submit a pull request. Although I don't know in what scenario one would pass a link (pointing to a directory) to Options._call, I'd rather fix it now than risk running into this issue again later.

Kazuhiko (kazuhiko) wrote :

I mean that once a directory was polluted by chmod(0600), the contents in the directory will no longer be deleted unless we chmod it with '+x'.

I don't care if my suggestion is applied or not so much if this pollution will no longer happen, but even though this pollution should no longer happen, still

    def retry_writeable (func, path, exc):
        os.chmod (path, 0600)
        func (path)

this code is anyway wrong for directories and can make more undeletable.

Good point. I wasn't thinking that far. I'll add it to the pull request. Thanks for persisting!

To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers