Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6512130 authored by David Anderson's avatar David Anderson
Browse files

Introduce inotify-based replacements for fs_mgr_wait_for_file.

Bug: 134966533
Test: fs_mgr_unit_test gtest
Change-Id: I36802b87cec59b5277267eb919851ca390fea425
parent bc420c47
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ cc_library {
    export_include_dirs: ["include"],
    include_dirs: ["system/vold"],
    srcs: [
        "file_wait.cpp",
        "fs_mgr.cpp",
        "fs_mgr_format.cpp",
        "fs_mgr_verity.cpp",

fs_mgr/file_wait.cpp

0 → 100644
+235 −0
Original line number Diff line number Diff line
// Copyright (C) 2019 The Android Open Source Project
//
// 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.

#include <fs_mgr/file_wait.h>

#include <limits.h>
#if defined(__linux__)
#include <poll.h>
#include <sys/inotify.h>
#endif
#if defined(WIN32)
#include <io.h>
#else
#include <unistd.h>
#endif

#include <functional>
#include <thread>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>

namespace android {
namespace fs_mgr {

using namespace std::literals;
using android::base::unique_fd;

bool PollForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) {
    auto start_time = std::chrono::steady_clock::now();

    while (true) {
        if (!access(path.c_str(), F_OK) || errno != ENOENT) return true;

        std::this_thread::sleep_for(50ms);

        auto now = std::chrono::steady_clock::now();
        auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
        if (time_elapsed > relative_timeout) return false;
    }
}

bool PollForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) {
    auto start_time = std::chrono::steady_clock::now();

    while (true) {
        if (access(path.c_str(), F_OK) && errno == ENOENT) return true;

        std::this_thread::sleep_for(50ms);

        auto now = std::chrono::steady_clock::now();
        auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
        if (time_elapsed > relative_timeout) return false;
    }
}

#if defined(__linux__)
class OneShotInotify {
  public:
    OneShotInotify(const std::string& path, uint32_t mask,
                   const std::chrono::milliseconds relative_timeout);

    bool Wait();

  private:
    bool CheckCompleted();
    int64_t RemainingMs() const;
    bool ConsumeEvents();

    enum class Result { Success, Timeout, Error };
    Result WaitImpl();

    unique_fd inotify_fd_;
    std::string path_;
    uint32_t mask_;
    std::chrono::time_point<std::chrono::steady_clock> start_time_;
    std::chrono::milliseconds relative_timeout_;
    bool finished_;
};

OneShotInotify::OneShotInotify(const std::string& path, uint32_t mask,
                               const std::chrono::milliseconds relative_timeout)
    : path_(path),
      mask_(mask),
      start_time_(std::chrono::steady_clock::now()),
      relative_timeout_(relative_timeout),
      finished_(false) {
    // If the condition is already met, don't bother creating an inotify.
    if (CheckCompleted()) return;

    unique_fd inotify_fd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK));
    if (inotify_fd < 0) {
        PLOG(ERROR) << "inotify_init1 failed";
        return;
    }

    std::string watch_path;
    if (mask == IN_CREATE) {
        watch_path = android::base::Dirname(path);
    } else {
        watch_path = path;
    }
    if (inotify_add_watch(inotify_fd, watch_path.c_str(), mask) < 0) {
        PLOG(ERROR) << "inotify_add_watch failed";
        return;
    }

    // It's possible the condition was met before the add_watch. Check for
    // this and abort early if so.
    if (CheckCompleted()) return;

    inotify_fd_ = std::move(inotify_fd);
}

bool OneShotInotify::Wait() {
    Result result = WaitImpl();
    if (result == Result::Success) return true;
    if (result == Result::Timeout) return false;

    // Some kind of error with inotify occurred, so fallback to a poll.
    std::chrono::milliseconds timeout(RemainingMs());
    if (mask_ == IN_CREATE) {
        return PollForFile(path_, timeout);
    } else if (mask_ == IN_DELETE_SELF) {
        return PollForFileDeleted(path_, timeout);
    } else {
        LOG(ERROR) << "Unknown inotify mask: " << mask_;
        return false;
    }
}

OneShotInotify::Result OneShotInotify::WaitImpl() {
    // If the operation completed super early, we'll never have created an
    // inotify instance.
    if (finished_) return Result::Success;
    if (inotify_fd_ < 0) return Result::Error;

    while (true) {
        auto remaining_ms = RemainingMs();
        if (remaining_ms <= 0) return Result::Timeout;

        struct pollfd event = {
                .fd = inotify_fd_,
                .events = POLLIN,
                .revents = 0,
        };
        int rv = poll(&event, 1, static_cast<int>(remaining_ms));
        if (rv <= 0) {
            if (rv == 0 || errno == EINTR) {
                continue;
            }
            PLOG(ERROR) << "poll for inotify failed";
            return Result::Error;
        }
        if (event.revents & POLLERR) {
            LOG(ERROR) << "error reading inotify for " << path_;
            return Result::Error;
        }

        // Note that we don't bother checking what kind of event it is, since
        // it's cheap enough to just see if the initial condition is satisified.
        // If it's not, we consume all the events available and continue.
        if (CheckCompleted()) return Result::Success;
        if (!ConsumeEvents()) return Result::Error;
    }
}

