Dans cette article j’essaie de résumer et d’expliquer les possibilitées offertes par fallocate (util-linux) et truncate (coreutils).
truncate permet de modifier la fin d’un fichier, réduire ou augmenter la taille d’un fichier.
fallocate permet de modifier n’importe quelle bloque d’un fichier.
truncate
Dans cette section j’interprète les résultats brutes sur truncate, obtenus lors des tests.
fichier non ouvert
- init
- état du fichier:
<data:truc><data:much> - action: truncate
- état du fichier:
<data:truc> - action: écriture en fin de fichier
- état du fichier:
<data:truc><data:plop>
fichier ouvert en écriture simple (non append)
- init
- état du fichier, et « seek » du processus:
<data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: truncate
- état du fichier, et « seek » du processus:
<data:truc>-----------|
« seek » du processus: 2 blocs après le début du fichier.
Note: le seek pointe sur une position qui n’existe pas dans le fichier. - action: écriture en fin de fichier
- état du fichier, et « seek » du processus:
<data:truc><metadata:\0><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
Note: en début d’écriture, le noyau enregistre des \0 sous forme de métadata pour combler l’écart entre la fin du fichier et le seek ; transformant le fichier en sparse file.
fichier ouvert en écriture append
- init
- état du fichier:
<data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier. - action: truncate
- état du fichier:
<data:truc> - action: écriture
- état du fichier:
<data:truc><data:plop>
fallocate
Dans cette section j’interprète les résultats brutes sur fallocate, obtenus lors des tests.
collapse-range
L’option « –collapse-range » n’est pas supportée sur btrfs.
fichier non ouvert
- init
- état du fichier:
<data:truc><data:much> - action: collapse-range
- état du fichier:
<data:much> - action: écriture en fin de fichier
- état du fichier:
<data:much><data:plop>
fichier ouvert en écriture simple (non append)
- init
- état du fichier, et « seek » du processus:
<data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: collapse-range
- état du fichier, et « seek » du processus:
<data:much>-----------|
« seek » du processus: 2 blocs après le début du fichier.
Note: le seek pointe sur une position qui n’existe pas dans le fichier. - action: écriture
- état du fichier, et « seek » du processus:
<data:much><metadata:\0><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
Note: en début d’écriture, le noyau enregistre des \0 sous forme de métadata pour combler l’écart entre la fin du fichier et le seek ; transformant le fichier en sparse file.
fichier ouvert en écriture append
- init
- état du fichier:
<data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier. - action: collapse-range
- état du fichier:
<data:much> - action: écriture
- état du fichier:
<data:much><data:plop>
punch-hole
fichier non ouvert
- init
- état du fichier:
<data:truc><data:much> - action: punch-hole
- état du fichier:
<metadata:\0><data:much> - action: écriture en fin de fichier
- état du fichier:
<metadata:\0><data:much><data:plop>
fichier ouvert en écriture simple (non append)
- init
- état du fichier, et « seek » du processus:
<data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: punch-hole
- état du fichier, et « seek » du processus:
<metadata:\0><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: écriture
- état du fichier, et « seek » du processus:
<metadata:\0><data:much><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
fichier ouvert en écriture append
- init:
- état du fichier:
<data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier. - action: punch-hole
- état du fichier:
<metadata:\0><data:much> - action: écriture
- état du fichier:
<metadata:\0><data:much><data:plop>
zero-range
L’option « –zero-range » n’est pas supportée sur btrfs.
fichier non ouvert
- init
- état du fichier:
<data:truc><data:much> - action: collapse-range
- état du fichier:
<data:\0><data:much> - action: écriture en fin de fichier
- état du fichier:
<data:\0><data:much><data:plop>
fichier ouvert en écriture simple (non append)
- init
- état du fichier, et « seek » du processus:
<data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: zero-range
- état du fichier, et « seek » du processus:
<data:\0><data:much>|
« seek » du processus: 2 blocs après le début du fichier. - action: écriture
- état du fichier, et « seek » du processus:
<data:\0><data:much><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
fichier ouvert en écriture append
- init
- état du fichier:
<data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier. - action: zero-range
- état du fichier:
<data:\0><data:much> - action: écriture
- état du fichier:
<data:\0><data:much><data:plop>
Note sur l’ouverture des fichiers
seek
Dans cet article, j’appel la position d’écriture d’un processus sur un fichier son « seek ». C’est un abut de language. En fait « seek » (au plutôt lseek), dans de nombreux languages de programmation, est le nom donné à la fonction permettant de modifier cette position.
fichier ouvert en écriture simple (non append)
On peut savoir qu’un fichier est ouvert en écriture « simple » (non append) par un processus de pid P en observant les flags d’ouvertures de son file discriptor dans /proc/<P>/fdinfo/….
Pour faire simple, si les flags match l’expression rationel ^\d{4}0\d{2}[12]$ alors le fichier est ouvert en écriture « simple ».
fichier ouvert en écriture append
On peut savoir qu’un fichier est ouvert en écriture « append » par un processus de pid P en observant les flags d’ouvertures de son file discriptor dans /proc/<P>/fdinfo/….
Pour faire simple, si les flags match l’expression rationel ^\d{4}2\d{2}[12]$ alors le fichier est ouvert en écriture « simple ».
C/C++
En C++ un fichier ouvert en « écriture simple » est un fichier qui a été ouvert avec le flag « out » mais pas le flag « app ». Un fichier ouvert en « écriture/append » a été ouvert avec les flag « out » et « app ».
Tests
Pour comprendre le fonctionnement d’un outil, le mieu est parfois, tous simplement, de le tester.
J’ai donc créé un script de test (appelé tests.sh) et l’ai fait tourner sur les deux filesystem que j’utilise régulièrement : btrfs et ext4.
#!/bin/bash
export LC_ALL=C
trap 'rm -f 8KiB_init_file testfile fifo_input' EXIT
echo -n -e "\n## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs\n\n"
set -x
dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1 &> /dev/null
du --block-size=1 --apparent-size 8KiB_init_file
du --block-size=1 8KiB_init_file
function print_size {
sleep 1
du --block-size=1 --apparent-size $1
du --block-size=1 $1
dd if=/dev/urandom of=$2 bs=4KiB count=1 conv=notrunc oflag=append &> /dev/null
sleep 1
du --block-size=1 --apparent-size $1
du --block-size=1 $1
}
function test_collapse-range {
sleep 1
fallocate --collapse-range --offset 0 --length 4KiB $1 && \
print_size $1 $2
echo
}
function test_punch-hole {
sleep 1
fallocate --punch-hole --offset 0 --length 4KiB $1 && \
print_size $1 $2
echo
}
function test_zero-range {
sleep 1
fallocate --zero-range --offset 0 --length 4KiB $1 && \
print_size $1 $2
echo
}
function test_truncate {
sleep 1
truncate -s 4K $1 && \
print_size $1 $2
echo
}
echo -n -e "\n##########\n## test sur fichier non ouvert\n##########\n\n"
cp 8KiB_init_file testfile
test_collapse-range testfile testfile
cp 8KiB_init_file testfile
test_punch-hole testfile testfile
cp 8KiB_init_file testfile
test_zero-range testfile testfile
cp 8KiB_init_file testfile
test_truncate testfile testfile
echo -n -e "\n##########\n## test sur fichier ouvert (non append)\n##########\n\n"
mkfifo fifo_input
cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_collapse-range testfile fifo_input
cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_punch-hole testfile fifo_input
cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_zero-range testfile fifo_input
cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_truncate testfile fifo_input
echo -n -e "\n##########\n## test sur fichier ouvert (append)\n##########\n\n"
cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_collapse-range testfile fifo_input
cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_punch-hole testfile fifo_input
cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_zero-range testfile fifo_input
cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_truncate testfile fifo_input
open est un petit programe créé pour cette démonstration, il ouvre testfile, se déplace à la fin du fichier est y écrit ce qu’il lit dans fifo. L’option « -append » permet de faire en sorte qu’il ouvre testfile en mode append.
ext4
Voici les résultats obtenu sur ext4.
max@test-host $ ./tests.sh
## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs
+ dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1
+ du --block-size=1 --apparent-size 8KiB_init_file
8192 8KiB_init_file
+ du --block-size=1 8KiB_init_file
8192 8KiB_init_file
+ echo -n -e '\n##########\n## test sur fichier non ouvert\n##########\n\n'
##########
## test sur fichier non ouvert
##########
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile testfile
+ sleep 1
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile testfile
+ sleep 1
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
12288 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile testfile
+ sleep 1
+ truncate -s 4K testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ echo -n -e '\n##########\n## test sur fichier ouvert (non append)\n##########\n\n'
##########
## test sur fichier ouvert (non append)
##########
+ mkfifo fifo_input
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ ./open fifo_input testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
12288 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ echo -n -e '\n##########\n## test sur fichier ouvert (append)\n##########\n\n'
##########
## test sur fichier ouvert (append)
##########
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
12288 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ rm -f 8KiB_init_file testfile fifo_input
brtfs
Voici les résultats obtenu sur btrfs.
max@test-host $ ./tests.sh
## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs
+ dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1
+ du --block-size=1 --apparent-size 8KiB_init_file
8192 8KiB_init_file
+ du --block-size=1 8KiB_init_file
8192 8KiB_init_file
+ echo -n -e '\n##########\n## test sur fichier non ouvert\n##########\n\n'
##########
## test sur fichier non ouvert
##########
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile testfile
+ sleep 1
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile testfile
+ sleep 1
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile testfile
+ sleep 1
+ truncate -s 4K testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ echo -n -e '\n##########\n## test sur fichier ouvert (non append)\n##########\n\n'
##########
## test sur fichier ouvert (non append)
##########
+ mkfifo fifo_input
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
10240 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
10240 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ echo -n -e '\n##########\n## test sur fichier ouvert (append)\n##########\n\n'
##########
## test sur fichier ouvert (append)
##########
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo
+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096 testfile
+ du --block-size=1 testfile
4096 testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192 testfile
+ du --block-size=1 testfile
8192 testfile
+ echo
+ rm -f 8KiB_init_file testfile fifo_input