| skipped 2 lines |
3 | 3 | | import ( |
4 | 4 | | "errors" |
5 | 5 | | "fmt" |
| 6 | + | "github.com/anchore/stereoscope/pkg/query" |
6 | 7 | | "io" |
7 | 8 | | "io/fs" |
8 | 9 | | "os" |
| skipped 31 lines |
40 | 41 | | currentWdRelativeToRoot string |
41 | 42 | | currentWd string |
42 | 43 | | fileTree *filetree.FileTree |
43 | | - | metadata map[file.ID]FileMetadata |
| 44 | + | fileIndex *file.Index |
44 | 45 | | // TODO: wire up to report these paths in the json report |
45 | 46 | | pathFilterFns []pathFilterFn |
46 | 47 | | refsByMIMEType map[string][]file.Reference |
| skipped 32 lines |
79 | 80 | | currentWd: cleanCWD, |
80 | 81 | | currentWdRelativeToRoot: currentWdRelRoot, |
81 | 82 | | fileTree: filetree.NewFileTree(), |
82 | | - | metadata: make(map[file.ID]FileMetadata), |
| 83 | + | fileIndex: file.NewIndex(), |
83 | 84 | | pathFilterFns: append([]pathFilterFn{isUnallowableFileType, isUnixSystemRuntimePath}, pathFilters...), |
84 | 85 | | refsByMIMEType: make(map[string][]file.Reference), |
85 | 86 | | errPaths: make(map[string]error), |
86 | 87 | | } |
87 | 88 | | |
| 89 | + | log.WithFields("path", cleanRoot).Debug("indexing full filetree") |
88 | 90 | | return &resolver, indexAllRoots(cleanRoot, resolver.indexTree) |
89 | 91 | | } |
90 | 92 | | |
91 | 93 | | func (r *directoryResolver) indexTree(root string, stager *progress.Stage) ([]string, error) { |
92 | | - | log.Debugf("indexing filesystem path=%q", root) |
| 94 | + | log.WithFields("path", root).Trace("indexing filetree") |
93 | 95 | | |
94 | 96 | | var roots []string |
95 | 97 | | var err error |
| skipped 110 lines |
206 | 208 | | |
207 | 209 | | // cases like "/" will be in the tree, but not been indexed yet (a special case). We want to capture |
208 | 210 | | // these cases as new paths to index. |
209 | | - | _, exists = r.metadata[ref.ID()] |
210 | | - | return exists |
| 211 | + | return r.fileIndex.Exists(*ref) |
211 | 212 | | } |
212 | 213 | | |
213 | 214 | | func (r directoryResolver) addDirectoryToIndex(p string, info os.FileInfo) error { |
| skipped 4 lines |
218 | 219 | | |
219 | 220 | | location := NewLocationFromDirectory(p, *ref) |
220 | 221 | | metadata := fileMetadataFromPath(p, info, r.isInIndex(location)) |
221 | | - | r.addFileMetadataToIndex(ref, metadata) |
| 222 | + | r.addFileToFileIndex(ref, metadata) |
222 | 223 | | |
223 | 224 | | return nil |
224 | 225 | | } |
| skipped 6 lines |
231 | 232 | | |
232 | 233 | | location := NewLocationFromDirectory(p, *ref) |
233 | 234 | | metadata := fileMetadataFromPath(p, info, r.isInIndex(location)) |
234 | | - | r.addFileMetadataToIndex(ref, metadata) |
| 235 | + | r.addFileToFileIndex(ref, metadata) |
235 | 236 | | |
236 | 237 | | return nil |
237 | 238 | | } |
| skipped 26 lines |
264 | 265 | | location.VirtualPath = p |
265 | 266 | | metadata := fileMetadataFromPath(p, usedInfo, r.isInIndex(location)) |
266 | 267 | | metadata.LinkDestination = linkTarget |
267 | | - | r.addFileMetadataToIndex(ref, metadata) |
| 268 | + | r.addFileToFileIndex(ref, metadata) |
268 | 269 | | |
269 | 270 | | return targetAbsPath, nil |
270 | 271 | | } |
271 | 272 | | |
272 | | - | func (r directoryResolver) addFileMetadataToIndex(ref *file.Reference, metadata FileMetadata) { |
| 273 | + | func (r directoryResolver) addFileToFileIndex(ref *file.Reference, metadata file.Metadata) { |
273 | 274 | | if ref != nil { |
274 | 275 | | if metadata.MIMEType != "" { |
275 | 276 | | r.refsByMIMEType[metadata.MIMEType] = append(r.refsByMIMEType[metadata.MIMEType], *ref) |
276 | 277 | | } |
277 | | - | r.metadata[ref.ID()] = metadata |
| 278 | + | r.fileIndex.Add(*ref, metadata, nil) |
278 | 279 | | } |
279 | 280 | | } |
280 | 281 | | |
| skipped 54 lines |
335 | 336 | | continue |
336 | 337 | | } |
337 | 338 | | |
338 | | - | // we should be resolving symlinks and preserving this information as a VirtualPath to the real file |
339 | | - | evaluatedPath, err := filepath.EvalSymlinks(userStrPath) |
| 339 | + | tree := r.fileTree |
| 340 | + | _, ref, err := tree.File(file.Path(userStrPath), filetree.FollowBasenameLinks) |
340 | 341 | | if err != nil { |
341 | | - | log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err) |
342 | | - | continue |
| 342 | + | return nil, err |
343 | 343 | | } |
344 | | - | |
345 | | - | // TODO: why not use stored metadata? |
346 | | - | fileMeta, err := os.Stat(evaluatedPath) |
347 | | - | if errors.Is(err, os.ErrNotExist) { |
348 | | - | // note: there are other kinds of errors other than os.ErrNotExist that may be given that is platform |
349 | | - | // specific, but essentially hints at the same overall problem (that the path does not exist). Such an |
350 | | - | // error could be syscall.ENOTDIR (see https://github.com/golang/go/issues/18974). |
351 | | - | continue |
352 | | - | } else if err != nil { |
353 | | - | // we don't want to consider any other syscalls that may hint at non-existence of the file/dir as |
354 | | - | // invalid paths. This logging statement is meant to raise IO or permissions related problems. |
355 | | - | var pathErr *os.PathError |
356 | | - | if !errors.As(err, &pathErr) { |
357 | | - | log.Warnf("path is not valid (%s): %+v", evaluatedPath, err) |
358 | | - | } |
| 344 | + | if ref == nil { |
| 345 | + | // no file found, keep looking through layers |
359 | 346 | | continue |
360 | 347 | | } |
361 | 348 | | |
362 | | - | // don't consider directories |
363 | | - | if fileMeta.IsDir() { |
| 349 | + | // don't consider directories (special case: there is no path information for /) |
| 350 | + | if ref.RealPath == "/" { |
364 | 351 | | continue |
| 352 | + | } else if r.fileIndex.Exists(*ref) { |
| 353 | + | metadata, err := r.fileIndex.Get(*ref) |
| 354 | + | if err != nil { |
| 355 | + | return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err) |
| 356 | + | } |
| 357 | + | if metadata.Metadata.IsDir { |
| 358 | + | continue |
| 359 | + | } |
365 | 360 | | } |
366 | 361 | | |
367 | | - | if runtime.GOOS == WindowsOS { |
368 | | - | userStrPath = windowsToPosix(userStrPath) |
369 | | - | } |
370 | | - | |
371 | | - | exists, ref, err := r.fileTree.File(file.Path(userStrPath), filetree.FollowBasenameLinks) |
372 | | - | if err == nil && exists { |
373 | | - | loc := NewVirtualLocationFromDirectory( |
374 | | - | r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root |
375 | | - | r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root |
376 | | - | *ref, |
377 | | - | ) |
378 | | - | references = append(references, loc) |
379 | | - | } |
| 362 | + | loc := NewVirtualLocationFromDirectory( |
| 363 | + | r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root |
| 364 | + | r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root |
| 365 | + | *ref, |
| 366 | + | ) |
| 367 | + | references = append(references, loc) |
380 | 368 | | } |
381 | 369 | | |
382 | 370 | | return references, nil |
| skipped 25 lines |
408 | 396 | | result := make([]Location, 0) |
409 | 397 | | |
410 | 398 | | for _, extension := range extensions { |
411 | | - | // TODO: is there a faster way to do this? |
412 | | - | globResults, err := r.fileTree.FilesByGlob("**/*"+extension, filetree.FollowBasenameLinks) |
| 399 | + | |
| 400 | + | refs, err := query.FilesByExtension(r.fileTree, r.fileIndex, extension) |
413 | 401 | | if err != nil { |
414 | 402 | | return nil, err |
415 | 403 | | } |
416 | | - | for _, globResult := range globResults { |
417 | | - | loc := NewVirtualLocationFromDirectory( |
418 | | - | r.responsePath(string(globResult.Reference.RealPath)), // the actual path relative to the resolver root |
419 | | - | r.responsePath(string(globResult.MatchPath)), // the path used to access this file, relative to the resolver root |
420 | | - | globResult.Reference, |
| 404 | + | for _, ref := range refs { |
| 405 | + | loc := NewLocationFromDirectory( |
| 406 | + | r.responsePath(string(ref.RealPath)), |
| 407 | + | ref, |
421 | 408 | | ) |
422 | 409 | | result = append(result, loc) |
423 | 410 | | } |
| skipped 6 lines |
430 | 417 | | result := make([]Location, 0) |
431 | 418 | | |
432 | 419 | | for _, filename := range filenames { |
433 | | - | // TODO: is there a faster way to do this? |
434 | | - | globResults, err := r.fileTree.FilesByGlob("**/"+filename, filetree.FollowBasenameLinks) |
| 420 | + | refs, err := query.FilesByBasename(r.fileTree, r.fileIndex, filename) |
435 | 421 | | if err != nil { |
436 | 422 | | return nil, err |
437 | 423 | | } |
438 | | - | for _, globResult := range globResults { |
439 | | - | loc := NewVirtualLocationFromDirectory( |
440 | | - | r.responsePath(string(globResult.Reference.RealPath)), // the actual path relative to the resolver root |
441 | | - | r.responsePath(string(globResult.MatchPath)), // the path used to access this file, relative to the resolver root |
442 | | - | globResult.Reference, |
| 424 | + | for _, ref := range refs { |
| 425 | + | loc := NewLocationFromDirectory( |
| 426 | + | r.responsePath(string(ref.RealPath)), |
| 427 | + | ref, |
443 | 428 | | ) |
444 | 429 | | result = append(result, loc) |
445 | 430 | | } |
| skipped 2 lines |
448 | 433 | | return result, nil |
449 | 434 | | } |
450 | 435 | | |
451 | | - | // TODO: duplicate code with FilesByBasename |
452 | 436 | | func (r directoryResolver) FilesByBasenameGlob(globs ...string) ([]Location, error) { |
453 | 437 | | result := make([]Location, 0) |
454 | 438 | | |
455 | 439 | | for _, glob := range globs { |
456 | | - | // TODO: is there a faster way to do this? |
457 | | - | globResults, err := r.fileTree.FilesByGlob("**/"+glob, filetree.FollowBasenameLinks) |
| 440 | + | refs, err := query.FilesByBasenameGlob(r.fileTree, r.fileIndex, glob) |
458 | 441 | | if err != nil { |
459 | 442 | | return nil, err |
460 | 443 | | } |
461 | | - | for _, globResult := range globResults { |
462 | | - | loc := NewVirtualLocationFromDirectory( |
463 | | - | r.responsePath(string(globResult.Reference.RealPath)), // the actual path relative to the resolver root |
464 | | - | r.responsePath(string(globResult.MatchPath)), // the path used to access this file, relative to the resolver root |
465 | | - | globResult.Reference, |
| 444 | + | for _, ref := range refs { |
| 445 | + | loc := NewLocationFromDirectory( |
| 446 | + | r.responsePath(string(ref.RealPath)), |
| 447 | + | ref, |
466 | 448 | | ) |
467 | 449 | | result = append(result, loc) |
468 | 450 | | } |
| skipped 57 lines |
526 | 508 | | return results |
527 | 509 | | } |
528 | 510 | | |
529 | | - | func (r *directoryResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { |
530 | | - | metadata, exists := r.metadata[location.ref.ID()] |
531 | | - | if !exists { |
532 | | - | return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) |
| 511 | + | func (r *directoryResolver) FileMetadataByLocation(location Location) (file.Metadata, error) { |
| 512 | + | indexEntry, err := r.fileIndex.Get(location.ref) |
| 513 | + | if err != nil { |
| 514 | + | return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, err) |
533 | 515 | | } |
534 | 516 | | |
535 | | - | return metadata, nil |
| 517 | + | return indexEntry.Metadata, nil |
536 | 518 | | } |
537 | 519 | | |
538 | 520 | | func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error) { |
| skipped 115 lines |