Pouvez-vous changer ce vers quoi un lien symbolique pointe après sa création?

122

Un système d'exploitation fournit-il un mécanisme (appel système - pas un programme en ligne de commande) pour changer le chemin référencé par un lien symbolique (lien symbolique) - autrement qu'en dissociant l'ancien et en en créant un nouveau?

Le standard POSIX ne le fait pas. Solaris 10 ne le fait pas. MacOS X 10.5 (Leopard) ne le fait pas. (Je suis assez certain que ni AIX ni HP-UX ne le font non plus. À en juger par cette liste d' appels système Linux, Linux n'a pas non plus un tel appel système.)

Y a-t-il quelque chose qui fait?

(Je m'attends à ce que la réponse soit "Non".)


Puisque prouver un négatif est difficile, réorganisons la question.

Si vous savez que certains systèmes d'exploitation (de type Unix) qui ne sont pas déjà répertoriés n'ont pas d'appel système pour réécrire la valeur d'un lien symbolique (la chaîne renvoyée par readlink()) sans supprimer l'ancien lien symbolique et en créer un nouveau, veuillez l'ajouter - ou les - dans une réponse.

Jonathan Leffler
la source
Quel est le problème avec une simple reconnexion? Pourquoi ne pas simplement émettre la lncommande (ou l'équiavalent API) écrasant l'ancien lien? Quel problème rencontrez-vous?
S.Lott
9
Drôle - je demande s'il y a un appel système pour faire un travail de programmation, et la question est marquée «appartient à un autre site».
Jonathan Leffler
3
Drôle - Il n'était absolument pas clair que vous recherchiez un appel système et vous venez de modifier la question pour ajouter ce détail. Alors, comment pouvez-vous vous attendre à ce que les gens déduisent quelque chose avant même de l'écrire?
Pascal Thivent
2
@ S.Lott: J'écris un article sur la sécurité et les liens symboliques. À un moment donné, je fais l'affirmation "le propriétaire réel, le groupe, les autorisations sur le lien symbolique lui-même sont sans importance" et le raisonnement est que le propriétaire du lien symbolique ne peut que le supprimer et ne pas changer la valeur. Je vérifie qu'il n'y a pas d'autre moyen que de supprimer le lien symbolique pour obtenir l'effet de «réécriture de la valeur du lien symbolique». J'ignore l'accès direct au disque brut et je pirate le FS de cette façon - cela nécessite des privilèges root et mes préoccupations concernent les utilisateurs non root, pas ce que root peut faire.
Jonathan Leffler
4
@Pascal: Je suis désolé - je n'avais pas réalisé qu'il n'était pas clair que je parlais d'appels système jusqu'à ce que les gens partent sur une tangente de ce que je voulais (qui était évidemment différent de ce que j'ai dit). Je suis désolé d'avoir induit en erreur; ce n'était pas intentionnel.
Jonathan Leffler

Réponses:

106

AFAIK, non, vous ne pouvez pas. Vous devez le supprimer et le recréer. En fait, vous pouvez écraser un lien symbolique et ainsi mettre à jour le chemin référencé par celui-ci:

$ ln -s .bashrc test
$ ls -al test
lrwxrwxrwx 1 pascal pascal 7 2009-09-23 17:12 test -> .bashrc
$ ln -s .profile test
ln: creating symbolic link `test': File exists
$ ln -s -f .profile test
$ ls -al test
lrwxrwxrwx 1 pascal pascal 8 2009-09-23 17:12 test -> .profile

EDIT : Comme l'OP l'a souligné dans un commentaire, l'utilisation de l' --forceoption fera lneffectuer un appel système à unlink()avant symlink(). Ci-dessous, la sortie de stracesur ma boîte Linux le prouvant:

$ strace -o /tmp/output.txt ln -s -f .bash_aliases test
$ grep -C3 ^unlink /tmp/output.txt 
lstat64("test", {st_mode=S_IFLNK|0777, st_size=7, ...}) = 0
stat64(".bash_aliases", {st_mode=S_IFREG|0644, st_size=2043, ...}) = 0
symlink(".bash_aliases", "test")        = -1 EEXIST (File exists)
unlink("test")                          = 0
symlink(".bash_aliases", "test")        = 0
close(0)                                = 0
close(1)                                = 0

Donc je suppose que la réponse finale est "non".

EDIT : Ce qui suit est copié de la réponse d' Arto Bendiken sur unix.stackexchange.com, vers 2016.

Cela peut en effet être fait de manière atomique rename(2), en créant d'abord le nouveau lien symbolique sous un nom temporaire, puis en écrasant proprement l'ancien lien symbolique en une seule fois. Comme l'indique la page de manuel :

Si newpath fait référence à un lien symbolique, le lien sera écrasé.

Dans le shell, vous feriez cela avec mv -Tcomme suit:

$ mkdir a b
$ ln -s a z
$ ln -s b z.new
$ mv -T z.new z

Vous pouvez stracecette dernière commande pour vous assurer qu'elle utilise bien rename(2)sous le capot:

$ strace mv -T z.new z
lstat64("z.new", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
lstat64("z", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
rename("z.new", "z")                    = 0

Notez que dans ce qui précède, les deux mv -Tet stracesont spécifiques à Linux.

Sur FreeBSD, utilisez mv -halternativement.

Note de l'éditeur: c'est ainsi que Capistrano le fait depuis des années maintenant, depuis ~ 2.15. Voir cette demande d'extraction .

Pascal Thivent
la source
2
L'option '-f' ne force-t-elle pas 'ln' à faire les appels système unlink () puis symlink ()?
Jonathan Leffler
1
Cela fait. Mais la question aurait pu être perçue comme "comment puis-je faire cela en une seule étape" et ensuite elle se résume à la définition de "étape" - ligne de commande? syscall?
Michael Krelin - hacker
1
Je viens de remarquer que vous avez ajouté un libellé d'appel système à votre question ;-)
Michael Krelin - hacker
2
@Pascal: c'est le cas. Sous Solaris, la sortie de 'truss ln -spx' et 'truss ln -s -fpx' montre un appel unlink () avant l'appel symlink () dans le second cas (et un appel symlink () échoué dans le premier). Je m'attendrais à ce que cela s'applique à la plupart sinon à toutes les variantes d'Unix.
Jonathan Leffler
12
+1 au commentaire @Taai - si vous avez affaire à un lien symbolique qui déréférence (pointe vers) un répertoire, vous devez spécifier "-n" pour être sûr que cette astuce fonctionne.
wally
161

Oui, vous pouvez!

$ ln -sfn source_file_or_directory_name softlink_name
Taai
la source
9
Merci d'avoir répondu. L' -foption signifie «supprimer la destination existante» avant de créer la nouvelle. Donc, cette commande atteint le résultat, mais en faisantunlink(2) suivi de symlink(2). Ce n'est pas le sujet de la question initiale. Notez également que la réponse acceptée et la réponse suivante la plus votée sont toutes deux utilisées ln -sfpour faire le travail, mais comme le stracemontre la sortie, c'est le cas unlink()et symlink()parce qu'il n'y a pas d'appel système pour modifier un lien symbolique.
Jonathan Leffler
17
Le -n semble être requis lorsque le lien d'origine va vers un répertoire au lieu d'un fichier.
alimenté à vapeur
11
Le commutateur «n» est important. J'ai eu du mal avec le problème que le lien symbolique n'a pas été mis à jour mais la commande a créé un autre lien à l'intérieur de celui existant car l'option «pas de déréférencement» n'était pas définie.
Jonathan Gruber
14

Il n'est pas nécessaire de dissocier explicitement l'ancien lien symbolique. Tu peux le faire:

ln -s newtarget temp
mv temp mylink

(ou utilisez le lien symbolique équivalent et renommez les appels). C'est mieux que de dissocier explicitement la liaison car le changement de nom est atomique, vous pouvez donc être assuré que le lien pointera toujours vers l'ancienne ou la nouvelle cible. Cependant, cela ne réutilisera pas l'inode d'origine.

Sur certains systèmes de fichiers, la cible du lien symbolique est stockée dans l'inode lui-même (à la place de la liste de blocage) si elle est suffisamment courte; ceci est déterminé au moment de sa création.

En ce qui concerne l'affirmation selon laquelle le propriétaire et le groupe réels sont immatériels, symlink (7) sous Linux dit qu'il y a un cas où il est significatif:

Le propriétaire et le groupe d'un lien symbolique existant peuvent être modifiés à l'aide de lchown (2). Le seul moment où la propriété d'un lien symbolique compte est lorsque le lien est supprimé ou renommé dans un répertoire qui a le bit sticky défini (voir stat (2)).

Les horodatages du dernier accès et de la dernière modification d'un lien symbolique peuvent être modifiés en utilisant utimensat (2) ou lutimes (3).

Sous Linux, les permissions d'un lien symbolique ne sont utilisées dans aucune opération; les autorisations sont toujours 0777 (lecture, écriture et exécution pour toutes les catégories d'utilisateurs) et ne peuvent pas être modifiées.

mark4o
la source
@ mark4o: The Good - La référence à lchown () et à la propriété dans un répertoire sticky-it est utile - merci. J'étais au courant de lchown () et ce n'était pas important pour ma thèse. J'étais également au courant des répertoires sticky-bit; Je pense que la propriété est presque une conséquence standard des règles - la différence, et probablement pourquoi elle est appelée, est que normalement vous pouvez supprimer un fichier si vous pouvez y écrire, et nominalement, n'importe qui peut écrire sur un lien symbolique parce que des autorisations 777, mais dans ce cas, les règles sont légèrement différentes.
Jonathan Leffler
1
@ mark4o: The Not So Good - la séquence de commandes montrée ne fait pas ce que vous pensez qu'elle fait (du moins, dans le scénario :) set -x -e; mkdir junk; ( cd junk; mkdir olddir newdir; ln -s olddir mylink; ls -ilR; ln -s newdir temp; ls -ilR; mv temp mylink; ls -ilR; ); rm -fr junk. Si vous créez des fichiers oldfile et newfile au lieu de répertoires et exécutez le script équivalent (principalement en changeant olddir en oldfile, newdir en newfile), alors vous obtenez l'effet que vous attendiez. Juste une complexité supplémentaire! Merci pour votre réponse.
Jonathan Leffler
2

Juste un avertissement aux bonnes réponses ci-dessus:

L'utilisation de la méthode -f / --force présente un risque de perdre le fichier si vous mélangez la source et la cible:

mbucher@server2:~/test$ ls -la
total 11448
drwxr-xr-x  2 mbucher www-data    4096 May 25 15:27 .
drwxr-xr-x 18 mbucher www-data    4096 May 25 15:13 ..
-rw-r--r--  1 mbucher www-data 4109466 May 25 15:26 data.tar.gz
-rw-r--r--  1 mbucher www-data 7582480 May 25 15:27 otherdata.tar.gz
lrwxrwxrwx  1 mbucher www-data      11 May 25 15:26 thesymlink -> data.tar.gz
mbucher@server2:~/test$ 
mbucher@server2:~/test$ ln -s -f thesymlink otherdata.tar.gz 
mbucher@server2:~/test$ 
mbucher@server2:~/test$ ls -la
total 4028
drwxr-xr-x  2 mbucher www-data    4096 May 25 15:28 .
drwxr-xr-x 18 mbucher www-data    4096 May 25 15:13 ..
-rw-r--r--  1 mbucher www-data 4109466 May 25 15:26 data.tar.gz
lrwxrwxrwx  1 mbucher www-data      10 May 25 15:28 otherdata.tar.gz -> thesymlink
lrwxrwxrwx  1 mbucher www-data      11 May 25 15:26 thesymlink -> data.tar.gz

Bien sûr, c'est intentionnel, mais des erreurs se produisent généralement. Ainsi, supprimer et reconstruire le lien symbolique est un peu plus de travail mais aussi un peu plus économique:

mbucher@server2:~/test$ rm thesymlink && ln -s thesymlink otherdata.tar.gz 
ln: creating symbolic link `otherdata.tar.gz': File exists

qui garde au moins mon dossier.

Markus Bucher
la source
Utiliser une option -fou --forceest toujours un peu dangereux; cela suppose que l'utilisateur sait de quoi il s'agit. Il est doublement mortel si vous l'utilisez habituellement ( rm -fr …chaque fois est dangereux).
Jonathan Leffler
1

La dissociation et la création du nouveau ne feraient-elles pas la même chose à la fin de toute façon?

mat b
la source
2
Pourquoi dissocier en premier lieu? Pourquoi ne pas simplement l'écraser?
S.Lott
5
Le résultat net est à peu près le même - mais le propriétaire et le groupe et les dernières heures de modification (et probablement le numéro d'inode) seraient tous différents, en général.
Jonathan Leffler
2
@ S.Lott: quel appel système le «écrasement»? Dans POSIX, un tel appel n'existe pas. Dans Solaris et MacOS X, un tel appel n'existe pas. Y a-t-il un appel pour faire ça sur ... Linux, AIX, HP-UX, tout autre chose qui ressemble à Unix? Windows n'a pas vraiment de liens symboliques de la même manière, AFAICT, donc ce n'est pas critique pour mon analyse - mais des informations sur l'API Windows équivalente seraient utiles.
Jonathan Leffler
@Jonathan Leffler: Ummm .... la ln --forcecommande écrasera absolument un lien existant.
S.Lott
Les gars, je pense que vous parlez à contre-courant. S.Lott discute d'un exécutable ln (1), tandis que matt b. discute du fait que symlink (2)ne prend pas en charge l'écrasement. Vous avez tous les deux raison, et je dirais qu'en regardant la mise en œuvre de ln (1)serait le moyen le plus idomatique d'accomplir la procédure de dissociation-reconnexion.
dmckee --- ex-moderator chaton
0

Juste au cas où cela aiderait: il existe un moyen d'éditer un lien symbolique avec midnight commander (mc). La commande de menu est (en français sur mon interface mc):

Fichier / Éditer le lien symbolique

qui peut être traduit par:

File / Edit symbolic link

Le raccourci est Cx Cs

Peut-être qu'il utilise en interne le ln --force commande en , je ne sais pas.

Maintenant, j'essaie de trouver un moyen d'éditer tout un tas de liens symboliques à la fois (c'est comme ça que je suis arrivé ici).

Pierre
la source
1
Avez-vous vérifié ce que MC fait dans les coulisses? J'imagine qu'en fait, il fait un unlink()suivi d'un symlink(), simplement parce que c'est ce que fournissent les systèmes de type Unix.
Jonathan Leffler
Non, je n'ai pas vérifié. Et oui, il est fort probable qu'il fasse ce que vous dites, en fait, le fait que j'édite une zone de texte donne le sentiment que je suis en train d'éditer la cible du lien symbolique, mais j'ai probablement été dupe ...
Pierre
0

Techniquement, il n'y a pas de commande intégrée pour modifier un lien symbolique existant. Cela peut être facilement réalisé avec quelques commandes courtes.

Voici une petite fonction bash / zsh que j'ai écrite pour mettre à jour un lien symbolique existant:

# -----------------------------------------
# Edit an existing symbolic link
#
# @1 = Name of symbolic link to edit
# @2 = Full destination path to update existing symlink with 
# -----------------------------------------
function edit-symlink () {
    if [ -z "$1" ]; then
        echo "Name of symbolic link you would like to edit:"
        read LINK
    else
        LINK="$1"
    fi
    LINKTMP="$LINK-tmp"
    if [ -z "$2" ]; then
        echo "Full destination path to update existing symlink with:"
        read DEST
    else
        DEST="$2"
    fi
    ln -s $DEST $LINKTMP
    rm $LINK
    mv $LINKTMP $LINK
    printf "Updated $LINK to point to new destination -> $DEST"
}
blizzrdof77
la source
2
Merci pour l'effort. Cela ne répond pas à mon exigence d'être un appel système en langage C. Il existe de nombreuses façons de modifier un lien tant que vous disposez d'une autorisation d'écriture sur le répertoire contenant le lien (ce que cela nécessite). Il n'y a aucun moyen que je connaisse, même maintenant, pour changer la valeur d'un lien symbolique sans faire unlink()et symlink()avec la nouvelle valeur, qui est ce qui se passe dans les coulisses avec votre code. Personnellement, j'aimerais que la fonction insiste sur exactement deux (ou peut-être un multiple de deux) arguments et caution si l'invocation n'est pas correcte. C'est une perspective particulière, cependant.
Jonathan Leffler