Tento dokument popisuje návrh implementace volby --recursive pro nástroj blockcopy, která umožní synchronizaci celých adresářových stromů. Změny nejsou zpětně kompatibilní s verzí 0.0.3.
Typické use cases: Primárním cílem blockcopy je efektivní kopírování LVM volumes, VM images (qcow2, raw), databázových datadirů (PostgreSQL, MySQL, MongoDB) a podobných velkých datových struktur. Tyto jsou typicky tvořeny běžnými adresáři a regular files bez speciálních atributů (xattr, ACL, hardlinky). Proto je podpora pokročilých filesystémových funkcí nízkou prioritou.
- Jednosměrný tok dat - zachován stávající model: checksum → retrieve → save
- Přenos pouze rozdílů - přečtou se a zahashují všechny soubory na obou stranách, přenesou se jen změněné bloky
- Deterministické pořadí - položky se zpracovávají v abecedním pořadí relativních cest (lexikografické řazení UTF-8 bytů), což zajišťuje reprodukovatelné chování
Formát čísel: Všechna vícebytová čísla v protokolu jsou kódována jako big-endian (network byte order), stejně jako ve stávající verzi blockcopy.
Poznámka k symlinkům: Symlinky se následují (followlinks=True). Přenáší se obsah cílového souboru/adresáře, nikoliv samotný symlink. Tím se zjednodušuje protokol a chování je konzistentní.
Checksum projde cílový adresář a pro každou položku odešle informace. Používají se dva příkazy podle typu položky:
"file" (4 bytes) - command identifier
path_len (4 bytes) - délka cesty v bajtech (big-endian)
path (N bytes) - relativní cesta (UTF-8)
file_size (8 bytes) - velikost souboru (big-endian)
Po příkazu file následují hashe všech bloků (pokud file_size > 0):
"Hash" (4 bytes) - command identifier
block_pos (8 bytes) - pozice v souboru (big-endian)
block_size (4 bytes) - velikost bloku (big-endian)
block_hash (64 bytes) - SHA3-512 digest
Po posledním hashi následuje konec souboru:
"endf" (4 bytes) - command identifier
"dire" (4 bytes) - command identifier
path_len (4 bytes) - délka cesty v bajtech (big-endian)
path (N bytes) - relativní cesta (UTF-8, bez trailing slash)
Adresáře nemají žádná následující data ani ukončovací příkaz.
"done" (4 bytes) - command identifier
dire + "" (path_len=0, root adresář)
file + "src/main.py" + 1024
Hash + 0 + 131072 + <hash1>
... (další hashe pokud je soubor větší)
endf
dire + "src/utils"
file + "src/utils/helper.py" + 512
Hash + 0 + 512 + <hash2>
endf
done
Retrieve čte hashe ze stdin, porovnává se zdrojovými soubory a odesílá změny. Podobně jako checksum používá oddělené příkazy pro soubory a adresáře:
"file" (4 bytes) - command identifier
path_len (4 bytes) - délka cesty v bajtech (big-endian)
path (N bytes) - relativní cesta (UTF-8)
Po příkazu file mohou následovat datové bloky (pro změněné/nové soubory) a vždy metadata:
"data" (4 bytes) - command identifier
block_pos (8 bytes) - pozice v souboru (big-endian)
block_size (4 bytes) - velikost dat (big-endian)
block_data (N bytes) - raw data
"dlzm" (4 bytes) - command identifier
block_pos (8 bytes) - pozice v souboru (big-endian)
block_size (4 bytes) - velikost komprimovaných dat (big-endian)
block_data (N bytes) - LZMA komprimovaná data
"meta" (4 bytes) - command identifier
atime_ns (8 bytes) - access time in nanoseconds (big-endian, signed)
mtime_ns (8 bytes) - modification time in nanoseconds (big-endian, signed)
mode (4 bytes) - file mode/permissions (big-endian)
uid (4 bytes) - user id (big-endian)
gid (4 bytes) - group id (big-endian)
owner_name_len (2 bytes) - délka jména vlastníka (big-endian)
owner_name (N bytes) - jméno vlastníka (UTF-8)
group_name_len (2 bytes) - délka jména skupiny (big-endian)
group_name (N bytes) - jméno skupiny (UTF-8)
total_size (8 bytes) - velikost souboru (big-endian)
"end" (3 bytes) - literal end marker
"endf" (4 bytes) - command identifier
"dire" (4 bytes) - command identifier
path_len (4 bytes) - délka cesty v bajtech (big-endian)
path (N bytes) - relativní cesta (UTF-8, bez trailing slash)
Po příkazu dire následují metadata:
"dmet" (4 bytes) - command identifier
atime_ns (8 bytes) - access time in nanoseconds (big-endian, signed)
mtime_ns (8 bytes) - modification time in nanoseconds (big-endian, signed)
mode (4 bytes) - directory mode/permissions (big-endian)
uid (4 bytes) - user id (big-endian)
gid (4 bytes) - group id (big-endian)
owner_name_len (2 bytes) - délka jména vlastníka (big-endian)
owner_name (N bytes) - jméno vlastníka (UTF-8)
group_name_len (2 bytes) - délka jména skupiny (big-endian)
group_name (N bytes) - jméno skupiny (UTF-8)
"end" (3 bytes) - literal end marker
Adresáře nemají ukončovací příkaz (příkaz dmet ukončuje sekvenci pro daný adresář).
Pro mazání souborů i adresářů (s --delete):
"remv" (4 bytes) - command identifier
path_len (4 bytes) - délka cesty v bajtech (big-endian)
path (N bytes) - relativní cesta (UTF-8)
"done" (4 bytes) - command identifier
# Nový soubor (všechna data)
file + "src/new_file.py"
data + 0 + 1024 + <data>
meta + <metadata s total_size=1024>
endf
# Změněný soubor (jen některé bloky)
file + "src/main.py"
data + 131072 + 512 + <changed_block>
meta + <metadata s total_size>
endf
# Beze změny (jen metadata, žádná data)
file + "src/utils/helper.py"
meta + <metadata>
endf
# Adresář
dire + "src/new_dir"
dmet + <metadata>
dire + "src/utils"
dmet + <metadata>
# Smazat soubor (existuje na dest, ne na source)
remv + "src/old_file.py"
# Smazat adresář
remv + "src/old_dir"
done
Poznámka: Save odvozuje akci z přijatých dat:
- Přišla
data→ zapsat bloky na dané pozice meta.total_size→ truncate soubor na správnou velikostdmet→ vytvořit adresář (pokud neexistuje) a nastavit metadataremv→ smazat položku
- Pokud
--recursive: a. Odeslat'dire's prázdnou cestou (path_len=0) - root adresář b. Rekurzivně projít cílový adresář (followlinks=True) c. Seřadit položky podle cesty (abecedně) d. Pro každou položku:- Adresář: odeslat příkaz
'dire' - Soubor: odeslat příkaz
'file', pak hashe všech bloků, pak'endf'e. Odeslat příkaz'done'
- Adresář: odeslat příkaz
- Jinak (původní chování):
- Zpracovat jako dosud (jeden soubor)
- Pokud
--recursive: a. Vytvořit prázdnou množinuseen_pathsa seznamto_deleteb. Číst příkazy ze stdin (checksum výstup) c. Pro každý'file'z dest:- Přidat cestu do
seen_paths - Zkontrolovat on-demand, jestli soubor existuje na source
- Pokud existuje: porovnat hashe, poslat
'file'+ změněné datové bloky +'meta' - Pokud neexistuje na source: přidat cestu do
to_deleted. Pro každý'dire'z dest: - Přidat cestu do
seen_paths - Zkontrolovat on-demand, jestli adresář existuje na source
- Pokud existuje: poslat
'dire'+'dmet' - Pokud neexistuje na source: přidat cestu do
to_deletee. Po'done'ze stdin: - Projít source adresář (
followlinks=True) - Pro každou položku která není v
seen_paths: poslat jako novou - Pokud
--delete: poslat'remv'pro položky vto_deletef. Odeslat'done'
- Přidat cestu do
- Jinak (původní chování):
- Zpracovat jako dosud
- Pokud
--recursive: a. Vytvořit prázdný seznampending_dir_metadatab. Zpracovat příkazy ze stdin:- Pro každý
'dire':- Vytvořit adresář (pokud neexistuje)
- Uložit metadata z
'dmet'dopending_dir_metadata
- Pro každý
'file':- Otevřít/vytvořit soubor
- Prealokovat místo pomocí
fallocate()(pokud je známa velikost z'meta') - Zapsat všechny přijaté
'data'bloky - Truncate na velikost z
'meta'.total_size - Aplikovat metadata z
'meta'
- Pro každý
'remv':- Smazat soubor nebo adresář (rekurzivně)
c. Po
'done':
- Smazat soubor nebo adresář (rekurzivně)
c. Po
- Aplikovat metadata adresářů z
pending_dir_metadata(v opačném pořadí - potomci před rodiči) d. Skončit
- Pro každý
- Jinak (původní chování):
- Zpracovat jako dosud
Poznámka: Metadata adresářů (zejména mtime) se aplikují až na konci, protože vytváření souborů uvnitř adresáře by změnilo jeho mtime.
blockcopy checksum --recursive /path/to/dest_dir | \
ssh srchost blockcopy retrieve --recursive /path/to/source_dir | \
blockcopy save --recursive /path/to/dest_dir
Smazat soubory na dest, které neexistují na source:
blockcopy save --recursive --delete /path/to/dest_dir
Výchozí chování: bez --delete se soubory na dest zachovávají.
Vyloučit soubory podle vzoru:
blockcopy checksum --recursive --exclude "*.pyc" --exclude "__pycache__" /path/to/dest
Poznámka: Implementace --exclude může být přidána v budoucí verzi.
Pokud cílový adresář neexistuje nebo je prázdný:
- Checksum pošle
'dire's prázdnou cestou (root) a pak'done' - Retrieve pošle všechny položky ze source jako nové
- Save vytvoří celou strukturu od začátku
- Přenášet i prázdné adresáře pomocí příkazu
dire - Save vytvoří adresář pomocí
os.makedirs(exist_ok=True)
Symlinky se následují (followlinks=True při os.walk). Toto znamená:
- Symlink na soubor: přenese se obsah cílového souboru pod cestou symlinku
- Symlink na adresář: rekurzivně se projde cílový adresář
- Broken symlink: přeskočí se s varováním na stderr (cíl neexistuje)
Výhody tohoto přístupu:
- Jednodušší protokol (není potřeba FTYPE_SYMLINK)
- Destination dostane skutečná data, ne závislost na lokálních cestách
- Konzistentní chování nezávislé na umístění source
Pokud se typ položky změní (soubor → adresář nebo adresář → soubor):
- Poslat příkaz
remvpro starou položku - Poslat příkaz
filenebodirepro novou položku (s daty/metadaty)
- Přenášet jako běžné soubory bez hashů
file(checksum) bude mítfile_size = 0- Nepošlou se žádné
Hashpříkazy - V retrieve se pošle
file+meta(bez data bloků)
- Pokud nelze přečíst soubor (PermissionError), vypsat varování na stderr a přeskočit
- Volba
--ignore-errorspro pokračování i při chybách (budoucí rozšíření)
Device nodes, sockets, named pipes (FIFOs) a další speciální soubory:
- Ignorovat (nepřenášet)
- Vypsat varování na stderr
Hardlinky na stejný inode:
- Přenést jako samostatné soubory (každý hardlink se stane nezávislou kopií)
- Pokusit se hardlinky detekovat (sledovat inode) a vypsat varování na stderr
- Budoucí rozšíření může přidat podporu pro zachování hardlinků
Některé filesystémy (např. Linux ext4) povolují libovolné byty v názvech souborů:
- Soubory s nevalidním UTF-8 v cestě přeskočit
- Vypsat varování na stderr
Sparse soubory (soubory s "děrami"):
- Přenášejí se jako plné soubory (nuly se přenesou jako data)
- Budoucí rozšíření může přidat podporu pro detekci a zachování sparse regionů
Adresáře se musí vytvářet před soubory, které obsahují:
- Položky seřadit tak, aby rodiče předcházeli potomkům
- Prakticky: seřadit podle cesty zajistí správné pořadí (
apředa/b.txt)
Soubory a adresáře mazat v opačném pořadí (potomci před rodiči):
- Příkazy
remvseřadit sestupně podle hloubky cesty - Nebo mazat rekurzivně pomocí
shutil.rmtree()pro adresáře
Stávající model ThreadPoolExecutor zachovat:
- 1 vlákno pro procházení adresáře a čtení souborů
- N vláken pro výpočet hashů
- 1 vlákno pro odesílání výstupu
Podobný model:
- 1 vlákno pro čtení source souborů
- N vláken pro porovnávání hashů a přípravu dat
- 1 vlákno pro odesílání výstupu
Jednovláknové zpracování (jako dosud) - zápis musí být sekvenční.
def walk_directory(path: Path, followlinks: bool = True) -> Iterator[Tuple[Path, bool, int]]:
"""
Rekurzivně projde adresář a vrátí informace o položkách.
Yields: (relativní_cesta, is_dir, velikost)
Položky jsou seřazené abecedně podle cesty.
Symlinky se následují (followlinks=True).
"""
def send_file_header(out: IO, path: str, size: int):
"""
Odešle příkaz 'file' (checksum → retrieve, s velikostí).
"""
def send_file_header_retrieve(out: IO, path: str):
"""
Odešle příkaz 'file' (retrieve → save, bez velikosti).
"""
def send_end_file(out: IO):
"""
Odešle příkaz 'endf'.
"""
def send_dire_header(out: IO, path: str):
"""
Odešle příkaz 'dire' (pro oba směry - stejný formát).
"""
def send_dire_meta(out: IO, ...):
"""
Odešle příkaz 'dmet' s metadaty adresáře.
"""
def send_remove(out: IO, path: str):
"""
Odešle příkaz 'remv'.
"""main_checksum()- přidat větev pro--recursivemain_retrieve()- přidat větev pro--recursivemain_save()- přidat větev pro--recursiveget_argument_parser()- přidat nové argumenty
@dataclass
class DirEntry:
"""Reprezentuje položku v adresáři."""
path: str # relativní cesta
is_dir: bool # True pro adresář, False pro soubor
size: int # velikost (0 pro adresáře)
mode: int
uid: int
gid: int
atime_ns: int
mtime_ns: intdef test_file_command_encoding():
"""Test správného kódování příkazu 'file' (checksum)."""
def test_file_command_decoding():
"""Test správného dekódování příkazu 'file'."""
def test_dire_command_encoding():
"""Test správného kódování příkazu 'dire'."""
def test_dire_command_decoding():
"""Test správného dekódování příkazu 'dire'."""
def test_remv_command_encoding():
"""Test kódování příkazu 'remv'."""
def test_meta_encoding():
"""Test kódování příkazu 'meta'."""
def test_dmet_encoding():
"""Test kódování příkazu 'dmet'."""
def test_walk_directory_order():
"""Test že walk_directory vrací položky v abecedním pořadí."""
def test_walk_directory_types():
"""Test správné detekce typů (soubor, adresář)."""
def test_walk_directory_follows_symlinks():
"""Test že walk_directory následuje symlinky."""def test_recursive_copy_basic():
"""
Test základní rekurzivní kopie.
- Vytvoř source adresář s několika soubory a podadresáři
- Spusť pipeline s --recursive
- Ověř že dest odpovídá source
"""
def test_recursive_copy_incremental():
"""
Test inkrementální kopie.
- Vytvoř identický source a dest
- Změň jeden soubor na source
- Spusť pipeline
- Ověř že se přenesl jen změněný blok
"""
def test_recursive_copy_new_files():
"""
Test přidání nových souborů.
- Vytvoř source s více soubory než dest
- Spusť pipeline
- Ověř že nové soubory existují na dest
"""
def test_recursive_copy_delete():
"""
Test mazání souborů s --delete.
- Vytvoř dest s více soubory než source
- Spusť pipeline s --delete
- Ověř že soubory neexistující na source byly smazány
"""
def test_recursive_copy_no_delete():
"""
Test zachování souborů bez --delete.
- Vytvoř dest s více soubory než source
- Spusť pipeline BEZ --delete
- Ověř že extra soubory na dest zůstaly
"""
def test_recursive_copy_empty_directory():
"""
Test kopie prázdných adresářů.
"""
def test_recursive_copy_symlinks_followed():
"""
Test že symlinky jsou následovány.
- Symlink na soubor: přenese se obsah
- Symlink na adresář: rekurzivně se projde obsah
- Ověření že dest obsahuje skutečná data, ne symlinky
"""
def test_recursive_copy_broken_symlink_skipped():
"""
Test že broken symlinky jsou přeskočeny s varováním.
"""
def test_recursive_copy_type_change():
"""
Test změny typu položky.
- Soubor → adresář
- Adresář → soubor
"""
def test_recursive_copy_permissions():
"""
Test zachování oprávnění.
- Různá oprávnění souborů
- Oprávnění adresářů
"""
def test_recursive_copy_timestamps():
"""
Test zachování časových razítek.
- mtime
- atime (pokud --times)
"""
def test_recursive_copy_large_directory():
"""
Test s velkým počtem souborů.
- 1000+ souborů
- Hluboká hierarchie (10+ úrovní)
"""
def test_recursive_copy_special_names():
"""
Test souborů se speciálními názvy.
- Mezery v názvech
- Unicode znaky
- Velmi dlouhé názvy
"""
def test_recursive_copy_zero_size_files():
"""
Test prázdných souborů.
"""
def test_recursive_copy_with_lzma():
"""
Test rekurzivní kopie s LZMA kompresí.
"""
def test_recursive_copy_progress():
"""
Test progress výstupu při rekurzivní kopii.
- Zobrazení aktuálního souboru
- Zobrazení celkového postupu
"""def test_recursive_rsync_identical():
"""
Po blockcopy --recursive by rsync neměl najít žádné rozdíly.
"""
def test_recursive_rsync_permissions():
"""
Ověř že oprávnění odpovídají rsync -a.
"""
def test_recursive_rsync_timestamps():
"""
Ověř že časová razítka odpovídají rsync -a.
"""def test_recursive_permission_denied():
"""
Test chování při chybě přístupu k souboru.
"""
def test_recursive_broken_symlink_warning():
"""
Test že nefunkční symlink vypíše varování a je přeskočen.
"""
def test_recursive_interrupted():
"""
Test přerušení uprostřed kopie.
"""
def test_recursive_disk_full():
"""
Test chování při plném disku na dest.
"""- Definovat nové příkazy protokolu (
file,dire,endf,dmet,remv) - Implementovat
DirEntrydataclass - Implementovat funkce pro kódování/dekódování nových příkazů
- Napsat jednotkové testy protokolu
- Implementovat
walk_directory()s followlinks=True - Rozšířit
main_checksum()o větev pro--recursive - Zachovat existující paralelizaci pro hashování
- Napsat testy pro checksum fázi
- Rozšířit
main_retrieve()o větev pro--recursive - Implementovat on-demand kontrolu existence souborů/adresářů na source
- Implementovat porovnávání hashů a odesílání změněných bloků
- Implementovat generování příkazů
remvpro smazání (s --delete) - Napsat testy pro retrieve fázi
- Rozšířit
main_save()o větev pro--recursive - Implementovat vytváření adresářů
- Implementovat zpracování příkazu
remv - Implementovat volbu
--delete - Napsat testy pro save fázi
- Napsat kompletní integrační testy
- Provést testy s reálnými daty
- Optimalizovat výkon
- Dokumentace
- Množina
seen_pathsse ukládá do paměti (pro detekci nových položek na source) - Seznam
to_deletese ukládá do paměti (pro mazání položek chybějících na source) - Odhad: 1M souborů ≈ 100-200 MB RAM pro cesty
- Soubory číst sekvenčně (lepší pro HDD)
- Zachovat stávající buffer velikosti (128 KB bloky)
- Pro SSD/NVMe by šlo zvýšit paralelizaci čtení souborů
- LZMA komprese výrazně sníží přenos dat
- Zvážit volbu
--compress-levelpro LZMA
Tato sekce dokumentuje klíčová rozhodnutí učiněná během návrhu a jejich zdůvodnění.
Původní návrh používal obecný příkaz entr (entry) s polem typu. Oddělené příkazy jsou:
- Čitelnější v dokumentaci i při debugování
- Jednodušší na parsování (není potřeba switch na typ)
- Explicitnější o tom, co následuje (hashe pro soubory, nic pro adresáře)
Původní návrh obsahoval FLAG_NEW, FLAG_MODIFIED, FLAG_UNCHANGED pro indikaci akce. Tyto flags byly odstraněny, protože save může inferovat akci z přijatých dat:
- Přišly bloky
data→ zapsat na dané pozice - Přišlo jen
metabezdata→ soubor beze změny, pouze aktualizovat metadata - Přišlo
remv→ smazat položku
Toto zjednodušuje protokol a eliminuje redundanci.
Volba --delete určuje, zda se mají mazat položky chybějící na source. Retrieve je správné místo, protože:
- Retrieve zná stav source adresáře
- Retrieve generuje příkazy
remvpro položky k smazání - Save pouze vykonává přijaté příkazy
Původní návrh načítal celý manifest source adresáře do paměti. On-demand přístup:
- Kontroluje existenci souborů/adresářů na source při zpracování každé položky z dest
- Po zpracování všech položek z dest projde source a najde nové položky
- Výhoda: nižší paměťové nároky pro velké adresáře
- Nevýhoda: více syscalls (stat pro každou položku)
I když adresáře nemají hashe, checksum posílá dire příkazy protože:
- Umožňuje detekci prázdných adresářů (které by jinak nebyly vidět)
- Umožňuje detekci adresářů, které na source neexistují (pro
--delete) - Retrieve potřebuje vědět o všech položkách na dest
Symlinky se nepřenášejí jako symlinky, ale jako jejich cílový obsah:
- Jednodušší protokol (není potřeba typ SYMLINK)
- Destination dostane skutečná data, ne závislost na lokálních cestách source
- Konzistentní chování nezávislé na umístění source
- Budoucí rozšíření
--no-dereferencemůže přidat podporu pro kopírování symlinků
Receiver může rozlišit režim podle prvního příkazu:
'dire'→ recursive režim (první příkaz je vždy root adresář spath_len=0)'Hash'→ single-file režim
Toto umožňuje:
- Auto-detekci režimu bez nutnosti explicitního flags
- Root adresář je legitimní položka - jeho metadata se také přenesou
- I prázdný dest adresář pošle
'dire's prázdnou cestou, takže recursive režim je vždy rozpoznatelný
--exclude/--includevzory--dry-runpro simulaci--checksumpro vynucení porovnání hashů i při shodné velikosti/mtime--ignore-existingpro přeskočení existujících souborů--no-dereferencepro kopírování symlinků jako symlinků (místo následování)- Podpora rozšířených atributů (xattr)
- Podpora ACL
- Podpora sparse souborů
- Obousměrná synchronizace (mimo scope - komplikuje jednosměrný tok)
- Podpora hardlinků (zachování hardlink vztahů)
Tato sekce popisuje pokročilé filesystémové funkce, které blockcopy zatím nepodporuje, ale mohou být přidány v budoucnu.
Rozšířené atributy jsou páry klíč-hodnota připojené k souborům a adresářům mimo standardní metadata (permissions, timestamps).
Příklady použití:
- SELinux security labels (
security.selinux) - Capabilities (
security.capability) - Uživatelská metadata (
user.*) - POSIX ACL uložené jako xattr (
system.posix_acl_access)
Ukázka práce s xattr:
# Nastavení xattr
setfattr -n user.comment -v "důležitý soubor" soubor.txt
# Čtení xattr
getfattr -n user.comment soubor.txt
# soubor.txt: user.comment="důležitý soubor"
# Seznam všech xattr
getfattr -d soubor.txt
# SELinux context
ls -Z soubor.txt
# -rw-r--r--. user user unconfined_u:object_r:user_home_t:s0 soubor.txtPython API:
import os
# Čtení
value = os.getxattr('/path/to/file', 'user.comment')
# Zápis
os.setxattr('/path/to/file', 'user.comment', b'hodnota')
# Seznam
attrs = os.listxattr('/path/to/file')Use cases pro blockcopy:
- Zachování SELinux kontextu při kopírování mezi servery se SELinux
- Přenos uživatelských metadat (tagy, komentáře)
- Zachování capabilities pro executable soubory
ACL rozšiřují standardní UNIX permissions (owner/group/other) o možnost nastavit oprávnění pro libovolné uživatele a skupiny.
Ukázka práce s ACL:
# Nastavení ACL - uživatel alice má právo číst
setfacl -m u:alice:r-- soubor.txt
# Nastavení ACL - skupina devs má právo číst a zapisovat
setfacl -m g:devs:rw- soubor.txt
# Zobrazení ACL
getfacl soubor.txt
# # file: soubor.txt
# # owner: bob
# # group: users
# user::rw-
# user:alice:r--
# group::r--
# group:devs:rw-
# mask::rw-
# other::r--
# Default ACL pro adresář (dědí se na nové soubory)
setfacl -d -m g:devs:rw- adresar/Python API:
import posix1e # pylibacl
# Čtení ACL
acl = posix1e.ACL(file='/path/to/file')
print(acl)
# Kopírování ACL
acl.applyto('/path/to/dest')Use cases pro blockcopy:
- Sdílené adresáře s komplexními oprávněními (více týmů, různé úrovně přístupu)
- Webové servery s oddělením oprávnění pro různé aplikace
- Zachování přístupu pro service accounts
Project quotas umožňují nastavit diskové kvóty na libovolné adresářové stromy, nezávisle na uživatelích a skupinách.
Co je "project": Project je číselný identifikátor (project ID) přiřazený adresářům a souborům. Všechny soubory se stejným project ID sdílejí společnou kvótu. Na rozdíl od user/group quotas není project vázán na existujícího uživatele.
Ukázka konfigurace:
# /etc/projid - mapování názvů na project ID
webapp:100
logs:101
backups:102
# /etc/projects - mapování project ID na adresáře
100:/var/www/webapp
101:/var/log/webapp
102:/backup/webapp
# Inicializace project quota na adresáři
xfs_quota -x -c 'project -s webapp' /mount
# Nastavení kvóty (soft 8GB, hard 10GB)
xfs_quota -x -c 'limit -p bsoft=8g bhard=10g webapp' /mount
# Zobrazení využití
xfs_quota -x -c 'report -p' /mount
# Project ID Used Soft Hard Warn/Grace
# ---------- ---------- ---------- ---------- ------------
# webapp 2048000 8388608 10485760 00 [--------]Atribut na souboru:
# Zobrazení project ID souboru
xfs_io -c 'lsattr -p' /var/www/webapp/index.html
# 100 /var/www/webapp/index.html
# Nastavení project ID
xfs_io -c 'chattr -p 100' /var/www/webapp/newfile.txtProč nepřenášíme:
- Project ID jsou specifické pro konkrétní server a jeho konfiguraci
- Destination server má typicky jiné project ID mapování
- Kvóty se nastavují na úrovni administrace, ne při kopírování dat
Reflinks umožňují vytvořit "lehkou kopii" souboru sdílením datových bloků. Bloky se skutečně zkopírují až při modifikaci (Copy-on-Write).
Ukázka:
# Vytvoření reflink kopie (okamžité, bez kopírování dat)
cp --reflink=always source.img dest.img
# Nebo pomocí xfs_io
xfs_io -c 'reflink source.img' dest.imgProč není relevantní pro blockcopy:
- Blockcopy je primárně pro síťový přenos mezi různými servery
- Reflinks fungují pouze v rámci jednoho filesystému
- Pro lokální kopírování na stejném XFS je vhodnější
cp --reflinknebo rsync - Blockcopy přenáší data přes síť, takže reflink optimalizace není aplikovatelná
fallocate() umožňuje předem alokovat místo pro soubor bez nutnosti zapisovat data. To může zrychlit zápis velkých souborů.
Použití v blockcopy:
Fáze save může před zápisem souboru prealokovat místo pomocí fallocate():
import os
def preallocate_file(fd, size):
"""Prealokuje místo pro soubor."""
try:
os.posix_fallocate(fd, 0, size)
except OSError:
# Fallback pro filesystémy bez podpory fallocate
passVýhody:
- Rychlejší zápis (filesystem nemusí alokovat bloky během zápisu)
- Menší fragmentace souborů
- Včasné zjištění nedostatku místa (před zahájením přenosu dat)
Poznámka: Toto bude implementováno jako součást --recursive.
Linux filesystémy (ext2/3/4, XFS, btrfs) podporují speciální atributy souborů nastavované pomocí chattr.
Běžné atributy:
| Flag | Název | Popis |
|---|---|---|
i |
Immutable | Soubor nelze měnit, mazat, přejmenovávat ani linkovat |
a |
Append-only | Do souboru lze pouze přidávat data (užitečné pro logy) |
d |
No-dump | Soubor se přeskočí při dump zálohování |
A |
No-atime | Neaktualizovat atime při čtení |
S |
Synchronous | Změny se zapisují synchronně na disk |
D |
Synchronous directory | Synchronní zápis pro adresář |
e |
Extents | Soubor používá extenty (ext4, automaticky) |
j |
Journaled | Data se zapisují do žurnálu před zápisem na disk |
Ukázka práce s atributy:
# Zobrazení atributů
lsattr soubor.txt
# ----i--------e---- soubor.txt
# Nastavení immutable
sudo chattr +i soubor.txt
# Odebrání immutable
sudo chattr -i soubor.txt
# Nastavení append-only (užitečné pro logy)
sudo chattr +a /var/log/app.log
# Rekurzivní nastavení
sudo chattr -R +i /etc/important/Python API:
import fcntl
import struct
# Konstanty pro ioctl
FS_IOC_GETFLAGS = 0x80086601
FS_IOC_SETFLAGS = 0x40086602
# Flags
FS_IMMUTABLE_FL = 0x00000010
FS_APPEND_FL = 0x00000020
FS_NODUMP_FL = 0x00000040
def get_file_attrs(path):
"""Přečte file attributes."""
with open(path, 'r') as f:
buf = struct.pack('i', 0)
result = fcntl.ioctl(f.fileno(), FS_IOC_GETFLAGS, buf)
return struct.unpack('i', result)[0]
def set_file_attrs(path, flags):
"""Nastaví file attributes."""
with open(path, 'r') as f:
buf = struct.pack('i', flags)
fcntl.ioctl(f.fileno(), FS_IOC_SETFLAGS, buf)Use cases:
- Ochrana konfiguračních souborů před náhodnou změnou (
+i) - Append-only logy pro audit trail (
+a) - Optimalizace výkonu vypnutím atime (
+A)
Proč neimplementujeme:
- Immutable soubory vyžadují root pro změnu - komplikuje přenos
- Atributy jsou často specifické pro konkrétní deployment
- rsync také standardně nepřenáší tyto atributy
- Lze přidat jako budoucí rozšíření s volbou
--file-attrs