tl; dr
is_path_exists_or_creatable()
Aşağıda tanımlanan işlevi çağırın .
Kesinlikle Python 3. İşte böyle yuvarlanıyoruz.
İki Sorunun Hikayesi
"Yol adı geçerliliğini ve geçerli yol adları için bu yolların varlığını veya yazılabilirliğini nasıl test ederim?" Sorusu açıkça iki ayrı sorudur. Her ikisi de ilginç ve her ikisi de burada gerçekten tatmin edici bir cevap almamışlar ... ya da bulabileceğim herhangi bir yerde .
vikki 'ın cevabı muhtemelen yakın hews ama olağanüstü dezavantajları vardır:
- Dosya tanıtıcılarını gereksiz yere açmak ( ... ve sonra güvenilir bir şekilde kapatmamak ).
- 0 baytlık dosyaları gereksiz yere yazmak ( ... ve sonra güvenilir bir şekilde kapatma veya silme ).
- Göz ardı edilemez geçersiz yol adları ile göz ardı edilebilir dosya sistemi sorunları arasında ayrım yapan işletim sistemine özgü hataları göz ardı etmek. Şaşırtıcı olmayan bir şekilde, bu Windows altında kritiktir. ( Aşağıya bakın. )
- Test edilecek yol adının üst dizinlerini eşzamanlı olarak (yeniden) taşıyan harici işlemlerden kaynaklanan yarış koşullarının yok sayılması. ( Aşağıya bakın. )
- Bu yol adının eski, yavaş veya başka bir şekilde geçici olarak erişilemeyen dosya sistemlerinde yer almasından kaynaklanan bağlantı zaman aşımlarının yok sayılması. Bu , halka açık hizmetleri potansiyel DoS kaynaklı saldırılara maruz bırakabilir . ( Aşağıya bakın. )
Bunların hepsini düzelteceğiz.
Soru 0: Yol Adı Geçerliliği Yine Nedir?
Kırılgan et kıyafetlerimizi pitonlarla dolu acı çukurlarına fırlatmadan önce, muhtemelen "yol adı geçerliliği" ile ne demek istediğimizi tanımlamalıyız. Geçerliliği tam olarak ne tanımlar?
"Yol adı geçerliliği" ile , mevcut sistemin kök dosya sistemine göre bir yol adının sözdizimsel doğruluğunu kastediyoruz - bu yolun veya üst dizinlerinin fiziksel olarak var olup olmadığına bakılmaksızın. Bir yol adı, kök dosya sisteminin tüm sözdizimsel gereksinimlerine uyuyorsa, bu tanıma göre sözdizimsel olarak doğrudur.
"Kök dosya sistemi" ile şunu kastediyoruz:
- POSIX uyumlu sistemlerde, dosya sistemi kök dizine (
/
) bağlanmıştır .
- Windows'ta dosya sistemi
%HOMEDRIVE%
, geçerli Windows kurulumunu içeren iki nokta üst üste eklenmiş sürücü harfine bağlanmıştır (tipik olarak ancak zorunlu değildirC:
).
"Sözdizimsel doğruluk" un anlamı da kök dosya sisteminin türüne bağlıdır. Tüm POSIX uyumlu dosya sistemleri için ext4
(ve hepsi değil ) dosya sistemleri için, bir yol adı sözdizimsel olarak doğrudur ancak ve ancak bu yol adı:
- Boş bayt içermez (yani
\x00
Python'da). Bu, tüm POSIX uyumlu dosya sistemleri için zor bir gerekliliktir.
- 255 bayttan uzun yol bileşeni içermez (örneğin,
'a'*256
Python'da). Bir yol bileşeninin içeren bir yol adı boyunda bir en uzun alt dizgesi /
karakteri (ör bergtatt
, ind
, i
ve fjeldkamrene
yol adı /bergtatt/ind/i/fjeldkamrene
).
Sözdizimsel doğruluk. Kök dosya sistemi. Bu kadar.
Soru 1: Yol Adı Geçerliliğini Şimdi Nasıl Yapalım?
Python'da yol adlarını doğrulamak şaşırtıcı bir şekilde sezgisel değildir. Burada Sahte İsim ile sıkı bir fikir birliği içindeyim : resmi os.path
paket bunun için kullanıma hazır bir çözüm sunmalıdır. Bilinmeyen (ve muhtemelen zorlayıcı olmayan) nedenlerden dolayı, öyle değil. Neyse ki, kendi geçici çözümünüzü ortaya çıkarmak o kadar da yürek burkan değil ...
Tamam, aslında öyle. Tüylü; o iğrenç; Muhtemelen parlarken homurdanırken kıkırdar ve kıkırdar. Ama ne yapacaksın? Nuthin '.
Yakında düşük seviyeli kodun radyoaktif uçurumuna ineceğiz. Ama önce üst düzey mağazadan konuşalım. Standart os.stat()
ve os.lstat()
işlevler, geçersiz yol adları iletildiğinde aşağıdaki istisnaları ortaya çıkarır:
- Varolmayan dizinlerde bulunan yol adları için
FileNotFoundError
.
- Mevcut dizinlerde bulunan yol adları için:
- Windows altında
WindowsError
, winerror
özniteliği 123
(yani, ERROR_INVALID_NAME
) olan örnekler .
- Diğer tüm işletim sistemleri altında:
- Boş bayt (yani,
'\x00'
) içeren yol adları için TypeError
.
- 255 bayttan daha uzun yol bileşenleri içeren yol adları için
OSError
, errcode
özniteliği aşağıdaki gibi olan örnekler :
- SunOS ve * BSD ailesi OSes altında
errno.ERANGE
,. (Bu, işletim sistemi düzeyinde bir hata gibi görünür, aksi takdirde POSIX standardının "seçici yorumu" olarak adlandırılır.)
- Diğer tüm işletim sistemleri altında
errno.ENAMETOOLONG
.
En önemlisi, bu yalnızca mevcut dizinlerde bulunan yol adlarının doğrulanabilir olduğu anlamına gelir . os.stat()
Ve os.lstat()
işlevleri jenerik yükseltmek FileNotFoundError
geçti pathnames var olmayan dizinleri ikamet etmekte iken bu yol adlarını geçersiz olup olmadığı bakılmaksızın, istisnalar. Dizin varlığı, yol adı geçersizliğine göre önceliklidir.
Bu var olmayan dizinleri ikamet eden yol adlarını olduğunu anlamına mı geliyor değil valide? Evet - bu yol adlarını mevcut dizinlerde yer alacak şekilde değiştirmediğimiz sürece. Ancak bu güvenli bir şekilde mümkün müdür? Bir yol adını değiştirmek, orijinal yol adını doğrulamamızı engellememeli mi?
Bu soruyu yanıtlamak için, yukarıdan ext4
dosya sistemindeki sözdizimsel olarak doğru yol adlarının boş bayt içeren yol bileşenleri (A) veya 255 bayttan uzun (B) içermediğini hatırlayın . Bu nedenle, bir ext4
yol adı ancak ve ancak o yol adındaki tüm yol bileşenleri geçerliyse geçerlidir. Bu, gerçek dünyadaki çoğu dosya sistemi için geçerlidir .
Bu bilgiçlik taslayan içgörü bize gerçekten yardımcı oluyor mu? Evet. Tek seferde tam yol adının doğrulanması sorununu, yalnızca o yol adındaki tüm yol bileşenlerini doğrulamanın daha küçük sorununa indirgiyor. Herhangi bir rasgele yol adı, aşağıdaki algoritma izlenerek platformlar arası bir şekilde (bu yol adının mevcut bir dizinde bulunup bulunmadığına bakılmaksızın) doğrulanabilir:
- Bu yol adını yol bileşenlerine bölün (örneğin,
/troldskog/faren/vild
listeye yol adı ['', 'troldskog', 'faren', 'vild']
).
- Bu tür her bileşen için:
- Bu bileşenle var olması garantili bir dizinin yol adını yeni bir geçici yol adıyla birleştirin (örneğin,
/troldskog
).
- Bu yol adını
os.stat()
veya öğesine iletin os.lstat()
. Bu yol adı ve dolayısıyla bileşen geçersizse, bu çağrının genel bir FileNotFoundError
istisna yerine geçersizlik türünü ortaya çıkaran bir istisna oluşturması garanti edilir . Neden? Çünkü bu yol adı var olan bir dizinde bulunuyor. (Dairesel mantık daireseldir.)
Var olması garanti edilen bir dizin var mı? Evet, ancak tipik olarak yalnızca bir: kök dosya sisteminin en üstteki dizini (yukarıda tanımlandığı gibi).
Başka bir dizinde bulunan (ve dolayısıyla varlığı garanti edilmez) yol adlarını, bu dizinin daha önce var olup olmadığı test edilmiş olsa bile yarış koşullarına davet eder os.stat()
veya os.lstat()
davet eder. Neden? Çünkü, harici işlemlerin bu test gerçekleştirildikten sonra ancak yol adı veya yoluna geçilmeden önce bu dizini aynı anda kaldırması engellenemez . Akıllara durgunluk veren deliliğin köpeklerini serbest bırakın!os.stat()
os.lstat()
Yukarıdaki yaklaşımın da önemli bir yan faydası vardır: güvenlik. (Değil mi o güzel?) Özellikle:
Basitçe böyle pathnames geçirerek güvenilmeyen kaynaklardan gelen keyfi pathnames doğrulayarak uygulamaları öne bakan os.stat()
veya os.lstat()
Hizmet Reddi (DoS) saldırıları ve diğer siyah şapka maskaralık duyarlıdır. Kötü niyetli kullanıcılar, eski veya başka bir şekilde yavaş olduğu bilinen dosya sistemlerinde bulunan yol adlarını tekrar tekrar doğrulamaya çalışabilir (örneğin, NFS Samba paylaşımları); bu durumda, gelen yol adlarını körü körüne ifade etmek, bağlantı zaman aşımlarıyla sonuçta başarısız olmaya veya işsizliğe dayanmak için zayıf kapasitenizden daha fazla zaman ve kaynak tüketmeye meyillidir.
Yukarıdaki yaklaşım, yalnızca bir yol adının yol bileşenlerini kök dosya sisteminin kök dizinine göre doğrulayarak bunu ortadan kaldırır. (Hatta varsa en o bayat, yavaş ya da ulaşılmaz, sen yol adı doğrulama daha büyük sorunlarımız var.)
Kayıp? Harika. Hadi başlayalım. (Python 3 varsayılmıştır. Bkz. "300 için Kırılgan Umut Nedir, leycec ?")
import errno, os
ERROR_INVALID_NAME = 123
'''
Windows-specific error code indicating an invalid pathname.
See Also
----------
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
Official listing of all such codes.
'''
def is_pathname_valid(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS;
`False` otherwise.
'''
try:
if not isinstance(pathname, str) or not pathname:
return False
_, pathname = os.path.splitdrive(pathname)
root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
if sys.platform == 'win32' else os.path.sep
assert os.path.isdir(root_dirname)
root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep
for pathname_part in pathname.split(os.path.sep):
try:
os.lstat(root_dirname + pathname_part)
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == ERROR_INVALID_NAME:
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
except TypeError as exc:
return False
else:
return True
Bitti. Bu koda gözlerinizi kısmayın. ( Isırır. )
Soru 2: Muhtemelen Geçersiz Yol Adı Varlığı veya Oluşturulabilirlik, Eh?
Yukarıdaki çözüm göz önüne alındığında, muhtemelen geçersiz yol adlarının varlığını veya yaratılabilirliğini test etmek, çoğunlukla önemsizdir. Buradaki küçük anahtar , geçilen yolu test etmeden önce önceden tanımlanmış işlevi çağırmaktır :
def is_path_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create the passed
pathname; `False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
return os.access(dirname, os.W_OK)
def is_path_exists_or_creatable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS _and_
either currently exists or is hypothetically creatable; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_creatable(pathname))
except OSError:
return False
Bitti ve bitti. Tam olarak değil.
Soru 3: Windows'ta Muhtemelen Geçersiz Yol Adı Varlığı veya Yazılabilirliği
Bir uyarı var. Tabii ki var.
Resmi os.access()
belgelerin kabul ettiği gibi:
Not: G / Ç işlemleri os.access()
, özellikle olağan POSIX izin biti modelinin ötesinde izin anlamlarına sahip olabilecek ağ dosya sistemlerindeki işlemler için, başarılı olacaklarını belirtse bile başarısız olabilir.
Şaşırtıcı olmayan bir şekilde, Windows burada olağan şüpheli. NTFS dosya sistemlerinde Erişim Kontrol Listelerinin (ACL) yaygın kullanımı sayesinde, basit POSIX izin biti modeli, temel Windows gerçekliğiyle zayıf bir şekilde eşleşir. Bu (tartışmalı bir şekilde) Python'un hatası olmasa da, yine de Windows uyumlu uygulamalar için endişe verici olabilir.
Eğer bu sensen, daha sağlam bir alternatif aranıyor. Aktarılan yol mevcut değilse , bunun yerine o yolun üst dizininde hemen silinmesi garantili geçici bir dosya oluşturmaya çalışırız - daha taşınabilir (pahalıysa) bir yaratıcılık testi:
import os, tempfile
def is_path_sibling_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create **siblings**
(i.e., arbitrary files in the parent directory) of the passed pathname;
`False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
try:
with tempfile.TemporaryFile(dir=dirname): pass
return True
except EnvironmentError:
return False
def is_path_exists_or_creatable_portable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname on the current OS _and_
either currently exists or is hypothetically creatable in a cross-platform
manner optimized for POSIX-unfriendly filesystems; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_sibling_creatable(pathname))
except OSError:
return False
Ancak, bu bile bu yeterli olmayabilir.
Kullanıcı Erişim Kontrolü (UAC) sayesinde, her zaman taklit edilemez Windows Vista ve bunun sonraki tüm yinelemeleri, açık bir şekilde sistem dizinleriyle ilgili izinler hakkında yalan söylüyor . Yönetici olmayan kullanıcılar, kurallı C:\Windows
veya C:\Windows\system32
dizinlerde dosya oluşturmaya çalıştığında , UAC yüzeysel olarak kullanıcının bunu yapmasına izin verirken, oluşturulan tüm dosyaları aslında o kullanıcının profilinde bir "Sanal Mağaza" olarak izole eder. (Kullanıcıları aldatmanın uzun vadede zararlı sonuçları olacağını kim tahmin edebilirdi?)
Bu çılgınca. Bu Windows.
Kanıtla
Cesaret edebilir miyiz? Yukarıdaki testleri test etme zamanı.
NULL, UNIX odaklı dosya sistemlerindeki yol adlarında yasaklanan tek karakter olduğundan, bunu soğuk, zor gerçeği göstermek için kullanalım - açıkçası beni eşit ölçüde sıkan ve kızdıran göz ardı edilemeyen Windows saçmalıklarını görmezden gelin:
>>> print('"foo.bar" valid? ' + str(is_pathname_valid('foo.bar')))
"foo.bar" valid? True
>>> print('Null byte valid? ' + str(is_pathname_valid('\x00')))
Null byte valid? False
>>> print('Long path valid? ' + str(is_pathname_valid('a' * 256)))
Long path valid? False
>>> print('"/dev" exists or creatable? ' + str(is_path_exists_or_creatable('/dev')))
"/dev" exists or creatable? True
>>> print('"/dev/foo.bar" exists or creatable? ' + str(is_path_exists_or_creatable('/dev/foo.bar')))
"/dev/foo.bar" exists or creatable? False
>>> print('Null byte exists or creatable? ' + str(is_path_exists_or_creatable('\x00')))
Null byte exists or creatable? False
Akıl sağlığının ötesinde. Acının ötesinde. Python taşınabilirliği ile ilgili endişeleri bulacaksınız.