Skip to content

Commit

Permalink
volume: add new option -o o=noquota
Browse files Browse the repository at this point in the history
add a new option to completely disable xfs quota usage for a volume.

xfs quota set on a volume, even just for tracking disk usage, can
cause weird errors if the volume is later re-used by a container with
a different quota projid.  More specifically, link(2) and rename(2)
might fail with EXDEV if the source file has a projid that is
different from the parent directory.

To prevent such kind of issues, the volume should be created
beforehand with `podman volume create -o o=noquota $ID`

Closes: #14049

Signed-off-by: Giuseppe Scrivano <[email protected]>
  • Loading branch information
giuseppe committed Apr 28, 2022
1 parent 9133a6d commit 91ead15
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 18 deletions.
3 changes: 2 additions & 1 deletion docs/source/markdown/podman-volume-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ The `device` option sets the device to be mounted, and is equivalent to the `dev
The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions:

- The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**.
- The `o` option supports the `size` option to set the maximum size of the created volume and the `inodes` option to set the maximum number of inodes for the volume. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page.
- The `o` option supports the `size` option to set the maximum size of the created volume, the `inodes` option to set the maximum number of inodes for the volume and `noquota` to completely disable quota support even for tracking of disk usage. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page.
- The `o` option supports .
- Using volume options other then the UID/GID options with the **local** driver requires root privileges.

When not using the **local** driver, the given options are passed directly to the volume plugin. In this case, supported options are dictated by the plugin in question, not Podman.
Expand Down
13 changes: 13 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,19 @@ func WithVolumeNoChown() VolumeCreateOption {
}
}

// WithVolumeDisableQuota prevents the volume from being assigned a quota.
func WithVolumeDisableQuota() VolumeCreateOption {
return func(volume *Volume) error {
if volume.valid {
return define.ErrVolumeFinalized
}

volume.config.DisableQuota = true

return nil
}
}

// withSetAnon sets a bool notifying libpod that this volume is anonymous and
// should be removed when containers using it are removed and volumes are
// specified for removal.
Expand Down
39 changes: 22 additions & 17 deletions libpod/runtime_volume_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key)
}
}
case "o", "type", "uid", "gid", "size", "inodes":
case "o", "type", "uid", "gid", "size", "inodes", "noquota":
// Do nothing, valid keys
default:
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key)
Expand Down Expand Up @@ -111,23 +111,28 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
if err := LabelVolumePath(fullVolPath); err != nil {
return nil, err
}
projectQuotaSupported := false

q, err := quota.NewControl(r.config.Engine.VolumePath)
if err == nil {
projectQuotaSupported = true
}
quota := quota.Quota{}
if volume.config.Size > 0 || volume.config.Inodes > 0 {
if !projectQuotaSupported {
return nil, errors.New("Volume options size and inodes not supported. Filesystem does not support Project Quota")
if volume.config.DisableQuota {
if volume.config.Size > 0 || volume.config.Inodes > 0 {
return nil, errors.New("volume options size and inodes cannot be used without quota")
}
quota.Size = volume.config.Size
quota.Inodes = volume.config.Inodes
}
if projectQuotaSupported {
if err := q.SetQuota(fullVolPath, quota); err != nil {
return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath)
} else {
projectQuotaSupported := false
q, err := quota.NewControl(r.config.Engine.VolumePath)
if err == nil {
projectQuotaSupported = true
}
quota := quota.Quota{}
if volume.config.Size > 0 || volume.config.Inodes > 0 {
if !projectQuotaSupported {
return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota")
}
quota.Size = volume.config.Size
quota.Inodes = volume.config.Inodes
}
if projectQuotaSupported {
if err := q.SetQuota(fullVolPath, quota); err != nil {
return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath)
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions libpod/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type VolumeConfig struct {
Size uint64 `json:"size"`
// Inodes maximum of the volume.
Inodes uint64 `json:"inodes"`
// DisableQuota indicates that the volume should completely disable using any
// quota tracking.
DisableQuota bool `json:"disableQuota,omitempty"`
}

// VolumeState holds the volume's mutable state.
Expand Down
3 changes: 3 additions & 0 deletions libpod/volume_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func (v *Volume) needsMount() bool {
if _, ok := v.config.Options["SIZE"]; ok {
index++
}
if _, ok := v.config.Options["NOQUOTA"]; ok {
index++
}
// when uid or gid is set there is also the "o" option
// set so we have to ignore this one as well
if index > 0 {
Expand Down
5 changes: 5 additions & 0 deletions pkg/domain/infra/abi/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error)
finalVal = append(finalVal, o)
// set option "GID": "$gid"
volumeOptions["GID"] = splitO[1]
case "noquota":
logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota")
libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota())
// set option "NOQUOTA": "true"
volumeOptions["NOQUOTA"] = "true"
default:
finalVal = append(finalVal, o)
}
Expand Down
22 changes: 22 additions & 0 deletions test/test_podman_baseline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,28 @@ else
echo "Overlay test within limits failed"
fi

before=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#`
podman $PODMANBASE volume create -o o=noquota test-no-quota
after=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#`

if [ $before != $after ];
then
echo "Test -o=noquota doesn't create a projid failed"
else
echo "Test -o=noquota doesn't create a projid passed"
fi

before=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#`
podman $PODMANBASE volume create -o test-no-quota
after=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#`

if [ $before == $after ];
then
echo "Test without -o=noquota creates a projid failed"
else
echo "Test without -o=noquota creates a projid passed"
fi

########
# Expected to fail
########
Expand Down

0 comments on commit 91ead15

Please sign in to comment.