Repository: google/gvisor
File: runsc/specutils/specutils.go (Go)

Code before:
	if err != nil {
		return nil, fmt.Errorf("error opening spec file %q: %v", filepath.Join(bundleDir, "config.json"), err)
	}
	defer specFile.Close()
	return ReadSpecFromFile(bundleDir, specFile, conf)
}

// ReadSpecFromFile reads an OCI runtime spec from the given file. It also fixes
// up the spec so that the rest of the code doesn't need to worry about it.
//  1. Normalizes all relative paths into absolute by prepending the bundle
//     dir to them.
//  2. Looks for flag overrides and applies them if any.
//  3. Removes seccomp rules if `RuntimeDefault` was used.
func ReadSpecFromFile(bundleDir string, specFile *os.File, conf *config.Config) (*specs.Spec, error) {
	if _, err := specFile.Seek(0, io.SeekStart); err != nil {
		return nil, fmt.Errorf("error seeking to beginning of file %q: %v", specFile.Name(), err)
	}
	specBytes, err := io.ReadAll(specFile)
	if err != nil {
		return nil, fmt.Errorf("error reading spec from file %q: %v", specFile.Name(), err)
	}
	var spec specs.Spec
	decoder := json.NewDecoder(bytes.NewReader(specBytes))
	if !conf.AllowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if err := decoder.Decode(&spec); err != nil {
		return nil, fmt.Errorf("error unmarshaling spec from file %q: %v\n %s", specFile.Name(), err, string(specBytes))
	}
	if err := ValidateSpec(&spec); err != nil {
		return nil, err
	}
	if err := fixSpec(&spec, bundleDir, conf); err != nil {
		return nil, err
	}
	return &spec, nil
}

func fixSpec(spec *specs.Spec, bundleDir string, conf *config.Config) error {
	// Turn any relative paths in the spec to absolute by prepending the bundleDir.
	spec.Root.Path = absPath(bundleDir, spec.Root.Path)
	for i := range spec.Mounts {
		m := &spec.Mounts[i]
		if m.Source != "" {
			m.Source = absPath(bundleDir, m.Source)
		}
	}
	// Look for config bundle annotations and verify that they exist.
	const configBundlePrefix = "dev.gvisor.bundle."
	var bundles []config.BundleName
	for annotation, val := range spec.Annotations {

Review comment (bug):
Sorry I only thought about this after seconds review, but what do you think about removing the flag, and instead having the behavior here be like:
- Try to parse with the `DisallowUnknownFields` option set.
- If it succeeds, great, go ahead.
- If it fails, try to parse again, this time without the `DisallowUnknownFields` option.
- If that fails too (malformed JSON?), return the error from the *second* parsing attempt.
- If the second parsing attempt succeeds, then log a warning with the error from the *first* parsing attempt, but still continue with the originally-decoded JSON.

This way avoids introducing a new flag, and also makes any lack of specific OCI option support in `runsc` more obvious even with default flags.

Code after:
	if err != nil {
		return nil, fmt.Errorf("error opening spec file %q: %v", filepath.Join(bundleDir, "config.json"), err)
	}
	defer specFile.Close()
	return ReadSpecFromFile(bundleDir, specFile, conf)
}

// ReadSpecFromFile reads an OCI runtime spec from the given file. It also fixes
// up the spec so that the rest of the code doesn't need to worry about it.
//  1. Normalizes all relative paths into absolute by prepending the bundle
//     dir to them.
//  2. Looks for flag overrides and applies them if any.
//  3. Removes seccomp rules if `RuntimeDefault` was used.
func ReadSpecFromFile(bundleDir string, specFile *os.File, conf *config.Config) (*specs.Spec, error) {
	if _, err := specFile.Seek(0, io.SeekStart); err != nil {
		return nil, fmt.Errorf("error seeking to beginning of file %q: %v", specFile.Name(), err)
	}
	specBytes, err := io.ReadAll(specFile)
	if err != nil {
		return nil, fmt.Errorf("error reading spec from file %q: %v", specFile.Name(), err)
	}
	var spec specs.Spec
	decoder := json.NewDecoder(bytes.NewReader(specBytes))
	decoder.DisallowUnknownFields()
	if errLooseDecode := decoder.Decode(&spec); errLooseDecode != nil {
		if errStrictDecode := json.Unmarshal(specBytes, &spec); errStrictDecode != nil {
			return nil, fmt.Errorf("error unmarshaling spec from file %q: %v\n %s", specFile.Name(), errStrictDecode, string(specBytes))
		} else {
			log.Warningf("OCI spec file %q contains fields unknown to `runsc`: %v. Ignoring these fields and continuing anyway.", specFile.Name(), errLooseDecode)
		}
	}
	if err := ValidateSpec(&spec); err != nil {
		return nil, err
	}
	if err := fixSpec(&spec, bundleDir, conf); err != nil {
		return nil, err
	}
	return &spec, nil
}

func fixSpec(spec *specs.Spec, bundleDir string, conf *config.Config) error {
	// Turn any relative paths in the spec to absolute by prepending the bundleDir.
	spec.Root.Path = absPath(bundleDir, spec.Root.Path)
	for i := range spec.Mounts {
		m := &spec.Mounts[i]
		if m.Source != "" {
			m.Source = absPath(bundleDir, m.Source)
		}
	}
	// Look for config bundle annotations and verify that they exist.
	const configBundlePrefix = "dev.gvisor.bundle."