Implement Registry.FindOrCreate.

FindOrCreate implements the behaviour of mq_open(2).

Updates #136
This commit is contained in:
Zyad A. Ali
2021-09-28 20:43:52 +02:00
parent 13d36561b8
commit 7df562d473
3 changed files with 167 additions and 25 deletions
+12 -12
View File
@@ -81,7 +81,7 @@ func NewRegistryImpl(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *
}
// Get implements mq.RegistryImpl.Get.
func (r *RegistryImpl) Get(ctx context.Context, name string, rOnly, wOnly, readWrite, block bool, flags uint32) (*vfs.FileDescription, bool, error) {
func (r *RegistryImpl) Get(ctx context.Context, name string, access mq.AccessType, block bool, flags uint32) (*vfs.FileDescription, bool, error) {
r.mu.Lock()
defer r.mu.Unlock()
@@ -91,13 +91,13 @@ func (r *RegistryImpl) Get(ctx context.Context, name string, rOnly, wOnly, readW
}
qInode := inode.(*queueInode)
if !qInode.queue.HasPermissions(auth.CredentialsFromContext(ctx), perm(rOnly, wOnly, readWrite)) {
if !qInode.queue.HasPermissions(auth.CredentialsFromContext(ctx), perm(access)) {
// "The queue exists, but the caller does not have permission to
// open it in the specified mode."
return nil, false, linuxerr.EACCES
}
fd, err := r.newFD(qInode.queue, qInode, rOnly, wOnly, readWrite, block, flags)
fd, err := r.newFD(qInode.queue, qInode, access, block, flags)
if err != nil {
return nil, false, err
}
@@ -105,7 +105,7 @@ func (r *RegistryImpl) Get(ctx context.Context, name string, rOnly, wOnly, readW
}
// New implements mq.RegistryImpl.New.
func (r *RegistryImpl) New(ctx context.Context, name string, q *mq.Queue, rOnly, wOnly, readWrite, block bool, perm linux.FileMode, flags uint32) (*vfs.FileDescription, error) {
func (r *RegistryImpl) New(ctx context.Context, name string, q *mq.Queue, access mq.AccessType, block bool, perm linux.FileMode, flags uint32) (*vfs.FileDescription, error) {
r.mu.Lock()
defer r.mu.Unlock()
@@ -115,7 +115,7 @@ func (r *RegistryImpl) New(ctx context.Context, name string, q *mq.Queue, rOnly,
if err != nil {
return nil, err
}
return r.newFD(q, qInode, rOnly, wOnly, readWrite, block, flags)
return r.newFD(q, qInode, access, block, flags)
}
// Unlink implements mq.RegistryImpl.Unlink.
@@ -154,8 +154,8 @@ func (r *RegistryImpl) lookup(ctx context.Context, name string) (kernfs.Inode, e
}
// newFD returns a new file description created using the given queue and inode.
func (r *RegistryImpl) newFD(q *mq.Queue, inode *queueInode, rOnly, wOnly, readWrite, block bool, flags uint32) (*vfs.FileDescription, error) {
view, err := mq.NewView(q, rOnly, wOnly, readWrite, block)
func (r *RegistryImpl) newFD(q *mq.Queue, inode *queueInode, access mq.AccessType, block bool, flags uint32) (*vfs.FileDescription, error) {
view, err := mq.NewView(q, access, block)
if err != nil {
return nil, err
}
@@ -172,13 +172,13 @@ func (r *RegistryImpl) newFD(q *mq.Queue, inode *queueInode, rOnly, wOnly, readW
}
// perm returns a permission mask created using given flags.
func perm(rOnly, wOnly, readWrite bool) fs.PermMask {
switch {
case readWrite:
func perm(access mq.AccessType) fs.PermMask {
switch access {
case mq.ReadWrite:
return fs.PermMask{Read: true, Write: true}
case wOnly:
case mq.WriteOnly:
return fs.PermMask{Write: true}
case rOnly:
case mq.ReadOnly:
return fs.PermMask{Read: true}
default:
return fs.PermMask{} // Can't happen, see NewView.
+1 -1
View File
@@ -87,7 +87,7 @@ func (i *IPCNamespace) InitPosixQueues(ctx context.Context, vfsObj *vfs.VirtualF
if err != nil {
return err
}
i.posixQueues = mq.NewRegistry(impl)
i.posixQueues = mq.NewRegistry(i.userNS, impl)
return nil
}
+154 -12
View File
@@ -18,6 +18,7 @@ package mq
import (
"bytes"
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
@@ -29,9 +30,31 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
// AccessType is the access type passed to mq_open.
type AccessType int
// Possible access types.
const (
ReadOnly AccessType = iota
WriteOnly
ReadWrite
)
const (
MaxName = 255 // Maximum size for a queue name.
maxPriority = linux.MQ_PRIO_MAX - 1 // Highest possible message priority.
maxQueuesDefault = linux.DFLT_QUEUESMAX // Default max number of queues.
maxMsgDefault = linux.DFLT_MSG // Default max number of messages per queue.
maxMsgMin = linux.MIN_MSGMAX // Min value for max number of messages per queue.
maxMsgLimit = linux.DFLT_MSGMAX // Limit for max number of messages per queue.
maxMsgHardLimit = linux.HARD_MSGMAX // Hard limit for max number of messages per queue.
msgSizeDefault = linux.DFLT_MSGSIZE // Default max message size.
msgSizeMin = linux.MIN_MSGSIZEMAX // Min value for max message size.
msgSizeLimit = linux.DFLT_MSGSIZEMAX // Limit for max message size.
msgSizeHardLimit = linux.HARD_MSGSIZEMAX // Hard limit for max message size.
)
// Registry is a POSIX message queue registry.
@@ -41,6 +64,12 @@ const (
//
// +stateify savable
type Registry struct {
// userNS is the user namespace containing this registry. Immutable.
userNS *auth.UserNamespace
// mu protects all fields below.
mu sync.Mutex `state:"nosave"`
// impl is an implementation of several message queue utilities needed by
// the registry. impl should be provided by mqfs.
impl RegistryImpl
@@ -54,13 +83,13 @@ type RegistryImpl interface {
// Get searchs for a queue with the given name, if it exists, the queue is
// used to create a new FD, return it and return true. If the queue doesn't
// exist, return false and no error. An error is returned if creation fails.
Get(ctx context.Context, name string, rOnly, wOnly, readWrite, block bool, flags uint32) (*vfs.FileDescription, bool, error)
Get(ctx context.Context, name string, access AccessType, block bool, flags uint32) (*vfs.FileDescription, bool, error)
// New creates a new inode and file description using the given queue,
// inserts the inode into the filesystem tree using the given name, and
// returns the file description. An error is returned if creation fails, or
// if the name already exists.
New(ctx context.Context, name string, q *Queue, rOnly, wOnly, readWrite, block bool, perm linux.FileMode, flags uint32) (*vfs.FileDescription, error)
New(ctx context.Context, name string, q *Queue, access AccessType, block bool, perm linux.FileMode, flags uint32) (*vfs.FileDescription, error)
// Unlink removes the queue with given name from the registry, and returns
// an error if the name doesn't exist.
@@ -73,12 +102,128 @@ type RegistryImpl interface {
// NewRegistry returns a new, initialized message queue registry. NewRegistry
// should be called when a new message queue filesystem is created, once per
// IPCNamespace.
func NewRegistry(impl RegistryImpl) *Registry {
func NewRegistry(userNS *auth.UserNamespace, impl RegistryImpl) *Registry {
return &Registry{
impl: impl,
userNS: userNS,
impl: impl,
}
}
// OpenOpts holds the options passed to FindOrCreate.
type OpenOpts struct {
Name string
Access AccessType
Create bool
Exclusive bool
Block bool
}
// FindOrCreate creates a new POSIX message queue or opens an existing queue.
// See mq_open(2).
func (r *Registry) FindOrCreate(ctx context.Context, opts OpenOpts, perm linux.FileMode, attr *linux.MqAttr) (*vfs.FileDescription, error) {
// mq_overview(7) mentions that: "Each message queue is identified by a name
// of the form '/somename'", but the mq_open(3) man pages mention:
// "The mq_open() library function is implemented on top of a system call
// of the same name. The library function performs the check that the
// name starts with a slash (/), giving the EINVAL error if it does not.
// The kernel system call expects name to contain no preceding slash, so
// the C library function passes name without the preceding slash (i.e.,
// name+1) to the system call."
// So we don't need to check it.
if len(opts.Name) == 0 {
return nil, linuxerr.ENOENT
}
if len(opts.Name) > MaxName {
return nil, linuxerr.ENAMETOOLONG
}
if strings.ContainsRune(opts.Name, '/') {
return nil, linuxerr.EACCES
}
if opts.Name == "." || opts.Name == ".." {
return nil, linuxerr.EINVAL
}
// Construct status flags.
var flags uint32
if opts.Block {
flags = linux.O_NONBLOCK
}
switch opts.Access {
case ReadOnly:
flags = flags | linux.O_RDONLY
case WriteOnly:
flags = flags | linux.O_WRONLY
case ReadWrite:
flags = flags | linux.O_RDWR
}
r.mu.Lock()
defer r.mu.Unlock()
fd, ok, err := r.impl.Get(ctx, opts.Name, opts.Access, opts.Block, flags)
if err != nil {
return nil, err
}
if ok {
if opts.Create && opts.Exclusive {
// "Both O_CREAT and O_EXCL were specified in oflag, but a queue
// with this name already exists."
return nil, linuxerr.EEXIST
}
return fd, nil
}
if !opts.Create {
// "The O_CREAT flag was not specified in oflag, and no queue with this name
// exists."
return nil, linuxerr.ENOENT
}
q, err := r.newQueueLocked(auth.CredentialsFromContext(ctx), fs.FileOwnerFromContext(ctx), fs.FilePermsFromMode(perm), attr)
if err != nil {
return nil, err
}
return r.impl.New(ctx, opts.Name, q, opts.Access, opts.Block, perm, flags)
}
// newQueueLocked creates a new queue using the given attributes. If attr is nil
// return a queue with default values, otherwise use attr to create a new queue,
// and return an error if attributes are invalid.
func (r *Registry) newQueueLocked(creds *auth.Credentials, owner fs.FileOwner, perms fs.FilePermissions, attr *linux.MqAttr) (*Queue, error) {
if attr == nil {
return &Queue{
owner: owner,
perms: perms,
maxMessageCount: int64(maxMsgDefault),
maxMessageSize: uint64(msgSizeDefault),
}, nil
}
// "O_CREAT was specified in oflag, and attr was not NULL, but
// attr->mq_maxmsg or attr->mq_msqsize was invalid. Both of these fields
// these fields must be greater than zero. In a process that is
// unprivileged (does not have the CAP_SYS_RESOURCE capability),
// attr->mq_maxmsg must be less than or equal to the msg_max limit, and
// attr->mq_msgsize must be less than or equal to the msgsize_max limit.
// In addition, even in a privileged process, attr->mq_maxmsg cannot
// exceed the HARD_MAX limit." - man mq_open(3).
if attr.MqMaxmsg <= 0 || attr.MqMsgsize <= 0 {
return nil, linuxerr.EINVAL
}
if attr.MqMaxmsg > maxMsgHardLimit || (!creds.HasCapabilityIn(linux.CAP_SYS_RESOURCE, r.userNS) && (attr.MqMaxmsg > maxMsgLimit || attr.MqMsgsize > msgSizeLimit)) {
return nil, linuxerr.EINVAL
}
return &Queue{
owner: owner,
perms: perms,
maxMessageCount: attr.MqMaxmsg,
maxMessageSize: uint64(attr.MqMsgsize),
}, nil
}
// Destroy destroys the registry and releases all held references.
func (r *Registry) Destroy(ctx context.Context) {
r.impl.Destroy(ctx)
@@ -117,9 +262,6 @@ type Queue struct {
// from this queue.
subscriber *Subscriber
// nonBlock is true if this queue is non-blocking.
nonBlock bool
// messageCount is the number of messages currently in the queue.
messageCount int64
@@ -171,13 +313,13 @@ type Writer struct {
}
// NewView creates a new view into a queue and returns it.
func NewView(q *Queue, rOnly, wOnly, readWrite, block bool) (View, error) {
switch {
case readWrite:
func NewView(q *Queue, access AccessType, block bool) (View, error) {
switch access {
case ReadWrite:
return ReaderWriter{Queue: q, block: block}, nil
case wOnly:
case WriteOnly:
return Writer{Queue: q, block: block}, nil
case rOnly:
case ReadOnly:
return Reader{Queue: q, block: block}, nil
default:
// This case can't happen, due to O_RDONLY flag being 0 and O_WRONLY