Re: [patch v2] truncate.2: EINVAL is returned for non regular files except directories

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Quentin,

On Sun, Oct 01, 2023 at 03:57:19PM +0100, Quentin Armitage wrote:
> truncate(2) returns EINVAL if the file argument is a socket, a FIFO or
> a character or block device. The current man page indicates that
> ftruncate() returns EINVAL for an fd that does not reference a regular
> file, but for truncate() the only reason given for returning EINVAL is
> that the length is invalid.
> 
> The following test program demonstrates the errors returned by truncate():
> =====================================================

I tweaked the program a little bit.

> #define _GNU_SOURCE
> 
> #include <unistd.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <errno.h>
> #include <sys/types.h>
> #include <fcntl.h>
> #include <sys/stat.h>
> #include <sys/sysmacros.h>
> #include <string.h>
> #include <err.h>

Add a space before the '#', so that git doesn't mess with it so easily.

Also, sort(1).

> 
> struct {
>   const char *fname;

Use 4-space indent.

>   mode_t mode;
>   unsigned dev_maj;
>   unsigned dev_min;
> } nodes[] = {
>   { "/tmp/trunc_file", S_IFREG | 0666 },

Explicitly set ,0 ,0

>   { "/tmp/trunc_fifo", S_IFIFO | 0666 },
>   { "/tmp/trunc_socket", S_IFSOCK | 0666 },
>   { "/tmp/trunc_char_dev", S_IFCHR | 0666, 10, 7},  // Second Amiga mouse, /dev/amigamouse1
>   { "/tmp/trunc_blk_dev", S_IFBLK | 0666, 13, 3 },  // Was XT disk /dev/xd3
>   { "/tmp/trunc_dir", 0666 },
> };
> 
> int main(int C, char **V)

int main(void)

is legal ISO C.  Since you're not using them, you can just use void.

> {
>   int n;
>   int ret;
> 
>   for (n = 0; n < sizeof(nodes) / sizeof(nodes[0]); n++) {

We prefer C99 variables for loops (defined in the for line).
Also, 'n' should be of type size_t.

>     /* Create the nodes */
>     if (!(nodes[n].mode & S_IFMT))
>       ret = mkdir(nodes[n].fname, nodes[n].mode);
>     else
>       ret = mknod(nodes[n].fname, nodes[n].mode,
>                   makedev(nodes[n].dev_maj, nodes[n].dev_min));
> 
>     if (ret) {
>       warn("mknod(%s) errno %d", nodes[n].fname, errno);

With a recent-enough glibc, the following is a bit simpler:

warn("mknod(\"%s\"): %#m", nodes[n].fname);

>       continue;
>     }
> 
>     /* Returns EINVAL for IFSOCK, IFIFO, S_IFBLK, S_IFCHR, EISDIR for a directory */
>     ret = truncate(nodes[n].fname, 0);
> 
>     if (ret)
>       warn("truncate(\"%s\") failed with errno %s", nodes[n].fname,
>               strerrorname_np(errno));

For consistency, the same goes here:

warnc(ret == -1 ? errno : 0, "truncate(\"%s\"): %#m", nodes[n].fname);

(I also used stderr for success, since it's still an error report;
it just says there are no errors.)

warnc(3) is available via libbsd (glibc could catch up here).

>     else
>       printf("truncate(\"%s\") succeeded\n", nodes[n].fname);
> 
>     /* Remove the nodes */
>     if (!(nodes[n].mode & S_IFMT))
>       ret = rmdir(nodes[n].fname);
>     else
>       ret = unlink(nodes[n].fname);
>     if (ret)
>       warn("unlink(%s) errno %d", nodes[n].fname, errno);
>   }
> }
> =====================================================
> 
> Compile the program and run it as user root.
> 
> output (if program name is trunc) should be:
> truncate("/tmp/trunc_file") succeeded
> trunc: truncate("/tmp/trunc_fifo") failed with errno EINVAL: Invalid argument
> trunc: truncate("/tmp/trunc_socket") failed with errno EINVAL: Invalid argument
> trunc: truncate("/tmp/trunc_char_dev") failed with errno EINVAL: Invalid argument
> trunc: truncate("/tmp/trunc_blk_dev") failed with errno EINVAL: Invalid argument
> trunc: truncate("/tmp/trunc_dir") failed with errno EISDIR: Is a directory

