@@ -63,12 +63,9 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) {
63
63
ctx , span := trace .StartSpan (ctx , "modfetch.download " + mod .String ())
64
64
defer span .Done ()
65
65
66
- // If the directory exists, and no .partial file exists, the module has
67
- // already been completely extracted. .partial files may be created when a
68
- // module zip directory is extracted in place instead of being extracted to a
69
- // temporary directory and renamed.
70
66
dir , err = DownloadDir (mod )
71
67
if err == nil {
68
+ // The directory has already been completely extracted (no .partial file exists).
72
69
return dir , nil
73
70
} else if dir == "" || ! errors .Is (err , os .ErrNotExist ) {
74
71
return "" , err
@@ -88,17 +85,21 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) {
88
85
}
89
86
defer unlock ()
90
87
88
+ ctx , span = trace .StartSpan (ctx , "unzip " + zipfile )
89
+ defer span .Done ()
90
+
91
91
// Check whether the directory was populated while we were waiting on the lock.
92
92
_ , dirErr := DownloadDir (mod )
93
93
if dirErr == nil {
94
94
return dir , nil
95
95
}
96
96
_ , dirExists := dirErr .(* DownloadDirPartialError )
97
97
98
- // Clean up any remaining temporary directories from previous runs, as well
99
- // as partially extracted diectories created by future versions of cmd/go.
100
- // This is only safe to do because the lock file ensures that their writers
101
- // are no longer active.
98
+ // Clean up any remaining temporary directories created by old versions
99
+ // (before 1.16), as well as partially extracted directories (indicated by
100
+ // DownloadDirPartialError, usually because of a .partial file). This is only
101
+ // safe to do because the lock file ensures that their writers are no longer
102
+ // active.
102
103
parentDir := filepath .Dir (dir )
103
104
tmpPrefix := filepath .Base (dir ) + ".tmp-"
104
105
if old , err := filepath .Glob (filepath .Join (parentDir , tmpPrefix + "*" )); err == nil {
@@ -116,88 +117,44 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) {
116
117
if err != nil {
117
118
return "" , err
118
119
}
119
- if err := os .Remove (partialPath ); err != nil && ! os .IsNotExist (err ) {
120
- return "" , err
121
- }
122
120
123
- // Extract the module zip directory.
121
+ // Extract the module zip directory at its final location .
124
122
//
125
- // By default, we extract to a temporary directory, then atomically rename to
126
- // its final location. We use the existence of the source directory to signal
127
- // that it has been extracted successfully (see DownloadDir). If someone
128
- // deletes the entire directory (e.g., as an attempt to prune out file
129
- // corruption), the module cache will still be left in a recoverable
130
- // state.
123
+ // To prevent other processes from reading the directory if we crash,
124
+ // create a .partial file before extracting the directory, and delete
125
+ // the .partial file afterward (all while holding the lock).
131
126
//
132
- // Unfortunately, os.Rename may fail with ERROR_ACCESS_DENIED on Windows if
133
- // another process opens files in the temporary directory. This is partially
134
- // mitigated by using robustio.Rename, which retries os.Rename for a short
135
- // time .
127
+ // Before Go 1.16, we extracted to a temporary directory with a random name
128
+ // then renamed it into place with os.Rename. On Windows, this failed with
129
+ // ERROR_ACCESS_DENIED when another process (usually an anti-virus scanner)
130
+ // opened files in the temporary directory .
136
131
//
137
- // To avoid this error completely, if unzipInPlace is set, we instead create a
138
- // .partial file (indicating the directory isn't fully extracted), then we
139
- // extract the directory at its final location, then we delete the .partial
140
- // file. This is not the default behavior because older versions of Go may
141
- // simply stat the directory to check whether it exists without looking for a
142
- // .partial file. If multiple versions run concurrently, the older version may
143
- // assume a partially extracted directory is complete.
144
- // TODO(golang.org/issue/36568): when these older versions are no longer
145
- // supported, remove the old default behavior and the unzipInPlace flag.
132
+ // Go 1.14.2 and higher respect .partial files. Older versions may use
133
+ // partially extracted directories. 'go mod verify' can detect this,
134
+ // and 'go clean -modcache' can fix it.
146
135
if err := os .MkdirAll (parentDir , 0777 ); err != nil {
147
136
return "" , err
148
137
}
149
-
150
- ctx , span = trace .StartSpan (ctx , "unzip " + zipfile )
151
- if unzipInPlace {
152
- if err := ioutil .WriteFile (partialPath , nil , 0666 ); err != nil {
153
- return "" , err
154
- }
155
- if err := modzip .Unzip (dir , mod , zipfile ); err != nil {
156
- fmt .Fprintf (os .Stderr , "-> %s\n " , err )
157
- if rmErr := RemoveAll (dir ); rmErr == nil {
158
- os .Remove (partialPath )
159
- }
160
- return "" , err
161
- }
162
- if err := os .Remove (partialPath ); err != nil {
163
- return "" , err
164
- }
165
- } else {
166
- tmpDir , err := ioutil .TempDir (parentDir , tmpPrefix )
167
- if err != nil {
168
- return "" , err
169
- }
170
- if err := modzip .Unzip (tmpDir , mod , zipfile ); err != nil {
171
- fmt .Fprintf (os .Stderr , "-> %s\n " , err )
172
- RemoveAll (tmpDir )
173
- return "" , err
174
- }
175
- if err := robustio .Rename (tmpDir , dir ); err != nil {
176
- RemoveAll (tmpDir )
177
- return "" , err
138
+ if err := ioutil .WriteFile (partialPath , nil , 0666 ); err != nil {
139
+ return "" , err
140
+ }
141
+ if err := modzip .Unzip (dir , mod , zipfile ); err != nil {
142
+ fmt .Fprintf (os .Stderr , "-> %s\n " , err )
143
+ if rmErr := RemoveAll (dir ); rmErr == nil {
144
+ os .Remove (partialPath )
178
145
}
146
+ return "" , err
147
+ }
148
+ if err := os .Remove (partialPath ); err != nil {
149
+ return "" , err
179
150
}
180
- defer span .Done ()
181
151
182
152
if ! cfg .ModCacheRW {
183
- // Make dir read-only only *after* renaming it.
184
- // os.Rename was observed to fail for read-only directories on macOS.
185
153
makeDirsReadOnly (dir )
186
154
}
187
155
return dir , nil
188
156
}
189
157
190
- var unzipInPlace bool
191
-
192
- func init () {
193
- for _ , f := range strings .Split (os .Getenv ("GODEBUG" ), "," ) {
194
- if f == "modcacheunzipinplace=1" {
195
- unzipInPlace = true
196
- break
197
- }
198
- }
199
- }
200
-
201
158
var downloadZipCache par.Cache
202
159
203
160
// DownloadZip downloads the specific module version to the
0 commit comments