Server IP : 3.128.248.115 / Your IP : 18.224.73.157 Web Server : Apache/2.4.41 (Ubuntu) System : Linux ip-172-31-33-233 5.15.0-1037-aws #41~20.04.1-Ubuntu SMP Mon May 22 18:18:00 UTC 2023 x86_64 User : www-data ( 33) PHP Version : 7.4.28 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /bin/ |
Upload File : |
#!/usr/bin/python3 # AWS EC2 HibInit Agent. This agent does several things: # 1. Upon startup it checks for sufficient swap space to allow hibernate and fails # if it's present but there's not enough of it. # 2. If there's no swap space, it creates it and launches a background thread to # touch all of its blocks to make sure that EBS volumes are pre-warmed. # 3. It updates the offset of the swap file in the kernel using SNAPSHOT_SET_SWAP_AREA ioctl. # # This file is compatible both with Python 2 and Python 3 import argparse import array import atexit import ctypes as ctypes import fcntl import mmap import os, signal import struct import sys import syslog import math from subprocess import check_call, check_output, STDOUT from threading import Thread from math import ceil from time import sleep try: from urllib.request import urlopen, Request except ImportError: from urllib2 import urlopen, Request, HTTPError try: from ConfigParser import ConfigParser, NoSectionError, NoOptionError except: from configparser import ConfigParser, NoSectionError, NoOptionError GRUB2_DIR = '/etc/default/grub.d' #space reserved for swap headers SWAP_RESERVED_SIZE = 16384 log_to_syslog = True log_to_stderr = True SWAP_FILE = '/swap-hibinit' URL = "http://169.254.169.254/latest/meta-data/hibernation/configured" def log(message): if log_to_syslog: syslog.syslog(message) if log_to_stderr: sys.stderr.write("%s\n" % message) def sigterm_handler(signal, frame): #save the state here or do whatever you want log('Process killed cleaning up!') if os.path.isfile(SWAP_FILE) and os.access(SWAP_FILE, os.R_OK): os.remove(SWAP_FILE) exit(0) def fallocate(fl, size): try: _libc = ctypes.CDLL('libc.so.6') _fallocate = _libc.fallocate _fallocate.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong] # (FD, mode, offset, len) res = _fallocate(fl.fileno(), 0, 0, size) if res != 0: raise Exception("Failed to perform fallocate(). Result: %d" % res) except Exception as e: log("Failed to call fallocate(), will use resize. Err: %s" % str(e)) fl.seek(size-1) fl.write(chr(0)) def get_file_block_number(filename): with open(filename, 'r') as handle: buf = array.array('L', [0]) # from linux/fs.h FIBMAP = 0x01 result = fcntl.ioctl(handle.fileno(), FIBMAP, buf) if result < 0: raise Exception("Failed to get the file offset. Error=%d" % result) return buf[0] def get_rootfs_size(): stat=os.statvfs('/') return math.ceil(float(stat.f_bsize * stat.f_blocks)/(1024*1024*1024)) def get_partuuid(device): return check_output( ['lsblk', '-dno', 'PARTUUID', device]).decode('ascii').strip() def patch_grub_config(swap_device, offset, grub2_dir): """Update GRUB2 config when needed""" if grub2_dir and os.path.exists(grub2_dir): offset_file = os.path.join(grub2_dir, '99-set-swap.cfg') if swap_device.startswith("/dev"): swap_device = "PARTUUID=%s" % get_partuuid(swap_device) grub_snippet = ( 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT' ' no_console_suspend=1 resume_offset=%d resume=%s"\n' % (offset, swap_device)) if ((not os.path.exists(offset_file) or open(offset_file).read() not in (grub_snippet))): log("Updating GRUB to use the device %s with offset %d for resume" % (swap_device, offset)) with open(offset_file, 'w') as fl: fl.write(grub_snippet) check_call('/usr/sbin/update-grub2') log("GRUB configuration is updated") def update_kernel_swap_offset(swapon, swapoff, filename, grub_update): swapon = swapon.format(swapfile=filename) log("Running: %s" % swapon) check_call(swapon, shell=True) log("Updating the kernel offset for the swapfile: %s" % filename) statbuf = os.stat(filename) dev = statbuf.st_dev offset = get_file_block_number(filename) if grub_update: dev_str = find_device_for_file(filename) patch_grub_config(dev_str, offset, GRUB2_DIR) else: log("Skipping GRUB configuration update") log("Setting swap device to %d with offset %d" % (dev, offset)) # Set the kernel swap offset, see https://www.kernel.org/doc/Documentation/power/userland-swsusp.txt # From linux/suspend_ioctls.h SNAPSHOT_SET_SWAP_AREA = 0x400C330D buf = struct.pack('LI', offset, dev) with open('/dev/snapshot', 'r') as snap: fcntl.ioctl(snap, SNAPSHOT_SET_SWAP_AREA, buf) log("Done updating the swap offset. Turning swapoff") swapoff = swapoff.format(swapfile=filename) log("Running: %s" % swapoff) check_call(swapoff, shell=True) def find_device_for_file(filename): # Find the mount point for the swap file ('df -P /swap') df_out = check_output(['df', '-P', filename]).decode('ascii') dev_str = df_out.split("\n")[1].split()[0] return dev_str class SwapInitializer(object): def __init__(self, filename, swap_size, touch_swap, mkswap, swapoff, swapon): self.filename = filename self.swap_size = swap_size self.mkswap = mkswap self.swapoff = swapoff self.swapon = swapon self.touch_swap = touch_swap def do_allocate(self): log("Allocating %d bytes in %s" % (self.swap_size, self.filename)) with open(self.filename, 'w+') as fl: fallocate(fl, self.swap_size) os.chmod(self.filename, 0o600) def init_swap(self): """ Initialize the swap using direct IO to avoid polluting the page cache """ try: cur_swap_size = os.stat(self.filename).st_size if cur_swap_size >= self.swap_size: log("Swap file size (%d bytes) is already large enough" % cur_swap_size) if self.init_mkswap(): return except OSError: try: os.unlink(self.filename) except: pass self.do_allocate() if not self.touch_swap: log("Swap pre-heating is skipped, the swap blocks won't be touched during " "to ensure they are ready") self.init_mkswap() return written = 0 log("Opening %s for direct IO" % self.filename) fd = os.open(self.filename, os.O_RDWR | os.O_DIRECT | os.O_SYNC | os.O_DSYNC) if fd < 0: raise Exception("Failed to initialize the swap. Err: %s" % os.strerror(os.errno)) filler_block = None try: # Create a filler block that is correctly aligned for direct IO filler_block = mmap.mmap(-1, 1024 * 1024) # We're using 'b' to avoid optimizations that might happen for zero-filled pages filler_block.write(b'b' * 1024 * 1024) log("Touching all blocks in %s" % self.filename) while written < self.swap_size: res = os.write(fd, filler_block) if res <= 0: raise Exception("Failed to touch a block. Err: %s" % os.strerror(os.errno)) written += res finally: os.close(fd) if filler_block: filler_block.close() log("Swap file %s is ready" % self.filename) self.init_mkswap() def init_mkswap(self): # Do mkswap try: mkswap = self.mkswap.format(swapfile=self.filename) log("Running: %s" % mkswap) check_call(mkswap, shell=True) return True except Exception as e: log("Failed to initialize swap, reason: %s" % str(e)) return False class BackgroundInitializerRunner(object): def __init__(self, swapper, update_grub): self.swapper = swapper self.thread = None self.error = None self.update_grub = update_grub def start_init(self): try: pid = os.fork() if pid > 0: # Exit parent process sys.exit(0) except OSError as e: print >> sys.stderr, "fork failed: %d (%s)" % (e.errno, e.strerror) sys.exit(1) # Configure the child processes environment os.chdir("/") os.setsid() os.umask(0o022) def do_async_init(self): try: self.swapper.init_swap() update_kernel_swap_offset(self.swapper.swapon, self.swapper.swapoff, self.swapper.filename, self.update_grub) except Exception as ex: log("Failed to initialize swap, reason: %s" % str(ex)) self.error = ex def swap_needs_touch(swapfile): # Walk the parent directories of the swapfile to find on which # filesystem it's mounted swap_place = swapfile dev = None while not dev: swap_place, _ = os.path.split(swap_place) try: dev = find_device_for_file(swap_place) except: pass if swap_place == '/': raise Exception("Failed to find the filesystem type of /") with open("/proc/mounts") as fl: lines = fl.read().split("\n") for ln in lines: if dev in ln and "xfs" in ln: return True return False class Config(object): def __init__(self, config, args): def get(section, name): try: return config.get(section, name) except NoSectionError: return None except NoOptionError: return None def get_int(section, name): v = get(section, name) if v is None: return None return int(v) self.log_to_syslog = self.merge( self.to_bool(get('core', 'log-to-syslog')), self.to_bool(args.log_to_syslog), True) self.log_to_stderr = self.merge( self.to_bool(get('core', 'log-to-stderr')), self.to_bool(args.log_to_stderr), True) self.mkswap = self.merge(get('swap', 'mkswap'), args.mkswap, 'mkswap {swapfile}') self.swapon = self.merge(get('swap', 'swapon'), args.swapon, 'swapon {swapfile}') self.swapoff = self.merge(get('swap', 'swapoff'), args.swapoff, 'swapoff {swapfile}') self.touch_swap = self.merge( self.to_bool(get('core', 'touch-swap')), self.to_bool(args.touch_swap), swap_needs_touch(SWAP_FILE)) self.grub_update = self.merge( self.to_bool(get('core', 'grub-update')), self.to_bool(args.grub_update), True) self.swap_percentage = self.merge( get_int('swap', 'percentage-of-ram'), args.swap_ram_percentage, 100) self.swap_mb = self.merge( get_int('swap', 'target-size-mb'), args.swap_target_size_mb, 4000) def merge(self, cf_value, arg_value, def_val): if arg_value is not None: return arg_value if cf_value is not None: return cf_value return def_val def to_bool(self, bool_str): """Parse the string and return the boolean value encoded or raise an exception""" if bool_str is None: return None if bool_str.lower() in ['true', 't', '1']: return True elif bool_str.lower() in ['false', 'f', '0']: return False # if here we couldn't parse it raise ValueError("%s is not recognized as a boolean value" % bool_str) def __str__(self): return str(self.__dict__) def hibernationEnabled(): """Returns a boolean indicating whether hibernation is enabled or not.""" response = None try: response = urlopen(URL) data = response.read() if data.lower() in ('false', b'false'): return False except: return False finally: if response: response.close() return True def main(): if not hibernationEnabled(): log("Instance Launch has not enabled Hibernation Configured Flag. hibinit-agent exiting!!") exit(0) # Validate if disk space>total RAM ram_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') if get_rootfs_size()<=(math.ceil(float(ram_bytes)/(1024*1024*1024))): log("Insufficient disk space. Cannot create setup for hibernation. Please allocate a larger root device") exit(1) # Parse arguments parser = argparse.ArgumentParser(description="An EC2 background process that creates a setup for instance hibernation " "at instance launch and also registers ACPI sleep event/actions") parser.add_argument('-c', '--config', help='Configuration file to use', type=str) parser.add_argument("-syslog", "--log-to-syslog", help='Log to syslog', type=str) parser.add_argument("-stderr", "--log-to-stderr", help='Log to stderr', type=str) parser.add_argument("-touch", "--touch-swap", help='Do swap initialization', type=str) parser.add_argument("-grub", "--grub-update", help='Update GRUB config with resume offset', type=str) parser.add_argument("-p", "--swap-ram-percentage", help='The target swap size as a percentage of RAM', type=int) parser.add_argument("-s", "--swap-target-size-mb", help='The target swap size in megabytes', type=int) parser.add_argument('--mkswap', help='The command line utility to set up swap', type=str) parser.add_argument('--swapon', help='The command line utility to turn on swap', type=str) parser.add_argument('--swapoff', help='The command line utility to turn off swap', type=str) args = parser.parse_args() config_file = ConfigParser() if args.config: config_file.read(args.config) config = Config(config_file, args) global log_to_syslog, log_to_stderr log_to_stderr = config.log_to_stderr log_to_syslog = config.log_to_syslog log("Effective config: %s" % config) target_swap_size = config.swap_mb * 1024 * 1024 swap_percentage_size = ram_bytes * config.swap_percentage // 100 if swap_percentage_size > target_swap_size: target_swap_size = int(swap_percentage_size) log("Will check if swap is at least: %d megabytes" % (target_swap_size // (1024*1024))) #Validate if swap file exists cur_swap = 0 if os.path.isfile(SWAP_FILE) and os.access(SWAP_FILE, os.R_OK): cur_swap = os.path.getsize(SWAP_FILE) bi = None if cur_swap >= target_swap_size - SWAP_RESERVED_SIZE: log("There's sufficient swap available (have %d, need %d)" % (cur_swap, target_swap_size)) update_kernel_swap_offset(config.swapon, config.swapoff, SWAP_FILE, config.grub_update) exit() #validate if instance was launched from pre-created image and swap size>=total RAM, if not re-create the swap elif cur_swap > 0 and (cur_swap < target_swap_size - SWAP_RESERVED_SIZE): log("Swap already exists! (have %d, need %d), deleting existing swap file %s" % (cur_swap, target_swap_size, SWAP_FILE)) os.remove(SWAP_FILE) log("Create swap and initialize it") # We need to create swap, but first validate that we have enough free space swap_dev = os.path.dirname(SWAP_FILE) st = os.statvfs(swap_dev) free_bytes = st.f_bavail * st.f_frsize #rounding off to swap_size+10mb for swap headers free_space_needed = target_swap_size + 10 * 1024 * 1024 if free_space_needed >= free_bytes: log("There's not enough space (%d present, %d needed) on the device: %s" % ( free_bytes, free_space_needed, swap_dev)) exit(1) log("There's enough space (%d present, %d needed) on the device: %s" % ( free_bytes, free_space_needed, swap_dev)) sw = SwapInitializer(SWAP_FILE, target_swap_size, config.touch_swap, config.mkswap, config.swapoff, config.swapon) bi = BackgroundInitializerRunner(sw, config.grub_update) signal.signal(signal.SIGTERM, sigterm_handler) if bi: bi.start_init() log("kicking child process to initiate the setup") bi.do_async_init() if __name__ == '__main__': main()