11/*
22Copyright 2016 The Kubernetes Authors.
3+
34Licensed under the Apache License, Version 2.0 (the "License");
45you may not use this file except in compliance with the License.
56You may obtain a copy of the License at
7+
68 http://www.apache.org/licenses/LICENSE-2.0
9+
710Unless required by applicable law or agreed to in writing, software
811distributed under the License is distributed on an "AS IS" BASIS,
912WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1013See the License for the specific language governing permissions and
1114limitations under the License.
1215*/
1316
17+ // copied from https://raw.githubusercontent.com/kubernetes/kubernetes/20d0ab7ae808aaddb1556c3c38ca0607663c50ac/pkg/volume/util/atomic_writer.go
18+
1419package util
1520
1621import (
@@ -82,6 +87,10 @@ const (
8287
8388// Write does an atomic projection of the given payload into the writer's target
8489// directory. Input paths must not begin with '..'.
90+ // setPerms is an optional pointer to a function that caller can provide to set the
91+ // permissions of the newly created files before they are published. The function is
92+ // passed subPath which is the name of the timestamped directory that was created
93+ // under target directory.
8594//
8695// The Write algorithm is:
8796//
@@ -94,37 +103,42 @@ const (
94103// portion of the payload was deleted and is still present on disk.
95104//
96105// 4. The data in the current timestamped directory is compared to the projected
97- // data to determine if an update is required.
106+ // data to determine if an update to data directory is required.
98107//
99- // 5. A new timestamped dir is created
108+ // 5. A new timestamped dir is created if an update is required.
100109//
101- // 6. The payload is written to the new timestamped directory
110+ // 6. The payload is written to the new timestamped directory.
111+ //
112+ // 7. Permissions are set (if setPerms is not nil) on the new timestamped directory and files.
113+ //
114+ // 8. A symlink to the new timestamped directory ..data_tmp is created that will
115+ // become the new data directory.
102116//
103- // 7. Symlinks and directory for new user-visible files are created (if needed).
117+ // 9. The new data directory symlink is renamed to the data directory; rename is atomic.
118+ //
119+ // 10. Symlinks and directory for new user-visible files are created (if needed).
104120//
105121// For example, consider the files:
106122// <target-dir>/podName
107123// <target-dir>/user/labels
108124// <target-dir>/k8s/annotations
109125//
110126// The user visible files are symbolic links into the internal data directory:
111- // <target-dir>/podName -> ..data/podName
112- // <target-dir>/usr -> ..data/usr
113- // <target-dir>/k8s -> ..data/k8s
127+ // <target-dir>/podName -> ..data/podName
128+ // <target-dir>/usr -> ..data/usr
129+ // <target-dir>/k8s -> ..data/k8s
114130//
115131// The data directory itself is a link to a timestamped directory with
116132// the real data:
117- // <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
118- //
119- // 8. A symlink to the new timestamped directory ..data_tmp is created that will
120- // become the new data directory
133+ // <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
134+ // NOTE(claudiub): We need to create these symlinks AFTER we've finished creating and
135+ // linking everything else. On Windows, if a target does not exist, the created symlink
136+ // will not work properly if the target ends up being a directory.
121137//
122- // 9. The new data directory symlink is renamed to the data directory; rename is atomic
138+ // 11. Old paths are removed from the user-visible portion of the target directory.
123139//
124- // 10. Old paths are removed from the user-visible portion of the target directory
125- //
126- // 11. The previous timestamped directory is removed, if it exists
127- func (w * AtomicWriter ) Write (payload map [string ]FileProjection ) error {
140+ // 12. The previous timestamped directory is removed, if it exists.
141+ func (w * AtomicWriter ) Write (payload map [string ]FileProjection , setPerms func (subPath string ) error ) error {
128142 // (1)
129143 cleanPayload , err := validatePayload (payload )
130144 if err != nil {
@@ -147,6 +161,7 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
147161 oldTsPath := filepath .Join (w .targetDir , oldTsDir )
148162
149163 var pathsToRemove sets.Set [string ]
164+ shouldWrite := true
150165 // if there was no old version, there's nothing to remove
151166 if len (oldTsDir ) != 0 {
152167 // (3)
@@ -161,64 +176,89 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
161176 klog .Errorf ("%s: error determining whether payload should be written to disk: %v" , w .logContext , err )
162177 return err
163178 } else if ! should && len (pathsToRemove ) == 0 {
164- klog .V (4 ).Infof ("%s: no update required for target directory %v" , w .logContext , w .targetDir )
165- return nil
179+ klog .V (4 ).Infof ("%s: write not required for data directory %v" , w .logContext , oldTsDir )
180+ // data directory is already up to date, but we need to make sure that
181+ // the user-visible symlinks are created.
182+ // See https://github.com/kubernetes/kubernetes/issues/121472 for more details.
183+ // Reset oldTsDir to empty string to avoid removing the data directory.
184+ shouldWrite = false
185+ oldTsDir = ""
166186 } else {
167187 klog .V (4 ).Infof ("%s: write required for target directory %v" , w .logContext , w .targetDir )
168188 }
169189 }
170190
171- // (5)
172- tsDir , err := w .newTimestampDir ()
173- if err != nil {
174- klog .V (4 ).Infof ("%s: error creating new ts data directory: %v" , w .logContext , err )
175- return err
176- }
177- tsDirName := filepath .Base (tsDir )
191+ if shouldWrite {
192+ // (5)
193+ tsDir , err := w .newTimestampDir ()
194+ if err != nil {
195+ klog .V (4 ).Infof ("%s: error creating new ts data directory: %v" , w .logContext , err )
196+ return err
197+ }
198+ tsDirName := filepath .Base (tsDir )
178199
179- // (6)
180- if err = w .writePayloadToDir (cleanPayload , tsDir ); err != nil {
181- klog .Errorf ("%s: error writing payload to ts data directory %s: %v" , w .logContext , tsDir , err )
182- return err
183- }
184- klog .V (4 ).Infof ("%s: performed write of new data to ts data directory: %s" , w .logContext , tsDir )
200+ // (6)
201+ if err = w .writePayloadToDir (cleanPayload , tsDir ); err != nil {
202+ klog .Errorf ("%s: error writing payload to ts data directory %s: %v" , w .logContext , tsDir , err )
203+ return err
204+ }
205+ klog .V (4 ).Infof ("%s: performed write of new data to ts data directory: %s" , w .logContext , tsDir )
185206
186- // (7)
187- if err = w .createUserVisibleFiles (cleanPayload ); err != nil {
188- klog .Errorf ("%s: error creating visible symlinks in %s: %v" , w .logContext , w .targetDir , err )
189- return err
190- }
207+ // (7)
208+ if setPerms != nil {
209+ if err := setPerms (tsDirName ); err != nil {
210+ klog .Errorf ("%s: error applying ownership settings: %v" , w .logContext , err )
211+ return err
212+ }
213+ }
191214
192- // (8)
193- newDataDirPath := filepath .Join (w .targetDir , newDataDirName )
194- if err = os .Symlink (tsDirName , newDataDirPath ); err != nil {
195- os .RemoveAll (tsDir )
196- klog .Errorf ("%s: error creating symbolic link for atomic update: %v" , w .logContext , err )
197- return err
198- }
215+ // (8)
216+ newDataDirPath := filepath .Join (w .targetDir , newDataDirName )
217+ if err = os .Symlink (tsDirName , newDataDirPath ); err != nil {
218+ if err := os .RemoveAll (tsDir ); err != nil {
219+ klog .Errorf ("%s: error removing new ts directory %s: %v" , w .logContext , tsDir , err )
220+ }
221+ klog .Errorf ("%s: error creating symbolic link for atomic update: %v" , w .logContext , err )
222+ return err
223+ }
199224
200- // (9)
201- if runtime .GOOS == "windows" {
202- os .Remove (dataDirPath )
203- err = os .Symlink (tsDirName , dataDirPath )
204- os .Remove (newDataDirPath )
205- } else {
206- err = os .Rename (newDataDirPath , dataDirPath )
225+ // (9)
226+ if runtime .GOOS == "windows" {
227+ if err := os .Remove (dataDirPath ); err != nil {
228+ klog .Errorf ("%s: error removing data dir directory %s: %v" , w .logContext , dataDirPath , err )
229+ }
230+ err = os .Symlink (tsDirName , dataDirPath )
231+ if err := os .Remove (newDataDirPath ); err != nil {
232+ klog .Errorf ("%s: error removing new data dir directory %s: %v" , w .logContext , newDataDirPath , err )
233+ }
234+ } else {
235+ err = os .Rename (newDataDirPath , dataDirPath )
236+ }
237+ if err != nil {
238+ if err := os .Remove (newDataDirPath ); err != nil && err != os .ErrNotExist {
239+ klog .Errorf ("%s: error removing new data dir directory %s: %v" , w .logContext , newDataDirPath , err )
240+ }
241+ if err := os .RemoveAll (tsDir ); err != nil {
242+ klog .Errorf ("%s: error removing new ts directory %s: %v" , w .logContext , tsDir , err )
243+ }
244+ klog .Errorf ("%s: error renaming symbolic link for data directory %s: %v" , w .logContext , newDataDirPath , err )
245+ return err
246+ }
207247 }
208- if err != nil {
209- os . Remove ( newDataDirPath )
210- os . RemoveAll ( tsDir )
211- klog .Errorf ("%s: error renaming symbolic link for data directory %s: %v" , w .logContext , newDataDirPath , err )
248+
249+ // (10 )
250+ if err = w . createUserVisibleFiles ( cleanPayload ); err != nil {
251+ klog .Errorf ("%s: error creating visible symlinks in %s: %v" , w .logContext , w . targetDir , err )
212252 return err
213253 }
214254
215- // (10 )
255+ // (11 )
216256 if err = w .removeUserVisiblePaths (pathsToRemove ); err != nil {
217257 klog .Errorf ("%s: error removing old visible symlinks: %v" , w .logContext , err )
218258 return err
219259 }
220260
221- // (11 )
261+ // (12 )
222262 if len (oldTsDir ) > 0 {
223263 if err = os .RemoveAll (oldTsPath ); err != nil {
224264 klog .Errorf ("%s: error removing old data directory %s: %v" , w .logContext , oldTsDir , err )
0 commit comments