Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling hibernation-setup-tool, hibernate and resume services #7

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d799ef4
Enabled service
ishaansehgal99 Jul 6, 2022
4551a79
Ensure directories created and files linked for service to run
ishaansehgal99 Jul 6, 2022
630be4f
Added optional arugments and permissions for tool and service file
ishaansehgal99 Jul 6, 2022
b70ad46
Delete
ishaansehgal99 Jul 6, 2022
8042081
Corrected link error. Named file
ishaansehgal99 Jul 7, 2022
361cc48
Ensured file was created not directory
ishaansehgal99 Jul 12, 2022
7938c45
Detect hibernate/resume using hooks. Ensure services files enabled an…
ishaansehgal99 Jul 25, 2022
9b15002
Cleaned up comments. Simplified check for is cold booted.
ishaansehgal99 Jul 26, 2022
7fd4005
Clean
ishaansehgal99 Jul 26, 2022
5f41ec3
Merge branch 'main' of https://github.com/microsoft/hibernation-setup…
ishaansehgal99 Jul 26, 2022
c1c6676
remove comment
ishaansehgal99 Jul 26, 2022
b2f191a
Removed additional conditional
ishaansehgal99 Jul 26, 2022
9f31117
Added comment
ishaansehgal99 Jul 27, 2022
4816cc8
Added tool and hook prefixes for better logging
ishaansehgal99 Jul 27, 2022
88dc86b
Minor logging addition
ishaansehgal99 Jul 28, 2022
583269d
Added log_notice log ability
ishaansehgal99 Aug 2, 2022
c365ce9
Merge branch 'main' of https://github.com/microsoft/hibernation-setup…
ishaansehgal99 Aug 2, 2022
77a73fd
Explicit check nftw succeeds
ishaansehgal99 Aug 2, 2022
3100c41
Formatting
ishaansehgal99 Aug 2, 2022
f58f229
Added failed hibernation state
ishaansehgal99 Aug 6, 2022
3e247ea
Merge branch 'main' of https://github.com/microsoft/hibernation-setup…
ishaansehgal99 Aug 8, 2022
ab98b8e
Resolve merge condflicts
ishaansehgal99 Aug 8, 2022
c33297f
Renamed enable_systemd_service function
ishaansehgal99 Aug 8, 2022
3f29064
Merge branch 'main' of https://github.com/microsoft/hibernation-setup…
ishaansehgal99 Aug 8, 2022
9c9a703
Fixed minor formatting
ishaansehgal99 Aug 8, 2022
9fe83d8
Added check to prevent re-enabling services if they have been enabled…
ishaansehgal99 Aug 10, 2022
d8df3c6
Disabling pre/post hibernate hooks if either fails to enable
ishaansehgal99 Aug 15, 2022
42100c1
Ensure prehook is always enabled
ishaansehgal99 Aug 18, 2022
6f2c0b1
Code for enabling/disabling post hooks in pre hooks
ishaansehgal99 Aug 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions hibernate.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=User hibernate actions
Before=hibernate.target

[Service]
Type=simple
ExecStart=/usr/sbin/hibernation-setup-tool -w pre -a hibernate

[Install]
WantedBy=hibernate.target
173 changes: 160 additions & 13 deletions hibernation-setup-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ enum host_vm_notification {
HOST_VM_NOTIFY_COLD_BOOT, /* Sent every time system cold boots */
HOST_VM_NOTIFY_HIBERNATING, /* Sent right before hibernation */
HOST_VM_NOTIFY_RESUMED_FROM_HIBERNATION, /* Sent right after hibernation */
HOST_VM_NOTIFY_FAILED_RESUME_FROM_HIBERNATION, /* Sent right after failed hibernation */
HOST_VM_NOTIFY_PRE_HIBERNATION_FAILED, /* Sent on errors when hibernating or resuming */
};

Expand Down Expand Up @@ -1107,7 +1108,7 @@ static struct resume_swap_area get_swap_area(const struct swap_file *swap)

log_info("Swap file %s is at device %ld, offset %d", swap->path, st.st_dev, offset);

