RetroArch/core_backup.c
2020-09-01 01:28:33 +02:00

768 lines
20 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2014-2017 - Jean-André Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2019-2020 - James Leaver
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <string/stdstring.h>
#include <lists/string_list.h>
#include <file/file_path.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <lists/dir_list.h>
#include <time/rtime.h>
#include <retro_miscellaneous.h>
#include "frontend/frontend_driver.h"
#include "file_path_special.h"
#include "verbosity.h"
#include "core_backup.h"
/* Holds all entries in a core backup list */
struct core_backup_list
{
core_backup_list_entry_t *entries;
size_t size;
size_t capacity;
};
/*********************/
/* Utility Functions */
/*********************/
/* Generates backup directory path for specified core.
* Returns false if 'core_path' and/or 'dir_core_assets'
* are invalid, or a filesystem error occurs */
static bool core_backup_get_backup_dir(
const char *dir_libretro, const char *dir_core_assets,
const char *core_filename,
char *backup_dir, size_t len)
{
char *last_underscore = NULL;
char core_file_id[PATH_MAX_LENGTH];
char tmp[PATH_MAX_LENGTH];
core_file_id[0] = '\0';
tmp[0] = '\0';
/* Extract core file 'ID' (name without extension + suffix)
* from core path */
if (string_is_empty(dir_libretro) ||
string_is_empty(core_filename) ||
(len < 1))
return false;
strlcpy(core_file_id, core_filename, sizeof(core_file_id));
/* > Remove file extension */
path_remove_extension(core_file_id);
if (string_is_empty(core_file_id))
return false;
/* > Remove platform-specific file name suffix,
* if required */
last_underscore = strrchr(core_file_id, '_');
if (!string_is_empty(last_underscore))
if (!string_is_equal(last_underscore, "_libretro"))
*last_underscore = '\0';
if (string_is_empty(core_file_id))
return false;
/* Get core backup directory
* > If no assets directory is defined, use
* core directory as a base */
fill_pathname_join(tmp, string_is_empty(dir_core_assets) ?
dir_libretro : dir_core_assets,
"core_backups", sizeof(tmp));
fill_pathname_join(backup_dir, tmp,
core_file_id, len);
if (string_is_empty(backup_dir))
return false;
/* > Create directory, if required */
if (!path_is_directory(backup_dir))
{
if (!path_mkdir(backup_dir))
{
RARCH_ERR("[core backup] Failed to create backup directory: %s.\n", backup_dir);
return false;
}
}
return true;
}
/* Generates a timestamped core backup file path from
* the specified core path. Returns true if successful */
bool core_backup_get_backup_path(
const char *core_path, uint32_t crc, enum core_backup_mode backup_mode,
const char *dir_core_assets, char *backup_path, size_t len)
{
int n;
time_t current_time;
struct tm time_info;
const char *core_filename = NULL;
char core_dir[PATH_MAX_LENGTH];
char backup_dir[PATH_MAX_LENGTH];
char backup_filename[PATH_MAX_LENGTH];
core_dir[0] = '\0';
backup_dir[0] = '\0';
backup_filename[0] = '\0';
/* Get core filename and parent directory */
if (string_is_empty(core_path))
return false;
core_filename = path_basename(core_path);
if (string_is_empty(core_filename))
return false;
fill_pathname_parent_dir(core_dir, core_path, sizeof(core_dir));
if (string_is_empty(core_dir))
return false;
/* Get backup directory */
if (!core_backup_get_backup_dir(core_dir, dir_core_assets, core_filename,
backup_dir, sizeof(backup_dir)))
return false;
/* Get current time */
time(&current_time);
rtime_localtime(&current_time, &time_info);
/* Generate backup filename */
n = snprintf(backup_filename, sizeof(backup_filename),
"%s.%04u%02u%02uT%02u%02u%02u.%08x.%u%s",
core_filename,
(unsigned)time_info.tm_year + 1900,
(unsigned)time_info.tm_mon + 1,
(unsigned)time_info.tm_mday,
(unsigned)time_info.tm_hour,
(unsigned)time_info.tm_min,
(unsigned)time_info.tm_sec,
crc,
(unsigned)backup_mode,
FILE_PATH_CORE_BACKUP_EXTENSION);
if ((n < 0) || (n >= 128))
n = 0; /* Silence GCC warnings... */
/* Build final path */
fill_pathname_join(backup_path, backup_dir,
backup_filename, len);
return true;
}
/* Returns detected type of specified core backup file */
enum core_backup_type core_backup_get_backup_type(const char *backup_path)
{
const char *backup_ext = NULL;
struct string_list *metadata_list = NULL;
char core_ext[255];
core_ext[0] = '\0';
if (string_is_empty(backup_path) || !path_is_valid(backup_path))
goto error;
/* Get backup file extension */
backup_ext = path_get_extension(backup_path);
if (string_is_empty(backup_ext))
goto error;
/* Get platform-specific dynamic library extension */
if (!frontend_driver_get_core_extension(core_ext, sizeof(core_ext)))
goto error;
/* Check if this is an archived backup */
if (string_is_equal_noncase(backup_ext,
FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT))
{
const char *backup_filename = NULL;
const char *src_ext = NULL;
/* Split the backup filename into its various
* metadata components */
backup_filename = path_basename(backup_path);
if (string_is_empty(backup_filename))
goto error;
metadata_list = string_split(backup_filename, ".");
if (!metadata_list || (metadata_list->size != 6))
goto error;
/* Get extension of source core file */
src_ext = metadata_list->elems[1].data;
if (string_is_empty(src_ext))
goto error;
/* Check whether extension is valid */
if (!string_is_equal_noncase(src_ext, core_ext))
goto error;
string_list_free(metadata_list);
metadata_list = NULL;
return CORE_BACKUP_TYPE_ARCHIVE;
}
/* Check if this is a plain dynamic library file */
if (string_is_equal_noncase(backup_ext, core_ext))
return CORE_BACKUP_TYPE_LIB;
error:
if (metadata_list)
{
string_list_free(metadata_list);
metadata_list = NULL;
}
return CORE_BACKUP_TYPE_INVALID;
}
/* Fetches crc value of specified core backup file.
* Returns true if successful */
bool core_backup_get_backup_crc(char *backup_path, uint32_t *crc)
{
struct string_list *metadata_list = NULL;
enum core_backup_type backup_type;
if (string_is_empty(backup_path) || !crc)
goto error;
/* Get backup type */
backup_type = core_backup_get_backup_type(backup_path);
switch (backup_type)
{
case CORE_BACKUP_TYPE_ARCHIVE:
{
const char *backup_filename = NULL;
const char *crc_str = NULL;
/* Split the backup filename into its various
* metadata components */
backup_filename = path_basename(backup_path);
if (string_is_empty(backup_filename))
goto error;
metadata_list = string_split(backup_filename, ".");
if (!metadata_list || (metadata_list->size != 6))
goto error;
/* Get crc string */
crc_str = metadata_list->elems[3].data;
if (string_is_empty(crc_str))
goto error;
/* Convert to an integer */
*crc = (uint32_t)string_hex_to_unsigned(crc_str);
if (*crc == 0)
goto error;
string_list_free(metadata_list);
metadata_list = NULL;
}
return true;
case CORE_BACKUP_TYPE_LIB:
{
intfstream_t *backup_file = NULL;
/* This is a plain dynamic library file,
* have to read file data to determine crc */
/* Open backup file */
backup_file = intfstream_open_file(
backup_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (backup_file)
{
bool success;
/* Get crc value */
success = intfstream_get_crc(backup_file, crc);
/* Close backup file */
intfstream_close(backup_file);
free(backup_file);
backup_file = NULL;
return success;
}
}
break;
default:
/* Backup is invalid */
break;
}
error:
if (metadata_list)
{
string_list_free(metadata_list);
metadata_list = NULL;
}
return false;
}
/* Fetches core path associated with specified core
* backup file. Returns detected type of backup
* file - CORE_BACKUP_TYPE_INVALID indicates that
* backup file cannot be restored/installed, or
* arguments are otherwise invalid */
enum core_backup_type core_backup_get_core_path(
const char *backup_path, const char *dir_libretro,
char *core_path, size_t len)
{
const char *backup_filename = NULL;
char *core_filename = NULL;
enum core_backup_type backup_type = CORE_BACKUP_TYPE_INVALID;
if (string_is_empty(backup_path) || string_is_empty(dir_libretro))
return backup_type;
backup_filename = path_basename(backup_path);
if (string_is_empty(backup_filename))
return backup_type;
/* Check backup type */
switch (core_backup_get_backup_type(backup_path))
{
case CORE_BACKUP_TYPE_ARCHIVE:
{
char *period = NULL;
/* This is an archived backup with timestamp/crc
* metadata in the filename */
core_filename = strdup(backup_filename);
/* Find the location of the second period */
period = strchr(core_filename, '.');
if (!period || (*(++period) == '\0'))
break;
period = strchr(period, '.');
if (!period)
break;
/* Trim everything after (and including) the
* second period */
*period = '\0';
if (string_is_empty(core_filename))
break;
/* All good - build core path */
fill_pathname_join(core_path, dir_libretro,
core_filename, len);
backup_type = CORE_BACKUP_TYPE_ARCHIVE;
}
break;
case CORE_BACKUP_TYPE_LIB:
/* This is a plain dynamic library file */
fill_pathname_join(core_path, dir_libretro,
backup_filename, len);
backup_type = CORE_BACKUP_TYPE_LIB;
break;
default:
/* Backup is invalid */
break;
}
if (core_filename)
{
free(core_filename);
core_filename = NULL;
}
return backup_type;
}
/*************************/
/* Backup List Functions */
/*************************/
/**************************************/
/* Initialisation / De-Initialisation */
/**************************************/
/* Parses backup file name and adds to backup list, if valid */
static bool core_backup_add_entry(core_backup_list_t *backup_list,
const char *core_filename, const char *backup_path)
{
char *backup_filename = NULL;
core_backup_list_entry_t *entry = NULL;
unsigned backup_mode = 0;
if (!backup_list ||
string_is_empty(core_filename) ||
string_is_empty(backup_path) ||
(backup_list->size >= backup_list->capacity))
goto error;
backup_filename = strdup(path_basename(backup_path));
if (string_is_empty(backup_filename))
goto error;
/* Ensure base backup filename matches core */
if (!string_starts_with(backup_filename, core_filename))
goto error;
/* Remove backup file extension */
path_remove_extension(backup_filename);
/* Parse backup filename metadata
* - <core_filename>.<timestamp>.<crc>.<backup_mode>
* - timestamp: YYYYMMDDTHHMMSS */
entry = &backup_list->entries[backup_list->size];
if (sscanf(backup_filename + strlen(core_filename),
".%04u%02u%02uT%02u%02u%02u.%08x.%u",
&entry->date.year, &entry->date.month, &entry->date.day,
&entry->date.hour, &entry->date.minute, &entry->date.second,
&entry->crc, &backup_mode) != 8)
goto error;
entry->backup_mode = (enum core_backup_mode)backup_mode;
/* Cache backup path */
entry->backup_path = strdup(backup_path);
backup_list->size++;
free(backup_filename);
return true;
error:
if (backup_filename)
free(backup_filename);
return false;
}
/* Creates a new core backup list containing entries
* for all existing backup files.
* Returns a handle to a new core_backup_list_t object
* on success, otherwise returns NULL. */
core_backup_list_t *core_backup_list_init(
const char *core_path, const char *dir_core_assets)
{
size_t i;
const char *core_filename = NULL;
struct string_list *dir_list = NULL;
core_backup_list_t *backup_list = NULL;
core_backup_list_entry_t *entries = NULL;
char core_dir[PATH_MAX_LENGTH];
char backup_dir[PATH_MAX_LENGTH];
core_dir[0] = '\0';
backup_dir[0] = '\0';
/* Get core filename and parent directory */
if (string_is_empty(core_path))
goto error;
core_filename = path_basename(core_path);
if (string_is_empty(core_filename))
goto error;
fill_pathname_parent_dir(core_dir, core_path, sizeof(core_dir));
if (string_is_empty(core_dir))
goto error;
/* Get backup directory */
if (!core_backup_get_backup_dir(core_dir, dir_core_assets, core_filename,
backup_dir, sizeof(backup_dir)))
goto error;
/* Get backup file list */
dir_list = dir_list_new(
backup_dir,
FILE_PATH_CORE_BACKUP_EXTENSION,
false, /* include_dirs */
false, /* include_hidden */
false, /* include_compressed */
false /* recursive */
);
/* Sanity check */
if (!dir_list)
goto error;
if (dir_list->size < 1)
goto error;
/* Ensure list is sorted in alphabetical order
* > This corresponds to 'timestamp' order */
dir_list_sort(dir_list, true);
/* Create core backup list */
backup_list = (core_backup_list_t*)malloc(sizeof(*backup_list));
if (!backup_list)
goto error;
backup_list->entries = NULL;
backup_list->capacity = 0;
backup_list->size = 0;
/* Create entries array
* (Note: Set this to the full size of the directory
* list - this may be larger than we need, but saves
* many inefficiencies later) */
entries = (core_backup_list_entry_t*)
calloc(dir_list->size, sizeof(*entries));
if (!entries)
goto error;
backup_list->entries = entries;
backup_list->capacity = dir_list->size;
/* Loop over backup files and parse file names */
for (i = 0; i < dir_list->size; i++)
{
const char *backup_path = dir_list->elems[i].data;
core_backup_add_entry(backup_list, core_filename, backup_path);
}
if (backup_list->size == 0)
goto error;
string_list_free(dir_list);
return backup_list;
error:
if (dir_list)
string_list_free(dir_list);
if (backup_list)
core_backup_list_free(backup_list);
return NULL;
}
/* Frees specified core backup list */
void core_backup_list_free(core_backup_list_t *backup_list)
{
size_t i;
if (!backup_list)
return;
if (backup_list->entries)
{
for (i = 0; i < backup_list->size; i++)
{
core_backup_list_entry_t *entry = &backup_list->entries[i];
if (!entry)
continue;
if (entry->backup_path)
{
free(entry->backup_path);
entry->backup_path = NULL;
}
}
free(backup_list->entries);
backup_list->entries = NULL;
}
free(backup_list);
}
/***********/
/* Getters */
/***********/
/* Returns number of entries in core backup list */
size_t core_backup_list_size(core_backup_list_t *backup_list)
{
if (!backup_list)
return 0;
return backup_list->size;
}
/* Returns number of entries of specified 'backup mode'
* (manual or automatic) in core backup list */
size_t core_backup_list_get_num_backups(
core_backup_list_t *backup_list,
enum core_backup_mode backup_mode)
{
size_t i;
size_t num_backups = 0;
if (!backup_list || !backup_list->entries)
return 0;
for (i = 0; i < backup_list->size; i++)
{
core_backup_list_entry_t *current_entry = &backup_list->entries[i];
if (current_entry &&
(current_entry->backup_mode == backup_mode))
num_backups++;
}
return num_backups;
}
/* Fetches core backup list entry corresponding
* to the specified entry index.
* Returns false if index is invalid. */
bool core_backup_list_get_index(
core_backup_list_t *backup_list,
size_t idx,
const core_backup_list_entry_t **entry)
{
if (!backup_list || !backup_list->entries || !entry)
return false;
if (idx >= backup_list->size)
return false;
*entry = &backup_list->entries[idx];
if (*entry)
return true;
return false;
}
/* Fetches core backup list entry corresponding
* to the specified core crc checksum value.
* Note that 'manual' and 'auto' backups are
* considered independent - we only compare
* crc values for the specified backup_mode.
* Returns false if entry is not found. */
bool core_backup_list_get_crc(
core_backup_list_t *backup_list,
uint32_t crc, enum core_backup_mode backup_mode,
const core_backup_list_entry_t **entry)
{
size_t i;
if (!backup_list || !backup_list->entries || !entry)
return false;
for (i = 0; i < backup_list->size; i++)
{
core_backup_list_entry_t *current_entry = &backup_list->entries[i];
if (current_entry &&
(current_entry->crc == crc) &&
(current_entry->backup_mode == backup_mode))
{
*entry = current_entry;
return true;
}
}
return false;
}
/* Fetches a string representation of a backup
* list entry timestamp.
* Returns false in the event of an error */
bool core_backup_list_get_entry_timestamp_str(
const core_backup_list_entry_t *entry,
enum core_backup_date_separator_type date_separator,
char *timestamp, size_t len)
{
int n;
const char *format_str = "";
if (!entry || (len < 20))
return false;
/* Get time format string */
switch (date_separator)
{
case CORE_BACKUP_DATE_SEPARATOR_SLASH:
format_str = "%04u/%02u/%02u %02u:%02u:%02u";
break;
case CORE_BACKUP_DATE_SEPARATOR_PERIOD:
format_str = "%04u.%02u.%02u %02u:%02u:%02u";
break;
default:
format_str = "%04u-%02u-%02u %02u:%02u:%02u";
break;
}
n = snprintf(timestamp, len,
format_str,
entry->date.year,
entry->date.month,
entry->date.day,
entry->date.hour,
entry->date.minute,
entry->date.second);
if ((n < 0) || (n >= 32))
n = 0; /* Silence GCC warnings... */
return true;
}
/* Fetches a string representation of a backup
* list entry crc value.
* Returns false in the event of an error */
bool core_backup_list_get_entry_crc_str(
const core_backup_list_entry_t *entry,
char *crc, size_t len)
{
int n;
if (!entry || (len < 9))
return false;
n = snprintf(crc, len, "%08x", entry->crc);
if ((n < 0) || (n >= 32))
n = 0; /* Silence GCC warnings... */
return true;
}