mirror of
https://github.com/containers/podman.git
synced 2026-02-05 15:45:08 +01:00
Update common, image, and storage deps to 94e31d2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
17
vendor/github.com/moby/moby/client/README.md
generated
vendored
17
vendor/github.com/moby/moby/client/README.md
generated
vendored
@@ -23,19 +23,28 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
// Create a new client that handles common environment variables
|
||||
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
|
||||
// API-version negotiation to allow downgrading the API version
|
||||
// when connecting with an older daemon version.
|
||||
apiClient, err := client.New(client.FromEnv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer apiClient.Close()
|
||||
|
||||
containers, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{All: true})
|
||||
// List all containers (both stopped and running).
|
||||
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, ctr := range containers {
|
||||
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
|
||||
// Print each container's ID, status and the image it was created from.
|
||||
fmt.Printf("%s %-22s %s\n", "ID", "STATUS", "IMAGE")
|
||||
for _, ctr := range result.Items {
|
||||
fmt.Printf("%s %-22s %s\n", ctr.ID, ctr.Status, ctr.Image)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
82
vendor/github.com/moby/moby/client/client.go
generated
vendored
82
vendor/github.com/moby/moby/client/client.go
generated
vendored
@@ -8,10 +8,8 @@ https://docs.docker.com/reference/api/engine/
|
||||
|
||||
You use the library by constructing a client object using [New]
|
||||
and calling methods on it. The client can be configured from environment
|
||||
variables by passing the [FromEnv] option, and the [WithAPIVersionNegotiation]
|
||||
option to allow downgrading the API version used when connecting with an older
|
||||
daemon version. Other options cen be configured manually by passing any of
|
||||
the available [Opt] options.
|
||||
variables by passing the [FromEnv] option. Other options can be configured
|
||||
manually by passing any of the available [Opt] options.
|
||||
|
||||
For example, to list running containers (the equivalent of "docker ps"):
|
||||
|
||||
@@ -30,7 +28,7 @@ For example, to list running containers (the equivalent of "docker ps"):
|
||||
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
|
||||
// API-version negotiation to allow downgrading the API version
|
||||
// when connecting with an older daemon version.
|
||||
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
apiClient, err := client.New(client.FromEnv)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -103,18 +101,16 @@ import (
|
||||
const DummyHost = "api.moby.localhost"
|
||||
|
||||
// MaxAPIVersion is the highest REST API version supported by the client.
|
||||
// If API-version negotiation is enabled (see [WithAPIVersionNegotiation],
|
||||
// [Client.NegotiateAPIVersion]), the client may downgrade its API version.
|
||||
// Similarly, the [WithVersion] and [WithVersionFromEnv] allow overriding
|
||||
// the version.
|
||||
// If API-version negotiation is enabled, the client may downgrade its API version.
|
||||
// Similarly, the [WithAPIVersion] and [WithAPIVersionFromEnv] options allow
|
||||
// overriding the version and disable API-version negotiation.
|
||||
//
|
||||
// This version may be lower than the version of the api library module used.
|
||||
const MaxAPIVersion = "1.52"
|
||||
|
||||
// fallbackAPIVersion is the version to fall back to if API-version negotiation
|
||||
// fails. API versions below this version are not supported by the client,
|
||||
// and not considered when negotiating.
|
||||
const fallbackAPIVersion = "1.44"
|
||||
// MinAPIVersion is the minimum API version supported by the client. API versions
|
||||
// below this version are not considered when performing API-version negotiation.
|
||||
const MinAPIVersion = "1.44"
|
||||
|
||||
// Ensure that Client always implements APIClient.
|
||||
var _ APIClient = &Client{}
|
||||
@@ -174,8 +170,13 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||
// It takes an optional list of [Opt] functional arguments, which are applied in
|
||||
// the order they're provided, which allows modifying the defaults when creating
|
||||
// the client. For example, the following initializes a client that configures
|
||||
// itself with values from environment variables ([FromEnv]), and has automatic
|
||||
// API version negotiation enabled ([WithAPIVersionNegotiation]).
|
||||
// itself with values from environment variables ([FromEnv]).
|
||||
//
|
||||
// By default, the client automatically negotiates the API version to use when
|
||||
// making requests. API version negotiation is performed on the first request;
|
||||
// subsequent requests do not re-negotiate. Use [WithAPIVersion] or
|
||||
// [WithAPIVersionFromEnv] to configure the client with a fixed API version
|
||||
// and disable API version negotiation.
|
||||
//
|
||||
// cli, err := client.New(
|
||||
// client.FromEnv,
|
||||
@@ -213,6 +214,12 @@ func New(ops ...Opt) (*Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.envAPIVersion != "" {
|
||||
c.setAPIVersion(cfg.envAPIVersion)
|
||||
} else if cfg.manualAPIVersion != "" {
|
||||
c.setAPIVersion(cfg.manualAPIVersion)
|
||||
}
|
||||
|
||||
if tr, ok := c.client.Transport.(*http.Transport); ok {
|
||||
// Store the base transport before we wrap it in tracing libs below
|
||||
// This is used, as an example, to close idle connections when the client is closed
|
||||
@@ -278,7 +285,7 @@ func (cli *Client) Close() error {
|
||||
// be negotiated when making the actual requests, and for which cases
|
||||
// we cannot do the negotiation lazily.
|
||||
func (cli *Client) checkVersion(ctx context.Context) error {
|
||||
if cli.manualOverride || !cli.negotiateVersion || cli.negotiated.Load() {
|
||||
if cli.negotiated.Load() {
|
||||
return nil
|
||||
}
|
||||
_, err := cli.Ping(ctx, PingOptions{
|
||||
@@ -306,36 +313,47 @@ func (cli *Client) ClientVersion() string {
|
||||
}
|
||||
|
||||
// negotiateAPIVersion updates the version to match the API version from
|
||||
// the ping response. It falls back to the lowest version supported if the
|
||||
// API version is empty, or returns an error if the API version is lower than
|
||||
// the lowest supported API version, in which case the version is not modified.
|
||||
// the ping response.
|
||||
//
|
||||
// It returns an error if version is invalid, or lower than the minimum
|
||||
// supported API version in which case the client's API version is not
|
||||
// updated, and negotiation is not marked as completed.
|
||||
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
|
||||
pingVersion = strings.TrimPrefix(pingVersion, "v")
|
||||
if pingVersion == "" {
|
||||
// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
|
||||
pingVersion = fallbackAPIVersion
|
||||
} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
|
||||
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
|
||||
var err error
|
||||
pingVersion, err = parseAPIVersion(pingVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if versions.LessThan(pingVersion, MinAPIVersion) {
|
||||
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, MinAPIVersion))
|
||||
}
|
||||
|
||||
// if the client is not initialized with a version, start with the latest supported version
|
||||
if cli.version == "" {
|
||||
cli.version = MaxAPIVersion
|
||||
negotiatedVersion := cli.version
|
||||
if negotiatedVersion == "" {
|
||||
negotiatedVersion = MaxAPIVersion
|
||||
}
|
||||
|
||||
// if server version is lower than the client version, downgrade
|
||||
if versions.LessThan(pingVersion, cli.version) {
|
||||
cli.version = pingVersion
|
||||
if versions.LessThan(pingVersion, negotiatedVersion) {
|
||||
negotiatedVersion = pingVersion
|
||||
}
|
||||
|
||||
// Store the results, so that automatic API version negotiation (if enabled)
|
||||
// won't be performed on the next request.
|
||||
if cli.negotiateVersion {
|
||||
cli.negotiated.Store(true)
|
||||
}
|
||||
cli.setAPIVersion(negotiatedVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAPIVersion sets the client's API version and marks API version negotiation
|
||||
// as completed, so that automatic API version negotiation (if enabled) won't
|
||||
// be performed on the next request.
|
||||
func (cli *Client) setAPIVersion(version string) {
|
||||
cli.version = version
|
||||
cli.negotiated.Store(true)
|
||||
}
|
||||
|
||||
// DaemonHost returns the host address used by the client
|
||||
func (cli *Client) DaemonHost() string {
|
||||
return cli.host
|
||||
|
||||
@@ -38,14 +38,22 @@ type clientConfig struct {
|
||||
userAgent *string
|
||||
// custom HTTP headers configured by users.
|
||||
customHTTPHeaders map[string]string
|
||||
// manualOverride is set to true when the version was set by users.
|
||||
manualOverride bool
|
||||
|
||||
// negotiateVersion indicates if the client should automatically negotiate
|
||||
// the API version to use when making requests. API version negotiation is
|
||||
// performed on the first request, after which negotiated is set to "true"
|
||||
// so that subsequent requests do not re-negotiate.
|
||||
negotiateVersion bool
|
||||
// manualAPIVersion contains the API version set by users. This field
|
||||
// will only be non-empty if a valid-formed version was set through
|
||||
// [WithAPIVersion].
|
||||
//
|
||||
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
|
||||
// takes precedence. Either field disables API-version negotiation.
|
||||
manualAPIVersion string
|
||||
|
||||
// envAPIVersion contains the API version set by users. This field
|
||||
// will only be non-empty if a valid-formed version was set through
|
||||
// [WithAPIVersionFromEnv].
|
||||
//
|
||||
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
|
||||
// takes precedence. Either field disables API-version negotiation.
|
||||
envAPIVersion string
|
||||
|
||||
// traceOpts is a list of options to configure the tracing span.
|
||||
traceOpts []otelhttp.Option
|
||||
@@ -56,7 +64,7 @@ type Opt func(*clientConfig) error
|
||||
|
||||
// FromEnv configures the client with values from environment variables. It
|
||||
// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
|
||||
// and [WithVersionFromEnv] options.
|
||||
// and [WithAPIVersionFromEnv] options.
|
||||
//
|
||||
// FromEnv uses the following environment variables:
|
||||
//
|
||||
@@ -71,7 +79,7 @@ func FromEnv(c *clientConfig) error {
|
||||
ops := []Opt{
|
||||
WithTLSClientConfigFromEnv(),
|
||||
WithHostFromEnv(),
|
||||
WithVersionFromEnv(),
|
||||
WithAPIVersionFromEnv(),
|
||||
}
|
||||
for _, op := range ops {
|
||||
if err := op(c); err != nil {
|
||||
@@ -241,18 +249,59 @@ func WithTLSClientConfigFromEnv() Opt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithVersion overrides the client version with the specified one. If an empty
|
||||
// version is provided, the value is ignored to allow version negotiation
|
||||
// (see [WithAPIVersionNegotiation]).
|
||||
// WithAPIVersion overrides the client's API version with the specified one,
|
||||
// and disables API version negotiation. If an empty version is provided,
|
||||
// this option is ignored to allow version negotiation. The given version
|
||||
// should be formatted "<major>.<minor>" (for example, "1.52"). It returns
|
||||
// an error if the given value not in the correct format.
|
||||
//
|
||||
// WithVersion does not validate if the client supports the given version,
|
||||
// and callers should verify if the version is in the correct format and
|
||||
// lower than the maximum supported version as defined by [MaxAPIVersion].
|
||||
func WithVersion(version string) Opt {
|
||||
// WithAPIVersion does not validate if the client supports the given version,
|
||||
// and callers should verify if the version lower than the maximum supported
|
||||
// version as defined by [MaxAPIVersion].
|
||||
//
|
||||
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
|
||||
// [WithAPIVersionFromEnv] are both set.
|
||||
func WithAPIVersion(version string) Opt {
|
||||
return func(c *clientConfig) error {
|
||||
if v := strings.TrimPrefix(version, "v"); v != "" {
|
||||
c.version = v
|
||||
c.manualOverride = true
|
||||
version = strings.TrimSpace(version)
|
||||
if val := strings.TrimPrefix(version, "v"); val != "" {
|
||||
ver, err := parseAPIVersion(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid API version (%s): %w", version, err)
|
||||
}
|
||||
c.manualAPIVersion = ver
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithVersion overrides the client version with the specified one.
|
||||
//
|
||||
// Deprecated: use [WithAPIVersion] instead.
|
||||
func WithVersion(version string) Opt {
|
||||
return WithAPIVersion(version)
|
||||
}
|
||||
|
||||
// WithAPIVersionFromEnv overrides the client version with the version specified in
|
||||
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
|
||||
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
|
||||
// is not modified.
|
||||
//
|
||||
// WithAPIVersion does not validate if the client supports the given version,
|
||||
// and callers should verify if the version lower than the maximum supported
|
||||
// version as defined by [MaxAPIVersion].
|
||||
//
|
||||
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
|
||||
// [WithAPIVersionFromEnv] are both set.
|
||||
func WithAPIVersionFromEnv() Opt {
|
||||
return func(c *clientConfig) error {
|
||||
version := strings.TrimSpace(os.Getenv(EnvOverrideAPIVersion))
|
||||
if val := strings.TrimPrefix(version, "v"); val != "" {
|
||||
ver, err := parseAPIVersion(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid API version (%s): %w", version, err)
|
||||
}
|
||||
c.envAPIVersion = ver
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -260,25 +309,21 @@ func WithVersion(version string) Opt {
|
||||
|
||||
// WithVersionFromEnv overrides the client version with the version specified in
|
||||
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
|
||||
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
|
||||
// is not modified.
|
||||
//
|
||||
// WithVersion does not validate if the client supports the given version,
|
||||
// and callers should verify if the version is in the correct format and
|
||||
// lower than the maximum supported version as defined by [MaxAPIVersion].
|
||||
// Deprecated: use [WithAPIVersionFromEnv] instead.
|
||||
func WithVersionFromEnv() Opt {
|
||||
return func(c *clientConfig) error {
|
||||
return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
|
||||
}
|
||||
return WithAPIVersionFromEnv()
|
||||
}
|
||||
|
||||
// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
|
||||
// With this option enabled, the client automatically negotiates the API version
|
||||
// to use when making requests. API version negotiation is performed on the first
|
||||
// request; subsequent requests do not re-negotiate.
|
||||
//
|
||||
// Deprecated: API-version negotiation is now enabled by default. Use [WithAPIVersion]
|
||||
// or [WithAPIVersionFromEnv] to disable API version negotiation.
|
||||
func WithAPIVersionNegotiation() Opt {
|
||||
return func(c *clientConfig) error {
|
||||
c.negotiateVersion = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
2
vendor/github.com/moby/moby/client/envvars.go
generated
vendored
2
vendor/github.com/moby/moby/client/envvars.go
generated
vendored
@@ -13,7 +13,7 @@ const (
|
||||
// be used to override the API version to use. Value must be
|
||||
// formatted as MAJOR.MINOR, for example, "1.19".
|
||||
//
|
||||
// This env-var is read by [FromEnv] and [WithVersionFromEnv] and when set to a
|
||||
// This env-var is read by [FromEnv] and [WithAPIVersionFromEnv] and when set to a
|
||||
// non-empty value, takes precedence over API version negotiation.
|
||||
//
|
||||
// This environment variable should be used for debugging purposes only, as
|
||||
|
||||
33
vendor/github.com/moby/moby/client/ping.go
generated
vendored
33
vendor/github.com/moby/moby/client/ping.go
generated
vendored
@@ -20,7 +20,7 @@ type PingOptions struct {
|
||||
//
|
||||
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
||||
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
|
||||
// with a fixed version ([WithVersion]), no negotiation is performed.
|
||||
// with a fixed version ([WithAPIVersion]), no negotiation is performed.
|
||||
//
|
||||
// If the API server's ping response does not contain an API version, or if the
|
||||
// client did not get a successful ping response, it assumes it is connected with
|
||||
@@ -29,9 +29,8 @@ type PingOptions struct {
|
||||
NegotiateAPIVersion bool
|
||||
|
||||
// ForceNegotiate forces the client to re-negotiate the API version, even if
|
||||
// API-version negotiation already happened. This option cannot be
|
||||
// used if the client is configured with a fixed version using (using
|
||||
// [WithVersion] or [WithVersionFromEnv]).
|
||||
// API-version negotiation already happened or it the client is configured
|
||||
// with a fixed version (using [WithAPIVersion] or [WithAPIVersionFromEnv]).
|
||||
//
|
||||
// This option has no effect if NegotiateAPIVersion is not set.
|
||||
ForceNegotiate bool
|
||||
@@ -72,10 +71,12 @@ type SwarmStatus struct {
|
||||
// for other non-success status codes, failing to connect to the API, or failing
|
||||
// to parse the API response.
|
||||
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
|
||||
if cli.manualOverride {
|
||||
if !options.NegotiateAPIVersion {
|
||||
// No API version negotiation needed; just return ping response.
|
||||
return cli.ping(ctx)
|
||||
}
|
||||
if !options.NegotiateAPIVersion && !cli.negotiateVersion {
|
||||
if cli.negotiated.Load() && !options.ForceNegotiate {
|
||||
// API version was already negotiated or manually set.
|
||||
return cli.ping(ctx)
|
||||
}
|
||||
|
||||
@@ -85,10 +86,19 @@ func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, e
|
||||
|
||||
ping, err := cli.ping(ctx)
|
||||
if err != nil {
|
||||
return cli.ping(ctx)
|
||||
return ping, err
|
||||
}
|
||||
|
||||
if cli.negotiated.Load() && !options.ForceNegotiate {
|
||||
// API version was already negotiated or manually set.
|
||||
//
|
||||
// We check cli.negotiated again under lock, to account for race
|
||||
// conditions with the check at the start of this function.
|
||||
return ping, nil
|
||||
}
|
||||
|
||||
if ping.APIVersion == "" {
|
||||
cli.setAPIVersion(MaxAPIVersion)
|
||||
return ping, nil
|
||||
}
|
||||
|
||||
@@ -112,10 +122,15 @@ func (cli *Client) ping(ctx context.Context) (PingResult, error) {
|
||||
// response-body to get error details from.
|
||||
return newPingResult(resp), nil
|
||||
}
|
||||
// close to allow reusing connection.
|
||||
ensureReaderClosed(resp)
|
||||
|
||||
// HEAD failed or returned a non-OK status; fallback to GET.
|
||||
req.Method = http.MethodGet
|
||||
resp, err = cli.doRequest(req)
|
||||
req2, err := cli.buildRequest(ctx, http.MethodGet, path.Join(cli.basePath, "/_ping"), nil, nil)
|
||||
if err != nil {
|
||||
return PingResult{}, err
|
||||
}
|
||||
resp, err = cli.doRequest(req2)
|
||||
defer ensureReaderClosed(resp)
|
||||
if err != nil {
|
||||
// Failed to connect.
|
||||
|
||||
84
vendor/github.com/moby/moby/client/request.go
generated
vendored
84
vendor/github.com/moby/moby/client/request.go
generated
vendored
@@ -67,31 +67,22 @@ func (cli *Client) delete(ctx context.Context, path string, query url.Values, he
|
||||
// prepareJSONRequest encodes the given body to JSON and returns it as an [io.Reader], and sets the Content-Type
|
||||
// header. If body is nil, or a nil-interface, a "nil" body is returned without
|
||||
// error.
|
||||
//
|
||||
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
|
||||
// TODO(thaJeztah): is "nil" the appropriate approach for an empty body, or should we use [http.NoBody] (or similar)?
|
||||
func prepareJSONRequest(body any, headers http.Header) (io.Reader, http.Header, error) {
|
||||
if body == nil {
|
||||
return nil, headers, nil
|
||||
}
|
||||
// encoding/json encodes a nil pointer as the JSON document `null`,
|
||||
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
|
||||
// That is almost certainly not what the caller intended as the request body.
|
||||
//
|
||||
// TODO(thaJeztah): consider moving this to jsonEncode, which would also allow returning an (empty) reader instead of nil.
|
||||
if reflect.TypeOf(body).Kind() == reflect.Ptr && reflect.ValueOf(body).IsNil() {
|
||||
return nil, headers, nil
|
||||
}
|
||||
|
||||
jsonBody, err := jsonEncode(body)
|
||||
if err != nil {
|
||||
return nil, headers, err
|
||||
}
|
||||
if jsonBody == nil || jsonBody == http.NoBody {
|
||||
// no content-type is set on empty requests.
|
||||
return jsonBody, headers, nil
|
||||
}
|
||||
|
||||
hdr := http.Header{}
|
||||
if headers != nil {
|
||||
hdr = headers.Clone()
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
|
||||
hdr.Set("Content-Type", "application/json")
|
||||
return jsonBody, hdr, nil
|
||||
}
|
||||
@@ -110,9 +101,6 @@ func (cli *Client) buildRequest(ctx context.Context, method, path string, body i
|
||||
req.Host = DummyHost
|
||||
}
|
||||
|
||||
if body != nil && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -248,7 +236,11 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
|
||||
if statusMsg == "" {
|
||||
statusMsg = http.StatusText(serverResp.StatusCode)
|
||||
}
|
||||
if serverResp.Body != nil {
|
||||
var reqMethod string
|
||||
if serverResp.Request != nil {
|
||||
reqMethod = serverResp.Request.Method
|
||||
}
|
||||
if serverResp.Body != nil && reqMethod != http.MethodHead {
|
||||
bodyMax := 1 * 1024 * 1024 // 1 MiB
|
||||
bodyR := &io.LimitedReader{
|
||||
R: serverResp.Body,
|
||||
@@ -333,25 +325,49 @@ func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Requ
|
||||
}
|
||||
|
||||
func jsonEncode(data any) (io.Reader, error) {
|
||||
var params bytes.Buffer
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(¶ms).Encode(data); err != nil {
|
||||
return nil, err
|
||||
switch x := data.(type) {
|
||||
case nil:
|
||||
return http.NoBody, nil
|
||||
case io.Reader:
|
||||
// http.NoBody or other readers
|
||||
return x, nil
|
||||
case json.RawMessage:
|
||||
if len(x) == 0 {
|
||||
return http.NoBody, nil
|
||||
}
|
||||
return bytes.NewReader(x), nil
|
||||
}
|
||||
return ¶ms, nil
|
||||
|
||||
// encoding/json encodes a nil pointer as the JSON document `null`,
|
||||
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
|
||||
// That is almost certainly not what the caller intended as the request body.
|
||||
if v := reflect.ValueOf(data); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return http.NoBody, nil
|
||||
}
|
||||
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(b), nil
|
||||
}
|
||||
|
||||
func ensureReaderClosed(response *http.Response) {
|
||||
if response != nil && response.Body != nil {
|
||||
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
||||
// see https://github.com/google/go-github/pull/317/files#r57536827
|
||||
//
|
||||
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
|
||||
// and check if context-cancellation should handle this as well. If still needed, consider
|
||||
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
|
||||
// methods.
|
||||
_, _ = io.CopyN(io.Discard, response.Body, 512)
|
||||
_ = response.Body.Close()
|
||||
if response == nil || response.Body == nil {
|
||||
return
|
||||
}
|
||||
if response.ContentLength == 0 || (response.Request != nil && response.Request.Method == http.MethodHead) {
|
||||
// No need to drain head requests or zero-length responses.
|
||||
_ = response.Body.Close()
|
||||
return
|
||||
}
|
||||
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
||||
// see https://github.com/google/go-github/pull/317/files#r57536827
|
||||
//
|
||||
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
|
||||
// and check if context-cancellation should handle this as well. If still needed, consider
|
||||
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
|
||||
// methods.
|
||||
_, _ = io.CopyN(io.Discard, response.Body, 512)
|
||||
_ = response.Body.Close()
|
||||
}
|
||||
|
||||
56
vendor/github.com/moby/moby/client/service_create.go
generated
vendored
56
vendor/github.com/moby/moby/client/service_create.go
generated
vendored
@@ -59,16 +59,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
|
||||
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
||||
}
|
||||
if options.QueryRegistry {
|
||||
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
||||
warnings = append(warnings, resolveWarning)
|
||||
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
}
|
||||
case options.Spec.TaskTemplate.PluginSpec != nil:
|
||||
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
||||
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
||||
}
|
||||
if options.QueryRegistry {
|
||||
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
||||
warnings = append(warnings, resolveWarning)
|
||||
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,35 +95,33 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
|
||||
}
|
||||
|
||||
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
||||
var warning string
|
||||
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
|
||||
warning = digestWarning(taskSpec.ContainerSpec.Image)
|
||||
} else {
|
||||
taskSpec.ContainerSpec.Image = img
|
||||
if len(imgPlatforms) > 0 {
|
||||
if taskSpec.Placement == nil {
|
||||
taskSpec.Placement = &swarm.Placement{}
|
||||
}
|
||||
taskSpec.Placement.Platforms = imgPlatforms
|
||||
}
|
||||
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth)
|
||||
if err != nil {
|
||||
return digestWarning(taskSpec.ContainerSpec.Image)
|
||||
}
|
||||
return warning
|
||||
taskSpec.ContainerSpec.Image = img
|
||||
if len(imgPlatforms) > 0 {
|
||||
if taskSpec.Placement == nil {
|
||||
taskSpec.Placement = &swarm.Placement{}
|
||||
}
|
||||
taskSpec.Placement.Platforms = imgPlatforms
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
||||
var warning string
|
||||
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
|
||||
warning = digestWarning(taskSpec.PluginSpec.Remote)
|
||||
} else {
|
||||
taskSpec.PluginSpec.Remote = img
|
||||
if len(imgPlatforms) > 0 {
|
||||
if taskSpec.Placement == nil {
|
||||
taskSpec.Placement = &swarm.Placement{}
|
||||
}
|
||||
taskSpec.Placement.Platforms = imgPlatforms
|
||||
}
|
||||
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth)
|
||||
if err != nil {
|
||||
return digestWarning(taskSpec.PluginSpec.Remote)
|
||||
}
|
||||
return warning
|
||||
taskSpec.PluginSpec.Remote = img
|
||||
if len(imgPlatforms) > 0 {
|
||||
if taskSpec.Placement == nil {
|
||||
taskSpec.Placement = &swarm.Placement{}
|
||||
}
|
||||
taskSpec.Placement.Platforms = imgPlatforms
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
||||
|
||||
10
vendor/github.com/moby/moby/client/service_update.go
generated
vendored
10
vendor/github.com/moby/moby/client/service_update.go
generated
vendored
@@ -82,16 +82,18 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options
|
||||
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
||||
}
|
||||
if options.QueryRegistry {
|
||||
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
||||
warnings = append(warnings, resolveWarning)
|
||||
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
}
|
||||
case options.Spec.TaskTemplate.PluginSpec != nil:
|
||||
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
||||
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
||||
}
|
||||
if options.QueryRegistry {
|
||||
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
||||
warnings = append(warnings, resolveWarning)
|
||||
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
vendor/github.com/moby/moby/client/utils.go
generated
vendored
42
vendor/github.com/moby/moby/client/utils.go
generated
vendored
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -32,6 +33,47 @@ func trimID(objType, id string) (string, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// parseAPIVersion checks v to be a well-formed ("<major>.<minor>")
|
||||
// API version. It returns an error if the value is empty or does not
|
||||
// have the correct format, but does not validate if the API version is
|
||||
// within the supported range ([MinAPIVersion] <= v <= [MaxAPIVersion]).
|
||||
//
|
||||
// It returns version after normalizing, or an error if validation failed.
|
||||
func parseAPIVersion(version string) (string, error) {
|
||||
if strings.TrimPrefix(strings.TrimSpace(version), "v") == "" {
|
||||
return "", cerrdefs.ErrInvalidArgument.WithMessage("value is empty")
|
||||
}
|
||||
major, minor, err := parseMajorMinor(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%d.%d", major, minor), nil
|
||||
}
|
||||
|
||||
// parseMajorMinor is a helper for parseAPIVersion.
|
||||
func parseMajorMinor(v string) (major, minor int, _ error) {
|
||||
if strings.HasPrefix(v, "v") {
|
||||
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("must be formatted <major>.<minor>")
|
||||
}
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("value is empty")
|
||||
}
|
||||
|
||||
majVer, minVer, ok := strings.Cut(v, ".")
|
||||
if !ok {
|
||||
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("must be formatted <major>.<minor>")
|
||||
}
|
||||
major, err := strconv.Atoi(majVer)
|
||||
if err != nil {
|
||||
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("invalid major version: must be formatted <major>.<minor>")
|
||||
}
|
||||
minor, err = strconv.Atoi(minVer)
|
||||
if err != nil {
|
||||
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("invalid minor version: must be formatted <major>.<minor>")
|
||||
}
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
// encodePlatforms marshals the given platform(s) to JSON format, to
|
||||
// be used for query-parameters for filtering / selecting platforms.
|
||||
func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
|
||||
|
||||
3
vendor/github.com/opencontainers/runc/internal/linux/doc.go
generated
vendored
Normal file
3
vendor/github.com/opencontainers/runc/internal/linux/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package linux provides minimal wrappers around Linux system calls, primarily
|
||||
// to provide support for automatic EINTR-retries.
|
||||
package linux
|
||||
28
vendor/github.com/opencontainers/runc/internal/linux/eintr.go
generated
vendored
Normal file
28
vendor/github.com/opencontainers/runc/internal/linux/eintr.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// retryOnEINTR takes a function that returns an error and calls it
|
||||
// until the error returned is not EINTR.
|
||||
func retryOnEINTR(fn func() error) error {
|
||||
for {
|
||||
err := fn()
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// retryOnEINTR2 is like retryOnEINTR, but it returns 2 values.
|
||||
func retryOnEINTR2[T any](fn func() (T, error)) (T, error) {
|
||||
for {
|
||||
val, err := fn()
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
return val, err
|
||||
}
|
||||
}
|
||||
}
|
||||
126
vendor/github.com/opencontainers/runc/internal/linux/linux.go
generated
vendored
Normal file
126
vendor/github.com/opencontainers/runc/internal/linux/linux.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Dup3 wraps [unix.Dup3].
|
||||
func Dup3(oldfd, newfd, flags int) error {
|
||||
err := retryOnEINTR(func() error {
|
||||
return unix.Dup3(oldfd, newfd, flags)
|
||||
})
|
||||
return os.NewSyscallError("dup3", err)
|
||||
}
|
||||
|
||||
// Exec wraps [unix.Exec].
|
||||
func Exec(cmd string, args []string, env []string) error {
|
||||
err := retryOnEINTR(func() error {
|
||||
return unix.Exec(cmd, args, env)
|
||||
})
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "exec", Path: cmd, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Getwd wraps [unix.Getwd].
|
||||
func Getwd() (wd string, err error) {
|
||||
wd, err = retryOnEINTR2(unix.Getwd)
|
||||
return wd, os.NewSyscallError("getwd", err)
|
||||
}
|
||||
|
||||
// Open wraps [unix.Open].
|
||||
func Open(path string, mode int, perm uint32) (fd int, err error) {
|
||||
fd, err = retryOnEINTR2(func() (int, error) {
|
||||
return unix.Open(path, mode, perm)
|
||||
})
|
||||
if err != nil {
|
||||
return -1, &os.PathError{Op: "open", Path: path, Err: err}
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// Openat wraps [unix.Openat].
|
||||
func Openat(dirfd int, path string, mode int, perm uint32) (fd int, err error) {
|
||||
fd, err = retryOnEINTR2(func() (int, error) {
|
||||
return unix.Openat(dirfd, path, mode, perm)
|
||||
})
|
||||
if err != nil {
|
||||
return -1, &os.PathError{Op: "openat", Path: path, Err: err}
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// Recvfrom wraps [unix.Recvfrom].
|
||||
func Recvfrom(fd int, p []byte, flags int) (n int, from unix.Sockaddr, err error) {
|
||||
err = retryOnEINTR(func() error {
|
||||
n, from, err = unix.Recvfrom(fd, p, flags)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return 0, nil, os.NewSyscallError("recvfrom", err)
|
||||
}
|
||||
return n, from, err
|
||||
}
|
||||
|
||||
// Sendmsg wraps [unix.Sendmsg].
|
||||
func Sendmsg(fd int, p, oob []byte, to unix.Sockaddr, flags int) error {
|
||||
err := retryOnEINTR(func() error {
|
||||
return unix.Sendmsg(fd, p, oob, to, flags)
|
||||
})
|
||||
return os.NewSyscallError("sendmsg", err)
|
||||
}
|
||||
|
||||
// SetMempolicy wraps set_mempolicy.
|
||||
func SetMempolicy(mode uint, mask *unix.CPUSet) error {
|
||||
err := retryOnEINTR(func() error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), unsafe.Sizeof(*mask)*8)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return os.NewSyscallError("set_mempolicy", err)
|
||||
}
|
||||
|
||||
// Readlinkat wraps [unix.Readlinkat].
|
||||
func Readlinkat(dir *os.File, path string) (string, error) {
|
||||
size := 4096
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n, err := retryOnEINTR2(func() (int, error) {
|
||||
return unix.Readlinkat(int(dir.Fd()), path, linkBuf)
|
||||
})
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
if n != size {
|
||||
return string(linkBuf[:n]), nil
|
||||
}
|
||||
// Possible truncation, resize the buffer.
|
||||
size *= 2
|
||||
}
|
||||
}
|
||||
|
||||
// GetPtyPeer is a wrapper for ioctl(TIOCGPTPEER).
|
||||
func GetPtyPeer(ptyFd uintptr, unsafePeerPath string, flags int) (*os.File, error) {
|
||||
// Make sure O_NOCTTY is always set -- otherwise runc might accidentally
|
||||
// gain it as a controlling terminal. O_CLOEXEC also needs to be set to
|
||||
// make sure we don't leak the handle either.
|
||||
flags |= unix.O_NOCTTY | unix.O_CLOEXEC
|
||||
|
||||
// There is no nice wrapper for this kind of ioctl in unix.
|
||||
peerFd, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
ptyFd,
|
||||
uintptr(unix.TIOCGPTPEER),
|
||||
uintptr(flags),
|
||||
)
|
||||
if errno != 0 {
|
||||
return nil, os.NewSyscallError("ioctl TIOCGPTPEER", errno)
|
||||
}
|
||||
return os.NewFile(peerFd, unsafePeerPath), nil
|
||||
}
|
||||
51
vendor/github.com/opencontainers/runc/internal/pathrs/mkdirall.go
generated
vendored
Normal file
51
vendor/github.com/opencontainers/runc/internal/pathrs/mkdirall.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2024-2025 SUSE LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// MkdirAllParentInRoot is like [MkdirAllInRoot] except that it only creates
|
||||
// the parent directory of the target path, returning the trailing component so
|
||||
// the caller has more flexibility around constructing the final inode.
|
||||
//
|
||||
// Callers need to be very careful operating on the trailing path, as trivial
|
||||
// mistakes like following symlinks can cause security bugs. Most people
|
||||
// should probably just use [MkdirAllInRoot] or [CreateInRoot].
|
||||
func MkdirAllParentInRoot(root, unsafePath string, mode os.FileMode) (*os.File, string, error) {
|
||||
// MkdirAllInRoot also does hallucinateUnsafePath, but we need to do it
|
||||
// here first because when we split unsafePath into (dir, file) components
|
||||
// we want to be doing so with the hallucinated path (so that trailing
|
||||
// dangling symlinks are treated correctly).
|
||||
unsafePath, err := hallucinateUnsafePath(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to construct hallucinated target path: %w", err)
|
||||
}
|
||||
|
||||
dirPath, filename := filepath.Split(unsafePath)
|
||||
if filepath.Join("/", filename) == "/" {
|
||||
return nil, "", fmt.Errorf("create parent dir in root subpath %q has bad trailing component %q", unsafePath, filename)
|
||||
}
|
||||
|
||||
dirFd, err := MkdirAllInRoot(root, dirPath, mode)
|
||||
return dirFd, filename, err
|
||||
}
|
||||
30
vendor/github.com/opencontainers/runc/internal/pathrs/mkdirall_pathrslite.go
generated
vendored
30
vendor/github.com/opencontainers/runc/internal/pathrs/mkdirall_pathrslite.go
generated
vendored
@@ -21,14 +21,13 @@ package pathrs
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MkdirAllInRootOpen attempts to make
|
||||
// MkdirAllInRoot attempts to make
|
||||
//
|
||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
||||
// os.MkdirAll(path, mode)
|
||||
@@ -49,19 +48,10 @@ import (
|
||||
// handling if unsafePath has already been scoped within the rootfs (this is
|
||||
// needed for a lot of runc callers and fixing this would require reworking a
|
||||
// lot of path logic).
|
||||
func MkdirAllInRootOpen(root, unsafePath string, mode os.FileMode) (*os.File, error) {
|
||||
// If the path is already "within" the root, get the path relative to the
|
||||
// root and use that as the unsafe path. This is necessary because a lot of
|
||||
// MkdirAllInRootOpen callers have already done SecureJoin, and refactoring
|
||||
// all of them to stop using these SecureJoin'd paths would require a fair
|
||||
// amount of work.
|
||||
// TODO(cyphar): Do the refactor to libpathrs once it's ready.
|
||||
if IsLexicallyInRoot(root, unsafePath) {
|
||||
subPath, err := filepath.Rel(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unsafePath = subPath
|
||||
func MkdirAllInRoot(root, unsafePath string, mode os.FileMode) (*os.File, error) {
|
||||
unsafePath, err := hallucinateUnsafePath(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct hallucinated target path: %w", err)
|
||||
}
|
||||
|
||||
// Check for any silly mode bits.
|
||||
@@ -87,13 +77,3 @@ func MkdirAllInRootOpen(root, unsafePath string, mode os.FileMode) (*os.File, er
|
||||
return pathrs.MkdirAllHandle(rootDir, unsafePath, mode)
|
||||
})
|
||||
}
|
||||
|
||||
// MkdirAllInRoot is a wrapper around MkdirAllInRootOpen which closes the
|
||||
// returned handle, for callers that don't need to use it.
|
||||
func MkdirAllInRoot(root, unsafePath string, mode os.FileMode) error {
|
||||
f, err := MkdirAllInRootOpen(root, unsafePath, mode)
|
||||
if err == nil {
|
||||
_ = f.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
82
vendor/github.com/opencontainers/runc/internal/pathrs/path.go
generated
vendored
82
vendor/github.com/opencontainers/runc/internal/pathrs/path.go
generated
vendored
@@ -19,7 +19,11 @@
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
)
|
||||
|
||||
// IsLexicallyInRoot is shorthand for strings.HasPrefix(path+"/", root+"/"),
|
||||
@@ -32,3 +36,81 @@ func IsLexicallyInRoot(root, path string) bool {
|
||||
path = strings.TrimRight(path, "/")
|
||||
return strings.HasPrefix(path+"/", root+"/")
|
||||
}
|
||||
|
||||
// LexicallyCleanPath makes a path safe for use with filepath.Join. This is
|
||||
// done by not only cleaning the path, but also (if the path is relative)
|
||||
// adding a leading '/' and cleaning it (then removing the leading '/'). This
|
||||
// ensures that a path resulting from prepending another path will always
|
||||
// resolve to lexically be a subdirectory of the prefixed path. This is all
|
||||
// done lexically, so paths that include symlinks won't be safe as a result of
|
||||
// using CleanPath.
|
||||
func LexicallyCleanPath(path string) string {
|
||||
// Deal with empty strings nicely.
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Ensure that all paths are cleaned (especially problematic ones like
|
||||
// "/../../../../../" which can cause lots of issues).
|
||||
|
||||
if filepath.IsAbs(path) {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
// If the path isn't absolute, we need to do more processing to fix paths
|
||||
// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
|
||||
// paths to relative ones.
|
||||
path = filepath.Clean(string(os.PathSeparator) + path)
|
||||
// This can't fail, as (by definition) all paths are relative to root.
|
||||
path, _ = filepath.Rel(string(os.PathSeparator), path)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// LexicallyStripRoot returns the passed path, stripping the root path if it
|
||||
// was (lexicially) inside it. Note that both passed paths will always be
|
||||
// treated as absolute, and the returned path will also always be absolute. In
|
||||
// addition, the paths are cleaned before stripping the root.
|
||||
func LexicallyStripRoot(root, path string) string {
|
||||
// Make the paths clean and absolute.
|
||||
root, path = LexicallyCleanPath("/"+root), LexicallyCleanPath("/"+path)
|
||||
switch {
|
||||
case path == root:
|
||||
path = "/"
|
||||
case root == "/":
|
||||
// do nothing
|
||||
default:
|
||||
path = strings.TrimPrefix(path, root+"/")
|
||||
}
|
||||
return LexicallyCleanPath("/" + path)
|
||||
}
|
||||
|
||||
// hallucinateUnsafePath creates a new unsafePath which has all symlinks
|
||||
// (including dangling symlinks) fully resolved and any non-existent components
|
||||
// treated as though they are real. This is effectively just a wrapper around
|
||||
// [securejoin.SecureJoin] that strips the root. This path *IS NOT* safe to use
|
||||
// as-is, you *MUST* operate on the returned path with pathrs-lite.
|
||||
//
|
||||
// The reason for this methods is that in previous runc versions, we would
|
||||
// tolerate nonsense paths with dangling symlinks as path components.
|
||||
// pathrs-lite does not support this, so instead we have to emulate this
|
||||
// behaviour by doing SecureJoin *purely to get a semi-reasonable path to use*
|
||||
// and then we use pathrs-lite to operate on the path safely.
|
||||
//
|
||||
// It would be quite difficult to emulate this in a race-free way in
|
||||
// pathrs-lite, so instead we use [securejoin.SecureJoin] to simply produce a
|
||||
// new candidate path for operations like [MkdirAllInRoot] so they can then
|
||||
// operate on the new unsafePath as if it was what the user requested.
|
||||
//
|
||||
// If unsafePath is already lexically inside root, it is stripped before
|
||||
// re-resolving it (this is done to ensure compatibility with legacy callers
|
||||
// within runc that call SecureJoin before calling into pathrs).
|
||||
func hallucinateUnsafePath(root, unsafePath string) (string, error) {
|
||||
unsafePath = LexicallyStripRoot(root, unsafePath)
|
||||
weirdPath, err := securejoin.SecureJoin(root, unsafePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unsafePath = LexicallyStripRoot(root, weirdPath)
|
||||
return unsafePath, nil
|
||||
}
|
||||
|
||||
13
vendor/github.com/opencontainers/runc/internal/pathrs/root_pathrslite.go
generated
vendored
13
vendor/github.com/opencontainers/runc/internal/pathrs/root_pathrslite.go
generated
vendored
@@ -19,12 +19,12 @@
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/runc/internal/linux"
|
||||
)
|
||||
|
||||
// OpenInRoot opens the given path inside the root with the provided flags. It
|
||||
@@ -48,12 +48,7 @@ func OpenInRoot(root, subpath string, flags int) (*os.File, error) {
|
||||
// include it in the passed flags. The fileMode argument uses unix.* mode bits,
|
||||
// *not* os.FileMode.
|
||||
func CreateInRoot(root, subpath string, flags int, fileMode uint32) (*os.File, error) {
|
||||
dir, filename := filepath.Split(subpath)
|
||||
if filepath.Join("/", filename) == "/" {
|
||||
return nil, fmt.Errorf("create in root subpath %q has bad trailing component %q", subpath, filename)
|
||||
}
|
||||
|
||||
dirFd, err := MkdirAllInRootOpen(root, dir, 0o755)
|
||||
dirFd, filename, err := MkdirAllParentInRoot(root, subpath, 0o755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,7 +59,7 @@ func CreateInRoot(root, subpath string, flags int, fileMode uint32) (*os.File, e
|
||||
// trailing symlinks, so this is safe to do. libpathrs's Root::create_file
|
||||
// works the same way.
|
||||
flags |= unix.O_CREAT | unix.O_NOFOLLOW
|
||||
fd, err := unix.Openat(int(dirFd.Fd()), filename, flags, fileMode)
|
||||
fd, err := linux.Openat(int(dirFd.Fd()), filename, flags, fileMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
22
vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go
generated
vendored
22
vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go
generated
vendored
@@ -2,15 +2,17 @@ package apparmor
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// IsEnabled returns true if apparmor is enabled for the host.
|
||||
IsEnabled = isEnabled
|
||||
// IsEnabled returns true if apparmor is enabled for the host.
|
||||
func IsEnabled() bool {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
// ApplyProfile will apply the profile with the specified name to the process after
|
||||
// the next exec. It is only supported on Linux and produces an ErrApparmorNotEnabled
|
||||
// on other platforms.
|
||||
ApplyProfile = applyProfile
|
||||
// ApplyProfile will apply the profile with the specified name to the process
|
||||
// after the next exec. It is only supported on Linux and produces an
|
||||
// [ErrApparmorNotEnabled] on other platforms.
|
||||
func ApplyProfile(name string) error {
|
||||
return applyProfile(name)
|
||||
}
|
||||
|
||||
// ErrApparmorNotEnabled indicates that AppArmor is not enabled or not supported.
|
||||
ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported")
|
||||
)
|
||||
// ErrApparmorNotEnabled indicates that AppArmor is not enabled or not supported.
|
||||
var ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported")
|
||||
|
||||
10
vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go
generated
vendored
10
vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/runc/internal/pathrs"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,7 +28,7 @@ func isEnabled() bool {
|
||||
}
|
||||
|
||||
func setProcAttr(attr, value string) error {
|
||||
attr = utils.CleanPath(attr)
|
||||
attr = pathrs.LexicallyCleanPath(attr)
|
||||
attrSubPath := "attr/apparmor/" + attr
|
||||
if _, err := os.Stat("/proc/self/" + attrSubPath); errors.Is(err, os.ErrNotExist) {
|
||||
// fall back to the old convention
|
||||
@@ -50,7 +49,7 @@ func setProcAttr(attr, value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// changeOnExec reimplements aa_change_onexec from libapparmor in Go
|
||||
// changeOnExec reimplements aa_change_onexec from libapparmor in Go.
|
||||
func changeOnExec(name string) error {
|
||||
if err := setProcAttr("exec", "exec "+name); err != nil {
|
||||
return fmt.Errorf("apparmor failed to apply profile: %w", err)
|
||||
@@ -58,9 +57,8 @@ func changeOnExec(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyProfile will apply the profile with the specified name to the process after
|
||||
// the next exec. It is only supported on Linux and produces an error on other
|
||||
// platforms.
|
||||
// applyProfile will apply the profile with the specified name to the process
|
||||
// after the next exec.
|
||||
func applyProfile(name string) error {
|
||||
if name == "" {
|
||||
return nil
|
||||
|
||||
135
vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go
generated
vendored
135
vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go
generated
vendored
@@ -1,135 +0,0 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
* Copyright 2016, 2017 SUSE LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MaxNameLen is the maximum length of the name of a file descriptor being sent
|
||||
// using SendFile. The name of the file handle returned by RecvFile will never be
|
||||
// larger than this value.
|
||||
const MaxNameLen = 4096
|
||||
|
||||
// oobSpace is the size of the oob slice required to store a single FD. Note
|
||||
// that unix.UnixRights appears to make the assumption that fd is always int32,
|
||||
// so sizeof(fd) = 4.
|
||||
var oobSpace = unix.CmsgSpace(4)
|
||||
|
||||
// RecvFile waits for a file descriptor to be sent over the given AF_UNIX
|
||||
// socket. The file name of the remote file descriptor will be recreated
|
||||
// locally (it is sent as non-auxiliary data in the same payload).
|
||||
func RecvFile(socket *os.File) (_ *os.File, Err error) {
|
||||
name := make([]byte, MaxNameLen)
|
||||
oob := make([]byte, oobSpace)
|
||||
|
||||
sockfd := socket.Fd()
|
||||
var (
|
||||
n, oobn int
|
||||
err error
|
||||
)
|
||||
|
||||
for {
|
||||
n, oobn, _, _, err = unix.Recvmsg(int(sockfd), name, oob, unix.MSG_CMSG_CLOEXEC)
|
||||
if err != unix.EINTR { //nolint:errorlint // unix errors are bare
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("recvmsg", err)
|
||||
}
|
||||
if n >= MaxNameLen || oobn != oobSpace {
|
||||
return nil, fmt.Errorf("recvfile: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
|
||||
}
|
||||
// Truncate.
|
||||
name = name[:n]
|
||||
oob = oob[:oobn]
|
||||
|
||||
scms, err := unix.ParseSocketControlMessage(oob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We cannot control how many SCM_RIGHTS we receive, and upon receiving
|
||||
// them all of the descriptors are installed in our fd table, so we need to
|
||||
// parse all of the SCM_RIGHTS we received in order to close all of the
|
||||
// descriptors on error.
|
||||
var fds []int
|
||||
defer func() {
|
||||
for i, fd := range fds {
|
||||
if i == 0 && Err == nil {
|
||||
// Only close the first one on error.
|
||||
continue
|
||||
}
|
||||
// Always close extra ones.
|
||||
_ = unix.Close(fd)
|
||||
}
|
||||
}()
|
||||
var lastErr error
|
||||
for _, scm := range scms {
|
||||
if scm.Header.Type == unix.SCM_RIGHTS {
|
||||
scmFds, err := unix.ParseUnixRights(&scm)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
} else {
|
||||
fds = append(fds, scmFds...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
// We do this after collecting the fds to make sure we close them all when
|
||||
// returning an error here.
|
||||
if len(scms) != 1 {
|
||||
return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
|
||||
}
|
||||
if len(fds) != 1 {
|
||||
return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
|
||||
}
|
||||
return os.NewFile(uintptr(fds[0]), string(name)), nil
|
||||
}
|
||||
|
||||
// SendFile sends a file over the given AF_UNIX socket. file.Name() is also
|
||||
// included so that if the other end uses RecvFile, the file will have the same
|
||||
// name information.
|
||||
func SendFile(socket *os.File, file *os.File) error {
|
||||
name := file.Name()
|
||||
if len(name) >= MaxNameLen {
|
||||
return fmt.Errorf("sendfd: filename too long: %s", name)
|
||||
}
|
||||
err := SendRawFd(socket, name, file.Fd())
|
||||
runtime.KeepAlive(file)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendRawFd sends a specific file descriptor over the given AF_UNIX socket.
|
||||
func SendRawFd(socket *os.File, msg string, fd uintptr) error {
|
||||
oob := unix.UnixRights(int(fd))
|
||||
for {
|
||||
err := unix.Sendmsg(int(socket.Fd()), []byte(msg), oob, nil, 0)
|
||||
if err != unix.EINTR { //nolint:errorlint // unix errors are bare
|
||||
return os.NewSyscallError("sendmsg", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
115
vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
generated
vendored
115
vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
generated
vendored
@@ -1,115 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
exitSignalOffset = 128
|
||||
)
|
||||
|
||||
// ExitStatus returns the correct exit status for a process based on if it
|
||||
// was signaled or exited cleanly
|
||||
func ExitStatus(status unix.WaitStatus) int {
|
||||
if status.Signaled() {
|
||||
return exitSignalOffset + int(status.Signal())
|
||||
}
|
||||
return status.ExitStatus()
|
||||
}
|
||||
|
||||
// WriteJSON writes the provided struct v to w using standard json marshaling
|
||||
// without a trailing newline. This is used instead of json.Encoder because
|
||||
// there might be a problem in json decoder in some cases, see:
|
||||
// https://github.com/docker/docker/issues/14203#issuecomment-174177790
|
||||
func WriteJSON(w io.Writer, v interface{}) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanPath makes a path safe for use with filepath.Join. This is done by not
|
||||
// only cleaning the path, but also (if the path is relative) adding a leading
|
||||
// '/' and cleaning it (then removing the leading '/'). This ensures that a
|
||||
// path resulting from prepending another path will always resolve to lexically
|
||||
// be a subdirectory of the prefixed path. This is all done lexically, so paths
|
||||
// that include symlinks won't be safe as a result of using CleanPath.
|
||||
func CleanPath(path string) string {
|
||||
// Deal with empty strings nicely.
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Ensure that all paths are cleaned (especially problematic ones like
|
||||
// "/../../../../../" which can cause lots of issues).
|
||||
|
||||
if filepath.IsAbs(path) {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
// If the path isn't absolute, we need to do more processing to fix paths
|
||||
// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
|
||||
// paths to relative ones.
|
||||
path = filepath.Clean(string(os.PathSeparator) + path)
|
||||
// This can't fail, as (by definition) all paths are relative to root.
|
||||
path, _ = filepath.Rel(string(os.PathSeparator), path)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// StripRoot returns the passed path, stripping the root path if it was
|
||||
// (lexicially) inside it. Note that both passed paths will always be treated
|
||||
// as absolute, and the returned path will also always be absolute. In
|
||||
// addition, the paths are cleaned before stripping the root.
|
||||
func StripRoot(root, path string) string {
|
||||
// Make the paths clean and absolute.
|
||||
root, path = CleanPath("/"+root), CleanPath("/"+path)
|
||||
switch {
|
||||
case path == root:
|
||||
path = "/"
|
||||
case root == "/":
|
||||
// do nothing
|
||||
default:
|
||||
path = strings.TrimPrefix(path, root+"/")
|
||||
}
|
||||
return CleanPath("/" + path)
|
||||
}
|
||||
|
||||
// SearchLabels searches through a list of key=value pairs for a given key,
|
||||
// returning its value, and the binary flag telling whether the key exist.
|
||||
func SearchLabels(labels []string, key string) (string, bool) {
|
||||
key += "="
|
||||
for _, s := range labels {
|
||||
if val, ok := strings.CutPrefix(s, key); ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Annotations returns the bundle path and user defined annotations from the
|
||||
// libcontainer state. We need to remove the bundle because that is a label
|
||||
// added by libcontainer.
|
||||
func Annotations(labels []string) (bundle string, userAnnotations map[string]string) {
|
||||
userAnnotations = make(map[string]string)
|
||||
for _, l := range labels {
|
||||
name, value, ok := strings.Cut(l, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if name == "bundle" {
|
||||
bundle = value
|
||||
} else {
|
||||
userAnnotations[name] = value
|
||||
}
|
||||
}
|
||||
return bundle, userAnnotations
|
||||
}
|
||||
277
vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
generated
vendored
277
vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go
generated
vendored
@@ -1,277 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/opencontainers/runc/internal/pathrs"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
haveCloseRangeCloexecBool bool
|
||||
haveCloseRangeCloexecOnce sync.Once
|
||||
)
|
||||
|
||||
func haveCloseRangeCloexec() bool {
|
||||
haveCloseRangeCloexecOnce.Do(func() {
|
||||
// Make sure we're not closing a random file descriptor.
|
||||
tmpFd, err := unix.FcntlInt(0, unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer unix.Close(tmpFd)
|
||||
|
||||
err = unix.CloseRange(uint(tmpFd), uint(tmpFd), unix.CLOSE_RANGE_CLOEXEC)
|
||||
// Any error means we cannot use close_range(CLOSE_RANGE_CLOEXEC).
|
||||
// -ENOSYS and -EINVAL ultimately mean we don't have support, but any
|
||||
// other potential error would imply that even the most basic close
|
||||
// operation wouldn't work.
|
||||
haveCloseRangeCloexecBool = err == nil
|
||||
})
|
||||
return haveCloseRangeCloexecBool
|
||||
}
|
||||
|
||||
type fdFunc func(fd int)
|
||||
|
||||
// fdRangeFrom calls the passed fdFunc for each file descriptor that is open in
|
||||
// the current process.
|
||||
func fdRangeFrom(minFd int, fn fdFunc) error {
|
||||
fdDir, closer, err := pathrs.ProcThreadSelfOpen("fd/", unix.O_DIRECTORY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get handle to /proc/thread-self/fd: %w", err)
|
||||
}
|
||||
defer closer()
|
||||
defer fdDir.Close()
|
||||
|
||||
fdList, err := fdDir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fdStr := range fdList {
|
||||
fd, err := strconv.Atoi(fdStr)
|
||||
// Ignore non-numeric file names.
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Ignore descriptors lower than our specified minimum.
|
||||
if fd < minFd {
|
||||
continue
|
||||
}
|
||||
// Ignore the file descriptor we used for readdir, as it will be closed
|
||||
// when we return.
|
||||
if uintptr(fd) == fdDir.Fd() {
|
||||
continue
|
||||
}
|
||||
// Run the closure.
|
||||
fn(fd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseExecFrom sets the O_CLOEXEC flag on all file descriptors greater or
|
||||
// equal to minFd in the current process.
|
||||
func CloseExecFrom(minFd int) error {
|
||||
// Use close_range(CLOSE_RANGE_CLOEXEC) if possible.
|
||||
if haveCloseRangeCloexec() {
|
||||
err := unix.CloseRange(uint(minFd), math.MaxInt32, unix.CLOSE_RANGE_CLOEXEC)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.Debugf("close_range failed, closing range one at a time (error: %v)", err)
|
||||
|
||||
// If close_range fails, we fall back to the standard loop.
|
||||
}
|
||||
// Otherwise, fall back to the standard loop.
|
||||
return fdRangeFrom(minFd, unix.CloseOnExec)
|
||||
}
|
||||
|
||||
//go:linkname runtime_IsPollDescriptor internal/poll.IsPollDescriptor
|
||||
|
||||
// In order to make sure we do not close the internal epoll descriptors the Go
|
||||
// runtime uses, we need to ensure that we skip descriptors that match
|
||||
// "internal/poll".IsPollDescriptor. Yes, this is a Go runtime internal thing,
|
||||
// unfortunately there's no other way to be sure we're only keeping the file
|
||||
// descriptors the Go runtime needs. Hopefully nothing blows up doing this...
|
||||
func runtime_IsPollDescriptor(fd uintptr) bool //nolint:revive
|
||||
|
||||
// UnsafeCloseFrom closes all file descriptors greater or equal to minFd in the
|
||||
// current process, except for those critical to Go's runtime (such as the
|
||||
// netpoll management descriptors).
|
||||
//
|
||||
// NOTE: That this function is incredibly dangerous to use in most Go code, as
|
||||
// closing file descriptors from underneath *os.File handles can lead to very
|
||||
// bad behaviour (the closed file descriptor can be re-used and then any
|
||||
// *os.File operations would apply to the wrong file). This function is only
|
||||
// intended to be called from the last stage of runc init.
|
||||
func UnsafeCloseFrom(minFd int) error {
|
||||
// We cannot use close_range(2) even if it is available, because we must
|
||||
// not close some file descriptors.
|
||||
return fdRangeFrom(minFd, func(fd int) {
|
||||
if runtime_IsPollDescriptor(uintptr(fd)) {
|
||||
// These are the Go runtimes internal netpoll file descriptors.
|
||||
// These file descriptors are operated on deep in the Go scheduler,
|
||||
// and closing those files from underneath Go can result in panics.
|
||||
// There is no issue with keeping them because they are not
|
||||
// executable and are not useful to an attacker anyway. Also we
|
||||
// don't have any choice.
|
||||
return
|
||||
}
|
||||
// There's nothing we can do about errors from close(2), and the
|
||||
// only likely error to be seen is EBADF which indicates the fd was
|
||||
// already closed (in which case, we got what we wanted).
|
||||
_ = unix.Close(fd)
|
||||
})
|
||||
}
|
||||
|
||||
// NewSockPair returns a new SOCK_STREAM unix socket pair.
|
||||
func NewSockPair(name string) (parent, child *os.File, err error) {
|
||||
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
|
||||
}
|
||||
|
||||
// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
|
||||
// corresponding to the unsafePath resolved within the root. Before passing the
|
||||
// fd, this path is verified to have been inside the root -- so operating on it
|
||||
// through the passed fdpath should be safe. Do not access this path through
|
||||
// the original path strings, and do not attempt to use the pathname outside of
|
||||
// the passed closure (the file handle will be freed once the closure returns).
|
||||
func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
|
||||
// Remove the root then forcefully resolve inside the root.
|
||||
unsafePath = StripRoot(root, unsafePath)
|
||||
fullPath, err := securejoin.SecureJoin(root, unsafePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving path inside rootfs failed: %w", err)
|
||||
}
|
||||
|
||||
procSelfFd, closer := ProcThreadSelf("fd/")
|
||||
defer closer()
|
||||
|
||||
// Open the target path.
|
||||
fh, err := os.OpenFile(fullPath, unix.O_PATH|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open o_path procfd: %w", err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
procfd := filepath.Join(procSelfFd, strconv.Itoa(int(fh.Fd())))
|
||||
// Double-check the path is the one we expected.
|
||||
if realpath, err := os.Readlink(procfd); err != nil {
|
||||
return fmt.Errorf("procfd verification failed: %w", err)
|
||||
} else if realpath != fullPath {
|
||||
return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath)
|
||||
}
|
||||
|
||||
return fn(procfd)
|
||||
}
|
||||
|
||||
// WithProcfdFile is a very minimal wrapper around [ProcThreadSelfFd], intended
|
||||
// to make migrating from [WithProcfd] and [WithProcfdPath] usage easier. The
|
||||
// caller is responsible for making sure that the provided file handle is
|
||||
// actually safe to operate on.
|
||||
func WithProcfdFile(file *os.File, fn func(procfd string) error) error {
|
||||
fdpath, closer := ProcThreadSelfFd(file.Fd())
|
||||
defer closer()
|
||||
|
||||
return fn(fdpath)
|
||||
}
|
||||
|
||||
type ProcThreadSelfCloser func()
|
||||
|
||||
var (
|
||||
haveProcThreadSelf bool
|
||||
haveProcThreadSelfOnce sync.Once
|
||||
)
|
||||
|
||||
// ProcThreadSelf returns a string that is equivalent to
|
||||
// /proc/thread-self/<subpath>, with a graceful fallback on older kernels where
|
||||
// /proc/thread-self doesn't exist. This method DOES NOT use SecureJoin,
|
||||
// meaning that the passed string needs to be trusted. The caller _must_ call
|
||||
// the returned procThreadSelfCloser function (which is runtime.UnlockOSThread)
|
||||
// *only once* after it has finished using the returned path string.
|
||||
func ProcThreadSelf(subpath string) (string, ProcThreadSelfCloser) {
|
||||
haveProcThreadSelfOnce.Do(func() {
|
||||
if _, err := os.Stat("/proc/thread-self/"); err == nil {
|
||||
haveProcThreadSelf = true
|
||||
} else {
|
||||
logrus.Debugf("cannot stat /proc/thread-self (%v), falling back to /proc/self/task/<tid>", err)
|
||||
}
|
||||
})
|
||||
|
||||
// We need to lock our thread until the caller is done with the path string
|
||||
// because any non-atomic operation on the path (such as opening a file,
|
||||
// then reading it) could be interrupted by the Go runtime where the
|
||||
// underlying thread is swapped out and the original thread is killed,
|
||||
// resulting in pull-your-hair-out-hard-to-debug issues in the caller. In
|
||||
// addition, the pre-3.17 fallback makes everything non-atomic because the
|
||||
// same thing could happen between unix.Gettid() and the path operations.
|
||||
//
|
||||
// In theory, we don't need to lock in the atomic user case when using
|
||||
// /proc/thread-self/, but it's better to be safe than sorry (and there are
|
||||
// only one or two truly atomic users of /proc/thread-self/).
|
||||
runtime.LockOSThread()
|
||||
|
||||
threadSelf := "/proc/thread-self/"
|
||||
if !haveProcThreadSelf {
|
||||
// Pre-3.17 kernels did not have /proc/thread-self, so do it manually.
|
||||
threadSelf = "/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/"
|
||||
if _, err := os.Stat(threadSelf); err != nil {
|
||||
// Unfortunately, this code is called from rootfs_linux.go where we
|
||||
// are running inside the pid namespace of the container but /proc
|
||||
// is the host's procfs. Unfortunately there is no real way to get
|
||||
// the correct tid to use here (the kernel age means we cannot do
|
||||
// things like set up a private fsopen("proc") -- even scanning
|
||||
// NSpid in all of the tasks in /proc/self/task/*/status requires
|
||||
// Linux 4.1).
|
||||
//
|
||||
// So, we just have to assume that /proc/self is acceptable in this
|
||||
// one specific case.
|
||||
if os.Getpid() == 1 {
|
||||
logrus.Debugf("/proc/thread-self (tid=%d) cannot be emulated inside the initial container setup -- using /proc/self instead: %v", unix.Gettid(), err)
|
||||
} else {
|
||||
// This should never happen, but the fallback should work in most cases...
|
||||
logrus.Warnf("/proc/thread-self could not be emulated for pid=%d (tid=%d) -- using more buggy /proc/self fallback instead: %v", os.Getpid(), unix.Gettid(), err)
|
||||
}
|
||||
threadSelf = "/proc/self/"
|
||||
}
|
||||
}
|
||||
return threadSelf + subpath, runtime.UnlockOSThread
|
||||
}
|
||||
|
||||
// ProcThreadSelfFd is small wrapper around ProcThreadSelf to make it easier to
|
||||
// create a /proc/thread-self handle for given file descriptor.
|
||||
//
|
||||
// It is basically equivalent to ProcThreadSelf(fmt.Sprintf("fd/%d", fd)), but
|
||||
// without using fmt.Sprintf to avoid unneeded overhead.
|
||||
func ProcThreadSelfFd(fd uintptr) (string, ProcThreadSelfCloser) {
|
||||
return ProcThreadSelf("fd/" + strconv.FormatUint(uint64(fd), 10))
|
||||
}
|
||||
|
||||
// Openat is a Go-friendly openat(2) wrapper.
|
||||
func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) {
|
||||
dirFd := unix.AT_FDCWD
|
||||
if dir != nil {
|
||||
dirFd = int(dir.Fd())
|
||||
}
|
||||
flags |= unix.O_CLOEXEC
|
||||
|
||||
fd, err := unix.Openat(dirFd, path, flags, mode)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "openat", Path: path, Err: err}
|
||||
}
|
||||
return os.NewFile(uintptr(fd), dir.Name()+"/"+path), nil
|
||||
}
|
||||
31
vendor/go.podman.io/image/v5/directory/directory_dest.go
generated
vendored
31
vendor/go.podman.io/image/v5/directory/directory_dest.go
generated
vendored
@@ -20,8 +20,6 @@ import (
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
)
|
||||
|
||||
const version = "Directory Transport Version: 1.1\n"
|
||||
|
||||
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
|
||||
// using the 'dir' transport
|
||||
var ErrNotContainerImageDir = errors.New("not a containers image directory, don't want to overwrite important data")
|
||||
@@ -33,7 +31,8 @@ type dirImageDestination struct {
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
stubs.AlwaysSupportsSignatures
|
||||
|
||||
ref dirReference
|
||||
ref dirReference
|
||||
usesNonSHA256Digest bool
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to a directory.
|
||||
@@ -76,9 +75,14 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
|
||||
return nil, err
|
||||
}
|
||||
// check if contents of version file is what we expect it to be
|
||||
if string(contents) != version {
|
||||
versionStr := string(contents)
|
||||
parsedVersion, err := parseVersion(versionStr)
|
||||
if err != nil {
|
||||
return nil, ErrNotContainerImageDir
|
||||
}
|
||||
if parsedVersion.isGreaterThan(maxSupportedVersion) {
|
||||
return nil, UnsupportedVersionError{Version: versionStr, Path: ref.resolvedPath}
|
||||
}
|
||||
} else {
|
||||
return nil, ErrNotContainerImageDir
|
||||
}
|
||||
@@ -94,11 +98,6 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
|
||||
return nil, fmt.Errorf("unable to create directory %q: %w", ref.resolvedPath, err)
|
||||
}
|
||||
}
|
||||
// create version file
|
||||
err = os.WriteFile(ref.versionPath(), []byte(version), 0o644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating version file %q: %w", ref.versionPath(), err)
|
||||
}
|
||||
|
||||
d := &dirImageDestination{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
@@ -151,13 +150,17 @@ func (d *dirImageDestination) PutBlobWithOptions(ctx context.Context, stream io.
|
||||
}
|
||||
}()
|
||||
|
||||
digester, stream := putblobdigest.DigestIfCanonicalUnknown(stream, inputInfo)
|
||||
digester, stream := putblobdigest.DigestIfUnknown(stream, inputInfo)
|
||||
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
size, err := io.Copy(blobFile, stream)
|
||||
if err != nil {
|
||||
return private.UploadedBlob{}, err
|
||||
}
|
||||
blobDigest := digester.Digest()
|
||||
if blobDigest.Algorithm() != digest.Canonical { // compare the special case in layerPath
|
||||
d.usesNonSHA256Digest = true
|
||||
}
|
||||
if inputInfo.Size != -1 && size != inputInfo.Size {
|
||||
return private.UploadedBlob{}, fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", blobDigest, inputInfo.Size, size)
|
||||
}
|
||||
@@ -257,6 +260,14 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
|
||||
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *dirImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
|
||||
versionToWrite := version1_1
|
||||
if d.usesNonSHA256Digest {
|
||||
versionToWrite = version1_2
|
||||
}
|
||||
err := os.WriteFile(d.ref.versionPath(), []byte(versionToWrite.String()), 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing version file %q: %w", d.ref.versionPath(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
21
vendor/go.podman.io/image/v5/directory/directory_src.go
generated
vendored
21
vendor/go.podman.io/image/v5/directory/directory_src.go
generated
vendored
@@ -26,7 +26,24 @@ type dirImageSource struct {
|
||||
|
||||
// newImageSource returns an ImageSource reading from an existing directory.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ref dirReference) private.ImageSource {
|
||||
func newImageSource(ref dirReference) (private.ImageSource, error) {
|
||||
versionPath := ref.versionPath()
|
||||
contents, err := os.ReadFile(versionPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("reading version file %q: %w", versionPath, err)
|
||||
}
|
||||
} else {
|
||||
versionStr := string(contents)
|
||||
parsedVersion, err := parseVersion(versionStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid version file content: %q", versionStr)
|
||||
}
|
||||
if parsedVersion.isGreaterThan(maxSupportedVersion) {
|
||||
return nil, UnsupportedVersionError{Version: versionStr, Path: ref.resolvedPath}
|
||||
}
|
||||
}
|
||||
|
||||
s := &dirImageSource{
|
||||
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
|
||||
HasThreadSafeGetBlob: false,
|
||||
@@ -36,7 +53,7 @@ func newImageSource(ref dirReference) private.ImageSource {
|
||||
ref: ref,
|
||||
}
|
||||
s.Compat = impl.AddCompat(s)
|
||||
return s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source, _as specified by the user_
|
||||
|
||||
17
vendor/go.podman.io/image/v5/directory/directory_transport.go
generated
vendored
17
vendor/go.podman.io/image/v5/directory/directory_transport.go
generated
vendored
@@ -146,7 +146,7 @@ func (ref dirReference) NewImage(ctx context.Context, sys *types.SystemContext)
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref dirReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ref), nil
|
||||
return newImageSource(ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
@@ -172,12 +172,19 @@ func (ref dirReference) manifestPath(instanceDigest *digest.Digest) (string, err
|
||||
}
|
||||
|
||||
// layerPath returns a path for a layer tarball within a directory using our conventions.
|
||||
func (ref dirReference) layerPath(digest digest.Digest) (string, error) {
|
||||
if err := digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, and could possibly result in a path with ../, so validate explicitly.
|
||||
func (ref dirReference) layerPath(d digest.Digest) (string, error) {
|
||||
if err := d.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, and could possibly result in a path with ../, so validate explicitly.
|
||||
return "", err
|
||||
}
|
||||
// FIXME: Should we keep the digest identification?
|
||||
return filepath.Join(ref.path, digest.Encoded()), nil
|
||||
|
||||
var filename string
|
||||
if d.Algorithm() == digest.Canonical {
|
||||
filename = d.Encoded()
|
||||
} else {
|
||||
filename = d.Algorithm().String() + "-" + d.Encoded()
|
||||
}
|
||||
|
||||
return filepath.Join(ref.path, filename), nil
|
||||
}
|
||||
|
||||
// signaturePath returns a path for a signature within a directory using our conventions.
|
||||
|
||||
62
vendor/go.podman.io/image/v5/directory/version.go
generated
vendored
Normal file
62
vendor/go.podman.io/image/v5/directory/version.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package directory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
versionPrefix = "Directory Transport Version: "
|
||||
)
|
||||
|
||||
// version represents a parsed directory transport version
|
||||
type version struct {
|
||||
major int
|
||||
minor int
|
||||
}
|
||||
|
||||
// Supported versions
|
||||
// Write version file based on digest algorithm used.
|
||||
// 1.1 for sha256-only images, 1.2 otherwise.
|
||||
var (
|
||||
version1_1 = version{major: 1, minor: 1}
|
||||
version1_2 = version{major: 1, minor: 2}
|
||||
maxSupportedVersion = version1_2
|
||||
)
|
||||
|
||||
// String formats a version as a string suitable for writing to the version file
|
||||
func (v version) String() string {
|
||||
return fmt.Sprintf("%s%d.%d\n", versionPrefix, v.major, v.minor)
|
||||
}
|
||||
|
||||
// parseVersion parses a version string into major and minor components.
|
||||
// Returns an error if the format is invalid.
|
||||
func parseVersion(versionStr string) (version, error) {
|
||||
var v version
|
||||
expectedFormat := versionPrefix + "%d.%d\n"
|
||||
// Sscanf parsing is a bit loose (treats spaces specially), but a strict check immediately follows
|
||||
n, err := fmt.Sscanf(versionStr, expectedFormat, &v.major, &v.minor)
|
||||
if err != nil || n != 2 || versionStr != v.String() {
|
||||
return version{}, fmt.Errorf("invalid version format")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// TODO: Potential refactor for better interoperability with `cmp`
|
||||
// https://github.com/containers/container-libs/pull/475#discussion_r2571131267
|
||||
// isGreaterThan returns true if v is greater than other
|
||||
func (v version) isGreaterThan(other version) bool {
|
||||
if v.major != other.major {
|
||||
return v.major > other.major
|
||||
}
|
||||
return v.minor > other.minor
|
||||
}
|
||||
|
||||
// UnsupportedVersionError indicates that the directory uses a version newer than we support
|
||||
type UnsupportedVersionError struct {
|
||||
Version string // The unsupported version string found
|
||||
Path string // The path to the directory
|
||||
}
|
||||
|
||||
func (e UnsupportedVersionError) Error() string {
|
||||
return fmt.Sprintf("unsupported directory transport version %q at %s", e.Version, e.Path)
|
||||
}
|
||||
1
vendor/go.podman.io/image/v5/docker/daemon/client.go
generated
vendored
1
vendor/go.podman.io/image/v5/docker/daemon/client.go
generated
vendored
@@ -19,7 +19,6 @@ func newDockerClient(sys *types.SystemContext) (*dockerclient.Client, error) {
|
||||
|
||||
opts := []dockerclient.Opt{
|
||||
dockerclient.WithHost(host),
|
||||
dockerclient.WithAPIVersionNegotiation(),
|
||||
}
|
||||
|
||||
// We conditionalize building the TLS configuration only to TLS sockets:
|
||||
|
||||
16
vendor/go.podman.io/storage/drivers/driver.go
generated
vendored
16
vendor/go.podman.io/storage/drivers/driver.go
generated
vendored
@@ -47,7 +47,6 @@ type CreateOpts struct {
|
||||
MountLabel string
|
||||
StorageOpt map[string]string
|
||||
*idtools.IDMappings
|
||||
ignoreChownErrors bool
|
||||
}
|
||||
|
||||
// MountOpts contains optional arguments for Driver.Get() methods.
|
||||
@@ -184,7 +183,7 @@ type DiffDriver interface {
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The io.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id string, parent string, options ApplyDiffOpts) (size int64, err error)
|
||||
ApplyDiff(id string, options ApplyDiffOpts) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
@@ -299,6 +298,19 @@ type DriverWithDiffer interface {
|
||||
DifferTarget(id string) (string, error)
|
||||
}
|
||||
|
||||
// ApplyDiffStaging is an interface for driver who can apply the diff without holding the main storage lock.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type ApplyDiffStaging interface {
|
||||
// StartStagingDiffToApply applies the new layer into a temporary directory.
|
||||
// It returns a CleanupTempDirFunc which can be nil or set regardless if the function return an error or not.
|
||||
// StagedAddition is only set when there is no error returned and the int64 value returns the size of the layer.
|
||||
// This can be done without holding the storage lock, if a parent is given the caller must check for existence
|
||||
// beforehand while holding a lock.
|
||||
StartStagingDiffToApply(parent string, options ApplyDiffOpts) (tempdir.CleanupTempDirFunc, *tempdir.StagedAddition, int64, error)
|
||||
// CommitStagedLayer commits the staged layer from StartStagingDiffToApply(). This must be done while holding the storage lock.
|
||||
CommitStagedLayer(id string, commit *tempdir.StagedAddition) error
|
||||
}
|
||||
|
||||
// Capabilities defines a list of capabilities a driver may implement.
|
||||
// These capabilities are not required; however, they do determine how a
|
||||
// graphdriver can be used.
|
||||
|
||||
2
vendor/go.podman.io/storage/drivers/fsdiff.go
generated
vendored
2
vendor/go.podman.io/storage/drivers/fsdiff.go
generated
vendored
@@ -151,7 +151,7 @@ func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, p
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, options ApplyDiffOpts) (int64, error) {
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id string, options ApplyDiffOpts) (int64, error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if options.Mappings == nil {
|
||||
|
||||
181
vendor/go.podman.io/storage/drivers/overlay/overlay.go
generated
vendored
181
vendor/go.podman.io/storage/drivers/overlay/overlay.go
generated
vendored
@@ -995,6 +995,49 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
|
||||
return d.create(id, parent, opts, true)
|
||||
}
|
||||
|
||||
// getLayerPermissions returns the base permissions to use for the layer directories.
|
||||
// The first return value is the idPair to create the possible parent directories with.
|
||||
// The second return value is the mode how it should be stored on disk.
|
||||
// The third return value is the mode the layer expects to have which may be stored
|
||||
// in an xattr when using forceMask, without forceMask both values are the same.
|
||||
func (d *Driver) getLayerPermissions(parent string, uidMaps, gidMaps []idtools.IDMap) (idtools.IDPair, idtools.Stat, idtools.Stat, error) {
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, idtools.Stat{}, idtools.Stat{}, err
|
||||
}
|
||||
|
||||
idPair := idtools.IDPair{
|
||||
UID: rootUID,
|
||||
GID: rootGID,
|
||||
}
|
||||
|
||||
st := idtools.Stat{IDs: idPair, Mode: defaultPerms}
|
||||
|
||||
if parent != "" {
|
||||
parentBase := d.dir(parent)
|
||||
parentDiff := filepath.Join(parentBase, "diff")
|
||||
if xSt, err := idtools.GetContainersOverrideXattr(parentDiff); err == nil {
|
||||
st = xSt
|
||||
} else {
|
||||
systemSt, err := system.Stat(parentDiff)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, idtools.Stat{}, idtools.Stat{}, err
|
||||
}
|
||||
st.IDs.UID = int(systemSt.UID())
|
||||
st.IDs.GID = int(systemSt.GID())
|
||||
st.Mode = os.FileMode(systemSt.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
forcedSt := st
|
||||
if d.options.forceMask != nil {
|
||||
forcedSt.IDs = idPair
|
||||
forcedSt.Mode = *d.options.forceMask
|
||||
}
|
||||
|
||||
return idPair, forcedSt, st, nil
|
||||
}
|
||||
|
||||
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnly bool) (retErr error) {
|
||||
dir, homedir, _ := d.dir2(id, readOnly)
|
||||
|
||||
@@ -1013,38 +1056,15 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
|
||||
return err
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
|
||||
idPair, forcedSt, st, err := d.getLayerPermissions(parent, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idPair := idtools.IDPair{
|
||||
UID: rootUID,
|
||||
GID: rootGID,
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllAndChownNew(path.Dir(dir), 0o755, idPair); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
st := idtools.Stat{IDs: idPair, Mode: defaultPerms}
|
||||
|
||||
if parent != "" {
|
||||
parentBase := d.dir(parent)
|
||||
parentDiff := filepath.Join(parentBase, "diff")
|
||||
if xSt, err := idtools.GetContainersOverrideXattr(parentDiff); err == nil {
|
||||
st = xSt
|
||||
} else {
|
||||
systemSt, err := system.Stat(parentDiff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.IDs.UID = int(systemSt.UID())
|
||||
st.IDs.GID = int(systemSt.GID())
|
||||
st.Mode = os.FileMode(systemSt.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileutils.Lexists(dir); err == nil {
|
||||
logrus.Warnf("Trying to create a layer %#v while directory %q already exists; removing it first", id, dir)
|
||||
// Don’t just os.RemoveAll(dir) here; d.Remove also removes the link in linkDir,
|
||||
@@ -1088,12 +1108,6 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
|
||||
}
|
||||
}
|
||||
|
||||
forcedSt := st
|
||||
if d.options.forceMask != nil {
|
||||
forcedSt.IDs = idPair
|
||||
forcedSt.Mode = *d.options.forceMask
|
||||
}
|
||||
|
||||
diff := path.Join(dir, "diff")
|
||||
if err := idtools.MkdirAs(diff, forcedSt.Mode, forcedSt.IDs.UID, forcedSt.IDs.GID); err != nil {
|
||||
return err
|
||||
@@ -1356,6 +1370,14 @@ func (d *Driver) getTempDirRoot(id string) string {
|
||||
return filepath.Join(d.home, tempDirName)
|
||||
}
|
||||
|
||||
// getTempDirRootForNewLayer returns the correct temp directory root based on where
|
||||
// the layer should be created.
|
||||
//
|
||||
// This must be kept in sync with GetTempDirRootDirs().
|
||||
func (d *Driver) getTempDirRootForNewLayer() string {
|
||||
return filepath.Join(d.homeDirForImageStore(), tempDirName)
|
||||
}
|
||||
|
||||
func (d *Driver) DeferredRemove(id string) (tempdir.CleanupTempDirFunc, error) {
|
||||
tempDirRoot := d.getTempDirRoot(id)
|
||||
t, err := tempdir.NewTempDir(tempDirRoot)
|
||||
@@ -2369,31 +2391,94 @@ func (d *Driver) DifferTarget(id string) (string, error) {
|
||||
return d.getDiffPath(id)
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root
|
||||
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if !d.isParent(id, parent) {
|
||||
if d.options.ignoreChownErrors {
|
||||
options.IgnoreChownErrors = d.options.ignoreChownErrors
|
||||
}
|
||||
if d.options.forceMask != nil {
|
||||
options.ForceMask = d.options.forceMask
|
||||
}
|
||||
return d.naiveDiff.ApplyDiff(id, parent, options)
|
||||
// StartStagingDiffToApply applies the new layer into a temporary directory.
|
||||
// It returns a CleanupTempDirFunc which can be nil or set regardless if the function return an error or not.
|
||||
// StagedAddition is only set when there is no error returned and the int64 value returns the size of the layer.
|
||||
// This can be done without holding the storage lock, if a parent is given the caller must check for existence
|
||||
// beforehand while holding a lock.
|
||||
//
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
func (d *Driver) StartStagingDiffToApply(parent string, options graphdriver.ApplyDiffOpts) (tempdir.CleanupTempDirFunc, *tempdir.StagedAddition, int64, error) {
|
||||
tempDirRoot := d.getTempDirRootForNewLayer()
|
||||
t, err := tempdir.NewTempDir(tempDirRoot)
|
||||
if err != nil {
|
||||
return nil, nil, -1, err
|
||||
}
|
||||
|
||||
sa, err := t.StageAddition()
|
||||
if err != nil {
|
||||
return t.Cleanup, nil, -1, err
|
||||
}
|
||||
|
||||
_, forcedSt, st, err := d.getLayerPermissions(parent, options.Mappings.UIDs(), options.Mappings.GIDs())
|
||||
if err != nil {
|
||||
// If we have a ENOENT it means the parent was removed which can happen as we are unlocked here.
|
||||
// In this case also wrap ErrLayerUnknown which some callers can handle to retry after recreating the parent.
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
err = fmt.Errorf("parent layer %q: %w: %w", parent, graphdriver.ErrLayerUnknown, err)
|
||||
}
|
||||
return t.Cleanup, nil, -1, err
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAs(sa.Path, forcedSt.Mode, forcedSt.IDs.UID, forcedSt.IDs.GID); err != nil {
|
||||
return t.Cleanup, nil, -1, err
|
||||
}
|
||||
|
||||
if d.options.forceMask != nil {
|
||||
st.Mode |= os.ModeDir
|
||||
if err := idtools.SetContainersOverrideXattr(sa.Path, st); err != nil {
|
||||
return t.Cleanup, nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
size, err := d.applyDiff(sa.Path, options)
|
||||
if err != nil {
|
||||
return t.Cleanup, nil, -1, err
|
||||
}
|
||||
|
||||
return t.Cleanup, sa, size, nil
|
||||
}
|
||||
|
||||
// CommitStagedLayer that was created with StartStagingDiffToApply().
|
||||
//
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
func (d *Driver) CommitStagedLayer(id string, sa *tempdir.StagedAddition) error {
|
||||
applyDir, err := d.getDiffPath(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The os.Rename() function used by CommitFunc errors when the target directory already
|
||||
// exists, as such delete the dir. The create() function creates it and it would be more
|
||||
// complicated to code in a way that it didn't create it.
|
||||
if err := os.Remove(applyDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sa.Commit(applyDir)
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root
|
||||
func (d *Driver) ApplyDiff(id string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
applyDir, err := d.getDiffPath(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.applyDiff(applyDir, options)
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root.
|
||||
// This can run concurrently with any other driver operations, as such it is the
|
||||
// callers responsibility to ensure the target path passed is safe to use if that is the case.
|
||||
func (d *Driver) applyDiff(target string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
idMappings := options.Mappings
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
applyDir, err := d.getDiffPath(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Applying tar in %s", applyDir)
|
||||
logrus.Debugf("Applying tar in %s", target)
|
||||
// Overlay doesn't need the parent id to apply the diff
|
||||
if err := untar(options.Diff, applyDir, &archive.TarOptions{
|
||||
if err := untar(options.Diff, target, &archive.TarOptions{
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
IgnoreChownErrors: d.options.ignoreChownErrors,
|
||||
@@ -2404,7 +2489,7 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return directory.Size(applyDir)
|
||||
return directory.Size(target)
|
||||
}
|
||||
|
||||
func (d *Driver) getComposefsData(id string) string {
|
||||
|
||||
52
vendor/go.podman.io/storage/drivers/template.go
generated
vendored
52
vendor/go.podman.io/storage/drivers/template.go
generated
vendored
@@ -1,52 +0,0 @@
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/storage/pkg/idtools"
|
||||
)
|
||||
|
||||
// TemplateDriver is just barely enough of a driver that we can implement a
|
||||
// naive version of CreateFromTemplate on top of it.
|
||||
type TemplateDriver interface {
|
||||
DiffDriver
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
Remove(id string) error
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as
|
||||
// another layer. Internally, it may even depend on that other layer
|
||||
// continuing to exist, as if it were actually a child of the child layer.
|
||||
func NaiveCreateFromTemplate(d TemplateDriver, id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error {
|
||||
var err error
|
||||
if readWrite {
|
||||
err = d.CreateReadWrite(id, parent, opts)
|
||||
} else {
|
||||
err = d.Create(id, parent, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diff, err := d.Diff(template, templateIDMappings, parent, parentIDMappings, opts.MountLabel)
|
||||
if err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer diff.Close()
|
||||
|
||||
applyOptions := ApplyDiffOpts{
|
||||
Diff: diff,
|
||||
Mappings: templateIDMappings,
|
||||
MountLabel: opts.MountLabel,
|
||||
IgnoreChownErrors: opts.ignoreChownErrors,
|
||||
}
|
||||
if _, err = d.ApplyDiff(id, parent, applyOptions); err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
4
vendor/go.podman.io/storage/drivers/vfs/driver.go
generated
vendored
4
vendor/go.podman.io/storage/drivers/vfs/driver.go
generated
vendored
@@ -132,11 +132,11 @@ func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idt
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root
|
||||
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
func (d *Driver) ApplyDiff(id string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if d.ignoreChownErrors {
|
||||
options.IgnoreChownErrors = d.ignoreChownErrors
|
||||
}
|
||||
return d.naiveDiff.ApplyDiff(id, parent, options)
|
||||
return d.naiveDiff.ApplyDiff(id, options)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
|
||||
38
vendor/go.podman.io/storage/internal/tempdir/tempdir.go
generated
vendored
38
vendor/go.podman.io/storage/internal/tempdir/tempdir.go
generated
vendored
@@ -6,6 +6,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -91,6 +92,26 @@ type TempDir struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
// StagedAddition is a temporary object which holds the information of where to
|
||||
// put the data into and then use Commit() to move the data into the final location.
|
||||
type StagedAddition struct {
|
||||
// Path is the temporary path. The path is not created so caller must create
|
||||
// a file or directory on it in order to use Commit(). The path is only valid
|
||||
// until Commit() is called or until the TempDir instance Cleanup() method is used.
|
||||
Path string
|
||||
}
|
||||
|
||||
// Commit the staged content into its final destination by using os.Rename().
|
||||
// That means the dest must be on the same on the same fs as the root directory
|
||||
// that was given to NewTempDir() and the dest must not exist yet.
|
||||
// Commit must only be called once per instance returned from the
|
||||
// StagedAddition() call.
|
||||
func (s *StagedAddition) Commit(destination string) error {
|
||||
err := os.Rename(s.Path, destination)
|
||||
s.Path = "" // invalidate Path to avoid reuse
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanupTempDirFunc is a function type that can be returned by operations
|
||||
// which need to perform cleanup actions later.
|
||||
type CleanupTempDirFunc func() error
|
||||
@@ -190,6 +211,23 @@ func NewTempDir(rootDir string) (*TempDir, error) {
|
||||
return td, nil
|
||||
}
|
||||
|
||||
// StageAddition creates a new temporary path that is returned as field in the StagedAddition
|
||||
// struct. The returned type StagedAddition has a Commit() function to move the content from
|
||||
// the temporary location to the final one.
|
||||
//
|
||||
// The caller MUST call Commit() before Cleanup() is called on the TempDir, otherwise the
|
||||
// staged content will be deleted and the Commit() will fail.
|
||||
// If the TempDir has been cleaned up already, this method will return an error.
|
||||
func (td *TempDir) StageAddition() (*StagedAddition, error) {
|
||||
if td.tempDirLock == nil {
|
||||
return nil, fmt.Errorf("temp dir instance not initialized or already cleaned up")
|
||||
}
|
||||
fileName := strconv.FormatUint(td.counter, 10) + "-addition"
|
||||
tmpAddPath := filepath.Join(td.tempDirPath, fileName)
|
||||
td.counter++
|
||||
return &StagedAddition{Path: tmpAddPath}, nil
|
||||
}
|
||||
|
||||
// StageDeletion moves the specified file into the instance's temporary directory.
|
||||
// The temporary directory must already exist (created during NewTempDir).
|
||||
// Files are renamed with a counter-based prefix (e.g., "0-filename", "1-filename") to ensure uniqueness.
|
||||
|
||||
408
vendor/go.podman.io/storage/layers.go
generated
vendored
408
vendor/go.podman.io/storage/layers.go
generated
vendored
@@ -31,6 +31,7 @@ import (
|
||||
"go.podman.io/storage/pkg/ioutils"
|
||||
"go.podman.io/storage/pkg/lockfile"
|
||||
"go.podman.io/storage/pkg/mount"
|
||||
"go.podman.io/storage/pkg/pools"
|
||||
"go.podman.io/storage/pkg/stringid"
|
||||
"go.podman.io/storage/pkg/system"
|
||||
"go.podman.io/storage/pkg/tarlog"
|
||||
@@ -195,11 +196,53 @@ type DiffOptions struct {
|
||||
Compression *archive.Compression
|
||||
}
|
||||
|
||||
// stagedLayerOptions are the options passed to .create to populate a staged
|
||||
// layerCreationContents are the options passed to .create to populate a staged
|
||||
// layer
|
||||
type stagedLayerOptions struct {
|
||||
type layerCreationContents struct {
|
||||
// These are used via the zstd:chunked pull paths
|
||||
DiffOutput *drivers.DriverWithDifferOutput
|
||||
DiffOptions *drivers.ApplyDiffWithDifferOpts
|
||||
|
||||
// stagedLayerExtraction is used by the normal tar layer extraction.
|
||||
stagedLayerExtraction *maybeStagedLayerExtraction
|
||||
}
|
||||
|
||||
// maybeStagedLayerExtraction is a helper to encapsulate details around extracting
|
||||
// a layer potentially before we even take a look if the driver implements the
|
||||
// ApplyDiffStaging interface.
|
||||
// This should be initialized with layerStore.newMaybeStagedLayerExtraction()
|
||||
type maybeStagedLayerExtraction struct {
|
||||
// diff contains the tar archive, can be compressed, must be non nil, but can be at EOF when the content was already staged
|
||||
diff io.Reader
|
||||
// staging interface of the storage driver, set when the driver supports staging and nil otherwise
|
||||
staging drivers.ApplyDiffStaging
|
||||
// result is a placeholder for the applyDiff() result so we can pass that down the stack easily.
|
||||
// If result is not nil the layer was staged successfully, if this is set stagedTarSplit and
|
||||
// stagedLayer must be set as well.
|
||||
result *applyDiffResult
|
||||
|
||||
// stagedTarSplit is the temp file where we staged the tar split file
|
||||
stagedTarSplit *tempdir.StagedAddition
|
||||
// stagedLayer is the temp directory where we staged the extracted layer content
|
||||
stagedLayer *tempdir.StagedAddition
|
||||
|
||||
// cleanupFuncs contains the set of tempdir cleanup function that get executed in cleanup()
|
||||
cleanupFuncs []tempdir.CleanupTempDirFunc
|
||||
}
|
||||
|
||||
type applyDiffResult struct {
|
||||
compressedDigest digest.Digest
|
||||
compressedSize int64
|
||||
compressionType archive.Compression
|
||||
uncompressedDigest digest.Digest
|
||||
uncompressedSize int64
|
||||
// size of the data, including the full size of sparse files, and excluding all metadata
|
||||
// It is neither compressedSize nor uncompressedSize.
|
||||
// The use case for this seems unclear, it gets returned in PutLayer() but in the Podman
|
||||
// stack at least that value is never used so maybe we can look into removing this.
|
||||
size int64
|
||||
uids []uint32
|
||||
gids []uint32
|
||||
}
|
||||
|
||||
// roLayerStore wraps a graph driver, adding the ability to refer to layers by
|
||||
@@ -216,6 +259,11 @@ type roLayerStore interface {
|
||||
// stopReading releases locks obtained by startReading.
|
||||
stopReading()
|
||||
|
||||
// checkIdOrNameConflict checks if the id or names are already in use and returns an
|
||||
// error in that case. As Special case if the layer already exists it returns it as
|
||||
// well together with the error.
|
||||
checkIdOrNameConflict(id string, names []string) (*Layer, error)
|
||||
|
||||
// Exists checks if a layer with the specified name or ID is known.
|
||||
Exists(id string) bool
|
||||
|
||||
@@ -288,7 +336,7 @@ type rwLayerStore interface {
|
||||
// underlying drivers do not themselves distinguish between writeable
|
||||
// and read-only layers. Returns the new layer structure and the size of the
|
||||
// diff which was applied to its parent to initialize its contents.
|
||||
create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader, slo *stagedLayerOptions) (*Layer, int64, error)
|
||||
create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, contents *layerCreationContents) (*Layer, int64, error)
|
||||
|
||||
// updateNames modifies names associated with a layer based on (op, names).
|
||||
updateNames(id string, names []string, op updateNameOperation) error
|
||||
@@ -354,6 +402,14 @@ type rwLayerStore interface {
|
||||
|
||||
// Dedup deduplicates layers in the store.
|
||||
dedup(drivers.DedupArgs) (drivers.DedupResult, error)
|
||||
|
||||
// newMaybeStagedLayerExtraction initializes a new maybeStagedLayerExtraction. The caller
|
||||
// must call maybeStagedLayerExtraction.cleanup() to remove any temporary files.
|
||||
newMaybeStagedLayerExtraction(diff io.Reader) *maybeStagedLayerExtraction
|
||||
|
||||
// stageWithUnlockedStore stages the layer content without needing the store locked.
|
||||
// If the driver does not support stage addition then this is a NOP and does nothing.
|
||||
stageWithUnlockedStore(m *maybeStagedLayerExtraction, parent string, options *LayerOptions) error
|
||||
}
|
||||
|
||||
type multipleLockFile struct {
|
||||
@@ -1307,13 +1363,8 @@ func (r *layerStore) Status() ([][2]string, error) {
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) PutAdditionalLayer(id string, parentLayer *Layer, names []string, aLayer drivers.AdditionalLayer) (layer *Layer, err error) {
|
||||
if duplicateLayer, idInUse := r.byid[id]; idInUse {
|
||||
return duplicateLayer, ErrDuplicateID
|
||||
}
|
||||
for _, name := range names {
|
||||
if _, nameInUse := r.byname[name]; nameInUse {
|
||||
return nil, ErrDuplicateName
|
||||
}
|
||||
if layer, err := r.checkIdOrNameConflict(id, names); err != nil {
|
||||
return layer, err
|
||||
}
|
||||
|
||||
parent := ""
|
||||
@@ -1378,8 +1429,25 @@ func (r *layerStore) pickStoreLocation(volatile, writeable bool) layerLocations
|
||||
}
|
||||
}
|
||||
|
||||
// checkIdOrNameConflict checks if the id or names are already in use and returns an
|
||||
// error in that case. As Special case if the layer already exists it returns it as
|
||||
// well together with the error.
|
||||
//
|
||||
// Requires startReading or startWriting.
|
||||
func (r *layerStore) checkIdOrNameConflict(id string, names []string) (*Layer, error) {
|
||||
if duplicateLayer, idInUse := r.byid[id]; idInUse {
|
||||
return duplicateLayer, ErrDuplicateID
|
||||
}
|
||||
for _, name := range names {
|
||||
if _, nameInUse := r.byname[name]; nameInUse {
|
||||
return nil, ErrDuplicateName
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) create(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader, slo *stagedLayerOptions) (layer *Layer, size int64, err error) {
|
||||
func (r *layerStore) create(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, contents *layerCreationContents) (layer *Layer, size int64, err error) {
|
||||
if moreOptions == nil {
|
||||
moreOptions = &LayerOptions{}
|
||||
}
|
||||
@@ -1400,14 +1468,8 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount
|
||||
_, idInUse = r.byid[id]
|
||||
}
|
||||
}
|
||||
if duplicateLayer, idInUse := r.byid[id]; idInUse {
|
||||
return duplicateLayer, -1, ErrDuplicateID
|
||||
}
|
||||
names = dedupeStrings(names)
|
||||
for _, name := range names {
|
||||
if _, nameInUse := r.byname[name]; nameInUse {
|
||||
return nil, -1, ErrDuplicateName
|
||||
}
|
||||
if layer, err := r.checkIdOrNameConflict(id, names); err != nil {
|
||||
return layer, -1, err
|
||||
}
|
||||
parent := ""
|
||||
if parentLayer != nil {
|
||||
@@ -1568,18 +1630,31 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount
|
||||
}
|
||||
|
||||
size = -1
|
||||
if diff != nil {
|
||||
if size, err = r.applyDiffWithOptions(layer.ID, moreOptions, diff); err != nil {
|
||||
cleanupFailureContext = "applying layer diff"
|
||||
return nil, -1, err
|
||||
}
|
||||
} else if slo != nil {
|
||||
if err := r.applyDiffFromStagingDirectory(layer.ID, slo.DiffOutput, slo.DiffOptions); err != nil {
|
||||
cleanupFailureContext = "applying staged directory diff"
|
||||
return nil, -1, err
|
||||
if contents != nil {
|
||||
if contents.stagedLayerExtraction != nil {
|
||||
if contents.stagedLayerExtraction.result != nil {
|
||||
// The layer is staged, just commit it and update the metadata.
|
||||
if err := contents.stagedLayerExtraction.commitLayer(r, layer.ID); err != nil {
|
||||
cleanupFailureContext = "committing staged layer diff"
|
||||
return nil, -1, err
|
||||
}
|
||||
r.applyDiffResultToLayer(layer, contents.stagedLayerExtraction.result)
|
||||
} else {
|
||||
// The diff was not staged, apply it now here instead.
|
||||
if size, err = r.applyDiffWithOptions(layer.ID, moreOptions, contents.stagedLayerExtraction.diff); err != nil {
|
||||
cleanupFailureContext = "applying layer diff"
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// staging logic for the chunked pull path
|
||||
if err := r.applyDiffFromStagingDirectory(layer.ID, contents.DiffOutput, contents.DiffOptions); err != nil {
|
||||
cleanupFailureContext = "applying staged directory diff"
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// applyDiffWithOptions() would have updated r.bycompressedsum
|
||||
// The layer creation content above would have updated r.bycompressedsum
|
||||
// and r.byuncompressedsum for us, but if we used a template
|
||||
// layer, we didn't call it, so add the new layer as candidates
|
||||
// for searches for layers by checksum
|
||||
@@ -2398,37 +2473,118 @@ func (r *layerStore) ApplyDiff(to string, diff io.Reader) (size int64, err error
|
||||
return r.applyDiffWithOptions(to, nil, diff)
|
||||
}
|
||||
|
||||
func createTarSplitFile(r *layerStore, layerID string) (*os.File, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(r.tspath(layerID)), 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.OpenFile(r.tspath(layerID), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
|
||||
}
|
||||
|
||||
// newMaybeStagedLayerExtraction initializes a new maybeStagedLayerExtraction. The caller
|
||||
// must call maybeStagedLayerExtraction.cleanup() to remove any temporary files.
|
||||
func (r *layerStore) newMaybeStagedLayerExtraction(diff io.Reader) *maybeStagedLayerExtraction {
|
||||
m := &maybeStagedLayerExtraction{
|
||||
diff: diff,
|
||||
}
|
||||
if d, ok := r.driver.(drivers.ApplyDiffStaging); ok {
|
||||
m.staging = d
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (sl *maybeStagedLayerExtraction) cleanup() error {
|
||||
return tempdir.CleanupTemporaryDirectories(sl.cleanupFuncs...)
|
||||
}
|
||||
|
||||
// stageWithUnlockedStore stages the layer content without needing the store locked.
|
||||
// If the driver does not support stage addition then this is a NOP and does nothing.
|
||||
// This should be done without holding the storage lock, if a parent is given the caller
|
||||
// must check for existence beforehand while holding a lock.
|
||||
func (r *layerStore) stageWithUnlockedStore(sl *maybeStagedLayerExtraction, parent string, layerOptions *LayerOptions) (retErr error) {
|
||||
if sl.staging == nil {
|
||||
return nil
|
||||
}
|
||||
td, err := tempdir.NewTempDir(filepath.Join(r.layerdir, tempDirPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sl.cleanupFuncs = append(sl.cleanupFuncs, td.Cleanup)
|
||||
|
||||
stagedTarSplit, err := td.StageAddition()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sl.stagedTarSplit = stagedTarSplit
|
||||
|
||||
f, err := os.OpenFile(stagedTarSplit.Path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// make sure to check for errors on close and return that one.
|
||||
defer func() {
|
||||
closeErr := f.Close()
|
||||
if retErr == nil {
|
||||
retErr = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
result, err := applyDiff(layerOptions, sl.diff, f, func(payload io.Reader) (int64, error) {
|
||||
cleanup, stagedLayer, size, err := sl.staging.StartStagingDiffToApply(parent, drivers.ApplyDiffOpts{
|
||||
Diff: payload,
|
||||
Mappings: idtools.NewIDMappingsFromMaps(layerOptions.UIDMap, layerOptions.GIDMap),
|
||||
// MountLabel is not supported for the unlocked extraction, see the comment in (*store).PutLayer()
|
||||
MountLabel: "",
|
||||
})
|
||||
sl.cleanupFuncs = append(sl.cleanupFuncs, cleanup)
|
||||
sl.stagedLayer = stagedLayer
|
||||
return size, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Sync(); err != nil {
|
||||
return fmt.Errorf("sync staged tar-split file: %w", err)
|
||||
}
|
||||
|
||||
sl.result = result
|
||||
return nil
|
||||
}
|
||||
|
||||
// commitLayer() commits the content that was staged in stageWithUnlockedStore()
|
||||
//
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) applyDiffWithOptions(to string, layerOptions *LayerOptions, diff io.Reader) (size int64, err error) {
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return -1, fmt.Errorf("not allowed to modify layer contents at %q: %w", r.layerdir, ErrStoreIsReadOnly)
|
||||
}
|
||||
|
||||
layer, ok := r.lookup(to)
|
||||
if !ok {
|
||||
return -1, ErrLayerUnknown
|
||||
func (sl *maybeStagedLayerExtraction) commitLayer(r *layerStore, layerID string) error {
|
||||
err := sl.stagedTarSplit.Commit(r.tspath(layerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sl.staging.CommitStagedLayer(layerID, sl.stagedLayer)
|
||||
}
|
||||
|
||||
// applyDiff can be called without holding any store locks so if the supplied
|
||||
// applyDriverFunc requires locking the caller must ensure proper locking.
|
||||
func applyDiff(layerOptions *LayerOptions, diff io.Reader, tarSplitFile *os.File, applyDriverFunc func(io.Reader) (int64, error)) (*applyDiffResult, error) {
|
||||
header := make([]byte, 10240)
|
||||
n, err := diff.Read(header)
|
||||
if err != nil && err != io.EOF {
|
||||
return -1, err
|
||||
return nil, err
|
||||
}
|
||||
compression := archive.DetectCompression(header[:n])
|
||||
defragmented := io.MultiReader(bytes.NewReader(header[:n]), diff)
|
||||
|
||||
// Decide if we need to compute digests
|
||||
var compressedDigest, uncompressedDigest digest.Digest // = ""
|
||||
result := applyDiffResult{}
|
||||
|
||||
var compressedDigester, uncompressedDigester digest.Digester // = nil
|
||||
if layerOptions != nil && layerOptions.OriginalDigest != "" &&
|
||||
layerOptions.OriginalDigest.Algorithm() == digest.Canonical {
|
||||
compressedDigest = layerOptions.OriginalDigest
|
||||
result.compressedDigest = layerOptions.OriginalDigest
|
||||
} else {
|
||||
compressedDigester = digest.Canonical.Digester()
|
||||
}
|
||||
if layerOptions != nil && layerOptions.UncompressedDigest != "" &&
|
||||
layerOptions.UncompressedDigest.Algorithm() == digest.Canonical {
|
||||
uncompressedDigest = layerOptions.UncompressedDigest
|
||||
result.uncompressedDigest = layerOptions.UncompressedDigest
|
||||
} else if compression != archive.Uncompressed {
|
||||
uncompressedDigester = digest.Canonical.Digester()
|
||||
}
|
||||
@@ -2442,13 +2598,15 @@ func (r *layerStore) applyDiffWithOptions(to string, layerOptions *LayerOptions,
|
||||
compressedCounter := ioutils.NewWriteCounter(compressedWriter)
|
||||
defragmented = io.TeeReader(defragmented, compressedCounter)
|
||||
|
||||
tsdata := bytes.Buffer{}
|
||||
tarSplitWriter := pools.BufioWriter32KPool.Get(tarSplitFile)
|
||||
defer pools.BufioWriter32KPool.Put(tarSplitWriter)
|
||||
|
||||
uidLog := make(map[uint32]struct{})
|
||||
gidLog := make(map[uint32]struct{})
|
||||
var uncompressedCounter *ioutils.WriteCounter
|
||||
|
||||
size, err = func() (int64, error) { // A scope for defer
|
||||
compressor, err := pgzip.NewWriterLevel(&tsdata, pgzip.BestSpeed)
|
||||
size, err := func() (int64, error) { // A scope for defer
|
||||
compressor, err := pgzip.NewWriterLevel(tarSplitWriter, pgzip.BestSpeed)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -2481,62 +2639,108 @@ func (r *layerStore) applyDiffWithOptions(to string, layerOptions *LayerOptions,
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return applyDriverFunc(payload)
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tarSplitWriter.Flush(); err != nil {
|
||||
return nil, fmt.Errorf("failed to flush tar-split writer buffer: %w", err)
|
||||
}
|
||||
|
||||
if compressedDigester != nil {
|
||||
result.compressedDigest = compressedDigester.Digest()
|
||||
}
|
||||
if uncompressedDigester != nil {
|
||||
result.uncompressedDigest = uncompressedDigester.Digest()
|
||||
}
|
||||
if result.uncompressedDigest == "" && compression == archive.Uncompressed {
|
||||
result.uncompressedDigest = result.compressedDigest
|
||||
}
|
||||
|
||||
if layerOptions != nil && layerOptions.OriginalDigest != "" && layerOptions.OriginalSize != nil {
|
||||
result.compressedSize = *layerOptions.OriginalSize
|
||||
} else {
|
||||
result.compressedSize = compressedCounter.Count
|
||||
}
|
||||
result.uncompressedSize = uncompressedCounter.Count
|
||||
result.compressionType = compression
|
||||
|
||||
result.uids = make([]uint32, 0, len(uidLog))
|
||||
for uid := range uidLog {
|
||||
result.uids = append(result.uids, uid)
|
||||
}
|
||||
slices.Sort(result.uids)
|
||||
result.gids = make([]uint32, 0, len(gidLog))
|
||||
for gid := range gidLog {
|
||||
result.gids = append(result.gids, gid)
|
||||
}
|
||||
slices.Sort(result.gids)
|
||||
|
||||
result.size = size
|
||||
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) applyDiffWithOptions(to string, layerOptions *LayerOptions, diff io.Reader) (_ int64, retErr error) {
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return -1, fmt.Errorf("not allowed to modify layer contents at %q: %w", r.layerdir, ErrStoreIsReadOnly)
|
||||
}
|
||||
|
||||
layer, ok := r.lookup(to)
|
||||
if !ok {
|
||||
return -1, ErrLayerUnknown
|
||||
}
|
||||
|
||||
tarSplitFile, err := createTarSplitFile(r, layer.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// make sure to check for errors on close and return that one.
|
||||
defer func() {
|
||||
closeErr := tarSplitFile.Close()
|
||||
if retErr == nil {
|
||||
retErr = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
result, err := applyDiff(layerOptions, diff, tarSplitFile, func(payload io.Reader) (int64, error) {
|
||||
options := drivers.ApplyDiffOpts{
|
||||
Diff: payload,
|
||||
Mappings: r.layerMappings(layer),
|
||||
MountLabel: layer.MountLabel,
|
||||
}
|
||||
size, err := r.driver.ApplyDiff(layer.ID, layer.Parent, options)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return size, err
|
||||
}()
|
||||
return r.driver.ApplyDiff(layer.ID, options)
|
||||
})
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(r.tspath(layer.ID)), 0o700); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(r.tspath(layer.ID), tsdata.Bytes(), 0o600); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if compressedDigester != nil {
|
||||
compressedDigest = compressedDigester.Digest()
|
||||
}
|
||||
if uncompressedDigester != nil {
|
||||
uncompressedDigest = uncompressedDigester.Digest()
|
||||
}
|
||||
if uncompressedDigest == "" && compression == archive.Uncompressed {
|
||||
uncompressedDigest = compressedDigest
|
||||
if err := tarSplitFile.Sync(); err != nil {
|
||||
return -1, fmt.Errorf("sync tar-split file: %w", err)
|
||||
}
|
||||
|
||||
updateDigestMap(&r.bycompressedsum, layer.CompressedDigest, compressedDigest, layer.ID)
|
||||
layer.CompressedDigest = compressedDigest
|
||||
if layerOptions != nil && layerOptions.OriginalDigest != "" && layerOptions.OriginalSize != nil {
|
||||
layer.CompressedSize = *layerOptions.OriginalSize
|
||||
} else {
|
||||
layer.CompressedSize = compressedCounter.Count
|
||||
}
|
||||
updateDigestMap(&r.byuncompressedsum, layer.UncompressedDigest, uncompressedDigest, layer.ID)
|
||||
layer.UncompressedDigest = uncompressedDigest
|
||||
layer.UncompressedSize = uncompressedCounter.Count
|
||||
layer.CompressionType = compression
|
||||
layer.UIDs = make([]uint32, 0, len(uidLog))
|
||||
for uid := range uidLog {
|
||||
layer.UIDs = append(layer.UIDs, uid)
|
||||
}
|
||||
slices.Sort(layer.UIDs)
|
||||
layer.GIDs = make([]uint32, 0, len(gidLog))
|
||||
for gid := range gidLog {
|
||||
layer.GIDs = append(layer.GIDs, gid)
|
||||
}
|
||||
slices.Sort(layer.GIDs)
|
||||
r.applyDiffResultToLayer(layer, result)
|
||||
|
||||
err = r.saveFor(layer)
|
||||
|
||||
return size, err
|
||||
return result.size, err
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) applyDiffResultToLayer(layer *Layer, result *applyDiffResult) {
|
||||
updateDigestMap(&r.bycompressedsum, layer.CompressedDigest, result.compressedDigest, layer.ID)
|
||||
layer.CompressedDigest = result.compressedDigest
|
||||
layer.CompressedSize = result.compressedSize
|
||||
updateDigestMap(&r.byuncompressedsum, layer.UncompressedDigest, result.uncompressedDigest, layer.ID)
|
||||
layer.UncompressedDigest = result.uncompressedDigest
|
||||
layer.UncompressedSize = result.uncompressedSize
|
||||
layer.CompressionType = result.compressionType
|
||||
layer.UIDs = result.uids
|
||||
layer.GIDs = result.gids
|
||||
}
|
||||
|
||||
// Requires (startReading or?) startWriting.
|
||||
@@ -2553,7 +2757,7 @@ func (r *layerStore) DifferTarget(id string) (string, error) {
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) applyDiffFromStagingDirectory(id string, diffOutput *drivers.DriverWithDifferOutput, options *drivers.ApplyDiffWithDifferOpts) error {
|
||||
func (r *layerStore) applyDiffFromStagingDirectory(id string, diffOutput *drivers.DriverWithDifferOutput, options *drivers.ApplyDiffWithDifferOpts) (retErr error) {
|
||||
ddriver, ok := r.driver.(drivers.DriverWithDiffer)
|
||||
if !ok {
|
||||
return ErrNotSupported
|
||||
@@ -2597,10 +2801,23 @@ func (r *layerStore) applyDiffFromStagingDirectory(id string, diffOutput *driver
|
||||
}
|
||||
|
||||
if diffOutput.TarSplit != nil {
|
||||
tsdata := bytes.Buffer{}
|
||||
compressor, err := pgzip.NewWriterLevel(&tsdata, pgzip.BestSpeed)
|
||||
tarSplitFile, err := createTarSplitFile(r, layer.ID)
|
||||
if err != nil {
|
||||
compressor = pgzip.NewWriter(&tsdata)
|
||||
return err
|
||||
}
|
||||
// make sure to check for errors on close and return that one.
|
||||
defer func() {
|
||||
closeErr := tarSplitFile.Close()
|
||||
if retErr == nil {
|
||||
retErr = closeErr
|
||||
}
|
||||
}()
|
||||
tarSplitWriter := pools.BufioWriter32KPool.Get(tarSplitFile)
|
||||
defer pools.BufioWriter32KPool.Put(tarSplitWriter)
|
||||
|
||||
compressor, err := pgzip.NewWriterLevel(tarSplitWriter, pgzip.BestSpeed)
|
||||
if err != nil {
|
||||
compressor = pgzip.NewWriter(tarSplitWriter)
|
||||
}
|
||||
if _, err := diffOutput.TarSplit.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
@@ -2614,11 +2831,12 @@ func (r *layerStore) applyDiffFromStagingDirectory(id string, diffOutput *driver
|
||||
return err
|
||||
}
|
||||
compressor.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(r.tspath(layer.ID)), 0o700); err != nil {
|
||||
return err
|
||||
|
||||
if err := tarSplitWriter.Flush(); err != nil {
|
||||
return fmt.Errorf("failed to flush tar-split writer buffer: %w", err)
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(r.tspath(layer.ID), tsdata.Bytes(), 0o600); err != nil {
|
||||
return err
|
||||
if err := tarSplitFile.Sync(); err != nil {
|
||||
return fmt.Errorf("sync tar-split file: %w", err)
|
||||
}
|
||||
}
|
||||
for k, v := range diffOutput.BigData {
|
||||
|
||||
5
vendor/go.podman.io/storage/pkg/lockfile/lockfile.go
generated
vendored
5
vendor/go.podman.io/storage/pkg/lockfile/lockfile.go
generated
vendored
@@ -420,7 +420,10 @@ func (l *LockFile) tryLock(lType rawfilelock.LockType) error {
|
||||
if !success {
|
||||
return fmt.Errorf("resource temporarily unavailable")
|
||||
}
|
||||
l.stateMutex.Lock()
|
||||
if !l.stateMutex.TryLock() {
|
||||
rwMutexUnlocker()
|
||||
return fmt.Errorf("resource temporarily unavailable")
|
||||
}
|
||||
defer l.stateMutex.Unlock()
|
||||
if l.counter == 0 {
|
||||
// If we're the first reference on the lock, we need to open the file again.
|
||||
|
||||
175
vendor/go.podman.io/storage/store.go
generated
vendored
175
vendor/go.podman.io/storage/store.go
generated
vendored
@@ -1449,12 +1449,48 @@ func (s *store) canUseShifting(uidmap, gidmap []idtools.IDMap) bool {
|
||||
return s.graphDriver.SupportsShifting(uidmap, gidmap)
|
||||
}
|
||||
|
||||
// On entry:
|
||||
// - rlstore must be locked for reading or writing
|
||||
// - rlstores MUST NOT be locked
|
||||
// Returns an extra unlock function to unlock any potentially read locked rlstores by this function.
|
||||
// The unlock function is always set and thus must always be called.
|
||||
func getParentLayer(rlstore roLayerStore, rlstores []roLayerStore, parent string) (*Layer, func(), error) {
|
||||
// function we return to the caller so the caller gets the right stores locked and can unlock at the proper time themselves
|
||||
var lockedLayerStores []roLayerStore
|
||||
unlock := func() {
|
||||
for _, i := range lockedLayerStores {
|
||||
i.stopReading()
|
||||
}
|
||||
}
|
||||
for _, l := range append([]roLayerStore{rlstore}, rlstores...) {
|
||||
lstore := l
|
||||
if lstore != rlstore {
|
||||
if err := lstore.startReading(); err != nil {
|
||||
return nil, unlock, err
|
||||
}
|
||||
lockedLayerStores = append(lockedLayerStores, lstore)
|
||||
}
|
||||
if l, err := lstore.Get(parent); err == nil && l != nil {
|
||||
return l, unlock, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, unlock, ErrLayerUnknown
|
||||
}
|
||||
|
||||
// On entry:
|
||||
// - rlstore must be locked for writing
|
||||
// - rlstores MUST NOT be locked
|
||||
func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader, slo *stagedLayerOptions) (*Layer, int64, error) {
|
||||
//
|
||||
// Returns the new copied LayerOptions with mappings set, the parent Layer and
|
||||
// an extra unlock function to unlock any potentially read locked rlstores by this function.
|
||||
// The unlock function is always set and thus must always be called.
|
||||
func populateLayerOptions(s *store, rlstore rwLayerStore, rlstores []roLayerStore, parent string, lOptions *LayerOptions) (*LayerOptions, *Layer, func(), error) {
|
||||
// WARNING: Update also the freshLayer checks in store.PutLayer if adding more logic here.
|
||||
var parentLayer *Layer
|
||||
var options LayerOptions
|
||||
// make sure we always return a valid func instead of nil so the caller can call it without checking
|
||||
unlock := func() {}
|
||||
if lOptions != nil {
|
||||
options = *lOptions
|
||||
options.BigData = slices.Clone(lOptions.BigData)
|
||||
@@ -1469,53 +1505,32 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare
|
||||
uidMap := options.UIDMap
|
||||
gidMap := options.GIDMap
|
||||
if parent != "" {
|
||||
var ilayer *Layer
|
||||
for _, l := range append([]roLayerStore{rlstore}, rlstores...) {
|
||||
lstore := l
|
||||
if lstore != rlstore {
|
||||
if err := lstore.startReading(); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
defer lstore.stopReading()
|
||||
}
|
||||
if l, err := lstore.Get(parent); err == nil && l != nil {
|
||||
ilayer = l
|
||||
parent = ilayer.ID
|
||||
break
|
||||
}
|
||||
var err error
|
||||
parentLayer, unlock, err = getParentLayer(rlstore, rlstores, parent)
|
||||
if err != nil {
|
||||
return nil, nil, unlock, err
|
||||
}
|
||||
if ilayer == nil {
|
||||
return nil, -1, ErrLayerUnknown
|
||||
}
|
||||
parentLayer = ilayer
|
||||
|
||||
if err := s.containerStore.startWriting(); err != nil {
|
||||
return nil, -1, err
|
||||
return nil, nil, unlock, err
|
||||
}
|
||||
defer s.containerStore.stopWriting()
|
||||
containers, err := s.containerStore.Containers()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
return nil, nil, unlock, err
|
||||
}
|
||||
for _, container := range containers {
|
||||
if container.LayerID == parent {
|
||||
return nil, -1, ErrParentIsContainer
|
||||
return nil, nil, unlock, ErrParentIsContainer
|
||||
}
|
||||
}
|
||||
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
|
||||
uidMap = ilayer.UIDMap
|
||||
uidMap = parentLayer.UIDMap
|
||||
}
|
||||
if !options.HostGIDMapping && len(options.GIDMap) == 0 {
|
||||
gidMap = ilayer.GIDMap
|
||||
gidMap = parentLayer.GIDMap
|
||||
}
|
||||
} else {
|
||||
// FIXME? It’s unclear why we are holding containerStore locked here at all
|
||||
// (and because we are not modifying it, why it is a write lock, not a read lock).
|
||||
if err := s.containerStore.startWriting(); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
defer s.containerStore.stopWriting()
|
||||
|
||||
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
|
||||
uidMap = s.uidMap
|
||||
}
|
||||
@@ -1533,7 +1548,7 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare
|
||||
GIDMap: copySlicePreferringNil(gidMap),
|
||||
}
|
||||
}
|
||||
return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, diff, slo)
|
||||
return &options, parentLayer, unlock, nil
|
||||
}
|
||||
|
||||
func (s *store) PutLayer(id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader) (*Layer, int64, error) {
|
||||
@@ -1541,11 +1556,92 @@ func (s *store) PutLayer(id, parent string, names []string, mountLabel string, w
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
var (
|
||||
contents *layerCreationContents
|
||||
options *LayerOptions
|
||||
parentLayer *Layer
|
||||
)
|
||||
|
||||
if diff != nil {
|
||||
m := rlstore.newMaybeStagedLayerExtraction(diff)
|
||||
defer func() {
|
||||
if err := m.cleanup(); err != nil {
|
||||
logrus.Errorf("Error cleaning up temporary directories: %v", err)
|
||||
}
|
||||
}()
|
||||
// driver can do unlocked staging so do that without holding the layer lock
|
||||
// Special case we only support it when no mount label is used. c/image doesn't set it for layers
|
||||
// and the overlay driver doesn't use it for extract today so it would be safe even when set but
|
||||
// that is not exactly obvious and if someone would implement the ApplyDiffStaging interface for
|
||||
// another driver that may be no longer true. So for now simply fall back to the locked extract path
|
||||
// to ensure we don't cause any weird issues here.
|
||||
if m.staging != nil && mountLabel == "" {
|
||||
// func so we have a scope for defer, we don't want to hold the lock for stageWithUnlockedStore()
|
||||
layer, err := func() (*Layer, error) {
|
||||
if err := rlstore.startWriting(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rlstore.stopWriting()
|
||||
|
||||
if layer, err := rlstore.checkIdOrNameConflict(id, names); err != nil {
|
||||
return layer, err
|
||||
}
|
||||
|
||||
var unlockLayerStores func()
|
||||
options, parentLayer, unlockLayerStores, err = populateLayerOptions(s, rlstore, rlstores, parent, lOptions)
|
||||
unlockLayerStores()
|
||||
return nil, err
|
||||
}()
|
||||
if err != nil {
|
||||
return layer, -1, err
|
||||
}
|
||||
|
||||
// make sure to use the resolved full ID if there is a parent
|
||||
if parentLayer != nil {
|
||||
parent = parentLayer.ID
|
||||
}
|
||||
|
||||
if err := rlstore.stageWithUnlockedStore(m, parent, options); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
contents = &layerCreationContents{
|
||||
stagedLayerExtraction: m,
|
||||
}
|
||||
}
|
||||
|
||||
if err := rlstore.startWriting(); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
defer rlstore.stopWriting()
|
||||
return s.putLayer(rlstore, rlstores, id, parent, names, mountLabel, writeable, lOptions, diff, nil)
|
||||
|
||||
if options == nil {
|
||||
var unlockLayerStores func()
|
||||
options, parentLayer, unlockLayerStores, err = populateLayerOptions(s, rlstore, rlstores, parent, lOptions)
|
||||
defer unlockLayerStores()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
} else if parent != "" {
|
||||
// We used the staged extraction without holding the lock.
|
||||
// Check again that the parent layer is still valid and exists.
|
||||
freshLayer, unlockLayerStores, err := getParentLayer(rlstore, rlstores, parent)
|
||||
defer unlockLayerStores()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
// In populateLayerOptions() we get the ID mappings in order to extract correctly, ensure the freshly
|
||||
// looked up parent Layer still has the same mappings to prevent silent UID/GID corruption.
|
||||
if !slices.Equal(freshLayer.UIDMap, parentLayer.UIDMap) || !slices.Equal(freshLayer.GIDMap, parentLayer.GIDMap) {
|
||||
// Fatal problem. Mappings changed so the parent must be considered different now.
|
||||
// Since we consumed the diff there is no we to recover, return error to caller. The caller would need to retry.
|
||||
// How likely is that and would need to return a special error so c/image could do the retries?
|
||||
return nil, -1, fmt.Errorf("error during staged layer apply, parent layer %q changed id mappings while the content was extracted, must retry layer creation", parent)
|
||||
}
|
||||
}
|
||||
return rlstore.create(id, parentLayer, names, mountLabel, nil, options, writeable, contents)
|
||||
}
|
||||
|
||||
func (s *store) CreateLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions) (*Layer, error) {
|
||||
@@ -1753,7 +1849,7 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlst
|
||||
}
|
||||
}
|
||||
layerOptions.TemplateLayer = layer.ID
|
||||
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, nil)
|
||||
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating an ID-mapped copy of layer %q: %w", layer.ID, err)
|
||||
}
|
||||
@@ -1924,7 +2020,7 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
|
||||
options.Flags[mountLabelFlag] = mountLabel
|
||||
}
|
||||
|
||||
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil, nil)
|
||||
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3182,11 +3278,16 @@ func (s *store) ApplyStagedLayer(args ApplyStagedLayerOptions) (*Layer, error) {
|
||||
|
||||
// if the layer doesn't exist yet, try to create it.
|
||||
|
||||
slo := stagedLayerOptions{
|
||||
contents := layerCreationContents{
|
||||
DiffOutput: args.DiffOutput,
|
||||
DiffOptions: args.DiffOptions,
|
||||
}
|
||||
layer, _, err = s.putLayer(rlstore, rlstores, args.ID, args.ParentLayer, args.Names, args.MountLabel, args.Writeable, args.LayerOptions, nil, &slo)
|
||||
options, parentLayer, unlockLayerStores, err := populateLayerOptions(s, rlstore, rlstores, args.ParentLayer, args.LayerOptions)
|
||||
defer unlockLayerStores()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layer, _, err = rlstore.create(args.ID, parentLayer, args.Names, args.MountLabel, nil, options, args.Writeable, &contents)
|
||||
return layer, err
|
||||
}
|
||||
|
||||
|
||||
2
vendor/go.podman.io/storage/userns.go
generated
vendored
2
vendor/go.podman.io/storage/userns.go
generated
vendored
@@ -197,7 +197,7 @@ outer:
|
||||
|
||||
// We need to create a temporary layer so we can mount it and lookup the
|
||||
// maximum IDs used.
|
||||
clayer, _, err := rlstore.create("", topLayer, nil, "", nil, layerOptions, false, nil, nil)
|
||||
clayer, _, err := rlstore.create("", topLayer, nil, "", nil, layerOptions, false, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
14
vendor/modules.txt
vendored
14
vendor/modules.txt
vendored
@@ -475,7 +475,7 @@ github.com/moby/moby/api/types/storage
|
||||
github.com/moby/moby/api/types/swarm
|
||||
github.com/moby/moby/api/types/system
|
||||
github.com/moby/moby/api/types/volume
|
||||
# github.com/moby/moby/client v0.1.0
|
||||
# github.com/moby/moby/client v0.2.1
|
||||
## explicit; go 1.24.0
|
||||
github.com/moby/moby/client
|
||||
github.com/moby/moby/client/internal
|
||||
@@ -575,12 +575,12 @@ github.com/opencontainers/go-digest
|
||||
## explicit; go 1.18
|
||||
github.com/opencontainers/image-spec/specs-go
|
||||
github.com/opencontainers/image-spec/specs-go/v1
|
||||
# github.com/opencontainers/runc v1.3.3
|
||||
## explicit; go 1.23.0
|
||||
# github.com/opencontainers/runc v1.4.0
|
||||
## explicit; go 1.24.0
|
||||
github.com/opencontainers/runc/internal/linux
|
||||
github.com/opencontainers/runc/internal/pathrs
|
||||
github.com/opencontainers/runc/libcontainer/apparmor
|
||||
github.com/opencontainers/runc/libcontainer/devices
|
||||
github.com/opencontainers/runc/libcontainer/utils
|
||||
# github.com/opencontainers/runtime-spec v1.3.0
|
||||
## explicit
|
||||
github.com/opencontainers/runtime-spec/specs-go
|
||||
@@ -773,7 +773,7 @@ go.opentelemetry.io/otel/trace
|
||||
go.opentelemetry.io/otel/trace/embedded
|
||||
go.opentelemetry.io/otel/trace/internal/telemetry
|
||||
go.opentelemetry.io/otel/trace/noop
|
||||
# go.podman.io/common v0.66.1-0.20251126122123-4fc82df3fdc0
|
||||
# go.podman.io/common v0.66.1-0.20251128185259-94e31d2e45ba
|
||||
## explicit; go 1.24.6
|
||||
go.podman.io/common/internal
|
||||
go.podman.io/common/internal/attributedstring
|
||||
@@ -843,7 +843,7 @@ go.podman.io/common/pkg/umask
|
||||
go.podman.io/common/pkg/util
|
||||
go.podman.io/common/pkg/version
|
||||
go.podman.io/common/version
|
||||
# go.podman.io/image/v5 v5.38.1-0.20251126122123-4fc82df3fdc0
|
||||
# go.podman.io/image/v5 v5.38.1-0.20251128185259-94e31d2e45ba
|
||||
## explicit; go 1.24.6
|
||||
go.podman.io/image/v5/copy
|
||||
go.podman.io/image/v5/directory
|
||||
@@ -917,7 +917,7 @@ go.podman.io/image/v5/transports
|
||||
go.podman.io/image/v5/transports/alltransports
|
||||
go.podman.io/image/v5/types
|
||||
go.podman.io/image/v5/version
|
||||
# go.podman.io/storage v1.61.1-0.20251125064110-c4e25180a61d
|
||||
# go.podman.io/storage v1.61.1-0.20251128185259-94e31d2e45ba
|
||||
## explicit; go 1.24.0
|
||||
go.podman.io/storage
|
||||
go.podman.io/storage/drivers
|
||||
|
||||
Reference in New Issue
Block a user