1 : /*
2 : * Copyright (c) 2013 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : #include <ctype.h>
8 : #include <errno.h>
9 : #include <stdint.h>
10 : #include <stdio.h>
11 : #include <stdlib.h>
12 : #include <string.h>
13 : #include <pthread.h>
14 :
15 : #include "native_client/src/include/portability.h"
16 : #include "native_client/src/include/portability_io.h"
17 : #include "native_client/src/include/portability_string.h"
18 : #include "native_client/src/include/nacl_macros.h"
19 : #include "native_client/src/shared/platform/nacl_check.h"
20 : #include "native_client/src/shared/platform/posix/nacl_file_lock.h"
21 : #include "native_client/src/shared/platform/posix/nacl_file_lock_intern.h"
22 : #include "native_client/tests/lock_manager/nacl_test_util_repl.h"
23 : #include "native_client/tests/lock_manager/nacl_test_util_sexp.h"
24 :
25 : static int g_test_driver_verbosity = 0;
26 :
27 : struct NaClExitCleanupList {
28 : struct NaClExitCleanupList *next;
29 : void (*fn)(void *arg);
30 : void *arg;
31 : } *g_NaCl_exit_cleanup_list = NULL;
32 :
33 0 : void NaClExitCleanupRmdir(void *arg) {
34 0 : char *dirname = (char *) arg;
35 0 : if (g_test_driver_verbosity) {
36 0 : printf("cleanup rmdir %s\n", dirname);
37 0 : }
38 0 : (void) rmdir(dirname);
39 0 : free(arg);
40 0 : }
41 :
42 7 : void NaClExitCleanupUnlink(void *arg) {
43 7 : char *fname = (char *) arg;
44 7 : if (g_test_driver_verbosity) {
45 0 : printf("cleanup unlink %s\n", fname);
46 0 : }
47 7 : (void) unlink(fname);
48 7 : free(arg);
49 7 : }
50 :
51 7 : void NaClExitCleanupPush(void (*fn)(void *), void *arg) {
52 7 : struct NaClExitCleanupList *new_top;
53 :
54 7 : new_top = (struct NaClExitCleanupList *) malloc(sizeof *new_top);
55 21 : CHECK(NULL != new_top);
56 7 : new_top->fn = fn;
57 7 : new_top->arg = arg;
58 7 : new_top->next = g_NaCl_exit_cleanup_list;
59 7 : g_NaCl_exit_cleanup_list = new_top;
60 7 : }
61 :
62 : void NaClExitCleanupDoCleanup(void) {
63 8 : struct NaClExitCleanupList *p;
64 8 : struct NaClExitCleanupList *next;
65 :
66 30 : for (p = g_NaCl_exit_cleanup_list; NULL != p; p = next) {
67 7 : (*p->fn)(p->arg);
68 7 : next = p->next;
69 7 : free(p);
70 7 : }
71 8 : g_NaCl_exit_cleanup_list = NULL;
72 8 : }
73 :
74 : struct NaClFileLockTestImpl {
75 : struct NaClFileLockTestInterface base;
76 : pthread_mutex_t mu;
77 : pthread_cond_t cv;
78 : size_t num_files;
79 : int *file_locks;
80 : };
81 :
82 4 : static int NaClFileLockTestSetNumFiles(struct NaClFileLockTestInterface *vself,
83 4 : size_t num_files) {
84 4 : struct NaClFileLockTestImpl *self = (struct NaClFileLockTestImpl *) vself;
85 4 : size_t ix;
86 4 : int rv = 0;
87 :
88 4 : pthread_mutex_lock(&self->mu);
89 8 : for (ix = (size_t) num_files; ix < self->num_files; ++ix) {
90 0 : if (-1 != self->file_locks[ix]) {
91 0 : rv = 0;
92 : /* fatal error */
93 0 : goto quit;
94 : }
95 0 : }
96 4 : self->file_locks = realloc(self->file_locks,
97 : num_files * sizeof *self->file_locks);
98 12 : CHECK(NULL != self->file_locks);
99 : /* cast to size_t is safe, due to < MAX_FILE pre-condition */
100 22 : for (ix = self->num_files; ix < (size_t) num_files; ++ix) {
101 7 : self->file_locks[ix] = -1; /* not taken; no owner thread-id */
102 7 : }
103 4 : self->num_files = (size_t) num_files;
104 4 : rv = 1;
105 : quit:
106 4 : pthread_mutex_unlock(&self->mu);
107 4 : return rv;
108 : }
109 :
110 : static void NaClFileLockTestSetFileIdentityData(
111 37 : struct NaClFileLockTestInterface *vself,
112 37 : void (*orig)(struct NaClFileLockEntry *entry,
113 : int desc),
114 37 : struct NaClFileLockEntry *entry,
115 37 : int desc) {
116 74 : UNREFERENCED_PARAMETER(vself);
117 74 : UNREFERENCED_PARAMETER(orig);
118 37 : entry->file_dev = 0;
119 37 : entry->file_ino = desc;
120 37 : }
121 :
122 15 : static int NaClFileLockTestTakeFileLock(struct NaClFileLockTestInterface *vself,
123 15 : void (*orig)(int desc),
124 15 : int thread_number,
125 15 : int desc) {
126 15 : struct NaClFileLockTestImpl *self = (struct NaClFileLockTestImpl *) vself;
127 15 : int success = 0;
128 :
129 30 : UNREFERENCED_PARAMETER(orig);
130 15 : pthread_mutex_lock(&self->mu);
131 15 : for (;;) {
132 15 : if (self->num_files <= (size_t) desc) {
133 0 : printf("Bad descriptor %d, num_files = %d\n",
134 : desc, (int) self->num_files);
135 0 : goto quit;
136 : }
137 15 : if (-1 == self->file_locks[desc]) {
138 : /* available -- take it! */
139 15 : self->file_locks[desc] = thread_number;
140 15 : success = 1;
141 15 : goto quit;
142 : }
143 0 : pthread_cond_wait(&self->cv, &self->mu);
144 0 : }
145 : quit:
146 15 : pthread_mutex_unlock(&self->mu);
147 15 : return success;
148 : }
149 :
150 15 : static int NaClFileLockTestDropFileLock(struct NaClFileLockTestInterface *vself,
151 15 : void (*orig)(int desc),
152 15 : int thread_number,
153 15 : int desc) {
154 15 : struct NaClFileLockTestImpl *self = (struct NaClFileLockTestImpl *) vself;
155 15 : int success = 0;
156 :
157 30 : UNREFERENCED_PARAMETER(orig);
158 15 : pthread_mutex_lock(&self->mu);
159 15 : if (self->num_files <= (size_t) desc) {
160 0 : printf("Bad descriptor %d, num_files = %d\n",
161 : desc, (int) self->num_files);
162 0 : goto quit;
163 : }
164 15 : if (thread_number != self->file_locks[desc]) {
165 0 : printf("Unlock when thread %d is not holding lock on %d\n",
166 : thread_number, desc);
167 0 : goto quit;
168 : }
169 15 : self->file_locks[desc] = -1;
170 15 : success = 1;
171 15 : pthread_cond_broadcast(&self->cv);
172 : quit:
173 15 : pthread_mutex_unlock(&self->mu);
174 15 : return success;
175 : }
176 :
177 4 : void NaClFileLockTestImplCtor(struct NaClFileLockTestImpl *self) {
178 4 : self->base.set_num_files = NaClFileLockTestSetNumFiles;
179 4 : self->base.set_identity = NaClFileLockTestSetFileIdentityData;
180 4 : self->base.take_lock = NaClFileLockTestTakeFileLock;
181 4 : self->base.drop_lock = NaClFileLockTestDropFileLock;
182 4 : pthread_mutex_init(&self->mu, (pthread_mutexattr_t const *) NULL);
183 4 : pthread_cond_init(&self->cv, (pthread_condattr_t const *) NULL);
184 4 : self->num_files = 0;
185 4 : self->file_locks = NULL;
186 4 : }
187 :
188 : struct NaClFileLockTestRealFileImpl {
189 : struct NaClFileLockTestInterface base;
190 : char const *tmp_dir; /* basename for temporary files */
191 : pthread_mutex_t mu;
192 : size_t num_files; /* monotonically non-decreasing */
193 : int *desc_map; /* test uses [0..num_files), which maps to real descriptors */
194 : };
195 :
196 : static int NaClFileLockTestRealFileSetNumFiles(
197 4 : struct NaClFileLockTestInterface *vself,
198 4 : size_t num_files) {
199 4 : struct NaClFileLockTestRealFileImpl *self =
200 : (struct NaClFileLockTestRealFileImpl *) vself;
201 4 : size_t ix;
202 4 : char file_path[4096];
203 4 : int rv = 0;
204 4 : int desc;
205 :
206 4 : pthread_mutex_lock(&self->mu);
207 4 : if (num_files < self->num_files) {
208 0 : printf("RealFile set-files must be monotonically non-decreasing\n");
209 0 : rv = 0;
210 0 : goto quit;
211 : }
212 4 : self->desc_map = realloc(self->desc_map,
213 : num_files * sizeof *self->desc_map);
214 12 : CHECK(NULL != self->desc_map);
215 22 : for (ix = self->num_files; ix < num_files; ++ix) {
216 7 : if ((size_t) SNPRINTF(file_path, sizeof file_path, "%s/%"NACL_PRIdS,
217 : self->tmp_dir, ix) >= sizeof file_path) {
218 0 : printf("RealFile path too long\n");
219 0 : rv = 0;
220 0 : goto quit;
221 : }
222 7 : if (g_test_driver_verbosity > 0) {
223 0 : printf("creating file %s\n", file_path);
224 0 : }
225 7 : desc = OPEN(file_path, O_CREAT | O_WRONLY, 0777);
226 7 : if (-1 == desc) {
227 0 : printf("RealFile tmp file creation problem: %s\n", file_path);
228 0 : rv = 0;
229 0 : goto quit;
230 : }
231 7 : NaClExitCleanupPush(NaClExitCleanupUnlink, STRDUP(file_path));
232 7 : if (1 != write(desc, "0", 1)) {
233 0 : printf("RealFile tmp file (%s) write failed\n", file_path);
234 0 : rv = 0;
235 0 : goto quit;
236 : }
237 : /* save desc in lookup table */
238 7 : self->desc_map[ix] = desc;
239 7 : }
240 4 : self->num_files = num_files;
241 4 : rv = 1;
242 : quit:
243 4 : pthread_mutex_unlock(&self->mu);
244 4 : return rv;
245 : }
246 :
247 : static int NaClFileLockTestRealFileGetDesc(
248 67 : struct NaClFileLockTestRealFileImpl *self,
249 67 : int desc) {
250 67 : int real_desc;
251 :
252 67 : pthread_mutex_lock(&self->mu);
253 134 : if (desc < 0 || (size_t) desc >= self->num_files) {
254 0 : printf("RealFiles: bad desc %d\n", desc);
255 0 : real_desc = -1;
256 0 : } else {
257 67 : real_desc = self->desc_map[desc];
258 : }
259 67 : pthread_mutex_unlock(&self->mu);
260 67 : return real_desc;
261 : }
262 :
263 : static void NaClFileLockTestRealFileSetFileIdentityData(
264 37 : struct NaClFileLockTestInterface *vself,
265 37 : void (*orig)(struct NaClFileLockEntry *entry,
266 : int desc),
267 37 : struct NaClFileLockEntry *entry,
268 37 : int desc) {
269 37 : struct NaClFileLockTestRealFileImpl *self =
270 : (struct NaClFileLockTestRealFileImpl *) vself;
271 37 : int real_desc = NaClFileLockTestRealFileGetDesc(self, desc);
272 37 : if (-1 != real_desc) {
273 : /*
274 : * This is done w/o self->mu because we never close except at Dtor.
275 : * This is also why num_files is monotonic non-decreasing.
276 : */
277 37 : if (g_test_driver_verbosity) {
278 0 : printf("real identity %d\n", real_desc);
279 0 : }
280 37 : (*orig)(entry, real_desc);
281 37 : }
282 37 : }
283 :
284 : static int NaClFileLockTestRealFileTakeFileLock(
285 15 : struct NaClFileLockTestInterface *vself,
286 15 : void (*orig)(int desc),
287 15 : int thread_number,
288 15 : int desc) {
289 15 : struct NaClFileLockTestRealFileImpl *self =
290 : (struct NaClFileLockTestRealFileImpl *) vself;
291 15 : int real_desc = NaClFileLockTestRealFileGetDesc(self, desc);
292 :
293 30 : UNREFERENCED_PARAMETER(thread_number);
294 15 : if (-1 == real_desc) {
295 0 : printf("RealFileTakeFileLock: Bad descriptor %d\n", desc);
296 0 : return 0;
297 : }
298 15 : if (g_test_driver_verbosity) {
299 0 : printf("real lock %d\n", real_desc);
300 0 : }
301 15 : (*orig)(real_desc);
302 15 : return 1;
303 15 : }
304 :
305 : static int NaClFileLockTestRealFileDropFileLock(
306 15 : struct NaClFileLockTestInterface *vself,
307 15 : void (*orig)(int desc),
308 15 : int thread_number,
309 15 : int desc) {
310 15 : struct NaClFileLockTestRealFileImpl *self =
311 : (struct NaClFileLockTestRealFileImpl *) vself;
312 15 : int real_desc = NaClFileLockTestRealFileGetDesc(self, desc);
313 :
314 30 : UNREFERENCED_PARAMETER(thread_number);
315 15 : if (-1 == real_desc) {
316 0 : printf("RealFileDropFileLock: Bad descriptor %d\n", desc);
317 0 : return 0;
318 : }
319 15 : if (g_test_driver_verbosity) {
320 0 : printf("real unlock %d\n", real_desc);
321 0 : }
322 15 : (*orig)(real_desc);
323 15 : return 1;
324 15 : }
325 :
326 : void NaClFileLockTestRealFileImplCtor(
327 4 : struct NaClFileLockTestRealFileImpl *self,
328 4 : char const *tmp_dir) {
329 4 : self->base.set_num_files = NaClFileLockTestRealFileSetNumFiles;
330 4 : self->base.set_identity = NaClFileLockTestRealFileSetFileIdentityData;
331 4 : self->base.take_lock = NaClFileLockTestRealFileTakeFileLock;
332 4 : self->base.drop_lock = NaClFileLockTestRealFileDropFileLock;
333 4 : self->tmp_dir = tmp_dir;
334 4 : pthread_mutex_init(&self->mu, (pthread_mutexattr_t const *) NULL);
335 4 : self->num_files = 0;
336 4 : self->desc_map = NULL;
337 4 : }
338 :
339 0 : static void read_crash(struct NaClSexpIo *p, char const *reason) {
340 0 : fprintf(stderr, "Syntax error at line %d: %s\n", p->line_num, reason);
341 0 : exit(1);
342 0 : }
343 :
344 8 : int main(int ac, char **av) {
345 8 : int opt;
346 8 : struct NaClSexpIo input;
347 8 : FILE *iob;
348 8 : int interactive = 0;
349 8 : int verbosity = 0;
350 : /*
351 : * See build.scons for actual delay settings; default: 10
352 : * millisecond
353 : */
354 8 : size_t epsilon_delay_nanos = 10 * NACL_NANOS_PER_MILLI;
355 8 : struct NaClFileLockTestImpl test_impl;
356 8 : struct NaClFileLockTestRealFileImpl test_real_file_impl;
357 8 : struct NaClFileLockTestInterface *test_interface = NULL;
358 8 : char const *tmp_dir = NULL;
359 :
360 8 : NaClSexpIoCtor(&input, stdin, read_crash);
361 28 : while (-1 != (opt = getopt(ac, av, "d:D:f:ivVt"))) {
362 12 : switch (opt) {
363 : case 'd':
364 : epsilon_delay_nanos =
365 8 : strtoul(optarg, (char **) NULL, 0) * NACL_NANOS_PER_MILLI;
366 8 : break;
367 : case 'D':
368 4 : tmp_dir = optarg;
369 4 : break;
370 : case 'f':
371 0 : iob = fopen(optarg, "r");
372 0 : if (NULL == iob) {
373 0 : perror("nacl_file_lock_test");
374 0 : fprintf(stderr,
375 : "nacl_file_lock_test: -f test file %s could not be opened\n",
376 : optarg);
377 0 : return 1;
378 : }
379 0 : NaClSexpIoSetIob(&input, iob);
380 0 : break;
381 : case 'i':
382 0 : interactive = 1;
383 0 : break;
384 : case 't':
385 0 : g_test_driver_verbosity++;
386 0 : break;
387 : case 'v':
388 0 : verbosity++;
389 0 : break;
390 : case 'V':
391 0 : NaClSexpIncVerbosity();
392 0 : break;
393 : default:
394 0 : fprintf(stderr,
395 : "Usage: nacl_file_lock_test [-vV] [-f fn] [-d ms]\n"
396 : " [-D directory]\n"
397 : " where the flags\n\n"
398 : " -v increases the test framework verbosity level\n"
399 : " -V increases the s-expression parser verbosity level\n"
400 : "\nand\n"
401 : " -f specifies the file containing the test script\n"
402 : " (default in standard input)\n"
403 : " -d specifies the no-event transition delay in ms\n"
404 : " -D specifies the temporary directory in which test\n"
405 : " temporary files would be created\n"
406 : );
407 0 : return 1;
408 : }
409 12 : }
410 8 : if (NULL == input.iob) {
411 0 : input.iob = stdin;
412 0 : }
413 :
414 8 : atexit(NaClExitCleanupDoCleanup);
415 :
416 8 : if (NULL == tmp_dir) {
417 4 : NaClFileLockTestImplCtor(&test_impl);
418 4 : test_interface = (struct NaClFileLockTestInterface *) &test_impl;
419 4 : } else {
420 4 : if (mkdir(tmp_dir, 0777) == 0) {
421 0 : NaClExitCleanupPush(NaClExitCleanupRmdir, STRDUP(tmp_dir));
422 0 : }
423 4 : NaClFileLockTestRealFileImplCtor(&test_real_file_impl, tmp_dir);
424 4 : test_interface = (struct NaClFileLockTestInterface *) &test_real_file_impl;
425 : }
426 :
427 8 : ReadEvalPrintLoop(&input,
428 : interactive,
429 : verbosity,
430 : epsilon_delay_nanos,
431 : test_interface);
432 :
433 8 : return 0;
434 8 : }
|