return (struct resume_swap_area){
return (struct resume_swap_area) {
.offset = offset,
.dev = st.st_dev,
};
Expand Down Expand Up @@ -1449,13 +1450,13 @@ static bool is_cold_boot(void)
if (lock_file_path) {
unlink(hibernate_lock_file_name);

if (access(lock_file_path, F_OK) < 0)
return true;
if (!access(lock_file_path, F_OK))
return false;

unlink(lock_file_path);
}

return false;
return true;
}

static void notify_vm_host(enum host_vm_notification notification)
Expand All @@ -1464,8 +1465,18 @@ static void notify_vm_host(enum host_vm_notification notification)
[HOST_VM_NOTIFY_COLD_BOOT] = "cold-boot",
[HOST_VM_NOTIFY_HIBERNATING] = "hibernating",
[HOST_VM_NOTIFY_RESUMED_FROM_HIBERNATION] = "resumed-from-hibernation",
[HOST_VM_NOTIFY_FAILED_RESUME_FROM_HIBERNATION] = "failed-resume-from-hibernation",
[HOST_VM_NOTIFY_PRE_HIBERNATION_FAILED] = "pre-hibernation-failed",
};

/ *
if (notification == HOST_VM_NOTIFY_HIBERNATING) {
// TODO: link_and_enable_systemd_service(___, ____, ____)
}
else if (notification == HOST_VM_NOTIFY_PRE_HIBERNATION_FAILED) {
spawn_and_wait("systemctl", 2, "disable", "resume.service");
}
*/

log_notice("Changed hibernation state to: %s\n", types[notification]);
}
Expand Down Expand Up @@ -1516,7 +1527,7 @@ static int handle_pre_systemd_suspend_notification(const char *action)
log_needs_pre_hook_prefix = true;
if (!strcmp(action, "hibernate")) {
log_info("Running pre-hibernate hooks");

/* Creating this directory with the right permissions is racy as
* we're writing to tmp which is world-writable. So do our best
* here to ensure that if this for loop terminates normally, the
Expand Down Expand Up @@ -1616,19 +1627,28 @@ static int handle_post_systemd_suspend_notification(const char *action)
const char *real_path;

log_info("Running post-hibernate hooks");

bool cold_booted = false;
real_path = readlink0(hibernate_lock_file_name, real_path_buf);
if (!real_path) {
/* No need to notify host VM here: if link wasn't there, it's most likely that the
* pre-hibernation hooks failed to create it in the first place. Log for debugging
* but keep going. */
log_info("This is probably fine, but couldn't readlink(%s): %s", hibernate_lock_file_name, strerror(errno));
} else if (unlink(real_path) < 0) {
log_info("This is fine, but couldn't remove %s: %s", real_path, strerror(errno));
}
cold_booted = true;
notify_vm_host(HOST_VM_NOTIFY_FAILED_RESUME_FROM_HIBERNATION);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my understanding - are we coming to conclusion that it is cold boot scenario even if there is some issue in creating the link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the symbolic link (/etc/hibernation-setup-tool.last_hibernation) to find the temporary file. If, after resuming from hibernate, we cannot read from that symbolic link we assume a cold boot. Keep in mind this symbolic link gets created just before hibernating (in the pre hook). So it should exist in the post hook, no room for user intervention.

}

if (unlink(hibernate_lock_file_name) < 0)
log_info("This is fine, but couldn't remove %s: %s", hibernate_lock_file_name, strerror(errno));

if (!cold_booted && access(real_path, F_OK) < 0) {
cold_booted = true;
notify_vm_host(HOST_VM_NOTIFY_FAILED_RESUME_FROM_HIBERNATION);
}

if (real_path && unlink(real_path) < 0)
log_info("This is fine, but couldn't remove %s: %s", real_path, strerror(errno));

