1 /*-
2  * Public Domain 2014-2018 MongoDB, Inc.
3  * Public Domain 2008-2014 WiredTiger, Inc.
4  *
5  * This is free and unencumbered software released into the public domain.
6  *
7  * Anyone is free to copy, modify, publish, use, compile, sell, or
8  * distribute this software, either in source code form or as a compiled
9  * binary, for any purpose, commercial or non-commercial, and by any
10  * means.
11  *
12  * In jurisdictions that recognize copyright laws, the author or authors
13  * of this software dedicate any and all copyright interest in the
14  * software to the public domain. We make this dedication for the benefit
15  * of the public at large and to the detriment of our heirs and
16  * successors. We intend this dedication to be an overt act of
17  * relinquishment in perpetuity of all present and future rights to this
18  * software under copyright law.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26  * OTHER DEALINGS IN THE SOFTWARE.
27  */
28 
29 #include "test_util.h"
30 
31 #include <sys/wait.h>
32 
33 #define	HOME_SIZE	512
34 static char home[HOME_SIZE];		/* Program working dir lock file */
35 #define	HOME_WR_SUFFIX	".WRNOLOCK"	/* Writable dir copy no lock file */
36 static char home_wr[HOME_SIZE + sizeof(HOME_WR_SUFFIX)];
37 #define	HOME_RD_SUFFIX	".RD"		/* Read-only dir */
38 static char home_rd[HOME_SIZE + sizeof(HOME_RD_SUFFIX)];
39 #define	HOME_RD2_SUFFIX	".RDNOLOCK"	/* Read-only dir no lock file */
40 static char home_rd2[HOME_SIZE + sizeof(HOME_RD2_SUFFIX)];
41 
42 static const char *saved_argv0;		/* Program command */
43 static const char * const uri = "table:main";
44 
45 #define	ENV_CONFIG						\
46     "create,log=(file_max=10M,archive=false,enabled),"		\
47     "operation_tracking=(enabled=false),transaction_sync=(enabled,method=none)"
48 #define	ENV_CONFIG_RD "operation_tracking=(enabled=false),readonly=true"
49 #define	ENV_CONFIG_WR "operation_tracking=(enabled=false),readonly=false"
50 #define	MAX_VAL	4096
51 #define	MAX_KV	10000
52 
53 #define	EXPECT_ERR	1
54 #define	EXPECT_SUCCESS	0
55 
56 #define	OP_READ		0
57 #define	OP_WRITE	1
58 
59 static void usage(void)
60     WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn));
61 static void
usage(void)62 usage(void)
63 {
64 	fprintf(stderr, "usage: %s [-h dir]\n", progname);
65 	exit(EXIT_FAILURE);
66 }
67 
68 static int
run_child(const char * homedir,int op,int expect)69 run_child(const char *homedir, int op, int expect)
70 {
71 	WT_CONNECTION *conn;
72 	WT_CURSOR *cursor;
73 	WT_SESSION *session;
74 	int i, ret;
75 	const char *cfg;
76 
77 	/*
78 	 * We expect the read-only database will allow the second read-only
79 	 * handle to succeed because no one can create or set the lock file.
80 	 */
81 	if (op == OP_READ)
82 		cfg = ENV_CONFIG_RD;
83 	else
84 		cfg = ENV_CONFIG_WR;
85 	if ((ret = wiredtiger_open(homedir, NULL, cfg, &conn)) == 0) {
86 		if (expect == EXPECT_ERR)
87 			testutil_die(
88 			    ret, "wiredtiger_open expected error, succeeded");
89 	} else {
90 		if (expect == EXPECT_SUCCESS)
91 			testutil_die(
92 			    ret, "wiredtiger_open expected success, error");
93 		/*
94 		 * If we expect an error and got one, we're done.
95 		 */
96 		return (0);
97 	}
98 
99 	/*
100 	 * Make sure we can read the data.
101 	 */
102 	testutil_check(conn->open_session(conn, NULL, NULL, &session));
103 
104 	testutil_check(session->open_cursor(session, uri, NULL, NULL, &cursor));
105 
106 	i = 0;
107 	while ((ret = cursor->next(cursor)) == 0)
108 		++i;
109 	if (i != MAX_KV)
110 		testutil_die(ret, "cursor walk");
111 	testutil_check(conn->close(conn, NULL));
112 	return (0);
113 }
114 
115 /*
116  * Child process opens both databases readonly.
117  */
118 static void
119 open_dbs(int, const char *, const char *,
120     const char *, const char *) WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn));
121 static void
open_dbs(int op,const char * dir,const char * dir_wr,const char * dir_rd,const char * dir_rd2)122 open_dbs(int op, const char *dir,
123     const char *dir_wr, const char *dir_rd, const char *dir_rd2)
124 {
125 	int expect, ret;
126 
127 	/*
128 	 * The parent has an open connection to all directories.
129 	 * We expect opening the writeable homes to return an error.
130 	 * It is a failure if the child successfully opens that.
131 	 */
132 	expect = EXPECT_ERR;
133 	if ((ret = run_child(dir, op, expect)) != 0)
134 		testutil_die(ret, "wiredtiger_open readonly allowed");
135 	if ((ret = run_child(dir_wr, op, expect)) != 0)
136 		testutil_die(ret, "wiredtiger_open readonly allowed");
137 
138 	/*
139 	 * The parent must have a read-only connection open to the
140 	 * read-only databases.  If the child is opening read-only
141 	 * too, we expect success.  Otherwise an error if the child
142 	 * attempts to open read/write (permission error).
143 	 */
144 	if (op == OP_READ)
145 		expect = EXPECT_SUCCESS;
146 	if ((ret = run_child(dir_rd, op, expect)) != 0)
147 		testutil_die(ret, "run child 1");
148 	if ((ret = run_child(dir_rd2, op, expect)) != 0)
149 		testutil_die(ret, "run child 2");
150 	exit(EXIT_SUCCESS);
151 }
152 
153 extern int __wt_optind;
154 extern char *__wt_optarg;
155 
156 int
main(int argc,char * argv[])157 main(int argc, char *argv[])
158 {
159 	WT_CONNECTION *conn, *conn2, *conn3, *conn4;
160 	WT_CURSOR *cursor;
161 	WT_ITEM data;
162 	WT_SESSION *session;
163 	uint64_t i;
164 	uint8_t buf[MAX_VAL];
165 	int ch, op, ret, status;
166 	char cmd[512];
167 	const char *working_dir;
168 	bool child;
169 
170 	(void)testutil_set_progname(argv);
171 
172 	/*
173 	 * Needed unaltered for system command later.
174 	 */
175 	saved_argv0 = argv[0];
176 
177 	working_dir = "WT_RD";
178 	child = false;
179 	op = OP_READ;
180 	while ((ch = __wt_getopt(progname, argc, argv, "Rh:W")) != EOF)
181 		switch (ch) {
182 		case 'R':
183 			child = true;
184 			op = OP_READ;
185 			break;
186 		case 'W':
187 			child = true;
188 			op = OP_WRITE;
189 			break;
190 		case 'h':
191 			working_dir = __wt_optarg;
192 			break;
193 		default:
194 			usage();
195 		}
196 	argc -= __wt_optind;
197 	if (argc != 0)
198 		usage();
199 
200 	/*
201 	 * Set up all the directory names.
202 	 */
203 	testutil_work_dir_from_path(home, sizeof(home), working_dir);
204 	testutil_check(__wt_snprintf(
205 	    home_wr, sizeof(home_wr), "%s%s", home, HOME_WR_SUFFIX));
206 	testutil_check(__wt_snprintf(
207 	    home_rd, sizeof(home_rd), "%s%s", home, HOME_RD_SUFFIX));
208 	testutil_check(__wt_snprintf(
209 	    home_rd2, sizeof(home_rd2), "%s%s", home, HOME_RD2_SUFFIX));
210 	if (!child) {
211 		testutil_make_work_dir(home);
212 		testutil_make_work_dir(home_wr);
213 		testutil_make_work_dir(home_rd);
214 		testutil_make_work_dir(home_rd2);
215 	} else
216 		/*
217 		 * We are a child process, we just want to call
218 		 * the open_dbs with the directories we have.
219 		 * The child function will exit.
220 		 */
221 		open_dbs(op, home, home_wr, home_rd, home_rd2);
222 
223 	/*
224 	 * Parent creates a database and table.  Then cleanly shuts down.
225 	 * Then copy database to read-only directory and chmod.
226 	 * Also copy database to read-only directory and remove the lock
227 	 * file.  One read-only database will have a lock file in the
228 	 * file system and the other will not.
229 	 * Parent opens all databases with read-only configuration flag.
230 	 * Parent forks off child who tries to also open all databases
231 	 * with the read-only flag.  It should error on the writeable
232 	 * directory, but allow it on the read-only directories.
233 	 * The child then confirms it can read all the data.
234 	 */
235 	/*
236 	 * Run in the home directory and create the table.
237 	 */
238 	testutil_check(wiredtiger_open(home, NULL, ENV_CONFIG, &conn));
239 	testutil_check(conn->open_session(conn, NULL, NULL, &session));
240 	testutil_check(
241 	    session->create(session, uri, "key_format=Q,value_format=u"));
242 	testutil_check(session->open_cursor(session, uri, NULL, NULL, &cursor));
243 
244 	/*
245 	 * Write data into the table and then cleanly shut down connection.
246 	 */
247 	memset(buf, 0, sizeof(buf));
248 	data.data = buf;
249 	data.size = MAX_VAL;
250 	for (i = 0; i < MAX_KV; ++i) {
251 		cursor->set_key(cursor, i);
252 		cursor->set_value(cursor, &data);
253 		testutil_check(cursor->insert(cursor));
254 	}
255 	testutil_check(conn->close(conn, NULL));
256 
257 	/*
258 	 * Copy the database.  Remove any lock file from one copy
259 	 * and chmod the copies to be read-only permissions.
260 	 */
261 	testutil_check(__wt_snprintf(cmd, sizeof(cmd),
262 	    "cp -rp %s/* %s; rm -f %s/WiredTiger.lock",
263 	    home, home_wr, home_wr));
264 	if ((status = system(cmd)) < 0)
265 		testutil_die(status, "system: %s", cmd);
266 
267 	testutil_check(__wt_snprintf(cmd, sizeof(cmd),
268 	    "cp -rp %s/* %s; chmod 0555 %s; chmod -R 0444 %s/*",
269 	    home, home_rd, home_rd, home_rd));
270 	if ((status = system(cmd)) < 0)
271 		testutil_die(status, "system: %s", cmd);
272 
273 	testutil_check(__wt_snprintf(cmd, sizeof(cmd),
274 	    "cp -rp %s/* %s; rm -f %s/WiredTiger.lock; "
275 	    "chmod 0555 %s; chmod -R 0444 %s/*",
276 	    home, home_rd2, home_rd2, home_rd2, home_rd2));
277 	if ((status = system(cmd)) < 0)
278 		testutil_die(status, "system: %s", cmd);
279 
280 	/*
281 	 * Run four scenarios.  Sometimes expect errors, sometimes success.
282 	 * The writable database directories should always fail to allow the
283 	 * child to open due to the lock file.  The read-only ones will only
284 	 * succeed when the child attempts read-only.
285 	 *
286 	 * 1.  Parent has read-only handle to all databases.  Child opens
287 	 *     read-only also.
288 	 * 2.  Parent has read-only handle to all databases.  Child opens
289 	 *     read-write.
290 	 * 3.  Parent has read-write handle to writable databases and
291 	 *     read-only to read-only databases.  Child opens read-only.
292 	 * 4.  Parent has read-write handle to writable databases and
293 	 *     read-only to read-only databases.  Child opens read-write.
294 	 */
295 	/*
296 	 * Open a connection handle to all databases.
297 	 */
298 	fprintf(stderr, " *** Expect several error messages from WT ***\n");
299 	/*
300 	 * Scenario 1.
301 	 */
302 	if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0)
303 		testutil_die(ret, "wiredtiger_open original home");
304 	if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0)
305 		testutil_die(ret, "wiredtiger_open write nolock");
306 	if ((ret = wiredtiger_open(home_rd, NULL, ENV_CONFIG_RD, &conn3)) != 0)
307 		testutil_die(ret, "wiredtiger_open readonly");
308 	if ((ret = wiredtiger_open(home_rd2, NULL, ENV_CONFIG_RD, &conn4)) != 0)
309 		testutil_die(ret, "wiredtiger_open readonly nolock");
310 
311 	/*
312 	 * Create a child to also open a connection handle to the databases.
313 	 * We cannot use fork here because using fork the child inherits the
314 	 * same memory image.  Therefore the WT process structure is set in
315 	 * the child even though it should not be.  So use 'system' to spawn
316 	 * an entirely new process.
317 	 *
318 	 * The child will exit with success if its test passes.
319 	 */
320 	testutil_check(__wt_snprintf(
321 	    cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir));
322 	if ((status = system(cmd)) < 0)
323 		testutil_die(status, "system: %s", cmd);
324 	if (WEXITSTATUS(status) != 0)
325 		testutil_die(WEXITSTATUS(status), "system: %s", cmd);
326 
327 	/*
328 	 * Scenario 2.  Run child with writable config.
329 	 */
330 	testutil_check(__wt_snprintf(
331 	    cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir));
332 	if ((status = system(cmd)) < 0)
333 		testutil_die(status, "system: %s", cmd);
334 	if (WEXITSTATUS(status) != 0)
335 		testutil_die(WEXITSTATUS(status), "system: %s", cmd);
336 
337 	/*
338 	 * Reopen the two writable directories and rerun the child.
339 	 */
340 	testutil_check(conn->close(conn, NULL));
341 	testutil_check(conn2->close(conn2, NULL));
342 	if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0)
343 		testutil_die(ret, "wiredtiger_open original home");
344 	if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0)
345 		testutil_die(ret, "wiredtiger_open write nolock");
346 	/*
347 	 * Scenario 3.  Child read-only.
348 	 */
349 	testutil_check(__wt_snprintf(
350 	    cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir));
351 	if ((status = system(cmd)) < 0)
352 		testutil_die(status, "system: %s", cmd);
353 	if (WEXITSTATUS(status) != 0)
354 		testutil_die(WEXITSTATUS(status), "system: %s", cmd);
355 
356 	/*
357 	 * Scenario 4.  Run child with writable config.
358 	 */
359 	testutil_check(__wt_snprintf(
360 	    cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir));
361 	if ((status = system(cmd)) < 0)
362 		testutil_die(status, "system: %s", cmd);
363 	if (WEXITSTATUS(status) != 0)
364 		testutil_die(WEXITSTATUS(status), "system: %s", cmd);
365 
366 	/*
367 	 * Clean-up.
368 	 */
369 	testutil_check(conn->close(conn, NULL));
370 	testutil_check(conn2->close(conn2, NULL));
371 	testutil_check(conn3->close(conn3, NULL));
372 	testutil_check(conn4->close(conn4, NULL));
373 	/*
374 	 * We need to chmod the read-only databases back so that they can
375 	 * be removed by scripts.
376 	 */
377 	testutil_check(__wt_snprintf(
378 	    cmd, sizeof(cmd), "chmod 0777 %s %s", home_rd, home_rd2));
379 	if ((status = system(cmd)) < 0)
380 		testutil_die(status, "system: %s", cmd);
381 	testutil_check(__wt_snprintf(
382 	    cmd, sizeof(cmd), "chmod -R 0666 %s/* %s/*", home_rd, home_rd2));
383 	if ((status = system(cmd)) < 0)
384 		testutil_die(status, "system: %s", cmd);
385 	printf(" *** Readonly test successful ***\n");
386 	return (EXIT_SUCCESS);
387 }
388