blob: 94c4ac075ccee494568ab449a863d3e42ca7556b [file] [log] [blame] [edit]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include "gtest/gtest.h"
#include "src/storage/fs_test/fs_test_fixture.h"
#include "src/storage/fs_test/misc.h"
namespace fs_test {
namespace {
using UnicodeTest = FilesystemTest;
// Character that is 32 bits when encoded in UTF-8.
// U+1F60E
constexpr char kSunglasses[] = "\xf0\x9f\x98\x8e";
// Character that is 24 bits when encoded in UTF-8.
// U+203D
constexpr char kInterrobang[] = "\xe2\x80\xbd";
// Character that is 16 bits when encoded in UTF-8.
// U+00F7
constexpr char kDivisionSign[] = "\xc3\xb7";
// Character that is 16 bits when encoded in UTF-8, but 8 bits when encoded in UTF-16.
// U+00BF
constexpr char kInvertedQuestionMark[] = "\xc2\xbf";
TEST_P(UnicodeTest, TestUnicodeDirectoryNames) {
ASSERT_NO_FATAL_FAILURE(CheckCanCreateDirectory(this, kSunglasses, true));
ASSERT_NO_FATAL_FAILURE(CheckCanCreateDirectory(this, kInterrobang, true));
ASSERT_NO_FATAL_FAILURE(CheckCanCreateDirectory(this, kDivisionSign, true));
ASSERT_NO_FATAL_FAILURE(CheckCanCreateDirectory(this, kInvertedQuestionMark, true));
}
TEST_P(UnicodeTest, TestRenameUnicodeSucceeds) {
ASSERT_EQ(mkdir(GetPath(kSunglasses).c_str(), 0755), 0);
// Note that on FAT32 this wouldn't change the short name of the directory.
ASSERT_EQ(rename(GetPath(kSunglasses).c_str(), GetPath(kInterrobang).c_str()), 0)
<< strerror(errno);
DIR* d;
ASSERT_EQ(opendir(GetPath(kSunglasses).c_str()), nullptr);
ASSERT_NE(d = opendir(GetPath(kInterrobang).c_str()), nullptr);
closedir(d);
// This would though - we go from having two UTF-16 codepoints to one.
ASSERT_EQ(rename(GetPath(kInterrobang).c_str(), GetPath(kDivisionSign).c_str()), 0);
ASSERT_EQ(opendir(GetPath(kInterrobang).c_str()), nullptr);
ASSERT_NE(d = opendir(GetPath(kDivisionSign).c_str()), nullptr);
closedir(d);
}
TEST_P(UnicodeTest, TestNonUtf8Names) {
// Valid UTF-8 byte sequences follow these bit patterns:
// 0xxx_xxxx
// 110x_xxxx 10xx_xxxx
// 1110_xxxx 10xx_xxxx 10xx_xxxx
// 1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
// This sequence is invalid because bit zero is set in the first byte, but bit one is not set
// (it's 1000_0000 1000_0001).
constexpr char kInvalidBytes[] = "\x80\x81";
ASSERT_EQ(mkdir(GetPath(kInvalidBytes).c_str(), 0755), -1);
ASSERT_EQ(errno, EINVAL);
}
void TestCreateAndDeleteUnicodeFilename(UnicodeTest* test, const char* name) {
int fd;
ASSERT_GE(fd = open(test->GetPath(name).c_str(), O_RDWR | O_CREAT), 0) << strerror(errno);
int result = write(fd, "abc", 4);
ASSERT_GT(result, 0) << strerror(errno);
ASSERT_EQ(unlink(test->GetPath(name).c_str()), 0) << strerror(errno);
close(fd);
}
TEST_P(UnicodeTest, TestUnicodeFileNames) {
ASSERT_NO_FATAL_FAILURE(TestCreateAndDeleteUnicodeFilename(this, kSunglasses));
ASSERT_NO_FATAL_FAILURE(TestCreateAndDeleteUnicodeFilename(this, kInterrobang));
ASSERT_NO_FATAL_FAILURE(TestCreateAndDeleteUnicodeFilename(this, kDivisionSign));
ASSERT_NO_FATAL_FAILURE(TestCreateAndDeleteUnicodeFilename(this, kInvertedQuestionMark));
}
TEST_P(UnicodeTest, TestUtf16UnpairedSurrogate) {
// This decodes to U+D800, which is reserved as a value for the first two bytes in a 4-byte UTF-16
// character.
constexpr char kUnpairedHighSurrogate[] = "\xed\xa0\x80";
ASSERT_EQ(mkdir(GetPath(kUnpairedHighSurrogate).c_str(), 0755), -1);
ASSERT_EQ(errno, EINVAL);
// This is U+DC00, which must be the last two bytes in a 4-byte UTF-16 character.
constexpr char kUnpairedLowSurrogate[] = "\xed\xb0\x80";
ASSERT_EQ(mkdir(GetPath(kUnpairedLowSurrogate).c_str(), 0755), -1);
ASSERT_EQ(errno, EINVAL);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, UnicodeTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test