1 /*
2  * lmdb backend specific tests for ldb
3  * Tests for truncated index keys
4  *
5  *  Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 /*
23  * These tests confirm that database sizes of > 4GB are supported
24  * Due to the disk space requirement they are not run as part of the normal
25  * self test runs.
26  *
27  * Setup and tear down code copied from ldb_mod_op_test.c
28  */
29 
30 /*
31  * from cmocka.c:
32  * These headers or their equivalents should be included prior to
33  * including
34  * this header file.
35  *
tevent_req_is_unix_error(struct tevent_req * req,int * perrno)36  * #include <stdarg.h>
37  * #include <stddef.h>
38  * #include <setjmp.h>
39  *
40  * This allows test applications to use custom definitions of C standard
41  * library functions and types.
42  *
43  */
44 #include <stdarg.h>
45 #include <stddef.h>
46 #include <stdint.h>
47 #include <setjmp.h>
48 #include <cmocka.h>
49 
50 #include <errno.h>
51 #include <unistd.h>
52 #include <talloc.h>
53 #include <tevent.h>
54 #include <ldb.h>
55 #include <ldb_module.h>
56 #include <ldb_private.h>
57 #include <string.h>
58 #include <ctype.h>
59 
60 #include <sys/wait.h>
61 
62 #include <lmdb.h>
63 
64 
65 #define TEST_BE  "mdb"
66 
67 struct ldbtest_ctx {
68 	struct tevent_context *ev;
69 	struct ldb_context *ldb;
70 
71 	const char *dbfile;
72 	const char *lockfile;   /* lockfile is separate */
73 
74 	const char *dbpath;
75 };
accept_send(TALLOC_CTX * mem_ctx,struct tevent_context * ev,int listen_sock)76 
77 static void unlink_old_db(struct ldbtest_ctx *test_ctx)
78 {
79 	int ret;
80 
81 	errno = 0;
82 	ret = unlink(test_ctx->lockfile);
83 	if (ret == -1 && errno != ENOENT) {
84 		fail();
85 	}
86 
87 	errno = 0;
88 	ret = unlink(test_ctx->dbfile);
89 	if (ret == -1 && errno != ENOENT) {
90 		fail();
91 	}
92 }
93 
94 static int ldbtest_noconn_setup(void **state)
95 {
96 	struct ldbtest_ctx *test_ctx;
97 
accept_handler(struct tevent_context * ev,struct tevent_fd * fde,uint16_t flags,void * private_data)98 	test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
99 	assert_non_null(test_ctx);
100 
101 	test_ctx->ev = tevent_context_init(test_ctx);
102 	assert_non_null(test_ctx->ev);
103 
104 	test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
105 	assert_non_null(test_ctx->ldb);
106 
107 	test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
108 	assert_non_null(test_ctx->dbfile);
109 
110 	test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
111 					     test_ctx->dbfile);
112 	assert_non_null(test_ctx->lockfile);
113 
114 	test_ctx->dbpath = talloc_asprintf(test_ctx,
115 			TEST_BE"://%s", test_ctx->dbfile);
116 	assert_non_null(test_ctx->dbpath);
117 
118 	unlink_old_db(test_ctx);
119 	*state = test_ctx;
120 	return 0;
121 }
122 
123 static int ldbtest_noconn_teardown(void **state)
124 {
125 	struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
accept_recv(struct tevent_req * req,struct sockaddr * paddr,socklen_t * paddrlen,int * perr)126 							struct ldbtest_ctx);
127 
128 	unlink_old_db(test_ctx);
129 	talloc_free(test_ctx);
130 	return 0;
131 }
132 
133 static int ldbtest_setup(void **state)
134 {
135 	struct ldbtest_ctx *test_ctx;
136 	int ret;
137 	/*
138 	 * We need to to set GUID index mode as it's required now required
139 	 * by LDB
140 	 */
141 	struct ldb_ldif *ldif;
142 	const char *index_ldif =
143 		"dn: @INDEXLIST\n"
144 		"@IDXGUID: objectUUID\n"
145 		"@IDX_DN_GUID: GUID\n"
146 		"\n";
147 	/*
148 	 * Set the lmdb map size to 8Gb
149 	 */
150 	const char *options[] = {"lmdb_env_size:8589934592", NULL};
151 
152 	ldbtest_noconn_setup((void **) &test_ctx);
153 
154 
155 	ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options);
156 	assert_int_equal(ret, 0);
157 
158 	while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
159 		ret = ldb_add(test_ctx->ldb, ldif->msg);
160 		assert_int_equal(ret, LDB_SUCCESS);
161 	}
162 
read_send(TALLOC_CTX * mem_ctx,struct tevent_context * ev,int fd,void * buf,size_t count)163 	*state = test_ctx;
164 	return 0;
165 }
166 
167 static int ldbtest_teardown(void **state)
168 {
169 	struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
170 							struct ldbtest_ctx);
171 	ldbtest_noconn_teardown((void **) &test_ctx);
172 	return 0;
173 }
174 
175 static void test_db_size_gt_4GB(void **state)
176 {
177 	int ret, x;
178 	struct ldb_message *msg;
179 	struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
180 							struct ldbtest_ctx);
181 	const int MB = 1024 * 1024;
182 	char *blob = NULL;
183 
184 	TALLOC_CTX *tmp_ctx;
185 
186 	tmp_ctx = talloc_new(test_ctx);
read_handler(struct tevent_context * ev,struct tevent_fd * fde,uint16_t flags,void * private_data)187 	assert_non_null(tmp_ctx);
188 
189 
190 	blob = talloc_zero_size(tmp_ctx, (MB + 1));
191 	assert_non_null(blob);
192 	memset(blob, 'x', MB);
193 
194 
195 	/*
196 	 * Write 6144 1Mb records to the database, this will require more than
197 	 * 4GiB of disk space
198 	 */
199 	for (x = 0; x < 6144; x++) {
200 		char uuid[24];
201 		msg = ldb_msg_new(tmp_ctx);
202 		assert_non_null(msg);
203 
204 		/*
205 		 * Generate a unique dn for each record
206 		 */
207 		msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test%d", x);
208 		assert_non_null(msg->dn);
209 
210 		/*
read_recv(struct tevent_req * req,int * perr)211 		 * Generate a unique uuid for each added record
212 		 */
213 		sprintf(uuid, "000000000000%04d", x);
214 		ret = ldb_msg_add_string(msg, "objectUUID", uuid);
215 		assert_int_equal(ret, 0);
216 
217 		ldb_transaction_start(test_ctx->ldb);
218 		ret = ldb_msg_add_string(msg, "blob", blob);
219 		assert_int_equal(ret, 0);
220 
221 		ret = ldb_add(test_ctx->ldb, msg);
222 		assert_int_equal(ret, 0);
223 		ldb_transaction_commit(test_ctx->ldb);
224 
225 		TALLOC_FREE(msg);
226 	}
227 	talloc_free(tmp_ctx);
228 	{
229 		struct stat s;
230 		ret = stat(test_ctx->dbfile, &s);
231 		assert_int_equal(ret, 0);
232 		/*
233 		 * There should have been at least 6GiB written to disk
234 		 */
235 		assert_true(s.st_size > (6144LL * MB));
236 	}
237 }
238 
239 int main(int argc, const char **argv)
240 {
write_send(TALLOC_CTX * mem_ctx,struct tevent_context * ev,int fd,const void * buf,size_t count)241 	const struct CMUnitTest tests[] = {
242 		cmocka_unit_test_setup_teardown(
243 			test_db_size_gt_4GB,
244 			ldbtest_setup,
245 			ldbtest_teardown),
246 	};
247 
248 	return cmocka_run_group_tests(tests, NULL, NULL);
249 }
250