-
Notifications
You must be signed in to change notification settings - Fork 4
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
base: main
Are you sure you want to change the base?
Changes from all commits
d799ef4
4551a79
630be4f
b70ad46
8042081
361cc48
7938c45
9b15002
7fd4005
5f41ec3
c1c6676
b2f191a
9f31117
4816cc8
88dc86b
583269d
c365ce9
77a73fd
3100c41
f58f229
3e247ea
ab98b8e
c33297f
3f29064
9c9a703
9fe83d8
d8df3c6
42100c1
6f2c0b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 */ | ||
}; | ||
|
||
|
@@ -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, | ||
}; | ||
|
@@ -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) | ||
|
@@ -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]); | ||
} | ||
|
@@ -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 | ||
|
@@ -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); | ||
} | ||
|
||
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. | ||
|
@@ -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; | ||
|
@@ -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")) | ||
|
@@ -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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also usr/lib/systemd/system based on the distro? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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[]) | ||
|
@@ -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"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[Unit] | ||
Description=User resume actions | ||
After=hibernate.target | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.