Finn ødelagte symlinker med Python

stemmer
17

Hvis jeg kaller os.stat()på en ødelagt symlink, kaster python et OSErrorunntak. Dette gjør det nyttig for å finne dem. Men er det noen andre grunner som os.stat()kan kaste et lignende unntak. Er det en mer presis måte å oppdage brutt symlinksmed Python under Linux?

Publisert på 21/08/2008 klokken 19:00
kilden bruker
På andre språk...                            


7 svar

stemmer
22

En vanlig Python sier er at det er lettere å be om tilgivelse enn tillatelse. Mens jeg er ikke en fan av denne uttalelsen i det virkelige liv, gjelder det i mange tilfeller. Vanligvis du unngå kode som kjeder to systemkall på samme fil, fordi du aldri vet hva som vil skje til filen i mellom to samtaler i koden din.

En typisk feil er å skrive noe sånt som :

if os.path.exists(path):
    os.unlink(path)

Den andre samtalen (os.unlink) kan mislykkes hvis noe annet slettet det etter din hvis testen, heve et unntak, og stoppe resten av funksjon fra å kjøre. (Du tenker kanskje at dette ikke skjer i det virkelige liv, men vi bare fisket en annen bug sånn ut av vår kodebasen forrige uke - og det var den type feil som igjen noen programmerere klør seg i hodet og utgir 'Heisenbug' for siste månedene)

Så, i ditt tilfelle, ville jeg sannsynligvis gjøre:

try:
    os.stat(path)
except OSError, e:
    if e.errno == errno.ENOENT:
        print 'path %s does not exist or is a broken symlink' % path
    else:
        raise e

Den irritasjonen her er at stat returnerer samme feilkode for en symlink som bare ikke er der og en ødelagt symlink.

Så jeg antar du har ikke noe valg enn å bryte atomicity, og gjøre noe sånt

if not os.path.exists(os.readlink(path)):
    print 'path %s is a broken symlink' % path
Svarte 25/08/2008 kl. 21:32
kilden bruker

stemmer
12

os.lstat () kan være nyttig. Hvis lstat () etterfølger og stat () svikter, så er det sannsynligvis en ødelagt link.

Svarte 21/08/2008 kl. 19:15
kilden bruker

stemmer
8

Dette er ikke atom men det fungerer.

os.path.islink(filename) and not os.path.exists(filename)

Faktisk av RTFM (lese den fantastiske manualen) ser vi

os.path.exists (bane)

Returnere True hvis banen refererer til en eksisterende bane. Returnerer usann for ødelagte symbolske lenker.

Den sier også:

På noen plattformer, kan denne funksjonen return false dersom tillatelse ikke gis til å utføre os.stat () på den forespurte filen, selv om banen fysisk eksisterer.

Så hvis du er bekymret for tillatelser, bør du legge til andre klausuler.

Svarte 28/06/2015 kl. 16:49
kilden bruker

stemmer
4

Kan jeg nevne testing for hardlinks uten python? / Bin / test har FIL1 -e FIL2 tilstand som er sant når filer dele en inode.

Derfor, noe som find . -type f -exec test \{} -ef /path/to/file \; -printfungerer for hard lenke testing til en bestemt fil.

Hvilket bringer meg til lesing man testog nevner av -Log -hsom begge arbeider på en fil og returnere true hvis denne filen er en symbolsk lenke, men det betyr ikke fortelle deg om mangler målet.

Jeg fant at head -0 FILE1ville returnere en exit koden 0hvis filen kan åpnes og 1hvis det ikke kan, som i tilfelle av en symbolsk lenke til en vanlig fil fungerer som en test for om det er målet kan leses.

Svarte 21/08/2008 kl. 19:13
kilden bruker

stemmer
2

os.path

Du kan prøve å bruke realpath () for å få det symlink peker på, så prøver å finne ut om det er en gyldig fil bruker er fil.

(Jeg er ikke i stand til å prøve det ut i øyeblikket, så du må spille rundt med den og se hva du får)

Svarte 21/08/2008 kl. 19:19
kilden bruker

stemmer
1

Jeg er ikke en python fyr, men det ser ut som os.readlink ()? Logikken jeg ville bruke i perl er å bruke readlink () for å finne målet og bruk stat () for å teste for å se om målet eksisterer.

Edit: Jeg slo ut noen perl at demoer readlink. Jeg tror Perl stat og readlink og python er os.stat () og os.readlink () er begge pakkemaskiner til de systemkall, så dette bør oversette rimelig samt konseptbeviskode:

wembley 0 /home/jj33/swap > cat p
my $f = shift;

while (my $l = readlink($f)) {
  print "$f -> $l\n";
  $f = $l;
}

