From owner-svn-src-all@FreeBSD.ORG Mon Jun 9 10:39:56 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 5DDAA50F; Mon, 9 Jun 2014 10:39:56 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 48C9626D3; Mon, 9 Jun 2014 10:39:56 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s59Adu1M033506; Mon, 9 Jun 2014 10:39:56 GMT (envelope-from jilles@svn.freebsd.org) Received: (from jilles@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s59Adt4H033501; Mon, 9 Jun 2014 10:39:55 GMT (envelope-from jilles@svn.freebsd.org) Message-Id: <201406091039.s59Adt4H033501@svn.freebsd.org> From: Jilles Tjoelker Date: Mon, 9 Jun 2014 10:39:55 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r267265 - in head: etc/mtree usr.bin/truncate usr.bin/truncate/tests X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 09 Jun 2014 10:39:56 -0000 Author: jilles Date: Mon Jun 9 10:39:55 2014 New Revision: 267265 URL: http://svnweb.freebsd.org/changeset/base/267265 Log: truncate: Detect integer overflow, fix relative sizes, add tests. The change to expand_number (r204654) broke detection of too large sizes and relative sizes ('+'/'-'). Also add some tests. PR: 190735 Submitted by: Kirk Russell MFC after: 1 week Added: head/usr.bin/truncate/tests/ head/usr.bin/truncate/tests/Makefile (contents, props changed) head/usr.bin/truncate/tests/truncate_test.sh (contents, props changed) Modified: head/etc/mtree/BSD.tests.dist head/usr.bin/truncate/Makefile head/usr.bin/truncate/truncate.c Modified: head/etc/mtree/BSD.tests.dist ============================================================================== --- head/etc/mtree/BSD.tests.dist Mon Jun 9 09:10:31 2014 (r267264) +++ head/etc/mtree/BSD.tests.dist Mon Jun 9 10:39:55 2014 (r267265) @@ -251,6 +251,8 @@ .. tr .. + truncate + .. uudecode .. uuencode Modified: head/usr.bin/truncate/Makefile ============================================================================== --- head/usr.bin/truncate/Makefile Mon Jun 9 09:10:31 2014 (r267264) +++ head/usr.bin/truncate/Makefile Mon Jun 9 10:39:55 2014 (r267265) @@ -1,7 +1,13 @@ # $FreeBSD$ +.include + PROG= truncate DPADD= ${LIBUTIL} LDADD= -lutil +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif + .include Added: head/usr.bin/truncate/tests/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.bin/truncate/tests/Makefile Mon Jun 9 10:39:55 2014 (r267265) @@ -0,0 +1,6 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/usr.bin/truncate +ATF_TESTS_SH= truncate_test + +.include Added: head/usr.bin/truncate/tests/truncate_test.sh ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.bin/truncate/tests/truncate_test.sh Mon Jun 9 10:39:55 2014 (r267265) @@ -0,0 +1,402 @@ +# +# Copyright 2014, Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $FreeBSD$ +# + +# Helper function that is always used to create and fill stderr.txt for these +# tests. +_custom_create_file() +{ + # The first argument is a command. + # The second is just a string. + case "${1}" in + creat) > stderr.txt ;; + print) [ "${2}" ] && \ + printf "%s\n" "${2}" >> stderr.txt ;; + esac +} + +# Helper function that create the file stderr.txt that contains the string +# passed in as the first argument. +create_stderr_file() +{ + _custom_create_file creat + _custom_create_file print "${1}" +} + +# Helper function that create the file stderr.txt that contains the expected +# truncate utility usage message. +create_stderr_usage_file() +{ + _custom_create_file creat + _custom_create_file print "${1}" + _custom_create_file print \ + "usage: truncate [-c] -s [+|-]size[K|k|M|m|G|g|T|t] file ..." + _custom_create_file print " truncate [-c] -r rfile file ..." +} + +atf_test_case illegal_option +illegal_option_head() +{ + atf_set "descr" "Verifies that truncate exits >0 when passed an" \ + "invalid command line option" +} +illegal_option_body() +{ + create_stderr_usage_file 'truncate: illegal option -- 7' + + # We expect the error message, with no new files. + atf_check -s not-exit:0 -e file:stderr.txt truncate -7 -s0 output.txt + [ ! -e output.txt ] || atf_fail "output.txt should not exist" +} + +atf_test_case illegal_size +illegal_size_head() +{ + atf_set "descr" "Verifies that truncate exits >0 when passed an" \ + "invalid power of two convention" +} +illegal_size_body() +{ + create_stderr_file "truncate: invalid size argument \`+1L'" + + # We expect the error message, with no new files. + atf_check -s not-exit:0 -e file:stderr.txt truncate -s+1L output.txt + [ ! -e output.txt ] || atf_fail "output.txt should not exist" +} + +atf_test_case too_large_size +too_large_size_head() +{ + atf_set "descr" "Verifies that truncate exits >0 when passed an" \ + "a size that is INT64_MAX < size <= UINT64_MAX" +} +too_large_size_body() +{ + create_stderr_file "truncate: invalid size argument \`8388608t'" + + # We expect the error message, with no new files. + atf_check -s not-exit:0 -e file:stderr.txt \ + truncate -s8388608t output.txt + [ ! -e output.txt ] || atf_fail "output.txt should not exist" +} + +atf_test_case opt_c +opt_c_head() +{ + atf_set "descr" "Verifies that -c prevents creation of new files" +} +opt_c_body() +{ + # No new files and truncate returns 0 as if this is a success. + atf_check truncate -c -s 0 doesnotexist.txt + [ ! -e output.txt ] || atf_fail "doesnotexist.txt should not exist" + > reference + atf_check truncate -c -r reference doesnotexist.txt + [ ! -e output.txt ] || atf_fail "doesnotexist.txt should not exist" + + create_stderr_file + + # The existing file will be altered by truncate. + > exists.txt + atf_check -e file:stderr.txt truncate -c -s1 exists.txt + [ -s exists.txt ] || atf_fail "exists.txt be larger than zero bytes" +} + +atf_test_case opt_rs +opt_rs_head() +{ + atf_set "descr" "Verifies that truncate command line flags" \ + "-s and -r cannot be specifed together" +} +opt_rs_body() +{ + create_stderr_usage_file + + # Force an error due to the use of both -s and -r. + > afile + atf_check -s not-exit:0 -e file:stderr.txt truncate -s0 -r afile afile +} + +atf_test_case no_files +no_files_head() +{ + atf_set "descr" "Verifies that truncate needs a list of files on" \ + "the command line" +} +no_files_body() +{ + create_stderr_usage_file + + # A list of files must be present on the command line. + atf_check -s not-exit:0 -e file:stderr.txt truncate -s1 +} + +atf_test_case bad_refer +bad_refer_head() +{ + atf_set "descr" "Verifies that truncate detects a non-existent" \ + "reference file" +} +bad_refer_body() +{ + create_stderr_file "truncate: afile: No such file or directory" + + # The reference file must exist before you try to use it. + atf_check -s not-exit:0 -e file:stderr.txt truncate -r afile afile + [ ! -e afile ] || atf_fail "afile should not exist" +} + +atf_test_case bad_truncate cleanup +bad_truncate_head() +{ + atf_set "descr" "Verifies that truncate reports an error during" \ + "truncation" +} +bad_truncate_body() +{ + create_stderr_file "truncate: exists.txt: Operation not permitted" + + # Trying to get the ftruncate() call to return -1. + > exists.txt + atf_check chflags uimmutable exists.txt + + atf_check -s not-exit:0 -e file:stderr.txt truncate -s1 exists.txt +} +bad_truncate_cleanup() +{ + chflags 0 exists.txt +} + +atf_test_case new_absolute_grow +new_absolute_grow_head() +{ + atf_set "descr" "Verifies truncate can make and grow a new 1m file" +} +new_absolute_grow_body() +{ + create_stderr_file + + # Create a new file and grow it to 1024 bytes. + atf_check -s exit:0 -e file:stderr.txt truncate -s1k output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" + + create_stderr_file + + # Grow the existing file to 1M. We are using absolute sizes. + atf_check -s exit:0 -e file:stderr.txt truncate -c -s1M output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m" +} + +atf_test_case new_absolute_shrink +new_absolute_shrink_head() +{ + atf_set "descr" "Verifies that truncate can make and" \ + "shrink a new 1m file" +} +new_absolute_shrink_body() +{ + create_stderr_file + + # Create a new file and grow it to 1048576 bytes. + atf_check -s exit:0 -e file:stderr.txt truncate -s1M output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m" + + create_stderr_file + + # Shrink the existing file to 1k. We are using absolute sizes. + atf_check -s exit:0 -e file:stderr.txt truncate -s1k output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" +} + +atf_test_case new_relative_grow +new_relative_grow_head() +{ + atf_set "descr" "Verifies truncate can make and grow a new 1m file" \ + "using relative sizes" +} +new_relative_grow_body() +{ + create_stderr_file + + # Create a new file and grow it to 1024 bytes. + atf_check -s exit:0 -e file:stderr.txt truncate -s+1k output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" + + create_stderr_file + + # Grow the existing file to 1M. We are using relative sizes. + atf_check -s exit:0 -e file:stderr.txt truncate -s+1047552 output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1048576 ] || atf_fail "expected file size of 1m" +} + +atf_test_case new_relative_shrink +new_relative_shrink_head() +{ + atf_set "descr" "Verifies truncate can make and shrink a new 1m file" \ + "using relative sizes" +} +new_relative_shrink_body() +{ + create_stderr_file + + # Create a new file and grow it to 1049600 bytes. + atf_check -s exit:0 -e file:stderr.txt truncate -s+1049600 output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1049600 ] || atf_fail "expected file size of 1m" + + create_stderr_file + + # Shrink the existing file to 1k. We are using relative sizes. + atf_check -s exit:0 -e file:stderr.txt truncate -s-1M output.txt + atf_check -s exit:1 cmp -s output.txt /dev/zero + eval $(stat -s output.txt) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" +} + +atf_test_case cannot_open +cannot_open_head() +{ + atf_set "descr" "Verifies truncate handles open failures correctly" \ + "in a list of files" + atf_set "require.user" "unprivileged" +} +cannot_open_body() +{ + # Create three files -- the middle file cannot allow writes. + > before + > 0000 + > after + atf_check chmod 0000 0000 + + create_stderr_file "truncate: 0000: Permission denied" + + # Create a new file and grow it to 1024 bytes. + atf_check -s not-exit:0 -e file:stderr.txt \ + truncate -c -s1k before 0000 after + eval $(stat -s before) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" + eval $(stat -s after) + [ ${st_size} -eq 1024 ] || atf_fail "expected file size of 1k" + eval $(stat -s 0000) + [ ${st_size} -eq 0 ] || atf_fail "expected file size of zero" +} + +atf_test_case reference +reference_head() +{ + atf_set "descr" "Verifies that truncate can use a reference file" +} +reference_body() +{ + # Create a 4 byte reference file. + printf "123\n" > reference + eval $(stat -s reference) + [ ${st_size} -eq 4 ] || atf_fail "reference file should be 4 bytes" + + create_stderr_file + + # Create a new file and grow it to 4 bytes. + atf_check -e file:stderr.txt truncate -r reference afile + eval $(stat -s afile) + [ ${st_size} -eq 4 ] || atf_fail "new file should also be 4 bytes" +} + +atf_test_case new_zero +new_zero_head() +{ + atf_set "descr" "Verifies truncate can make and grow zero byte file" +} +new_zero_body() +{ + create_stderr_file + + # Create a new file and grow it to zero bytes. + atf_check -s exit:0 -e file:stderr.txt truncate -s0 output.txt + eval $(stat -s output.txt) + [ ${st_size} -eq 0 ] || atf_fail "expected file size of zero" + + # Pretend to grow the file. + atf_check -s exit:0 -e file:stderr.txt truncate -s+0 output.txt + eval $(stat -s output.txt) + [ ${st_size} -eq 0 ] || atf_fail "expected file size of zero" +} + +atf_test_case negative +negative_head() +{ + atf_set "descr" "Verifies truncate treats negative sizes as zero" +} +negative_body() +{ + # Create a 5 byte file. + printf "abcd\n" > afile + eval $(stat -s afile) + [ ${st_size} -eq 5 ] || atf_fail "afile file should be 5 bytes" + + create_stderr_file + + # Create a new file and do a 100 byte negative relative shrink. + atf_check -e file:stderr.txt truncate -s-100 afile + eval $(stat -s afile) + [ ${st_size} -eq 0 ] || atf_fail "new file should now be zero bytes" +} + +atf_init_test_cases() +{ + atf_add_test_case illegal_option + atf_add_test_case illegal_size + atf_add_test_case too_large_size + atf_add_test_case opt_c + atf_add_test_case opt_rs + atf_add_test_case no_files + atf_add_test_case bad_refer + atf_add_test_case bad_truncate + atf_add_test_case cannot_open + atf_add_test_case new_absolute_grow + atf_add_test_case new_absolute_shrink + atf_add_test_case new_relative_grow + atf_add_test_case new_relative_shrink + atf_add_test_case reference + atf_add_test_case new_zero + atf_add_test_case negative +} Modified: head/usr.bin/truncate/truncate.c ============================================================================== --- head/usr.bin/truncate/truncate.c Mon Jun 9 09:10:31 2014 (r267264) +++ head/usr.bin/truncate/truncate.c Mon Jun 9 10:39:55 2014 (r267265) @@ -54,8 +54,8 @@ main(int argc, char **argv) { struct stat sb; mode_t omode; - off_t oflow, rsize, tsize; - int64_t sz; + off_t oflow, rsize, sz, tsize; + uint64_t usz; int ch, error, fd, oflags; char *fname, *rname; @@ -73,11 +73,13 @@ main(int argc, char **argv) rname = optarg; break; case 's': - if (expand_number(optarg, &sz) == -1) + do_relative = *optarg == '+' || *optarg == '-'; + if (expand_number(do_relative ? optarg + 1 : optarg, + &usz) == -1 || (off_t)usz < 0) errx(EXIT_FAILURE, "invalid size argument `%s'", optarg); - if (*optarg == '+' || *optarg == '-') - do_relative = 1; + + sz = (*optarg == '-') ? -(off_t)usz : (off_t)usz; got_size = 1; break; default: