chromium/base/files/file_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/files/file.h"

#include <stdint.h>

#include <utility>

#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_BASE_TRACING)
#include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h"  // no-presubmit-check nogncheck
#endif  // BUILDFLAG(ENABLE_BASE_TRACING)

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/environment.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gtest_util.h"
#endif

File;
FilePath;

TEST(FileTest, Create) {}

TEST(FileTest, SelfSwap) {}

TEST(FileTest, Async) {}

TEST(FileTest, DeleteOpenFile) {}

TEST(FileTest, ReadWrite) {}

TEST(FileTest, ReadWriteSpans) {}

TEST(FileTest, GetLastFileError) {}

TEST(FileTest, Append) {}


TEST(FileTest, Length) {}

// Flakily fails: http://crbug.com/86494
#if BUILDFLAG(IS_ANDROID)
TEST(FileTest, TouchGetInfo) {
#else
TEST(FileTest, DISABLED_TouchGetInfo) {}

// Test we can retrieve the file's creation time through File::GetInfo().
TEST(FileTest, GetInfoForCreationTime) {}

TEST(FileTest, ReadAtCurrentPosition) {}

TEST(FileTest, ReadAtCurrentPositionSpans) {}

TEST(FileTest, WriteAtCurrentPosition) {}

TEST(FileTest, WriteAtCurrentPositionSpans) {}

TEST(FileTest, Seek) {}

TEST(FileTest, Duplicate) {}

TEST(FileTest, DuplicateDeleteOnClose) {}

#if BUILDFLAG(ENABLE_BASE_TRACING)
TEST(FileTest, TracedValueSupport) {}
#endif  // BUILDFLAG(ENABLE_BASE_TRACING)

#if BUILDFLAG(IS_WIN)
// Flakily times out on Windows, see http://crbug.com/846276.
#define MAYBE_WriteDataToLargeOffset
#else
#define MAYBE_WriteDataToLargeOffset
#endif
TEST(FileTest, MAYBE_WriteDataToLargeOffset) {}

TEST(FileTest, AddFlagsForPassingToUntrustedProcess) {}

#if BUILDFLAG(IS_WIN)
TEST(FileTest, GetInfoForDirectory) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath empty_dir =
      temp_dir.GetPath().Append(FILE_PATH_LITERAL("gpfi_test"));
  ASSERT_TRUE(CreateDirectory(empty_dir));

  base::File dir(
      ::CreateFile(empty_dir.value().c_str(), GENERIC_READ | GENERIC_WRITE,
                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
                   OPEN_EXISTING,
                   FILE_FLAG_BACKUP_SEMANTICS,  // Needed to open a directory.
                   NULL));
  ASSERT_TRUE(dir.IsValid());

  base::File::Info info;
  EXPECT_TRUE(dir.GetInfo(&info));
  EXPECT_TRUE(info.is_directory);
  EXPECT_FALSE(info.is_symbolic_link);
  EXPECT_EQ(0, info.size);
}

TEST(FileTest, DeleteNoop) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // Creating and closing a file with DELETE perms should do nothing special.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());
  file.Close();
  ASSERT_TRUE(base::PathExists(file_path));
}

TEST(FileTest, Delete) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // Creating a file with DELETE and then marking for delete on close should
  // delete it.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());
  ASSERT_TRUE(file.DeleteOnClose(true));
  file.Close();
  ASSERT_FALSE(base::PathExists(file_path));
}

TEST(FileTest, DeleteThenRevoke) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // Creating a file with DELETE, marking it for delete, then clearing delete on
  // close should not delete it.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());
  ASSERT_TRUE(file.DeleteOnClose(true));
  ASSERT_TRUE(file.DeleteOnClose(false));
  file.Close();
  ASSERT_TRUE(base::PathExists(file_path));
}

TEST(FileTest, IrrevokableDeleteOnClose) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // DELETE_ON_CLOSE cannot be revoked by this opener.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_DELETE_ON_CLOSE |
             base::File::FLAG_WIN_SHARE_DELETE |
             base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());
  // https://msdn.microsoft.com/library/windows/desktop/aa364221.aspx says that
  // setting the dispositon has no effect if the handle was opened with
  // FLAG_DELETE_ON_CLOSE. Do not make the test's success dependent on whether
  // or not SetFileInformationByHandle indicates success or failure. (It happens
  // to indicate success on Windows 10.)
  file.DeleteOnClose(false);
  file.Close();
  ASSERT_FALSE(base::PathExists(file_path));
}

