diff --git a/list_mv.ksh b/list_mv.ksh new file mode 100755 index 0000000..e1c2e10 --- /dev/null +++ b/list_mv.ksh @@ -0,0 +1,48 @@ +#!/usr/bin/ksh +# +# List MVs using API call to RSC, for diagnostic purposes +# Written for HCL / Nokia +# v1.1 - James Pattinson - October 2025 +# +# usage: list_mv.ksh + + +get_script_dir() { + # Portable way to get script directory for Linux and HP/UX (ksh compatible) + src="$0" + while [ -h "$src" ]; do + dir=$(cd -P $(dirname "$src") >/dev/null 2>&1 && pwd) + src=$(readlink "$src") + case $src in + /*) ;; # absolute path + *) src="$dir/$src";; + esac + done + cd -P $(dirname "$src") >/dev/null 2>&1 && pwd +} +MYDIR=$(get_script_dir) +. $MYDIR/rbk_api.conf +. $MYDIR/oracle_funcs.ksh + +# Script starts here + +echo "Service account in use is $ID" + +echo "Managed Volumes" + + +ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume" +rest_api_get + +awk '{ + pos = 1 + while (match(substr($0, pos), /"name":"[^"]*"/)) { + name = substr($0, pos + RSTART + 7, RLENGTH - 8) + # Remove any trailing quote if present + sub(/"$/, "", name) + print name + pos += RSTART + RLENGTH - 1 + } +}' /tmp/rbkresponse.$$ + +cleanup diff --git a/list_mv.sh b/list_mv.sh deleted file mode 100755 index defb79d..0000000 --- a/list_mv.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# -# List MVs using API call to RSC, for diagnostic purposes -# Written for HCL / Nokia -# v1.1 - James Pattinson - October 2025 -# -# usage: list_mv.sh - - -get_script_dir() { - # Portable way to get script directory for Linux and HP/UX - local src="$0" - while [ -h "$src" ]; do - dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)" - src="$(readlink "$src")" - [[ $src != /* ]] && src="$dir/$src" - done - cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd -} -MYDIR="$(get_script_dir)" -source $MYDIR/rbk_api.conf -source $MYDIR/oracle_funcs.sh - -# Script starts here - -echo Service account in use is $ID - -echo "Managed Volumes" - -ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume" -rest_api_get - -grep -o '"name":"[^"]*"' /tmp/rbkresponse.$$ | cut -d'"' -f4 - -cleanup diff --git a/oracle_funcs.sh b/oracle_funcs.ksh similarity index 75% rename from oracle_funcs.sh rename to oracle_funcs.ksh index 26274dc..0b0b804 100755 --- a/oracle_funcs.sh +++ b/oracle_funcs.ksh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/ksh # # Oracle shell script support functions # Written for HCL / Nokia @@ -50,16 +50,18 @@ echo "`$DATE` -$$-: CALLED $0 $@" >> $LOGFILE trap ctrl_c INT -function ctrl_c () { - echo "`$DATE` -$$-: TRAPPED CTRL-C - EXITING" >> $LOGFILE - exit_with_error + +ctrl_c() { + echo "`$DATE` -$$-: TRAPPED CTRL-C - EXITING" >> $LOGFILE + exit_with_error } -function ctrl_c_inhibit () { - echo "`$DATE` -$$-: TRAPPED CTRL-C - CONTINUING" >> $LOGFILE + +ctrl_c_inhibit() { + echo "`$DATE` -$$-: TRAPPED CTRL-C - CONTINUING" >> $LOGFILE } -exit_with_error () { +exit_with_error() { # if [ $usingsatoken ]; then # ENDPOINT="https://$RUBRIK_IP/api/internal/session/me" # rest_api_delete @@ -75,7 +77,7 @@ exit_with_error () { exit 1 } -check_http_error () { +check_http_error() { # All good responses start with a 2 if [ ${http_response:0:1} != "2" ]; then echo FATAL: HTTP error from API call: $http_response. The server responded with: @@ -83,7 +85,7 @@ check_http_error () { fi } -check_pid () { +check_pid() { if [ -f $PIDFILE ] then @@ -118,32 +120,29 @@ check_pid () { # Get a new token only if AUTH_TOKEN is not already set -check_get_token () { +check_get_token() { if [ -z "$AUTH_TOKEN" ]; then get_token fi } -get_token () { +get_token() { - trap '' INT - echo "`$DATE` -$$-: AUTH USER $ID" >> $LOGFILE - MYENDPOINT="https://$RUBRIK_IP/api/v1/service_account/session" - MYPAYLOAD="{\"serviceAccountId\":\"$ID\",\"secret\":\"$SECRET\"}" + echo "`$DATE` -$$-: AUTH USER $ID" >> $LOGFILE + MYENDPOINT="https://$RUBRIK_IP/api/v1/service_account/session" + MYPAYLOAD='{"serviceAccountId":"'$ID'","secret":"'$SECRET'"}' - http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $MYENDPOINT -H "accept: application/json" -H "Content-Type: application/json" -d $MYPAYLOAD) - check_http_error + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $MYENDPOINT -H "accept: application/json" -H "Content-Type: application/json" -d "$MYPAYLOAD") + check_http_error - AUTH_TOKEN=$(grep -o '"token":"[^"]*"' /tmp/rbkresponse.$$ | cut -d'"' -f4) - SESSION=$(egrep '"sessionId"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}' | sed 's/\"//g') - EXPIRATION=$(egrep '"expirationTime"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}' | sed 's/\"//g' | sed 's/.000Z//;s/T/Z/') + AUTH_TOKEN=$(cat /tmp/rbkresponse.$$ | awk '{match($0, /"token":"[^"]*"/); if (RSTART > 0) {print substr($0, RSTART+9, RLENGTH-10);}}') echo "`$DATE` -$$-: AUTH SESSION $SESSION" >> $LOGFILE - trap ctrl_c INT + } # HTTP GET: Given $ENDPOINT write output to file -rest_api_get () { +rest_api_get() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X GET $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN") echo "`$DATE` -$$-: REST API GET: ENDPOINT $ENDPOINT" >> $LOGFILE @@ -154,7 +153,7 @@ rest_api_get () { check_http_error } -rest_api_get_2 () { +rest_api_get_2() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse2.$$ -w "%{http_code}" -X GET $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN") echo "`$DATE` -$$-: REST API GET: ENDPOINT $ENDPOINT" >> $LOGFILE @@ -166,7 +165,7 @@ rest_api_get_2 () { } # HTTP POST: Given $ENDPOINT and $PAYLOAD write output to file -rest_api_post () { +rest_api_post() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d $PAYLOAD) echo "`$DATE` -$$-: REST API POST: ENDPOINT $ENDPOINT" >> $LOGFILE @@ -178,7 +177,7 @@ rest_api_post () { check_http_error } -rest_api_post_empty () { +rest_api_post_empty() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json") echo "`$DATE` -$$-: REST API POST: ENDPOINT $ENDPOINT" >> $LOGFILE @@ -190,7 +189,7 @@ rest_api_post_empty () { check_http_error } -rest_api_patch () { +rest_api_patch() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X PATCH $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD") echo "`$DATE` -$$-: REST API PATCH: ENDPOINT $ENDPOINT" >> $LOGFILE @@ -202,20 +201,20 @@ rest_api_patch () { check_http_error } -rest_api_delete () { +rest_api_delete() { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X DELETE $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN") echo "`$DATE` -$$-: REST API DELETE: $http_response: ENDPOINT $ENDPOINT" >> $LOGFILE check_http_error } -get_mv () { +get_mv() { ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume?name=$mv_name&is_relic=false" rest_api_get - mvId=$(egrep '"id"[^,]*' /tmp/rbkresponse.$$ | egrep -o 'ManagedVolume:::[a-z0-9-]*') - numChannels=$(egrep '"numChannels"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}') + mvId=$(awk '{match($0, /ManagedVolume:::[a-z0-9-]*/); if (RSTART > 0) {id=substr($0, RSTART, RLENGTH); sub(/"$/, "", id); print id}}' /tmp/rbkresponse.$$) + numChannels=$(awk '{match($0, /"numChannels":[ ]*[0-9]+/); if (RSTART > 0) {val=substr($0, RSTART, RLENGTH); sub(/.*:[ ]*/, "", val); print val}}' /tmp/rbkresponse.$$) if [[ $mvId == "" ]]; then echo ERROR: MV with name $mv_name was not found @@ -224,15 +223,15 @@ get_mv () { } -get_data_mv () { +get_data_mv() { mv_name=${HOST}_${ORACLE_SID}_data ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume?name=$mv_name&is_relic=false" rest_api_get - mvId=$(egrep '"id"[^,]*' /tmp/rbkresponse.$$ | egrep -o 'ManagedVolume:::[a-z0-9-]*') - numChannels=$(egrep '"numChannels"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}') + mvId=$(awk '{match($0, /ManagedVolume:::[a-z0-9-]*/); if (RSTART > 0) {id=substr($0, RSTART, RLENGTH); sub(/"$/, "", id); print id}}' /tmp/rbkresponse.$$) + numChannels=$(awk '{match($0, /"numChannels":[ ]*[0-9]+/); if (RSTART > 0) {val=substr($0, RSTART, RLENGTH); sub(/.*:[ ]*/, "", val); print val}}' /tmp/rbkresponse.$$) if [[ $mvId == "" ]]; then echo ERROR: MV with name $mv_name was not found @@ -241,15 +240,15 @@ get_data_mv () { } -get_log_mv () { +get_log_mv() { # Look for a log volume. If not present, return the data volume - mv_name=${HOST}_${ORACLE_SID}_log + mv_name=${HOST}_${ORACLE_SID}_logs ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume?name=$mv_name&is_relic=false" rest_api_get - logMvId=$(egrep '"id"[^,]*' /tmp/rbkresponse.$$ | egrep -o 'ManagedVolume:::[a-z0-9-]*') + logMvId=$(awk '{match($0, /ManagedVolume:::[a-z0-9-]*/); if (RSTART > 0) {id=substr($0, RSTART, RLENGTH); sub(/"$/, "", id); print id}}' /tmp/rbkresponse.$$) if [[ $logMvId == "" ]]; then echo "INFO: Log volume ($mv_name) not found. Logs will be written to DB volume" @@ -259,23 +258,23 @@ get_log_mv () { ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume?name=$mv_name&is_relic=false" rest_api_get - mvId=$(egrep '"id"[^,]*' /tmp/rbkresponse.$$ | egrep -o 'ManagedVolume:::[a-z0-9-]*') - numChannels=$(egrep '"numChannels"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}') + mvId=$(awk '{match($0, /ManagedVolume:::[a-z0-9-]*/); if (RSTART > 0) {id=substr($0, RSTART, RLENGTH); sub(/"$/, "", id); print id}}' /tmp/rbkresponse.$$) + numChannels=$(awk '{match($0, /"numChannels":[ ]*[0-9]+/); if (RSTART > 0) {val=substr($0, RSTART, RLENGTH); sub(/.*:[ ]*/, "", val); print val}}' /tmp/rbkresponse.$$) if [[ $mvId == "" ]]; then echo ERROR: MV with name $mv_name was not found exit_with_error fi else - mvId=$(egrep '"id"[^,]*' /tmp/rbkresponse.$$ | egrep -o 'ManagedVolume:::[a-z0-9-]*') - numChannels=$(egrep '"numChannels"[^,]*' /tmp/rbkresponse.$$ | awk -F: '{print $2}') + mvId=$(awk '{match($0, /ManagedVolume:::[a-z0-9-]*/); if (RSTART > 0) {id=substr($0, RSTART, RLENGTH); sub(/"$/, "", id); print id}}' /tmp/rbkresponse.$$) + numChannels=$(awk '{match($0, /"numChannels":[ ]*[0-9]+/); if (RSTART > 0) {val=substr($0, RSTART, RLENGTH); sub(/.*:[ ]*/, "", val); print val}}' /tmp/rbkresponse.$$) logMvPresent=1 echo "INFO: Log volume ($mv_name) exists with $numChannels channels" fi } -open_mv () { +open_mv() { PIDFILE=/tmp/mvLock_${mv_name}.pid check_pid @@ -285,7 +284,7 @@ open_mv () { } -close_mv () { +close_mv() { if [ -z "${SLANAME}" ]; then echo Closing MV $mv_name using default assigned SLA @@ -301,7 +300,7 @@ close_mv () { } -cleanup () { +cleanup() { # if [ $usingsatoken ]; then # ENDPOINT="https://$RUBRIK_IP/api/internal/session/me" # rest_api_delete diff --git a/rman_db.sh b/rman_db.ksh similarity index 77% rename from rman_db.sh rename to rman_db.ksh index bb652cc..fa61383 100755 --- a/rman_db.sh +++ b/rman_db.ksh @@ -1,26 +1,29 @@ -#!/bin/bash +#!/usr/bin/ksh # # RMAN DB backup with incremental Merge # Written for HCL / Nokia # v1.0 - James Pattinson - October 2025 # -# usage: rman_db.sh +# usage: rman_db.ksh get_script_dir() { - # Portable way to get script directory for Linux and HP/UX - local src="$0" + # Portable way to get script directory for Linux and HP/UX (ksh compatible) + src="$0" while [ -h "$src" ]; do - dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)" - src="$(readlink "$src")" - [[ $src != /* ]] && src="$dir/$src" + dir=$(cd -P $(dirname "$src") >/dev/null 2>&1 && pwd) + src=$(readlink "$src") + case $src in + /*) ;; # absolute path + *) src="$dir/$src";; + esac done - cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd + cd -P $(dirname "$src") >/dev/null 2>&1 && pwd } -MYDIR="$(get_script_dir)" +MYDIR=$(get_script_dir) export ORACLE_SID=$1 -. $HOME/.profile +# . $HOME/.profile export ORAENV_ASK=NO export ORACLE_SID=$1 @@ -29,12 +32,15 @@ export ORACLE_SID=$1 export ORAENV_ASK=YES -source $MYDIR/rbk_api.conf -source $MYDIR/oracle_funcs.sh +. $MYDIR/rbk_api.conf +. $MYDIR/oracle_funcs.ksh #ORACLE_SID=$1 -usage() { echo "Usage: $0 ]" 1>&2; exit 1; } +usage() { + echo "Usage: $0 ]" 1>&2 + exit 1 +} if [ -z "${ORACLE_SID}" ]; then usage @@ -46,7 +52,7 @@ fi export NLS_DATE_FORMAT='mm-dd-yyyy hh24:mi:ss' export NLS_LANG=AMERICAN_AMERICA.AL32UTF8 -MOUNTPOINT=$MOUNTPOINT_PREFIX$ORACLE_SID +MOUNTPOINT=$MOUNTPOINT_PREFIX/$ORACLE_SID/data mkdir -p $RMAN_LOG_DIR/$ORACLE_SID/ RMAN_LOG=$RMAN_LOG_DIR/$ORACLE_SID/rman_${ORACLE_SID}_DB_$(date +%d%m%y).log @@ -63,6 +69,8 @@ fi get_data_mv open_mv +echo "DEBUG: numChannels=$numChannels, MOUNTPOINT=$MOUNTPOINT" + if [ $? -ne 0 ]; then echo ERROR: Unable to open MV, aborting exit_with_error @@ -71,18 +79,20 @@ fi echo Running RMAN with log to $RMAN_LOG -if [[ $numChannels -eq 1 ]]; then +if [ $numChannels -eq 1 ]; then allocate="allocate channel 'ch1' device type disk format '$MOUNTPOINT/%U';" release="release channel ch1;" channel0="$MOUNTPOINT" else - - for i in $(seq 0 $(($numChannels - 1))); do - allocate+="allocate channel 'c$i' device type disk format '$MOUNTPOINT/c$i/%U';" - release+="release channel c$i;" + allocate="" + release="" + i=0 + while [ $i -lt $numChannels ]; do + allocate="$allocate allocate channel 'c$i' device type disk format '$MOUNTPOINT/c$i/%U';" + release="$release release channel c$i;" + i=$(expr $i + 1) done channel0="$MOUNTPOINT/c0" - fi # Save the current time (minus one hour) to ensure we catch all archive logs @@ -94,6 +104,7 @@ startTime=$(date +%m-%d-%Y\ %H:%M:%S -d '-1 hour') # Now perform the backup and merge rman nocatalog log $RMAN_LOG append > /dev/null < +# usage: rman_logs.ksh get_script_dir() { - # Portable way to get script directory for Linux and HP/UX - local src="$0" + # Portable way to get script directory for Linux and HP/UX (ksh compatible) + src="$0" while [ -h "$src" ]; do - dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)" - src="$(readlink "$src")" - [[ $src != /* ]] && src="$dir/$src" + dir=$(cd -P $(dirname "$src") >/dev/null 2>&1 && pwd) + src=$(readlink "$src") + case $src in + /*) ;; # absolute path + *) src="$dir/$src";; + esac done - cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd + cd -P $(dirname "$src") >/dev/null 2>&1 && pwd } -MYDIR="$(get_script_dir)" +MYDIR=$(get_script_dir) export ORACLE_SID=$1 -. $HOME/.profile +#. $HOME/.profile export ORAENV_ASK=NO export ORACLE_SID=$1 @@ -28,12 +31,15 @@ export ORACLE_SID=$1 export ORAENV_ASK=YES -source $MYDIR/rbk_api.conf -source $MYDIR/oracle_funcs.sh +. $MYDIR/rbk_api.conf +. $MYDIR/oracle_funcs.ksh #ORACLE_SID=$1 -usage() { echo "Usage: $0 ]" 1>&2; exit 1; } +usage() { + echo "Usage: $0 ]" 1>&2 + exit 1 +} if [ -z "${ORACLE_SID}" ]; then usage @@ -56,11 +62,7 @@ if [ $? -ne 0 ]; then exit_with_error fi -if [[ $logMvPresent -eq 1 ]] && [[ $numChannels -eq 1 ]] ; then - MOUNTPOINT=$MOUNTPOINT_PREFIX${ORACLE_SID}_log -else - MOUNTPOINT=$MOUNTPOINT_PREFIX$ORACLE_SID -fi +MOUNTPOINT=$MOUNTPOINT_PREFIX/$ORACLE_SID/logs # Disk space check dusage=$(df -Ph | egrep "$MOUNTPOINT" | sed s/%//g | awk -v spaceWarn=$MV_SPACE_WARN '{ if($5 >= spaceWarn) print $0;}') @@ -73,19 +75,16 @@ fi echo Running RMAN with log to $RMAN_LOG -if [[ $numChannels -eq 1 ]]; then - allocate="allocate channel 'ch1' device type disk format '$MOUNTPOINT/%U';" - release="release channel ch1;" - channel0="$MOUNTPOINT" -else +allocate="" +release="" +i=0 +while [ $i -lt $numChannels ]; do + allocate="$allocate allocate channel 'c$i' device type disk format '$MOUNTPOINT/c$i/%U';" + release="$release release channel c$i;" + i=$(expr $i + 1) +done +channel0="$MOUNTPOINT/c0" - for i in $(seq 0 $(($numChannels - 1))); do - allocate+="allocate channel 'c$i' device type disk format '$MOUNTPOINT/log_c$i/%U';" - release+="release channel c$i;" - done - channel0="$MOUNTPOINT/log_c0" - -fi # RMAN Part Here ############################################################################### @@ -97,11 +96,11 @@ crosscheck backup; run { set controlfile autobackup format for device type disk to '$channel0/cf_%F'; $allocate -backup archivelog all not backed up 1 times tag 'RUBRIK_LOGS'; +backup archivelog all not backed up 1 times tag 'rubrik_pit_logs'; $release } allocate channel for maintenance device type disk; -delete noprompt backup of archivelog all completed before 'sysdate-3' tag RUBRIK_LOGS; +delete noprompt backup of archivelog all completed before 'sysdate-2' tag rubrik_pit_logs; release channel; EOF ############################################################################### @@ -121,7 +120,8 @@ find $MOUNTPOINT -type f -exec chmod 644 {} + 2>/dev/null close_mv -if [[ $HOSTLOGRET -gt 0 ]]; then + +if [ $HOSTLOGRET -gt 0 ]; then echo "Starting post-backup RMAN log purge" diff --git a/rubrik_mv_op.sh b/rubrik_mv_op.ksh similarity index 63% rename from rubrik_mv_op.sh rename to rubrik_mv_op.ksh index 1be4e73..f72a99c 100755 --- a/rubrik_mv_op.sh +++ b/rubrik_mv_op.ksh @@ -1,30 +1,36 @@ -#!/bin/bash +#!/usr/bin/ksh # # Open and Close MV using API call to CDM # Written for AvonHCL / Nokia # v1.0 - James Pattinson - October 2025 # -# usage: rubrik_mv_op.sh -d -v -o +# usage: rubrik_mv_op.ksh -d -v -o # # -d Oracle DBNAME # -v Volume to operate on, logs or data # -o Operation to perform - open or close the MV get_script_dir() { - # Portable way to get script directory for Linux and HP/UX - local src="$0" + # Portable way to get script directory for Linux and HP/UX (ksh compatible) + src="$0" while [ -h "$src" ]; do - dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)" - src="$(readlink "$src")" - [[ $src != /* ]] && src="$dir/$src" + dir=$(cd -P $(dirname "$src") >/dev/null 2>&1 && pwd) + src=$(readlink "$src") + case $src in + /*) ;; # absolute path + *) src="$dir/$src";; + esac done - cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd + cd -P $(dirname "$src") >/dev/null 2>&1 && pwd } -MYDIR="$(get_script_dir)" -source $MYDIR/rbk_api.conf -source $MYDIR/oracle_funcs.sh +MYDIR=$(get_script_dir) +. $MYDIR/rbk_api.conf +. $MYDIR/oracle_funcs.ksh -usage() { echo "Usage: $0 -d -v -o " 1>&2; exit 1; } +usage() { + echo "Usage: $0 -d -v -o " 1>&2 + exit 1 +} force=0