1#!/bin/bash 2# 3# 4# This file and its contents are supplied under the terms of the 5# Common Development and Distribution License ("CDDL"), version 1.0. 6# You may only use this file in accordance with the terms of version 7# 1.0 of the CDDL. 8# 9# A full copy of the text of the CDDL should have accompanied this 10# source. A copy is of the CDDL is also available via the Internet 11# at http://www.illumos.org/license/CDDL. 12# 13 14# 15# Copyright 2010 Chris Love. All rights reserved. 16# Copyright 2017, Joyent, Inc. 17# 18 19BLOCK="" 20for i in {1..512}; do 21 BLOCK+="." 22done 23 24 25checktest() 26{ 27 local actual=$1 28 local output=$2 29 local test=$3 30 31 if [[ "$actual" != "$output" ]]; then 32 echo "$CMD: test $test: FAIL" 33 echo -e "$CMD: test $test: expected output:\n$output" 34 echo -e "$CMD: test $test: actual output:\n$actual" 35 else 36 echo "$CMD: test $test: pass" 37 fi 38} 39 40checkfail() 41{ 42 printf "foobar" | $PROG $* &> /dev/null 43 44 if [[ $? -eq 0 ]]; then 45 printf '%s: test "test %s": was supposed to fail\n' "$CMD" "$*" 46 else 47 printf '%s: test "%s": pass\n' "$CMD" "$*" 48 fi 49} 50 51# 52# Test cases for 'tail', some based on CoreUtils test cases (validated 53# with legacy Solaris 'tail' and/or xpg4 'tail'). Note that this is designed 54# to be able to run on BSD systems as well to check our behavior against 55# theirs (some behavior that is known to be idiosyncratic to illumos is 56# skipped on non-illumos systems). 57# 58PROG=/usr/bin/tail 59CMD=`basename $0` 60DIR="" 61 62while [[ $# -gt 0 ]]; do 63 case $1 in 64 -x) 65 PROG=/usr/xpg4/bin/tail 66 shift 67 ;; 68 -o) 69 PROG=$2 70 shift 2 71 ;; 72 -d) 73 DIR=$2 74 shift 2 75 ;; 76 *) 77 echo "Usage: tailtests.sh" \ 78 "[-x][-o <override tail executable>]" \ 79 "[-d <override output directory>]" 80 exit 1 81 ;; 82 esac 83done 84 85# 86# Shut bash up upon receiving a term so we can drop it on our children 87# without disrupting the output. 88# 89trap "exit 0" TERM 90 91echo "$CMD: program is $PROG" 92 93if [[ $DIR != "" ]]; then 94 echo "$CMD: directory is $DIR" 95fi 96 97o=`echo -e "bcd"` 98a=`echo -e "abcd" | $PROG +2c` 99checktest "$a" "$o" 1 100 101o=`echo -e ""` 102a=`echo "abcd" | $PROG +8c` 103checktest "$a" "$o" 2 104 105o=`echo -e "abcd"` 106a=`echo "abcd" | $PROG -9c` 107checktest "$a" "$o" 3 108 109o=`echo -e "x"` 110a=`echo -e "x" | $PROG -1l` 111checktest "$a" "$o" 4 112 113o=`echo -e "\n"` 114a=`echo -e "x\ny\n" | $PROG -1l` 115checktest "$a" "$o" 5 116 117o=`echo -e "y\n"` 118a=`echo -e "x\ny\n" | $PROG -2l` 119checktest "$a" "$o" 6 120 121o=`echo -e "y"` 122a=`echo -e "x\ny" | $PROG -1l` 123checktest "$a" "$o" 7 124 125o=`echo -e "x\ny\n"` 126a=`echo -e "x\ny\n" | $PROG +1l` 127checktest "$a" "$o" 8 128 129o=`echo -e "y\n"` 130a=`echo -e "x\ny\n" | $PROG +2l` 131checktest "$a" "$o" 9 132 133o=`echo -e "x"` 134a=`echo -e "x" | $PROG -1` 135checktest "$a" "$o" 10 136 137o=`echo -e "\n"` 138a=`echo -e "x\ny\n" | $PROG -1` 139checktest "$a" "$o" 11 140 141o=`echo -e "y\n"` 142a=`echo -e "x\ny\n" | $PROG -2` 143checktest "$a" "$o" 12 144 145o=`echo -e "y"` 146a=`echo -e "x\ny" | $PROG -1` 147checktest "$a" "$o" 13 148 149o=`echo -e "x\ny\n"` 150a=`echo -e "x\ny\n" | $PROG +1` 151checktest "$a" "$o" 14 152 153o=`echo -e "y\n"` 154a=`echo -e "x\ny\n" | $PROG +2` 155checktest "$a" "$o" 15 156 157o=`printf "yyz\n"` 158a=`printf "xyyyyyyyyyyz\n" | $PROG +10c` 159checktest "$a" "$o" 16 160 161o=`printf "y\ny\nz\n"` 162a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG +10l` 163checktest "$a" "$o" 17 164 165o=`printf "y\ny\ny\ny\ny\ny\ny\ny\ny\nz\n"` 166a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG -10l` 167checktest "$a" "$o" 18 168 169a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\n"` 170o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +10lr` 171checktest "$a" "$o" 19 172 173a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\nf\n"` 174o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -10lr` 175checktest "$a" "$o" 20 176 177a=`printf "o\nn\nm\nl\n"` 178o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +10cr` 179checktest "$a" "$o" 21 180 181a=`printf "o\nn\nm\nl\nk\n"` 182o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -10cr` 183checktest "$a" "$o" 22 184 185# 186# For reasons that are presumably as accidental as they are ancient, legacy 187# (and closed) Solaris tail(1) allows +c, +l and -l to be aliases for +10c, 188# +10l and -10l, respectively. If we are on SunOS, verify that this silly 189# behavior is functional. 190# 191if [[ `uname -s` == "SunOS" ]]; then 192 o=`printf "yyz\n"` 193 a=`printf "xyyyyyyyyyyz\n" | $PROG +c` 194 checktest "$a" "$o" 16a 195 196 o=`printf "y\ny\nz\n"` 197 a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG +l` 198 checktest "$a" "$o" 17a 199 200 o=`printf "y\ny\ny\ny\ny\ny\ny\ny\ny\nz\n"` 201 a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG -l` 202 checktest "$a" "$o" 18a 203 204 a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\n"` 205 206 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +lr` 207 checktest "$a" "$o" 19a 208 209 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +l -r` 210 checktest "$a" "$o" 19a 211 212 a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\nf\n"` 213 214 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -lr` 215 checktest "$a" "$o" 20a 216 217 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -l -r` 218 checktest "$a" "$o" 20b 219 220 a=`printf "o\nn\nm\nl\n"` 221 222 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +cr` 223 checktest "$a" "$o" 21a 224 225 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +c -r` 226 checktest "$a" "$o" 21a 227 228 a=`printf "o\nn\nm\nl\nk\n"` 229 230 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -cr` 231 checktest "$a" "$o" 22a 232 233 o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -c -r` 234 checktest "$a" "$o" 22b 235fi 236 237o=`echo -e "c\nb\na"` 238a=`echo -e "a\nb\nc" | $PROG -r` 239checktest "$a" "$o" 23 240 241# 242# Now we want to do a series of follow tests. 243# 244if [[ $DIR == "" ]]; then 245 export TMPDIR=/var/tmp 246 tdir=$(mktemp -d -t tailtest.XXXXXXXX || exit 1) 247else 248 tdir=$(mktemp -d $DIR/tailtest.XXXXXXXX || exit 1) 249fi 250 251follow=$tdir/follow 252moved=$tdir/follow.moved 253out=$tdir/out 254 255# 256# First, verify that following works in its most basic sense. 257# 258echo -e "a\nb\nc" > $follow 259$PROG -f $follow > $out 2> /dev/null & 260child=$! 261sleep 2 262echo -e "d\ne\nf" >> $follow 263sleep 1 264kill $child 265sleep 1 266 267o=`echo -e "a\nb\nc\nd\ne\nf\n"` 268a=`cat $out` 269checktest "$a" "$o" 24 270rm $follow 271 272# 273# Now verify that following correctly follows the file being moved. 274# 275echo -e "a\nb\nc" > $follow 276$PROG -f $follow > $out 2> /dev/null & 277child=$! 278sleep 2 279mv $follow $moved 280 281echo -e "d\ne\nf" >> $moved 282sleep 1 283kill $child 284sleep 1 285 286o=`echo -e "a\nb\nc\nd\ne\nf\n"` 287a=`cat $out` 288checktest "$a" "$o" 25 289rm $moved 290 291# 292# And now truncation with the new offset being less than the old offset. 293# 294echo -e "a\nb\nc" > $follow 295$PROG -f $follow > $out 2> /dev/null & 296child=$! 297sleep 2 298echo -e "d\ne\nf" >> $follow 299sleep 1 300echo -e "g\nh\ni" > $follow 301sleep 1 302kill $child 303sleep 1 304 305o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` 306a=`cat $out` 307checktest "$a" "$o" 26 308rm $follow 309 310# 311# And truncation with the new offset being greater than the old offset. 312# 313echo -e "a\nb\nc" > $follow 314sleep 1 315$PROG -f $follow > $out 2> /dev/null & 316child=$! 317sleep 2 318echo -e "d\ne\nf" >> $follow 319sleep 1 320echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $follow 321sleep 1 322kill $child 323sleep 1 324 325o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"` 326a=`cat $out` 327checktest "$a" "$o" 27 328rm $follow 329 330# 331# Verify that we can follow the moved file and correctly see a truncation. 332# 333echo -e "a\nb\nc" > $follow 334$PROG -f $follow > $out 2> /dev/null & 335child=$! 336sleep 2 337mv $follow $moved 338 339echo -e "d\ne\nf" >> $moved 340sleep 1 341echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $moved 342sleep 1 343kill $child 344sleep 1 345 346o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"` 347a=`cat $out` 348checktest "$a" "$o" 28 349rm $moved 350 351# 352# Verify that capital-F follow properly deals with truncation 353# 354echo -e "a\nb\nc" > $follow 355$PROG -F $follow > $out 2> /dev/null & 356child=$! 357sleep 2 358echo -e "d\ne\nf" >> $follow 359sleep 1 360echo -e "g\nh\ni" > $follow 361sleep 1 362kill $child 363sleep 1 364 365o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` 366a=`cat $out` 367checktest "$a" "$o" 29 368rm $follow 369 370# 371# Verify that capital-F follow _won't_ follow the moved file and will 372# correctly deal with recreation of the original file. 373# 374echo -e "a\nb\nc" > $follow 375$PROG -F $follow > $out 2> /dev/null & 376child=$! 377sleep 2 378mv $follow $moved 379 380echo -e "x\ny\nz" >> $moved 381 382# 383# At this point, tail is polling on stat'ing the missing file; we need to 384# be sure to sleep long enough after recreating it to know that it will pick 385# it up. 386# 387echo -e "d\ne\nf" > $follow 388sleep 5 389kill $child 390sleep 1 391 392o=`echo -e "a\nb\nc\nd\ne\nf\n"` 393a=`cat $out` 394checktest "$a" "$o" 30 395rm $moved 396 397# 398# Verify that following two files works. 399# 400echo -e "one" > $follow 401echo -e "two" > $moved 402$PROG -f $follow $moved > $out 2> /dev/null & 403child=$! 404sleep 2 405echo -e "three" >> $follow 406sleep 1 407echo -e "four" >> $moved 408sleep 1 409echo -e "five" >> $follow 410sleep 1 411kill $child 412sleep 1 413 414# There is a bug where the content comes before the header lines, 415# where rlines/mapprint happens before the header. A pain to fix. 416# In this test, just make sure we see both files change. 417o="one 418 419==> $follow <== 420two 421 422==> $moved <== 423 424==> $follow <== 425three 426 427==> $moved <== 428four 429 430==> $follow <== 431five" 432a=`cat $out` 433checktest "$a" "$o" 31 434rm $follow $moved 435 436if [[ `uname -s` == "SunOS" ]]; then 437 # 438 # Use DTrace to truncate the file between the return from port_get() 439 # and the reassociation of the file object with the port, exposing 440 # any race conditions whereby FILE_TRUNC events are lost. 441 # 442 cat /dev/null > $follow 443 dtrace -c "$PROG -f $follow" -s /dev/stdin > $out <<EOF 444 #pragma D option destructive 445 #pragma D option quiet 446 447 pid\$target::port_get:return 448 /++i == 5/ 449 { 450 stop(); 451 system("cat /dev/null > $follow"); 452 system("prun %d", pid); 453 } 454 455 tick-1sec 456 { 457 system("echo %d >> $follow", j++); 458 } 459 460 tick-1sec 461 /j == 10/ 462 { 463 exit(0); 464 } 465EOF 466 467 o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"` 468 a=`cat $out` 469 checktest "$a" "$o" 31a 470 rm $follow 471 472 cat /dev/null > $follow 473 dtrace -c "$PROG -f $follow" -s /dev/stdin > $out <<EOF 474 #pragma D option destructive 475 #pragma D option quiet 476 477 pid\$target::port_get:return 478 /++i == 5/ 479 { 480 stop(); 481 system("mv $follow $moved"); 482 system("cat /dev/null > $moved"); 483 system("prun %d", pid); 484 } 485 486 tick-1sec 487 { 488 system("echo %d >> %s", j++, 489 i < 5 ? "$follow" : "$moved"); 490 } 491 492 tick-1sec 493 /j == 10/ 494 { 495 exit(0); 496 } 497EOF 498 499 o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"` 500 a=`cat $out` 501 checktest "$a" "$o" 31b 502 rm $moved 503 504 # 505 # Verify that -F will deal properly with the file being truncated 506 # not by truncation, but rather via an ftruncate() from logadm. 507 # 508 cat /dev/null > $follow 509 ( $PROG -F $follow > $out ) & 510 child=$! 511 echo -e "a\nb\nc\nd\ne\nf" >> $follow 512 logadm -c $follow 513 sleep 2 514 echo -e "g\nh\ni" >> $follow 515 sleep 2 516 kill $child 517 sleep 1 518 519 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` 520 a=`cat $out` 521 checktest "$a" "$o" 31c 522fi 523 524# 525# We're now going to test that while we may miss output due to truncations 526# occurring faster than tail can read, we don't ever repeat output. 527# 528cat /dev/null > $follow 529( $PROG -f $follow > $out ) & 530tchild=$! 531( let i=0 ; while true; do echo $i > $follow ; sleep 0.1; let i=i+1 ; done ) & 532child=$! 533sleep 10 534kill $tchild 535kill $child 536 537a=`sort $out | uniq -c | sort -n | tail -1 | awk '{ print $1 }'` 538o=1 539 540checktest "$a" "$o" 32 541 542# Test different ways of specifying character offsets 543o=`printf "d\n"` 544 545a=`printf "hello\nworld\n" | $PROG -c2` 546checktest "$a" "$o" 33 547 548a=`printf "hello\nworld\n" | $PROG -c-2` 549checktest "$a" "$o" 34 550 551a=`printf "hello\nworld\n" | $PROG -c 2` 552checktest "$a" "$o" 35 553 554a=`printf "hello\nworld\n" | $PROG -c -2` 555checktest "$a" "$o" 36 556 557a=`printf "hello\nworld\n" | $PROG -2c` 558checktest "$a" "$o" 37 559 560o=`printf "llo\nworld\n"` 561 562a=`printf "hello\nworld\n" | $PROG -c +3` 563checktest "$a" "$o" 38 564 565a=`printf "hello\nworld\n" | $PROG -c+3` 566checktest "$a" "$o" 39 567 568a=`printf "hello\nworld\n" | $PROG +3c` 569checktest "$a" "$o" 40 570 571# Test various ways of specifying block offsets 572o=`printf "$BLOCK"` 573 574a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b1` 575checktest "$a" "$o" 41 576 577a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b 1` 578checktest "$a" "$o" 42 579 580a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b -1` 581checktest "$a" "$o" 43 582 583a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b +2` 584checktest "$a" "$o" 44 585 586# Test that illegal arguments aren't allowed 587 588checkfail +b2 589checkfail +c3 590checkfail -l3 591checkfail -cz 592checkfail -bz 593checkfail -nz 594checkfail -3n 595checkfail +3n 596checkfail +n3 597checkfail -lfoobar 598 599echo "$CMD: completed" 600 601exit $errs 602 603