restore / common.c
hotboxxgenn's picture
Upload 181 files
3db5480 verified
/*
* common.c
* Misc functions used in idevicerestore
*
* Copyright (c) 2012-2019 Nikias Bassen. All Rights Reserved.
* Copyright (c) 2012 Martin Szulecki. All Rights Reserved.
* Copyright (c) 2010 Joshua Hill. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <libgen.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#ifdef WIN32
#include <windows.h>
#include <conio.h>
#ifndef _O_EXCL
#define _O_EXCL 0x0400
#endif
#ifndef O_EXCL
#define O_EXCL _O_EXCL
#endif
#else
#include <sys/time.h>
#include <pthread.h>
#include <termios.h>
#endif
#include "common.h"
#include "endianness.h"
#define MAX_PRINT_LEN 64*1024
struct idevicerestore_mode_t idevicerestore_modes[] = {
{ 0, "WTF" },
{ 1, "DFU" },
{ 2, "Recovery" },
{ 3, "Restore" },
{ 4, "Normal" },
{ -1, NULL }
};
int idevicerestore_debug = 0;
#define idevicerestore_err_buff_size 256
static char idevicerestore_err_buff[idevicerestore_err_buff_size] = {0, };
static FILE* info_stream = NULL;
static FILE* error_stream = NULL;
static FILE* debug_stream = NULL;
static int info_disabled = 0;
static int error_disabled = 0;
static int debug_disabled = 0;
void info(const char* format, ...)
{
if (info_disabled) return;
va_list vargs;
va_start(vargs, format);
vfprintf((info_stream) ? info_stream : stdout, format, vargs);
va_end(vargs);
}
void error(const char* format, ...)
{
va_list vargs, vargs2;
va_start(vargs, format);
va_copy(vargs2, vargs);
vsnprintf(idevicerestore_err_buff, idevicerestore_err_buff_size, format, vargs);
va_end(vargs);
if (!error_disabled) {
vfprintf((error_stream) ? error_stream : stderr, format, vargs2);
}
va_end(vargs2);
}
void debug(const char* format, ...)
{
if (debug_disabled) return;
if (!idevicerestore_debug) {
return;
}
va_list vargs;
va_start(vargs, format);
vfprintf((debug_stream) ? debug_stream : stderr, format, vargs);
va_end(vargs);
}
void idevicerestore_set_info_stream(FILE* strm)
{
if (strm) {
info_disabled = 0;
info_stream = strm;
} else {
info_disabled = 1;
}
}
void idevicerestore_set_error_stream(FILE* strm)
{
if (strm) {
error_disabled = 0;
error_stream = strm;
} else {
error_disabled = 1;
}
}
void idevicerestore_set_debug_stream(FILE* strm)
{
if (strm) {
debug_disabled = 0;
debug_stream = strm;
} else {
debug_disabled = 1;
}
}
const char* idevicerestore_get_error(void)
{
if (idevicerestore_err_buff[0] == 0) {
return NULL;
} else {
char* p = NULL;
while ((strlen(idevicerestore_err_buff) > 0) && (p = strrchr(idevicerestore_err_buff, '\n'))) {
p[0] = '\0';
}
return (const char*)idevicerestore_err_buff;
}
}
int write_file(const char* filename, const void* data, size_t size) {
size_t bytes = 0;
FILE* file = NULL;
debug("Writing data to %s\n", filename);
file = fopen(filename, "wb");
if (file == NULL) {
error("write_file: Unable to open file %s\n", filename);
return -1;
}
bytes = fwrite(data, 1, size, file);
fclose(file);
if (bytes != size) {
error("ERROR: Unable to write entire file: %s: %d of %d\n", filename, (int)bytes, (int)size);
return -1;
}
return size;
}
int read_file(const char* filename, void** data, size_t* size) {
size_t bytes = 0;
size_t length = 0;
FILE* file = NULL;
char* buffer = NULL;
struct stat fst;
debug("Reading data from %s\n", filename);
*size = 0;
*data = NULL;
file = fopen(filename, "rb");
if (file == NULL) {
error("read_file: cannot open %s: %s\n", filename, strerror(errno));
return -1;
}
if (fstat(fileno(file), &fst) < 0) {
error("read_file: fstat: %s\n", strerror(errno));
return -1;
}
length = fst.st_size;
buffer = (char*) malloc(length);
if (buffer == NULL) {
error("ERROR: Out of memory\n");
fclose(file);
return -1;
}
bytes = fread(buffer, 1, length, file);
fclose(file);
if (bytes != length) {
error("ERROR: Unable to read entire file\n");
free(buffer);
return -1;
}
*size = length;
*data = buffer;
return 0;
}
void debug_plist(plist_t plist) {
uint32_t size = 0;
char* data = NULL;
plist_to_xml(plist, &data, &size);
if (size <= MAX_PRINT_LEN)
info("%s:printing %i bytes plist:\n%s", __FILE__, size, data);
else
info("%s:supressed printing %i bytes plist...\n", __FILE__, size);
free(data);
}
void print_progress_bar(double progress) {
#ifndef WIN32
if (info_disabled) return;
int i = 0;
if(progress < 0) return;
if(progress > 100) progress = 100;
info("\r[");
for(i = 0; i < 50; i++) {
if(i < progress / 2) info("=");
else info(" ");
}
info("] %5.1f%%", progress);
if(progress >= 100) info("\n");
fflush((info_stream) ? info_stream : stdout);
#endif
}
#define GET_RAND(min, max) ((rand() % (max - min)) + min)
char *generate_guid(void)
{
char *guid = (char *) malloc(sizeof(char) * 37);
const char *chars = "ABCDEF0123456789";
srand(time(NULL));
int i = 0;
for (i = 0; i < 36; i++) {
if (i == 8 || i == 13 || i == 18 || i == 23) {
guid[i] = '-';
continue;
} else {
guid[i] = chars[GET_RAND(0, 16)];
}
}
guid[36] = '\0';
return guid;
}
int mkdir_with_parents(const char *dir, int mode)
{
if (!dir) return -1;
if (__mkdir(dir, mode) == 0) {
return 0;
} else {
if (errno == EEXIST) {
return 0;
} else if (errno == ENOENT) {
// ignore
} else {
return -1;
}
}
int res;
char *parent = strdup(dir);
char *parentdir = dirname(parent);
if (parentdir && (strcmp(parentdir, ".") != 0) && (strcmp(parentdir, dir) != 0)) {
res = mkdir_with_parents(parentdir, mode);
} else {
res = -1;
}
free(parent);
if (res == 0) {
mkdir_with_parents(dir, mode);
}
return res;
}
#ifndef HAVE_MKSTEMP
/* Based on libc's __gen_tempname() from sysdeps/posix/tempname.c
Copyright (C) 1991-2018 Free Software Foundation, Inc.
With changes from https://stackoverflow.com/a/6036308 and some
additional changes. */
int mkstemp(char *tmpl)
{
static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int len;
char *XXXXXX;
static unsigned long long value;
unsigned long long random_time_bits;
unsigned int count;
int fd = -1;
int save_errno = errno;
/* A lower bound on the number of temporary files to attempt to
generate. The maximum total number of temporary file names that
can exist for a given template is 62**6. It should never be
necessary to try all these combinations. Instead if a reasonable
number of names is tried (we define reasonable as 62**3) fail to
give the system administrator the chance to remove the problems. */
#define ATTEMPTS_MIN (62 * 62 * 62)
/* The number of times to attempt to generate a temporary file. To
conform to POSIX, this must be no smaller than TMP_MAX. */
#if ATTEMPTS_MIN < TMP_MAX
unsigned int attempts = TMP_MAX;
#else
unsigned int attempts = ATTEMPTS_MIN;
#endif
len = strlen (tmpl);
if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
{
errno = EINVAL;
return -1;
}
/* This is where the Xs start. */
XXXXXX = &tmpl[len - 6];
/* Get some more or less random data. */
#ifdef WIN32
{
SYSTEMTIME stNow;
FILETIME ftNow;
// get system time
GetSystemTime(&stNow);
if (!SystemTimeToFileTime(&stNow, &ftNow))
{
errno = -1;
return -1;
}
random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32)
| (unsigned long long)ftNow.dwLowDateTime);
}
value += random_time_bits ^ ((unsigned long long)GetCurrentProcessId() << 32 | (unsigned long long)GetCurrentThreadId());
#else
{
struct timeval tvNow = {0, 0};
gettimeofday(&tvNow, NULL);
random_time_bits = (((unsigned long long)tvNow.tv_sec << 32)
| (unsigned long long)tvNow.tv_usec);
}
value += random_time_bits ^ ((unsigned long long)getpid() << 32 | (unsigned long long)(uintptr_t)pthread_self());
#endif
for (count = 0; count < attempts; value += 7777, ++count)
{
unsigned long long v = value;
/* Fill in the random bits. */
XXXXXX[0] = letters[v % 62];
v /= 62;
XXXXXX[1] = letters[v % 62];
v /= 62;
XXXXXX[2] = letters[v % 62];
v /= 62;
XXXXXX[3] = letters[v % 62];
v /= 62;
XXXXXX[4] = letters[v % 62];
v /= 62;
XXXXXX[5] = letters[v % 62];
#ifdef WIN32
fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE);
#else
fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
#endif
if (fd >= 0)
{
errno = save_errno;
return fd;
}
else if (errno != EEXIST)
return -1;
}
/* We got out of the loop because we ran out of combinations to try. */
errno = EEXIST;
return -1;
}
#endif
char *get_temp_filename(const char *prefix)
{
char *result = NULL;
char *tmpdir;
size_t lt;
size_t lp;
const char *TMPVARS[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", NULL };
int i = 0;
int fd;
/* check the prefix parameter */
if (!prefix) {
prefix = "tmp_";
}
#ifdef WIN32
if (strchr(prefix, '/') || strchr(prefix, '\\')) return NULL;
#else
if (strchr(prefix, '/')) return NULL;
#endif
while (TMPVARS[i] && ((tmpdir = getenv(TMPVARS[i])) == NULL)) i++;
if (!tmpdir || access(tmpdir, W_OK|X_OK) != 0) {
#ifdef WIN32
tmpdir = "C:\\WINDOWS\\TEMP";
#else
tmpdir = P_tmpdir;
#endif
}
if (!tmpdir || access(tmpdir, W_OK|X_OK) != 0) {
return NULL;
}
lt = strlen(tmpdir);
if (lt < 1) {
return NULL;
}
lp = strlen(prefix);
result = malloc(lt + lp + 8);
memcpy(result, tmpdir, lt);
#ifdef WIN32
if (tmpdir[lt-1] != '/' && tmpdir[lt-1] != '\\') result[lt++] = '\\';
#else
if (tmpdir[lt-1] != '/') result[lt++] = '/';
#endif
strncpy(result + lt, prefix, lp);
strcpy(result + lt + lp, "XXXXXX");
fd = mkstemp(result);
if (fd < 0) {
free(result);
result = NULL;
}
close(fd);
return result;
}
void idevicerestore_progress(struct idevicerestore_client_t* client, int step, double progress)
{
if(client && client->progress_cb) {
client->progress_cb(step, progress, client->progress_cb_data);
} else {
// we don't want to be too verbose in regular idevicerestore.
if ((step == RESTORE_STEP_UPLOAD_FS) || (step == RESTORE_STEP_VERIFY_FS) || (step == RESTORE_STEP_FLASH_FW)) {
print_progress_bar(100.0 * progress);
}
}
}
#ifndef HAVE_STRSEP
char* strsep(char** strp, const char* delim)
{
char *p, *s;
if (strp == NULL || *strp == NULL || **strp == '\0') return NULL;
s = *strp;
p = s + strcspn(s, delim);
if (*p != '\0') *p++ = '\0';
*strp = p;
return s;
}
#endif
#ifndef HAVE_REALPATH
char* realpath(const char *filename, char *resolved_name)
{
#ifdef WIN32
if (access(filename, F_OK) != 0) {
return NULL;
}
if (GetFullPathName(filename, MAX_PATH, resolved_name, NULL) == 0) {
return NULL;
}
return resolved_name;
#else
#error please provide a realpath implementation for this platform
return NULL;
#endif
}
#endif
#ifdef WIN32
#define BS_CC '\b'
#define CTRL_C_CC 0x03
#define ESC_CC 0x1B
#define my_getch _getch
#else
#define BS_CC 0x7f
static int my_getch(void)
{
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
#endif
void get_user_input(char *buf, int maxlen, int secure)
{
int len = 0;
int c;
while ((c = my_getch()) > 0) {
if ((c == '\r') || (c == '\n')) {
break;
} else if (isprint(c)) {
if (len < maxlen-1)
buf[len++] = c;
fputc((secure) ? '*' : c, stdout);
} else if (c == BS_CC) {
if (len > 0) {
fputs("\b \b", stdout);
len--;
}
}
#ifdef WIN32
else if (c == CTRL_C_CC || c == ESC_CC) {
c = -1;
break;
}
#endif
}
if (c < 0) {
len = 0;
}
fputs("\n", stdout);
buf[len] = 0;
}
uint64_t _plist_dict_get_uint(plist_t dict, const char *key)
{
uint64_t uintval = 0;
char *strval = NULL;
uint64_t strsz = 0;
plist_t node = plist_dict_get_item(dict, key);
if (!node) {
return (uint64_t)-1LL;
}
switch (plist_get_node_type(node)) {
case PLIST_UINT:
plist_get_uint_val(node, &uintval);
break;
case PLIST_STRING:
plist_get_string_val(node, &strval);
if (strval) {
uintval = strtoull(strval, NULL, 0);
free(strval);
}
break;
case PLIST_DATA:
plist_get_data_val(node, &strval, &strsz);
if (strval) {
if (strsz == 8) {
uintval = le64toh(*(uint64_t*)strval);
} else if (strsz == 4) {
uintval = le32toh(*(uint32_t*)strval);
} else if (strsz == 2) {
uintval = le16toh(*(uint16_t*)strval);
} else if (strsz == 1) {
uintval = strval[0];
} else {
error("%s: ERROR: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz);
}
free(strval);
}
break;
default:
break;
}
return uintval;
}
uint8_t _plist_dict_get_bool(plist_t dict, const char *key)
{
uint8_t bval = 0;
uint64_t uintval = 0;
char *strval = NULL;
uint64_t strsz = 0;
plist_t node = plist_dict_get_item(dict, key);
if (!node) {
return 0;
}
switch (plist_get_node_type(node)) {
case PLIST_BOOLEAN:
plist_get_bool_val(node, &bval);
break;
case PLIST_UINT:
plist_get_uint_val(node, &uintval);
bval = (uint8_t)uintval;
break;
case PLIST_STRING:
plist_get_string_val(node, &strval);
if (strval) {
if (strcmp(strval, "true")) {
bval = 1;
} else if (strcmp(strval, "false")) {
bval = 0;
}
free(strval);
}
break;
case PLIST_DATA:
plist_get_data_val(node, &strval, &strsz);
if (strval) {
if (strsz == 1) {
bval = strval[0];
} else {
error("%s: ERROR: invalid size %" PRIu64 " for data to boolean conversion\n", __func__, strsz);
}
free(strval);
}
break;
default:
break;
}
return bval;
}