From f64f39c9c8ee7957cf4eb0b8c7022a6cc4df954e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexe=C3=AF=20KADIR?= <alexei.kadir@gmail.com>
Date: Thu, 15 Feb 2024 12:50:30 +0100
Subject: [PATCH] Implemented some more functions

---
 src/backing.c | 125 ++++++++++++++++++++++++++++++++++++++++++++---
 src/backing.h |   3 +-
 src/disk.c    |   4 +-
 src/entry.c   | 131 +++++++++++++++++++++++++++++++++++++++++++++++---
 src/entry.h   |  14 +++---
 src/sandbox.c |   7 ++-
 src/utils.c   |  37 +++++++++++---
 src/utils.h   |  14 +++++-
 8 files changed, 306 insertions(+), 29 deletions(-)

diff --git a/src/backing.c b/src/backing.c
index 91e884f..773d256 100644
--- a/src/backing.c
+++ b/src/backing.c
@@ -7,6 +7,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
 #include <sys/stat.h>
 
 char* get_backings_path(void) {
@@ -22,10 +24,16 @@ bool is_valid_backing_name(const char* name) {
 	if (length == 0)
 		return false;
 
-	// Check that the name is a number (the number is the timestamp of the backing disk creation)
+	// 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')
-			return false;
+		if (name[i] >= '0' && name[i] <= '9')
+			timestamp_length++;
+		else
+			break;
+
+	if (timestamp_length == 0)
+		return false;
 
 	return true;
 }
@@ -65,7 +73,112 @@ bool backing_exists(const char* backing) {
 	return true;
 }
 
-uint64_t get_backing_creation_time(const char* backing) {
-	// A valid backing name is a number, corresponding to the timestamp of the backing disk creation
-	return strtoull(backing, NULL, 10);
+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;
 }
diff --git a/src/backing.h b/src/backing.h
index e698774..962e5f0 100644
--- a/src/backing.h
+++ b/src/backing.h
@@ -37,5 +37,6 @@ char* find_latest_backing(void);
 
 /// @brief Creates a new backing disk.
 /// @param backing_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* backing_disk);
\ No newline at end of file
+bool create_backing(const char* backing_disk, const char* description);
\ No newline at end of file
diff --git a/src/disk.c b/src/disk.c
index a855a92..d121134 100644
--- a/src/disk.c
+++ b/src/disk.c
@@ -12,7 +12,7 @@ bool create_empty_disk(const char* disk, uint64_t size) {
 	char* stderrb = NULL;
 
 	// Create an empty disk
-	int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", disk, format("%" PRIu64, size), NULL);
+	int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", disk, format("%" PRIu64, size), NULL);
 
 	// Check if the command was successful
 	if (result != 0) {
@@ -40,7 +40,7 @@ bool create_backed_disk(const char* disk, const char* backing) {
 	char* stderrb = NULL;
 
 	// Create a backed disk
-	int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL);
+	int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL);
 
 	// Check if the command was successful
 	if (result != 0) {
diff --git a/src/entry.c b/src/entry.c
index 474d2bf..62ede8e 100644
--- a/src/entry.c
+++ b/src/entry.c
@@ -5,10 +5,15 @@
 
 #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);
@@ -70,21 +75,135 @@ bool entry_exists(const char* entry) {
 	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
-	int result = mkdir(entry_path, 0755);
+	bool result = create_directory(entry_path, 0700, 0, 0);
 
 	// Free the entry path
 	free(entry_path);
 
-	if (result != 0)
-		return false;
-
-	return true;
+	return result;
 }
 
 bool delete_entry(const char* entry) {
@@ -93,7 +212,7 @@ bool delete_entry(const char* entry) {
 		return false;
 
 	// Delete the entry directory
-	bool result = delete_directory(entry_path);
+	bool result = delete_directory(entry_path, 0);
 
 	// Free the entry path
 	free(entry_path);
diff --git a/src/entry.h b/src/entry.h
index a0f4f48..95024cc 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -3,6 +3,8 @@
 #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);
@@ -22,11 +24,16 @@ char* get_entry_path(const char* entry);
 /// @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 Get the last access time of the specified entry.
+/// @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);
@@ -45,11 +52,6 @@ bool create_entry(const char* entry);
 /// @return true if the entry was deleted, otherwise false.
 bool delete_entry(const char* entry);
 
-/// @brief Updates the last access time of the specified entry.
-/// @param entry The valid entry name, which must exist.
-/// @return true if the last access time was updated, otherwise false.
-bool update_entry_last_access(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);
diff --git a/src/sandbox.c b/src/sandbox.c
index 027de13..fbfb88e 100644
--- a/src/sandbox.c
+++ b/src/sandbox.c
@@ -10,7 +10,12 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <libgen.h>
+#include <string.h>
+#include <errno.h>
 
 int main(int argc, char* argv[]) {
-    log_msg(LOG_INFO, "Available space : %lld", get_available_space());
+	while (1) {
+		char* oldest_entry = find_oldest_entry();
+		free(oldest_entry);
+	}
 }
\ No newline at end of file
diff --git a/src/utils.c b/src/utils.c
index 2e13013..c8a8d0e 100755
--- a/src/utils.c
+++ b/src/utils.c
@@ -7,6 +7,7 @@
 #include <time.h>
 #include <unistd.h>
 #include <dirent.h>
+#include <errno.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -270,11 +271,32 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
 	return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
 }
 
-bool delete_directory(const char* directory) {
+bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid) {
+	// Create the directory
+	if (mkdir(directory, mode) != 0) {
+		log_msg(LOG_ERROR, "Failed to create directory '%s' (%s).", directory, strerror(errno));
+		return false;
+	}
+
+	// Change the owner of the directory
+	if (chown(directory, uid, gid) != 0) {
+		log_msg(LOG_ERROR, "Failed to change the owner of directory '%s' (%s).", directory, strerror(errno));
+		return false;
+	}
+
+	return true;
+}
+
+bool delete_directory(const char* directory, int level) {
+	if (level > MAX_RECURSION_LEVEL) {
+		log_msg(LOG_ERROR, "Too many levels of recursion when deleting directory '%s'.", directory);
+		return false;
+	}
+
 	// Open the directory
 	DIR* dir = opendir(directory);
 	if (dir == NULL) {
-		log_msg(LOG_ERROR, "Failed to open directory '%s'.", directory);
+		log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", directory, strerror(errno));
 		return false;
 	}
 
@@ -297,7 +319,7 @@ bool delete_directory(const char* directory) {
 		struct stat statbuf;
 		int result = stat(entry_path, &statbuf);
 		if (result != 0) {
-			log_msg(LOG_ERROR, "Failed to get information about entry '%s'.", entry_path);
+			log_msg(LOG_ERROR, "Failed to get information about file '%s' (%s).", entry_path, strerror(errno));
 			free(entry_path);
 			closedir(dir);
 			return false;
@@ -305,7 +327,9 @@ bool delete_directory(const char* directory) {
 
 		if (S_ISDIR(statbuf.st_mode)) {
 			// Delete the directory
-			if (!delete_directory(entry_path)) {
+			if (!delete_directory(entry_path, level + 1)) {
+				log_msg(LOG_ERROR, "Failed to delete directory '%s'.", entry_path);
+
 				free(entry_path);
 				closedir(dir);
 				return false;
@@ -313,7 +337,8 @@ bool delete_directory(const char* directory) {
 		} else {
 			// Delete the file
 			if (unlink(entry_path) != 0) {
-				log_msg(LOG_ERROR, "Failed to delete entry '%s'.", entry_path);
+				log_msg(LOG_ERROR, "Failed to delete file '%s' (%s).", entry_path, strerror(errno));
+
 				free(entry_path);
 				closedir(dir);
 				return false;
@@ -328,7 +353,7 @@ bool delete_directory(const char* directory) {
 
 	// Delete the directory
 	if (rmdir(directory) != 0) {
-		log_msg(LOG_ERROR, "Failed to delete directory '%s'.", directory);
+		log_msg(LOG_ERROR, "Failed to delete directory '%s' (%s).", directory, strerror(errno));
 		return false;
 	}
 
diff --git a/src/utils.h b/src/utils.h
index c8df5d4..c31ff7a 100755
--- a/src/utils.h
+++ b/src/utils.h
@@ -2,6 +2,9 @@
 
 #include <stdbool.h>
 #include <stdint.h>
+#include <sys/types.h>
+
+#define MAX_RECURSION_LEVEL 256
 
 /// @brief The log levels.
 typedef enum {
@@ -31,7 +34,16 @@ void log_msg(LogLevel level, const char* fmt, ...);
 /// @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);
+bool delete_directory(const char* directory, int level);