Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 18 Jun 2007 21:07:26 +1000 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Tim Kientzle <kientzle@FreeBSD.org>
Cc:        cvs-src@FreeBSD.org, src-committers@FreeBSD.org, cvs-all@FreeBSD.org, Colin Percival <cperciva@FreeBSD.org>
Subject:   Re: cvs commit: src/lib/libarchive archive_read_open_fd.c archive_read_open_filename.c
Message-ID:  <20070618195605.J43008@delplex.bde.org>
In-Reply-To: <46760F7A.8020209@freebsd.org>
References:  <200706180036.l5I0asac023540@repoman.freebsd.org> <4675DFC8.8060108@freebsd.org> <46760F7A.8020209@freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sun, 17 Jun 2007, Tim Kientzle wrote:

> Colin Percival wrote:
>> Tim Kientzle wrote:
>> 
>>>  I fear I'll have to avoid seeks ... tape drives on
>>>  FreeBSD seem to return garbage from lseek().
>> 
>> Is there any reason why the tape drivers can't be fixed?

lseek(2) succeeds for almost all file types except pipes.  It just
changes the file offset.  The underlying file "driver" is not
consulted except to check (f_ops->fo_flags & DFLAG_SEEKABLE).
A single fileops flag like this cannot handle any variations
between files under one fileops, and using one gives bugs like
the following:
- the (non-)seekability of fifos was broken for several years
   by not having a fileops struct in fifofs.  The generic fileops
   for "vnodes" was misused, and that fileops of course has to have
   DFLAG_SEEKABLE set so that lseek() succeeds on regular files, so
   lseek() succeded bogusly on fifos.
- devfs's fileops is used for all device files.  It of course has
   to have DFLAG_SEEKABLE set so that lseek() succeeds on disks.
   Thus lseek() succeeds bogusly for amlmost all device files except
   disks, including tapes.

The success of lseek() doesn't mean that i/o at the resulting offset
is possible or even that the resulting offset is considered when
doing i/o.  Not considering the resulting offset is the only thing
that is clearly a bug here.  Among device files, disks are approximately
the only subtype where the offset is even considered.  This works in
some cases as follows:
- physio(9) passes the offset down
- physio() also sets b_blkno for compatibility.  This has always been
   broken, since physio() blindly discards any residual bytes if the
   ofset is not a multiple of DEV_BSIZE.  This used to do bad things
   in disk drivers when userland seeks to an offset that is not a
   multiple of DEV_BSIZE.
- Now, most disks are handled by geom.  geom ignores b_blkno and
   converts the offset to a block nmber itself.  It returns EINVAL at
   i/o time if the offset is not a multiple of the sector size.  This
   is not the only way that an offset can be invalid.  geom returns the
   wrong error EIO for offsets beyond the end of the media.
Tape devices should probably work similarly -- reject offsets that they
can't handle at i/o time.  This might be all offsets except 0.

dd(1) has some messes to handle this problem.  It depends on tape
devices setting D_TAPE in their cdevsw.  At least ast and sa seem to
do this correctly.  It never trusts lseek() to determine seekability
of devices, but depends on disk devices setting D_DISK and memory
devices setting D_MEM in their cdevsw to determine seekability.  It
and other things really want a per-file D_SEEKABLE, but that is not
available, especially in userland across all OS's.  The FreeBSD ioctl
to read the cdevsw flags is also very unportable.  4.4BSD dd refuses to
seek on what it thinks are cdevs, pipes and tapes.  It uses the
MTIOCGET ioctl to attempt to determine if a devices is a tape.

> The basic problem is that an lseek() call to a tape
> drive returns success exactly as if it worked.
> Daniel O'Connor recently sent me the following
> ktrace from bsdtar doing a tar -t of an uncompressed
> tar archive from his Tandberg TS400 connected to an
> Adaptec 29160 controller:
>
>  5378 bsdtar   CALL  lseek(0x3,0,0,0x1)
>  5378 bsdtar   RET   lseek 51200/0xc800
>  5378 bsdtar   CALL  lseek(0x3,0,0x2f800,0x1)
>  5378 bsdtar   RET   lseek 245760/0x3c000
> 
> Note that the second call returns a new file position
> that's exactly 0x2f800 bytes beyond the former file
> position, even though nothing has actually happened.

Because lseek() doesn't go anywhere near the device driver.
I think it is correct for lseek() to have no physical effect
until i/o time.

> I think any of the following would be reasonable behaviors:
> * lseek() could return ESPIPE ("illegal seek")
> * lseek() could return an unchanged file offset
>   (indicating that the file position was unchanged by
>    the attempted seek).
> * lseek() could return ENOTSUP ("unsupported operation")
> As I said though, I just don't know that code well
> enough to propose a fix.

lseek(9) is trivial compared with making all drivers (or even one)
actually understand seeking :-).

Another minor problem is that top-level code blindly advances the
file offset after successful i/o, although the offset may be
meaningless.  Subsequent lseek(... SEEK_CUR) then show the bogus
offset.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20070618195605.J43008>