Skip to content

Linux Attitude

Le libre est un état d'esprit

Archive

Archive for August, 2012

Niveau :      
Résumé : atime, ctime, mtime, crtime

Je constate que je n'ai jamais fait d'article sur les dates de fichier, c'est bien dommage car on me pose souvent la question. Voila qui va régler ce malentendu.

ctime et mtime

Unix depuis très longtemps ne stocke pas la date de création d'un fichier (contrairement à windows). A la place il stocke 3 dates au format timestamp unix. Les plus difficiles à comprendre sont mtime et ctime. Mais en réalité c'est simple, il suffit d'apprendre leur nom :

  • mtime : modification time, date de dwœernière modification du contenu du fichier
  • ctime : change time, date de dernier changement des métadonnées du fichier (par exemple le propriétaire)

Rien que pour vous, j'ai un moyen antimnémotechnique : M comme Contenu et C comme Métadonnée. Je ne sais pas vous, mais moi ça marche.

Attention, la modification du contenu d'un fichier modifie sa date de modification, qui est une métadonnée ... Elle modifie donc aussi sa date de changement. En conséquence de quoi on a toujours ctime >= mtime.

atime

Historiquement unix a eu l'idée originale de stocker la date de dernier accès à un fichier, le atime (access time). Ça a l'air cool, mais en réalité c'est une fausse bonne idée car elle interfère avec la notion fondamentale de lecture. En effet, pour mettre à jour le atime, chaque lecture impliquera une écriture.

Plus le temps passe plus le atime est problématique. Ça a commencé avec les sites web qui avaient des problèmes de performance à cause de la mise à jour du atime lors de la lecture des fichiers. On a alors inventé la possibilité de désactiver le atime au montage (option noatime).

Puis ça continue avec le SSD, écrire le atime implique une écriture toute petite à chaque lecture de fichier, ce qui implique la modification d'un inode et donc d'un block disque. Et avec le fonctionnement du SSD cela implique la lecture et la réécriture d'un gros bloc de données, donc des problèmes de performances. Puisque le atime reste utilisé par quelques applications (aussi rares soient-elles), on a inventé l'option relatime, qui ne met a jour le atime que s'il était égal ou antérieur au ctime. Ça permet de détecter une lecture depuis la dernière modification tout en faisant sauter un grand nombre de mises à jour du atime (mais pas toutes).

Le problème suivant intervient avec les snapshots, lorsqu'on utilise des snapshots de disques (à travers LVM ou à travers un serveur de VM), on se retrouve à mettre à jour le disque de snapshot et donc occuper de l'espace disque rien qu'en le lisant. Le summum étant atteint avec btrfs. Si vous faites un snapshot avec btrfs il ne vous prendra quasiment aucune place sur le disque. Si vous faites un find sur ce snapshot, la mise à jour du atime va quasiment remplir l'espace disque du snapshot d'une copie complète des données originales.

Conclusion, oubliez le atime ...

crtime

Comme je vous l'ai dit, windows stocke la date de création, c'est donc tout naturellement que les linuxiens ont voulu la même fonctionnalité. C'est enfin chose faite avec ext4. En effet, ext4 a profité de l'agrandissement de la taille des inode (de 128 à 256) pour stocker plus d'informations, celles-ci incluent la date à la nanoseconde près et la date de création.

Cool, mais comment fait-on ?

Malheureusement, les outils habituels n'ont pas encore été mis à jour pour récupérer cette information. Il nous faut donc le faire à la main :

# d'abord on récupère le numéro d'inode du fichier
$ stat --format "%i" /chemin/vers/le/fichier
# puis on récupère la date de création
$ debugfs -R 'stat <123456>' /dev/partition | grep crtime

Notez que ça ne marche que sur des partitions ayant des inodes de 256 octets, ce qui exclue les systèmes de fichiers mis à jour depuis ext2 et ceux venant d'un ext3 avec des inodes de 128 octets.

Niveau :      
Résumé : alias / /%{username}

Aujourd'hui je voudrais faire un serveur web qui se comporterait comme un serveur FTP ou SFTP. Lorsqu'un utilisateur unix se connecte à un serveur FTP avec un login et un mot de passe, il lui est présenté un contenu qui lui est propre (son $HOME par exemple).

Comment faire l'équivalent avec apache ?

Dit autrement, je voudrais que les 2 commandes suivantes renvoient un contenu différent :

$ wget http://userA@www.monserveur.com/ # répertoire A
$ wget http://userB@www.monserveur.com/ # répertoire B

Pour cela il faut jouer avec les RewriteRules, mais c'est plus balaise que ça en a l'air.

Premièrement il faut une authentification. J'ai choisi ldap, mais prenez la méthode que vous préférez : http://httpd.apache.org/docs/2.2/mo... tout ce qui commence par mod_authn est valable. Voici comment on la met en place :

<Location />
        AuthType basic
        AuthName "My server"
        AuthBasicProvider ldap
        AuthLDAPURL ldap://serveur.ldap.com/dc=domaine,dc=com?uid?sub
        AuthLDAPBindDN cn=user,ou=technical,dc=domaine,dc=com
        AuthLDAPBindPassword password
        Require valid-user
</Location>

Ensuite on joue avec RewriteRule, les variables s'appellent sous la forme %{ENV:VARIABLE}

# utilisez %{ENV:USER_AUTH} pour ceux qui n'ont pas choisi l'authent LDAP
RewriteRule ^(.*) /%{ENV:AUTHENTICATE_uid}/$1

