// locktest: flock() and fcntl() record locks, each property a program // relies on checked between a parent and a child -- the only way to see a // lock is to be someone it keeps out. #include #include #include #include #include #include #include static int bad; #define CHECK(cond, ...) do { \ if (!(cond)) { printf("locktest: FAIL " __VA_ARGS__); printf("\n"); bad++; } \ } while (0) #define PATH "/tmp/locktest" // Run `fn` in a child and return its exit status. static int child(int (*fn)(void)) { pid_t p = fork(); if (p == 0) _exit(fn()); int st = 0; waitpid(p, &st, 0); return WIFEXITED(st) ? WEXITSTATUS(st) : 99; } static int try_flock_ex(void) { int fd = open(PATH, O_RDWR); return flock(fd, LOCK_EX | LOCK_NB) == 0 ? 0 : (errno == EWOULDBLOCK ? 1 : 2); } static int try_flock_sh(void) { int fd = open(PATH, O_RDWR); return flock(fd, LOCK_SH | LOCK_NB) == 0 ? 0 : 1; } static int set(int fd, int cmd, short type, off_t start, off_t len) { struct flock fl = { .l_type = type, .l_whence = SEEK_SET, .l_start = start, .l_len = len }; return fcntl(fd, cmd, &fl); } static int try_range_10_19(void) { int fd = open(PATH, O_RDWR); return set(fd, F_SETLK, F_WRLCK, 10, 10) == 0 ? 0 : 1; } static int try_range_50_59(void) { int fd = open(PATH, O_RDWR); return set(fd, F_SETLK, F_WRLCK, 50, 10) == 0 ? 0 : 1; } static int wait_range_10_19(void) { int fd = open(PATH, O_RDWR); return set(fd, F_SETLKW, F_WRLCK, 10, 10) == 0 ? 0 : 1; } static int lock_and_exit(void) { int fd = open(PATH, O_RDWR); return set(fd, F_SETLK, F_WRLCK, 0, 0) == 0 ? 0 : 1; } int main(void) { int fd = open(PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); CHECK(fd >= 0, "open: %s", strerror(errno)); write(fd, "0123456789012345678901234567890123456789", 40); // flock: exclusive keeps everyone out, shared lets shared in. CHECK(flock(fd, LOCK_EX) == 0, "flock LOCK_EX"); CHECK(child(try_flock_ex) == 1, "a second LOCK_EX is refused with EWOULDBLOCK"); CHECK(flock(fd, LOCK_SH) == 0, "convert to LOCK_SH"); CHECK(child(try_flock_sh) == 0, "LOCK_SH alongside LOCK_SH"); CHECK(child(try_flock_ex) == 1, "LOCK_EX against LOCK_SH"); CHECK(flock(fd, LOCK_UN) == 0 && child(try_flock_ex) == 0, "LOCK_UN releases"); // Closing the last descriptor on the description drops a flock. int fd2 = open(PATH, O_RDWR); CHECK(flock(fd2, LOCK_EX) == 0, "flock on a second description"); close(fd2); CHECK(child(try_flock_ex) == 0, "close releases flock"); // flock and fcntl locks do not see each other. CHECK(flock(fd, LOCK_EX) == 0 && child(try_range_10_19) == 0, "fcntl ignores flock"); flock(fd, LOCK_UN); // Ranges: a lock on 0..39 with 15..16 unlocked is two locks. CHECK(set(fd, F_SETLK, F_WRLCK, 0, 40) == 0, "F_SETLK 0..39"); CHECK(child(try_range_50_59) == 0, "outside the range is free"); CHECK(child(try_range_10_19) == 1, "inside the range is not"); CHECK(set(fd, F_SETLK, F_UNLCK, 10, 10) == 0, "unlock 10..19"); CHECK(child(try_range_10_19) == 0, "the hole is free"); struct flock q = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 5 }; pid_t p = fork(); if (p == 0) { int cfd = open(PATH, O_RDWR); _exit(fcntl(cfd, F_GETLK, &q) == 0 && q.l_type == F_WRLCK && q.l_pid == getppid() && q.l_start == 0 && q.l_len == 10 ? 0 : 1); } int st; waitpid(p, &st, 0); CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "F_GETLK reports the holder"); // A process's record locks go when it closes *any* descriptor on the file. int fd3 = open(PATH, O_RDONLY); close(fd3); CHECK(child(try_range_10_19) == 0 && child(try_range_50_59) == 0, "closing another descriptor releases the process's locks"); // A blocked waiter is woken when the lock goes. CHECK(set(fd, F_SETLK, F_WRLCK, 0, 40) == 0, "relock"); p = fork(); if (p == 0) _exit(wait_range_10_19()); usleep(100000); CHECK(waitpid(p, &st, WNOHANG) == 0, "F_SETLKW waits"); set(fd, F_SETLK, F_UNLCK, 0, 0); waitpid(p, &st, 0); CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "F_SETLKW is woken by the unlock"); // And they go when the process exits. CHECK(child(lock_and_exit) == 0 && set(fd, F_SETLK, F_WRLCK, 0, 0) == 0, "exit releases record locks"); close(fd); unlink(PATH); printf(bad ? "locktest: %d failed\n" : "locktest: ok\n", bad); return bad != 0; }