├── LICENSE ├── README.md ├── logind-disable-hibernate-swap-check.conf └── zram-hibernate /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zram-hibernate 2 | 3 | Allows dynamic swap changes to activate disk-based storage as swap for hibernation support when a system typically uses only zram swap during normal operation. 4 | 5 | Some applications keep growing until they fill RAM and swap (Google Chrome is a great example). This can significantly slow down a system by "swapping it to a crawl" - filling memory and swap to the point that the system isn't doing anything but swapping. 6 | 7 | To avoid this problem entirely, one can simply disable swap so it can't slow down the system, but then virtual memory isn't available for the application to use during normal operation either. Using ZRAM instead of a swap disk is a great alternative since it doesn't have as much of a negative performance impact. A system with ZRAM, however, cannot hibernate to disk, which is a significant portability limitation for systems with a short battery life, no battery at all, or broken/missing suspend/standby functionality. 8 | 9 | That is why I created this tool - to allow one to use ZRAM during normal operation for reasonable virtual memory expansion, while also reasonably constraining application memory use, but also allowing the system to activate disk swap when needed for hibernation. 10 | 11 | ## Linux Hibernation High-Level Summary 12 | Linux Hibernation has 2 main phases: Shutdown and Resume. 13 | 14 | - Shutdown is a matter of pausing applications, storing memory contents and a hibernation signature to disk-based swap, and powering off the system. 15 | - Resume is similar to a normal system boot process, except that the kernel `resume=` parameter identifies a swap device to attempt to use to restore the system state upon bootup. When this option is present on boot, the kernel checks the swap device specified by `resume` for the hibernation signature, and if present, it restores the system state from the swap device into memory. Upon successful restoration, the kernel then clears the hibernation signature from the resume device and resumes execution of the system state as it was previous to Shutdown. 16 | 17 | Resume without a hibernation signature (such as after a normal system shutdown) will result in the standard system bootup process - running an instance of init/systemd/openrc as PID 1, etc. 18 | 19 | It is highly recommended to use a dedicated swap partition for hibernation. Swap files are less safe for hibernation as it could trigger race conditions or caching bugs in device drivers, filesystems or kernel cache. 20 | 21 | ## Principle of Operations / How it works 22 | 23 | This script finds and activates a disk-based swap, moves all ZRAM content to the swap device, disables ZRAM, and hibernates the system to the swap device. Upon resume from hibernation (after the system reboots and the kernel resumes successfully) it restores the original swap configuration, restoring ZRAM, and disabling any disk swap it added for during the hibernation/shutdown phase. 24 | 25 | Part of this process is detecting where your system wants to store it's hibernation data. This is primarily done by analyzing your system's kernel command line for the `resume=` parameter. This is the safest method as your system would need this to resume from hibernation after shutdown anyhow. Assuming this is not present, however, the system can use /etc/fstab swap entries for more obscure configurations. 26 | 27 | This script also detects if existing swap devices are already active and simply uses them if it believes there is already enough swap to contain your hibernation data. In this case, it would also keep them active after resume, since that was the original state. 28 | 29 | ## Usage 30 | 31 | zram-hibernate supports a number of arguments that affect its operation and provide troubleshooting information: 32 | 33 | ``` 34 | -h --help show help text 35 | -v --verbose increase output verbosity 36 | -n --dry-run show what would be done without making changes 37 | -t --test do everything except actually affect the system power state 38 | -d --debug debug data parsing logic only 39 | ``` 40 | 41 | To safely test swap detection and operation before system-wide installation you can simply call: 42 | 43 | ``` 44 | zram-hibernate -tn 45 | ``` 46 | 47 | System-wide installation is performed by hooking into systemd's hibernation callback, placing the script or a symlink to it in `/usr/lib/systemd/system-sleep/`. 48 | 49 | Normal usage is generally transparent after installation by executing a system power command like: 50 | 51 | ``` 52 | systemctl hibernate 53 | ``` 54 | 55 | To manually invoke hibernation without systemwide installation you can also call: 56 | 57 | ``` 58 | zram-hibernate 59 | ``` 60 | 61 | Verbosity defaults to level 2 when running manually, and 0 when called by systemctl. 62 | 63 | If detection fails to work as expected, or you need to manually specify the hibernation device to use, you can create a configuration file `/etc/zram-hibernate.conf` with a line specifying the desired swap device. The file contents should be similar to the following lines: 64 | 65 | ``` 66 | KERNEL_SWAP_DEVICE=/dev/mapper/swap-device 67 | KERNEL_SWAP_DEVICE=/dev/disk/by-label/My_Swap_Disk 68 | KERNEL_SWAP_DEVICE=/dev/disk/by-uuid/1234567890ABCDE 69 | KERNEL_SWAP_DEVICE=/swapfile.swp 70 | KERNEL_SWAP_DEVICE=/swapfile.swp 71 | ``` 72 | 73 | This file is also necessary if you want to use a swap file or the `resume_offset=` kernel option, as they often mean runtime detection of the `resume=` kernel option will not properly detect the actual resume device. Note that swap files can have unknown race conditions, and it is recommended to use a dedicated swap partition whenever possible. If you use the configuration file, be aware that many of the sanity checks done during the detection process are no longer possible, so it's your responsibility to ensure the kernel command line arguments and configuration file contents are correctly configured to work properly. 74 | 75 | --- 76 | Background reference information for the extremely curious follows... 77 | 78 | Information about integration with systemd: 79 | 80 | https://blog.christophersmart.com/2016/05/11/running-scripts-before-and-after-suspend-with-systemd/ 81 | 82 | Short summary: add script to /usr/lib/systemd/system-sleep/ that supports pre/post arguments. More details available in systemd-suspend.service manpage. 83 | 84 | There's likely a way to integrate with openrc as well, possibly using apcid or something like that, but I haven't needed to do so yet. 85 | -------------------------------------------------------------------------------- /logind-disable-hibernate-swap-check.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | Environment=SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1 3 | -------------------------------------------------------------------------------- /zram-hibernate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # hibernate wrapper: 3 | # - check for or add disk swap 4 | # - maybe remove zram 5 | # - call systemctl hibernate 6 | # - sleep 5s or so 7 | # - restore zram if removed 8 | # - remove/restore disk swap state 9 | ########################################################### 10 | # Copyright 2022 Brian Gisseler 11 | # 12 | # Licensed under the Apache License, Version 2.0 (the "License"); 13 | # you may not use this file except in compliance with the License. 14 | # You may obtain a copy of the License at 15 | # 16 | # http://www.apache.org/licenses/LICENSE-2.0 17 | # 18 | # Unless required by applicable law or agreed to in writing, software 19 | # distributed under the License is distributed on an "AS IS" BASIS, 20 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | # See the License for the specific language governing permissions and 22 | # limitations under the License. 23 | 24 | TMPFN="/tmp/$( basename "$0" ).tmp" 25 | 26 | initTputVars() { 27 | # NOTE: look at man 5 terminfo for details on these codes 28 | TPUTEL=`tput el` # clear to EOL 29 | TPUTED=`tput ed` # clear to end-of-screen 30 | TPUTSGR0=`tput sgr0` # reset to normal formatting 31 | # save codes (no args) 32 | for S in dim bold sc rc cud1 cuu1 cub1 cuf1 ; do 33 | printf -v "TPUT${S}" '%s' "$( tput $S )" 34 | done 35 | # colors 36 | for C in $( seq 0 7 ) ; do 37 | printf -v "TPUTSETAF${C}" '%s' "$( tput setaf $C )" 38 | printf -v "TPUTSETAB${C}" '%s' "$( tput setab $C )" 39 | printf -v "TPUTSETAB${C}" '%s' "$( tput setab $C )" 40 | done 41 | # set COLUMNS and LINES variables appropriately 42 | if [ -z "$COLUMNS" ]; then 43 | tput cols > /dev/null 44 | fi 45 | } 46 | initTputVars 47 | 48 | clearline() { 49 | printf "\r$TPUTEL" 50 | } 51 | 52 | cleardown() { 53 | printf "\r$TPUTED" 54 | } 55 | 56 | saveCursor() { 57 | echo -en "$TPUTsc" 58 | } 59 | 60 | restoreCursor() { 61 | echo -en "$TPUTrc" 62 | } 63 | 64 | getCursorYX() { 65 | # Returns the text cursor's current row and column (0 indexed) 66 | local CURSORX 67 | local CURSORY 68 | # based on code from https://stackoverflow.com/questions/2575037/how-to-get-the-cursor-position-in-bash 69 | # based on a script from http://invisible-island.net/xterm/xterm.faq.html 70 | exec < /dev/tty 71 | oldstty=$(stty -g) 72 | stty raw -echo min 0 73 | IFS=';' read -sdR -p $'\E[6n' CURSORY CURSORX 74 | stty $oldstty 75 | # parse and return data 76 | CURSORY="${CURSORY#*[}" 77 | CURSORY=$(( CURSORY - 1 )) 78 | CURSORX=$(( CURSORX - 1 )) 79 | echo "$CURSORY $CURSORX" 80 | } 81 | 82 | setCursorYX() { 83 | # moves the text cursor to the specified row and column 84 | # $1 row # (0 indexed) 85 | # $2 col # (0 indexed) 86 | local Y=$1 87 | local X=$2 88 | tput cup "$Y" "$X" 89 | } 90 | 91 | cursestest() { 92 | local C 93 | local I 94 | local F 95 | local B 96 | local S 97 | echo "basic cursor test..." 98 | getCursorYX 99 | echo "homing cursor via snake... " 100 | local X 101 | local Y 102 | local D=1 103 | read Y X <<<"$( getCursorYX )" 104 | echo -n "($X,$Y) " 105 | echo -en '@\b' 106 | while [ $Y != 0 -o $X != 0 ]; do 107 | echo -en ' ' 108 | (( X += D )) 109 | if [ $X -lt 0 -o $X -gt $COLUMNS ]; then 110 | (( Y -= 1 )) 111 | (( D = 0 - D )) 112 | (( X += D )) 113 | fi 114 | setCursorYX $Y $X 115 | echo -en '@\b' 116 | sleep 0.002 117 | done 118 | } 119 | 120 | getLock() { 121 | if [ -e "$TMPFN" ]; then 122 | if [ ! -s "$TMPFN" ]; then 123 | echo "[$$] FATAL: unable to acquire lock: empty lockfile: $TMPFN" >&2 124 | exit 1 125 | fi 126 | local LOCKPID=$( grep -Em 1 "^LOCKPID=" < "$TMPFN" | sed -r 's/^LOCKPID=//g' ) 127 | if [ -e "/proc/$LOCKPID" ]; then 128 | echo "[$$] FATAL: unable to acquire lock: lock busy by pid $LOCKPID: /proc/$LOCKPID" >&2 129 | exit 1 130 | fi 131 | if [ $( ps $LOCKPID | wc -l ) != 1 ]; then 132 | echo "[$$] FATAL: unable to acquire lock: lock busy by pid $LOCKPID: ps $LOCKPID" >&2 133 | exit 1 134 | fi 135 | isVerbose 2 && echo "[$$] removing stale lock..." 136 | doUnlock 137 | getLock 138 | else 139 | isVerbose 2 && echo "[$$] creating tmpfile..." 140 | touch "$TMPFN" 141 | sleep 0.1 142 | echo "LOCKPID=$$" >> "$TMPFN" 143 | sleep 0.1 144 | local LOCKPID=$( grep -Em 1 "^LOCKPID=" < "$TMPFN" | sed -r 's/^LOCKPID=//g' ) 145 | if [ "$LOCKPID" != "$$" ]; then 146 | echo "[$$] FATAL: unable to acquire lock: owned by another process: $TMPFN" >&2 147 | exit 1 148 | fi 149 | local LOCKPIDS=$( grep -Ec "^LOCKPID=" < "$TMPFN" ) 150 | if [ $LOCKPIDS != 1 ]; then 151 | echo "[$$] WARNING: multiple locks attempted: $TMPFN" >&2 152 | grep -E "^LOCKPID=" < "$TMPFN" | grep -Exv "LOCKPID=$$" | sed -r "s~^LOCKPID=([0-9]+)\$~[$$] - \1~g" 153 | sed -ri "s~^LOCKPID=[0-9]+\$~LOCKPID=$$~" "$TMPFN" 154 | local LOCKPID=$( grep -Em 1 "^LOCKPID=" < "$TMPFN" | sed -r 's/^LOCKPID=//g' ) 155 | if [ "$LOCKPID" != "$$" ]; then 156 | echo "[$$] FATAL: unable to acquire lock: owned by another process: $TMPFN" >&2 157 | exit 1 158 | fi 159 | fi 160 | isVerbose 2 && echo "[$$] lock acquired." 161 | fi 162 | } 163 | 164 | doUnlock() { 165 | if [ -f "$TMPFN" ]; then 166 | if [ $VERBOSE -lt 3 ]; then 167 | rm -f "$TMPFN" 168 | else 169 | # with verbosity level 3, don't delete the TMPFN unless we're replacing it immediately 170 | local LOCKPID=$( grep -Em 1 "^LOCKPID=" < "$TMPFN" | sed -r 's/^LOCKPID=//g' ) 171 | if [ "$LOCKPID" != "$$" ]; then 172 | rm -f "$TMPFN" 173 | fi 174 | fi 175 | fi 176 | } 177 | 178 | table2vars() { 179 | local PREFIX=$1 180 | sed -r 's/\s+/\t/g' - | awk -F"\t" -vPREFIX="$PREFIX" ' 181 | BEGIN { 182 | RECCNT=0 183 | FIELDCNT=0 184 | } 185 | (FIELDCNT==0) { 186 | for(F=1; F<=NF; F++) { 187 | #print "FIELD[" F "]=" $F 188 | FIELD[F] = $F 189 | gsub(/[^0-9A-Z_a-z]/, "", FIELD[F]) 190 | FIELDCNT += 1 191 | } 192 | #print "found " FIELDCNT " fields." 193 | next 194 | } 195 | { 196 | for(F=1; F<=FIELDCNT; F++) { 197 | if (match($F, /^-?[0-9]+$/)) { 198 | QUOTE="" 199 | } else if (match($F, /^[-+,.\/0-9:A-Z^_a-z]+$/)) { 200 | QUOTE="" 201 | } else { 202 | QUOTE="\"" 203 | } 204 | print PREFIX "" RECCNT "_" FIELD[F] "=" QUOTE $F QUOTE 205 | shift 206 | } 207 | RECCNT += 1 208 | } 209 | END { 210 | print PREFIX "S=" RECCNT 211 | }' - 212 | return $? 213 | } 214 | 215 | getMemInfo() { 216 | grep -E '^(Mem|Swap)(Free|Available|Total|Used):' < /proc/meminfo | sed -r 's~^([^:]+):\s+([0-9]+)( kB)?~\1=\2~' 217 | return $? 218 | } 219 | 220 | getCalculatedInfo() { 221 | # Ensure essential information is present 222 | if [ -z "$SwapTotal" ]; then 223 | echo "FATAL: Unable to determine SwapTotal" >&2 224 | return 1 225 | elif [ $(( SwapTotal +0 )) -lt 1 ] && [ "$SwapTotal" != "0" ]; then 226 | echo "FATAL: SwapTotal is invalid: $SwapTotal" >&2 227 | return 1 228 | fi 229 | # Ensure SwapUsed is defined 230 | SwapUsed=$(( SwapTotal - SwapFree )) 231 | if [ "$SwapUsed" == "" ]; then 232 | echo "FATAL: Unable to determine SwapUsed." >&2 233 | return 1 234 | fi 235 | echo "SwapUsed=$SwapUsed" 236 | # calculate overcommit 237 | if [ $MemAvailable -gt $SwapUsed ]; then 238 | MemOvercommit=0 239 | else 240 | MemOvercommit=$(( SwapUsed - MemAvailable )) 241 | fi 242 | echo "MemOvercommit=$MemOvercommit" 243 | # done 244 | return 0 245 | } 246 | 247 | getActiveSwapInfo() { 248 | { 249 | IFS= read -r HEADER 250 | printf '%s\n' "$HEADER" 251 | sort -gk 5 252 | } < /proc/swaps | table2vars "ACTIVESWAP" 253 | return $? 254 | } 255 | 256 | getFstabSwapInfo() { 257 | awk "$( cat << FSTABSWAPS 258 | BEGIN { 259 | NUM=0 260 | } 261 | /^\\s*[^#]/{ 262 | # UUID/device mountpoint type options dump pass 263 | DEVICE=\$1 264 | #MOUNT=\$2 265 | TYPE=\$3 266 | OPTIONS=\$4 267 | #DUMP=\$5 268 | #PASS=\$6 269 | if (!match(TYPE, /^([Ss][Ww][Aa][Pp]|[Aa][Uu][Tt][Oo])\$/)) { 270 | next 271 | } 272 | #print "-- " DEVICE ": TYPE=" TYPE ", OPTIONS=" OPTIONS 273 | if (match(DEVICE, /^[^/]/)) { 274 | # lookup device name using blkid -t DEVICE 275 | CMD="blkid -t '" DEVICE "' | sed -r 's~^(/[^:]+): ~DEVICE=\"\\\\1\" ~g ; s~\" ([A-Z])~\"\\\\n\\\\1~g'" 276 | I = NUM - 1 277 | while ((CMD | getline RET) > 0) { 278 | if (match(RET, /^DEVICE=/)) { 279 | I += 1 280 | } 281 | print "FSTABSWAP" I "_" RET 282 | } 283 | close(CMD) 284 | while (I >= NUM) { 285 | print "FSTABSWAP" NUM "_TYPE=\"" TYPE "\"" 286 | print "FSTABSWAP" NUM "_OPTIONS=\"" OPTIONS "\"" 287 | NUM += 1 288 | } 289 | } else { 290 | print "FSTABSWAP" NUM "_DEVICE=\"" DEVICE "\"" 291 | print "FSTABSWAP" NUM "_TYPE=\"" TYPE "\"" 292 | print "FSTABSWAP" NUM "_OPTIONS=\"" OPTIONS "\"" 293 | NUM += 1 294 | } 295 | } 296 | END { 297 | print "FSTABSWAPS=" NUM 298 | } 299 | FSTABSWAPS 300 | )" < /etc/fstab | sort -u 301 | return $? 302 | } 303 | 304 | getResumeInfo() { 305 | # try to parse configuration file to get resume values 306 | if [ -f "/etc/zram-hibernate.conf" ]; then 307 | local CONF_FILE="/etc/zram-hibernate.conf" 308 | if [ ! -r "$CONF_FILE" ]; then 309 | echo "getResumeInfo() Unable to read configuration file: $CONF_FILE: permission denied" >&2 310 | return 128 311 | fi 312 | local KERNEL_RESUME_DEVICE=$( grep -Em1 '^KERNEL_SWAP_DEVICE=' "$CONF_FILE" | cut -d= -f2 | sed -r 's~^(["'"'"'])(.*)\1$~\2~g' ) 313 | if [ -z "$KERNEL_RESUME_DEVICE" ]; then 314 | true 315 | elif [ ! -e "$KERNEL_RESUME_DEVICE" ]; then 316 | echo "getResumeInfo() manually configured swap device failed: $KERNEL_RESUME_DEVICE: file does not exist. Please check $CONF_FILE" >&2 317 | return 128 318 | else 319 | echo "KERNEL_RESUME_RAW=$KERNEL_RESUME_DEVICE" 320 | echo "KERNEL_RESUME_DEVICE=$KERNEL_RESUME_DEVICE" 321 | blkid "$KERNEL_RESUME_DEVICE" | sed -r 's~^(/[^:]+): ~DEVICE="\1" ~g ; s~" ([A-Z])~"\n\1~g' | sed -r 's~^(.*)$~KERNEL_RESUME_\1~g' | grep -Ev '^KERNEL_RESUME_DEVICE=|^KERNEL_RESUME_TYPE=' 322 | echo "KERNEL_RESUME_CONF='$CONF_FILE'" 323 | return 0 324 | fi 325 | fi 326 | # parse kernel cmdline to get resume value 327 | local KERNEL_RESUME_RAW=$( sed -r 's~^(.+ )?(resume=[^ ]+)( .+)?$~\2~g ; s~^resume=~~g ; t ; d' /proc/cmdline ) 328 | local RET=$? 329 | #echo "getResumeInfo() KERNEL_RESUME_RAW=$KERNEL_RESUME_RAW" >&2 330 | local KERNEL_RESUME_DEVICE=$KERNEL_RESUME_RAW 331 | if [[ $KERNEL_RESUME_RAW =~ ^.*=.*$ ]]; then 332 | KERNEL_RESUME_DEVICE=$( blkid -t "$KERNEL_RESUME_RAW" | sed -r 's~^(/[^:]+): ~DEVICE="\1" ~g ; s~" ([A-Z])~"\n\1~g' | sed -r 's~^DEVICE=~~g ; t REMOVEQUOTES ; d ; : REMOVEQUOTES ; s~^"(.*)"$~\1~g' ) 333 | #echo "getResumeInfo() KERNEL_RESUME_DEVICE=$KERNEL_RESUME_DEVICE" >&2 334 | fi 335 | if [ ! -b "$KERNEL_RESUME_DEVICE" ]; then 336 | echo "getResumeInfo() Not a valid block device: KERNEL_RESUME_DEVICE=$KERNEL_RESUME_DEVICE" >&2 337 | RET=128 338 | fi 339 | # parse kernel cmdline to get resume_offset 340 | local KERNEL_RESUME_OFFSET_RAW=$( sed -r 's~^(.+ )?(resume_offset=[^ ]+)( .+)?$~\2~g ; s~^resume_offset=~~g ; t ; d' /proc/cmdline ) 341 | # <(cat /proc/cmdline <(echo -n ' resume_offset=6051767' ) | tr -d "\n" ) 342 | local ORET=$? 343 | local KERNEL_RESUME_OFFSET=$KERNEL_RESUME_OFFSET_RAW 344 | if [ "$KERNEL_RESUME_OFFSET_RAW" == "" ]; then 345 | #echo "getResumeInfo():$LINENO KERNEL_RESUME_OFFSET=$KERNEL_RESUME_OFFSET" >&2 346 | true 347 | elif [[ $KERNEL_RESUME_OFFSET_RAW =~ ^[0-9]+$ ]]; then 348 | #echo "getResumeInfo():$LINENO KERNEL_RESUME_OFFSET=$KERNEL_RESUME_OFFSET" >&2 349 | true 350 | # TODO: find KERNEL_RESUME_REAL_DEVICE 351 | else 352 | echo "getResumeInfo():$LINENO Not a valid offset: KERNEL_RESUME_OFFSET=$KERNEL_RESUME_OFFSET" >&2 353 | if [ $ORET -eq 0 ]; then 354 | ORET=128 355 | fi 356 | fi 357 | if [ $RET == 0 ]; then 358 | echo "KERNEL_RESUME_RAW=$KERNEL_RESUME_RAW" 359 | echo "KERNEL_RESUME_DEVICE=$KERNEL_RESUME_DEVICE" 360 | blkid "$KERNEL_RESUME_DEVICE" | sed -r 's~^(/[^:]+): ~DEVICE="\1" ~g ; s~" ([A-Z])~"\n\1~g' | sed -r 's~^(.*)$~KERNEL_RESUME_\1~g' | grep -Ev '^KERNEL_RESUME_DEVICE=|^KERNEL_RESUME_TYPE=' 361 | if [ $ORET == 0 ]; then 362 | echo "KERNEL_RESUME_OFFSET=$KERNEL_RESUME_OFFSET" 363 | fi 364 | return $ORET 365 | else 366 | return $RET 367 | fi 368 | } 369 | 370 | getSwapInfo() { 371 | # merge these variables into a single namespace: 372 | # - ACTIVESWAP_* 373 | # - FSTABSWAP_* 374 | # - if no size is present (from activeSwap), determine size of DEVICE and set SWAP_SIZE here, along with setting SWAP_USED=0. 375 | ( 376 | getResumeInfo 377 | getActiveSwapInfo 378 | getFstabSwapInfo 379 | ) | awk "$( cat << SWAPVARS 380 | BEGIN { 381 | ACTIVESWAPS=0 382 | ACTIVESWAP_KEYS=0 383 | FSTABSWAPS=0 384 | FSTABSWAP_KEYS=0 385 | } 386 | function getDevSize(DEVICE, TYPE,m,CMD,RET) { 387 | TYPE="0 0:0 0" 388 | if (match(DEVICE, /^[- ,./0-9:@A-Z^\\\\_a-z~]+$/)) { 389 | CMD="stat -c '%d %Hr:%Lr %s' " DEVICE 390 | CMD | getline TYPE 391 | close(CMD) 392 | } 393 | # block device 394 | if (match(TYPE, /^5 [0-9]+:[0-9]+ 0$/, m)) { 395 | CMD="sed -r 's~^.*\\\\s+([0-9]+)\\\\s+(\\\\S+)$~/dev/\\\\2\\\\t\\\\1~g ; t ; d' /proc/partitions | grep -E '^" DEVICE "\\\\s' | cut -f2" 396 | CMD | getline SIZE 397 | close(CMD) 398 | # regular file 399 | } else if (match(TYPE, /^([0-9]+) 0:0 ([1-9][0-9]*)$/, m)) { 400 | SIZE=m[2] / 1024 401 | # default to zero 402 | } else { 403 | SIZE=0 404 | } 405 | return SIZE 406 | } 407 | function getSwapIndexForDevice(DEVICE, I) { 408 | for(I in SWAP) { 409 | if (SWAP[I]["DEVICE"] == DEVICE) { 410 | return I 411 | } 412 | } 413 | return "" 414 | } 415 | /^KERNEL_RESUME_[A-Z]+=/{ 416 | if (match(\$0, /^KERNEL_RESUME_([A-Z]+)=(\S+)$/, m)) { 417 | if (match(m[2], /^(['"])(.*)(['"])$/, m2) && m2[1] == m2[3]) { 418 | m[2]=m2[2] 419 | } 420 | KERNEL_RESUME[m[1]]=m[2] 421 | } 422 | next 423 | } 424 | /^ACTIVESWAPS=/{ 425 | if (match(\$0, /^ACTIVESWAPS=([0-9]+)$/, m)) { 426 | ACTIVESWAPS=m[1] 427 | next 428 | } 429 | } 430 | /^FSTABSWAPS=/{ 431 | if (match(\$0, /^FSTABSWAPS=([0-9]+)$/, m)) { 432 | FSTABSWAPS=m[1] 433 | next 434 | } 435 | } 436 | /^ACTIVESWAP[0-9]+_/ { 437 | if (match(\$0, /^ACTIVESWAP([0-9]+)_([0-9A-Z_a-z]+)=(.*)$/, m)) { 438 | ACTIVESWAP[m[1]][m[2]]=m[3] 439 | if (ACTIVESWAP_KEY_IDX[m[2]] > "") { 440 | ACTIVESWAP_KEY[ACTIVESWAP_KEY_IDX[m[2]]]=m[2] 441 | } else { 442 | ACTIVESWAP_KEY[ACTIVESWAP_KEYS]=m[2] 443 | ACTIVESWAP_KEY_IDX[m[2]]=ACTIVESWAP_KEYS 444 | ACTIVESWAP_KEYS+=1 445 | } 446 | if (m[2] == "Filename") { 447 | if (ACTIVESWAP_DEVICE_IDX[m[3]] == "") { 448 | ACTIVESWAP_DEVICE_IDX[m[3]]=m[1] 449 | } 450 | } 451 | next 452 | } 453 | } 454 | /^FSTABSWAP[0-9]+_/ { 455 | if (match(\$0, /^FSTABSWAP([0-9]+)_([0-9A-Z_a-z]+)=(.*)$/, m)) { 456 | if (match(m[3], /^(['"])(.*)(['"])$/, m3) && m3[1] == m3[3]) { 457 | m[3]=m3[2] 458 | } 459 | FSTABSWAP[m[1]][m[2]]=m[3] 460 | if (FSTABSWAP_KEY_IDX[m[2]] > "") { 461 | FSTABSWAP_KEY[FSTABSWAP_KEY_IDX[m[2]]]=m[2] 462 | } else { 463 | FSTABSWAP_KEY[FSTABSWAP_KEYS]=m[2] 464 | FSTABSWAP_KEY_IDX[m[2]]=FSTABSWAP_KEYS 465 | FSTABSWAP_KEYS+=1 466 | } 467 | if (m[2] == "DEVICE") { 468 | if (FSTABSWAP_DEVICE_IDX[m[3]] == "") { 469 | FSTABSWAP_DEVICE_IDX[m[3]]=m[1] 470 | } 471 | } 472 | next 473 | } 474 | } 475 | { 476 | print "UNKNOWN_SWAP_LINE_" NR "=" \$0 477 | } 478 | END { 479 | SWAPS=0 480 | #print "ACTIVESWAP_KEYS:" 481 | #for(I=0; I "") { 537 | #print " [" K "]: " FSTABSWAP_KEY[K] "=" FSTABSWAP[I][FSTABSWAP_KEY[K]] 538 | SWAP[SWAPIDX][toupper(FSTABSWAP_KEY[K])]=FSTABSWAP[I][FSTABSWAP_KEY[K]] 539 | } 540 | } 541 | } 542 | # ensure we fill out details for each swap 543 | print "SWAPS=" SWAPS 544 | #print "SWAPS:" 545 | for(I=0; I&2 582 | exit 1 583 | fi 584 | grep -Ev "$FILTER" "$NEWFN" > "$NEWFN.tmp" \ 585 | && echo "$LINE" >> "$NEWFN.tmp" \ 586 | && mv "$NEWFN.tmp" "$NEWFN" 587 | done 588 | mv "$NEWFN" "$TMPFN" 589 | } 590 | 591 | getVars() { 592 | getMemInfo | updateVars || return $? 593 | getSwapInfo | updateVars || return $? 594 | # HACK: DUMP DEBUG INFO 595 | #cat "$TMPFN" 596 | . "$TMPFN" 597 | getCalculatedInfo | updateVars || return $? 598 | # HACK: DUMP DEBUG INFO 599 | #cat "$TMPFN" 600 | . "$TMPFN" 601 | return 0 602 | } 603 | 604 | debugVars() { 605 | for F in getMemInfo getResumeInfo getActiveSwapInfo getFstabSwapInfo getSwapInfo ; do 606 | echo " ----- $F() ----------------------------------------" 607 | $F || echo "$F() returned: $?" 608 | done 609 | echo " ---------------------------------------------" 610 | if isVerbose 3 ; then 611 | getMemInfo | updateVars || return $? 612 | getSwapInfo | updateVars || return $? 613 | . "$TMPFN" 614 | getCalculatedInfo || echo "getCalculatedInfo() returned: $?" 615 | # DUMP DEBUG INFO 616 | cat "$TMPFN" 617 | fi 618 | return 0 619 | } 620 | 621 | selectObject() { 622 | # select a named object with properties and set variables accordingly 623 | # $1 object prefix (SWAP, DISK, etc) 624 | # $2 object index in array 625 | local REGEX_PREFIX="^$1$2_" 626 | local REGEX_GREP="$REGEX_PREFIX[0-9A-Z_a-z]+=" 627 | local PREFIX_REPLACEMENT="$1_" 628 | local VARS=$( set | grep -E "$REGEX_GREP" | sed -r "s~$REGEX_PREFIX~$PREFIX_REPLACEMENT~g" ) 629 | local RET=$? 630 | # echo "selectObject($1,$2): VARS=$VARS" 631 | export $VARS 632 | return $RET 633 | } 634 | 635 | prettyKB() { 636 | local KB=$1 637 | KB=$(( KB +0 )) 638 | if [ -z "$KB" ]; then 639 | echo -n "(unable to parse '$KB')" 640 | elif grep -Eqv '^[0-9]+$' <<<"$KB" ; then 641 | echo -n "(invalid integer '$KB')" 642 | elif [ $KB -gt 10485760 ]; then 643 | echo -n "$(( KB / 1048576 ))G" 644 | elif [ $KB -gt 10240 ]; then 645 | echo -n "$(( KB / 1024 ))M" 646 | else 647 | echo -n "${KB}K" 648 | fi 649 | } 650 | 651 | status() { 652 | echo "[$( date +%T )] Used=$( prettyKB $((SwapUsed + MemTotal - MemAvailable)) ) MemFree=$( prettyKB $MemFree ) SwapUsed=$( prettyKB $SwapUsed )/$( prettyKB $SwapTotal ) Overcommit=$( prettyKB $MemOvercommit )" 653 | if [ -n "$SWAPS" ] && [ "$SWAPS" -gt 0 ]; then 654 | for I in `seq 0 $(( SWAPS - 1 ))` ; do 655 | selectObject SWAP $I 656 | local SWAPFN=$( basename "$SWAP_DEVICE" ) 657 | if [ -n "$SWAPFN" ]; then 658 | if [ -n "$SWAP_SIZE" ] && [ $(( SWAP_SIZE +0 )) -gt 0 ]; then 659 | SWAPPERC=$( awk -vU=$SWAP_USED -vS=$SWAP_SIZE 'BEGIN{printf("%4.1f",100.0*U/S)}' ) 660 | elif [[ $SWAP_USED -eq 0 ]]; then 661 | SWAPPERC=" 0.0" 662 | elif [[ $SWAP_USED -ge $SWAP_SIZE ]]; then 663 | SWAPPERC=" 100" 664 | else 665 | SWAPPERC=" (?)" 666 | fi 667 | printf "%13s: %s" "$SWAPFN" "$SWAPPERC% ($( prettyKB $SWAP_USED )/$( prettyKB $SWAP_SIZE )) [$SWAP_PRIORITY]" 668 | echo '' 669 | fi 670 | done 671 | fi 672 | } 673 | 674 | statusLoop() { 675 | local DONE=0 676 | trap "DONE=1" INT TERM 677 | # if cursor is at the bottom of the screen, scroll it up 678 | local Y 679 | local T 680 | local C 681 | read Y T <<<$( getCursorYX ) 682 | getVars || return $? 683 | C=$(( SWAPS + 3 )) 684 | if [ $(( Y + C )) -ge $LINES ]; then 685 | # move cursor down C lines then back up C lines 686 | T=$C 687 | while [ $T -ge 0 ]; do 688 | echo '' 689 | (( T-- )) 690 | done 691 | T=$C 692 | while [ $T -ge 0 ]; do 693 | echo -en "$TPUTcuu1" 694 | (( T-- )) 695 | done 696 | fi 697 | # loop while displaying status 698 | while [ -t 1 -a $DONE == 0 -a -e "$TMPFN" -a ! -e "/tmp/dienow" ]; do 699 | getVars || return $? 700 | saveCursor 701 | clearline 702 | echo '' 703 | status 704 | restoreCursor 705 | sleep 3 706 | done 707 | cleardown 708 | } 709 | 710 | isVerbose() { 711 | if [[ $VERBOSE -ge $1 ]]; then 712 | return 0 713 | fi 714 | return 1 715 | } 716 | 717 | doSwapoff() { 718 | echo 16 > /proc/sys/vm/page-cluster && swapoff $* 719 | local RET=$? 720 | echo 3 > /proc/sys/vm/page-cluster 721 | return $RET 722 | } 723 | 724 | doSwapon() { 725 | echo 3 > /proc/sys/vm/page-cluster && swapon $* 726 | local RET=$? 727 | return $RET 728 | } 729 | 730 | doSwapRemoval() { 731 | # remove swaps as needed 732 | if isVerbose 1 ; then 733 | status 734 | echo "doSwapRemoval(): Attempting to add/remove swaps..." 735 | statusLoop & 736 | local CHILDPID=$! 737 | fi 738 | # remove unnecessary swaps (adjust the next line) 739 | doSwapoff $* 740 | RET=$? 741 | if isVerbose 1 ; then 742 | kill -s SIGTERM $CHILDPID 743 | wait -f $CHILDPID 744 | echo "doSwapRemoval(): swapoff $* completed [$RET]" 745 | elif [ $RET != 0 ]; then 746 | echo "doSwapRemoval(): swapoff $* completed [$RET]" 747 | fi 748 | getVars || echo "doSwapRemoval(): getVars() failed: $?" 749 | isVerbose 1 && status 750 | return $RET 751 | } 752 | 753 | isDiskSwap() { 754 | # returns success if the swap device specified is on persistent storage 755 | # $1 device path to test 756 | # return 0 = success; device is persistent storage 757 | # return 1 = failure; not persistent storage 758 | # return 2 = unknown; device status is unknown 759 | local DEV_TYPE 760 | local DEV_MAJOR 761 | local DEV_MINOR 762 | read DEV_TYPE DEV_MAJOR DEV_MINOR <<<"$( stat -c '%d %Hr %Lr' "$1" )" 763 | if [[ $DEV_MAJOR == 0 ]] && [[ $DEV_MINOR == 0 ]] && [ -f "$1" ] ; then 764 | # Swap file 765 | local DEV_MODE 766 | local DEV_USER 767 | local DEV_GROUP 768 | read DEV_MODE DEV_UID DEV_GID <<<"$( stat -c '%a %u %g' "$1" )" 769 | if [[ $DEV_MODE != 600 ]] && [[ $DEV_MODE != 700 ]] ; then 770 | echo "Invalid swap file mode: $1: $DEV_MODE: should be 0600 or 0700" >&2 771 | return 2 772 | elif [[ $DEV_UID != 0 ]] ; then 773 | echo "Invalid swap file owner: $1: $DEV_UID: should be 0/root" >&2 774 | return 2 775 | elif [[ $DEV_GID != 0 ]] ; then 776 | echo "Invalid swap file group: $1: $DEV_GID: should be 0/root" >&2 777 | return 2 778 | fi 779 | # verify that the underlying block device is sane 780 | read DEV_FSTYPE DEV_BLKDEV <<<"$( findmnt -no FSTYPE,SOURCE --target "$1" )" 781 | case "$DEV_FSTYPE" in 782 | ext2|ext3|ext4|xfs|vfat) 783 | true 784 | ;; 785 | proc|sysfs|tmpfs|ramfs\ 786 | |bdev|cgroup|cgroup2|cpuset\ 787 | |binfmt_misc|hugetlbfs\ 788 | |devtmpfs|devpts|autofs\ 789 | |configfs|debugfs|tracefs\ 790 | |securityfs|mqueue|pstore\ 791 | |sockfs|bpf|pipefs) 792 | echo "swap file on unsupported filesystem: $1: $DEV_FSTYPE" >&2 793 | return 1 794 | ;; 795 | fuseblk|fuse|fusectl) 796 | echo "FATAL: swap file on FUSE filesystem could be dangerous: $1: $DEV_FSTYPE on $DEV_BLKDEV" >&2 797 | return 2 798 | ;; 799 | *) 800 | if [ "$DEV_FSTYPE" == "btrfs" ]; then 801 | if [[ $DEV_BLKDEV =~ ^/dev/.*\[/.+\]$ ]]; then 802 | echo "Detected btrfs with subvol: $DEV_BLKDEV" >&2 803 | IFS=" " read DEV_BLKDEV DEV_BLKDEV_SUBVOL <<<"$( sed -r 's~^(/dev/.*)\[/(.+)\]$~\1\t\2~g' <<<"$DEV_BLKDEV" )" 804 | echo "Block device subvol: $DEV_BLKDEV_SUBVOL" >&2 805 | echo "Block device: $DEV_BLKDEV" >&2 806 | fi 807 | fi 808 | echo "WARNING: swap file on $DEV_FSTYPE filesystem is not well tested and could be dangerous: $1: on $DEV_BLKDEV" >&2 809 | echo " Sleeping for 5 seconds... Press Ctrl+C to abort." >&2 810 | sleep 5 811 | ;; 812 | esac 813 | if [ -z "$DEV_BLKDEV" ]; then 814 | echo "FATAL: swap file on invalid block device: $DEV_BLKDEV / $1" >&2 815 | return 2 816 | fi 817 | isVerbose 1 && echo "Testing underlying block device for: $1: $DEV_BLKDEV" >&2 818 | isDiskSwap "$DEV_BLKDEV" 819 | return $? 820 | elif [[ $DEV_TYPE != 5 ]]; then 821 | echo "Unknown device type: $1: $DEV_TYPE" >&2 822 | return 2 823 | fi 824 | local DRIVER 825 | read DRIVER <<<"$( grep -Fx 'Block devices:' -A9999 /proc/devices | sed -r 's/^$//g ; T ; q' | grep -E "^ *$DEV_MAJOR " | cut -b 4- )" 826 | case "$DRIVER" in 827 | zram) 828 | return 1 829 | ;; 830 | sd|blkext) 831 | return 0 832 | ;; 833 | esac 834 | echo "Unknown device type: $1: $DEV_MAJOR:$DEV_MINOR $DRIVER" >&2 835 | return 2 836 | } 837 | 838 | ensureDiskSwap() { 839 | # check for or add disk swap 840 | # 841 | local RET=0 842 | getVars || return $? 843 | isVerbose 1 && { 844 | echo "ensureDiskSwap(): Initial swap status..." 845 | status 846 | } 847 | # determine which swaps need to be removed 848 | local REMOVE_SWAP_IDX_LIST="" 849 | local REMOVE_SWAP_SIZE=0 850 | local RESTORE_SWAPS="" 851 | local REMOVE_SWAPS="" 852 | local POSSIBLE_TARGET_SWAP_IDX_LIST="" 853 | local NEXT_PRIORITY=0 854 | # find volatile swaps (not persistent) 855 | for I in `seq 0 $(( SWAPS - 1 ))` ; do 856 | selectObject SWAP $I 857 | if [ -z "$SWAP_DEVICE" ]; then 858 | continue 859 | fi 860 | if [ "$SWAP_PRIORITY" != "" ]; then 861 | if [ "$SWAP_PRIORITY" -ge "$NEXT_PRIORITY" ]; then 862 | NEXT_PRIORITY=$(( SWAP_PRIORITY + 1 )) 863 | elif [ "$SWAP_PRIORITY" -lt 0 ]; then 864 | true 865 | else 866 | echo "ensureDiskSwap(): Warning: Selected SWAP $I has invalid priority: $SWAP_PRIORITY" 867 | fi 868 | fi 869 | if ! isDiskSwap "$SWAP_DEVICE" ; then 870 | # add this index to REMOVE_SWAP_IDX_LIST 871 | REMOVE_SWAP_IDX_LIST="$REMOVE_SWAP_IDX_LIST $I" 872 | REMOVE_SWAP_SIZE=$(( REMOVE_SWAP_SIZE + SWAP_USED )) 873 | REMOVE_SWAPS="$REMOVE_SWAPS $SWAP_DEVICE" 874 | # NOTE: required swap info for reattach: DEVICE PRIORITY 875 | if [ "$RESTORE_SWAPS" != "" ]; then 876 | RESTORE_SWAPS="$RESTORE_SWAPS " 877 | fi 878 | RESTORE_SWAPS="$RESTORE_SWAPS$SWAP_PRIORITY:$SWAP_DEVICE" 879 | else 880 | POSSIBLE_TARGET_SWAP_IDX_LIST="$POSSIBLE_TARGET_SWAP_IDX_LIST $I" 881 | if [ "$SWAP_ACTIVE" == 1 ]; then 882 | RESTORE_SWAPS="$RESTORE_SWAPS$SWAP_PRIORITY:$SWAP_DEVICE" 883 | fi 884 | fi 885 | done 886 | isVerbose 3 && echo "ensureDiskSwap(): REMOVE_SWAP_SIZE=$REMOVE_SWAP_SIZE" 887 | # calculate memory needed 888 | local MEM_USED=$(( MemTotal - MemAvailable )) 889 | local SIZE_NEEDED=$(( SwapUsed + MEM_USED )) 890 | isVerbose 3 && echo "ensureDiskSwap(): FULL SIZE_NEEDED=$SIZE_NEEDED" 891 | #TODO: find out what conditions are required for compressing hibernation state, and therefore reducing SIZE_NEEDED 892 | SIZE_NEEDED=$(( SIZE_NEEDED / 2 )) 893 | isVerbose 3 && echo "ensureDiskSwap(): HALF SIZE_NEEDED=$SIZE_NEEDED" 894 | 895 | # try to use swap referred to by kernel "resume" argument 896 | if [ "$KERNEL_RESUME_SWAP_IDX" != "" ]; then 897 | selectObject "SWAP" $KERNEL_RESUME_SWAP_IDX 898 | if ! isDiskSwap "$SWAP_DEVICE" ; then 899 | echo "SWAP $KERNEL_RESUME_SWAP_IDX: $SWAP_DEVICE: is not a disk" >&2 900 | elif [ $(( SWAP_SIZE +0 )) -lt $SIZE_NEEDED ]; then 901 | echo "SWAP $KERNEL_RESUME_SWAP_IDX: is invalid size" >&2 902 | elif [ "$SWAP_ACTIVE" == "0" -a $(( SWAP_SIZE - SWAP_USED )) -lt $SIZE_NEEDED ]; then 903 | echo "SWAP $KERNEL_RESUME_SWAP_IDX: $SWAP_DEVICE: is inactive and too small" >&2 904 | else 905 | POSSIBLE_TARGET_SWAP_IDX_LIST="$KERNEL_RESUME_SWAP_IDX $( sed -r "s/(^| )$KERNEL_RESUME_SWAP_IDX( |\$)/ /g ; s/^ +| +\$//g" <<<"$POSSIBLE_TARGET_SWAP_IDX_LIST" )" 906 | fi 907 | fi 908 | POSSIBLE_TARGET_SWAP_IDX_LIST=$( sed -r "s/^ +| +\$//g" <<<"$POSSIBLE_TARGET_SWAP_IDX_LIST" ) 909 | 910 | # find ideal target(s) among detected swaps by iterating over $POSSIBLE_TARGET_SWAP_IDX_LIST 911 | local ADD_SWAP_IDX_LIST="" 912 | local ADD_SWAP_SIZE=0 913 | #TODO: maybe replace the following 'if' with a 'while' to add multiple swaps? 914 | if [ "$POSSIBLE_TARGET_SWAP_IDX_LIST" != "" -a "$ADD_SWAP_SIZE" -lt "$SIZE_NEEDED" ]; then 915 | local SELECTED=-1 916 | local SELECTED_SIZE=-1 917 | for I in $POSSIBLE_TARGET_SWAP_IDX_LIST ; do 918 | selectObject SWAP $I 919 | if [ -z "$SWAP_DEVICE" ]; then 920 | continue 921 | elif [ -z "$SWAP_OPTIONS" ]; then 922 | echo "ensureDiskSwap(): Warning: SWAP $I $SWAP_DEVICE has zero length OPTIONS value" >&2 923 | continue 924 | elif [[ $SWAP_DEVICE =~ ^/dev\(/[^/]*\)*/zram[0-9]+ ]]; then 925 | echo "ensureDiskSwap(): SWAP $I $SWAP_DEVICE is a zram device" 926 | continue 927 | elif [ $(( SWAP_SIZE +0 )) -lt "$SIZE_NEEDED" ]; then 928 | echo "ensureDiskSwap(): SWAP $I $SWAP_DEVICE is too small: $( prettyKB $SWAP_SIZE ) < $( prettyKB $SIZE_NEEDED ))" 929 | continue 930 | elif [ $(( SWAP_SIZE +0 )) -lt "$SELECTED_SIZE" ]; then 931 | echo "ensureDiskSwap(): SWAP $I $SWAP_DEVICE is too small: $( prettyKB $SWAP_SIZE ) < $( prettyKB $SELECTED_SIZE ))" 932 | continue 933 | else 934 | isVerbose 1 && echo "ensureDiskSwap(): selecting SWAP $I $SWAP_DEVICE [$SWAP_PRIORITY] $( prettyKB $SWAP_SIZE )" 935 | SELECTED=$I 936 | SELECTED_SIZE=$SWAP_SIZE 937 | fi 938 | done 939 | if [ "$SELECTED" -ge 0 ]; then 940 | selectObject SWAP $SELECTED 941 | if [ "$SELECTED" != "$KERNEL_RESUME_SWAP_IDX" ]; then 942 | echo "ensureDiskSwap(): FATAL: Selected SWAP is not in kernel resume argument." >&2 943 | return 1 944 | fi 945 | ADD_SWAP_IDX_LIST="$ADD_SWAP_IDX_LIST $SELECTED" 946 | ADD_SWAP_SIZE=$(( ADD_SWAP_SIZE + SWAP_SIZE )) 947 | POSSIBLE_TARGET_SWAP_IDX_LIST=$( sed -r "s/(^| )$SELECTED( |\$)/\\1\\2/g ; s/ +/ /g ; s/^ +| +\$//g" <<<"$POSSIBLE_TARGET_SWAP_IDX_LIST" ) 948 | fi 949 | fi 950 | # report fatal condition and abort 951 | if [ "$ADD_SWAP_IDX_LIST" == "" ]; then 952 | echo "FATAL: no valid swaps found. cannot suspend to disk." >&2 953 | return 1 954 | fi 955 | isVerbose 3 && echo "ADD_SWAP_IDX_LIST=$ADD_SWAP_IDX_LIST" 956 | 957 | # store swaps to restore later 958 | isVerbose 1 && echo "ensureDiskSwap(): Storing current SWAP state..." 959 | isVerbose 3 && echo "RESTORE_SWAPS=\"$RESTORE_SWAPS\"" 960 | echo "RESTORE_SWAPS=\"$RESTORE_SWAPS\"" | updateVars || return $? 961 | echo "$RESTORE_SWAPS" > "$TMPFN.restoreswaps" 962 | 963 | # activate the swaps in ADD_SWAP_IDX_LIST 964 | for I in $ADD_SWAP_IDX_LIST ; do 965 | selectObject SWAP $I 966 | if [ "$SWAP_ACTIVE" == 0 ]; then 967 | if [ "$SWAP_PRIORITY" == "" ]; then 968 | SWAP_PRIORITY=$NEXT_PRIORITY 969 | fi 970 | isVerbose 1 && echo "ensureDiskSwap(): Activating SWAP $I: $SWAP_DEVICE [$SWAP_PRIORITY]" 971 | if [[ $DRYRUN == 0 ]]; then 972 | isVerbose 2 && echo "ensureDiskSwap(): executing: swapon -p '$SWAP_PRIORITY' '$SWAP_DEVICE'" 973 | doSwapon -p "$SWAP_PRIORITY" "$SWAP_DEVICE" || return $? 974 | else 975 | echo "ensureDiskSwap(): DRYRUN: would call: swapon -p '$SWAP_PRIORITY' '$SWAP_DEVICE'" 976 | fi 977 | else 978 | echo "ensureDiskSwap(): Warning: SWAP $I was already active." >&2 979 | fi 980 | done 981 | 982 | if [ -n "$REMOVE_SWAPS" ]; then 983 | isVerbose 1 && echo "ensureDiskSwap(): Preparing to remove SWAP entries..." 984 | if [[ $DRYRUN == 0 ]]; then 985 | isVerbose 2 && echo "ensureDiskSwap(): executing: doSwapRemoval $REMOVE_SWAPS" 986 | doSwapRemoval $REMOVE_SWAPS || return $? 987 | else 988 | echo "ensureDiskSwap(): DRYRUN: would call: doSwapRemoval $REMOVE_SWAPS" 989 | fi 990 | fi 991 | 992 | getVars || return $? 993 | isVerbose 1 && { 994 | echo "ensureDiskSwap(): final status:" 995 | status 996 | } 997 | return 0 998 | } 999 | 1000 | doHibernate() { 1001 | # call systemctl hibernate and sleep a few seconds 1002 | # 1003 | # validate systemctl binary 1004 | local SYSTEMCTL=$( which systemctl 2> /dev/null ) 1005 | local CMD=$1 1006 | if [ -z "$SYSTEMCTL" ]; then 1007 | echo "ERROR: unable to find systemctl binary" >&2 1008 | exit 1 1009 | elif [ ! -x "$SYSTEMCTL" ]; then 1010 | echo "ERROR: unable to execute systemctl binary: $SYSTEMCTL" >&2 1011 | exit 1 1012 | fi 1013 | # validate systemctl subcommand 1014 | local CNT 1015 | local RET 1016 | CNT=$( $SYSTEMCTL --help | grep -E "^ $CMD " | wc -l ) 1017 | RET=$? 1018 | if [ $RET != 0 ]; then 1019 | echo "ERROR: could not detect systemctl support for subcommand '$CMD'" >&2 1020 | exit 1 1021 | elif [ $CNT -gt 1 ]; then 1022 | echo "ERROR: could not detect systemctl support for subcommand '$CMD'; unknown response returned" >&2 1023 | exit 1 1024 | elif [ $CNT != 1 ]; then 1025 | echo "ERROR: systemctl does not support subcommand '$CMD'" >&2 1026 | exit 1 1027 | fi 1028 | # try to run systemctl subcommand (or dryrun) 1029 | local SUBCMD="$CMD" 1030 | CMD="$SYSTEMCTL $SUBCMD" 1031 | if [[ $TEST != 0 ]]; then 1032 | echo "TEST: would execute: $CMD" 1033 | RET=0 1034 | elif [[ $DRYRUN != 0 ]]; then 1035 | echo "DRYRUN: would execute: $CMD" 1036 | RET=0 1037 | else 1038 | isVerbose 2 && echo "doHibernate(): executing: $CMD" 1039 | local PRESECS=$SECONDS 1040 | $CMD 1041 | RET=$? 1042 | if [ $RET != 0 ] || isVerbose 2 ; then 1043 | echo "doHibernate(): back from: $CMD (exit code=$RET)" 1044 | fi 1045 | if [ $RET == 0 ]; then 1046 | echo "doHibernate(): sleeping until hibernation activates..." 1047 | local SHOWTIMEOUT=$( isVerbose 2 && echo -n 1 ) 1048 | while [ $(( SECONDS - PRESECS )) -lt 60 ] \ 1049 | && ! $SYSTEMCTL is-active "systemd-$SUBCMD" | grep -qvE '^inactive$' ; do 1050 | sleep 0.5 1051 | if [ "$SHOWTIMEOUT" == "1" ]; then 1052 | echo -en "timeout in $(( 60 + PRESECS - SECONDS )) sec... \r" 1053 | elif [ $(( SECONDS - PRESECS )) -gt 20 ]; then 1054 | SHOWTIMEOUT=1 1055 | fi 1056 | done 1057 | clearline 1058 | # sleep until system hibernation returns 1059 | sleep 2 1060 | while $SYSTEMCTL is-active -q "systemd-$SUBCMD" ; do 1061 | sleep 2 1062 | echo -n "!" 1063 | done 1064 | clearline 1065 | echo "doHibernate(): awake after hibernation" 1066 | fi 1067 | fi 1068 | return $RET 1069 | } 1070 | 1071 | restoreSwapState() { 1072 | # restore initial swap state 1073 | local NEXT_PRIORITY=0 1074 | getVars || return $? 1075 | isVerbose 1 && { 1076 | echo "restoreSwapState(): Initial swap status..." 1077 | status 1078 | } 1079 | # check if we need to load RESTORE_SWAPS from file 1080 | if [ "$RESTORE_SWAPS" == "" ]; then 1081 | # check that restoreswaps file exists 1082 | if [ ! -f "$TMPFN.restoreswaps" ]; then 1083 | echo "ERROR: Unable to load SWAP state description: file not found" >&2 1084 | return 1 1085 | fi 1086 | isVerbose 1 && echo "restoreSwapState(): Loading original SWAP state description..." 1087 | read RESTORE_SWAPS < "$TMPFN.restoreswaps" 1088 | else 1089 | isVerbose 1 && echo "restoreSwapState(): Reusing existing SWAP state description..." 1090 | fi 1091 | isVerbose 3 && echo "RESTORE_SWAPS=$RESTORE_SWAPS" 1092 | 1093 | # process RESTORE_SWAPS to determine how to restore swaps 1094 | isVerbose 1 && echo "restoreSwapState(): Processing SWAP state description..." 1095 | local ADD_SWAP_IDX_LIST="" 1096 | local REMOVE_SWAP_IDX_LIST="" 1097 | local ADD_SWAPS="" 1098 | local REMOVE_SWAPS="" 1099 | local UNKNOWN_PAIRS="$RESTORE_SWAPS" 1100 | for I in `seq 0 $(( SWAPS - 1 ))` ; do 1101 | selectObject SWAP $I 1102 | if [ -z "$SWAP_DEVICE" ]; then 1103 | continue 1104 | fi 1105 | local IN_RESTORE_SWAPS=0 1106 | local PAIR 1107 | for PAIR in $UNKNOWN_PAIRS ; do 1108 | IFS=":" read PAIR_PRIORITY PAIR_DEVICE <<<"$PAIR" 1109 | isVerbose 3 && echo "restoreSwapState(): parsed PAIR: $PAIR_DEVICE [$PAIR_PRIORITY]" 1110 | if [ "$SWAP_DEVICE" == "$PAIR_DEVICE" ]; then 1111 | isVerbose 3 && echo "restoreSwapState(): SWAP $I matches: $SWAP_DEVICE [$SWAP_PRIORITY]" 1112 | if [ -z "$SWAP_PRIORITY" ]; then 1113 | local NAME="SWAP${I}_PRIORITY" 1114 | echo "$NAME=$PAIR_PRIORITY" | updateVars || return $? 1115 | printf -v "$NAME" '%s' "$PAIR_PRIORITY" || return $? 1116 | SWAP_PRIORITY="$PAIR_PRIORITY" 1117 | elif [ "$SWAP_PRIORITY" != "$PAIR_PRIORITY" ]; then 1118 | echo "restoreSwapState(): ERROR: Priority mismatch: $SWAP_DEVICE ($SWAP_PRIORITY vs $PAIR_PRIORITY)" >&2 1119 | return 1 1120 | fi 1121 | IN_RESTORE_SWAPS=1 1122 | break 1123 | fi 1124 | done 1125 | if [ "$IN_RESTORE_SWAPS" == 1 ]; then 1126 | # remove entry from UNKNOWN_PAIRS 1127 | UNKNOWN_PAIRS=$( sed -r "s~(^|\\t)$PAIR(\\t|\$)~\\1\\2~g ; s~\\t\\t+~\\t~g ; s~^\\t+|\\t+\$~~g" <<<"$UNKNOWN_PAIRS" ) 1128 | if [ "$SWAP_ACTIVE" == 0 ]; then 1129 | # add to ADD_SWAPS 1130 | ADD_SWAP_IDX_LIST="$ADD_SWAP_IDX_LIST $I" 1131 | ADD_SWAPS="$ADD_SWAPS $SWAP_DEVICE" 1132 | fi 1133 | else 1134 | if [ "$SWAP_ACTIVE" == 1 ]; then 1135 | # add to REMOVE_SWAPS 1136 | REMOVE_SWAP_IDX_LIST="$REMOVE_SWAP_IDX_LIST $I" 1137 | REMOVE_SWAPS="$REMOVE_SWAPS $SWAP_DEVICE" 1138 | fi 1139 | fi 1140 | done 1141 | # cleanup spacing 1142 | ADD_SWAPS=$( sed -r "s/ +/ /g ; s/^ +| +\$//g" <<<"$ADD_SWAPS" ) 1143 | REMOVE_SWAPS=$( sed -r "s/ +/ /g ; s/^ +| +\$//g" <<<"$REMOVE_SWAPS" ) 1144 | isVerbose 3 && { 1145 | echo "ADD_SWAPS=$ADD_SWAPS" 1146 | echo "REMOVE_SWAPS=$REMOVE_SWAPS" 1147 | } 1148 | 1149 | isVerbose 1 && echo "restoreSwapState(): Activating original SWAP devices..." 1150 | for I in $ADD_SWAP_IDX_LIST ; do 1151 | selectObject SWAP $I 1152 | if [ "$SWAP_PRIORITY" == "" ]; then 1153 | SWAP_PRIORITY=$NEXT_PRIORITY 1154 | #TODO: finish this code... 1155 | echo "TODO: finish this code... LINE=$LINENO" 1156 | return 1 1157 | fi 1158 | isVerbose 1 && echo "restoreSwapState(): Activating SWAP $I: $SWAP_DEVICE [$SWAP_PRIORITY]" 1159 | # report zram changing sizes 1160 | if [[ $SWAP_DEVICE =~ ^/dev/zram[0-9]+$ ]]; then 1161 | local CURRENT_SIZE=$( lsblk -bno SIZE "$SWAP_DEVICE" ) 1162 | if [ -z "$CURRENT_SIZE" ]; then 1163 | CURRENT_SIZE=0 1164 | fi 1165 | CURRENT_SIZE=$(( ( CURRENT_SIZE +0 ) / 1024 )) 1166 | if [ "$CURRENT_SIZE" != "$SWAP_SIZE" ]; then 1167 | echo "restoreSwapState(): $SWAP_DEVICE size is ${CURRENT_SIZE}K, expecting ${SWAP_SIZE}K" >&2 1168 | return 1 1169 | elif [ $CURRENT_SIZE -lt 1024 ]; then 1170 | echo "restoreSwapState(): $SWAP_DEVICE size is ${CURRENT_SIZE}K, expecting >= 1024K" >&2 1171 | return 1 1172 | fi 1173 | fi 1174 | # reactivate swap 1175 | if [[ $DRYRUN == 0 ]]; then 1176 | isVerbose 2 && echo "restoreSwapState(): executing: swapon -p '$SWAP_PRIORITY' '$SWAP_DEVICE'" 1177 | doSwapon -p "$SWAP_PRIORITY" "$SWAP_DEVICE" || return $? 1178 | else 1179 | echo "restoreSwapState(): DRYRUN: would call: swapon -p '$SWAP_PRIORITY' '$SWAP_DEVICE'" 1180 | fi 1181 | done 1182 | 1183 | if [ "$REMOVE_SWAPS" != "" ]; then 1184 | isVerbose 1 && echo "restoreSwapState(): Removing hibernation SWAP entries..." 1185 | if [[ $DRYRUN == 0 ]]; then 1186 | isVerbose 2 && echo "restoreSwapState(): executing: doSwapRemoval $REMOVE_SWAPS" 1187 | doSwapRemoval $REMOVE_SWAPS || return $? 1188 | else 1189 | echo "restoreSwapState(): DRYRUN: would call: doSwapRemoval $REMOVE_SWAPS" 1190 | fi 1191 | fi 1192 | 1193 | getVars || return $? 1194 | isVerbose 1 && { 1195 | echo "restoreSwapState(): final status:" 1196 | status 1197 | } 1198 | return 0 1199 | } 1200 | 1201 | pre_main() { 1202 | getLock 1203 | ensureDiskSwap || { RET=$? ; doUnlock ; exit $RET ; } 1204 | doUnlock 1205 | } 1206 | 1207 | post_main() { 1208 | getLock 1209 | restoreSwapState || { RET=$? ; doUnlock ; exit $RET ; } 1210 | doUnlock 1211 | } 1212 | 1213 | debug_main() { 1214 | local RET 1215 | getLock 1216 | debugVars 1217 | doUnlock 1218 | } 1219 | 1220 | main() { 1221 | local RET 1222 | getLock 1223 | # check for or add disk swap 1224 | ensureDiskSwap || { RET=$? ; doUnlock ; exit $RET ; } 1225 | # hibernate and sleep 5s or so 1226 | local CMD=$( basename $0 ) 1227 | if [ "$CMD" == "zram-hibernate" ]; then 1228 | CMD="hibernate" 1229 | fi 1230 | doHibernate $CMD 1231 | # restore initial swap state 1232 | restoreSwapState || { RET=$? ; doUnlock ; exit $RET ; } 1233 | doUnlock 1234 | } 1235 | 1236 | showHelp() { 1237 | cat <<<" 1238 | usage: $( basename "$0" ) {-h|--help} 1239 | $( basename "$0" ) [options] 1240 | - a symlink to this binary placed in /usr/lib/systemd/system-sleep/ 1241 | will execute pre & post system sleep. 1242 | - naming this script 'hibernate' or 'suspend', or executing via a 1243 | symlink with these names will wrap around 'sudo' and 1244 | 'systemctl {hibernate|suspend}' 1245 | options: 1246 | -h --help show this help text 1247 | -v --verbose increase output verbosity 1248 | -n --dry-run show what would be done without making changes 1249 | -t --test do everything except actually affect the system power state 1250 | -d --debug debug data parsing logic only 1251 | " 1252 | exit $HELP 1253 | } 1254 | 1255 | if [ $UID != 0 ]; then 1256 | sudo $0 $* 1257 | exit $? 1258 | fi 1259 | 1260 | HELP=0 1261 | DEBUG=0 1262 | DRYRUN=0 1263 | TEST=0 1264 | PRE=0 1265 | POST=0 1266 | VERBOSE=0 1267 | ACTION="" 1268 | while [[ $# -gt 0 ]]; do 1269 | ARG="$1" 1270 | NEXTARG="" 1271 | shift 1272 | while [ -n "$ARG" ]; do 1273 | case "$ARG" in 1274 | -h|--help) HELP=1 ;; 1275 | -n|--dryrun|--dry-run) DRYRUN=1 ;; 1276 | -t|--test) TEST=1 ;; 1277 | -d|--debug) DEBUG=1 ;; 1278 | -v|--verbose) VERBOSE=$(( VERBOSE + 1 )) ;; 1279 | --cursestest) 1280 | cursestest 1281 | exit $? 1282 | ;; 1283 | pre) PRE=1 ;; 1284 | post) POST=1 ;; 1285 | suspend|hibernate|hybrid-sleep) 1286 | ACTION=$ARG 1287 | ;; 1288 | suspend-then-hibernate) 1289 | ACTION=$ARG 1290 | if [ -n "$SYSTEMD_SLEEP_ACTION" ]; then 1291 | ACTION=$SYSTEMD_SLEEP_ACTION 1292 | fi 1293 | ;; 1294 | # unknown commands 1295 | -*) 1296 | if [[ $ARG =~ ^-[A-Za-z][A-Za-z] ]]; then 1297 | if [ -z "$NEXTARG" ]; then 1298 | NEXTARG="-${ARG:2}" 1299 | ARG="${ARG:0:2}" 1300 | else 1301 | NEXTARG="-${ARG:2} $NEXTARG" 1302 | ARG="${ARG:0:2}" 1303 | fi 1304 | continue 1305 | else 1306 | HELP=1 1307 | echo "error: invalid argument: $ARG" >&2 1308 | fi 1309 | ;; 1310 | *) 1311 | HELP=1 1312 | echo "error: invalid argument: $ARG" >&2 1313 | ;; 1314 | esac 1315 | ARG="$NEXTARG" 1316 | NEXTARG="" 1317 | done 1318 | done 1319 | 1320 | if [ "$PRE$POST$VERBOSE" == "000" ]; then 1321 | VERBOSE=2 1322 | fi 1323 | if [[ $HELP != 0 ]]; then 1324 | showHelp 1325 | elif [[ $DEBUG != 0 ]]; then 1326 | debug_main 1327 | elif [ "$ACTION" == "suspend" ]; then 1328 | echo "Nothing to do: No swap needed for pure suspend" 1329 | elif [[ $PRE != 0 ]]; then 1330 | if [ "$ACTION" == "suspend-after-failed-hibernate" ]; then 1331 | echo "Nothing to do: No swap needed for suspend-after-failed-hibernate" 1332 | exit 0 1333 | fi 1334 | pre_main 1335 | elif [[ $POST != 0 ]]; then 1336 | post_main 1337 | else 1338 | main 1339 | fi 1340 | --------------------------------------------------------------------------------