hbc/channel/channelapp/source/manage.c
2019-01-09 21:40:28 +09:00

562 lines
10 KiB
C

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <malloc.h>
#include <errno.h>
#include <ogcsys.h>
#include "../config.h"
#include "appentry.h"
#include "theme.h"
#include "unzip.h"
#include "dialogs.h"
#include "i18n.h"
#include "panic.h"
#include "manage.h"
s32 dir_exists(char *dirname) {
struct stat st;
if (stat(dirname, &st) != 0)
return 0;
if (S_ISDIR(st.st_mode))
return 1;
gprintf("'%s' exists, but is no directory\n", dirname);
return -1;
}
static s32 mkdir_hier(char *dirname) {
char dir[PATH_MAX];
size_t i;
s32 res;
strcpy(dir, dirname);
if (dir[strlen(dir) - 1] != '/')
strcat(dir, "/");
i = 1;
while (dir[i]) {
if (dir[i] == '/') {
dir[i] = 0;
res = dir_exists(dir);
if (res < 0)
return res;
if (!res) {
gprintf("mkdir '%s'\n", dir);
if (mkdir(dir, 0755)) {
gprintf("mkdir failed: %d\n", errno);
return -1;
}
}
dir[i] = '/';
}
i++;
}
return 0;
}
static s32 rmdir_hier_iter(const char *dirname) {
s32 res = -1;
DIR* d;
struct dirent *de;
gprintf("rmdir_hier_iter '%s'\n", dirname);
d = opendir(dirname);
if (!d) {
gprintf("opendir '%s' failed\n", dirname);
return -1;
}
char newpath[PATH_MAX];
while ((de = readdir(d))) {
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
continue;
strcpy(newpath, dirname);
if (newpath[strlen(newpath) - 1] != '/')
strcat(newpath, "/");
strcat(newpath, de->d_name);
if (de->d_type == DT_DIR) {
res = rmdir_hier_iter(newpath);
if (res)
goto exit;
}
gprintf("unlinking '%s'\n", newpath);
res = unlink(newpath);
if (res) {
gprintf("error unlinking '%s'\n", newpath);
goto exit;
}
}
res = 0;
exit:
closedir(d);
return res;
}
static s32 rmdir_hier(const char *dirname) {
char buf[PATH_MAX];
sprintf(buf, "%s/%s", app_path, dirname);
gprintf("rmdir_hier '%s'\n", buf);
s32 res = rmdir_hier_iter(buf);
if (!res) {
gprintf("unlinking dir '%s'\n", (buf));
res = unlink(buf);
if (res)
gprintf("error unlinking '%s'\n", buf);
}
return res;
}
bool manage_is_zip(const u8 *data) {
return memcmp(data, "PK\x03\x04", 4) == 0;
}
bool manage_check_zip_app(u8 *data, u32 data_len, char *dirname, u32 *bytes) {
unzFile uf;
int res, i;
bool ret = false;
unz_global_info gi;
uf = unzOpen(data, data_len);
if (!uf) {
gprintf("unzOpen failed\n");
return false;
}
res = unzGetGlobalInfo (uf, &gi);
if (res != UNZ_OK) {
gprintf("unzGetGlobalInfo failed: %d\n", res);
goto error;
}
if ((gi.number_entry < 2) || (gi.number_entry > 1024)) {
gprintf("invalid file count\n");
goto error;
}
char filename[256];
unz_file_info fi;
u8 got_elf = 0;
u8 got_dol = 0;
u8 got_theme = 0;
dirname[0] = 0;
*bytes = 0;
for (i = 0; i < gi.number_entry; ++i) {
res = unzGetCurrentFileInfo(uf, &fi, filename, sizeof(filename) ,NULL, 0, NULL, 0);
if (res != UNZ_OK) {
gprintf("unzGetCurrentFileInfo failed: %d\n", res);
goto error;
}
gprintf("found '%s' %lu -> %lu\n", filename, fi.compressed_size, fi.uncompressed_size);
if (filename[0] == '/' || strchr(filename, '\\') || strchr(filename, ':')) {
gprintf("invalid char in filename\n");
goto error;
}
if (fi.flag & 1) {
gprintf("encrypted entry\n");
goto error;
}
if (fi.uncompressed_size > 0) {
*bytes += fi.uncompressed_size;
if (!dirname[0]) {
char *p = strchr(filename, '/');
if (p) {
strncpy(dirname, filename, p - filename + 1);
dirname[p - filename + 1] = 0;
gprintf("dirname='%s'\n", dirname);
} else {
gprintf("missing pathname\n");
goto error;
}
} else {
if (strncmp(filename, dirname, strlen(dirname))) {
gprintf("additional pathname\n");
goto error;
}
}
if (!strcasecmp(filename + strlen(dirname), app_fn_boot_elf))
got_elf = 1;
else if (!strcasecmp(filename + strlen(dirname), app_fn_boot_dol))
got_dol = 1;
else if (!strcasecmp(filename + strlen(dirname), app_fn_theme))
got_theme = 1;
}
if (i != gi.number_entry - 1) {
res = unzGoToNextFile(uf);
if (res != UNZ_OK) {
gprintf("unzGoToNextFile failed: %d\n", res);
goto error;
}
}
}
if (strlen(dirname) && ((got_elf + got_dol + got_theme) == 1)) {
ret = true;
dirname[strlen(dirname) - 1] = 0;
}
error:
res = unzClose(uf);
if (res)
gprintf("unzClose failed: %d\n", res);
return ret;
}
bool manage_check_zip_theme(u8 *data, u32 data_len) {
unzFile uf;
int res, i;
bool ret = false;
unz_global_info gi;
if (data_len > MAX_THEME_ZIP_SIZE) {
gprintf("theme size too big: %u\n", data_len);
return false;
}
uf = unzOpen(data, data_len);
if (!uf) {
gprintf("unzOpen failed\n");
return false;
}
res = unzGetGlobalInfo (uf, &gi);
if (res != UNZ_OK) {
gprintf("unzGetGlobalInfo failed: %d\n", res);
goto error;
}
if ((gi.number_entry < 1) || (gi.number_entry > 64)) {
gprintf("invalid file count\n");
goto error;
}
char filename[256];
unz_file_info fi;
bool got_xml = false;
for (i = 0; i < gi.number_entry; ++i) {
res = unzGetCurrentFileInfo(uf, &fi, filename, sizeof(filename) ,NULL, 0, NULL, 0);
if (res != UNZ_OK) {
gprintf("unzGetCurrentFileInfo failed: %d\n", res);
goto error;
}
gprintf("found '%s' %lu -> %lu\n", filename, fi.compressed_size, fi.uncompressed_size);
if (filename[0] == '/' || strchr(filename, '\\') || strchr(filename, ':')) {
gprintf("invalid char in filename\n");
goto error;
}
if (fi.flag & 1) {
gprintf("encrypted entry\n");
goto error;
}
if (fi.uncompressed_size > 0) {
char *p = strchr(filename, '/');
if (p) {
gprintf("directories not accepted\n");
goto error;
}
if (!strcasecmp(filename, theme_fn_xml)) {
got_xml = true;
} else if (!theme_is_valid_fn(filename)) {
gprintf("not a valid theme filename\n");
goto error;
}
}
if (i != gi.number_entry - 1) {
res = unzGoToNextFile(uf);
if (res != UNZ_OK) {
gprintf("unzGoToNextFile failed: %d\n", res);
goto error;
}
}
}
if (got_xml)
ret = true;
error:
res = unzClose(uf);
if (res)
gprintf("unzClose failed: %d\n", res);
return ret;
}
static bool manage_extract_zip(u8 *data, u32 data_len,
mutex_t *mutex, u32 *progress) {
unzFile uf;
int res, i;
bool ret = false;
unz_global_info gi;
u8 *buf = NULL;
uf = unzOpen(data, data_len);
if (!uf) {
gprintf("unzOpen failed\n");
return false;
}
res = unzGetGlobalInfo (uf, &gi);
if (res != UNZ_OK) {
gprintf("unzGetGlobalInfo failed: %d\n", res);
goto error;
}
unz_file_info fi;
char filename[256];
char sd_filename[PATH_MAX];
char *p;
int fd;
buf = (u8 *) pmalloc(8 * 1024);
for (i = 0; i < gi.number_entry; ++i) {
res = unzGetCurrentFileInfo(uf, &fi, filename, sizeof(filename) ,NULL, 0, NULL, 0);
if (res != UNZ_OK) {
gprintf("unzGetCurrentFileInfo failed: %d\n", res);
goto error;
}
gprintf("extracting '%s' %lu -> %lu\n", filename, fi.compressed_size, fi.uncompressed_size);
if (fi.uncompressed_size > 0) {
res = unzOpenCurrentFile(uf);
if (res != UNZ_OK) {
gprintf("unzOpenCurrentFile failed: %d\n", res);
goto error;
}
sprintf(sd_filename, "%s/%s", app_path, filename);
p = strrchr(sd_filename, '/');
if (!p) {
gprintf("invalid path: %s\n", sd_filename);
goto error;
}
*p = 0;
res = mkdir_hier(sd_filename);
if (res) {
gprintf("mkdir_hier failed: %d\n", res);
goto error;
}
*p = '/';
fd = open(sd_filename, O_CREAT|O_WRONLY|O_TRUNC);
if (fd < 0) {
gprintf("error opening file: %d\n", fd);
goto error;
}
do {
res = unzReadCurrentFile(uf, buf, 8 * 1024);
if (res < 0) {
gprintf("unzReadCurrentFile failed: %d\n", res);
break;
}
if (res > 0) {
if (res != write(fd, buf, res)) {
gprintf("short write");
res = -1;
break;
}
LWP_MutexLock (*mutex);
*progress += res;
LWP_MutexUnlock (*mutex);
}
} while (res > 0);
close(fd);
unzCloseCurrentFile(uf);
if (res < 0) {
gprintf("error extracting file: %d\n", res);
goto error;
}
}
if (i != gi.number_entry - 1) {
res = unzGoToNextFile(uf);
if (res != UNZ_OK) {
gprintf("unzGoToNextFile failed: %d\n", res);
goto error;
}
}
}
ret = true;
error:
free(buf);
res = unzClose(uf);
if (res)
gprintf("unzClose failed: %d\n", res);
return ret;
}
static lwpq_t manage_queue;
static lwp_t manage_thread;
static u8 manage_stack[MANAGE_THREAD_STACKSIZE] ATTRIBUTE_ALIGN (32);
typedef struct {
bool running;
bool extract;
// extract
u8 *data;
u32 data_len;
mutex_t mutex;
u32 progress;
// delete
const char *dirname;
bool success;
} manage_zip_arg;
static void *manage_func (void *arg) {
manage_zip_arg *ta = (manage_zip_arg *) arg;
if (ta->extract)
ta->success = manage_extract_zip(ta->data, ta->data_len, &ta->mutex, &ta->progress);
else
ta->success = 0 == rmdir_hier(ta->dirname);
ta->running = false;
return NULL;
}
bool manage_run(view *sub_view, const char *dirname,
u8 *data, u32 data_len, u32 bytes) {
s32 res;
u32 progress = 0;
char caption[PATH_MAX];
view *v;
manage_zip_arg ta;
res = LWP_MutexInit(&ta.mutex, false);
if (res) {
gprintf ("error creating mutex: %d\n", res);
return false;
}
ta.running = true;
ta.data = data;
ta.data_len = data_len;
ta.progress = 0;
ta.dirname = dirname;
ta.extract = data != NULL;
memset(&manage_stack, 0, MANAGE_THREAD_STACKSIZE);
LWP_InitQueue(&manage_queue);
res = LWP_CreateThread(&manage_thread, manage_func, &ta, manage_stack,
MANAGE_THREAD_STACKSIZE, MANAGE_THREAD_PRIO);
if (res) {
gprintf ("error creating thread: %d\n", res);
LWP_CloseQueue(manage_queue);
return false;
}
const char *text;
if (data) {
text = _("Extracting");
sprintf(caption, "%s '%s'...", text, dirname);
v = dialog_progress(sub_view, caption, bytes);
dialog_fade(v, true);
} else {
v = sub_view;
view_show_throbber(true);
}
while (true) {
LWP_MutexLock(ta.mutex);
if (!ta.running) {
LWP_MutexUnlock(ta.mutex);
break;
}
progress = ta.progress;
LWP_MutexUnlock (ta.mutex);
if (data)
dialog_set_progress(v, progress);
else
view_throbber_tickle();
view_plot(v, DIALOG_MASK_COLOR, NULL, NULL, NULL);
}
if (data) {
dialog_fade(v, false);
view_free(v);
} else {
view_show_throbber(false);
}
//LWP_SuspendThread(manage_thread);
LWP_CloseQueue(manage_queue);
LWP_MutexDestroy(ta.mutex);
return ta.success;
}