Mais ça ne marche pas. Tout d'abord on apprend qu'il faut mettre les règles dans le Location sinon les variables d'authentification ne sont pas disponibles. Ensuite on a oublié d'activer le rewrite engine. Et enfin dans un location (ou un directory) on ne matche plus la request uri mais le chemin créé à partir de cette uri. Cela qui une fois corrigé nous donne quelque chose comme ça :

<Location />
        RewriteEngine On
        # le résultat sera automatiquement préfixé par le DocumentRoot si on ne le fait pas nous même
        # notez l'absence du / initial dans le pattern ...
        RewriteRule ^var/www/html/(.*) /%{ENV:AUTHENTICATE_uid}/$1 
</Location>

Mais ça ne marche toujours pas. En effet, le rewrite engine d'apache est réentrant, quelle que soit la modification et quel que soit le flag utilisé, si une url est modifiée, apache fait une redirection interne et relance toute la machinerie (dont les redirections). Pour les petits malins, non [L] n'est pas fait pour ça, par contre la doc évoque un [END] qui aurait cette fonctionnalité mais je ne l'ai pas trouvé.

Il nous faut donc un moyen de détecter qu'une url a déjà été transformée par une RewriteRule. Malheureusement les variables (comme %{ENV:AUTHENTICATE_uid}) ne sont valables que dans la partie gauche de RewriteCond ou dans la partie droite de RewriteRule ce qui nous limite sévèrement. On ne peut pas matcher le chemin en pour détecter qu'il contient le répertoire de l'utilisateur. De plus on ne peut pas utiliser une autre racine, apache ajouterait automatiquement le DocumentRoot en cours.

J'ai essayé en utilisant des variables d'environnement temporaire (avec [E=VAR:VALUE]), mais le rewrite engine l'évalue trop tard et ne détecte pas la nouvelle valeur de la variable modifiée par lui-même.

Ma solution est donc de mettre en place un unique répertoire contenant les utilisateurs avec un nom improbable car il ne pourra pas être utilisé comme nom de répertoire dans les répertoires utilisateurs. Et d'utiliser ce nom de répertoire comme marqueur d'url déjà traitée. Ce qui nous donne :

        # mon répertoire s'appelle ____data____
        RewriteRule ^var/www/html/(?!____data____/)(.*) /____data____/%{ENV:AUTHENTICATE_uid}/$1

C'est bien joli, mais ça n'empercherait pas un utilisateur d'aller voir dans le répertorie de son voisin. En effet, ce ne sont que des rewrite rules, pas des droits d'accès. Puisqu'on ne peut pas utiliser les noms de répertoire utilisateur dans les require pour le matcher avec les nom d'utilisateur (on se mordrait la queue (et ça fait mal (au dos))). Il nous faut donc le garantir d'une autre façon, en forçant tout utilisateur a n'accéder qu'à son répertoire avec une autre règle.

        # ce qu'on veut c'est éviter l'utilisateur qui voudrait bypasser la première règle avec un http://www.monserveur.com/____data____/userX
        RewriteRule ^var/www/html/____data____/.*?/(.*) /var/www/html/____data____/%{ENV:AUTHENTICATE_uid}/$1
        # et on voudrait ausst éviter que l'utilisateur puisse scanner la racine
        RewriteRule ^var/www/html/____data____/?$ /var/www/html/____data____/%{ENV:AUTHENTICATE_uid}/

Notez l'ajout de /var/www/html dans les chaines de remplacement, c'est pour éviter qu'apache pense qu'on a modifié le chemin si on n'a rien changé.

Et c'est gagné, on a enfin trouvé !

Je vous laisse donc profiter du résultat :
Edit : la version finale minimise l'appel aux règles, prend en compte les chemins qui se terminent par / et les tentatives d'accès à des répertoires non autorisés.

<Location />
        AuthType basic
        AuthName "My server"
        AuthBasicProvider ldap
        AuthLDAPURL ldap://serveur.ldap.com/dc=domaine,dc=com?uid?sub
        AuthLDAPBindDN cn=user,ou=technical,dc=domaine,dc=com
        AuthLDAPBindPassword password
        Require valid-user

        # make http behave like ftp
        RewriteEngine On
        # create home dir var
        RewriteRule .* - [E=USER_ROOT:/var/www/html/____data____/%{ENV:AUTHENTICATE_homeDirectory}]
        RewriteCond %{ENV:USER_ROOT} !-d
        RewriteRule .* - [E=USER_ROOT:/var/www/html/forbidden]

        # redirection vers les repertpoires utilisateur
        RewriteCond %{REQUEST_FILENAME} !^/var/www/html/____data____
        RewriteRule ^var/www/html/(.*) %{ENV:USER_ROOT}/$1 [L,DPI]

        # impossibilite de lire la racine
        RewriteCond %{REQUEST_FILENAME} ^/var/www/html/____data____/?$
        RewriteRule .* %{ENV:USER_ROOT} [L,DPI]

        # impossibilite de lire le repertoire d'un autre
        RewriteCond %{ENV:AUTHENTICATE_homeDirectory}|%{REQUEST_FILENAME} !^(.*?)\|/var/www/html/____data____/\1
        RewriteRule ^var/www/html/____data____/?[^/]*/?(.*) %{ENV:USER_ROOT}/$1 [L,DPI]
</Location>

<Directory /var/www/html/forbidden>
        deny from all
</Directory>

Note : et pour ceux qui voudraient vraiment faire du FTP avec apache il y a mod_ftp.