2000-11-16 01:28:43

by Mikael Pettersson

[permalink] [raw]
Subject: 2.4.0-test10 truncate() change broke `dd'

2.4.0-test10 broke `dd' for block devices, due to the following
change to do_sys_truncate & do_sys_ftruncate:

diff -u --recursive --new-file v2.4.0-test9/linux/fs/open.c linux/fs/open.c
--- v2.4.0-test9/linux/fs/open.c Sun Oct 8 10:50:33 2000
+++ linux/fs/open.c Thu Oct 26 08:11:21 2000
@@ -103,7 +103,7 @@
inode = nd.dentry->d_inode;

error = -EACCES;
- if (S_ISDIR(inode->i_mode))
+ if (!S_ISREG(inode->i_mode))
goto dput_and_out;

error = permission(inode,MAY_WRITE);
@@ -164,7 +164,7 @@
dentry = file->f_dentry;
inode = dentry->d_inode;
error = -EACCES;
- if (S_ISDIR(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
+ if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
goto out_putf;
error = -EPERM;
if (IS_IMMUTABLE(inode) || IS_APPEND(inode))

I noticed because I needed to build a boot floppy with an
initial ram disk under 2.4.0-test11pre5. The standard recipe
(Documentation/ramdisk.txt) basically goes:
- dd if=bzImage of=/dev/fd0 bs=1k
notice how many blocks dd reported (NNN)
- dd if=ram_image of=/dev/fd0 bs=1k seek=NNN
dd implements the seek=NNN option by calling ftruncate() before
starting the write. This is where 2.4.0-test10 breaks, since
ftruncate on a block device now provokes an EACCES error.

Maybe `dd' is buggy and should use lseek() instead, but this has
apparently worked for a long time.

Does anyone know the reason for the S_ISDIR -> !S_ISREG change in test10?

/Mikael


2000-11-16 08:32:52

by Alexander Viro

[permalink] [raw]
Subject: Re: 2.4.0-test10 truncate() change broke `dd'



On Thu, 16 Nov 2000, Mikael Pettersson wrote:

> I noticed because I needed to build a boot floppy with an
> initial ram disk under 2.4.0-test11pre5. The standard recipe
> (Documentation/ramdisk.txt) basically goes:
> - dd if=bzImage of=/dev/fd0 bs=1k
> notice how many blocks dd reported (NNN)
> - dd if=ram_image of=/dev/fd0 bs=1k seek=NNN
> dd implements the seek=NNN option by calling ftruncate() before
> starting the write. This is where 2.4.0-test10 breaks, since
> ftruncate on a block device now provokes an EACCES error.

And what kind of meaning would you assign to truncate on floppy?

> Maybe `dd' is buggy and should use lseek() instead, but this has
> apparently worked for a long time.

Use conv=notrunc.

> Does anyone know the reason for the S_ISDIR -> !S_ISREG change in test10?

For one thing, you really don't want it working on pipes. For another -
it's just damn meaningless on devices, symlinks and sockets. Which leaves
regular files.

OTOH, -EACCES looks wrong - for directories we must return -EISDIR and for
sockets ftruncate() should return -EINVAL. Adopting -EINVAL for devices
and pipes may be a good idea... Andries, could you comment on that?

2000-11-16 12:04:02

by Mikael Pettersson

[permalink] [raw]
Subject: Re: 2.4.0-test10 truncate() change broke `dd'

On Thu, 16 Nov 2000, Alexander Viro wrote:

> And what kind of meaning would you assign to truncate on floppy?

On a block or char device, truncate == lseek seems reasonable.

My guess is that dd uses ftruncate because that's correct for
regular files and has happened to also work (as an alias for
lseek) for devices.

> Use conv=notrunc.

I didn't know about notrunc. Yet another GNU invention?

/Mikael

2000-11-16 12:23:59

by Alexander Viro

[permalink] [raw]
Subject: Re: 2.4.0-test10 truncate() change broke `dd'



On Thu, 16 Nov 2000, Mikael Pettersson wrote:

> On Thu, 16 Nov 2000, Alexander Viro wrote:
>
> > And what kind of meaning would you assign to truncate on floppy?
>
> On a block or char device, truncate == lseek seems reasonable.

Huh? On regular files ftruncate() doesn't modify the current position
at all. Try and you'll see. Besides, WTF _is_ lseek() for a character
device?

> My guess is that dd uses ftruncate because that's correct for
> regular files and has happened to also work (as an alias for
> lseek) for devices.
>
> > Use conv=notrunc.
>
> I didn't know about notrunc. Yet another GNU invention?

Maybe, but I doubt it. Anyway, it made its way into 4.4BSD, it's present
in Solaris, it's in SuS and AFAIK in POSIX. GNU might be the origin, but
it might equally well be a BSDism.

2000-11-16 15:14:30

by Andries Brouwer

[permalink] [raw]
Subject: Re: 2.4.0-test10 truncate() change broke `dd'

On Thu, Nov 16, 2000 at 06:53:30AM -0500, Alexander Viro wrote:

> > I didn't know about notrunc. Yet another GNU invention?
>
> Maybe, but I doubt it. Anyway, it made its way into 4.4BSD, it's present
> in Solaris, it's in SuS and AFAIK in POSIX.

Yes (for 1003.2-1992).
It is not in 4.3BSD.

2000-11-17 17:47:33

by Andries Brouwer

[permalink] [raw]
Subject: Re: 2.4.0-test10 truncate() change broke `dd'

On Thu, Nov 16, 2000 at 03:02:31AM -0500, Alexander Viro wrote:
: On Thu, 16 Nov 2000, Mikael Pettersson wrote:
:
:: dd implements the seek=NNN option by calling ftruncate() before
:: starting the write. This is where 2.4.0-test10 breaks, since
:: ftruncate on a block device now provokes an EACCES error.
:: Does anyone know the reason for the S_ISDIR -> !S_ISREG change in test10?
:
: For one thing, you really don't want it working on pipes. For another -
: it's just damn meaningless on devices, symlinks and sockets. Which leaves
: regular files.
:
: OTOH, -EACCES looks wrong - for directories we must return -EISDIR and for
: sockets ftruncate() should return -EINVAL. Adopting -EINVAL for devices
: and pipes may be a good idea... Andries, could you comment on that?

Well, open() must return EISDIR upon an attempt to open a
directory for writing. Similarly, truncate() must return EISDIR
for a directory. On the other hand, ftruncate() returns EINVAL
for a fd not open for writing (and not EISDIR).

The behaviour on files other than regular files and shared memory
is mostly undefined.

The Austin draft says (among other things)

=====================================================================
If fd is not a valid file descriptor open for writing, the
ftruncate( ) function shall fail. If fd refers to a regular file,
the ftruncate( ) function shall cause the size of the file to be
truncated to length. If the size of the file previously exceeded
length, the extra data shall no longer be available to reads on the
file. If the file previously was smaller than this size, ftruncate( )
shall either increase the size of the file or fail. [XSI-conformant
systems shall increase the size of the file.] If the file size is
increased, the extended area shall appear as if it were zero-filled.
The value of the seek pointer shall not be modified by a call
to ftruncate( ). Upon successful completion, if fd refers to a
regular file, the ftruncate( ) function shall mark for update the
st_ctime and st_mtime fields of the file and the S_ISUID and S_ISGID
bits of the file mode may be cleared. If the ftruncate( ) function is
unsuccessful, the file is unaffected.

If fd refers to a directory, ftruncate( ) shall fail.
If fd refers to any other file type, except a shared memory object,
the result is unspecified.

... [shm description omitted] ...

The ftruncate( ) function shall fail if:
EINTR A signal was caught during execution.
EINVAL The length argument was less than 0.
EFBIG or EINVAL The length argument was greater than the maximum file size.
EFBIG The file is a regular file and length is greater than
the offset maximum established in the open file description
associated with fd.
EIO An I/O error occurred while reading from or writing to a file system.
EBADF or EINVAL The fd argument is not a file descriptor open for writing.
EINVAL The fd argument references a file that was opened without write
permission.
EROFS The named file resides on a read-only file system.
=====================================================================

In other words, it is unspecified what happens on special files.
Of course other error returns are permitted (in non-listed situations).

Digital Unix Man says:
---------------------------------------------------------------------
The truncate() and ftruncate() functions have no effect on FIFO special
files or directories.

EAGAIN The write operation failed due to an enforced write
lock on the file.
EINVAL The file is not a regular file.
---------------------------------------------------------------------


SunOS Man says:
---------------------------------------------------------------------
EINVAL fd is not a valid descriptor of a file open for writing
or fd refers to a socket, not to a file.
---------------------------------------------------------------------


AIX Man says:
---------------------------------------------------------------------
EINVAL The file is not a regular file.
---------------------------------------------------------------------


Etc. So - there seems little doubt that if we return an error
for non-regular files, the error should be -EINVAL.

Andries