Discussion:
truncate: new module
(too old to reply)
Bruno Haible
2017-05-13 00:58:11 UTC
Permalink
Raw Message
The 'largefile' module should trigger replacement functions for all POSIX
functions that take an 'off_t' argument. In particular 'truncate'. Strangely,
gnulib does not have a 'truncate' module so far.


2017-05-13 Bruno Haible <***@clisp.org>

truncate: New module.
* lib/unistd.in.h (truncate): New declaration.
* lib/truncate.c: New file.
* m4/truncate.m4: New file.
* m4/unistd_h.m4 (gl_UNISTD_H): Test whether 'truncate' is declared.
(gl_UNISTD_H_DEFAULTS): Initialize GNULIB_TRUNCATE, HAVE_TRUNCATE,
REPLACE_TRUNCATE.
* modules/unistd (Makefile.am): Substitute GNULIB_TRUNCATE,
HAVE_TRUNCATE, REPLACE_TRUNCATE.
* modules/truncate: New file.
* tests/test-unistd-c++.cc (truncate): Test signature.
* doc/posix-functions/truncate.texi: Mention the new module.

* tests/test-truncate.c: New file.
* modules/truncate-tests: New file.

diff --git a/doc/posix-functions/truncate.texi b/doc/posix-functions/truncate.texi
index a21f098..b52f2b3 100644
--- a/doc/posix-functions/truncate.texi
+++ b/doc/posix-functions/truncate.texi
@@ -4,14 +4,10 @@

POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/truncate.html}

-Gnulib module: ---
+Gnulib module: truncate

Portability problems fixed by Gnulib:
@itemize
-@end itemize
-
-Portability problems not fixed by Gnulib:
-@itemize
@item
This function is missing on some platforms:
mingw, MSVC 9.
@@ -20,3 +16,7 @@ On platforms where @code{off_t} is a 32-bit type, this function is not
applicable to arbitrary lengths for files larger than 2 GB. The fix is to
use the @code{AC_SYS_LARGEFILE} macro.
@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
+@end itemize
diff --git a/lib/truncate.c b/lib/truncate.c
new file mode 100644
index 0000000..8ca5c0c
--- /dev/null
+++ b/lib/truncate.c
@@ -0,0 +1,51 @@
+/* truncate emulations for native Windows.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+
+int
+truncate (const char *filename, off_t length)
+{
+ int fd;
+
+ if (length == 0)
+ {
+ fd = open (filename, O_WRONLY | O_TRUNC);
+ if (fd < 0)
+ return -1;
+ }
+ else
+ {
+ fd = open (filename, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ if (ftruncate (fd, length) < 0)
+ {
+ int saved_errno = errno;
+ close (fd);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+ close (fd);
+ return 0;
+}
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index d9f741f..8222fd2 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -1457,6 +1457,36 @@ _GL_WARN_ON_USE (symlinkat, "symlinkat is not portable - "
#endif


+#if @GNULIB_TRUNCATE@
+/* Change the size of the file designated by FILENAME to become equal to LENGTH.
+ Return 0 if successful, otherwise -1 and errno set.
+ See the POSIX:2008 specification
+ <http://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html>. */
+# if @REPLACE_TRUNCATE@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# undef truncate
+# define truncate rpl_truncate
+# endif
+_GL_FUNCDECL_RPL (truncate, int, (const char *filename, off_t length)
+ _GL_ARG_NONNULL ((1)));
+_GL_CXXALIAS_RPL (truncate, int, (const char *filename, off_t length));
+# else
+# if !@HAVE_TRUNCATE@
+_GL_FUNCDECL_SYS (truncate, int, (const char *filename, off_t length)
+ _GL_ARG_NONNULL ((1)));
+# endif
+_GL_CXXALIAS_SYS (truncate, int, (const char *filename, off_t length));
+# endif
+_GL_CXXALIASWARN (truncate);
+#elif defined GNULIB_POSIXCHECK
+# undef truncate
+# if HAVE_RAW_DECL_TRUNCATE
+_GL_WARN_ON_USE (truncate, "truncate is unportable - "
+ "use gnulib module truncate for portability");
+# endif
+#endif
+
+
#if @GNULIB_TTYNAME_R@
/* Store at most BUFLEN characters of the pathname of the terminal FD is
open on in BUF. Return 0 on success, otherwise an error number. */
diff --git a/m4/truncate.m4 b/m4/truncate.m4
new file mode 100644
index 0000000..9d348eb
--- /dev/null
+++ b/m4/truncate.m4
@@ -0,0 +1,33 @@
+# truncate.m4 serial 1 -*- Autoconf -*-
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_TRUNCATE],
+[
+ AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+ AC_CHECK_FUNCS_ONCE([truncate])
+ if test $ac_cv_func_truncate = yes; then
+ m4_ifdef([gl_LARGEFILE], [
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ case "$host_os" in
+ mingw*)
+ dnl Native Windows, and Large File Support is requested.
+ dnl The mingw64 truncate64() function is based on ftruncate64(),
+ dnl which is unreliable (it may delete the file, see
+ dnl <http://mingw-w64.sourcearchive.com/documentation/2.0-1/ftruncate64_8c_source.html>).
+ dnl Use gnulib's ftruncate() and truncate() implementation instead.
+ REPLACE_TRUNCATE=1
+ ;;
+ esac
+ ], [
+ :
+ ])
+ else
+ HAVE_TRUNCATE=0
+ fi
+])
+
+# Prerequisites of lib/truncate.c.
+AC_DEFUN([gl_PREREQ_TRUNCATE], [:])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 25aef19..cc44677 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 69
+# unistd_h.m4 serial 70
dnl Copyright (C) 2006-2017 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -46,8 +46,8 @@ AC_DEFUN([gl_UNISTD_H],
gethostname getlogin getlogin_r getpagesize
getusershell setusershell endusershell
group_member isatty lchown link linkat lseek pipe pipe2 pread pwrite
- readlink readlinkat rmdir sethostname sleep symlink symlinkat ttyname_r
- unlink unlinkat usleep])
+ readlink readlinkat rmdir sethostname sleep symlink symlinkat
+ truncate ttyname_r unlink unlinkat usleep])
])

