/* ************************************************* * *********** README ****************************** * ************************************************* * * COMPILE : make * RUN : ./locktests -n <number of concurent process> -f <test file> [-P] * * GOAL : This test tries to stress the fcntl locking functions. A * master process sets a lock on a file region (this is called "byte * range locking"). Some slave processes try to perform operations on * this region, such as read, write, set a new lock ... The expected * results of these operations are known. If the operation result is * the same as the expected one, the test suceeds, else it fails. * * * * Slaves are concurent processes or thread. * -n <num> : Number of threads to use (mandatory). * -f <file> : Run the test on given test file defined by the -f option (mandatory). * -c <num> : Number of clients to connect before starting the tests. * * HISTORY : This program was written to stress NFSv4 locks. * EXAMPLE : ./locktests -n 50 -f /file/system/to/test * * * Vincent ROQUETA 2005 - vincent.roqueta@ext.bull.net * BULL S.A. */ #include "locktests.h" int MAXLEN = 64; int MAXTEST = 10; extern int maxClients; extern int fdServer; char message[M_SIZE]; int slaveReader; int masterReader; int slaveWriter; /* Which lock will be applied by the master process on test startup */ int LIST_LOCKS[] = { READLOCK, WRITELOCK, READLOCK, WRITELOCK, READLOCK, WRITELOCK, READLOCK, WRITELOCK, BYTELOCK_READ, BYTELOCK_WRITE }; /* The operations the slave processes will try to perform */ int LIST_TESTS[] = { WRONLY, WRONLY, RDONLY, RDONLY, READLOCK, WRITELOCK, WRITELOCK, READLOCK, BYTELOCK_READ, BYTELOCK_WRITE }; /* List of test names */ char *LIST_NAMES_TESTS[] = { "WRITE ON A READ LOCK", "WRITE ON A WRITE LOCK", "READ ON A READ LOCK", "READ ON A WRITE LOCK", "SET A READ LOCK ON A READ LOCK", "SET A WRITE LOCK ON A WRITE LOCK", "SET A WRITE LOCK ON A READ LOCK", "SET A READ LOCK ON A WRITE LOCK", "READ LOCK THE WHOLE FILE BYTE BY BYTE", "WRITE LOCK THE WHOLE FILE BYTE BY BYTE" }; /* List of expected test results, when slaves are processes */ int LIST_RESULTS_PROCESS[] = { SUCCES, SUCCES, SUCCES, SUCCES, SUCCES, ECHEC, ECHEC, ECHEC, SUCCES, SUCCES }; /* List of expected test results, when slaves are threads */ int LIST_RESULTS_THREADS[] = { SUCCES, SUCCES, SUCCES, SUCCES, SUCCES, SUCCES, SUCCES, SUCCES, ECHEC, ECHEC }; int *LIST_RESULTS = NULL; char *eType = NULL; int TOTAL_RESULT_OK[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void *slave(void *data); int (*finish) (int a); int finishProcess(int a) { exit(a); } int (*load) (void); struct dataPub dp; /* Functions to access tests/tests names/tests results*/ int testSuiv(int n) { return LIST_TESTS[n]; } int resAttSuiv(int n) { return LIST_RESULTS[n]; } char *nomTestSuiv(int n) { return LIST_NAMES_TESTS[n]; } int lockSuiv(int n) { return LIST_LOCKS[n]; } /* Verify the test result is the expected one */ int matchResult(int r, int n) { P("r=%d\n", r); if (r == LIST_RESULTS[n]) return 1; else return 0; } /* Increments the number of process which have successfully passed the test */ void counter(int r, int n) { TOTAL_RESULT_OK[n] += matchResult(r, n); } /* Special case for test 'lock file byte byte by byte'. * We ensure each byte is correctly locked. */ void validationResults(int n) { int i, u, l, fsize; struct flock request; fsize = dp.nclnt * (maxClients + 1); TOTAL_RESULT_OK[n] = 0; l = FALSE; u = TRUE; /* If the expected operation result is a success, we will have to increase the number of correct results */ if (LIST_RESULTS[n]) { l = TRUE; u = FALSE; } for (i = 0; i < fsize; i++) { request.l_type = F_WRLCK; request.l_whence = SEEK_SET; request.l_start = i; request.l_len = 1; fcntl(dp.fd, F_GETLK, &request); /* Ensure the lock is correctly set */ if (request.l_type != F_UNLCK) TOTAL_RESULT_OK[n] += l; else TOTAL_RESULT_OK[n] += u; } } int initTest(void) { P("Master opens %s\n", dp.fname); dp.fd = open(dp.fname, OPENFLAGS, MANDMODES); if (dp.fd < 0) { perror("lock test : can't open test file :"); finish(1); } P("fd=%d\n", dp.fd); return 0; } struct dataChild *initClientFork(int i) { struct dataPriv *dpr; struct dataChild *df; /* Initialize private data fields */ dpr = malloc(sizeof(struct dataPriv)); df = malloc(sizeof(struct dataChild)); dpr->whoami = i; df->dp = &dp; df->dpr = dpr; /* Initialize master to client pipe */ dp.lclnt[i] = malloc(sizeof(int) * 2); if (pipe(dp.lclnt[i]) < 0) { perror("Impossible to create pipe\n"); exit(1); } P("Initialization %d\n", i); write(0, ".", 1); return df; } int initialize(int clnt) { /* Initialize private data fields */ printf("Init\n"); dp.nclnt = clnt; dp.lclnt = malloc(sizeof(int *) * clnt); dp.lthreads = malloc(sizeof(pthread_t) * clnt); /* Initialize client to master pipe */ if (pipe(dp.master) < 0) { perror("Master pipe creation error\n"); exit(1); } printf("%s initialization\n", eType); load(); initTest(); return 0; } void cleanClient(struct dataChild *df) { int i; i = df->dpr->whoami; free(dp.lclnt[i]); free(df->dpr); free(df); } void clean(void) { free(dp.lthreads); free(dp.lclnt); } int loadProcess(void) { int i; struct dataChild *df; for (i = 0; i < dp.nclnt; i++) { df = initClientFork(i); if (!fork()) { P("Running slave num: %d\n", df->dpr->whoami); write(0, ".", 1); slave((void *)df); cleanClient(df); exit(0); } } return 0; } void lockWholeFile(struct flock *request) { request->l_whence = SEEK_SET; request->l_start = 0; /* Lock the whole file */ request->l_len = 0; } void selectTest(int n, struct s_test *test) { test->test = testSuiv(n); test->resAtt = resAttSuiv(n); test->nom = nomTestSuiv(n); test->type = lockSuiv(n); } /* Final test report */ int report(int clnt) { int rc = 0; int i; int totalClients; totalClients = clnt * (maxClients + 1); printf ("\n%s number : %d - Remote clients: %d local client 1 - Total client %d - Total concurent tests: %d\n", eType, clnt, maxClients, maxClients + 1, totalClients); printf("%s number running test successfully :\n", eType); for (i = 0; i < MAXTEST; i++) { if (TOTAL_RESULT_OK[i] != totalClients) rc = 1; printf("%d %s of %d successfully ran test : %s\n", TOTAL_RESULT_OK[i], eType, totalClients, LIST_NAMES_TESTS[i]); } return rc; } int serverSendLocal(void) { int i; /* Synchronize slave processes */ /* Configure slaves for test */ for (i = 0; i < dp.nclnt; i++) write(dp.lclnt[i][1], message, M_SIZE); return 0; } void serverSendNet(void) { writeToAllClients(message); } int serverReceiveNet(void) { int i, c; for (c = 0; c < maxClients; c++) { for (i = 0; i < dp.nclnt; i++) { serverReceiveClient(c); } } return 0; } int serverReceiveLocal(void) { int i; for (i = 0; i < dp.nclnt; i++) read(masterReader, message, M_SIZE); return 0; } int clientReceiveLocal(void) { read(slaveReader, message, M_SIZE); return 0; } int clientSend(void) { write(slaveWriter, message, M_SIZE); return 0; } int serverSend(void) { serverSendNet(); serverSendLocal(); return 0; } int serverReceive(void) { serverReceiveNet(); serverReceiveLocal(); return 0; } /* binary structure <-> ASCII functions used to ensure data will be correctly used over * the network, especially when multiples clients do not use the same hardware architecture. */ int serializeTLock(struct s_test *tLock) { memset(message, 0, M_SIZE); sprintf(message, "T:%d:%d:%d::", tLock->test, tLock->type, tLock->resAtt); return 0; } void unSerializeTLock(struct s_test *tLock) { sscanf(message, "T:%d:%d:%d::", &(tLock->test), &(tLock->type), &(tLock->resAtt)); memset(message, 0, M_SIZE); } void serializeFLock(struct flock *request) { int len, pid, start; memset(message, 0, M_SIZE); len = (int)request->l_len; pid = (int)request->l_pid; start = (int)request->l_start; /* Beware to length of integer conversions ... */ sprintf(message, "L:%hd:%hd:%d:%d:%d::", request->l_type, request->l_whence, start, len, pid); } void serializeResult(int result) { memset(message, 0, M_SIZE); sprintf(message, "R:%d::", result); } void unSerializeResult(int *result) { sscanf(message, "R:%d::", result); } void unSerializeFLock(struct flock *request) { int len, pid, start; sscanf(message, "L:%hd:%hd:%d:%d:%d::", &(request->l_type), &(request->l_whence), &start, &len, &pid); request->l_start = (off_t) start; request->l_len = (off_t) len; request->l_pid = (pid_t) pid; } int serverSendLockClient(struct flock *request, int client) { serializeFLock(request); return serverSendClient(client); } int serverSendLockLocal(struct flock *request, int slave) { serializeFLock(request); return write(dp.lclnt[slave][1], message, M_SIZE); } int getLockSection(struct flock *request) { memset(message, 0, M_SIZE); clientReceiveLocal(); unSerializeFLock(request); return 0; } int sendLockTest(struct s_test *tLock) { serializeTLock(tLock); serverSend(); return 0; } int getLockTest(struct s_test *tLock) { clientReceiveLocal(); unSerializeTLock(tLock); return 0; } int sendResult(int result) { serializeResult(result); clientSend(); return 0; } int getResults(int ntest) { int i, c; int result = 0; /* Add remote test results */ for (c = 0; c < maxClients; c++) { for (i = 0; i < dp.nclnt; i++) { serverReceiveClient(c); unSerializeResult(&result); counter(result, ntest); } } /* Add local test results */ for (i = 0; i < dp.nclnt; i++) { read(masterReader, message, M_SIZE); unSerializeResult(&result); counter(result, ntest); } return 0; } #ifdef DEBUG #define P(a,b) memset(dbg,0,16);sprintf(dbg,a,b);write(0,dbg,16); #endif /* In the case of a network use, the master of the client application si only * a 'repeater' of information. It resends server-master instructions to its own slaves. */ void masterClient(void) { fd_set fdread; struct timeval tv; int n, i, r, m, start; #ifdef DEBUG char dbg[16]; #endif struct flock lock; int t; masterReader = dp.master[0]; FD_ZERO(&fdread); tv.tv_sec = 50; tv.tv_usec = 0; n = fdServer > masterReader ? fdServer : masterReader; printf("Master Client - fdServer=%d\n", fdServer); while (1) { /* Add slave and server pipe file descriptors */ FD_ZERO(&fdread); FD_SET(fdServer, &fdread); FD_SET(masterReader, &fdread); r = select(n + 1, &fdread, NULL, NULL, &tv); if (r < 0) { perror("select:\n"); continue; } if (r == 0) { exit(0); } if (FD_ISSET(fdServer, &fdread)) { /* We just have received information from the server. * We repeat it to slaves. */ i = readFromServer(message); t = message[0]; switch (t) { case 'L': /* Lock instruction. We need to send a different section to lock to each process */ unSerializeFLock(&lock); start = lock.l_start; for (i = 0; i < dp.nclnt; i++) { lock.l_start = start + i; serializeFLock(&lock); write(dp.lclnt[i][1], message, M_SIZE); } printf("\n"); continue; case 'T': /* Test instruction. Ensure server is not sending the END(ish) instruction to end tests */ /* To be rewritten asap */ m = atoi(&(message[2])); if (m == END) break; if (m == CLEAN) printf("\n"); serverSendLocal(); continue; } break; } else { /* Else, we read information from slaves and repeat them to the server */ for (i = 0; i < dp.nclnt; i++) { r = read(masterReader, message, M_SIZE); r = write(fdServer, message, M_SIZE); if (r < 0) perror("write : "); } continue; } } /* Receive the END(ish) instruction */ /* Repeat it to the slaves */ printf("Exitting...\n"); serverSendLocal(); /* Ok, we can quit */ printf("Bye :)\n"); } int master(void) { int i, n, bl; int clnt; char tmp[MAXLEN], *buf; #ifdef DEBUG char dbg[16]; #endif struct flock request; struct s_test tLock; enum state_t state; int offset; /* A test sentence written in the file */ char phraseTest[] = "Ceci est une phrase test ecrite par le maitre dans le fichier"; bl = -1; clnt = dp.nclnt; masterReader = dp.master[0]; state = SELECT; /* Start with the first test ;) */ n = 0; printf("\n--------------------------------------\n"); while (1) { switch (state) { case SELECT: /* Select the test to perform */ printf("\n"); E("Master: SELECT"); selectTest(n, &tLock); state = tLock.type; bl = 0; if (n < MAXTEST) { memset(tmp, 0, MAXLEN); sprintf(tmp, "TEST : TRY TO %s:", LIST_NAMES_TESTS[n]); write(0, tmp, strlen(tmp)); } else state = END; P("state=%d\n", state); n += 1; continue; case RDONLY: case WRONLY: case READLOCK: P("Read lock :%d\n", state); request.l_type = F_RDLCK; state = LOCK; continue; case WRITELOCK: P("Write lock :%d\n", state); request.l_type = F_WRLCK; state = LOCK; continue; case LOCK: /* Apply the wanted lock */ E("Master: LOCK"); write(dp.fd, phraseTest, strlen(phraseTest)); lockWholeFile(&request); if (fcntl(dp.fd, F_SETLK, &request) < 0) { perror("Master: can't set lock\n"); perror("Echec\n"); exit(0); } E("Master"); state = SYNC; continue; case BYTELOCK_READ: bl = 1; request.l_type = F_RDLCK; state = SYNC; continue; case BYTELOCK_WRITE: bl = 1; request.l_type = F_WRLCK; state = SYNC; continue; case BYTELOCK: /* The main idea is to lock all the bytes in a file. Each slave process locks one byte. * * We need : * - To create a file of a length equal to the total number of slave processes * - send the exact section to lock to each slave * - ensure locks have been correctly set */ /* Create a string to record in the test file. Length is exactly the number of sub process */ P("Master: BYTELOCK: %d\n", state); buf = malloc(clnt * (maxClients + 1)); memset(buf, '*', clnt); write(dp.fd, buf, clnt); free(buf); /* Each slave process re-writes its own field to lock */ request.l_whence = SEEK_SET; request.l_start = 0; request.l_len = 1; /* Start to send sections to lock to remote process (network clients) */ for (i = 0; i < maxClients; i++) { /* Set the correct byte to lock */ offset = (i + 1) * clnt; request.l_start = (off_t) offset; serverSendLockClient(&request, i); } /* Now send sections to local processes */ for (i = 0; i < clnt; i++) { request.l_start = i; serverSendLockLocal(&request, i); } state = RESULT; continue; case SYNC: sendLockTest(&tLock); if (bl) { state = BYTELOCK; continue; } if (n < MAXTEST + 1) state = RESULT; else state = END; continue; case RESULT: /* Read results by one */ getResults(n - 1); if (bl) validationResults(n - 1); state = CLEAN; continue; case CLEAN: /* Ask the clients to stop testing ... */ tLock.test = CLEAN; serializeTLock(&tLock); serverSend(); /* ... and wait for an ack before closing */ serverReceive(); /* Ignore message content : that is only an ack */ /* close and open file */ close(dp.fd); initTest(); state = SELECT; continue; case END: tLock.test = END; serializeTLock(&tLock); serverSend(); sleep(2); break; printf("(end)\n"); exit(0); } break; } return report(clnt); } /* Slave process/thread */ void *slave(void *data) { struct dataChild *df; int i, a, result, ftest; struct s_test tLock; struct flock request; char tmp[16]; #ifdef DEBUG char dbg[16]; #endif char *phraseTest = "L'ecriture a reussi"; int len; enum state_t state; result = -1; ftest = -1; len = strlen(phraseTest); df = (struct dataChild *)data; i = df->dpr->whoami; P("Slave n=%d\n", i); slaveReader = dp.lclnt[i][0]; slaveWriter = dp.master[1]; state = SYNC; errno = 0; memset(tmp, 0, 16); while (1) { switch (state) { case SELECT: case SYNC: getLockTest(&tLock); state = tLock.test; P("Slave State=%d\n", state); continue; case RDONLY: /* Try to read a file */ P("TEST READ ONLY %d\n", RDONLY); if ((ftest = open(dp.fname, O_RDONLY | O_NONBLOCK)) < 0) { result = ECHEC; state = RESULT; if (dp.verbose) perror("Open:"); continue; } P("fd=%d\n", ftest); a = read(ftest, tmp, 16); if (a < 16) result = ECHEC; else result = SUCCES; state = RESULT; continue; case WRONLY: /* Try to write a file */ P("TEST WRITE ONLY %d\n", WRONLY); if ((ftest = open(dp.fname, O_WRONLY | O_NONBLOCK)) < 0) { result = ECHEC; state = RESULT; if (dp.verbose) perror("Open read only:"); continue; } P("fd=%d\n", ftest); if (write(ftest, phraseTest, len) < len) result = ECHEC; else result = SUCCES; state = RESULT; continue; case LOCK: case READLOCK: /* Try to read a read-locked section */ P("READ LOCK %d\n", F_RDLCK); if ((ftest = open(dp.fname, O_RDONLY | O_NONBLOCK)) < 0) { result = ECHEC; state = RESULT; if (dp.verbose) perror("Open read-write:"); continue; } P("fd=%d\n", ftest); /* Lock the whole file */ request.l_type = F_RDLCK; request.l_whence = SEEK_SET; request.l_start = 0; request.l_len = 0; if (fcntl(ftest, F_SETLK, &request) < 0) { if (dp.verbose || errno != EAGAIN) perror("RDONLY: fcntl"); result = ECHEC; } else result = SUCCES; state = RESULT; continue; case WRITELOCK: /* Try to write a file */ P("WRITE LOCK %d\n", F_WRLCK); if ((ftest = open(dp.fname, O_WRONLY | O_NONBLOCK)) < 0) { result = ECHEC; state = RESULT; if (dp.verbose) perror("\nOpen:"); continue; } /* Lock the whole file */ P("fd=%d\n", ftest); request.l_type = F_WRLCK; request.l_whence = SEEK_SET; request.l_start = 0; request.l_len = 0; if (fcntl(ftest, F_SETLK, &request) < 0) { if (dp.verbose || errno != EAGAIN) perror("\nWRONLY: fcntl"); result = ECHEC; } else result = SUCCES; state = RESULT; continue; case BYTELOCK_READ: P("BYTE LOCK READ: %d\n", state); state = BYTELOCK; continue; case BYTELOCK_WRITE: P("BYTE LOCK WRITE: %d\n", state); state = BYTELOCK; continue; case BYTELOCK: /* Wait for the exact section to lock. The whole file is sent by the master */ P("BYTE LOCK %d\n", state); getLockSection(&request); if ((ftest = open(dp.fname, O_RDWR | O_NONBLOCK)) < 0) { result = ECHEC; state = RESULT; if (dp.verbose) perror("\nOpen:"); continue; } if (fcntl(ftest, F_SETLK, &request) < 0) { if (dp.verbose || errno != EAGAIN) perror("\nBTLOCK: fcntl"); result = ECHEC; state = RESULT; continue; } /* Change the character at the given position for an easier verification */ a = lseek(ftest, request.l_start, SEEK_SET); write(ftest, "=", 1); result = SUCCES; state = RESULT; continue; case RESULT: if (result == SUCCES) write(0, "=", 1); else write(0, "x", 1); P("RESULT: %d\n", result); sendResult(result); state = SYNC; continue; case CLEAN: close(ftest); /* Send CLEAN Ack */ sendResult(result); state = SYNC; continue; case END: E("(End)\n"); finish(0); printf("Unknown command\n"); finish(1); } } } char *nextArg(int argc, char **argv, int *i) { if (((*i) + 1) < argc) { (*i) += 1; return argv[(*i)]; } return NULL; } int usage(void) { printf("locktest -n <number of process> -f <test file> [-T]\n"); printf("Number of child process must be higher than 1\n"); exit(0); return 0; } int main(int argc, char **argv) { int rc = 0; int i, nThread = 0; char *tmp; int type = 0; int clients; type = PROCESS; dp.fname = NULL; dp.verbose = 0; int server = 1; char *host; host = NULL; clients = 0; for (i = 1; i < argc; i++) { if (!strcmp("-n", argv[i])) { if (!(tmp = nextArg(argc, argv, &i))) usage(); nThread = atoi(tmp); continue; } if (!strcmp("-f", argv[i])) { if (!(dp.fname = nextArg(argc, argv, &i))) usage(); continue; } if (!strcmp("-v", argv[i])) { dp.verbose = TRUE; continue; } if (!strcmp("-c", argv[i])) { if (!(clients = atoi(nextArg(argc, argv, &i)))) usage(); continue; } if (!strcmp("--server", argv[i])) { if (!(host = nextArg(argc, argv, &i))) usage(); server = 0; continue; } printf("Ignoring unknown option: %s\n", argv[i]); } if (server) { if (!(dp.fname && nThread)) usage(); if (clients > 0) { configureServer(clients); setupClients(type, dp.fname, nThread); } } else { configureClient(host); dp.fname = malloc(512); memset(dp.fname, 0, 512); getConfiguration(&type, dp.fname, &nThread); } if (dp.verbose) printf("By process.\n"); load = loadProcess; eType = "process"; finish = finishProcess; LIST_RESULTS = LIST_RESULTS_PROCESS; initialize(nThread); if (server) { rc = master(); } else { masterClient(); free(dp.fname); } clean(); return rc; }