TEST(FileTest, IrrevokableDeleteOnCloseOther) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // DELETE_ON_CLOSE cannot be revoked by another opener.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_DELETE_ON_CLOSE |
             base::File::FLAG_WIN_SHARE_DELETE |
             base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());

  File file2(file_path,
             (base::File::FLAG_OPEN | base::File::FLAG_READ |
              base::File::FLAG_WRITE | base::File::FLAG_WIN_SHARE_DELETE |
              base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file2.IsValid());

  file2.DeleteOnClose(false);
  file2.Close();
  ASSERT_TRUE(base::PathExists(file_path));
  file.Close();
  ASSERT_FALSE(base::PathExists(file_path));
}

TEST(FileTest, DeleteWithoutPermission) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // It should not be possible to mark a file for deletion when it was not
  // created/opened with DELETE.
  File file(file_path, (base::File::FLAG_CREATE | base::File::FLAG_READ |
                        base::File::FLAG_WRITE));
  ASSERT_TRUE(file.IsValid());
  ASSERT_FALSE(file.DeleteOnClose(true));
  file.Close();
  ASSERT_TRUE(base::PathExists(file_path));
}

TEST(FileTest, UnsharedDeleteOnClose) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // Opening with DELETE_ON_CLOSE when a previous opener hasn't enabled sharing
  // will fail.
  File file(file_path, (base::File::FLAG_CREATE | base::File::FLAG_READ |
                        base::File::FLAG_WRITE));
  ASSERT_TRUE(file.IsValid());
  File file2(
      file_path,
      (base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE |
       base::File::FLAG_DELETE_ON_CLOSE | base::File::FLAG_WIN_SHARE_DELETE));
  ASSERT_FALSE(file2.IsValid());

  file.Close();
  ASSERT_TRUE(base::PathExists(file_path));
}

TEST(FileTest, NoDeleteOnCloseWithMappedFile) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  // Mapping a file into memory blocks DeleteOnClose.
  File file(file_path,
            (base::File::FLAG_CREATE | base::File::FLAG_READ |
             base::File::FLAG_WRITE | base::File::FLAG_CAN_DELETE_ON_CLOSE));
  ASSERT_TRUE(file.IsValid());
  ASSERT_EQ(5, file.WriteAtCurrentPos("12345", 5));

  {
    base::MemoryMappedFile mapping;
    ASSERT_TRUE(mapping.Initialize(file.Duplicate()));
    ASSERT_EQ(5U, mapping.length());

    EXPECT_FALSE(file.DeleteOnClose(true));
  }

  file.Close();
  ASSERT_TRUE(base::PathExists(file_path));
}

// Check that we handle the async bit being set incorrectly in a sane way.
TEST(FileTest, UseSyncApiWithAsyncFile) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  FilePath file_path = temp_dir.GetPath().AppendASCII("file");

  File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE |
                           base::File::FLAG_ASYNC);
  File lying_file(file.TakePlatformFile(), false /* async */);
  ASSERT_TRUE(lying_file.IsValid());

  ASSERT_EQ(lying_file.WriteAtCurrentPos("12345", 5), -1);
}

TEST(FileDeathTest, InvalidFlags) {
  EXPECT_CHECK_DEATH_WITH(
      {
        // When this test is running as Admin, TMP gets ignored and temporary
        // files/folders are created in %ProgramFiles%. This means that the
        // temporary folder created by the death test never gets deleted, as it
        // crashes before the `base::ScopedTempDir` goes out of scope and also
        // does not get automatically cleaned by by the test runner.
        //
        // To avoid this from happening, this death test explicitly creates the
        // temporary folder in TMP, which is set by the test runner parent
        // process to a temporary folder for the test. This means that the
        // folder created here is always deleted during test runner cleanup.
        std::string tmp_folder;
        ASSERT_TRUE(base::Environment::Create()->GetVar("TMP", &tmp_folder));
        base::ScopedTempDir temp_dir;
        ASSERT_TRUE(temp_dir.CreateUniqueTempDirUnderPath(
            base::FilePath(base::UTF8ToWide(tmp_folder))));
        FilePath file_path = temp_dir.GetPath().AppendASCII("file");

        File file(file_path,
                  base::File::FLAG_CREATE | base::File::FLAG_WIN_EXECUTE |
                      base::File::FLAG_READ | base::File::FLAG_WIN_NO_EXECUTE);
      },
      "FLAG_WIN_NO_EXECUTE");
}
#endif  // BUILDFLAG(IS_WIN)