AC_DEFUN([gl_UNISTD_MODULE_INDICATOR],
@@ -102,6 +102,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
GNULIB_SLEEP=0; AC_SUBST([GNULIB_SLEEP])
GNULIB_SYMLINK=0; AC_SUBST([GNULIB_SYMLINK])
GNULIB_SYMLINKAT=0; AC_SUBST([GNULIB_SYMLINKAT])
+ GNULIB_TRUNCATE=0; AC_SUBST([GNULIB_TRUNCATE])
GNULIB_TTYNAME_R=0; AC_SUBST([GNULIB_TTYNAME_R])
GNULIB_UNISTD_H_NONBLOCKING=0; AC_SUBST([GNULIB_UNISTD_H_NONBLOCKING])
GNULIB_UNISTD_H_SIGPIPE=0; AC_SUBST([GNULIB_UNISTD_H_SIGPIPE])
@@ -139,6 +140,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
HAVE_SLEEP=1; AC_SUBST([HAVE_SLEEP])
HAVE_SYMLINK=1; AC_SUBST([HAVE_SYMLINK])
HAVE_SYMLINKAT=1; AC_SUBST([HAVE_SYMLINKAT])
+ HAVE_TRUNCATE=1; AC_SUBST([HAVE_TRUNCATE])
HAVE_UNLINKAT=1; AC_SUBST([HAVE_UNLINKAT])
HAVE_USLEEP=1; AC_SUBST([HAVE_USLEEP])
HAVE_DECL_ENVIRON=1; AC_SUBST([HAVE_DECL_ENVIRON])
@@ -179,6 +181,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
REPLACE_SLEEP=0; AC_SUBST([REPLACE_SLEEP])
REPLACE_SYMLINK=0; AC_SUBST([REPLACE_SYMLINK])
REPLACE_SYMLINKAT=0; AC_SUBST([REPLACE_SYMLINKAT])
+ REPLACE_TRUNCATE=0; AC_SUBST([REPLACE_TRUNCATE])
REPLACE_TTYNAME_R=0; AC_SUBST([REPLACE_TTYNAME_R])
REPLACE_UNLINK=0; AC_SUBST([REPLACE_UNLINK])
REPLACE_UNLINKAT=0; AC_SUBST([REPLACE_UNLINKAT])
diff --git a/modules/truncate b/modules/truncate
new file mode 100644
index 0000000..e141b1a
--- /dev/null
+++ b/modules/truncate
@@ -0,0 +1,32 @@
+Description:
+truncate() function: truncate a file to a specified length.
+
+Files:
+lib/truncate.c
+m4/truncate.m4
+
+Depends-on:
+unistd
+sys_types
+largefile
+open [test $HAVE_TRUNCATE = 0 || test $REPLACE_TRUNCATE = 1]
+ftruncate [test $HAVE_TRUNCATE = 0 || test $REPLACE_TRUNCATE = 1]
+
+configure.ac:
+gl_FUNC_TRUNCATE
+if test $HAVE_TRUNCATE = 0 || test $REPLACE_TRUNCATE = 1; then
+ AC_LIBOBJ([truncate])
+ gl_PREREQ_TRUNCATE
+fi
+gl_UNISTD_MODULE_INDICATOR([truncate])
+
+Makefile.am:
+
+Include:
+<unistd.h>
+
+License:
+GPL
+
+Maintainer:
+all
diff --git a/modules/truncate-tests b/modules/truncate-tests
new file mode 100644
index 0000000..9c1125f
--- /dev/null
+++ b/modules/truncate-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-truncate.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-truncate
+check_PROGRAMS += test-truncate
diff --git a/modules/unistd b/modules/unistd
index 8af837c..c258110 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -77,6 +77,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's/@''GNULIB_SLEEP''@/$(GNULIB_SLEEP)/g' \
-e 's/@''GNULIB_SYMLINK''@/$(GNULIB_SYMLINK)/g' \
-e 's/@''GNULIB_SYMLINKAT''@/$(GNULIB_SYMLINKAT)/g' \
+ -e 's/@''GNULIB_TRUNCATE''@/$(GNULIB_TRUNCATE)/g' \
-e 's/@''GNULIB_TTYNAME_R''@/$(GNULIB_TTYNAME_R)/g' \
-e 's/@''GNULIB_UNISTD_H_GETOPT''@/0$(GNULIB_${gl_include_guard_prefix}_UNISTD_H_GETOPT)/g' \
-e 's/@''GNULIB_UNISTD_H_NONBLOCKING''@/$(GNULIB_UNISTD_H_NONBLOCKING)/g' \
@@ -114,6 +115,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's|@''HAVE_SLEEP''@|$(HAVE_SLEEP)|g' \
-e 's|@''HAVE_SYMLINK''@|$(HAVE_SYMLINK)|g' \
-e 's|@''HAVE_SYMLINKAT''@|$(HAVE_SYMLINKAT)|g' \
+ -e 's|@''HAVE_TRUNCATE''@|$(HAVE_TRUNCATE)|g' \
-e 's|@''HAVE_UNLINKAT''@|$(HAVE_UNLINKAT)|g' \
-e 's|@''HAVE_USLEEP''@|$(HAVE_USLEEP)|g' \
-e 's|@''HAVE_DECL_ENVIRON''@|$(HAVE_DECL_ENVIRON)|g' \
@@ -155,6 +157,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's|@''REPLACE_SLEEP''@|$(REPLACE_SLEEP)|g' \
-e 's|@''REPLACE_SYMLINK''@|$(REPLACE_SYMLINK)|g' \
-e 's|@''REPLACE_SYMLINKAT''@|$(REPLACE_SYMLINKAT)|g' \
+ -e 's|@''REPLACE_TRUNCATE''@|$(REPLACE_TRUNCATE)|g' \
-e 's|@''REPLACE_TTYNAME_R''@|$(REPLACE_TTYNAME_R)|g' \
-e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
-e 's|@''REPLACE_UNLINKAT''@|$(REPLACE_UNLINKAT)|g' \
diff --git a/tests/test-truncate.c b/tests/test-truncate.c
new file mode 100644
index 0000000..f5b72e1
--- /dev/null
+++ b/tests/test-truncate.c
@@ -0,0 +1,110 @@
+/* Test truncating a file.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (truncate, int, (const char *, off_t));
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include "ignore-value.h"
+#include "macros.h"
+
+#define BASE "test-truncate.t"
+
+int
+main (int argc, char *argv[])
+{
+ /* Clean up any trash from prior testsuite runs. */
+ ignore_value (system ("rm -rf " BASE "*"));
+
+ {
+ int fd = open (BASE "file", O_RDWR | O_TRUNC | O_CREAT, 0600);
+ ASSERT (fd >= 0);
+ ASSERT (write (fd, "Hello", 5) == 5);
+ close (fd);
+ }
+
+ {
+ int fd = open (BASE "file", O_RDONLY);
+ ASSERT (fd >= 0);
+ ASSERT (lseek (fd, 0, SEEK_END) == 5);
+ close (fd);
+ }
+
+ /* Test increasing the size. */
+ ASSERT (truncate (BASE "file", 314159) == 0);
+ {
+ int fd = open (BASE "file", O_RDONLY);
+ ASSERT (fd >= 0);
+ ASSERT (lseek (fd, 0, SEEK_END) == 314159);
+ close (fd);
+ }
+
+ /* Test reducing the size. */
+ ASSERT (truncate (BASE "file", 3) == 0);
+ {
+ int fd = open (BASE "file", O_RDONLY);
+ ASSERT (fd >= 0);
+ ASSERT (lseek (fd, 0, SEEK_END) == 3);
+ close (fd);
+ }
+
+ /* Test reducing the size to 0. */
+ ASSERT (truncate (BASE "file", 0) == 0);
+ {
+ int fd = open (BASE "file", O_RDONLY);
+ ASSERT (fd >= 0);
+ ASSERT (lseek (fd, 0, SEEK_END) == 0);
+ close (fd);
+ }
+
+ /* Test behaviour for nonexistent files. */
+ {
+ errno = 0;
+ ASSERT (truncate ("/nonexistent", 0) == -1);
+ ASSERT (errno == ENOENT);
+ }
+ /* Test behaviour for directories. */
+ {
+ errno = 0;
+ ASSERT (truncate (".", 0) == -1);
+ ASSERT (errno == EISDIR
+ || /* on native Windows */ errno == EACCES);
+ }
+ /* Test behaviour for trailing slashes. */
+ {
+ errno = 0;
+ ASSERT (truncate (BASE "file/", 0) == -1);
+ ASSERT (errno == ENOTDIR
+ || /* on native Windows */ errno == EINVAL);
+ }
+ /* Test behaviour for invalid lengths. */
+ {
+ errno = 0;
+ ASSERT (truncate (BASE "file", -3) == -1);
+ ASSERT (errno == EINVAL);
+ }
+
+ /* Cleanup. */
+ ASSERT (unlink (BASE "file") == 0);
+
+ return 0;
+}
diff --git a/tests/test-unistd-c++.cc b/tests/test-unistd-c++.cc
index a19acc8..14cdb4a 100644
--- a/tests/test-unistd-c++.cc
+++ b/tests/test-unistd-c++.cc
@@ -200,6 +200,10 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::symlinkat, int,
(char const *, int, char const *));
#endif

+#if GNULIB_TEST_TRUNCATE
+SIGNATURE_CHECK (GNULIB_NAMESPACE::truncate, int, (const char *, off_t));
+#endif
+
#if GNULIB_TEST_TTYNAME_R
SIGNATURE_CHECK (GNULIB_NAMESPACE::ttyname_r, int,
(int fd, char *buf, size_t buflen));

Loading...