diff --git a/base/fs.cpp b/base/fs.cpp index 115d62244..5769a87a2 100644 --- a/base/fs.cpp +++ b/base/fs.cpp @@ -73,7 +73,11 @@ std::string get_absolute_path(const std::string& filename) bool is_path_separator(std::string::value_type chr) { - return (chr == '\\' || chr == '/'); + return ( +#if LAF_WINDOWS + chr == '\\' || +#endif + chr == '/'); } std::string get_file_path(const std::string& filename) diff --git a/base/fs_tests.cpp b/base/fs_tests.cpp index 8eff88554..21b261238 100644 --- a/base/fs_tests.cpp +++ b/base/fs_tests.cpp @@ -1,4 +1,5 @@ // LAF Base Library +// Copyright (c) 2024 Igara Studio S.A. // Copyright (c) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -6,6 +7,7 @@ #include +#include "base/file_content.h" #include "base/fs.h" using namespace base; @@ -45,96 +47,131 @@ TEST(FS, MakeAllDirectories) TEST(FS, IsPathSeparator) { - EXPECT_TRUE (is_path_separator('\\')); EXPECT_TRUE (is_path_separator('/')); EXPECT_FALSE(is_path_separator('a')); EXPECT_FALSE(is_path_separator('+')); EXPECT_FALSE(is_path_separator(':')); + +#if LAF_WINDOWS + EXPECT_TRUE (is_path_separator('\\')); +#else + EXPECT_FALSE(is_path_separator('\\')); +#endif } TEST(FS, GetFilePath) { - EXPECT_EQ("C:\\foo", get_file_path("C:\\foo\\main.cpp")); EXPECT_EQ("C:/foo", get_file_path("C:/foo/pack.tar.gz")); EXPECT_EQ(".", get_file_path("./main.cpp")); - EXPECT_EQ(".", get_file_path(".\\main.cpp")); EXPECT_EQ("", get_file_path("\\main.cpp")); EXPECT_EQ("", get_file_path("main.cpp")); EXPECT_EQ("", get_file_path("main.")); EXPECT_EQ("", get_file_path("main")); EXPECT_EQ("C:/foo", get_file_path("C:/foo/")); - EXPECT_EQ("C:", get_file_path("C:\\")); - EXPECT_EQ("C:", get_file_path("C:\\.cpp")); EXPECT_EQ("", get_file_path(".cpp")); EXPECT_EQ("", get_file_path("")); + +#if LAF_WINDOWS + EXPECT_EQ("C:\\foo", get_file_path("C:\\foo\\main.cpp")); + EXPECT_EQ(".", get_file_path(".\\main.cpp")); + EXPECT_EQ("C:", get_file_path("C:\\")); + EXPECT_EQ("C:", get_file_path("C:\\.cpp")); +#else + EXPECT_EQ("", get_file_path("C:\\foo\\main.cpp")); + EXPECT_EQ("", get_file_path(".\\main.cpp")); + EXPECT_EQ("", get_file_path("C:\\")); + EXPECT_EQ("", get_file_path("C:\\.cpp")); +#endif } TEST(FS, GetFileName) { - EXPECT_EQ("main.cpp", get_file_name("C:\\foo\\main.cpp")); EXPECT_EQ("pack.tar.gz", get_file_name("C:/foo/pack.tar.gz")); EXPECT_EQ("main.cpp", get_file_name("./main.cpp")); - EXPECT_EQ("main.cpp", get_file_name(".\\main.cpp")); - EXPECT_EQ("main.cpp", get_file_name("\\main.cpp")); EXPECT_EQ("main.cpp", get_file_name("main.cpp")); EXPECT_EQ("main.", get_file_name("main.")); EXPECT_EQ("main", get_file_name("main")); EXPECT_EQ("", get_file_name("C:/foo/")); - EXPECT_EQ("", get_file_name("C:\\")); - EXPECT_EQ(".cpp", get_file_name("C:\\.cpp")); EXPECT_EQ(".cpp", get_file_name(".cpp")); EXPECT_EQ("", get_file_name("")); + +#if LAF_WINDOWS + EXPECT_EQ("main.cpp", get_file_name("C:\\foo\\main.cpp")); + EXPECT_EQ("main.cpp", get_file_name(".\\main.cpp")); + EXPECT_EQ("main.cpp", get_file_name("\\main.cpp")); + EXPECT_EQ("", get_file_name("C:\\")); + EXPECT_EQ(".cpp", get_file_name("C:\\.cpp")); +#else + EXPECT_EQ("C:\\foo\\main.cpp", get_file_name("C:\\foo\\main.cpp")); + EXPECT_EQ(".\\main.cpp", get_file_name(".\\main.cpp")); + EXPECT_EQ("\\main.cpp", get_file_name("\\main.cpp")); + EXPECT_EQ("C:\\", get_file_name("C:\\")); + EXPECT_EQ("C:\\.cpp", get_file_name("C:\\.cpp")); +#endif } TEST(FS, GetFileExtension) { - EXPECT_EQ("cpp", get_file_extension("C:\\foo\\main.cpp")); EXPECT_EQ("gz", get_file_extension("C:/foo/pack.tar.gz")); EXPECT_EQ("cpp", get_file_extension("./main.cpp")); - EXPECT_EQ("cpp", get_file_extension(".\\main.cpp")); - EXPECT_EQ("cpp", get_file_extension("\\main.cpp")); EXPECT_EQ("cpp", get_file_extension("main.cpp")); EXPECT_EQ("", get_file_extension("main.")); EXPECT_EQ("", get_file_extension("main")); EXPECT_EQ("", get_file_extension("C:/foo/")); - EXPECT_EQ("", get_file_extension("C:\\")); - EXPECT_EQ("cpp", get_file_extension("C:\\.cpp")); EXPECT_EQ("cpp", get_file_extension(".cpp")); EXPECT_EQ("", get_file_extension("")); + + // Same results on Windows/macOS/Linux + EXPECT_EQ("cpp", get_file_extension("C:\\foo\\main.cpp")); + EXPECT_EQ("cpp", get_file_extension(".\\main.cpp")); + EXPECT_EQ("cpp", get_file_extension("\\main.cpp")); + EXPECT_EQ("", get_file_extension("C:\\")); + EXPECT_EQ("cpp", get_file_extension("C:\\.cpp")); } TEST(FS, GetFileTitle) { - EXPECT_EQ("main", get_file_title("C:\\foo\\main.cpp")); EXPECT_EQ("pack.tar", get_file_title("C:/foo/pack.tar.gz")); EXPECT_EQ("main", get_file_title("./main.cpp")); - EXPECT_EQ("main", get_file_title(".\\main.cpp")); - EXPECT_EQ("main", get_file_title("\\main.cpp")); EXPECT_EQ("main", get_file_title("main.cpp")); EXPECT_EQ("main", get_file_title("main.")); EXPECT_EQ("main", get_file_title("main")); EXPECT_EQ("", get_file_title("C:/foo/")); - EXPECT_EQ("", get_file_title("C:\\")); - EXPECT_EQ("", get_file_title("C:\\.cpp")); EXPECT_EQ("", get_file_title(".cpp")); EXPECT_EQ("", get_file_title("")); + +#if LAF_WINDOWS + EXPECT_EQ("main", get_file_title("C:\\foo\\main.cpp")); + EXPECT_EQ("main", get_file_title(".\\main.cpp")); + EXPECT_EQ("main", get_file_title("\\main.cpp")); + EXPECT_EQ("", get_file_title("C:\\")); + EXPECT_EQ("", get_file_title("C:\\.cpp")); +#else + EXPECT_EQ("C:\\foo\\main", get_file_title("C:\\foo\\main.cpp")); + EXPECT_EQ(".\\main", get_file_title(".\\main.cpp")); + EXPECT_EQ("\\main", get_file_title("\\main.cpp")); + EXPECT_EQ("C:\\", get_file_title("C:\\")); + EXPECT_EQ("C:\\", get_file_title("C:\\.cpp")); +#endif } TEST(FS, GetFileTitleWithPath) { - EXPECT_EQ("C:\\foo\\main", get_file_title_with_path("C:\\foo\\main.cpp")); EXPECT_EQ("C:/foo/pack.tar", get_file_title_with_path("C:/foo/pack.tar.gz")); EXPECT_EQ("./main", get_file_title_with_path("./main.cpp")); - EXPECT_EQ(".\\main", get_file_title_with_path(".\\main.cpp")); - EXPECT_EQ("\\main", get_file_title_with_path("\\main.cpp")); EXPECT_EQ("main", get_file_title_with_path("main.cpp")); EXPECT_EQ("main", get_file_title_with_path("main.")); EXPECT_EQ("main", get_file_title_with_path("main")); EXPECT_EQ("C:/foo/", get_file_title_with_path("C:/foo/")); - EXPECT_EQ("C:\\", get_file_title_with_path("C:\\")); - EXPECT_EQ("C:\\", get_file_title_with_path("C:\\.cpp")); EXPECT_EQ("", get_file_title_with_path(".cpp")); EXPECT_EQ("", get_file_title_with_path("")); + + // Same results on Windows/macOS/Linux + EXPECT_EQ("C:\\foo\\main", get_file_title_with_path("C:\\foo\\main.cpp")); + EXPECT_EQ(".\\main", get_file_title_with_path(".\\main.cpp")); + EXPECT_EQ("\\main", get_file_title_with_path("\\main.cpp")); + EXPECT_EQ("C:\\", get_file_title_with_path("C:\\")); + EXPECT_EQ("C:\\", get_file_title_with_path("C:\\.cpp")); } TEST(FS, JoinPath) @@ -146,16 +183,26 @@ TEST(FS, JoinPath) EXPECT_EQ("fn", join_path("", "fn")); EXPECT_EQ("/fn", join_path("/", "fn")); EXPECT_EQ("/this"+sep+"fn", join_path("/this", "fn")); + EXPECT_EQ("C:\\path"+sep+"fn", join_path("C:\\path", "fn")); +#if LAF_WINDOWS EXPECT_EQ("C:\\path\\fn", join_path("C:\\path\\", "fn")); +#else + EXPECT_EQ("C:\\path\\/fn", join_path("C:\\path\\", "fn")); +#endif } TEST(FS, RemovePathSeparator) { - EXPECT_EQ("C:\\foo", remove_path_separator("C:\\foo\\")); EXPECT_EQ("C:/foo", remove_path_separator("C:/foo/")); EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp")); EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp/")); + +#if LAF_WINDOWS + EXPECT_EQ("C:\\foo", remove_path_separator("C:\\foo\\")); +#else + EXPECT_EQ("C:\\foo\\", remove_path_separator("C:\\foo\\")); +#endif } TEST(FS, HasFileExtension) @@ -215,6 +262,20 @@ TEST(FS, CompareFilenames) EXPECT_EQ(1, compare_filenames("a1-64-10.png", "a1-64-9.png")); } +TEST(FS, CopyFiles) +{ + std::vector data = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; + const std::string dst = "_test_copy_.tmp"; + + if (base::is_file(dst)) + base::delete_file(dst); + + base::write_file_content("_test_orig_.tmp", data.data(), data.size()); + base::copy_file("_test_orig_.tmp", dst, true); + + EXPECT_EQ(data, base::read_file_content(dst)); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/base/fs_unix.h b/base/fs_unix.h index e85238c4c..e4b0c830e 100644 --- a/base/fs_unix.h +++ b/base/fs_unix.h @@ -1,18 +1,23 @@ // LAF Base Library -// Copyright (c) 2021 Igara Studio S.A. +// Copyright (c) 2021-2024 Igara Studio S.A. // Copyright (c) 2001-2018 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. +#include "base/file_handle.h" +#include "base/ints.h" +#include "base/paths.h" +#include "base/time.h" + #include #include #include #include #include -#include // Required for PATH_MAX -#include // Required for rename() +#include +#include #include #include #include @@ -25,9 +30,6 @@ #include #endif -#include "base/paths.h" -#include "base/time.h" - #define MAXPATHLEN 1024 namespace base { @@ -67,9 +69,43 @@ void move_file(const std::string& src, const std::string& dst) std::string(std::strerror(errno))); } -void copy_file(const std::string& src, const std::string& dst, bool overwrite) +void copy_file(const std::string& src_fn, const std::string& dst_fn, + const bool overwrite) { - throw std::runtime_error("Error copying file: unimplemented"); + // First copy the file content + FileHandle src = open_file(src_fn, "rb"); + if (!src) { + throw std::runtime_error("Cannot open source file " + + std::string(std::strerror(errno))); + } + + FileHandle dst = open_file(dst_fn, "wb"); + if (!dst) { + throw std::runtime_error("Cannot open destination file " + + std::string(std::strerror(errno))); + } + + // Copy data in 4KB chunks + constexpr size_t kChunkSize = 4096; + std::vector buf(kChunkSize); + while (size_t bytes = std::fread(buf.data(), 1, buf.size(), src.get())) { + std::fwrite(buf.data(), 1, bytes, dst.get()); + } + + // Now copy file attributes (mode and owner) + struct stat sts; + stat(src_fn.c_str(), &sts); + fchmod(fileno(dst.get()), sts.st_mode); + fchown(fileno(dst.get()), sts.st_uid, sts.st_gid); + + // Check that the output file has the same mode and owner +#if _DEBUG + struct stat sts2; + stat(dst_fn.c_str(), &sts2); + ASSERT(sts.st_mode == sts2.st_mode); + ASSERT(sts.st_uid == sts2.st_uid); + ASSERT(sts.st_gid == sts2.st_gid); +#endif } void delete_file(const std::string& path) diff --git a/base/memory.cpp b/base/memory.cpp index 6414c8dfd..7a0ec3e28 100644 --- a/base/memory.cpp +++ b/base/memory.cpp @@ -1,5 +1,5 @@ // LAF Base Library -// Copyright (c) 2022 Igara Studio S.A. +// Copyright (c) 2022-2024 Igara Studio S.A. // Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -9,6 +9,8 @@ #include "config.h" #endif +#include "base/debug.h" + #include #include #include @@ -304,6 +306,10 @@ void* base_aligned_alloc(std::size_t bytes, std::size_t alignment) #if LAF_WINDOWS return _aligned_malloc(bytes, alignment); #else + ASSERT(alignment > 0); + std::size_t misaligned = (bytes % alignment); + if (misaligned > 0) + bytes += alignment - misaligned; return aligned_alloc(alignment, bytes); #endif }