Perfected the disk utilities
This commit is contained in:
parent
e6bc757343
commit
01ea511b2d
227
src/backing.c
227
src/backing.c
@ -1,227 +0,0 @@
|
|||||||
#include "backing.h"
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
#include "sandbox.h"
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
char* get_backings_path(void) {
|
|
||||||
return format("%s/%s", MASTER_DIRECTORY, BACKINGS_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_backing_name(const char* name) {
|
|
||||||
if (name == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check that the name is not empty
|
|
||||||
size_t length = strlen(name);
|
|
||||||
if (length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check that the name starts with a number, corresponding to the timestamp of the backing disk creation
|
|
||||||
size_t timestamp_length = 0;
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
if (name[i] >= '0' && name[i] <= '9')
|
|
||||||
timestamp_length++;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (timestamp_length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* get_backing_path(const char* backing) {
|
|
||||||
char* backings_path = get_backings_path();
|
|
||||||
if (backings_path == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Combine the backings path with the backing name
|
|
||||||
char* backing_path = format("%s/%s", backings_path, backing);
|
|
||||||
|
|
||||||
// Free the backings path
|
|
||||||
free(backings_path);
|
|
||||||
|
|
||||||
return backing_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool backing_exists(const char* backing) {
|
|
||||||
char* backing_path = get_backing_path(backing);
|
|
||||||
if (backing_path == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check if the backing path exists
|
|
||||||
struct stat statbuf;
|
|
||||||
int result = stat(backing_path, &statbuf);
|
|
||||||
|
|
||||||
// Free the backing path
|
|
||||||
free(backing_path);
|
|
||||||
|
|
||||||
if (result != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!S_ISREG(statbuf.st_mode))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char** list_backings(void) {
|
|
||||||
char* path = get_backings_path();
|
|
||||||
if (path == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Open the directory
|
|
||||||
DIR* dir = opendir(path);
|
|
||||||
if (dir == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno));
|
|
||||||
free(path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
free(path);
|
|
||||||
|
|
||||||
// Count the number of entries
|
|
||||||
size_t count = 0;
|
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(dir)) != NULL)
|
|
||||||
if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name))
|
|
||||||
count++;
|
|
||||||
|
|
||||||
// Allocate the array of strings
|
|
||||||
char** backings = malloc((count + 1) * sizeof(char*));
|
|
||||||
if (backings == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to allocate memory for the backings array.");
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the array of strings
|
|
||||||
rewinddir(dir);
|
|
||||||
size_t index = 0;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name)) {
|
|
||||||
backings[index] = strdup(entry->d_name);
|
|
||||||
if (backings[index] == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to allocate memory for the backing name.");
|
|
||||||
for (size_t i = 0; i < index; i++)
|
|
||||||
free(backings[i]);
|
|
||||||
free(backings);
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate the array of strings
|
|
||||||
backings[count] = NULL;
|
|
||||||
|
|
||||||
// Close the directory
|
|
||||||
closedir(dir);
|
|
||||||
|
|
||||||
return backings;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t get_backing_creation_time(const char* backing) {
|
|
||||||
size_t length = strlen(backing);
|
|
||||||
size_t timestamp_length = 0;
|
|
||||||
|
|
||||||
// Find the length of the timestamp
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
if (backing[i] >= '0' && backing[i] <= '9')
|
|
||||||
timestamp_length++;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Extract the timestamp
|
|
||||||
char* timestamp = strndup(backing, timestamp_length);
|
|
||||||
if (timestamp == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Convert the timestamp to a number
|
|
||||||
uint64_t creation_time = strtoull(timestamp, NULL, 10);
|
|
||||||
|
|
||||||
// Free the timestamp
|
|
||||||
free(timestamp);
|
|
||||||
|
|
||||||
return creation_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* find_latest_backing(void) {
|
|
||||||
char** backings = list_backings();
|
|
||||||
if (backings == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Find the latest backing disk
|
|
||||||
char* latest_backing = NULL;
|
|
||||||
uint64_t latest_time = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; backings[i] != NULL; i++) {
|
|
||||||
uint64_t time = get_backing_creation_time(backings[i]);
|
|
||||||
if (time >= latest_time) {
|
|
||||||
latest_time = time;
|
|
||||||
latest_backing = backings[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate the latest backing disk
|
|
||||||
if (latest_backing != NULL)
|
|
||||||
latest_backing = strdup(latest_backing);
|
|
||||||
|
|
||||||
// Free the backings
|
|
||||||
for (size_t i = 0; backings[i] != NULL; i++)
|
|
||||||
free(backings[i]);
|
|
||||||
free(backings);
|
|
||||||
|
|
||||||
return latest_backing;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool create_backing(const char* disk, const char* description) {
|
|
||||||
// Create the backing disk name
|
|
||||||
uint64_t timestamp = (uint64_t)time(NULL);
|
|
||||||
char* backing_name = NULL;
|
|
||||||
|
|
||||||
if (description == NULL || strlen(description) == 0)
|
|
||||||
backing_name = format("%lu", timestamp);
|
|
||||||
else
|
|
||||||
backing_name = format("%lu-%s", timestamp, description);
|
|
||||||
|
|
||||||
if (backing_name == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to create the backing disk name.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the backing disk path
|
|
||||||
char* backings_path = get_backings_path();
|
|
||||||
if (backings_path == NULL) {
|
|
||||||
free(backing_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* backing_path = format("%s/%s", backings_path, backing_name);
|
|
||||||
free(backing_name);
|
|
||||||
free(backings_path);
|
|
||||||
|
|
||||||
if (backing_path == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Copy the disk to the backing disk
|
|
||||||
if (!copy_file(disk, backing_path, 0755, 0, 0)) {
|
|
||||||
free(backing_path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free the backing disk path
|
|
||||||
free(backing_path);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/// @brief Returns the directory path where the backing disks are stored.
|
|
||||||
/// @return The directory path where the backing disks are stored. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* get_backings_path(void);
|
|
||||||
|
|
||||||
/// @brief Checks if the specified backing disk name is valid.
|
|
||||||
/// @param name The backing disk name to check.
|
|
||||||
/// @return true if the backing disk name is valid, otherwise false.
|
|
||||||
bool is_valid_backing_name(const char* name);
|
|
||||||
|
|
||||||
/// @brief Returns the path to the specified backing disk.
|
|
||||||
/// @param backing The valid backing disk name.
|
|
||||||
/// @return The path to the specified backing disk. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* get_backing_path(const char* backing);
|
|
||||||
|
|
||||||
/// @brief Checks if the specified backing disk exists.
|
|
||||||
/// @param backing The valid backing disk name.
|
|
||||||
/// @return true if the backing disk exists, otherwise false.
|
|
||||||
bool backing_exists(const char* backing);
|
|
||||||
|
|
||||||
/// @brief Lists the backing disks.
|
|
||||||
/// @return The list of backing disks. The caller is responsible for freeing the returned array and strings. This function can return NULL.
|
|
||||||
char** list_backings(void);
|
|
||||||
|
|
||||||
/// @brief Get the creation time of the specified backing disk.
|
|
||||||
/// @param backing The valid backing disk name.
|
|
||||||
/// @return The creation time of the specified backing disk. If the backing disk does not exist, this function returns 0.
|
|
||||||
uint64_t get_backing_creation_time(const char* backing);
|
|
||||||
|
|
||||||
/// @brief Finds the latest backing disk.
|
|
||||||
/// @return The latest backing disk. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* find_latest_backing(void);
|
|
||||||
|
|
||||||
/// @brief Creates a new backing disk.
|
|
||||||
/// @param disk The disk to use as the backing disk. Warning: the disk must be part of the backing disks.
|
|
||||||
/// @param description The description of the backing disk.
|
|
||||||
/// @return true if the backing disk was created, otherwise false.
|
|
||||||
bool create_backing(const char* disk, const char* description);
|
|
271
src/disk.c
271
src/disk.c
@ -2,120 +2,233 @@
|
|||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <json-c/json.h>
|
#include <json-c/json.h>
|
||||||
|
|
||||||
bool create_empty_disk(const char* disk, uint64_t size) {
|
bool create_empty_disk(const char* path, uint64_t size) {
|
||||||
char* stdoutb = NULL;
|
char* errb = NULL;
|
||||||
char* stderrb = NULL;
|
|
||||||
|
|
||||||
// Create an empty disk
|
// Convert the size to a string
|
||||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", disk, format("%" PRIu64, size), NULL);
|
char* size_str = format("%lu", size);
|
||||||
|
if (size_str == NULL) {
|
||||||
|
logmsg(LOG_ERROR, "Failed to allocate memory for the size string.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the command was successful
|
// Create the disk
|
||||||
if (result != 0) {
|
int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL);
|
||||||
// Remove the newline character from the stderr buffer
|
|
||||||
size_t length = strlen(stderrb);
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
if (stderrb[i] == '\n')
|
|
||||||
stderrb[i] = ' ';
|
|
||||||
log_msg(LOG_ERROR, "Failed to create the empty disk %s (%s).", disk, stderrb);
|
|
||||||
|
|
||||||
free(stdoutb);
|
free(size_str);
|
||||||
free(stderrb);
|
|
||||||
|
// Check for errors
|
||||||
|
if (ret != 0) {
|
||||||
|
if (errb == NULL)
|
||||||
|
logmsg(LOG_ERROR, "Failed to create disk %s.", path);
|
||||||
|
else {
|
||||||
|
size_t length = strlen(errb);
|
||||||
|
for (size_t i = 0; i < length; i++)
|
||||||
|
if (errb[i] == '\n')
|
||||||
|
errb[i] = ' ';
|
||||||
|
|
||||||
|
logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(stdoutb);
|
|
||||||
free(stderrb);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool create_backed_disk(const char* disk, const char* backing) {
|
bool create_backed_disk(const char* path, const char* backing_disk) {
|
||||||
char* stdoutb = NULL;
|
char* errb = NULL;
|
||||||
char* stderrb = NULL;
|
|
||||||
|
|
||||||
// Create a backed disk
|
// Create the disk
|
||||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL);
|
int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL);
|
||||||
|
|
||||||
// Check if the command was successful
|
// Check for errors
|
||||||
if (result != 0) {
|
if (ret != 0) {
|
||||||
// Remove the newline character from the stderr buffer
|
if (errb == NULL)
|
||||||
size_t length = strlen(stderrb);
|
logmsg(LOG_ERROR, "Failed to create disk %s.", path);
|
||||||
for (size_t i = 0; i < length; i++)
|
else {
|
||||||
if (stderrb[i] == '\n')
|
size_t length = strlen(errb);
|
||||||
stderrb[i] = ' ';
|
for (size_t i = 0; i < length; i++)
|
||||||
log_msg(LOG_ERROR, "Failed to create the backed disk %s (%s).", disk, stderrb);
|
if (errb[i] == '\n')
|
||||||
|
errb[i] = ' ';
|
||||||
|
|
||||||
free(stdoutb);
|
logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
|
||||||
free(stderrb);
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(stdoutb);
|
return true;
|
||||||
free(stderrb);
|
}
|
||||||
|
|
||||||
|
bool trim_disk(const char* path) {
|
||||||
|
char* tmp_path = format("%s.tmp", path);
|
||||||
|
if (tmp_path == NULL) {
|
||||||
|
logmsg(LOG_ERROR, "Failed to allocate memory for the temporary disk path.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskInfo info;
|
||||||
|
if (!get_disk_info(path, &info)) {
|
||||||
|
free(tmp_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* errb = NULL;
|
||||||
|
|
||||||
|
// Create the trimmed disk
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (info.backing_file != NULL) {
|
||||||
|
char* backing_str = format("backing_file=%s", info.backing_file);
|
||||||
|
if (backing_str == NULL) {
|
||||||
|
logmsg(LOG_ERROR, "Failed to allocate memory for the backing file string.");
|
||||||
|
|
||||||
|
free_disk_info(&info);
|
||||||
|
free(tmp_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = exec(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_str, path, tmp_path, NULL);
|
||||||
|
|
||||||
|
free(backing_str);
|
||||||
|
} else
|
||||||
|
ret = exec(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", path, tmp_path, NULL);
|
||||||
|
|
||||||
|
// Free the disk info as we don't need it anymore
|
||||||
|
free_disk_info(&info);
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
if (ret != 0) {
|
||||||
|
if (errb == NULL)
|
||||||
|
logmsg(LOG_ERROR, "Failed to trim disk %s.", path);
|
||||||
|
else {
|
||||||
|
size_t length = strlen(errb);
|
||||||
|
for (size_t i = 0; i < length; i++)
|
||||||
|
if (errb[i] == '\n')
|
||||||
|
errb[i] = ' ';
|
||||||
|
|
||||||
|
logmsg(LOG_ERROR, "Failed to trim disk %s (%s).", path, errb);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
free(tmp_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the original disk with the trimmed disk
|
||||||
|
if (rename(tmp_path, path) != 0) {
|
||||||
|
logmsg(LOG_ERROR, "Failed to replace disk %s with the trimmed disk (%s).", path, strerror(errno));
|
||||||
|
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
free(tmp_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tmp_path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rebase_disk(const char* path, const char* backing_disk) {
|
||||||
|
char* errb = NULL;
|
||||||
|
|
||||||
|
// Rebase the disk
|
||||||
|
int ret = exec(NULL, &errb, "qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing_disk, path, NULL);
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
if (ret != 0) {
|
||||||
|
if (errb == NULL)
|
||||||
|
logmsg(LOG_ERROR, "Failed to rebase disk %s.", path);
|
||||||
|
else {
|
||||||
|
size_t length = strlen(errb);
|
||||||
|
for (size_t i = 0; i < length; i++)
|
||||||
|
if (errb[i] == '\n')
|
||||||
|
errb[i] = ' ';
|
||||||
|
|
||||||
|
logmsg(LOG_ERROR, "Failed to rebase disk %s (%s).", path, errb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* get_backing_file(const char* disk) {
|
bool get_disk_info(const char* path, DiskInfo* info) {
|
||||||
char* stdoutb = NULL;
|
char* outb = NULL;
|
||||||
char* stderrb = NULL;
|
char* errb = NULL;
|
||||||
|
|
||||||
// Get the backing file of the disk
|
int ret = exec(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL);
|
||||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, NULL);
|
|
||||||
|
|
||||||
// Check if the command was successful
|
if (ret != 0) {
|
||||||
if (result != 0) {
|
if (errb == NULL)
|
||||||
// Remove the newline character from the stderr buffer
|
logmsg(LOG_ERROR, "Failed to get information about disk %s.", path);
|
||||||
size_t length = strlen(stderrb);
|
else {
|
||||||
for (size_t i = 0; i < length; i++)
|
size_t length = strlen(errb);
|
||||||
if (stderrb[i] == '\n')
|
for (size_t i = 0; i < length; i++)
|
||||||
stderrb[i] = ' ';
|
if (errb[i] == '\n')
|
||||||
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s (%s).", disk, stderrb);
|
errb[i] = ' ';
|
||||||
|
|
||||||
free(stdoutb);
|
logmsg(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb);
|
||||||
free(stderrb);
|
}
|
||||||
|
|
||||||
return NULL;
|
free(outb);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(stderrb);
|
// Free the error buffer, as we don't need it anymore
|
||||||
|
free(errb);
|
||||||
|
|
||||||
|
json_object* root = json_tokener_parse(outb);
|
||||||
|
|
||||||
|
// Free the output buffer, as we don't need it anymore
|
||||||
|
free(outb);
|
||||||
|
|
||||||
// Parse the JSON output
|
|
||||||
json_object* root = json_tokener_parse(stdoutb);
|
|
||||||
if (root == NULL) {
|
if (root == NULL) {
|
||||||
log_msg(LOG_ERROR, "Failed to parse the JSON output of the command.");
|
logmsg(LOG_ERROR, "Failed to parse the JSON output from qemu-img.");
|
||||||
|
return false;
|
||||||
free(stdoutb);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the backing file
|
json_object* virtual_size = json_object_object_get(root, "virtual-size");
|
||||||
json_object* backing_file = NULL;
|
if (virtual_size == NULL)
|
||||||
if (!json_object_object_get_ex(root, "backing-filename", &backing_file)) {
|
info->virtual_size = 0;
|
||||||
json_object_put(root);
|
else
|
||||||
free(stdoutb);
|
info->virtual_size = json_object_get_int64(virtual_size);
|
||||||
|
|
||||||
return NULL;
|
json_object* actual_size = json_object_object_get(root, "actual-size");
|
||||||
|
if (actual_size == NULL)
|
||||||
|
info->actual_size = 0;
|
||||||
|
else
|
||||||
|
info->actual_size = json_object_get_int64(actual_size);
|
||||||
|
|
||||||
|
json_object* backing_file = json_object_object_get(root, "backing-filename");
|
||||||
|
if (backing_file == NULL)
|
||||||
|
info->backing_file = NULL;
|
||||||
|
else {
|
||||||
|
info->backing_file = strdup(json_object_get_string(backing_file));
|
||||||
|
if (info->backing_file == NULL) {
|
||||||
|
logmsg(LOG_ERROR, "Failed to allocate memory for the backing file path.");
|
||||||
|
|
||||||
|
json_object_put(root);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the backing file as a string0
|
json_object_put(root);
|
||||||
char* backing_file_str = strdup(json_object_get_string(backing_file));
|
return true;
|
||||||
if (backing_file_str == NULL) {
|
}
|
||||||
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s.", disk);
|
|
||||||
|
void free_disk_info(DiskInfo* info) {
|
||||||
json_object_put(root);
|
if (info->backing_file != NULL)
|
||||||
free(stdoutb);
|
free(info->backing_file);
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return backing_file_str;
|
|
||||||
}
|
}
|
||||||
|
59
src/disk.h
59
src/disk.h
@ -1,32 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
/// @brief Creates an empty disk.
|
typedef struct DiskInfo {
|
||||||
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
|
uint64_t virtual_size;
|
||||||
/// @param size The size of the disk to create in bytes.
|
uint64_t actual_size;
|
||||||
/// @return true if the disk is created, otherwise false.
|
char* backing_file;
|
||||||
bool create_empty_disk(const char* disk, uint64_t size);
|
} DiskInfo;
|
||||||
|
|
||||||
/// @brief Creates a backed disk.
|
/// @brief Creates an empty disk at the given path with the given size.
|
||||||
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
|
/// @param path The path to the disk. Any existing file at this path will be overwritten.
|
||||||
/// @param backing The path to the backing file, which exists.
|
/// @param size The size of the disk, in bytes.
|
||||||
/// @return true if the disk is created, otherwise false.
|
/// @return True if the disk was created successfully, false otherwise.
|
||||||
bool create_backed_disk(const char* disk, const char* backing);
|
bool create_empty_disk(const char* path, uint64_t size);
|
||||||
|
|
||||||
/// @brief Packs the disk to reduce its size.
|
/// @brief Creates a disk at the given path with the given backing disk.
|
||||||
/// @param disk The path to the disk to pack, which exists.
|
/// @param path The path to the disk.
|
||||||
/// @return true if the disk is packed, otherwise false.
|
/// @param backing_disk The path to the backing disk.
|
||||||
bool pack_disk(const char* disk);
|
/// @return True if the disk was created successfully, false otherwise.
|
||||||
|
bool create_backed_disk(const char* path, const char* backing_disk);
|
||||||
|
|
||||||
/// @brief Moves the backing file of a disk.
|
/// @brief Trims the given disk to remove any unused space.
|
||||||
/// @param disk The path to the disk, which exists.
|
/// @param path The path to the disk.
|
||||||
/// @param backing The path to the new backing file, which exists.
|
/// @return True if the disk was trimmed successfully, false otherwise.
|
||||||
/// @return true if the backing file is moved, otherwise false.
|
bool trim_disk(const char* path);
|
||||||
bool move_backing_file(const char* disk, const char* backing);
|
|
||||||
|
|
||||||
/// @brief Gets the backing file of a disk.
|
/// @brief Changes the backing disk of the given disk.
|
||||||
/// @param disk The path to the disk, which exists.
|
/// @param path The path to the disk.
|
||||||
/// @return The backing file of the disk. The caller is responsible for freeing the returned string. This function can return NULL.
|
/// @param backing_disk The path to the new backing disk.
|
||||||
char* get_backing_file(const char* disk);
|
/// @return True if the disk was rebased successfully, false otherwise.
|
||||||
|
bool rebase_disk(const char* path, const char* backing_disk);
|
||||||
|
|
||||||
|
/// @brief Gets information about the given disk.
|
||||||
|
/// @param path The path to the disk.
|
||||||
|
/// @param info The DiskInfo struct to fill with information about the disk.
|
||||||
|
/// @return True if the disk information was retrieved successfully, false otherwise.
|
||||||
|
bool get_disk_info(const char* path, DiskInfo* info);
|
||||||
|
|
||||||
|
/// @brief Frees the memory used by the given DiskInfo struct.
|
||||||
|
/// @param info The DiskInfo struct to free.
|
||||||
|
void free_disk_info(DiskInfo* info);
|
256
src/entry.c
256
src/entry.c
@ -1,256 +0,0 @@
|
|||||||
#include "entry.h"
|
|
||||||
|
|
||||||
#include "sandbox.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/statvfs.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
|
|
||||||
char* get_entries_path(void) {
|
|
||||||
return format("%s/%s", MASTER_DIRECTORY, ENTRIES_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_entry_name(const char* name) {
|
|
||||||
if (name == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check that the name is not empty
|
|
||||||
size_t length = strlen(name);
|
|
||||||
if (length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check that the name does not contain /
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
if (name[i] == '/')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check that the name is not . or ..
|
|
||||||
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* get_entry_path(const char* entry) {
|
|
||||||
char* entries_path = get_entries_path();
|
|
||||||
if (entries_path == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Combine the entries path with the entry name
|
|
||||||
char* entry_path = format("%s/%s", entries_path, entry);
|
|
||||||
|
|
||||||
// Free the entries path
|
|
||||||
free(entries_path);
|
|
||||||
|
|
||||||
return entry_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool entry_exists(const char* entry) {
|
|
||||||
char* entry_path = get_entry_path(entry);
|
|
||||||
if (entry_path == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check if the entry path exists
|
|
||||||
struct stat statbuf;
|
|
||||||
int result = stat(entry_path, &statbuf);
|
|
||||||
|
|
||||||
// Free the entry path
|
|
||||||
free(entry_path);
|
|
||||||
|
|
||||||
if (result != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!S_ISDIR(statbuf.st_mode))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* get_entry_disk_path(const char* entry) {
|
|
||||||
char* entry_path = get_entry_path(entry);
|
|
||||||
if (entry_path == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Combine the entry path with the disk name
|
|
||||||
char* disk_path = format("%s/%s", entry_path, DISK_PATH);
|
|
||||||
|
|
||||||
// Free the entry path
|
|
||||||
free(entry_path);
|
|
||||||
|
|
||||||
return disk_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
char** list_entries(void) {
|
|
||||||
char* path = get_entries_path();
|
|
||||||
if (path == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Open the directory
|
|
||||||
DIR* dir = opendir(path);
|
|
||||||
if (dir == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno));
|
|
||||||
free(path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
free(path);
|
|
||||||
|
|
||||||
// Count the number of entries
|
|
||||||
size_t count = 0;
|
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(dir)) != NULL)
|
|
||||||
if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name))
|
|
||||||
count++;
|
|
||||||
|
|
||||||
// Allocate the array of entries
|
|
||||||
char** entries = malloc((count + 1) * sizeof(char*));
|
|
||||||
if (entries == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to allocate memory for the entries array.");
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the array of entries
|
|
||||||
rewinddir(dir);
|
|
||||||
size_t index = 0;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name)) {
|
|
||||||
entries[index] = strdup(entry->d_name);
|
|
||||||
if (entries[index] == NULL) {
|
|
||||||
log_msg(LOG_ERROR, "Failed to allocate memory for the entry name.");
|
|
||||||
for (size_t i = 0; i < index; i++)
|
|
||||||
free(entries[i]);
|
|
||||||
free(entries);
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate the array of entries
|
|
||||||
entries[count] = NULL;
|
|
||||||
|
|
||||||
// Close the directory
|
|
||||||
closedir(dir);
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t get_entry_last_access(const char* entry) {
|
|
||||||
char* disk = get_entry_disk_path(entry);
|
|
||||||
if (disk == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Get the last access time of the disk
|
|
||||||
struct stat statbuf;
|
|
||||||
int result = stat(disk, &statbuf);
|
|
||||||
|
|
||||||
// Free the disk path
|
|
||||||
free(disk);
|
|
||||||
|
|
||||||
if (result != 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return statbuf.st_atime;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* find_oldest_entry(void) {
|
|
||||||
char** entries = list_entries();
|
|
||||||
if (entries == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Find the oldest entry
|
|
||||||
char* oldest_entry = NULL;
|
|
||||||
uint64_t oldest_time = UINT64_MAX;
|
|
||||||
|
|
||||||
for (size_t i = 0; entries[i] != NULL; i++) {
|
|
||||||
uint64_t time = get_entry_last_access(entries[i]);
|
|
||||||
if (time <= oldest_time) {
|
|
||||||
oldest_time = time;
|
|
||||||
oldest_entry = entries[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate the oldest entry
|
|
||||||
if (oldest_entry != NULL)
|
|
||||||
oldest_entry = strdup(oldest_entry);
|
|
||||||
|
|
||||||
// Free the entries
|
|
||||||
for (size_t i = 0; entries[i] != NULL; i++)
|
|
||||||
free(entries[i]);
|
|
||||||
free(entries);
|
|
||||||
|
|
||||||
return oldest_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool create_entry(const char* entry) {
|
|
||||||
char* entry_path = get_entry_path(entry);
|
|
||||||
if (entry_path == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Create the entry directory
|
|
||||||
bool result = create_directory(entry_path, 0700, 0, 0);
|
|
||||||
|
|
||||||
// Free the entry path
|
|
||||||
free(entry_path);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool delete_entry(const char* entry) {
|
|
||||||
char* entry_path = get_entry_path(entry);
|
|
||||||
if (entry_path == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Delete the entry directory
|
|
||||||
bool result = delete_directory(entry_path, 0);
|
|
||||||
|
|
||||||
// Free the entry path
|
|
||||||
free(entry_path);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t get_available_space(void) {
|
|
||||||
char* entries_path = get_entries_path();
|
|
||||||
if (entries_path == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Get the available space in the entries directory
|
|
||||||
struct statvfs statbuf;
|
|
||||||
int result = statvfs(entries_path, &statbuf);
|
|
||||||
|
|
||||||
// Free the entries path
|
|
||||||
free(entries_path);
|
|
||||||
|
|
||||||
if (result != 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return statbuf.f_bsize * statbuf.f_bavail;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reserve_space(uint64_t space) {
|
|
||||||
// While the available space is not enough, delete the oldest entry
|
|
||||||
while (get_available_space() < space) {
|
|
||||||
char* oldest_entry = find_oldest_entry();
|
|
||||||
if (oldest_entry == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool result = delete_entry(oldest_entry);
|
|
||||||
free(oldest_entry);
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
62
src/entry.h
62
src/entry.h
@ -1,62 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define DISK_PATH "disk"
|
|
||||||
|
|
||||||
/// @brief Returns the directory path where the entries are stored.
|
|
||||||
/// @return The directory path where the entries are stored. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* get_entries_path(void);
|
|
||||||
|
|
||||||
/// @brief Checks if the specified entry name is valid.
|
|
||||||
/// @param name The entry name to check.
|
|
||||||
/// @return true if the entry name is valid, otherwise false.
|
|
||||||
bool is_valid_entry_name(const char* name);
|
|
||||||
|
|
||||||
/// @brief Returns the path to the specified entry.
|
|
||||||
/// @param entry The valid entry name.
|
|
||||||
/// @return The path to the specified entry. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* get_entry_path(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Checks if the specified entry exists.
|
|
||||||
/// @param entry The valid entry name.
|
|
||||||
/// @return true if the entry exists, otherwise false.
|
|
||||||
bool entry_exists(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Returns the path to the disk of the specified entry.
|
|
||||||
/// @param entry The valid entry name.
|
|
||||||
/// @return The path to the disk of the specified entry. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* get_entry_disk_path(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Lists the entries.
|
|
||||||
/// @return The list of entries. The caller is responsible for freeing the returned array and strings. This function can return NULL.
|
|
||||||
char** list_entries(void);
|
|
||||||
|
|
||||||
/// @brief Returns the last access time of the specified entry.
|
|
||||||
/// @param entry The valid entry name, which must exist.
|
|
||||||
/// @return The last access time of the specified entry. If the entry does not exist, this function returns 0.
|
|
||||||
uint64_t get_entry_last_access(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Finds the oldest entry (based on last access time).
|
|
||||||
/// @return The oldest entry. The caller is responsible for freeing the returned string. This function can return NULL.
|
|
||||||
char* find_oldest_entry(void);
|
|
||||||
|
|
||||||
/// @brief Creates a new entry.
|
|
||||||
/// @param entry The valid entry name.
|
|
||||||
/// @return true if the entry was created, otherwise false.
|
|
||||||
bool create_entry(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Deletes the specified entry.
|
|
||||||
/// @param entry The valid entry name.
|
|
||||||
/// @return true if the entry was deleted, otherwise false.
|
|
||||||
bool delete_entry(const char* entry);
|
|
||||||
|
|
||||||
/// @brief Returns the available space in the entries directory.
|
|
||||||
/// @return The available space in the entries directory. If the entries directory does not exist, this function returns 0.
|
|
||||||
uint64_t get_available_space(void);
|
|
||||||
|
|
||||||
/// @brief Reserves the specified space in the entries directory, by deleting the oldest entries.
|
|
||||||
/// @param space The space to reserve, in bytes.
|
|
||||||
/// @return true if the space was fully reserved, otherwise false.
|
|
||||||
bool reserve_space(uint64_t space);
|
|
@ -2,30 +2,16 @@
|
|||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include "backing.h"
|
|
||||||
#include "entry.h"
|
|
||||||
#include "disk.h"
|
#include "disk.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdarg.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <libgen.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
char** backings = list_backings();
|
create_empty_disk("test.qcow2", 1024 * 1024 * 1024);
|
||||||
|
create_backed_disk("test2.qcow2", "test.qcow2");
|
||||||
|
rebase_disk("test2.qcow2", "test.qcow2");
|
||||||
|
|
||||||
if (backings == NULL) {
|
return 0;
|
||||||
fprintf(stderr, "Failed to list the backing disks: %s\n", strerror(errno));
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; backings[i] != NULL; i++)
|
|
||||||
printf("%s\n", backings[i]);
|
|
||||||
|
|
||||||
for (size_t i = 0; backings[i] != NULL; i++)
|
|
||||||
free(backings[i]);
|
|
||||||
|
|
||||||
free(backings);
|
|
||||||
}
|
}
|
11
src/sandbox.h
Executable file → Normal file
11
src/sandbox.h
Executable file → Normal file
@ -1,8 +1,3 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MASTER_DIRECTORY "/var/lib/sandbox"
|
int main(int argc, char* argv[]);
|
||||||
|
|
||||||
#define BACKINGS_DIRECTORY "backings"
|
|
||||||
#define ENTRIES_DIRECTORY "entries"
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]);
|
|
730
src/utils.c
Executable file → Normal file
730
src/utils.c
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
80
src/utils.h
Executable file → Normal file
80
src/utils.h
Executable file → Normal file
@ -1,58 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#define MAX_RECURSION_LEVEL 256
|
typedef enum {
|
||||||
|
LOG_DEBUG,
|
||||||
/// @brief The log levels.
|
LOG_INFO,
|
||||||
typedef enum {
|
LOG_WARN,
|
||||||
LOG_DEBUG,
|
LOG_ERROR,
|
||||||
LOG_INFO,
|
} LogLevel;
|
||||||
LOG_WARN,
|
|
||||||
LOG_ERROR,
|
char* format(const char* fmt, ...);
|
||||||
} LogLevel;
|
|
||||||
|
void logmsg(LogLevel level, const char* fmt, ...);
|
||||||
/// @brief Formats a string using the specified format and arguments.
|
|
||||||
/// @param fmt The format string.
|
int exec(char** outb, char** errb, const char* file, ...);
|
||||||
/// @param ... The arguments to use when formatting the string.
|
|
||||||
/// @return The formatted string. The caller is responsible for freeing the returned string. This function can return NULL.
|
char* read_file(int fd);
|
||||||
char* format(const char* fmt, ...);
|
|
||||||
|
bool copy_file(const char* src, const char* dst, mode_t mode);
|
||||||
/// @brief Logs a formatted message to the console.
|
|
||||||
/// @param level The log level.
|
|
||||||
/// @param fmt The format string.
|
|
||||||
/// @param ... The arguments to use when formatting the string.
|
|
||||||
void log_msg(LogLevel level, const char* fmt, ...);
|
|
||||||
|
|
||||||
/// @brief Executes a command and returns the exit code.
|
|
||||||
/// @param stdout_buffer A pointer to a buffer that will receive the standard output of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
|
|
||||||
/// @param stderr_buffer A pointer to a buffer that will receive the standard error of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
|
|
||||||
/// @param file The path to the command to execute.
|
|
||||||
/// @param ... The arguments to pass to the command, followed by a NULL pointer. No need to include the command name in the arguments.
|
|
||||||
/// @return The exit code of the command.
|
|
||||||
int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...);
|
|
||||||
|
|
||||||
/// @brief Creates the specified directory.
|
|
||||||
/// @param directory The directory to create.
|
|
||||||
/// @param mode The mode to use when creating the directory.
|
|
||||||
/// @param uid The user ID to use when creating the directory.
|
|
||||||
/// @param gid The group ID to use when creating the directory.
|
|
||||||
/// @return true if the directory was created, otherwise false.
|
|
||||||
bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid);
|
|
||||||
|
|
||||||
/// @brief Deletes the specified directory and all its contents.
|
|
||||||
/// @param directory The directory to delete.
|
|
||||||
/// @param level The current level of recursion. This parameter is used internally and should be set to 0.
|
|
||||||
/// @return true if the directory was deleted, otherwise false.
|
|
||||||
bool delete_directory(const char* directory, int level);
|
|
||||||
|
|
||||||
/// @brief Copies the specified file to the specified destination.
|
|
||||||
/// @param source The source file to copy.
|
|
||||||
/// @param destination The destination file.
|
|
||||||
/// @param mode The mode to use when creating the destination file.
|
|
||||||
/// @param uid The user ID to use when creating the destination file.
|
|
||||||
/// @param gid The group ID to use when creating the destination file.
|
|
||||||
/// @return true if the file was copied, otherwise false.
|
|
||||||
bool copy_file(const char* source, const char* destination, mode_t mode, uid_t uid, gid_t gid);
|
|
Loading…
Reference in New Issue
Block a user