Support recursively read-only (RRO) mounts
<!--
Please make sure you've read and understood our contributing guidelines;
https://github.com/moby/moby/blob/master/CONTRIBUTING.md

** Make sure all your commits include a signature generated with `git commit -s` **

For additional information on our contributing process, read our contributing
guide https://docs.docker.com/opensource/code/

If this is a bug fix, make sure your description includes "fixes #xxxx", or
"closes #xxxx"

Please provide the following information:
-->
- Fixes #44978
- Fixes docker/for-linux#788
- Fixes https://github.com/moby/moby/issues/23499



**- What I did**

Added the support for recursively read-only (RRO) mounts.

`docker run -v /foo:/foo:ro` is now recursively read-only on kernel >= 5.12.
Automatically falls back to the legacy non-recursively read-only mount mode on kernel < 5.12.

~Use `ro-non-recursive` to disable RRO.
Use `ro-force-recursive` or `rro` to explicitly enable RRO. (Fails on kernel < 5.12)~
(**EDIT**: the CLI options are being changed in https://github.com/moby/moby/pull/46037 and https://github.com/docker/cli/pull/4316 )

It is highly recommended to use RRO in conjunction with `rprivate` propagation.

**- How I did it**

By using OCI mount option "rro", which is implemented by calling `mount_setattr(2)` with  `AT_RECURSIVE` and `MOUNT_ATTR_RDONLY`.

**- How to verify it**

Run `docker run -v /mnt:/mnt:ro,rprivate` on kernel >= 5.12, and make sure `/mnt/recursive-mount-dir` is read-only.

**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->
Support recursively read-only (RRO) mounts

**- A picture of a cute animal (not mandatory but encouraged)**
:penguin:

volume/mounts/parser.go
package mounts // import "github.com/docker/docker/volume/mounts"

import (
	"errors"
	"runtime"

	"github.com/docker/docker/api/types/mount"
)

// ErrVolumeTargetIsRoot is returned when the target destination is root.
// It's used by both LCOW and Linux parsers.
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")

// read-write modes
var rwModes = map[string]bool{
	"rw": true,
	"ro": true,
}

// Parser represents a platform specific parser for mount expressions
type Parser interface {
	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
	ParseVolumesFrom(spec string) (string, string, error)
	DefaultPropagationMode() mount.Propagation
	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
	DefaultCopyMode() bool
	ValidateVolumeName(name string) error
	ReadWrite(mode string) bool
	IsBackwardCompatible(m *MountPoint) bool
	HasResource(m *MountPoint, absPath string) bool
	ValidateTmpfsMountDestination(dest string) error
	ValidateMountConfig(mt *mount.Mount) error
}

// NewParser creates a parser for the current host OS
func NewParser() Parser {
	if runtime.GOOS == "windows" {
		return NewWindowsParser()
	}
	return NewLinuxParser()
}

package mounts // import "github.com/docker/docker/volume/mounts"

import (
	"errors"
	"runtime"

	"github.com/docker/docker/api/types/mount"
)

// ErrVolumeTargetIsRoot is returned when the target destination is root.
// It's used by both LCOW and Linux parsers.
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")

// read-write modes
var rwModes = map[string]bool{
	"rw":                 true,
	"ro":                 true, // attempts recursive read-only if possible
	"ro-non-recursive":   true, // makes the mount non-recursively read-only, but still leaves the mount recursive
	"ro-force-recursive": true, // raises an error if the mount cannot be made recursively read-only
	"rro":                true, // alias for ro-force-recursive
}

// Parser represents a platform specific parser for mount expressions
type Parser interface {
	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
	ParseVolumesFrom(spec string) (string, string, error)
	DefaultPropagationMode() mount.Propagation
	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
	DefaultCopyMode() bool
	ValidateVolumeName(name string) error
	ReadWrite(mode string) bool
	IsBackwardCompatible(m *MountPoint) bool
	HasResource(m *MountPoint, absPath string) bool
	ValidateTmpfsMountDestination(dest string) error
	ValidateMountConfig(mt *mount.Mount) error
}

// NewParser creates a parser for the current host OS
func NewParser() Parser {
	if runtime.GOOS == "windows" {
		return NewWindowsParser()
	}
	return NewLinuxParser()
}

- For future reference; we reverted the new options for the short-hand form, and only keep the advanced options for the advanced (`--mount`) syntax; see https://github.com/moby/moby/pull/46037