gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/mq.cc (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <fcntl.h>
    16  #include <mqueue.h>
    17  #include <sched.h>
    18  #include <sys/poll.h>
    19  #include <sys/stat.h>
    20  #include <unistd.h>
    21  
    22  #include <string>
    23  
    24  #include "test/util/capability_util.h"
    25  #include "test/util/cleanup.h"
    26  #include "test/util/fs_util.h"
    27  #include "test/util/mount_util.h"
    28  #include "test/util/posix_error.h"
    29  #include "test/util/temp_path.h"
    30  #include "test/util/test_util.h"
    31  
    32  #define NAME_MAX 255
    33  
    34  namespace gvisor {
    35  namespace testing {
    36  namespace {
    37  
    38  // PosixQueue is a RAII class used to automatically clean POSIX message queues.
    39  class PosixQueue {
    40   public:
    41    PosixQueue(mqd_t fd, std::string name) : fd_(fd), name_(std::string(name)) {}
    42    PosixQueue(const PosixQueue&) = delete;
    43    PosixQueue& operator=(const PosixQueue&) = delete;
    44  
    45    // Move constructor.
    46    PosixQueue(PosixQueue&& q) {
    47      fd_ = q.fd_;
    48      name_ = q.name_;
    49      // Call PosixQueue::release, to prevent the object being released from
    50      // unlinking the underlying queue.
    51      q.release();
    52    }
    53  
    54    ~PosixQueue() {
    55      if (fd_ != -1) {
    56        EXPECT_THAT(mq_close(fd_), SyscallSucceeds());
    57        EXPECT_THAT(mq_unlink(name_.c_str()), SyscallSucceeds());
    58      }
    59    }
    60  
    61    mqd_t fd() { return fd_; }
    62  
    63    const char* name() { return name_.c_str(); }
    64  
    65    mqd_t release() {
    66      mqd_t old = fd_;
    67      fd_ = -1;
    68      return old;
    69    }
    70  
    71   private:
    72    mqd_t fd_;
    73    std::string name_;
    74  };
    75  
    76  // MqOpen wraps mq_open(3) using a given name.
    77  PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag) {
    78    mqd_t fd = mq_open(name.c_str(), oflag);
    79    if (fd == -1) {
    80      return PosixError(errno, absl::StrFormat("mq_open(%s, %d)", name, oflag));
    81    }
    82    return PosixQueue(fd, name);
    83  }
    84  
    85  // MqOpen wraps mq_open(3) using a given name.
    86  PosixErrorOr<PosixQueue> MqOpen(int oflag, mode_t mode, struct mq_attr* attr) {
    87    auto name = "/" + NextTempBasename();
    88    mqd_t fd = mq_open(name.c_str(), oflag, mode, attr);
    89    if (fd == -1) {
    90      return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag));
    91    }
    92    return PosixQueue(fd, name);
    93  }
    94  
    95  // MqOpen wraps mq_open(3) using a generated name.
    96  PosixErrorOr<PosixQueue> MqOpen(std::string name, int oflag, mode_t mode,
    97                                  struct mq_attr* attr) {
    98    mqd_t fd = mq_open(name.c_str(), oflag, mode, attr);
    99    if (fd == -1) {
   100      return PosixError(errno, absl::StrFormat("mq_open(%d)", oflag));
   101    }
   102    return PosixQueue(fd, name);
   103  }
   104  
   105  // MqUnlink wraps mq_unlink(2).
   106  PosixError MqUnlink(std::string name) {
   107    int err = mq_unlink(name.c_str());
   108    if (err == -1) {
   109      return PosixError(errno, absl::StrFormat("mq_unlink(%s)", name.c_str()));
   110    }
   111    return NoError();
   112  }
   113  
   114  // MqClose wraps mq_close(2).
   115  PosixError MqClose(mqd_t fd) {
   116    int err = mq_close(fd);
   117    if (err == -1) {
   118      return PosixError(errno, absl::StrFormat("mq_close(%d)", fd));
   119    }
   120    return NoError();
   121  }
   122  
   123  // Test simple opening and closing of a message queue.
   124  TEST(MqTest, Open) {
   125    ASSERT_NO_ERRNO(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   126  }
   127  
   128  TEST(MqTest, ModeWithFileType) {
   129    // S_IFIFO should be ignored.
   130    ASSERT_NO_ERRNO(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777 | S_IFIFO, nullptr));
   131  }
   132  
   133  // Test mq_open(2) after mq_unlink(2).
   134  TEST(MqTest, OpenAfterUnlink) {
   135    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   136        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   137  
   138    ASSERT_NO_ERRNO(MqUnlink(queue.name()));
   139    EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(ENOENT));
   140    ASSERT_NO_ERRNO(MqClose(queue.release()));
   141  }
   142  
   143  // Test using invalid args with mq_open.
   144  TEST(MqTest, OpenInvalidArgs) {
   145    // Name must start with a slash.
   146    EXPECT_THAT(MqOpen("test", O_RDWR), PosixErrorIs(EINVAL));
   147  
   148    // Name can't contain more that one slash.
   149    EXPECT_THAT(MqOpen("/test/name", O_RDWR), PosixErrorIs(EACCES));
   150  
   151    // Both "." and ".." can't be used as queue names.
   152    EXPECT_THAT(MqOpen(".", O_RDWR), PosixErrorIs(EINVAL));
   153    EXPECT_THAT(MqOpen("..", O_RDWR), PosixErrorIs(EINVAL));
   154  
   155    // mq_attr's mq_maxmsg and mq_msgsize must be > 0.
   156    struct mq_attr attr;
   157    attr.mq_maxmsg = -1;
   158    attr.mq_msgsize = 10;
   159  
   160    EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr),
   161                PosixErrorIs(EINVAL));
   162  
   163    attr.mq_maxmsg = 10;
   164    attr.mq_msgsize = -1;
   165  
   166    EXPECT_THAT(MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, &attr),
   167                PosixErrorIs(EINVAL));
   168  
   169    // Names should be shorter than NAME_MAX.
   170    char max[NAME_MAX + 3];
   171    max[0] = '/';
   172    for (size_t i = 1; i < NAME_MAX + 2; i++) {
   173      max[i] = 'a';
   174    }
   175    max[NAME_MAX + 2] = '\0';
   176  
   177    EXPECT_THAT(MqOpen(std::string(max), O_RDWR | O_CREAT | O_EXCL, 0777, &attr),
   178                PosixErrorIs(ENAMETOOLONG));
   179  }
   180  
   181  // Test creating a queue that already exists.
   182  TEST(MqTest, CreateAlreadyExists) {
   183    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   184        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   185  
   186    EXPECT_THAT(MqOpen(queue.name(), O_RDWR | O_CREAT | O_EXCL, 0777, nullptr),
   187                PosixErrorIs(EEXIST));
   188  }
   189  
   190  // Test opening a queue that doesn't exists.
   191  TEST(MqTest, NoQueueExists) {
   192    // Choose a name to pass that's unlikely to exist if the test is run locally.
   193    EXPECT_THAT(MqOpen("/gvisor-mq-test-nonexistent-queue", O_RDWR),
   194                PosixErrorIs(ENOENT));
   195  }
   196  
   197  // Test trying to re-open a queue with invalid permissions.
   198  TEST(MqTest, OpenNoAccess) {
   199    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   200        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0000, nullptr));
   201  
   202    EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES));
   203    EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES));
   204    EXPECT_THAT(MqOpen(queue.name(), O_RDWR), PosixErrorIs(EACCES));
   205  }
   206  
   207  // Test trying to re-open a read-only queue for write.
   208  TEST(MqTest, OpenReadAccess) {
   209    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   210        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0400, nullptr));
   211  
   212    EXPECT_THAT(MqOpen(queue.name(), O_WRONLY), PosixErrorIs(EACCES));
   213    EXPECT_NO_ERRNO(MqOpen(queue.name(), O_RDONLY));
   214    queue.release();
   215  }
   216  
   217  // Test trying to re-open a write-only queue for read.
   218  TEST(MqTest, OpenWriteAccess) {
   219    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   220        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0200, nullptr));
   221  
   222    EXPECT_THAT(MqOpen(queue.name(), O_RDONLY), PosixErrorIs(EACCES));
   223    EXPECT_NO_ERRNO(MqOpen(queue.name(), O_WRONLY));
   224    queue.release();
   225  }
   226  
   227  // Test changing IPC namespace.
   228  TEST(MqTest, ChangeIpcNamespace) {
   229    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   230  
   231    // When changing IPC namespaces, Linux doesn't invalidate or close the
   232    // previously opened file descriptions and allows operations to be performed
   233    // on them normally, until they're closed.
   234    //
   235    // To test this we create a new queue, use unshare(CLONE_NEWIPC) to change
   236    // into a new IPC namespace, and trying performing a read(2) on the queue.
   237    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   238        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   239  
   240    // As mq_unlink(2) uses queue's name, it should fail after changing IPC
   241    // namespace. To clean the queue, we should unlink it now, this should not
   242    // cause a problem, as the queue presists until the last mq_close(2).
   243    ASSERT_NO_ERRNO(MqUnlink(queue.name()));
   244  
   245    ASSERT_THAT(unshare(CLONE_NEWIPC), SyscallSucceeds());
   246  
   247    const size_t msgSize = 60;
   248    char queueRead[msgSize];
   249    ASSERT_THAT(read(queue.fd(), &queueRead[0], msgSize - 1), SyscallSucceeds());
   250  
   251    ASSERT_NO_ERRNO(MqClose(queue.release()));
   252  
   253    // Unlinking should fail now after changing IPC namespace.
   254    EXPECT_THAT(MqUnlink(queue.name()), PosixErrorIs(ENOENT));
   255  }
   256  
   257  // Test mounting the mqueue filesystem.
   258  TEST(MqTest, Mount) {
   259    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   260  
   261    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   262        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   263  
   264    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   265    ASSERT_NO_ERRNO(Mount("none", dir.path(), "mqueue", 0, "", 0));
   266  }
   267  
   268  // Test mounting the mqueue filesystem to several places.
   269  TEST(MqTest, MountSeveral) {
   270    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   271    constexpr int numMounts = 3;
   272  
   273    // mountDirs should outlive mountCUs and queue so that its destructor succeeds
   274    // in unlinking the mountpoints and does not interfere with queue destruction.
   275    testing::TempPath mountDirs[numMounts];
   276    testing::Cleanup mountCUs[numMounts];
   277    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   278        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   279  
   280    for (int i = 0; i < numMounts; ++i) {
   281      mountDirs[i] = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   282      mountCUs[i] = ASSERT_NO_ERRNO_AND_VALUE(
   283          Mount("none", mountDirs[i].path(), "mqueue", 0, "", 0));
   284    }
   285  
   286    // Ensure that queue is visible from all mounts.
   287    for (int i = 0; i < numMounts; ++i) {
   288      ASSERT_NO_ERRNO(Stat(JoinPath(mountDirs[i].path(), queue.name())));
   289    }
   290  }
   291  
   292  // Test mounting mqueue and opening a queue as normal file.
   293  TEST(MqTest, OpenAsFile) {
   294    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   295  
   296    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   297        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   298  
   299    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   300    auto mnt =
   301        ASSERT_NO_ERRNO_AND_VALUE(Mount("none", dir.path(), "mqueue", 0, "", 0));
   302  
   303    // Open queue using open(2).
   304    auto fd = ASSERT_NO_ERRNO_AND_VALUE(
   305        Open(JoinPath(dir.path(), queue.name()), O_RDONLY));
   306  
   307    const size_t msgSize = 60;
   308    char queueRead[msgSize];
   309    queueRead[msgSize - 1] = '\0';
   310  
   311    ASSERT_THAT(read(fd.get(), &queueRead[0], msgSize - 1), SyscallSucceeds());
   312  
   313    std::string want(
   314        "QSIZE:0          NOTIFY:0     SIGNO:0     NOTIFY_PID:0     ");
   315    std::string got(queueRead);
   316    EXPECT_EQ(got, want);
   317  }
   318  
   319  // Test removing a queue using unlink(2).
   320  TEST(MqTest, UnlinkAsFile) {
   321    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   322  
   323    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   324        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   325  
   326    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   327    auto mnt =
   328        ASSERT_NO_ERRNO_AND_VALUE(Mount("none", dir.path(), "mqueue", 0, "", 0));
   329  
   330    ASSERT_NO_ERRNO(
   331        UnlinkAt(FileDescriptor(), JoinPath(dir.path(), queue.name()), 0));
   332  
   333    // Trying to unlink again should fail.
   334    EXPECT_THAT(MqUnlink(queue.name()), PosixErrorIs(ENOENT));
   335    queue.release();
   336  }
   337  
   338  // Test read(2) from an empty queue.
   339  TEST(MqTest, ReadEmpty) {
   340    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   341        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   342  
   343    const size_t msgSize = 60;
   344    char queueRead[msgSize];
   345    queueRead[msgSize - 1] = '\0';
   346  
   347    ASSERT_THAT(read(queue.fd(), &queueRead[0], msgSize - 1), SyscallSucceeds());
   348  
   349    std::string want(
   350        "QSIZE:0          NOTIFY:0     SIGNO:0     NOTIFY_PID:0     ");
   351    std::string got(queueRead);
   352    EXPECT_EQ(got, want);
   353  }
   354  
   355  // Test poll(2) on an empty queue.
   356  TEST(MqTest, PollEmpty) {
   357    PosixQueue queue = ASSERT_NO_ERRNO_AND_VALUE(
   358        MqOpen(O_RDWR | O_CREAT | O_EXCL, 0777, nullptr));
   359  
   360    struct pollfd pfd;
   361    pfd.fd = queue.fd();
   362    pfd.events = POLLOUT | POLLIN | POLLRDNORM | POLLWRNORM;
   363  
   364    ASSERT_THAT(poll(&pfd, 1, -1), SyscallSucceeds());
   365    ASSERT_EQ(pfd.revents, POLLOUT | POLLWRNORM);
   366  }
   367  
   368  }  // namespace
   369  }  // namespace testing
   370  }  // namespace gvisor