1 #!/bin/bash 2 # 3 # Copyright (C) 2015 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 # Version: 1.3-a8 18 # 19 set -o nounset 20 BASE_UMASK=$(umask) 21 umask 077 22 23 # 24 # Settings 25 # 26 JACK_HOME="${JACK_HOME:=$HOME/.jack-server}" 27 CLIENT_SETTING="${CLIENT_SETTING:=$HOME/.jack-settings}" 28 TMPDIR=${TMPDIR:=/tmp} 29 JACK_SERVER_VM_ARGUMENTS="${JACK_SERVER_VM_ARGUMENTS:=-Dfile.encoding=UTF-8 -XX:+TieredCompilation}" 30 JACK_EXTRA_CURL_OPTIONS=${JACK_EXTRA_CURL_OPTIONS:=} 31 32 LAUNCHER_JAR="$JACK_HOME/launcher.jar" 33 LAUNCHER_NAME=com.android.jack.launcher.ServerLauncher 34 CURRENT_CHARSET=$(locale charmap) 35 if [ -z "$CURRENT_CHARSET" ]; then 36 CHARSET_ARGUMENT= 37 else 38 CHARSET_ARGUMENT=";charset=$CURRENT_CHARSET" 39 fi 40 41 JACK_LOGS_DIR="$JACK_HOME"/logs 42 JACK_OUT_ERR="$JACK_LOGS_DIR"/outputs.txt 43 JACK_CONNECTION_TIMEOUT=300 44 45 # 46 # Load client settings 47 # 48 if [ -f "$CLIENT_SETTING" ]; then 49 source "$CLIENT_SETTING" 50 fi 51 52 # 53 # Create or update client settings if needed 54 # 55 if [[ ! -f "$CLIENT_SETTING" || $SETTING_VERSION -lt 4 ]]; then 56 echo "Writing client settings in" $CLIENT_SETTING 57 cat >"$CLIENT_SETTING.$$" <<-EOT 58 # Server settings 59 SERVER_HOST=${SERVER_HOST:=127.0.0.1} 60 SERVER_PORT_SERVICE=${SERVER_PORT_SERVICE:=8076} 61 SERVER_PORT_ADMIN=${SERVER_PORT_ADMIN:=8077} 62 63 # Internal, do not touch 64 SETTING_VERSION=4 65 EOT 66 ln -f "$CLIENT_SETTING.$$" "$CLIENT_SETTING" 67 rm "$CLIENT_SETTING.$$" 68 source "$CLIENT_SETTING" 69 fi 70 71 usage () { 72 echo "Usage : $0 [ install-server <launcher.jar> <server.jar> | uninstall-server | list <program> | update <program> <program.jar> | start-server | stop-server | kill-server | list-server | server-stat | server-log | server-gc | cleanup-server | dump-report]" 73 } 74 75 abort () { exit 255; } 76 77 # 78 # $1: curl command status 79 # $2: HTTP status 80 # 81 handleHttpErrors() { 82 if [ $1 -eq 0 ]; then 83 # No problem, let's go 84 return 0; 85 elif [ $1 -eq 7 ]; then 86 echo "No Jack server running. Try 'jack-admin start-server'" >&2 87 abort 88 elif [ $1 -eq 35 ]; then 89 echo "SSL error when connecting to the Jack server. Try 'jack-diagnose'" >&2 90 abort 91 elif [ $1 -eq 58 ]; then 92 echo "Failed to contact Jack server: Problem reading ${JACK_HOME}/client.pem. Try 'jack-diagnose'" >&2 93 abort 94 elif [ $1 -eq 60 ]; then 95 echo "Failed to authenticate Jack server certificate. Try 'jack-diagnose'" >&2 96 abort 97 elif [ $1 -eq 77 ]; then 98 echo "Failed to contact Jack server: Problem reading ${JACK_HOME}/server.pem. Try 'jack-diagnose'" >&2 99 abort 100 elif [ $1 -eq 22 ]; then 101 # Http code not OK, let's decode and abort 102 if [ $2 -eq 400 ]; then 103 # 400: Bad request 104 echo "Bad request, see Jack server log" >&2 105 abort 106 else 107 # Other 108 echo "Internal unknown error ($2), try 'jack-diagnose' or see Jack server log" >&2 109 abort 110 fi 111 else 112 echo "Communication error with Jack server $1. Try 'jack-diagnose'" >&2 113 abort 114 fi 115 } 116 117 checkCurlVersion () { 118 curl --version | grep -q "SecureTransport" 119 if [ "$?" -eq 0 ]; then 120 echo "Unsupported curl, please use a curl not based on SecureTransport" >&2 121 abort 122 fi 123 } 124 125 # 126 # $1: program name 127 # $2: jar of the program 128 # 129 updateProgram () { 130 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 131 --cert "${JACK_HOME}/client.pem" \ 132 --cacert "${JACK_HOME}/server.pem" \ 133 --output /dev/null \ 134 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 135 -X PUT \ 136 -F "jar=@$2;type=application/octet-stream" \ 137 -F "force=$FORCE_INSTALLATION;type=text/plain$CHARSET_ARGUMENT" \ 138 --noproxy ${SERVER_HOST} \ 139 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/$1 \ 140 ) 141 handleHttpErrors $? $HTTP_CODE 142 143 if [ "$1" == server ]; then 144 echo "Server updated, waiting for restart" 145 waitServerStarted 146 fi 147 } 148 149 isServerRunning () { 150 RETRY_SESSION=3 151 DONE=1 152 let DATE_TIMEOUT=$(date +%s)+$JACK_CONNECTION_TIMEOUT 153 while [ "$DONE" -ne 0 ]; do 154 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 155 --cert "${JACK_HOME}/client.pem" \ 156 --cacert "${JACK_HOME}/server.pem" \ 157 --output /dev/null \ 158 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 159 -X GET \ 160 -H "Accept: text/plain$CHARSET_ARGUMENT" \ 161 --noproxy ${SERVER_HOST} \ 162 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/server \ 163 ) 164 CURL_CODE=$? 165 if [ $CURL_CODE -eq 0 ]; then 166 # No problem, let's go 167 return 0; 168 elif [ $CURL_CODE -eq 7 ]; then 169 return 1 170 else 171 # In case of partial, timeout, empty response, network error, let's retry 172 if [ $RETRY_SESSION -eq 0 ]; then 173 echo "Communication error with Jack server ($CURL_CODE), try 'jack-diagnose' or see Jack server log" >&2 174 abort 175 else 176 if [ $(date +%s) -lt $DATE_TIMEOUT ]; then 177 let RETRY_SESSION=RETRY_SESSION-1 178 else 179 echo "Communication error with Jack server ($CURL_CODE), try 'jack-diagnose' or see Jack server log" >&2 180 abort 181 fi 182 fi 183 fi 184 done 185 } 186 187 waitServerStarted () { 188 DONE=1 189 let DATE_TIMEOUT=$(date +%s)+$JACK_CONNECTION_TIMEOUT 190 while [ "$DONE" -ne 0 ]; do 191 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 192 --cert "${JACK_HOME}/client.pem" \ 193 --cacert "${JACK_HOME}/server.pem" \ 194 --output /dev/null \ 195 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 196 -X GET \ 197 -H "Accept: text/plain$CHARSET_ARGUMENT" \ 198 --noproxy ${SERVER_HOST} \ 199 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/server \ 200 ) 201 CURL_CODE=$? 202 if [ $CURL_CODE -eq 7 ] || [ $CURL_CODE -eq 35 ] || [ $CURL_CODE -eq 58 ] || [ $CURL_CODE -eq 60 ] || [ $CURL_CODE -eq 77 ]; then 203 if [ $(date +%s) -ge $DATE_TIMEOUT ]; then 204 echo "Jack server failed to (re)start, try 'jack-diagnose' or see Jack server log" >&2 205 abort 206 else 207 sleep 1 208 fi 209 else 210 # A connection was opened, no need to know if it went well 211 DONE=0; 212 fi 213 done 214 } 215 216 # 217 # $1: program name 218 # 219 listProgramVersion () { 220 exec 3>&1 221 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 222 --cert "${JACK_HOME}/client.pem" \ 223 --cacert "${JACK_HOME}/server.pem" \ 224 --output >(tr -d '\015' >&3) \ 225 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 226 -X GET \ 227 -H "Accept: text/plain$CHARSET_ARGUMENT" \ 228 --noproxy ${SERVER_HOST} \ 229 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/$1 \ 230 ) 231 handleHttpErrors $? $HTTP_CODE 232 exec 3>&- 233 } 234 235 # 236 # Decoding argument 237 # 238 if [ $# -eq 0 ] 239 then 240 usage 241 abort 242 fi 243 244 set +o errexit 245 246 FORCE_INSTALLATION=false 247 case $1 in 248 force-update) 249 FORCE_INSTALLATION=true 250 COMMAND=update;; 251 *) 252 COMMAND=$1;; 253 esac 254 255 case $COMMAND in 256 install-server) 257 if [ $# -ne 3 ]; then 258 usage 259 abort 260 fi 261 if [ ! -r "$2" ]; then 262 echo "Jack server launcher jar \"$2\" is not readable" >&2 263 abort 264 fi 265 if [ ! -r "$3" ]; then 266 echo "Jack server jar \"$3\" is not readable" >&2 267 abort 268 fi 269 270 checkCurlVersion 271 272 if [ ! -d "$JACK_HOME" ]; then 273 echo Installing jack server in \"$JACK_HOME\" 274 mkdir -p "$JACK_HOME" 275 cp $2 "$LAUNCHER_JAR" 276 cp $3 "$JACK_HOME/server-1.jar" 277 mkdir "$JACK_LOGS_DIR" 278 keytool -genkeypair -validity 3650 -alias server -keyalg RSA -keysize 2048 -keypass Jack-Server -storepass Jack-Server -dname "CN=$SERVER_HOST" -keystore "$JACK_HOME/server.jks" 279 keytool -genkeypair -validity 3650 -alias client -keyalg RSA -keysize 2048 -keypass Jack-Server -storepass Jack-Server -dname "CN=$(id -un)@$(uname -n)" -keystore "$JACK_HOME/client.jks" 280 else 281 echo "Jack server already installed in \"$JACK_HOME\"" >&2 282 abort 283 fi 284 exit 0 ;; 285 286 287 uninstall-server) 288 if [ ! -d "$JACK_HOME" ]; then 289 echo "Jack server in \"$JACK_HOME\" not found" >&2 290 abort 291 else 292 echo "Removing jack server from \"$JACK_HOME\"" 293 rm -rf "$JACK_HOME" 294 fi 295 exit 0 ;; 296 297 298 list) 299 if [ $# -ne 2 ] 300 then 301 usage 302 abort 303 fi 304 305 listProgramVersion $2 ;; 306 307 308 update) 309 if [ $# -lt 3 ]; then 310 usage 311 abort 312 fi 313 314 if [ $# -gt 4 ]; then 315 usage 316 abort 317 fi 318 319 if [ ! -r "$3" ]; then 320 echo "Failed to update $2 of Jack server: \"$3\" is not readable" >&2 321 abort 322 fi 323 324 checkCurlVersion 325 326 if [ $FORCE_INSTALLATION = true ]; then 327 updateProgram $2 $3 328 else 329 if [ $# -eq 4 ]; then 330 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 331 --cert "${JACK_HOME}/client.pem" \ 332 --cacert "${JACK_HOME}/server.pem" \ 333 --output /dev/null \ 334 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 335 -X HEAD \ 336 --data "$4" \ 337 -H "Content-Type:application/vnd.jack.select-exact;version=1" \ 338 --noproxy ${SERVER_HOST} \ 339 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/$2 \ 340 ) 341 CURL_CODE=$? 342 if [ $CURL_CODE -eq 22 ]; then 343 if [ $HTTP_CODE -eq 404 ]; then 344 # version not found, proceed to installation 345 updateProgram $2 $3 346 exit 0 347 fi 348 fi 349 handleHttpErrors $CURL_CODE $HTTP_CODE 350 else 351 # No version provided, proceed directly without testing 352 updateProgram $2 $3 353 fi 354 fi 355 exit 0;; 356 357 358 stop-server) 359 echo "Stopping background server" 360 361 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 362 --cert "${JACK_HOME}/client.pem" \ 363 --cacert "${JACK_HOME}/server.pem" \ 364 --output /dev/null \ 365 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 366 -X POST \ 367 --noproxy ${SERVER_HOST} \ 368 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/server/stop \ 369 ) 370 handleHttpErrors $? $HTTP_CODE ;; 371 372 373 server-stat) 374 echo "Getting statistic from background server" 375 376 exec 3>&1 377 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 378 --cert "${JACK_HOME}/client.pem" \ 379 --cacert "${JACK_HOME}/server.pem" \ 380 --output >(tr -d '\015' >&3) \ 381 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 382 -X GET \ 383 -H "Accept: text/plain$CHARSET_ARGUMENT" \ 384 --noproxy ${SERVER_HOST} \ 385 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/stat \ 386 ) 387 handleHttpErrors $? $HTTP_CODE 388 exec 3>&- ;; 389 390 391 server-log) 392 exec 3>&1 393 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 394 --cert "${JACK_HOME}/client.pem" \ 395 --cacert "${JACK_HOME}/server.pem" \ 396 --output >(tr -d '\015' >&3) \ 397 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 398 -X GET \ 399 -H "Accept: text/plain$CHARSET_ARGUMENT" \ 400 --noproxy ${SERVER_HOST} \ 401 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/launcher/log \ 402 ) 403 handleHttpErrors $? $HTTP_CODE 404 exec 3>&- ;; 405 406 407 kill-server) 408 echo "Killing background server" 409 SERVERS_PID=$(ps -A -o "pid args" -u `id -u -n` | grep $LAUNCHER_NAME | grep -v grep | awk '{print $1}') 410 if [ -z "$SERVERS_PID" ]; then 411 echo "No Jack server to kill" >&2 412 exit 2 413 fi 414 415 for PID in $SERVERS_PID; do 416 kill $PID 2>/dev/null 417 TIMEOUT=30 418 while [ "$TIMEOUT" -ne 0 ]; do 419 kill -0 $PID 2>/dev/null 420 if [ $? -ne 0 ]; then 421 continue 2 422 fi 423 sleep 1 424 let TIMEOUT=TIMEOUT-1 425 done 426 kill -KILL $PID 2>/dev/null 427 DONE=$? 428 while [ $DONE -eq 0 ]; do 429 kill -0 $PID 2>/dev/null 430 DONE=$? 431 sleep 1 432 done 433 done 434 435 exit 0 ;; 436 437 438 list-server) 439 ps -A -o "user pid args" | grep $LAUNCHER_NAME | grep -v grep 440 exit $? ;; 441 442 443 start-server) 444 if [ ! -d "$JACK_HOME" ]; then 445 echo "Jack server installation not found" >&2 446 abort 447 fi 448 449 isServerRunning 450 RUNNING=$? 451 if [ "$RUNNING" = 0 ]; then 452 echo "Server is already running" 453 else 454 JACK_SERVER_COMMAND="java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME" 455 echo "Launching Jack server" $JACK_SERVER_COMMAND 456 ( 457 trap "" SIGHUP 458 for i in $(seq 3 255); do 459 eval exec "$i"'>&-' 460 done 461 cd "$JACK_HOME" 462 umask $BASE_UMASK 463 exec $JACK_SERVER_COMMAND 464 abort 465 ) >"$JACK_OUT_ERR" 2>&1 & 466 fi 467 468 waitServerStarted 469 exit 0 ;; 470 471 472 server-log-level) 473 if [ $# -eq 4 ] 474 then 475 LIMIT=$3 476 COUNT=$4 477 elif [ $# -eq 2 ] 478 then 479 COUNT=5 480 if [ \( "$2" = "ERROR" \) -o \( "$2" = "WARNING" \) ] 481 then 482 LIMIT=1048576 483 else 484 LIMIT=10485760 485 fi 486 else 487 usage 488 abort 489 fi 490 491 echo "Setting logging parameters of background server" 492 493 HTTP_CODE=$(curl --fail $JACK_EXTRA_CURL_OPTIONS \ 494 --cert "${JACK_HOME}/client.pem" \ 495 --cacert "${JACK_HOME}/server.pem" \ 496 --output /dev/null \ 497 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 498 --request PUT \ 499 --form "level=$2;type=text/plain$CHARSET_ARGUMENT" \ 500 --form "limit=$LIMIT;type=text/plain$CHARSET_ARGUMENT" \ 501 --form "count=$COUNT;type=text/plain$CHARSET_ARGUMENT" \ 502 --noproxy ${SERVER_HOST} \ 503 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/launcher/log/level \ 504 ) 505 handleHttpErrors $? $HTTP_CODE ;; 506 507 508 server-gc) 509 echo "Requesting a garbage collection to the background server" 510 511 HTTP_CODE=$(curl -f $JACK_EXTRA_CURL_OPTIONS \ 512 --cert "${JACK_HOME}/client.pem" \ 513 --cacert "${JACK_HOME}/server.pem" \ 514 --output /dev/null \ 515 --no-buffer --write-out '%{http_code}' --silent --connect-timeout $JACK_CONNECTION_TIMEOUT \ 516 -X POST \ 517 --noproxy ${SERVER_HOST} \ 518 https://${SERVER_HOST}:$SERVER_PORT_ADMIN/gc \ 519 ) 520 handleHttpErrors $? $HTTP_CODE ;; 521 522 523 # 524 # Should be run when server is off. Allows to clean files that could be forgotten on disk in case of 525 # server VM crash after an update. 526 # 527 cleanup-server) 528 shopt -s nullglob 529 for file in $JACK_HOME/jack/*.deleted; do 530 rm "${file%.deleted}" 531 rm "$file" 532 done 533 exit 0 ;; 534 535 536 dump-report) 537 if [ ! -d "$JACK_HOME" ]; then 538 echo "Failed to locate Jack server installation" >&2 539 abort 540 fi 541 542 echo "Creating report..." 543 REPORT="jack-report.$$.zip" 544 REPORT_PATH="$(pwd)/$REPORT" 545 REPORT_INFO="$JACK_HOME/report.$$.txt" 546 547 if [ -e "$REPORT" ]; then 548 echo "Failed to create Jack server report '$REPORT', file already exists" >&2 549 abort 550 fi 551 552 trap 'rm -f "$REPORT_INFO" 2>/dev/null;' EXIT 553 554 date >>"$REPORT_INFO" 2>&1 555 556 echo "Dumping Jack server stacks..." 557 echo >>"$REPORT_INFO" 558 echo "\$ ps -A -o \"pid args\" | grep $LAUNCHER_NAME | grep -v grep | awk '{print $1}' | xargs kill -3" >>"$REPORT_INFO" 559 (ps -A -o "pid args" | grep $LAUNCHER_NAME | grep -v grep | awk '{print $1}' | xargs kill -3) >>"$REPORT_INFO" 2>&1 560 561 echo "Getting current user id..." 562 echo >>"$REPORT_INFO" 563 echo "\$ id -u" >>"$REPORT_INFO" 564 id -u >>"$REPORT_INFO" 565 566 echo "Listing Jack server process..." 567 echo >>"$REPORT_INFO" 568 echo "\$ ps -A -o \"uid pid args\" | grep $LAUNCHER_NAME | grep -v grep" >>"$REPORT_INFO" 569 (ps -A -o "uid pid args" | grep $LAUNCHER_NAME | grep -v grep) >>"$REPORT_INFO" 2>&1 570 571 echo "Listing process using Jack server service port $SERVER_PORT_SERVICE..." 572 echo >>"$REPORT_INFO" 573 echo "\$ lsof -i TCP:$SERVER_PORT_SERVICE -l" >>"$REPORT_INFO" 574 lsof -i TCP:$SERVER_PORT_SERVICE -l >>"$REPORT_INFO" 2>&1 575 576 echo "Listing process using Jack server admin port $SERVER_PORT_ADMIN..." 577 echo >>"$REPORT_INFO" 578 echo "\$ lsof -i TCP:$SERVER_PORT_ADMIN -l" >>"$REPORT_INFO" 579 lsof -i TCP:$SERVER_PORT_ADMIN -l >>"$REPORT_INFO" 2>&1 580 581 echo "Collecting Jack client configuration..." 582 echo >>"$REPORT_INFO" 583 echo "\$ cat \"\$CLIENT_SETTING\"" >>"$REPORT_INFO" 584 cat "$CLIENT_SETTING" >>"$REPORT_INFO" 2>&1 585 586 echo "Listing Jack server installation dir..." 587 echo >>"$REPORT_INFO" 588 echo "\$ cd \"\$JACK_HOME\"; ls -l -R -n ." >>"$REPORT_INFO" 589 (cd "$JACK_HOME" ; ls -l -R -n . >>"$REPORT_INFO" 2>&1) 590 591 echo "Collecting curl version..." 592 echo >>"$REPORT_INFO" 593 echo "\$ curl --version" >>"$REPORT_INFO" 594 curl --version >>"$REPORT_INFO" 2>&1 595 596 echo "Collecting curl connection info..." 597 echo >>"$REPORT_INFO" 598 echo "\$ JACK_EXTRA_CURL_OPTIONS=-v jack-admin list server" >>"$REPORT_INFO" 599 JACK_EXTRA_CURL_OPTIONS=-v "$0" list server >>"$REPORT_INFO" 2>&1 600 601 echo "Collecting Jack server stats..." 602 echo >>"$REPORT_INFO" 603 echo "\$ jack-admin server-stat" >>"$REPORT_INFO" 604 "$0" server-stat >>"$REPORT_INFO" 2>&1 605 606 echo "Zipping Jack server installation dir except keys and certificates..." 607 (cd "$JACK_HOME"; zip --exclude \*.pem \*.jks --recurse-paths "$REPORT_PATH" .) >/dev/null 608 echo "Jack server report saved in '$REPORT'. Consider reviewing content before publishing." 609 exit 0 ;; 610 611 612 *) 613 usage 614 abort ;; 615 esac 616 617 618 # Exit 619 620 exit 0 621