if (umount2("/tmp/hibernation-setup-tool", UMOUNT_NOFOLLOW | MNT_DETACH) < 0) {
/* EINVAL is returned if this isn't a mount point. This is normal if /tmp was already
* a tmpfs and we didn't create and mounted this directory in the pre-hibernate hook.
Expand All @@ -1641,7 +1661,8 @@ static int handle_post_systemd_suspend_notification(const char *action)
if (!recursive_rmdir("/tmp/hibernation-setup-tool"))
log_info("While removing /tmp/hibernation-setup-tool: %s", strerror(errno));

notify_vm_host(HOST_VM_NOTIFY_RESUMED_FROM_HIBERNATION);
if (!cold_booted)
notify_vm_host(HOST_VM_NOTIFY_RESUMED_FROM_HIBERNATION);
log_info("Post-hibernation hooks executed successfully");

return 0;
Expand All @@ -1653,7 +1674,7 @@ static int handle_post_systemd_suspend_notification(const char *action)

static int handle_systemd_suspend_notification(const char *argv0, const char *when, const char *action)
{
// Uncomment to view hook logs in syslogs
// Uncomment to view hook logs in syslog
// log_needs_syslog = true;

if (!strcmp(when, "pre"))
Expand All @@ -1666,15 +1687,139 @@ static int handle_systemd_suspend_notification(const char *argv0, const char *wh
return 1;
}

static void link_hook(const char *src, const char *dest)
static bool link_hook(const char *src, const char *dest)
{
if (link(src, dest) < 0) {
if (errno != EEXIST)
return log_fatal("Couldn't link %s to %s: %s", src, dest, strerror(errno));
return false;
}

log_info("Notifying systemd of new hooks");
spawn_and_wait("systemctl", 1, "daemon-reload");
return true;
}

static bool hard_link_file(const char *curr_file_path, char *target_dir, char *target_file) {
// Setup file permissions
char *mode_str = "0755";
int mode = strtol(mode_str, 0, 8);

// Construct target path for file
char target_path[PATH_MAX];
snprintf(target_path, sizeof(target_path), "%s%s%s", target_dir, "/", target_file);

// Set file permissions
if (chmod(curr_file_path, mode) < 0) {
log_info("Couldn't set permissions of %s to %s: %s", curr_file_path, mode_str, strerror(errno));
return false;
}

// Create/ensure target directory for file
if (!mkdir(target_dir, 0755) && errno != EEXIST) {
log_info("Couldn't create target directory %s to store target file %s: %s", target_dir, target_path, strerror(errno));
return false;
}

// Move file to target location
if (link(curr_file_path, target_path) < 0 && errno != EEXIST) {
log_info("Couldn't link %s to %s: %s", curr_file_path, target_path, strerror(errno));
return false;
}

return true;
}

static bool link_and_enable_systemd_service(const char *dir_parent, char *service_file_name, char *systemd_dir) {
// Setup service file permissions
char *service_mode_str = "0644";
int service_mode = strtol(service_mode_str, 0, 8);

// Construct existing path to service file
char service_tool_path[PATH_MAX];
snprintf(service_tool_path, sizeof(service_tool_path), "%s%s%s", dir_parent, "/", service_file_name);

// Construct target path for service file
char systemd_path[PATH_MAX];
snprintf(systemd_path, sizeof(systemd_path), "%s%s%s", systemd_dir, "/", service_file_name);

// Set service file permissions
if(chmod(service_tool_path, service_mode) < 0) {
log_info("Couldn't set permissions of %s to %s: %s", service_tool_path, service_mode_str, strerror(errno));
return false;
}

// Create/ensure target directory for service file
if(!mkdir(systemd_dir, 0755) && errno != EEXIST) {
log_info("Couldn't create directory location %s to store service: %s", systemd_dir, strerror(errno));
return false;
}

// Move service file to target location
if(!link_hook(service_tool_path, systemd_path)) {
log_info("Couldn't link %s to %s: %s", service_tool_path, systemd_path, strerror(errno));
return false;
}

// Enable service
spawn_and_wait("systemctl", 2, "enable", service_file_name);
return true;
}

static bool ensure_systemd_services_enabled(char *dest_dir) {
const char *execfn = (const char *)getauxval(AT_EXECFN);
const char *usr_sbin_default = "/usr/sbin", *systemd_dir_default = "/lib/systemd/system";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also usr/lib/systemd/system based on the distro?

Copy link
Contributor Author

@ishaansehgal99 ishaansehgal99 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Ubuntu /usr/lib and /lib and different directories. On Ubuntu, /usr/lib is a user-based directory to store things that require access to libraries. The /lib folder is the actual place for essential standard libraries. We need to put our services in this directory in order to access the targets we need (hibernate.target) and for systemd to recognize us as a legitimate service.

After testing on Debian, Rhel and CentOS, the /usr/lib and /lib directories are identical for each of them. On each of them lib is a symbolic link pointing to /usr/lib. In other words, /lib and /usr/lib go to the same place.

/lib is preferred because it includes ubuntu and so works on all distros.

Additional testing: On Debian tool executes without any issues.

However, Rhel has the additional challenges of not having enough space on root "/" partition no matter how much space it's created with. This causes the free space check to prevent tool from running, and if that check is removed, it causes insufficient space error upon allocating hibfile.


char usr_sbin_dir[PATH_MAX], systemd_dir[PATH_MAX];

if (dest_dir) {
snprintf(usr_sbin_dir, sizeof(usr_sbin_dir), "%s%s", dest_dir, usr_sbin_default);
snprintf(systemd_dir, sizeof(systemd_dir), "%s%s", dest_dir, systemd_dir_default);
} else {
snprintf(usr_sbin_dir, sizeof(usr_sbin_dir), "%s", usr_sbin_default);
snprintf(systemd_dir, sizeof(systemd_dir), "%s", systemd_dir_default);
}

if (!hard_link_file(execfn, usr_sbin_dir, "hibernation-setup-tool")) {
log_info("Couldn't create hard link to %s to store hibernation tool executable: %s", usr_sbin_dir, strerror(errno));
return false;
}

char* last_slash = strrchr(execfn, '/');
if(last_slash == NULL)
return false;
*last_slash = '\0';

// If tool is being executed by the systemd service
// tool is in /usr/sbin. In which case services are
// already setup and we do not want to link and enable them again.
if(!strncmp(execfn, usr_sbin_default, sizeof("/usr/sbin") - 1)) {
log_info("Services Enabled. Service running tool in folder: %s", execfn);
return true;
}

bool tool_service_enabled = false, pre_hook_service_enabled = false, post_hook_service_enabled = false;

tool_service_enabled = link_and_enable_systemd_service(execfn, "hibernation-setup-tool.service", systemd_dir);
if (!tool_service_enabled)
log_info("Couldn't enable hibernation tool service in %s: %s", systemd_dir, strerror(errno));

pre_hook_service_enabled = link_and_enable_systemd_service(execfn, "hibernate.service", systemd_dir);

if (pre_hook_service_enabled) {
post_hook_service_enabled = link_and_enable_systemd_service(execfn, "resume.service", systemd_dir);
if (!post_hook_service_enabled) {
log_info("Couldn't enable post hook resume service in %s: %s", systemd_dir, strerror(errno));
}
}
else {
// If pre hook failed to enable, disable post hook.
// Prevents post hook from failing and logging "failed-resume-from-hibernation",
// when cause of failure is simply pre hooks never getting executed.
spawn_and_wait("systemctl", 2, "disable", "resume.service");
log_info("Couldn't enable pre hook hibernate service in %s: %s", systemd_dir, strerror(errno));
}

return tool_service_enabled && pre_hook_service_enabled && post_hook_service_enabled;
}

int main(int argc, char *argv[])
Expand Down Expand Up @@ -1791,6 +1936,8 @@ int main(int argc, char *argv[])

if (is_hyperv()) {
ensure_udev_rules_are_installed();
if(!ensure_systemd_services_enabled(dest_dir))
log_info("Could not enable systemd services");
}

log_info("Swap file for VM hibernation set up successfully");
Expand Down
10 changes: 10 additions & 0 deletions resume.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=User resume actions
After=hibernate.target

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean it gets triggered only after resume or after cold boot as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This service only gets triggered after resume. I have tested this very thoroughly.


[Service]
Type=simple
ExecStart=/usr/sbin/hibernation-setup-tool -w post -a hibernate

[Install]
WantedBy=hibernate.target