HEX
Server: LiteSpeed
System: Linux ip-172-31-76-142.ec2.internal 4.14.158-129.185.amzn2.x86_64 #1 SMP Tue Dec 24 03:15:32 UTC 2019 x86_64
User: 69b4844ae61d4e92bf26ad98af552775 (1065)
PHP: 7.2.27
Disabled: exec,passthru,shell_exec,system,eval
Upload Files
File: //bin/hibinit-agent
#!/usr/bin/python2
# 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
import signal
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

#space reserved for swap headers
SWAP_RESERVED_SIZE = 16384
log_to_syslog = True
SWAP_FILE = '/swap'
URL = "http://169.254.169.254/latest/meta-data/hibernation/configured"

def log(message):
    if log_to_syslog:
        syslog.syslog(message)

def sigterm_handler(signal, frame):
    #save the state here or do whatever you want
    log('Process killed cleaning up!')
    cmd = "swapon -s | cut -f1,1 | grep -w {name}"
    cmd = cmd.format(name=SWAP_FILE)
    swapoff = "swapoff {filename}"
    cmd = check_output(cmd, shell=True)
    if cmd.strip() == SWAP_FILE:
        print "Turning off swap for cleanup"
        swapoff = swapoff.format(filename=SWAP_FILE)
        check_call(swapoff, shell=True)
    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))


#This is only for grub2
def findGrubMount():
    confFile = ''
    pathList = []
    pathList.append("/etc/grub2-efi.cfg")
    pathList.append("/boot/grub2/grub.cfg")
    pathList.append("/boot/grub2-efi/grub.cfg")
    pathList.append("/etc/grub2.cfg")
    pathList.append("NULL")
    cmd = 'stat -L -c %m'
    for ln in pathList:
	if ln!='NULL' and os.path.isfile(ln) and os.access(ln, os.R_OK):
	    break
    if ln=='NULL':
	return None
    cmd = cmd + ' ' + ln
    mount = check_output(cmd, shell=True)
    return mount.strip()


def patch_grub_config(swap_device, offset):
    log("Updating GRUB to use the device %s with offset %d for resume" % (swap_device, offset))
    cmd = "grubby --update-kernel=ALL --args='no_console_suspend={console} resume_offset={offset} resume={swap_device}'"
    cmd = cmd.format(console='1', offset=offset, swap_device=swap_device)
    mount_point = findGrubMount()
    if mount_point is None:
	log("Grub configuration is not updated. Grub cannot found under /boot or /etc. Please run manual grub update with resume parameters")
	return
    fsfreeze = "sync && mountpoint -q {mount} && trap '' HUP INT QUIT TERM && fsfreeze -f {mount} && fsfreeze -u {mount}"
    fsfreeze = fsfreeze.format(mount=mount_point)
    check_call(cmd, shell=True)
    check_call(fsfreeze, shell=True)
    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)
    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, 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(0022)

    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.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() == '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("-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_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)
    signal.signal(signal.SIGHUP, sigterm_handler)
    signal.signal(signal.SIGINT, sigterm_handler)
    signal.signal(signal.SIGQUIT, sigterm_handler)
    if bi:
        bi.start_init()

    log("kicking child process to initiate the setup")
    bi.do_async_init()
 
if __name__ == '__main__':
    main()