bool OneShotInotify::CheckCompleted() {
    if (mask_ == IN_CREATE) {
        finished_ = !access(path_.c_str(), F_OK) || errno != ENOENT;
    } else if (mask_ == IN_DELETE_SELF) {
        finished_ = access(path_.c_str(), F_OK) && errno == ENOENT;
    } else {
        LOG(ERROR) << "Unexpected mask: " << mask_;
    }
    return finished_;
}

bool OneShotInotify::ConsumeEvents() {
    // According to the manpage, this is enough to read at least one event.
    static constexpr size_t kBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1;
    char buffer[kBufferSize];

    do {
        ssize_t rv = TEMP_FAILURE_RETRY(read(inotify_fd_, buffer, sizeof(buffer)));
        if (rv <= 0) {
            if (rv == 0 || errno == EAGAIN) {
                return true;
            }
            PLOG(ERROR) << "read inotify failed";
            return false;
        }
    } while (true);
}

int64_t OneShotInotify::RemainingMs() const {
    auto remaining = (std::chrono::steady_clock::now() - start_time_);
    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(remaining);
    return (relative_timeout_ - elapsed).count();
}
#endif

bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) {
#if defined(__linux__)
    OneShotInotify inotify(path, IN_CREATE, relative_timeout);
    return inotify.Wait();
#else
    return PollForFile(path, relative_timeout);
#endif
}

// Wait at most |relative_timeout| milliseconds for |path| to stop existing.
bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) {
#if defined(__linux__)
    OneShotInotify inotify(path, IN_DELETE_SELF, relative_timeout);
    return inotify.Wait();
#else
    return PollForFileDeleted(path, relative_timeout);
#endif
}

}  // namespace fs_mgr
}  // namespace android
+34 −0
Original line number Diff line number Diff line
// Copyright (C) 2019 The Android Open Source Project
//
// 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.

#pragma once

#include <chrono>
#include <string>

namespace android {
namespace fs_mgr {

// Wait at most |relative_timeout| milliseconds for |path| to exist. dirname(path)
// must already exist. For example, to wait on /dev/block/dm-6, /dev/block must
// be a valid directory.
bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout);

// Wait at most |relative_timeout| milliseconds for |path| to stop existing.
// Note that this only returns true if the inode itself no longer exists, i.e.,
// all outstanding file descriptors have been closed.
bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout);

}  // namespace fs_mgr
}  // namespace android
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ cc_test {
        "libfstab",
    ],
    srcs: [
        "file_wait_test.cpp",
        "fs_mgr_test.cpp",
    ],

+91 −0
Original line number Diff line number Diff line
// Copyright (C) 2019 The Android Open Source Project
//
// 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.

#include <chrono>
#include <string>
#include <thread>

#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <fs_mgr/file_wait.h>
#include <gtest/gtest.h>

using namespace std::literals;
using android::base::unique_fd;
using android::fs_mgr::WaitForFile;
using android::fs_mgr::WaitForFileDeleted;

class FileWaitTest : public ::testing::Test {
  protected:
    void SetUp() override {
        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
        test_file_ = temp_dir_.path + "/"s + tinfo->name();
    }

    void TearDown() override { unlink(test_file_.c_str()); }

    TemporaryDir temp_dir_;
    std::string test_file_;
};

TEST_F(FileWaitTest, FileExists) {
    unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700));
    ASSERT_GE(fd, 0);

    ASSERT_TRUE(WaitForFile(test_file_, 500ms));
    ASSERT_FALSE(WaitForFileDeleted(test_file_, 500ms));
}

TEST_F(FileWaitTest, FileDoesNotExist) {
    ASSERT_FALSE(WaitForFile(test_file_, 500ms));
    ASSERT_TRUE(WaitForFileDeleted(test_file_, 500ms));
}

TEST_F(FileWaitTest, CreateAsync) {
    std::thread thread([this] {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700));
    });
    EXPECT_TRUE(WaitForFile(test_file_, 3s));
    thread.join();
}

TEST_F(FileWaitTest, CreateOtherAsync) {
    std::thread thread([this] {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700));
    });
    EXPECT_FALSE(WaitForFile(test_file_ + ".wontexist", 2s));
    thread.join();
}

TEST_F(FileWaitTest, DeleteAsync) {
    // Note: need to close the file, otherwise inotify considers it not deleted.
    {
        unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700));
        ASSERT_GE(fd, 0);
    }

    std::thread thread([this] {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        unlink(test_file_.c_str());
    });
    EXPECT_TRUE(WaitForFileDeleted(test_file_, 3s));
    thread.join();
}

TEST_F(FileWaitTest, BadPath) {
    ASSERT_FALSE(WaitForFile("/this/path/does/not/exist", 5ms));
    EXPECT_EQ(errno, ENOENT);
}