Here's what I tweaked, if you want to just pick it:


=====================================================
 #define _GNU_SOURCE

 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <unistd.h>

struct {
    const char *fname;
    mode_t mode;
    unsigned dev_maj;
    unsigned dev_min;
} nodes[] = {
    {"/tmp/trunc_file", S_IFREG | 0666, 0, 0},
    {"/tmp/trunc_fifo", S_IFIFO | 0666, 0, 0},
    {"/tmp/trunc_socket", S_IFSOCK | 0666, 0, 0},
    {"/tmp/trunc_char_dev", S_IFCHR | 0666, 10, 7},  // Second Amiga mouse, /dev/amigamouse1
    {"/tmp/trunc_blk_dev", S_IFBLK | 0666, 13, 3},  // Was XT disk /dev/xd3
    {"/tmp/trunc_dir", 0666, 0, 0},
};

int
main(void)
{
    int  ret;

    for (size_t n = 0; n < sizeof(nodes) / sizeof(nodes[0]); n++) {
        /* Create the nodes */
        if (!(nodes[n].mode & S_IFMT))
            ret = mkdir(nodes[n].fname, nodes[n].mode);
        else
            ret = mknod(nodes[n].fname, nodes[n].mode,
                        makedev(nodes[n].dev_maj, nodes[n].dev_min));

        if (ret == -1) {
            warn("mknod(\"%s\") %#m", nodes[n].fname);
            continue;
        }

        /* Returns EINVAL for IFSOCK, IFIFO, S_IFBLK, S_IFCHR, EISDIR for a directory */
        ret = truncate(nodes[n].fname, 0);
        warnc(ret == -1 ? errno : 0, "truncate(\"%s\"): %#m", nodes[n].fname);

        /* Remove the nodes */
        ret = (nodes[n].mode & S_IFMT) ? unlink(nodes[n].fname)
                                       : rmdir(nodes[n].fname);
        if (ret == -1)
            warn("unlink(\"%s\"): %#m", nodes[n].fname);
    }
}
=====================================================
Compile the program with $(pkgconf --cflags --libs libbsd-overlay)
Run it as user root.

The output (if program name is trunc) should be:
trunc: truncate("/tmp/trunc_file"): 0: Success
trunc: truncate("/tmp/trunc_fifo"): EINVAL: Invalid argument
trunc: truncate("/tmp/trunc_socket"): EINVAL: Invalid argument
trunc: truncate("/tmp/trunc_char_dev"): EINVAL: Invalid argument
trunc: truncate("/tmp/trunc_blk_dev"): EINVAL: Invalid argument
trunc: truncate("/tmp/trunc_dir"): EISDIR: Is a directory



> 
> Signed-off-by: Quentin Armitage <quentin@xxxxxxxxxxxxxxx>
> 
> diff --git a/man2/truncate.2 b/man2/truncate.2
> index 703f598b3..44750b9e2 100644
> --- a/man2/truncate.2
> +++ b/man2/truncate.2
> @@ -112,7 +112,9 @@ and
>  .B EINVAL
>  The argument
>  .I length
> -is negative or larger than the maximum file size.
> +is negative or larger than the maximum file size,
> +or the named file is a socket, a FIFO,
> +or a block or character device.

The same page has the following below, for ftruncate(2):

       EINVAL fd does not reference a regular file or a  POSIX  shared
              memory object.

You could check that to have consistent wording in both.

Cheers,
Alex

>  .TP
>  .B EIO
>  An I/O error occurred updating the inode.
> 

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux