@@ -32,31 +32,45 @@ const (
3232 filePerm = 0o644
3333)
3434
35- // syncDir calls fsync on the provided path .
35+ // syncDir opens the specified directory and calls op before syncing and closing the handle on the directory .
3636//
37- // This is intended to be used to sync directories in which we've just created new entries.
38- func syncDir (d string ) error {
39- fd , err := os .Open (d )
37+ // This dance ensures that the inode of the specified directory cannot be evicted from the kernel inode cache while
38+ // the operation is underway, and so any error which occurs while updating metadata about a file operation which happens
39+ // _within_ that directory is detected.
40+ //
41+ // This function is intended to be used by the other functions in this file.
42+ func syncDir (dir string , op func () error ) (err error ) {
43+ fd , err := os .OpenFile (dir , os .O_RDONLY | syscall .O_DIRECTORY , 0 )
4044 if err != nil {
41- return fmt .Errorf ("failed to open %q: %v" , d , err )
45+ return fmt .Errorf ("failed to open %q: %w" , dir , err )
46+ }
47+ defer func () {
48+ e := fd .Close ()
49+ if err == nil {
50+ err = e
51+ }
52+ }()
53+
54+ if err := op (); err != nil {
55+ return err
4256 }
4357
4458 if err := fd .Sync (); err != nil {
45- return fmt .Errorf ("failed to sync %q: %v " , d , err )
59+ return fmt .Errorf ("failed to sync %q: %w " , dir , err )
4660 }
47- return fd . Close ()
61+ return nil
4862}
4963
5064// mkdirAll is a reimplementation of os.mkdirAll but where we fsync the parent directory/ies
5165// we modify.
52- func mkdirAll (name string , perm os.FileMode ) ( err error ) {
66+ func mkdirAll (name string , perm os.FileMode ) error {
5367 name = strings .TrimSuffix (name , string (filepath .Separator ))
5468 if name == "" {
5569 return nil
5670 }
5771
5872 // Finally, check and create the dir if necessary.
59- dir , _ := filepath .Split (name )
73+ dir := filepath .Dir (name )
6074 di , err := os .Lstat (name )
6175 switch {
6276 case errors .Is (err , syscall .ENOENT ):
@@ -72,13 +86,14 @@ func mkdirAll(name string, perm os.FileMode) (err error) {
7286 // create the final entry in the requested path.
7387 fallthrough
7488 case errors .Is (err , os .ErrNotExist ):
75- // We'll see ErrNotExist if the final entry in the requested path doesn't exist,
76- // so we simply attempt to create it in here.
77- if err := os .Mkdir (name , perm ); err != nil {
78- return fmt .Errorf ("%q: %v" , name , err )
79- }
80- // And be sure to sync the parent directory.
81- return syncDir (dir )
89+ return syncDir (dir , func () error {
90+ // We'll see ErrNotExist if the final entry in the requested path doesn't exist,
91+ // so we simply attempt to create it in here.
92+ if err := os .Mkdir (name , perm ); err != nil {
93+ return fmt .Errorf ("%q: %v" , name , err )
94+ }
95+ return nil
96+ })
8297 case err != nil :
8398 return fmt .Errorf ("lstat %q: %v" , name , err )
8499 case ! di .IsDir ():
@@ -94,47 +109,52 @@ func mkdirAll(name string, perm os.FileMode) (err error) {
94109// Returns an error if a file already exists at the specified location, or it's unable to fully write the
95110// data & close the file.
96111func createEx (name string , d []byte ) error {
97- dir , _ := filepath .Split (name )
112+ dir := filepath .Dir (name )
98113 if err := mkdirAll (dir , dirPerm ); err != nil {
99- return fmt .Errorf ("failed to make entries directory structure: %w" , err )
114+ return fmt .Errorf ("failed to make directory structure: %w" , err )
100115 }
101-
102- tmpName , err := createTemp (name , d )
103- if err != nil {
104- return fmt .Errorf ("failed to create temp file: %v" , err )
105- }
106- defer func () {
107- if err := os .Remove (tmpName ); err != nil {
108- klog .Warningf ("Failed to remove temporary file %q: %v" , tmpName , err )
116+ return syncDir (dir , func () error {
117+ tmpName , err := createTemp (name , d )
118+ if err != nil {
119+ return fmt .Errorf ("failed to create temp file: %v" , err )
109120 }
110- }()
111-
112- if err := os .Link (tmpName , name ); err != nil {
113- // Wrap the error here because we need to know if it's os.ErrExists at higher levels.
114- return fmt .Errorf ("failed to link temporary file to target %q: %w" , name , err )
115- }
121+ defer func () {
122+ if err := os .Remove (tmpName ); err != nil {
123+ klog .Warningf ("Failed to remove temporary file %q: %v" , tmpName , err )
124+ }
125+ }()
116126
117- return syncDir (dir )
127+ if err := os .Link (tmpName , name ); err != nil {
128+ // Wrap the error here because we need to know if it's os.ErrExists at higher levels.
129+ return fmt .Errorf ("failed to link temporary file to target %q: %w" , name , err )
130+ }
131+ return nil
132+ })
118133}
119134
120135// overwrite atomically creates/overwrites a file at the given path containing the provided data, and syncs
121136// the directory containing the overwritten/created file.
122137func overwrite (name string , d []byte ) error {
123- dir , _ := filepath .Split (name )
138+ dir := filepath .Dir (name )
124139 if err := mkdirAll (dir , dirPerm ); err != nil {
125- return fmt .Errorf ("failed to make entries directory structure: %w" , err )
126- }
127-
128- tmpName , err := createTemp (name , d )
129- if err != nil {
130- return fmt .Errorf ("failed to create temp file: %v" , err )
140+ return fmt .Errorf ("failed to make directory structure: %w" , err )
131141 }
142+ return syncDir (dir , func () error {
143+ dir , _ := filepath .Split (name )
144+ if err := mkdirAll (dir , dirPerm ); err != nil {
145+ return fmt .Errorf ("failed to make entries directory structure: %w" , err )
146+ }
132147
133- if err := os .Rename (tmpName , name ); err != nil {
134- return fmt .Errorf ("failed to rename temporary file to target %q: %w" , name , err )
135- }
148+ tmpName , err := createTemp (name , d )
149+ if err != nil {
150+ return fmt .Errorf ("failed to create temp file: %v" , err )
151+ }
136152
137- return syncDir (dir )
153+ if err := os .Rename (tmpName , name ); err != nil {
154+ return fmt .Errorf ("failed to rename temporary file to target %q: %w" , name , err )
155+ }
156+ return nil
157+ })
138158}
139159
140160// createTemp creates a new temporary file in the directory dir, with a name based on the provided prefix,
0 commit comments