/* * Copyright (C) 2014 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 "gtest/gtest.h" #include #include #include #include #include #include #include #include #include #include #include #include "file.h" // NOLINT //------------------------------------------------------------------------------ // Test simple file IO TEST(File, Mkstemp) { char* temp = strdup(TempFileTemplate()); int fd = mkstemp(temp); ASSERT_NE(fd, -1); ASSERT_EQ(close(fd), 0); ASSERT_EQ(unlink(temp), 0); free(temp); } extern "C" int mkstemps(char* templ, int suffix_len); TEST(File, Mkstemps) { char* temp; asprintf(&temp, "%s%s", TempFileTemplate(), ".txt"); int fd = mkstemps(temp, 4); ASSERT_NE(fd, -1); ASSERT_EQ(access(temp, R_OK | W_OK), 0); ASSERT_EQ(close(fd), 0); ASSERT_EQ(unlink(temp), 0); free(temp); } TEST(File, Fdopen) { TempFile f; ASSERT_TRUE(f.get()); } TEST(File, ReadWrite) { TempFile f; ASSERT_EQ(fwrite("Hello", 1, 5, f.get()), 5U); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); char buf[5]; ASSERT_EQ(fread(&buf, 1, 5, f.get()), 5U); EXPECT_EQ(buf[0], 'H'); EXPECT_EQ(buf[4], 'o'); } TEST(File, PReadWrite) { TempFile f; const char* hello = "Hello"; ASSERT_EQ(pwrite(f.fd(), hello, strlen(hello), 0), 5); char buf[5]; ASSERT_EQ(pread(f.fd(), buf, 4, 1), 4); buf[4] = '\0'; EXPECT_STREQ(buf, "ello"); } TEST(File, fileno) { TempFile f; ASSERT_EQ(f.fd(), fileno(f.get())); } TEST(File, Ftell) { TempFile f; EXPECT_EQ(ftell(f.get()), 0); ASSERT_EQ(fwrite("Hello", 1, 5, f.get()), 5U); EXPECT_EQ(ftell(f.get()), 5); ASSERT_EQ(fseek(f.get(), 1, SEEK_SET), 0); EXPECT_EQ(ftell(f.get()), 1); } TEST(File, Lseek) { TempFile f; ASSERT_EQ(fwrite("Hello", 1, 5, f.get()), 5U); ASSERT_EQ(fflush(f.get()), 0); EXPECT_EQ(lseek(f.fd(), 1, SEEK_SET), 1); EXPECT_EQ(lseek(f.fd(), 1, SEEK_CUR), 2); EXPECT_EQ(lseek(f.fd(), -1, SEEK_END), 4); EXPECT_EQ(lseek64(f.fd(), -2, SEEK_END), 3); } TEST(File, Ftruncate) { TempFile f; ASSERT_EQ(fwrite("Hello", 1, 5, f.get()), 5U); ASSERT_EQ(fflush(f.get()), 0); ASSERT_EQ(lseek(f.fd(), 0, SEEK_END), 5); ASSERT_EQ(lseek(f.fd(), 0, SEEK_SET), 0); EXPECT_EQ(ftruncate(f.fd(), -1), -1); EXPECT_EQ(ftruncate(f.fd(), 3), 0); EXPECT_EQ(lseek(f.fd(), 0, SEEK_END), 3); } TEST(File, Reopen) { TempFile f; // freopen(nullptr, ...) is not supported in bionic. ASSERT_TRUE(freopen(f.FileName(), "r", f.get())); EXPECT_EQ(fwrite("Hello", 1, 5, f.get()), 0U); ASSERT_TRUE(freopen(f.FileName(), "r+", f.get())); EXPECT_EQ(fwrite("Hello", 1, 5, f.get()), 5U); } TEST(File, ODirectoryFlag) { TempFile f; errno = 0; // Tries to open a file with O_DIRECTORY, which should fail. ASSERT_EQ(open(f.FileName(), O_RDONLY | O_DIRECTORY), -1); EXPECT_EQ(errno, ENOTDIR); } TEST(File, TempFile) { FILE* f = tmpfile(); ASSERT_TRUE(f); ASSERT_EQ(fclose(f), 0); } void TestStatBuf(const struct stat& buf, int file_size, const char* msg) { EXPECT_EQ(buf.st_size, file_size) << msg; EXPECT_EQ(buf.st_nlink, 1U) << msg; // regular file with chmod 600. EXPECT_EQ(static_cast(buf.st_mode), S_IFREG | S_IRUSR | S_IWUSR) << msg; EXPECT_NE(buf.st_blksize, 0U) << msg; EXPECT_EQ(buf.st_blksize % 512, 0U) << msg; EXPECT_NE(buf.st_mtime, 0) << msg; // We do not support st_atime/st_ctime. } TEST(File, Stat) { // Make sure file will have the exact permissions we want. mode_t saved_umask = umask(S_IRWXG | S_IRWXO); TempFile f; ASSERT_GE(fputs("test", f.get()), 0); ASSERT_EQ(fflush(f.get()), 0); struct stat buf; ASSERT_EQ(stat(f.FileName(), &buf), 0); TestStatBuf(buf, 4, "stat"); ASSERT_EQ(lstat(f.FileName(), &buf), 0); TestStatBuf(buf, 4, "lstat"); ASSERT_EQ(fstat(f.fd(), &buf), 0); TestStatBuf(buf, 4, "fstat"); // Restore umask. umask(saved_umask); } int vfprintf_call(FILE* f, const char* format, ...) { va_list args; va_start(args, format); int result = vfprintf(f, format, args); va_end(args); return result; } int vfscanf_call(FILE* f, const char* format, ...) { va_list args; va_start(args, format); int result = vfscanf(f, format, args); va_end(args); return result; } TEST(File, PrintfScanf) { TempFile f; ASSERT_GT(fprintf(f.get(), "%d %lf %lld %p\n", 1, 2.0, 3LL, &f), 0); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); int res_int; double res_double; int64_t res_long; TempFile* res_pointer; ASSERT_EQ(fscanf(f.get(), "%d%lf%lld%p", &res_int, &res_double, &res_long, &res_pointer), 4); EXPECT_EQ(res_int, 1); EXPECT_DOUBLE_EQ(res_double, 2.0); EXPECT_EQ(res_long, 3); EXPECT_EQ(res_pointer, &f); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); ASSERT_EQ(vfscanf_call(f.get(), "%d%lf%lld%p", &res_int, &res_double, &res_long, &res_pointer), 4); EXPECT_EQ(res_int, 1); EXPECT_DOUBLE_EQ(res_double, 2.0); EXPECT_EQ(res_long, 3); EXPECT_EQ(res_pointer, &f); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); ASSERT_GT(vfprintf_call(f.get(), "%.1lf_%d_%lld\n", 1.0, 2, 3LL), 0); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); char data[256]; ASSERT_EQ(fscanf(f.get(), "%s", data), 1); EXPECT_STREQ(data, "1.0_2_3"); const char* in_str = "http://foo.bar.com/main?lang=US"; char more_data[64]; ASSERT_EQ(sscanf(in_str, "%15[^:]:%[^\n]", data, more_data), 2); // NOLINT EXPECT_STREQ(data, "http"); EXPECT_STREQ(more_data, "//foo.bar.com/main?lang=US"); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); ASSERT_GT(fprintf(f.get(), "%0*d\n", 2, 1), 0); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); ASSERT_EQ(fscanf(f.get(), "%s", data), 1); EXPECT_STREQ(data, "01"); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); } TEST(File, PositionalPrintf) { TempFile f; char buf[256]; #define CHECK_PRINTF(result, format, ...) \ do { \ ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); \ ASSERT_GT(fprintf(f.get(), format "\n", __VA_ARGS__), 0); \ ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); \ ASSERT_EQ(fgets(buf, sizeof(buf), f.get()), buf); \ EXPECT_STREQ(buf, result "\n"); \ } while (0) // Good CHECK_PRINTF("2 3.0", "%1$d %2$.1lf", 2, 3.0); CHECK_PRINTF("2 3.0", "%2$d %1$.1lf", 3.0, 2); CHECK_PRINTF("2.000", "%2$.*1$lf", 3, 2.0); CHECK_PRINTF(" abc", "%2$*1$s", 4, "abc"); CHECK_PRINTF("1 1 2", "%1$d %1$d %2$d", 1, 2); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat" // Bad CHECK_PRINTF("2 3 1", "%2$d %d %1$d", 1, 2, 3); CHECK_PRINTF(" abc 4 1", "%1$*2$s %d %3$d", "abc", 4, 1); // Ugly CHECK_PRINTF(" abc 1", "%1$*s %d", 4, "abc", 1); CHECK_PRINTF("1 2 2", "%d %d %1$2$d", 1, 2); #pragma GCC diagnostic pop #undef CHECK_PRINTF } TEST(File, FdPrintf) { TempFile f; using FdPrintf = int (*)(int fd, const char* format, ...); FdPrintf fdprintf = reinterpret_cast(dlsym(RTLD_DEFAULT, "fdprintf")); ASSERT_GT(fdprintf(f.fd(), "%.1lf %d %lld\n", 1.0, 2, 3LL), 0); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); char buf[16]; ASSERT_EQ(fgets(buf, sizeof(buf), f.get()), buf); EXPECT_STREQ(buf, "1.0 2 3\n"); } TEST(File, GetPut) { TempFile f; ASSERT_GE(fputs("Hell", f.get()), 0); ASSERT_EQ(fputc('o', f.get()), 'o'); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); EXPECT_EQ(fgetc(f.get()), 'H'); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); char buf[6]; ASSERT_EQ(buf, fgets(buf, sizeof(buf), f.get())); EXPECT_EQ(buf[0], 'H'); EXPECT_EQ(buf[4], 'o'); } TEST(File, Feof) { TempFile f; ASSERT_EQ(fgetc(f.get()), EOF); ASSERT_TRUE(feof(f.get())); clearerr(f.get()); ASSERT_FALSE(feof(f.get())); } TEST(File, Ungetc) { TempFile f; ASSERT_EQ(ungetc(' ', f.get()), ' '); ASSERT_EQ(fgetc(f.get()), ' '); ASSERT_EQ(fgetc(f.get()), EOF); } TEST(File, Setvbuf) { char buf[1024]; memset(buf, 1, sizeof(buf)); TempFile f; EXPECT_EQ(setvbuf(f.get(), nullptr, _IOFBF, 1024), 0); ASSERT_EQ(setvbuf(f.get(), buf, _IOFBF, sizeof(buf)), 0); char data[2048]; memset(data, 2, sizeof(data)); ASSERT_EQ(fwrite(data, 1, 1, f.get()), 1U); // Check that buffer is used EXPECT_TRUE(memchr(buf, 2, sizeof(buf))); // Check that it doesn't corrupt read/writes. ASSERT_EQ(fwrite(data, 1, sizeof(data), f.get()), sizeof(data)); ASSERT_EQ(fseek(f.get(), 0, SEEK_SET), 0); char rdata[2048]; ASSERT_EQ(fread(rdata, 1, sizeof(rdata), f.get()), sizeof(rdata)); EXPECT_EQ(memcmp(data, rdata, sizeof(data)), 0); } TEST(File, SetBuffer) { char buf[1024]; memset(buf, 1, sizeof(buf)); TempFile f; setbuffer(f.get(), buf, sizeof(buf)); char data = 2; ASSERT_EQ(fwrite(&data, 1, 1, f.get()), 1U); ASSERT_TRUE(memchr(buf, 2, sizeof(buf))); } int vsprintf_call(char* str, const char* format, ...) { va_list args; va_start(args, format); int result = vsprintf(str, format, args); va_end(args); return result; } int vsnprintf_call(char* str, size_t n, const char* format, ...) { va_list args; va_start(args, format); int result = vsnprintf(str, n, format, args); va_end(args); return result; } int vasprintf_call(char** str, const char* format, ...) { va_list args; va_start(args, format); int result = vasprintf(str, format, args); va_end(args); return result; } TEST(File, StringPrintfScanf) { char data[256]; ASSERT_GT(snprintf(data, sizeof(data), "%d %lf %lld %p", 1, 2.0, 3LL, data), 0); int res_int; double res_double; int64_t res_int64; void* res_pointer; ASSERT_EQ(sscanf(data, "%d%lf%lld%p", // NOLINT &res_int, &res_double, &res_int64, &res_pointer), 4); EXPECT_EQ(res_int, 1); EXPECT_DOUBLE_EQ(res_double, 2.0); EXPECT_EQ(res_int64, 3); EXPECT_EQ(res_pointer, data); ASSERT_GT(sprintf(data, "%.1lf %d %lld", 1.0, 2, 3LL), 0); // NOLINT EXPECT_STREQ(data, "1.0 2 3"); ASSERT_GT(sprintf(data, "%%%c", 'd'), 0); // NOLINT EXPECT_STREQ(data, "%d"); res_int64 = 3; ASSERT_GT(sprintf(data, "%d %ld %lld %qd %d", // NOLINT 1, (long)2, res_int64, res_int64 + 1, 5), 0); // NOLINT EXPECT_STREQ(data, "1 2 3 4 5"); ASSERT_GT(sprintf(data, "%s", "one two"), 0); // NOLINT EXPECT_STREQ(data, "one two"); char* new_data = nullptr; ASSERT_GT(asprintf(&new_data, "%.1lf %d %lld", 1.0, 2, 3LL), 0); // NOLINT ASSERT_NE(new_data, nullptr); EXPECT_STREQ(new_data, "1.0 2 3"); free(new_data); char word[256]; ASSERT_EQ(sscanf(data, "%s", word), 1); // NOLINT EXPECT_STREQ(word, "one"); ASSERT_GT(vsprintf_call(data, "%.1lf %d %lld", 1.0, 2, 3LL), 0); EXPECT_STREQ(data, "1.0 2 3"); ASSERT_GT(vsnprintf_call(data, 256, "%.1lf %d %lld", 1.0, 2, 3LL), 0); EXPECT_STREQ(data, "1.0 2 3"); new_data = nullptr; ASSERT_GT(vasprintf_call(&new_data, "%.1lf %d %lld", 1.0, 2, 3LL), 0); ASSERT_NE(new_data, nullptr); EXPECT_STREQ(new_data, "1.0 2 3"); free(new_data); } TEST(File, Select) { fd_set read; struct timeval timeout; FD_ZERO(&read); FD_SET(STDOUT_FILENO, &read); timeout.tv_sec = 0; timeout.tv_usec = 1; ASSERT_EQ(select(STDOUT_FILENO + 1, &read, nullptr, nullptr, &timeout), 0); fd_set write; FD_ZERO(&write); FD_SET(STDOUT_FILENO, &write); ASSERT_EQ(select(STDOUT_FILENO + 1, nullptr, &write, nullptr, nullptr), 1); } void* ThreadPipeReadFunc(void* arg) { int* iarg = reinterpret_cast(arg); int fd = iarg[0]; char buf; for (int i = 0; i < 1000; i++) { if (read(fd, &buf, 1) != 1) { return nullptr; } } iarg[1] = 1; return nullptr; } TEST(File, Pipe) { int fds[2]; pthread_t thread; int arg[2]; char buf = 0; pipe(fds); arg[0] = fds[0]; arg[1] = 0; pthread_create(&thread, nullptr, ThreadPipeReadFunc, &arg); for (int i = 0; i < 1000; i++) { EXPECT_EQ(write(fds[1], &buf, 1), 1); } pthread_join(thread, nullptr); EXPECT_EQ(arg[1], 1); } // This function is implemented but is not present in public bionic headers. extern "C" char* mkdtemp(char* tmpl); TEST(File, TempDir) { char* temp = strdup(TempFileTemplate()); // Currently mkdtemp can reuse existing directory because our mkdir is not // POSIX compliant on Pepper FS. See crbug.com/314879. ASSERT_EQ(mkdtemp(temp), temp); struct stat dir_stat; ASSERT_EQ(stat(temp, &dir_stat), 0); ASSERT_TRUE(S_ISDIR(dir_stat.st_mode)); ASSERT_EQ(rmdir(temp), 0); free(temp); } class TempDir { public: TempDir() { name_ = strdup(TempFileTemplate()); if (mkdtemp(name_) != name_) { free(name_); name_ = nullptr; } } explicit TempDir(const char* dir) { const char* kDirTemplate = "/ndk-tests-XXXXXX"; size_t max_len = strlen(dir) + strlen(kDirTemplate) + 1; name_ = reinterpret_cast(malloc(max_len)); snprintf(name_, max_len, "%s%s", dir, kDirTemplate); if (mkdtemp(name_) != name_) { free(name_); name_ = nullptr; } } ~TempDir() { if (name_ != nullptr) { rmdir(name_); free(name_); } } const char* GetDirName() const { return name_; } const char* GetBaseName() { const char* result = name_; for (char* p = name_; *p; p++) { if (*p == '/') { result = p + 1; } } return result; } private: char* name_; }; struct dirent* SkipDotDirsWithReaddir(DIR* pdir, struct dirent* entry) { // skip "." and ".." while (entry != nullptr && entry->d_name[0] == '.') { entry = readdir(pdir); // NOLINT(runtime/threadsafe_fn) } return entry; } // We can't create files in /tmp directories outside of predefined places. // We create directories inside of our temporary directory instead. TEST(File, Readdir) { TempDir dir; ASSERT_NE(dir.GetDirName(), nullptr); TempDir dir1(dir.GetDirName()); ASSERT_NE(dir1.GetDirName(), nullptr); TempDir dir2(dir.GetDirName()); ASSERT_NE(dir2.GetDirName(), nullptr); DIR* pdir = opendir(dir.GetDirName()); ASSERT_NE(pdir, nullptr); struct dirent* entry = readdir(pdir); // NOLINT(runtime/threadsafe_fn) entry = SkipDotDirsWithReaddir(pdir, entry); ASSERT_NE(entry, nullptr); EXPECT_EQ(entry->d_type, DT_DIR); bool isDir1 = strcmp(dir1.GetBaseName(), entry->d_name) == 0; bool isDir2 = strcmp(dir2.GetBaseName(), entry->d_name) == 0; EXPECT_TRUE(isDir1 || isDir2); entry = readdir(pdir); // NOLINT(runtime/threadsafe_fn) entry = SkipDotDirsWithReaddir(pdir, entry); ASSERT_NE(entry, nullptr); EXPECT_EQ(entry->d_type, DT_DIR); isDir1 |= strcmp(dir1.GetBaseName(), entry->d_name) == 0; isDir2 |= strcmp(dir2.GetBaseName(), entry->d_name) == 0; EXPECT_TRUE(isDir1 && isDir2); entry = readdir(pdir); // NOLINT(runtime/threadsafe_fn) entry = SkipDotDirsWithReaddir(pdir, entry); ASSERT_EQ(entry, nullptr); ASSERT_EQ(closedir(pdir), 0); } extern "C" int getdents(unsigned int, dirent*, unsigned int) __attribute__((__weak__)); TEST(File, Getdents) { TempDir dir; ASSERT_NE(dir.GetDirName(), nullptr); TempDir dir1(dir.GetDirName()); ASSERT_NE(dir1.GetDirName(), nullptr); int dir_fd = open(dir.GetDirName(), O_RDONLY | O_DIRECTORY); ASSERT_NE(dir_fd, -1); char buf[1024]; int res = getdents(dir_fd, reinterpret_cast(buf), sizeof(buf)); ASSERT_GT(res, 0); struct dirent* entry = reinterpret_cast(buf); int entries = 0; int pos = 0; bool has_dir1 = false; // Check that d_reclen is available first. d_type is the next field in the // dirent structure. while (static_cast(offsetof(struct dirent, d_type)) <= res - pos && entry->d_reclen + pos <= res) { entries++; EXPECT_EQ(entry->d_type, DT_DIR); if (strcmp(entry->d_name, dir1.GetBaseName()) == 0) { has_dir1 = true; } pos += entry->d_reclen; entry = reinterpret_cast(buf + pos); } EXPECT_EQ(entries, 3); EXPECT_TRUE(has_dir1); close(dir_fd); } bool SkipDotDirsWithReaddir_r(DIR* pdir, struct dirent* entry, struct dirent** result) { // skip "." and ".." while (*result != nullptr && entry->d_name[0] == '.') { if (readdir_r(pdir, entry, result) != 0) { return false; } } return true; } void TestReaddirRWithDir(DIR* pdir, const char* innerdir) { struct dirent entry; struct dirent* result; ASSERT_EQ(readdir_r(pdir, &entry, &result), 0); ASSERT_TRUE(SkipDotDirsWithReaddir_r(pdir, &entry, &result)); ASSERT_EQ(result, &entry); EXPECT_EQ(entry.d_type, DT_DIR); EXPECT_STREQ(entry.d_name, innerdir); ASSERT_EQ(readdir_r(pdir, &entry, &result), 0); ASSERT_TRUE(SkipDotDirsWithReaddir_r(pdir, &entry, &result)); EXPECT_EQ(result, nullptr); } TEST(File, Readdir_r) { TempDir dir; ASSERT_NE(dir.GetDirName(), nullptr); TempDir dir1(dir.GetDirName()); ASSERT_NE(dir1.GetDirName(), nullptr); DIR* pdir = opendir(dir.GetDirName()); ASSERT_NE(pdir, nullptr); TestReaddirRWithDir(pdir, dir1.GetBaseName()); ASSERT_EQ(closedir(pdir), 0); } TEST(File, Rewinddir) { TempDir dir; ASSERT_NE(dir.GetDirName(), nullptr); TempDir dir1(dir.GetDirName()); ASSERT_NE(dir1.GetDirName(), nullptr); DIR* pdir = opendir(dir.GetDirName()); ASSERT_NE(pdir, nullptr); TestReaddirRWithDir(pdir, dir1.GetBaseName()); rewinddir(pdir); TestReaddirRWithDir(pdir, dir1.GetBaseName()); ASSERT_EQ(closedir(pdir), 0); } int ScandirFilter(const struct dirent* entry) { return entry->d_name[0] != '.'; } int ScandirComparator(const struct dirent** entry1, const struct dirent** entry2) { return strcmp((*entry1)->d_name, (*entry2)->d_name); } TEST(File, Scandir) { TempDir dir; ASSERT_NE(dir.GetDirName(), nullptr); TempDir dir1(dir.GetDirName()); ASSERT_NE(dir1.GetDirName(), nullptr); TempDir dir2(dir.GetDirName()); ASSERT_NE(dir2.GetDirName(), nullptr); struct dirent** namelist; int size = scandir(dir.GetDirName(), &namelist, ScandirFilter, ScandirComparator); ASSERT_EQ(size, 2); ASSERT_LE(ScandirComparator(const_cast(&namelist[0]), const_cast(&namelist[1])), 0); for (int i = 0; i < size; i++) { free(namelist[i]); } free(namelist); } TEST(File, FlockAlwaysSucceeds) { TempFile f; EXPECT_EQ(flock(f.fd(), LOCK_SH), 0); EXPECT_EQ(flock(f.fd(), LOCK_EX), 0); EXPECT_EQ(flock(f.fd(), LOCK_UN), 0); } // Simple pseudo file. struct funopen_cookie { int pos; char magic; }; int funopen_read(void* cookie, char* data, int size) { funopen_cookie* file = static_cast(cookie); for (int i = 0; i < size; i++) { data[i] = (file->pos + i) % 256; } file->pos += size; return size; } int funopen_write(void* cookie, const char* data, int size) { funopen_cookie* file = static_cast(cookie); for (int i = 0; i < size; i++) { if (data[i] != file->magic) { errno = EIO; return 0; } } file->pos += size; return size; } fpos_t funopen_seek(void* cookie, fpos_t pos, int whence) { funopen_cookie* file = static_cast(cookie); switch (whence) { case SEEK_SET: file->pos = pos; break; case SEEK_CUR: file->pos += pos; break; default: errno = EINVAL; return -1; } return file->pos; } int funopen_close(void* /* cookie */) { return 0; } TEST(File, Funopen) { funopen_cookie cookie = {0, 'a'}; FILE* f = funopen(&cookie, funopen_read, funopen_write, funopen_seek, funopen_close); ASSERT_NE(f, nullptr); // Disable buffering to make all file operations call funopen_* functions. ASSERT_EQ(setvbuf(f, nullptr, _IONBF, 0), 0); const size_t kBufSize = 4; char buf[kBufSize]; ASSERT_EQ(fread(buf, 1, kBufSize, f), kBufSize); for (size_t i = 0; i < kBufSize; i++) { EXPECT_EQ(static_cast(i), buf[i]); } memset(buf, 'b', kBufSize); ASSERT_EQ(fwrite(buf, 1, kBufSize, f), 0U); memset(buf, 'a', kBufSize); ASSERT_EQ(fwrite(buf, 1, kBufSize, f), kBufSize); ASSERT_EQ(ftell(f), static_cast(2 * kBufSize)); ASSERT_EQ(fseek(f, kBufSize, SEEK_SET), 0); ASSERT_EQ(ftell(f), static_cast(kBufSize)); // No one can hear your scream in our pseudo file. ASSERT_EQ(fread(buf, 1, kBufSize, f), kBufSize); for (size_t i = 0; i < kBufSize; i++) { EXPECT_EQ(buf[i], static_cast(i + kBufSize)); } ASSERT_EQ(fclose(f), 0); } TEST(File, UmaskActsSanely) { mode_t saved_umask = umask(0600); EXPECT_EQ(umask(0700), 0600); EXPECT_EQ(umask(0600), 0700); umask(saved_umask); }