1 /*
2   Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "mysql/harness/filesystem.h"
26 
27 ////////////////////////////////////////
28 // Test system include files
29 #include "test/helpers.h"
30 
31 ////////////////////////////////////////
32 // Third-party include files
33 #include "gtest/gtest.h"
34 
35 ////////////////////////////////////////
36 // Standard include files
37 
38 #include <fstream>
39 #include <iostream>
40 #include <stdexcept>
41 #include <vector>
42 
43 using std::back_inserter;
44 using std::cout;
45 using std::endl;
46 
47 using mysql_harness::Directory;
48 using mysql_harness::Path;
49 
50 Path g_here;
51 
TEST(TestFilesystem,TestPath)52 TEST(TestFilesystem, TestPath) {
53   // Testing basic path construction
54   EXPECT_EQ(Path("/data/logger.cfg"), "/data/logger.cfg");
55   EXPECT_EQ(Path("data/logger.cfg"), "data/logger.cfg");
56   EXPECT_EQ(Path("/"), "/");
57   EXPECT_EQ(Path("//"), "/");
58   EXPECT_EQ(Path("////////"), "/");
59   EXPECT_EQ(Path("/data/"), "/data");
60   EXPECT_EQ(Path("data/"), "data");
61   EXPECT_EQ(Path("data////"), "data");
62 
63   // Testing dirname function
64   EXPECT_EQ(Path("foo.cfg").dirname(), ".");
65   EXPECT_EQ(Path("foo/bar.cfg").dirname(), "foo");
66   EXPECT_EQ(Path("/foo/bar.cfg").dirname(), "/foo");
67   EXPECT_EQ(Path("/").dirname(), "/");
68 
69   // Testing basename function
70   EXPECT_EQ(Path("foo.cfg").basename(), "foo.cfg");
71   EXPECT_EQ(Path("foo/bar.cfg").basename(), "bar.cfg");
72   EXPECT_EQ(Path("/foo/bar.cfg").basename(), "bar.cfg");
73   EXPECT_EQ(Path("/").basename(), "/");
74 
75   // Testing join function (and indirectly the append function).
76   Path new_path = Path("data").join("test");
77   EXPECT_EQ(new_path, "data/test");
78 
79   std::string test_data_dir = mysql_harness::get_tests_data_dir(g_here.str());
80 
81   // Testing file status checking functions
82   EXPECT_EQ(Path(test_data_dir).type(), Path::FileType::DIRECTORY_FILE);
83 
84 #ifdef _WIN32
85   EXPECT_EQ(Path("c:").type(), Path::FileType::DIRECTORY_FILE);
86 #endif
87 
88   EXPECT_EQ(Path(test_data_dir).join("logger.cfg").type(),
89             Path::FileType::REGULAR_FILE);
90   EXPECT_EQ(Path(test_data_dir).join("does-not-exist.cfg").type(),
91             Path::FileType::FILE_NOT_FOUND);
92 
93   EXPECT_TRUE(Path(test_data_dir).is_directory());
94   EXPECT_FALSE(Path(test_data_dir).join("logger.cfg").is_directory());
95   EXPECT_FALSE(Path(test_data_dir).is_regular());
96   EXPECT_TRUE(Path(test_data_dir).join("logger.cfg").is_regular());
97 }
98 
TEST(TestFilesystem,EmptyPath)99 TEST(TestFilesystem, EmptyPath) {
100   // Testing error usage
101   EXPECT_THROW({ Path path(""); }, std::invalid_argument);
102 
103   // Default-constructed paths should be possible to create, but not
104   // to use.
105   Path path;
106   EXPECT_THROW(path.is_regular(), std::invalid_argument);
107   EXPECT_THROW(path.is_directory(), std::invalid_argument);
108   EXPECT_THROW(path.type(), std::invalid_argument);
109   EXPECT_THROW(path.append(g_here), std::invalid_argument);
110   EXPECT_THROW(path.join(g_here), std::invalid_argument);
111   EXPECT_THROW(path.basename(), std::invalid_argument);
112   EXPECT_THROW(path.dirname(), std::invalid_argument);
113   EXPECT_THROW(g_here.append(path), std::invalid_argument);
114   EXPECT_THROW(g_here.join(path), std::invalid_argument);
115 
116   // Once a real path is moved into it, all should be fine.
117   path = g_here;
118   EXPECT_EQ(path, g_here);
119   EXPECT_TRUE(path.is_directory());
120   EXPECT_FALSE(path.is_regular());
121 }
122 
TEST(TestFilesystem,TestDirectory)123 TEST(TestFilesystem, TestDirectory) {
124   std::string test_data_dir = mysql_harness::get_tests_data_dir(g_here.str());
125 
126   {
127     // These are the files in the "data" directory in the test
128     // directory. Please update it if you add more files.
129     //
130     // TODO(Mats): Do not use the data directory for this but create a
131     // dedicated directory for testing this feature.
132     Directory directory{Path(test_data_dir).join("logger.d")};
133     std::vector<Path> expect{
134         Path(test_data_dir).join("logger.d/one.cfg"),
135         Path(test_data_dir).join("logger.d/magic.cfg"),
136         Path(test_data_dir).join("logger.d/default.cfg"),
137     };
138 
139     decltype(expect) result(directory.begin(), directory.end());
140     EXPECT_SETEQ(expect, result);
141   }
142 
143   {
144     // These are files in the "data" directory in the test
145     // directory. Please update it if you add more files.
146     Directory directory{Path(test_data_dir)};
147     std::vector<Path> expect{
148         Path(test_data_dir).join("tests-bad-1.cfg"),
149         Path(test_data_dir).join("tests-bad-2.cfg"),
150         Path(test_data_dir).join("tests-bad-3.cfg"),
151     };
152 
153     decltype(expect) result(directory.glob("tests-bad*.cfg"), directory.end());
154     EXPECT_SETEQ(expect, result);
155   }
156 }
157 
158 // unfortunately it's not (reasonably) possible to make folders read-only on
159 // Windows, therefore we can run the following 2 tests only on Unix
160 // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
TEST(TestFilesystem,IsReadableIfFileCanBeRead)161 TEST(TestFilesystem, IsReadableIfFileCanBeRead) {
162 #ifndef _WIN32
163 
164   // create temporary file
165   const std::string directory = mysql_harness::get_tmp_dir("tmp");
166   std::shared_ptr<void> exit_guard(
167       nullptr, [&](void *) { mysql_harness::delete_dir_recursive(directory); });
168 
169   mysql_harness::Path path = mysql_harness::Path(directory).join("/tmp_file");
170   std::ofstream file(path.str());
171 
172   if (!file.good())
173     throw(std::runtime_error("Could not create file " + path.str()));
174 
175   // make file readable
176   chmod(path.c_str(), S_IRUSR);
177   ASSERT_TRUE(path.is_readable());
178 #endif
179 }
180 
TEST(TestFilesystem,IsNotReadableIfFileCanNotBeRead)181 TEST(TestFilesystem, IsNotReadableIfFileCanNotBeRead) {
182 #ifndef _WIN32
183 
184   // create temporary file
185   const std::string directory = mysql_harness::get_tmp_dir("tmp");
186   std::shared_ptr<void> exit_guard(
187       nullptr, [&](void *) { mysql_harness::delete_dir_recursive(directory); });
188 
189   mysql_harness::Path path = mysql_harness::Path(directory).join("/tmp_file");
190   std::ofstream file(path.str());
191 
192   if (!file.good())
193     throw(std::runtime_error("Could not create file " + path.str()));
194 
195   // make file readable
196   chmod(path.c_str(), S_IWUSR | S_IXUSR);
197   ASSERT_FALSE(path.is_readable());
198 #endif
199 }
200 
TEST(TestFilesystem,delete_dir_recursive)201 TEST(TestFilesystem, delete_dir_recursive) {
202   using mysql_harness::mkdir;
203   std::ofstream ofs;
204   mkdir("testdir", 0700);
205   mkdir("testdir/a", 0700);
206   mkdir("testdir/a/b", 0700);
207   mkdir("testdir/a/a", 0700);
208   std::ofstream().open("testdir/f");
209   std::ofstream().open("testdir/f2");
210   std::ofstream().open("testdir/a/f");
211   std::ofstream().open("testdir/a/b/f");
212   EXPECT_EQ(0, mysql_harness::delete_dir_recursive("testdir"));
213 }
214 
215 /*
216  * Tests mysql_harness::mkdir()
217  */
218 
TEST(TestFilesystem,Mkdir)219 TEST(TestFilesystem, Mkdir) {
220   constexpr auto kMode = 0700;
221 
222   auto tmp_dir = mysql_harness::get_tmp_dir("test");
223   std::shared_ptr<void> exit_guard(
224       nullptr, [&](void *) { mysql_harness::delete_dir_recursive(tmp_dir); });
225 
226   // non-recursive should fail
227   EXPECT_NE(0, mysql_harness::mkdir(tmp_dir + "/a/b/c/d", kMode));
228 
229   // recursive should be fine
230   EXPECT_EQ(0, mysql_harness::mkdir(tmp_dir + "/a/b/c/d", kMode, true));
231 
232   // make sure it really exists
233   mysql_harness::Path my_path(tmp_dir + "/a/b/c/d");
234   EXPECT_TRUE(my_path.exists());
235 
236   // we just created one, trying to recursively create it once more
237   // should succeed as 'mkdir -p' does
238   EXPECT_EQ(0, mysql_harness::mkdir(tmp_dir + "/a/b/c/d", kMode, true));
239 
240   // create a regular file and try to create a directory with the same name,
241   // that should fail
242   {
243     mysql_harness::Path file_path(tmp_dir + "/a/b/c/regular_file");
244     std::fstream f;
245     f.open(file_path.str(), std::ios::out);
246   }
247   EXPECT_NE(0,
248             mysql_harness::mkdir(tmp_dir + "/a/b/c/regular_file", kMode, true));
249 
250   // empty path should throw
251   EXPECT_THROW(mysql_harness::mkdir("", kMode, true), std::invalid_argument);
252 }
253 
main(int argc,char * argv[])254 int main(int argc, char *argv[]) {
255   g_here = Path(argv[0]).dirname();
256 
257   ::testing::InitGoogleTest(&argc, argv);
258   return RUN_ALL_TESTS();
259 }
260