if (!-e $f) {
  print "$f doesn't exist\n";
}
wembley 0 /home/jj33/swap > ls -l | grep ^l
lrwxrwxrwx    1 jj33  users          17 Aug 21 14:30 link -> non-existant-file
lrwxrwxrwx    1 root     users          31 Oct 10  2007 mm -> ../systems/mm/20071009-rewrite//
lrwxrwxrwx    1 jj33  users           2 Aug 21 14:34 mmm -> mm/
wembley 0 /home/jj33/swap > perl p mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p mmm
mmm -> mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p link
link -> non-existant-file
non-existant-file doesn't exist
wembley 0 /home/jj33/swap >
Svarte 21/08/2008 kl. 19:14
kilden bruker

stemmer
0

Jeg hadde et lignende problem: hvordan fange ødelagte symlinks, selv når de forekommer i noen av foreldrene dir? Jeg ønsket også å loggføre alle av dem (i en søknad håndtere et ganske stort antall filer), men uten for mange gjentakelser.

Her er hva jeg kom opp med, inkludert enhet tester.

fileutil.py :

import os
from functools import lru_cache
import logging

logger = logging.getLogger(__name__)

@lru_cache(maxsize=2000)
def check_broken_link(filename):
    """
    Check for broken symlinks, either at the file level, or in the
    hierarchy of parent dirs.
    If it finds a broken link, an ERROR message is logged.
    The function is cached, so that the same error messages are not repeated.

    Args:
        filename: file to check

    Returns:
        True if the file (or one of its parents) is a broken symlink.
        False otherwise (i.e. either it exists or not, but no element
        on its path is a broken link).

    """
    if os.path.isfile(filename) or os.path.isdir(filename):
        return False
    if os.path.islink(filename):
        # there is a symlink, but it is dead (pointing nowhere)
        link = os.readlink(filename)
        logger.error('broken symlink: {} -> {}'.format(filename, link))
        return True
    # ok, we have either:
    #   1. a filename that simply doesn't exist (but the containing dir
           does exist), or
    #   2. a broken link in some parent dir
    parent = os.path.dirname(filename)
    if parent == filename:
        # reached root
        return False
    return check_broken_link(parent)

Enhet tester:

import logging
import shutil
import tempfile
import os

import unittest
from ..util import fileutil


class TestFile(unittest.TestCase):

    def _mkdir(self, path, create=True):
        d = os.path.join(self.test_dir, path)
        if create:
            os.makedirs(d, exist_ok=True)
        return d

    def _mkfile(self, path, create=True):
        f = os.path.join(self.test_dir, path)
        if create:
            d = os.path.dirname(f)
            os.makedirs(d, exist_ok=True)
            with open(f, mode='w') as fp:
                fp.write('hello')
        return f

    def _mklink(self, target, path):
        f = os.path.join(self.test_dir, path)
        d = os.path.dirname(f)
        os.makedirs(d, exist_ok=True)
        os.symlink(target, f)
        return f

    def setUp(self):
        # reset the lru_cache of check_broken_link
        fileutil.check_broken_link.cache_clear()

        # create a temporary directory for our tests
        self.test_dir = tempfile.mkdtemp()

        # create a small tree of dirs, files, and symlinks
        self._mkfile('a/b/c/foo.txt')
        self._mklink('b', 'a/x')
        self._mklink('b/c/foo.txt', 'a/f')
        self._mklink('../..', 'a/b/c/y')
        self._mklink('not_exist.txt', 'a/b/c/bad_link.txt')
        bad_path = self._mkfile('a/XXX/c/foo.txt', create=False)
        self._mklink(bad_path, 'a/b/c/bad_path.txt')
        self._mklink('not_a_dir', 'a/bad_dir')

    def tearDown(self):
        # Remove the directory after the test
        shutil.rmtree(self.test_dir)

    def catch_check_broken_link(self, expected_errors, expected_result, path):
        filename = self._mkfile(path, create=False)
        with self.assertLogs(level='ERROR') as cm:
            result = fileutil.check_broken_link(filename)
            logging.critical('nothing')  # trick: emit one extra message, so the with assertLogs block doesn't fail
        error_logs = [r for r in cm.records if r.levelname is 'ERROR']
        actual_errors = len(error_logs)
        self.assertEqual(expected_result, result, msg=path)
        self.assertEqual(expected_errors, actual_errors, msg=path)

    def test_check_broken_link_exists(self):
        self.catch_check_broken_link(0, False, 'a/b/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/x/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/f')
        self.catch_check_broken_link(0, False, 'a/b/c/y/b/c/y/b/c/foo.txt')

    def test_check_broken_link_notfound(self):
        self.catch_check_broken_link(0, False, 'a/b/c/not_found.txt')

    def test_check_broken_link_badlink(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_link.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_link.txt')

    def test_check_broken_link_badpath(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_path.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_path.txt')

    def test_check_broken_link_badparent(self):
        self.catch_check_broken_link(1, True, 'a/bad_dir/c/foo.txt')
        self.catch_check_broken_link(0, True, 'a/bad_dir/c/foo.txt')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir/c')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir')

if __name__ == '__main__':
    unittest.main()
Svarte 27/10/2016 kl. 01:49
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more