From: Alejandro Colomar <alx@kernel.org>
To: Quentin Armitage <quentin@armitage.org.uk>
Cc: linux-man@vger.kernel.org
Subject: Re: [patch v2] truncate.2: EINVAL is returned for non regular files except directories
Date: Tue, 3 Oct 2023 02:17:32 +0200 [thread overview]
Message-ID: <ZRtdo6mTiMBmjGbX@debian> (raw)
In-Reply-To: <0feddd7ac2b9d59dd8c35e4b3452dfaad7d57788.camel@armitage.org.uk>
[-- Attachment #1: Type: text/plain, Size: 6906 bytes --]
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@armitage.org.uk>
>
> 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 #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
prev parent reply other threads:[~2023-10-03 0:17 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-10-01 14:57 [patch v2] truncate.2: EINVAL is returned for non regular files except directories Quentin Armitage
2023-10-03 0:17 ` Alejandro Colomar [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ZRtdo6mTiMBmjGbX@debian \
--to=alx@kernel.org \
--cc=linux-man@vger.kernel.org \
--cc=quentin@armitage.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.