From mboxrd@z Thu Jan 1 00:00:00 1970 Date: Sun, 21 Jun 2015 03:44:44 -0700 From: Anthony Martin To: 9fans@9fans.net Message-ID: <20150621104444.GA7504@dinah> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="AqsLC8rIMeq19msA" Content-Disposition: inline Subject: [9fans] Why does pread(2) modify the channel offset? Topicbox-Message-UUID: 59f72a20-ead9-11e9-9d60-3106f5b1d025 --AqsLC8rIMeq19msA Content-Type: text/plain; charset=us-ascii Content-Disposition: inline During the investigation of a failure in the Go test suite, I was led to the following question: Why does pread(2) modify the channel offset? When pread and pwrite were added to the kernel in Feb 2001, neither of them modified the underlying channel offset for a given file descriptor. This was also the behavior when the 4th Edition was released. Fast forward to a year later in May 2003. The code was reorganized slightly and pread began modifying the channel offset. I think this change was inadvertent because pwrite remained the same (i.e. not modifying the offset) and because it means that you can't mix calls to read and pread on the same file descriptor without confusion. Mixing calls is very uncommon, though, so nobody noticed at the time. A new test was added to the Go test suite that does this and it instantly caused the Plan 9 builder to fail. I've attached a small program that shows the problem. It's output looks like this: % 8c -FTVw test.c && 8l -o test test.c % ./test 0: init -> offset = 0 1: write 1 -> offset = 1 2: pwrite 1 0 -> offset = 1 3: seek 0 -> offset = 0 4: read 1 -> offset = 1 5: pread 1 0 -> offset = 2 % The pread in step five clearly affects the file offset. However, the documentation gives the impression that both pread and pwrite should update the channel offset. Note the first sentence in the following passage from pread(2): Pread and Pwrite are equivalent to a seek(2) to offset fol- lowed by a read or write. By combining the operations in a single atomic call, they more closely match the 9P protocol (see intro(5)) and, more important, permit multiprocess pro- grams to execute multiple concurrent read and write opera- tions on the same file descriptor without interference. That leaves us with two options: 1. the docs are correct and pwrite is wrong, or 2. the docs are incorrect and pread is wrong. I think the latter is more likely. (Note, also, that all of the modern Unix variants have APIs that assume the offset is unchanged after a pread or pwrite). Thoughts? Anthony --AqsLC8rIMeq19msA Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="test.c" #include #include void run(int step, int fd) { vlong ret, off; char buf[1], *msg; msg = nil; switch(step){ case 0: ret = 0; msg = "init"; break; case 1: ret = write(fd, buf, 1); msg = "write 1"; break; case 2: ret = pwrite(fd, buf, 1, 0); msg = "pwrite 1 0"; break; case 3: ret = seek(fd, 0, 0); msg = "seek 0"; break; case 4: ret = read(fd, buf, 1); msg = "read 1"; break; case 5: ret = pread(fd, buf, 1, 0); msg = "pread 1 0"; break; } if(ret == -1) sysfatal("%s failed: %r", msg); off = seek(fd, 0, 1); if(off < 0) sysfatal("seek failed: %r"); print("%d: %-10s -> offset = %lld\n", step, msg, off); } char *tmpfile = "/tmp/foo.6e907b64"; void main(void) { int fd, i; if((fd = create(tmpfile, ORDWR, 0600)) < 0) sysfatal("create failed: %r"); for(i = 0; i <= 5; i++) run(i, fd); close(fd); if(remove(tmpfile) < 0) sysfatal("remove failed: %r"); } --AqsLC8rIMeq19msA--