func()

in lib/lib.go [269:417]


func (obj *Scanner) Scan(ctx context.Context, path safepath.Path, info *interfaces.Info) error {
	errors := []error{}
	mu := &sync.Mutex{} // guards list of errors
	wg := &sync.WaitGroup{}

	// TODO: we could switch and avoid doing this if we knew that
	// zero backends were going to need it, but we know most will,
	// so avoid optimizing early, and skip pre-checking for this.
	var data []byte
	var err error
	if !info.FileInfo.IsDir() {
		data, err = os.ReadFile(path.Path())
		if err != nil {
			return err // TODO: errwrap?
		}
	}

Loop:
	for _, backend := range obj.Backends {
		// Some backends aren't particularly well-behaved with
		// regards to obeying the context cancellation signal.
		// In an effort to short-circuit things if needed, we
		// run a check ourselves and break out early if we see
		// that we have cancelled early. This change improves
		// cancellation latency significantly.
		select {
		case <-ctx.Done():
			errors = append(errors, ctx.Err())
			break Loop
		default:
		}

		// TODO: add a counting semaphore if it's desired
		wg.Add(1)
		obj.wg.Add(1)
		go func(backend interfaces.Backend) {
			defer wg.Done()
			defer obj.wg.Done()

			if obj.Debug {
				obj.Logf("scanning: %s", path)
			}

			var result *interfaces.Result
			var err error

			// XXX: cache lookups here?
			//	if x, ok := backend.(interfaces.CachedDataBackend); ok {
			//		result, err = x.LookupData(ctx, data, info)
			//	} else if x, ok := backend.(interfaces.CachedPathBackend); ok {
			//		result, err = x.LookupPath(ctx, path, info)
			//	}
			// XXX: if err, look at cache policy, otherwise continue or err

			if _, exists := obj.skipdirs[backend][info.UID]; info.FileInfo.IsDir() && exists {
				if obj.Debug {
					obj.Logf("skip dir: %s", path)
				}
				return
			}

			// XXX: wrap these in a helper function
			if x, ok := backend.(interfaces.DataBackend); ok {
				//if len(data) == 0 { // possible directory
				//	return // skip directories!
				//}
				result, err = x.ScanData(ctx, data, info)
			} else if x, ok := backend.(interfaces.PathBackend); ok {
				result, err = x.ScanPath(ctx, path, info)
			} else {
				return
			}

			// If a backend returns interfaces.SkipDir, then
			// this is the signal that it doesn't need to
			// return any different information in a deeper
			// hierarchy of that scan. This is common when
			// we have a backend that makes a whole
			// repository or directory determination. In
			// this case, store this rejection so that we
			// don't wastefully call this backend again with
			// a child path. The backend will likely want to
			// also return a result with the SkipDir. It
			// must not return SkipDir in response to a non
			// directory path.
			if err == interfaces.SkipDir {
				// TODO: we could use a different mutex
				// but I'm lazy and it won't help much!
				obj.mu.Lock()
				obj.skipdirs[backend][info.UID] = struct{}{}
				obj.mu.Unlock()
			} else if err != nil {
				// XXX: ShutdownOnError and cancel the ctx?
				mu.Lock()
				errors = append(errors, err)
				mu.Unlock()
				return // goroutine ends
			}

			if result == nil { // skip nil results
				return
			}
			// tag (annotate) the result
			tagResultBackend(result, backend)

			// store results
			obj.mu.Lock()
			if _, exists := obj.results[info.UID]; !exists {
				obj.results[info.UID] = make(map[interfaces.Backend]*interfaces.Result)
			}
			if old, exists := obj.results[info.UID][backend]; exists {
				// If we get a duplicate result, this can happen
				// if there's a bug, or if we asked to scan the
				// same thing more than once. As a result, run a
				// cmp on both results, and if they're the same,
				// then we can safely ignore this issue.
				// XXX: can cached results cause this to fail?
				if err := old.Cmp(result); err != nil {
					e := errwrap.Wrapf(err, "duplicate result for path: %s", path)
					mu.Lock()
					errors = append(errors, e)
					mu.Unlock()
					return // goroutine ends
				}
			}
			obj.results[info.UID][backend] = result
			obj.mu.Unlock()

			// XXX: cache results
			//	if x, ok := backend.(interfaces.CachedDataBackend); ok {
			//		result, err = x.LookupData(ctx, data, info)
			//	} else if x, ok := backend.(interfaces.CachedPathBackend); ok {
			//		result, err = x.LookupPath(ctx, path, info)
			//	}

		}(backend)
	}
	wg.Wait()

	if len(errors) > 0 {
		var ea error
		for _, e := range errors {
			ea = errwrap.Append(ea, e)
		}
		return errwrap.Wrapf(ea, "scan func errored")
	}

	return nil
}