/*- * Public Domain 2014-present MongoDB, Inc. * Public Domain 2008-2014 WiredTiger, Inc. * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "test_util.h" #include #define HOME_SIZE 512 static char home[HOME_SIZE]; /* Program working dir lock file */ #define HOME_WR_SUFFIX ".WRNOLOCK" /* Writable dir copy no lock file */ static char home_wr[HOME_SIZE + sizeof(HOME_WR_SUFFIX)]; #define HOME_RD_SUFFIX ".RD" /* Read-only dir */ static char home_rd[HOME_SIZE + sizeof(HOME_RD_SUFFIX)]; #define HOME_RD2_SUFFIX ".RDNOLOCK" /* Read-only dir no lock file */ static char home_rd2[HOME_SIZE + sizeof(HOME_RD2_SUFFIX)]; static const char *saved_argv0; /* Program command */ static const char *const uri = "table:main"; #define ENV_CONFIG \ "create,log=(file_max=10M,archive=false,enabled)," \ "operation_tracking=(enabled=false),transaction_sync=(enabled,method=none)" #define ENV_CONFIG_RD "operation_tracking=(enabled=false),readonly=true" #define ENV_CONFIG_WR "operation_tracking=(enabled=false),readonly=false" #define MAX_VAL 4096 #define MAX_KV 10000 #define EXPECT_ERR 1 #define EXPECT_SUCCESS 0 #define OP_READ 0 #define OP_WRITE 1 static void usage(void) WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn)); static void usage(void) { fprintf(stderr, "usage: %s [-h dir]\n", progname); exit(EXIT_FAILURE); } static int run_child(const char *homedir, int op, int expect) { WT_CONNECTION *conn; WT_CURSOR *cursor; WT_SESSION *session; int i, ret; const char *cfg; /* * We expect the read-only database will allow the second read-only handle to succeed because no * one can create or set the lock file. */ if (op == OP_READ) cfg = ENV_CONFIG_RD; else cfg = ENV_CONFIG_WR; if ((ret = wiredtiger_open(homedir, NULL, cfg, &conn)) == 0) { if (expect == EXPECT_ERR) testutil_die(ret, "wiredtiger_open expected error, succeeded"); } else { if (expect == EXPECT_SUCCESS) testutil_die(ret, "wiredtiger_open expected success, error"); /* * If we expect an error and got one, we're done. */ return (0); } /* * Make sure we can read the data. */ testutil_check(conn->open_session(conn, NULL, NULL, &session)); testutil_check(session->open_cursor(session, uri, NULL, NULL, &cursor)); i = 0; while ((ret = cursor->next(cursor)) == 0) ++i; if (i != MAX_KV) testutil_die(ret, "cursor walk"); testutil_check(conn->close(conn, NULL)); return (0); } /* * Child process opens both databases readonly. */ static void open_dbs(int, const char *, const char *, const char *, const char *) WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn)); static void open_dbs(int op, const char *dir, const char *dir_wr, const char *dir_rd, const char *dir_rd2) { int expect, ret; /* * The parent has an open connection to all directories. We expect opening the writeable homes * to return an error. It is a failure if the child successfully opens that. */ expect = EXPECT_ERR; if ((ret = run_child(dir, op, expect)) != 0) testutil_die(ret, "wiredtiger_open readonly allowed"); if ((ret = run_child(dir_wr, op, expect)) != 0) testutil_die(ret, "wiredtiger_open readonly allowed"); /* * The parent must have a read-only connection open to the read-only databases. If the child is * opening read-only too, we expect success. Otherwise an error if the child attempts to open * read/write (permission error). */ if (op == OP_READ) expect = EXPECT_SUCCESS; if ((ret = run_child(dir_rd, op, expect)) != 0) testutil_die(ret, "run child 1"); if ((ret = run_child(dir_rd2, op, expect)) != 0) testutil_die(ret, "run child 2"); exit(EXIT_SUCCESS); } extern int __wt_optind; extern char *__wt_optarg; int main(int argc, char *argv[]) { WT_CONNECTION *conn, *conn2, *conn3, *conn4; WT_CURSOR *cursor; WT_ITEM data; WT_SESSION *session; uint64_t i; uint8_t buf[MAX_VAL]; int ch, op, ret, status; char cmd[512]; const char *working_dir; bool child; (void)testutil_set_progname(argv); /* * Needed unaltered for system command later. */ saved_argv0 = argv[0]; working_dir = "WT_RD"; child = false; op = OP_READ; while ((ch = __wt_getopt(progname, argc, argv, "Rh:W")) != EOF) switch (ch) { case 'R': child = true; op = OP_READ; break; case 'W': child = true; op = OP_WRITE; break; case 'h': working_dir = __wt_optarg; break; default: usage(); } argc -= __wt_optind; if (argc != 0) usage(); /* * Set up all the directory names. */ testutil_work_dir_from_path(home, sizeof(home), working_dir); testutil_check(__wt_snprintf(home_wr, sizeof(home_wr), "%s%s", home, HOME_WR_SUFFIX)); testutil_check(__wt_snprintf(home_rd, sizeof(home_rd), "%s%s", home, HOME_RD_SUFFIX)); testutil_check(__wt_snprintf(home_rd2, sizeof(home_rd2), "%s%s", home, HOME_RD2_SUFFIX)); if (!child) { testutil_make_work_dir(home); testutil_make_work_dir(home_wr); testutil_make_work_dir(home_rd); testutil_make_work_dir(home_rd2); } else /* * We are a child process, we just want to call the open_dbs with the directories we have. * The child function will exit. */ open_dbs(op, home, home_wr, home_rd, home_rd2); /* * Parent creates a database and table. Then cleanly shuts down. Then copy database to read-only * directory and chmod. Also copy database to read-only directory and remove the lock file. One * read-only database will have a lock file in the file system and the other will not. Parent * opens all databases with read-only configuration flag. Parent forks off child who tries to * also open all databases with the read-only flag. It should error on the writeable directory, * but allow it on the read-only directories. The child then confirms it can read all the data. */ /* * Run in the home directory and create the table. */ testutil_check(wiredtiger_open(home, NULL, ENV_CONFIG, &conn)); testutil_check(conn->open_session(conn, NULL, NULL, &session)); testutil_check(session->create(session, uri, "key_format=Q,value_format=u")); testutil_check(session->open_cursor(session, uri, NULL, NULL, &cursor)); /* * Write data into the table and then cleanly shut down connection. */ memset(buf, 0, sizeof(buf)); data.data = buf; data.size = MAX_VAL; for (i = 0; i < MAX_KV; ++i) { cursor->set_key(cursor, i); cursor->set_value(cursor, &data); testutil_check(cursor->insert(cursor)); } testutil_check(conn->close(conn, NULL)); /* * Copy the database. Remove any lock file from one copy and chmod the copies to be read-only * permissions. */ testutil_check(__wt_snprintf( cmd, sizeof(cmd), "cp -rp %s/* %s; rm -f %s/WiredTiger.lock", home, home_wr, home_wr)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); testutil_check(__wt_snprintf(cmd, sizeof(cmd), "cp -rp %s/* %s; chmod 0555 %s; chmod -R 0444 %s/*", home, home_rd, home_rd, home_rd)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); testutil_check(__wt_snprintf(cmd, sizeof(cmd), "cp -rp %s/* %s; rm -f %s/WiredTiger.lock; chmod 0555 %s; chmod -R 0444 %s/*", home, home_rd2, home_rd2, home_rd2, home_rd2)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); /* * Run four scenarios. Sometimes expect errors, sometimes success. * The writable database directories should always fail to allow the * child to open due to the lock file. The read-only ones will only * succeed when the child attempts read-only. * * 1. Parent has read-only handle to all databases. Child opens * read-only also. * 2. Parent has read-only handle to all databases. Child opens * read-write. * 3. Parent has read-write handle to writable databases and * read-only to read-only databases. Child opens read-only. * 4. Parent has read-write handle to writable databases and * read-only to read-only databases. Child opens read-write. */ /* * Open a connection handle to all databases. */ fprintf(stderr, " *** Expect several error messages from WT ***\n"); /* * Scenario 1. */ if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0) testutil_die(ret, "wiredtiger_open original home"); if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0) testutil_die(ret, "wiredtiger_open write nolock"); if ((ret = wiredtiger_open(home_rd, NULL, ENV_CONFIG_RD, &conn3)) != 0) testutil_die(ret, "wiredtiger_open readonly"); if ((ret = wiredtiger_open(home_rd2, NULL, ENV_CONFIG_RD, &conn4)) != 0) testutil_die(ret, "wiredtiger_open readonly nolock"); /* * Create a child to also open a connection handle to the databases. We cannot use fork here * because using fork the child inherits the same memory image. Therefore the WT process * structure is set in the child even though it should not be. So use 'system' to spawn an * entirely new process. * * The child will exit with success if its test passes. */ testutil_check(__wt_snprintf(cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); if (WEXITSTATUS(status) != 0) testutil_die(WEXITSTATUS(status), "system: %s", cmd); /* * Scenario 2. Run child with writable config. */ testutil_check(__wt_snprintf(cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); if (WEXITSTATUS(status) != 0) testutil_die(WEXITSTATUS(status), "system: %s", cmd); /* * Reopen the two writable directories and rerun the child. */ testutil_check(conn->close(conn, NULL)); testutil_check(conn2->close(conn2, NULL)); if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0) testutil_die(ret, "wiredtiger_open original home"); if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0) testutil_die(ret, "wiredtiger_open write nolock"); /* * Scenario 3. Child read-only. */ testutil_check(__wt_snprintf(cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); if (WEXITSTATUS(status) != 0) testutil_die(WEXITSTATUS(status), "system: %s", cmd); /* * Scenario 4. Run child with writable config. */ testutil_check(__wt_snprintf(cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); if (WEXITSTATUS(status) != 0) testutil_die(WEXITSTATUS(status), "system: %s", cmd); /* * Clean-up. */ testutil_check(conn->close(conn, NULL)); testutil_check(conn2->close(conn2, NULL)); testutil_check(conn3->close(conn3, NULL)); testutil_check(conn4->close(conn4, NULL)); /* * We need to chmod the read-only databases back so that they can be removed by scripts. */ testutil_check(__wt_snprintf(cmd, sizeof(cmd), "chmod 0777 %s %s", home_rd, home_rd2)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); testutil_check(__wt_snprintf(cmd, sizeof(cmd), "chmod -R 0666 %s/* %s/*", home_rd, home_rd2)); if ((status = system(cmd)) < 0) testutil_die(status, "system: %s", cmd); printf(" *** Readonly test successful ***\n"); return (EXIT_SUCCESS); }