2024-02-17 01:34:58 +01:00
|
|
|
#include "disk.h"
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
#include <errno.h>
|
2024-02-17 23:59:38 +01:00
|
|
|
#include <stdio.h>
|
2024-02-17 01:34:58 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
2024-02-18 18:09:53 +01:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <libgen.h>
|
2024-02-17 01:34:58 +01:00
|
|
|
#include <json-c/json.h>
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result CreateRootDisk(const char* disk_path, uint64_t size) {
|
2024-02-17 01:34:58 +01:00
|
|
|
// Convert the size to a string
|
|
|
|
char* size_str;
|
2024-02-18 18:09:53 +01:00
|
|
|
if (Format(&size_str, "%lu", size) == FAILURE)
|
|
|
|
return FAILURE;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Create the root disk
|
2024-02-17 01:34:58 +01:00
|
|
|
int exit_code;
|
2024-02-18 18:09:53 +01:00
|
|
|
// char* stdoutb; No need to capture stdout
|
2024-02-17 23:59:38 +01:00
|
|
|
char* stderrb;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result result = RunExecutable(&exit_code, NULL, &stderrb, "qemu-img", "create", "-f", "qcow2", disk_path, size_str, NULL);
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the size string
|
2024-02-17 23:59:38 +01:00
|
|
|
free(size_str);
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the execution failed, return the error
|
|
|
|
if (result != SUCCESS)
|
|
|
|
return result;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the exit code is non-zero, return the error
|
2024-02-17 23:59:38 +01:00
|
|
|
if (exit_code != 0) {
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the error message is empty, return a generic error
|
|
|
|
if (stderrb == NULL)
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to create root disk '%s'.", disk_path);
|
|
|
|
else {
|
|
|
|
// Remove all newlines from the error message
|
|
|
|
for (char* c = stderrb; *c != '\0'; c++)
|
|
|
|
if (*c == '\n')
|
|
|
|
*c = ' ';
|
|
|
|
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to create root disk '%s' (%s).", disk_path, stderrb);
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the error message
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// As there can still be an error message for a successful exit code, we need to free it
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result CreateBackedDisk(const char* disk_path, const char* backing_disk_path) {
|
|
|
|
// Create the backed disk
|
2024-02-17 01:34:58 +01:00
|
|
|
int exit_code;
|
2024-02-18 18:09:53 +01:00
|
|
|
// char* stdoutb; No need to capture stdout
|
2024-02-17 23:59:38 +01:00
|
|
|
char* stderrb;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result result = RunExecutable(&exit_code, NULL, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk_path, disk_path, NULL);
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the execution failed, return the error
|
|
|
|
if (result != SUCCESS)
|
|
|
|
return result;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the exit code is non-zero, return the error
|
2024-02-17 23:59:38 +01:00
|
|
|
if (exit_code != 0) {
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the error message is empty, return a generic error
|
|
|
|
if (stderrb == NULL)
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to create backed disk '%s' with backing disk '%s'.", disk_path, backing_disk_path);
|
|
|
|
else {
|
|
|
|
// Remove all newlines from the error message
|
|
|
|
for (char* c = stderrb; *c != '\0'; c++)
|
|
|
|
if (*c == '\n')
|
|
|
|
*c = ' ';
|
|
|
|
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to create backed disk '%s' with backing disk '%s' (%s).", disk_path, backing_disk_path, stderrb);
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the error message
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// As there can still be an error message for a successful exit code, we need to free it
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result TrimDisk(const char* disk_path) {
|
|
|
|
// Get a temporary file path to store the trimmed disk
|
|
|
|
char* trimmed_disk_path;
|
|
|
|
if (Format(&trimmed_disk_path, "%s.trimmed", disk_path) == FAILURE)
|
|
|
|
return FAILURE;
|
2024-02-17 02:39:41 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Get info about the disk
|
2024-02-17 02:39:41 +01:00
|
|
|
DiskInfo info;
|
2024-02-18 18:09:53 +01:00
|
|
|
if (GetDiskInfo(disk_path, &info) == FAILURE) {
|
|
|
|
free(trimmed_disk_path);
|
|
|
|
return FAILURE;
|
2024-02-17 02:39:41 +01:00
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Trim the disk
|
2024-02-17 02:39:41 +01:00
|
|
|
int exit_code;
|
2024-02-18 18:09:53 +01:00
|
|
|
// char* stdoutb; No need to capture stdout
|
2024-02-17 23:59:38 +01:00
|
|
|
char* stderrb;
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the disk is not backed, we can just use the "convert" command to trim it
|
|
|
|
Result result;
|
|
|
|
|
|
|
|
if (info.backing_file_path == NULL)
|
|
|
|
result = RunExecutable(&exit_code, NULL, &stderrb, "qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", disk_path, trimmed_disk_path, NULL);
|
|
|
|
else {
|
|
|
|
// We need to get the option to specify the backing file
|
|
|
|
char* backing_option;
|
|
|
|
if (Format(&backing_option, "backing_file=%s", info.backing_file_path) == FAILURE) {
|
|
|
|
free(trimmed_disk_path);
|
2024-02-17 23:59:38 +01:00
|
|
|
FreeDiskInfo(&info);
|
2024-02-18 18:09:53 +01:00
|
|
|
return FAILURE;
|
2024-02-17 02:39:41 +01:00
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Trim the disk using the "convert" command
|
|
|
|
result = RunExecutable(&exit_code, NULL, &stderrb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_option, disk_path, trimmed_disk_path, NULL);
|
2024-02-17 02:39:41 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the backing option
|
|
|
|
free(backing_option);
|
|
|
|
}
|
2024-02-17 02:39:41 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the disk info
|
2024-02-17 23:59:38 +01:00
|
|
|
FreeDiskInfo(&info);
|
2024-02-17 02:39:41 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the execution failed, return the error
|
|
|
|
if (result != SUCCESS) {
|
|
|
|
free(trimmed_disk_path);
|
|
|
|
return result;
|
2024-02-17 02:39:41 +01:00
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the exit code is non-zero, return the error
|
2024-02-17 23:59:38 +01:00
|
|
|
if (exit_code != 0) {
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the error message is empty, return a generic error
|
|
|
|
if (stderrb == NULL)
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to trim disk '%s'.", disk_path);
|
|
|
|
else {
|
|
|
|
// Remove all newlines from the error message
|
|
|
|
for (char* c = stderrb; *c != '\0'; c++)
|
|
|
|
if (*c == '\n')
|
|
|
|
*c = ' ';
|
|
|
|
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to trim disk '%s' (%s).", disk_path, stderrb);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free the error message
|
|
|
|
free(stderrb);
|
|
|
|
free(trimmed_disk_path);
|
2024-02-17 02:39:41 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// As there can still be an error message for a successful exit code, we need to free it
|
|
|
|
free(stderrb);
|
2024-02-17 23:59:38 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Move the trimmed disk to the original path
|
|
|
|
if (rename(trimmed_disk_path, disk_path) != 0) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to move trimmed disk '%s' to original path '%s' (%s).", trimmed_disk_path, disk_path, strerror(errno));
|
|
|
|
unlink(trimmed_disk_path);
|
|
|
|
free(trimmed_disk_path);
|
2024-02-17 02:39:41 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the temporary file path
|
|
|
|
free(trimmed_disk_path);
|
2024-02-17 02:39:41 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result RebackDisk(const char* disk_path, const char* backing_disk_path) {
|
|
|
|
// Reback the disk
|
2024-02-17 01:34:58 +01:00
|
|
|
int exit_code;
|
2024-02-18 18:09:53 +01:00
|
|
|
// char* stdoutb; No need to capture stdout
|
2024-02-17 23:59:38 +01:00
|
|
|
char* stderrb;
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result result = RunExecutable(&exit_code, NULL, &stderrb, "qemu-img", "rebase", "-f", "qcow2", "-F", "qcow2", "-u", "-b", backing_disk_path, disk_path, NULL);
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the execution failed, return the error
|
|
|
|
if (result != SUCCESS)
|
|
|
|
return result;
|
2024-02-17 23:59:38 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the exit code is non-zero, return the error
|
2024-02-17 23:59:38 +01:00
|
|
|
if (exit_code != 0) {
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the error message is empty, return a generic error
|
|
|
|
if (stderrb == NULL)
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to reback disk '%s' with backing disk '%s'.", disk_path, backing_disk_path);
|
|
|
|
else {
|
|
|
|
// Remove all newlines from the error message
|
|
|
|
for (char* c = stderrb; *c != '\0'; c++)
|
|
|
|
if (*c == '\n')
|
|
|
|
*c = ' ';
|
|
|
|
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to reback disk '%s' with backing disk '%s' (%s).", disk_path, backing_disk_path, stderrb);
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the error message
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// As there can still be an error message for a successful exit code, we need to free it
|
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result GetDiskInfo(const char* disk_path, DiskInfo* _info) {
|
|
|
|
// Initialize the disk info
|
2024-02-17 23:59:38 +01:00
|
|
|
*_info = (DiskInfo){0};
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-17 23:59:38 +01:00
|
|
|
// Get the disk info
|
2024-02-17 01:34:58 +01:00
|
|
|
int exit_code;
|
2024-02-17 23:59:38 +01:00
|
|
|
char* stdoutb;
|
|
|
|
char* stderrb;
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result result = RunExecutable(&exit_code, &stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk_path, NULL);
|
2024-02-17 23:59:38 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the execution failed, return the error
|
|
|
|
if (result != SUCCESS)
|
|
|
|
return result;
|
2024-02-17 23:59:38 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// If the exit code is non-zero, return the error
|
|
|
|
if (exit_code != 0) {
|
|
|
|
// If the error message is empty, return a generic error
|
|
|
|
if (stderrb == NULL)
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to get info for disk '%s'.", disk_path);
|
|
|
|
else {
|
|
|
|
// Remove all newlines from the error message
|
|
|
|
for (char* c = stderrb; *c != '\0'; c++)
|
|
|
|
if (*c == '\n')
|
|
|
|
*c = ' ';
|
|
|
|
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to get info for disk '%s' (%s).", disk_path, stderrb);
|
|
|
|
}
|
2024-02-17 23:59:38 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the error message
|
2024-02-17 23:59:38 +01:00
|
|
|
free(stdoutb);
|
2024-02-18 18:09:53 +01:00
|
|
|
free(stderrb);
|
2024-02-17 01:34:58 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
2024-02-18 18:09:53 +01:00
|
|
|
|
|
|
|
// No need for the error message anymore
|
2024-02-17 23:59:38 +01:00
|
|
|
free(stderrb);
|
2024-02-17 02:46:21 +01:00
|
|
|
|
2024-02-17 01:34:58 +01:00
|
|
|
// Parse the JSON output
|
2024-02-17 23:59:38 +01:00
|
|
|
json_object* root = json_tokener_parse(stdoutb);
|
2024-02-18 18:09:53 +01:00
|
|
|
if (root == NULL) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to parse JSON output for disk '%s'.", disk_path);
|
|
|
|
free(stdoutb);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// No need for the JSON string anymore
|
2024-02-17 23:59:38 +01:00
|
|
|
free(stdoutb);
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Get the size of the disk
|
|
|
|
json_object* size_obj;
|
|
|
|
if (!json_object_object_get_ex(root, "virtual-size", &size_obj)) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to get virtual size for disk '%s'.", disk_path);
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
2024-02-17 01:34:58 +01:00
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
errno = 0;
|
|
|
|
_info->size = json_object_get_int64(size_obj);
|
|
|
|
if (errno != 0) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to parse virtual size for disk '%s'.", disk_path);
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Get the allocated size of the disk
|
|
|
|
json_object* allocation_obj;
|
|
|
|
if (!json_object_object_get_ex(root, "actual-size", &allocation_obj)) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to get actual size for disk '%s'.", disk_path);
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
errno = 0;
|
|
|
|
_info->allocated = json_object_get_int64(allocation_obj);
|
|
|
|
if (errno != 0) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to parse actual size for disk '%s'.", disk_path);
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Get the backing file of the disk
|
|
|
|
json_object* backing_file_obj;
|
|
|
|
if (!json_object_object_get_ex(root, "backing-filename", &backing_file_obj))
|
|
|
|
_info->backing_file_path = NULL;
|
|
|
|
else {
|
|
|
|
const char* backing_file_path = json_object_get_string(backing_file_obj);
|
|
|
|
if (backing_file_path == NULL) {
|
|
|
|
Log(LOG_LEVEL_ERROR, "Failed to parse backing file for disk '%s'.", disk_path);
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Duplicate(backing_file_path, &_info->backing_file_path) == FAILURE) {
|
|
|
|
json_object_put(root);
|
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
2024-02-17 01:34:58 +01:00
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Get the backing identifier of the disk (basename)
|
|
|
|
if (Duplicate(basename(_info->backing_file_path), &_info->backing_identifier) == FAILURE) {
|
|
|
|
json_object_put(root);
|
2024-02-18 14:56:36 +01:00
|
|
|
FreeDiskInfo(_info);
|
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
// Free the JSON object
|
|
|
|
json_object_put(root);
|
2024-02-17 01:34:58 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
|
2024-02-18 18:09:53 +01:00
|
|
|
Result FreeDiskInfo(DiskInfo* info) {
|
|
|
|
free(info->backing_file_path);
|
2024-02-18 14:56:36 +01:00
|
|
|
free(info->backing_identifier);
|
2024-02-18 18:09:53 +01:00
|
|
|
return SUCCESS;
|
|
|
|
}
|