@@ -35,9 +35,9 @@ func HasFilepathPrefix(path, prefix string) (bool, error) {
35
35
// handling of volume name/drive letter on Windows. vnPath and vnPrefix
36
36
// are first compared, and then used to initialize initial values of p and
37
37
// d which will be appended to for incremental checks using
38
- // isCaseSensitiveFilesystem and then equality.
38
+ // IsCaseSensitiveFilesystem and then equality.
39
39
40
- // no need to check isCaseSensitiveFilesystem because VolumeName return
40
+ // no need to check IsCaseSensitiveFilesystem because VolumeName return
41
41
// empty string on all non-Windows machines
42
42
vnPath := strings .ToLower (filepath .VolumeName (path ))
43
43
vnPrefix := strings .ToLower (filepath .VolumeName (prefix ))
@@ -82,7 +82,7 @@ func HasFilepathPrefix(path, prefix string) (bool, error) {
82
82
// something like ext4 filesystem mounted on FAT
83
83
// mountpoint, mounted on ext4 filesystem, i.e. the
84
84
// problematic filesystem is not the last one.
85
- caseSensitive , err := isCaseSensitiveFilesystem (filepath .Join (d , dirs [i ]))
85
+ caseSensitive , err := IsCaseSensitiveFilesystem (filepath .Join (d , dirs [i ]))
86
86
if err != nil {
87
87
return false , errors .Wrap (err , "failed to check filepath prefix" )
88
88
}
@@ -135,7 +135,7 @@ func EquivalentPaths(p1, p2 string) (bool, error) {
135
135
}
136
136
137
137
if p1Filename != "" || p2Filename != "" {
138
- caseSensitive , err := isCaseSensitiveFilesystem (filepath .Join (p1 , p1Filename ))
138
+ caseSensitive , err := IsCaseSensitiveFilesystem (filepath .Join (p1 , p1Filename ))
139
139
if err != nil {
140
140
return false , errors .Wrap (err , "could not check for filesystem case-sensitivity" )
141
141
}
@@ -193,7 +193,7 @@ func renameByCopy(src, dst string) error {
193
193
return errors .Wrapf (os .RemoveAll (src ), "cannot delete %s" , src )
194
194
}
195
195
196
- // isCaseSensitiveFilesystem determines if the filesystem where dir
196
+ // IsCaseSensitiveFilesystem determines if the filesystem where dir
197
197
// exists is case sensitive or not.
198
198
//
199
199
// CAVEAT: this function works by taking the last component of the given
@@ -212,7 +212,7 @@ func renameByCopy(src, dst string) error {
212
212
// If the input directory is such that the last component is composed
213
213
// exclusively of case-less codepoints (e.g. numbers), this function will
214
214
// return false.
215
- func isCaseSensitiveFilesystem (dir string ) (bool , error ) {
215
+ func IsCaseSensitiveFilesystem (dir string ) (bool , error ) {
216
216
alt := filepath .Join (filepath .Dir (dir ), genTestFilename (filepath .Base (dir )))
217
217
218
218
dInfo , err := os .Stat (dir )
@@ -264,6 +264,89 @@ func genTestFilename(str string) string {
264
264
}, str )
265
265
}
266
266
267
+ var errPathNotDir = errors .New ("given path is not a directory" )
268
+
269
+ // ReadActualFilenames is used to determine the actual file names in given directory.
270
+ //
271
+ // On case sensitive file systems like ext4, it will check if those files exist using
272
+ // `os.Stat` and return a map with key and value as filenames which exist in the folder.
273
+ //
274
+ // Otherwise, it reads the contents of the directory
275
+ func ReadActualFilenames (dirPath string , names []string ) (map [string ]string , error ) {
276
+ actualFilenames := make (map [string ]string , len (names ))
277
+ if len (names ) <= 0 {
278
+ // This isn't expected to happen for current usage.
279
+ // Adding edge case handling, maybe useful in future
280
+ return actualFilenames , nil
281
+ }
282
+ // First, check that the given path is valid and it is a directory
283
+ dirStat , err := os .Stat (dirPath )
284
+ if err != nil {
285
+ return nil , errors .Wrap (err , "failed to read actual filenames" )
286
+ }
287
+
288
+ if ! dirStat .IsDir () {
289
+ return nil , errPathNotDir
290
+ }
291
+
292
+ // Ideally, we would use `os.Stat` for getting the actual file names
293
+ // but that returns the name we passed in as an argument and not the actual filename.
294
+ // So we are forced to list the directory contents and check
295
+ // against that. Since this check is costly, we do it only if absolutely necessary.
296
+ caseSensitive , err := IsCaseSensitiveFilesystem (dirPath )
297
+ if err != nil {
298
+ return nil , errors .Wrap (err , "failed to read actual filenames" )
299
+ }
300
+ if caseSensitive {
301
+ // There will be no difference between actual filename and given filename
302
+ // So just check if those files exist.
303
+ for _ , name := range names {
304
+ _ , err := os .Stat (filepath .Join (dirPath , name ))
305
+ if err == nil {
306
+ actualFilenames [name ] = name
307
+ } else if ! os .IsNotExist (err ) {
308
+ // Some unexpected err, return it.
309
+ return nil , errors .Wrap (err , "failed to read actual filenames" )
310
+ }
311
+ }
312
+ return actualFilenames , nil
313
+ }
314
+
315
+ dir , err := os .Open (dirPath )
316
+ if err != nil {
317
+ return nil , errors .Wrap (err , "failed to read actual filenames" )
318
+ }
319
+ defer dir .Close ()
320
+
321
+ // Pass -1 to read all files in directory
322
+ files , err := dir .Readdir (- 1 )
323
+ if err != nil {
324
+ return nil , errors .Wrap (err , "failed to read actual filenames" )
325
+ }
326
+
327
+ // namesMap holds the mapping from lowercase name to search name.
328
+ // Using this, we can avoid repeatedly looping through names.
329
+ namesMap := make (map [string ]string , len (names ))
330
+ for _ , name := range names {
331
+ namesMap [strings .ToLower (name )] = name
332
+ }
333
+
334
+ for _ , file := range files {
335
+ if file .Mode ().IsRegular () {
336
+ searchName , ok := namesMap [strings .ToLower (file .Name ())]
337
+ if ok {
338
+ // We are interested in this file, case insensitive match successful
339
+ actualFilenames [searchName ] = file .Name ()
340
+ if len (actualFilenames ) == len (names ) {
341
+ // We found all that we were looking for
342
+ return actualFilenames , nil
343
+ }
344
+ }
345
+ }
346
+ }
347
+ return actualFilenames , nil
348
+ }
349
+
267
350
var (
268
351
errSrcNotDir = errors .New ("source is not a directory" )
269
352
errDstExist = errors .New ("destination already exists" )
0 commit comments