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

Commit e4344cdb authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "GD-storage: Add files library"

parents a7809aa4 02b90971
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -299,6 +299,7 @@ cc_test {
        "libbluetooth-protos",
        "libbluetooth_gd",
        "libgmock",
        "libc++fs",
    ],
    shared_libs: [
        "libchrome",
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ filegroup {
    name: "BluetoothOsSources_linux_generic",
    srcs: [
        "linux_generic/alarm.cc",
        "linux_generic/files.cc",
        "linux_generic/handler.cc",
        "linux_generic/reactor.cc",
        "linux_generic/repeating_alarm.cc",
@@ -35,6 +36,7 @@ filegroup {
    name: "BluetoothOsTestSources_linux_generic",
    srcs: [
        "linux_generic/alarm_unittest.cc",
        "linux_generic/files_test.cc",
        "linux_generic/handler_unittest.cc",
        "linux_generic/queue_unittest.cc",
        "linux_generic/reactor_unittest.cc",

system/gd/os/files.h

0 → 100644
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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 <iterator>
#include <optional>

namespace bluetooth {
namespace os {

// Implement ability to read a whole file from |path| into a C++ string, return std::nullopt on failure
//
// Do not use this with large files
std::optional<std::string> ReadSmallFile(const std::string& path);

// Implement ability to safely write to a file. This function is needed because of deficiencies in existing C++ file
// libraries, namely:
// - The ability to open and sync directories with storage media
// - The ability to block and sync file to storage media
// Return true on success, false on failure
bool WriteToFile(const std::string& path, const std::string& data);

}  // namespace os
}  // namespace bluetooth
 No newline at end of file
+179 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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 "os/files.h"

#include <fcntl.h>
#include <libgen.h>
#include <sys/stat.h>
#include <unistd.h>

#include <cctype>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <streambuf>
#include <string>

#include "os/log.h"

namespace {

void HandleError(const std::string& temp_path, int* dir_fd, FILE** fp) {
  // This indicates there is a write issue.  Unlink as partial data is not
  // acceptable.
  unlink(temp_path.c_str());
  if (*fp) {
    fclose(*fp);
    *fp = nullptr;
  }
  if (*dir_fd != -1) {
    close(*dir_fd);
    *dir_fd = -1;
  }
}

}  // namespace

namespace bluetooth {
namespace os {

std::optional<std::string> ReadSmallFile(const std::string& path) {
  std::ifstream input(path, std::ios::binary | std::ios::ate);
  if (!input) {
    LOG_WARN("Failed to open file '%s', error: %s", path.c_str(), strerror(errno));
    return std::nullopt;
  }
  auto file_size = input.tellg();
  if (file_size < 0) {
    LOG_WARN("Failed to get file size for '%s', error: %s", path.c_str(), strerror(errno));
    return std::nullopt;
  }
  std::string result(file_size, '\0');
  if (!input.seekg(0)) {
    LOG_WARN("Failed to go back to the beginning of file '%s', error: %s", path.c_str(), strerror(errno));
    return std::nullopt;
  }
  if (!input.read(result.data(), result.size())) {
    LOG_WARN("Failed to read file '%s', error: %s", path.c_str(), strerror(errno));
    return std::nullopt;
  }
  input.close();
  return result;
}

bool WriteToFile(const std::string& path, const std::string& data) {
  ASSERT(!path.empty());
  // Steps to ensure content of data gets to disk:
  //
  // 1) Open and write to temp file (e.g. bt_config.conf.new).
  // 2) Flush the stream buffer to the temp file.
  // 3) Sync the temp file to disk with fsync().
  // 4) Rename temp file to actual config file (e.g. bt_config.conf).
  //    This ensures atomic update.
  // 5) Sync directory that has the conf file with fsync().
  //    This ensures directory entries are up-to-date.
  //
  // We are using traditional C type file methods because C++ std::filesystem and std::ofstream do not support:
  // - Operation on directories
  // - fsync() to ensure content is written to disk

  // Build temp config file based on config file (e.g. bt_config.conf.new).
  const std::string temp_path = path + ".new";

  // Extract directory from file path (e.g. /data/misc/bluedroid).
  // libc++fs is not supported in APEX yet and hence cannot use std::filesystem::path::parent_path
  std::string directory_path;
  {
    // Make a temporary variable as inputs to dirname() will be modified and return value points to input char array
    // temp_path_for_dir must not be destroyed until results from dirname is appended to directory_path
    std::string temp_path_for_dir(path);
    directory_path.append(dirname(temp_path_for_dir.data()));
  }
  if (directory_path.empty()) {
    LOG_ERROR("error extracting directory from '%s', error: %s", path.c_str(), strerror(errno));
    return false;
  }

  int dir_fd = open(directory_path.c_str(), O_RDONLY);
  if (dir_fd < 0) {
    LOG_ERROR("unable to open dir '%s', error: %s", directory_path.c_str(), strerror(errno));
    return false;
  }

  FILE* fp = fopen(temp_path.c_str(), "wt");
  if (!fp) {
    LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  if (fprintf(fp, "%s", data.c_str()) < 0) {
    LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // Flush the stream buffer to the temp file.
  if (fflush(fp) < 0) {
    LOG_ERROR("unable to write flush buffer to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // Sync written temp file out to disk. fsync() is blocking until data makes it
  // to disk.
  if (fsync(fileno(fp)) < 0) {
    LOG_WARN("unable to fsync file '%s', error: %s", temp_path.c_str(), strerror(errno));
    // Allow fsync to fail and continue
  }

  if (fclose(fp) != 0) {
    LOG_ERROR("unable to close file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }
  fp = nullptr;

  // Change the file's permissions to Read/Write by User and Group
  if (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
    LOG_ERROR("unable to change file permissions '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // Rename written temp file to the actual config file.
  if (rename(temp_path.c_str(), path.c_str()) == -1) {
    LOG_ERROR("unable to commit file from '%s' to '%s', error: %s", temp_path.c_str(), path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // This should ensure the directory is updated as well.
  if (fsync(dir_fd) < 0) {
    LOG_WARN("unable to fsync dir '%s', error: %s", directory_path.c_str(), strerror(errno));
  }

  if (close(dir_fd) < 0) {
    LOG_ERROR("unable to close dir '%s', error: %s", directory_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }
  return true;
}

}  // namespace os
}  // namespace bluetooth
 No newline at end of file
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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 "os/files.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <cstdio>
#include <filesystem>

namespace testing {

using bluetooth::os::ReadSmallFile;
using bluetooth::os::WriteToFile;

TEST(FilesTest, write_read_loopback_test) {
  auto temp_dir = std::filesystem::temp_directory_path();
  auto temp_file = temp_dir / "file_1.txt";
  std::string text = "Hello world!\n";
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
}

TEST(FilesTest, overwrite_test) {
  auto temp_dir = std::filesystem::temp_directory_path();
  auto temp_file = temp_dir / "file_1.txt";
  std::string text = "Hello world!\n";
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  text = "Foo bar!\n";
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
}

TEST(FilesTest, write_read_empty_string_test) {
  auto temp_dir = std::filesystem::temp_directory_path();
  auto temp_file = temp_dir / "file_1.txt";
  std::string text;
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
}

TEST(FilesTest, read_non_existing_file_test) {
  EXPECT_FALSE(ReadSmallFile("/woof"));
}

}  // namespace testing
 No newline at end of file