/* * Example usage in 3 terminals: * while :; do ./locktest; done * while :; do ./locktest; done * cd /dev/shm/locktest && while :; do od -tx1 target | grep 78; done * The latter command shouldn't detect any 78's, but it does with any of: * - flock() removed * - fstat() vs. stat() comparison removed * - close(fd); before the rename() */ #include #include #include #include #include #include #include #include #define PATH "/dev/shm/locktest" #define TARGET_FILE PATH "/target" #define TMP_FILE PATH "/tmp" int main(int argc, char **argv) { char tmp_filename[1024]; int uid = argc > 1 ? atoi(argv[1]) : 0; snprintf(tmp_filename, sizeof(tmp_filename), "%s.%u", TMP_FILE, uid); srandom(getpid()); if (mkdir(PATH, 0700) && errno != EEXIST) { perror("mkdir"); return 1; } int fd = open(tmp_filename, O_CREAT | O_WRONLY, 0600); if (fd == -1) { perror("open"); return 1; } usleep(random() & 0x3fff); if (flock(fd, LOCK_EX | LOCK_NB)) { perror("flock"); return 1; } struct stat st1, st2; if (fstat(fd, &st1) || stat(tmp_filename, &st2)) { perror("[f]stat"); /* unlink(tmp_filename); Probably already gone, but may have been recreated just now */ return 1; } if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { fprintf(stderr, "Another invocation won race\n"); return 1; } usleep(random() & 0x3fff); char buf[1024]; memset(buf, 'x', sizeof(buf)); if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { fprintf(stderr, "Partial write\n"); unlink(tmp_filename); return 1; } if (fsync(fd)) { perror("fsync"); unlink(tmp_filename); return 1; } if ((random() & 0x1f) == 0) { fprintf(stderr, "Fault injection\n"); unlink(tmp_filename); return 1; } usleep(random() & 0x3fff); if (lseek(fd, 0, SEEK_SET)) { perror("lseek"); unlink(tmp_filename); return 1; } memset(buf, 'a', sizeof(buf)); if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { fprintf(stderr, "Partial write\n"); unlink(tmp_filename); return 1; } if (fsync(fd)) { perror("fsync"); unlink(tmp_filename); return 1; } /* Don't! close(fd); */ usleep(random() & 0x3fff); if (rename(tmp_filename, TARGET_FILE)) { perror("rename"); unlink(tmp_filename); return 1; } return 0; }