1717package symlinks
1818
1919import (
20+ "errors"
2021 "fmt"
2122 "os"
2223 "path/filepath"
2324 "strings"
2425
26+ "github.com/moby/sys/symlink"
2527 "github.com/urfave/cli/v2"
2628
2729 "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
30+ "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
2831 "github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
2932)
3033
@@ -33,7 +36,6 @@ type command struct {
3336}
3437
3538type config struct {
36- hostRoot string
3739 links cli.StringSlice
3840 containerSpec string
3941}
@@ -65,12 +67,6 @@ func (m command) build() *cli.Command {
6567 Destination : & cfg .links ,
6668 },
6769 // The following flags are testing-only flags.
68- & cli.StringFlag {
69- Name : "host-root" ,
70- Usage : "The root on the host filesystem to use to resolve symlinks. This is only intended for testing." ,
71- Destination : & cfg .hostRoot ,
72- Hidden : true ,
73- },
7470 & cli.StringFlag {
7571 Name : "container-spec" ,
7672 Usage : "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN. This is only intended for testing." ,
@@ -95,61 +91,77 @@ func (m command) run(c *cli.Context, cfg *config) error {
9591
9692 created := make (map [string ]bool )
9793 for _ , l := range cfg .links .Value () {
94+ if created [l ] {
95+ m .logger .Debugf ("Link %v already processed" , l )
96+ continue
97+ }
9898 parts := strings .Split (l , "::" )
9999 if len (parts ) != 2 {
100- m .logger .Warningf ("Invalid link specification %v" , l )
101- continue
100+ return fmt .Errorf ("invalid symlink specification %v" , l )
102101 }
103102
104- err := m .createLink (created , cfg . hostRoot , containerRoot , parts [0 ], parts [1 ])
103+ err := m .createLink (containerRoot , parts [0 ], parts [1 ])
105104 if err != nil {
106- m . logger . Warningf ( "Failed to create link %v: %v " , parts , err )
105+ return fmt . Errorf ( "failed to create link %v: %w " , parts , err )
107106 }
107+ created [l ] = true
108108 }
109109 return nil
110110}
111111
112- func (m command ) createLink (created map [string ]bool , hostRoot string , containerRoot string , target string , link string ) error {
113- linkPath , err := changeRoot (hostRoot , containerRoot , link )
112+ // createLink creates a symbolic link in the specified container root.
113+ // This is equivalent to:
114+ //
115+ // chroot {{ .containerRoot }} ln -s {{ .target }} {{ .link }}
116+ //
117+ // If the specified link already exists and points to the same target, this
118+ // operation is a no-op. If the link points to a different target, an error is
119+ // returned.
120+ //
121+ // Note that if the link path resolves to an absolute path oudside of the
122+ // specified root, this is treated as an absolute path in this root.
123+ func (m command ) createLink (containerRoot string , targetPath string , link string ) error {
124+ linkPath := filepath .Join (containerRoot , link )
125+
126+ exists , err := doesLinkExist (targetPath , linkPath )
114127 if err != nil {
115- m . logger . Warningf ( "Failed to resolve path for link %v relative to %v : %v" , link , containerRoot , err )
128+ return fmt . Errorf ( "failed to check if link exists : %w" , err )
116129 }
117- if created [ linkPath ] {
118- m .logger .Debugf ("Link %v already created " , linkPath )
130+ if exists {
131+ m .logger .Debugf ("Link %s already exists " , linkPath )
119132 return nil
120133 }
121134
122- targetPath , err := changeRoot ( hostRoot , "/" , target )
135+ resolvedLinkPath , err := symlink . FollowSymlinkInScope ( linkPath , containerRoot )
123136 if err != nil {
124- m . logger . Warningf ( "Failed to resolve path for target %v relative to %v: %v " , target , "/" , err )
137+ return fmt . Errorf ( "failed to follow path for link %v relative to %v: %w " , link , containerRoot , err )
125138 }
126139
127- m .logger .Infof ("Symlinking %v to %v" , linkPath , targetPath )
128- err = os .MkdirAll (filepath .Dir (linkPath ), 0755 )
140+ m .logger .Infof ("Symlinking %v to %v" , resolvedLinkPath , targetPath )
141+ err = os .MkdirAll (filepath .Dir (resolvedLinkPath ), 0755 )
129142 if err != nil {
130143 return fmt .Errorf ("failed to create directory: %v" , err )
131144 }
132- err = os .Symlink (target , linkPath )
145+ err = os .Symlink (targetPath , resolvedLinkPath )
133146 if err != nil {
134147 return fmt .Errorf ("failed to create symlink: %v" , err )
135148 }
136149
137150 return nil
138151}
139152
140- func changeRoot (current string , new string , path string ) (string , error ) {
141- if ! filepath .IsAbs (path ) {
142- return path , nil
153+ // doesLinkExist returns true if link exists and points to target.
154+ // An error is returned if link exists but points to a different target.
155+ func doesLinkExist (target string , link string ) (bool , error ) {
156+ currentTarget , err := symlinks .Resolve (link )
157+ if errors .Is (err , os .ErrNotExist ) {
158+ return false , nil
143159 }
144-
145- relative := path
146- if current != "" {
147- r , err := filepath .Rel (current , path )
148- if err != nil {
149- return "" , err
150- }
151- relative = r
160+ if err != nil {
161+ return false , fmt .Errorf ("failed to resolve existing symlink %s: %w" , link , err )
152162 }
153-
154- return filepath .Join (new , relative ), nil
163+ if currentTarget == target {
164+ return true , nil
165+ }
166+ return true , fmt .Errorf ("unexpected link target: %s" , currentTarget )
155167}
0 commit comments