Skip to content

Instantly share code, notes, and snippets.

@coffeemug
Created May 11, 2012 06:55
Show Gist options
  • Save coffeemug/2658017 to your computer and use it in GitHub Desktop.
Save coffeemug/2658017 to your computer and use it in GitHub Desktop.
Ubuntu 12.04 ext4 ftruncate deadlock
/* This program demonstrates a bug in the Linux kernel version 3.2.0 using the
`ext4` filesystem.
Steps to compile:
$ gcc -o demo demo.c -laio
Steps to reproduce:
1. Run `./demo FILE`, where `FILE` is the path where the program will put the
file it operates on. The path must point into an `ext4` filesystem.
2. The program opens the file with the `O_DIRECT` flag; calls `ftruncate()` on
it; submits a hundred write operations using `libaio`; and then calls
`ftruncate()` again.
3. The second call to `ftruncate()` does not return. The process does not
respond to signals, not even `SIGKILL`. Any other process that tries to
access the file also locks up and does not respond to `SIGKILL`.
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <libaio.h>
#include <stdlib.h>
#define FIRST_FTRUNCATE_SIZE (1024 * 1024 * 20)
#define BLOCK_SIZE 4096
#define BLOCK_COUNT 100
#define IO_QUEUE_DEPTH 64
#define SECOND_FTRUNCATE_SIZE (1024 * 1024 * 40)
int main(int argc, char *argv[]) {
int res, fd, i;
void *buffer;
struct iocb **iocbs;
io_context_t ioctx;
if (argc != 2) {
fprintf(stderr, "usage: %s FILE\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_RDWR|O_CREAT|O_EXCL|O_DIRECT, 0644);
if (fd == -1) {
perror("open()");
exit(EXIT_FAILURE);
}
res = ftruncate(fd, FIRST_FTRUNCATE_SIZE);
if (res != 0) {
perror("ftruncate()");
exit(EXIT_FAILURE);
}
res = posix_memalign(&buffer, getpagesize(), BLOCK_SIZE);
if (res != 0) {
perror("posix_memalign()");
exit(EXIT_FAILURE);
}
bzero(buffer, BLOCK_SIZE);
iocbs = malloc(sizeof(struct iocb *) * BLOCK_COUNT);
if (!iocbs) {
fprintf(stderr, "malloc() = NULL\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < BLOCK_COUNT; i++) {
iocbs[i] = malloc(sizeof(struct iocb));
if (!iocbs[i]) {
fprintf(stderr, "malloc() = NULL\n");
exit(EXIT_FAILURE);
}
bzero(iocbs[i], sizeof(struct iocb));
iocbs[i]->aio_fildes = fd;
iocbs[i]->u.c.offset = i * BLOCK_SIZE;
iocbs[i]->u.c.buf = buffer;
iocbs[i]->u.c.nbytes = BLOCK_SIZE;
iocbs[i]->aio_lio_opcode = IO_CMD_PWRITE;
}
bzero(&ioctx, sizeof(io_context_t));
res = io_setup(IO_QUEUE_DEPTH, &ioctx);
if (res != 0) {
fprintf(stderr, "io_setup(): %s\n", strerror(-res));
exit(EXIT_FAILURE);
}
i = 0;
while (i < BLOCK_COUNT) {
res = io_submit(ioctx, BLOCK_COUNT - i, iocbs + i);
if (res < 0) {
fprintf(stderr, "io_submit(): %s\n", strerror(-res));
exit(EXIT_FAILURE);
}
i += res;
}
fprintf(stderr, "This might lock up..\n");
res = ftruncate(fd, SECOND_FTRUNCATE_SIZE);
if (res != 0) {
perror("ftruncate()");
exit(EXIT_FAILURE);
}
fprintf(stderr, "It didn't lock up.\n");
res = io_destroy(ioctx);
if (res != 0) {
fprintf(stderr, "io_destroy(): %s\n", strerror(-res));
exit(EXIT_FAILURE);
}
for (i = 0; i < BLOCK_COUNT; i++) {
free(iocbs[i]);
}
free(iocbs);
free(buffer);
res = close(fd);
if (res != 0) {
perror("close()");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment