DF-4802 / initlivelock.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /* DF-4802: initsd() verify livelock trigger. * * initsd() (sys/dev/raid/vinum/vinumrevive.c:526-565) loops * `while (!verified)` rewriting and re-reading each init block when the * readback does not match what was written (lines 555-560: `verified = 0` * on mismatch, no retry bound). The subdisk I/O goes through sdio() on * the raw subdisk device, which can be written concurrently * (vinumstrategy() VINUM_SD_TYPE -> sdio() directly, no state check: * sys/dev/raid/vinum/vinumrequest.c:135-139). A writer that keeps * touching the block being initialized turns the mismatch branch into an * unbounded, uninterruptible kernel loop (biowait has no PCATCH): the * ioctl thread never returns and cannot be killed. * * This driver: parent issues VINUM_SETSTATE(object_initializing, force) * then loops VINUM_SETSTATE(object_up, verify=1) over the sd; child * hammers the first 64 KB of the raw subdisk device with garbage. * * Build: cc -O -o /tmp/initlivelock /tmp/initlivelock.c * Run (root): /tmp/initlivelock <sdno> & * Expect (stock): parent wedges in-kernel (state D, kill -9 ineffective). * Expect (fix): parent exits with EIO after <=3 retries. */ #include <sys/ioccom.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #define L 'F' #define MAX_IOCTL_REPLY 256 /* mirror of struct vinum_ioctl_msg (sys/dev/raid/vinum/vinumio.h) */ struct vimsg { int index; int type; /* objecttype: 1 = sd_object */ int state; /* objectstate: 0=down 1=initializing 2=initialized 3=up */ int op; int force; int recurse; int verify; int otherobject; int rename; long long offset; int blocksize; }; struct virep { int error; char msg[MAX_IOCTL_REPLY - sizeof(int)]; }; #define VINUM_SETSTATE_DF _IOC(IOC_IN | IOC_OUT, L, 76, MAX_IOCTL_REPLY) #define T_SD 1 #define ST_INIT 1 #define ST_UP 3 int main(int argc, char **argv) { struct vimsg msg; struct virep *rep = (struct virep *)&msg; int fd, sdno, i; char dev[64]; if (argc < 2) { fprintf(stderr, "usage: %s sdno [sd-name]\n", argv[0]); return 2; } sdno = atoi(argv[1]); snprintf(dev, sizeof(dev), "/dev/vinum/sd/%s", (argc > 2) ? argv[2] : "vol.p0.s0"); fd = open("/dev/vinum/controld", O_RDWR); if (fd < 0) { perror("controld"); return 1; } /* child: hammer the first 64k of the raw sd with garbage */ if (fork() == 0) { int sfd = open(dev, O_WRONLY); char junk[4096]; long n = 0; if (sfd < 0) { perror(dev); _exit(1); } memset(junk, 0xa5, sizeof(junk)); for (;;) { junk[0] = n & 0xff; junk[1] = (n >> 8) & 0xff; pwrite(sfd, junk, sizeof(junk), (n % 16) * 4096); n++; } } /* sd -> sd_initializing */ memset(&msg, 0, sizeof(msg)); msg.index = sdno; msg.type = T_SD; msg.state = ST_INIT; msg.force = 1; ioctl(fd, VINUM_SETSTATE_DF, &msg); fprintf(stderr, "setstate(initializing): error=%d msg=%s\n", rep->error, rep->msg); if (rep->error) { fprintf(stderr, "could not enter initializing state\n"); return 1; } /* drive the init one block at a time with verify=1. * NOTE: the ioctl reply overlays the message buffer, so the * template must be re-filled before every call. */ for (i = 0; i < 500; i++) { memset(&msg, 0, sizeof(msg)); msg.index = sdno; msg.type = T_SD; msg.state = ST_UP; msg.verify = 1; ioctl(fd, VINUM_SETSTATE_DF, &msg); if (i == 0) fprintf(stderr, "first block: error=%d msg=%s\n", rep->error, rep->msg); if (rep->error != EAGAIN) { fprintf(stderr, "block %d: error=%d (%s) msg=%s -- init loop ENDED\n", i, rep->error, strerror(rep->error), rep->msg); break; } if (i % 40 == 0) fprintf(stderr, "block %d ok (EAGAIN)\n", i); } fprintf(stderr, "PARENT RETURNED FROM INIT LOOP (no livelock)\n"); return 0; } |