diff options
Diffstat (limited to 'tools/idevicesyslog.c')
| -rw-r--r-- | tools/idevicesyslog.c | 1332 |
1 files changed, 1236 insertions, 96 deletions
diff --git a/tools/idevicesyslog.c b/tools/idevicesyslog.c index f2b3a2f..88af4c1 100644 --- a/tools/idevicesyslog.c +++ b/tools/idevicesyslog.c @@ -2,169 +2,1309 @@ * idevicesyslog.c * Relay the syslog of a device to stdout * + * Copyright (c) 2010-2020 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define TOOL_NAME "idevicesyslog" + #include <stdio.h> #include <string.h> #include <errno.h> #include <signal.h> #include <stdlib.h> -#include <glib.h> +#include <unistd.h> +#include <getopt.h> +#include <time.h> + +#ifdef _WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif #include <libimobiledevice/libimobiledevice.h> -#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/syslog_relay.h> +#include <libimobiledevice-glue/termcolors.h> +#include <libimobiledevice/ostrace.h> static int quit_flag = 0; +static int exit_on_disconnect = 0; +static int show_device_name = 0; +static int force_syslog_relay = 0; -void print_usage(int argc, char **argv); +static char* udid = NULL; +static char** proc_filters = NULL; +static int num_proc_filters = 0; +static int proc_filter_excluding = 0; -/** - * signal handler function for cleaning up properly - */ -static void clean_exit(int sig) +static int* pid_filters = NULL; +static int num_pid_filters = 0; + +static char** msg_filters = NULL; +static int num_msg_filters = 0; + +static char** msg_reverse_filters = NULL; +static int num_msg_reverse_filters = 0; + +static char** trigger_filters = NULL; +static int num_trigger_filters = 0; +static char** untrigger_filters = NULL; +static int num_untrigger_filters = 0; +static int triggered = 0; + +static idevice_t device = NULL; +static syslog_relay_client_t syslog = NULL; +static ostrace_client_t ostrace = NULL; + +static const char QUIET_FILTER[] = "CircleJoinRequested|CommCenter|HeuristicInterpreter|MobileMail|PowerUIAgent|ProtectedCloudKeySyncing|SpringBoard|UserEventAgent|WirelessRadioManagerd|accessoryd|accountsd|aggregated|analyticsd|appstored|apsd|assetsd|assistant_service|backboardd|biometrickitd|bluetoothd|calaccessd|callservicesd|cloudd|com.apple.Safari.SafeBrowsing.Service|contextstored|corecaptured|coreduetd|corespeechd|cdpd|dasd|dataaccessd|distnoted|dprivacyd|duetexpertd|findmydeviced|fmfd|fmflocatord|gpsd|healthd|homed|identityservicesd|imagent|itunescloudd|itunesstored|kernel|locationd|maild|mDNSResponder|mediaremoted|mediaserverd|mobileassetd|nanoregistryd|nanotimekitcompaniond|navd|nsurlsessiond|passd|pasted|photoanalysisd|powerd|powerlogHelperd|ptpd|rapportd|remindd|routined|runningboardd|searchd|sharingd|suggestd|symptomsd|timed|thermalmonitord|useractivityd|vmd|wifid|wirelessproxd"; + +static int use_network = 0; + +static long long start_time = -1; +static long long size_limit = -1; +static long long age_limit = -1; + +static char *line = NULL; +static int line_buffer_size = 0; +static int lp = 0; + +static void add_filter(const char* filterstr) { - fprintf(stderr, "Exiting...\n"); - quit_flag++; + int filter_len = strlen(filterstr); + const char* start = filterstr; + const char* end = filterstr + filter_len; + const char* p = start; + while (p <= end) { + if ((*p == '|') || (*p == '\0')) { + if (p-start > 0) { + char* procn = malloc(p-start+1); + if (!procn) { + fprintf(stderr, "ERROR: malloc() failed\n"); + exit(EXIT_FAILURE); + } + memcpy(procn, start, p-start); + procn[p-start] = '\0'; + char* endp = NULL; + int pid_value = (int)strtol(procn, &endp, 10); + if (!endp || *endp == 0) { + int *new_pid_filters = realloc(pid_filters, sizeof(int) * (num_pid_filters+1)); + if (!new_pid_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + pid_filters = new_pid_filters; + pid_filters[num_pid_filters] = pid_value; + num_pid_filters++; + } else { + char **new_proc_filters = realloc(proc_filters, sizeof(char*) * (num_proc_filters+1)); + if (!new_proc_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + proc_filters = new_proc_filters; + proc_filters[num_proc_filters] = procn; + num_proc_filters++; + } + } + start = p+1; + } + p++; + } } -int main(int argc, char *argv[]) +static int find_char(char c, char** p, const char* end) { - lockdownd_client_t client = NULL; - idevice_t phone = NULL; - idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - int i; - char uuid[41]; - uint16_t port = 0; - uuid[0] = 0; + while ((**p != c) && (*p < end)) { + (*p)++; + } + return (**p == c); +} - signal(SIGINT, clean_exit); - signal(SIGQUIT, clean_exit); - signal(SIGTERM, clean_exit); - signal(SIGPIPE, SIG_IGN); +static void stop_logging(void); - /* parse cmdline args */ - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { - idevice_set_debug_level(1); - continue; - } - else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { - i++; - if (!argv[i] || (strlen(argv[i]) != 40)) { - print_usage(argc, argv); - return 0; +static int message_filter_matching(const char* message) +{ + if (num_msg_filters > 0) { + int found = 0; + int i; + for (i = 0; i < num_msg_filters; i++) { + if (strstr(message, msg_filters[i])) { + found = 1; + break; } - strcpy(uuid, argv[i]); - continue; } - else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - print_usage(argc, argv); + if (!found) { return 0; } - else { - print_usage(argc, argv); + } + if (num_msg_reverse_filters > 0) { + int found = 0; + int i; + for (i = 0; i < num_msg_reverse_filters; i++) { + if (strstr(message, msg_reverse_filters[i])) { + found = 1; + break; + } + } + if (found) { return 0; } } + return 1; +} - if (uuid[0] != 0) { - ret = idevice_new(&phone, uuid); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found with uuid %s, is it plugged in?\n", uuid); - return -1; +static int process_filter_matching(int pid, const char* process_name, int process_name_length) +{ + int proc_matched = 0; + if (num_pid_filters > 0) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_pid_filters; i++) { + if (pid == pid_filters[i]) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; } } - else - { - ret = idevice_new(&phone, NULL); - if (ret != IDEVICE_E_SUCCESS) { - printf("No device found, is it plugged in?\n"); - return -1; + if (num_proc_filters > 0 && !proc_matched) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!proc_filters[i]) continue; + if (strncmp(proc_filters[i], process_name, process_name_length) == 0) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; } } + return proc_matched; +} - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicesyslog")) { - idevice_free(phone); - return -1; +static void syslog_callback(char c, void *user_data) +{ + if (lp >= line_buffer_size-1) { + line_buffer_size+=1024; + char* _line = realloc(line, line_buffer_size); + if (!_line) { + fprintf(stderr, "ERROR: realloc failed\n"); + exit(EXIT_FAILURE); + } + line = _line; } + line[lp++] = c; + if (c == '\0') { + int shall_print = 0; + int trigger_off = 0; + lp--; + char* linep = &line[0]; + do { + if (lp < 16) { + shall_print = 1; + cprintf(FG_WHITE); + break; + } - /* start syslog_relay service and retrieve port */ - ret = lockdownd_start_service(client, "com.apple.syslog_relay", &port); - if ((ret == LOCKDOWN_E_SUCCESS) && port) { - lockdownd_client_free(client); - - /* connect to socket relay messages */ - idevice_connection_t conn = NULL; - if ((idevice_connect(phone, port, &conn) != IDEVICE_E_SUCCESS) || !conn) { - printf("ERROR: Could not open usbmux connection.\n"); - } else { - while (!quit_flag) { - char *receive = NULL; - uint32_t datalen = 0, bytes = 0, recv_bytes = 0; + if (line[3] == ' ' && line[6] == ' ' && line[15] == ' ') { + char* end = &line[lp]; + char* p = &line[16]; + + /* device name */ + char* device_name_start = p; + char* device_name_end = p; + if (!find_char(' ', &p, end)) break; + device_name_end = p; + p++; - ret = idevice_connection_receive(conn, (char *) &datalen, sizeof(datalen), &bytes); - if (ret < 0) { - fprintf(stderr, "Error receiving data. Exiting...\n"); + /* check if we have any triggers/untriggers */ + if (num_untrigger_filters > 0 && triggered) { + int found = 0; + int i; + for (i = 0; i < num_untrigger_filters; i++) { + if (strstr(device_name_end+1, untrigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 1; + } else { + shall_print = 1; + trigger_off = 1; + } + } else if (num_trigger_filters > 0 && !triggered) { + int found = 0; + int i; + for (i = 0; i < num_trigger_filters; i++) { + if (strstr(device_name_end+1, trigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } + triggered = 1; + shall_print = 1; + } else if (num_trigger_filters == 0 && num_untrigger_filters > 0 && !triggered) { + shall_print = 0; + quit_flag++; break; } - datalen = GUINT32_FROM_BE(datalen); + /* check message filters */ + shall_print = message_filter_matching(device_name_end+1); + if (!shall_print) { + break; + } - if (datalen == 0) - continue; + /* process name */ + char* proc_name_start = p; + char* proc_name_end = p; + if (!find_char('[', &p, end)) break; + char* process_name_start = proc_name_start; + char* process_name_end = p; + char* pid_start = p+1; + char* pp = process_name_start; + if (find_char('(', &pp, p)) { + process_name_end = pp; + } + if (!find_char(']', &p, end)) break; + p++; + if (*p != ' ') break; + proc_name_end = p; + p++; - recv_bytes += bytes; - receive = (char *) malloc(sizeof(char) * datalen); + /* match pid / process name */ + char* endp = NULL; + int pid_value = (int)strtol(pid_start, &endp, 10); + if (process_filter_matching(pid_value, process_name_start, process_name_end-process_name_start)) { + shall_print = 1; + } else { + if (num_pid_filters > 0 || num_proc_filters > 0) { + shall_print = 0; + break; + } + } - while (!quit_flag && (recv_bytes <= datalen)) { - ret = idevice_connection_receive(conn, receive, datalen, &bytes); + /* log level */ + char* level_start = p; + char* level_end = p; + const char* level_color = NULL; + if (!strncmp(p, "<Notice>:", 9)) { + level_end += 9; + level_color = FG_GREEN; + } else if (!strncmp(p, "<Error>:", 8)) { + level_end += 8; + level_color = FG_RED; + } else if (!strncmp(p, "<Warning>:", 10)) { + level_end += 10; + level_color = FG_YELLOW; + } else if (!strncmp(p, "<Debug>:", 8)) { + level_end += 8; + level_color = FG_MAGENTA; + } else { + level_color = FG_WHITE; + } - if (bytes == 0) - break; + /* write date and time */ + cprintf(FG_LIGHT_GRAY); + fwrite(line, 1, 16, stdout); + + if (show_device_name) { + /* write device name */ + cprintf(FG_DARK_YELLOW); + fwrite(device_name_start, 1, device_name_end-device_name_start+1, stdout); + cprintf(COLOR_RESET); + } - recv_bytes += bytes; + /* write process name */ + cprintf(FG_BRIGHT_CYAN); + fwrite(process_name_start, 1, process_name_end-process_name_start, stdout); + cprintf(FG_CYAN); + fwrite(process_name_end, 1, proc_name_end-process_name_end+1, stdout); - fwrite(receive, sizeof(char), bytes, stdout); + /* write log level */ + cprintf(level_color); + if (level_end > level_start) { + fwrite(level_start, 1, level_end-level_start, stdout); + p = level_end; } - free(receive); + lp -= p - linep; + linep = p; + + cprintf(FG_WHITE); + + } else { + shall_print = 1; + cprintf(FG_WHITE); + } + } while (0); + + if ((num_msg_filters == 0 && num_msg_reverse_filters == 0 && num_proc_filters == 0 && num_pid_filters == 0 && num_trigger_filters == 0 && num_untrigger_filters == 0) || shall_print) { + fwrite(linep, 1, lp, stdout); + cprintf(COLOR_RESET); + fflush(stdout); + if (trigger_off) { + triggered = 0; + } + } + line[0] = '\0'; + lp = 0; + return; + } +} + +static void ostrace_syslog_callback(const void* buf, size_t len, void* user_data) +{ + if (len < 0x81) { + fprintf(stderr, "Error: not enough data in callback function?!\n"); + return; + } + + struct ostrace_packet_header_t *trace_hdr = (struct ostrace_packet_header_t*)buf; + + if (trace_hdr->marker != 2 || (trace_hdr->type != 8 && trace_hdr->type != 2)) { + fprintf(stderr, "unexpected packet data %02x %08x\n", trace_hdr->marker, trace_hdr->type); + } + + const char* dataptr = (const char*)buf + trace_hdr->header_size; + const char* process_name = dataptr; + const char* image_name = (trace_hdr->imagepath_len > 0) ? dataptr + trace_hdr->procpath_len : NULL; + const char* message = (trace_hdr->message_len > 0) ? dataptr + trace_hdr->procpath_len + trace_hdr->imagepath_len : NULL; + //const char* subsystem = (trace_hdr->subsystem_len > 0) ? dataptr + trace_hdr->procpath_len + trace_hdr->imagepath_len + trace_hdr->message_len : NULL; + //const char* category = (trace_hdr->category_len > 0) ? dataptr + trace_hdr->procpath_len + trace_hdr->imagepath_len + trace_hdr->message_len + trace_hdr->subsystem_len : NULL; + + int shall_print = 1; + int trigger_off = 0; + const char* process_name_short = (process_name) ? strrchr(process_name, '/') : ""; + process_name_short = (process_name_short) ? process_name_short+1 : process_name; + const char* image_name_short = (image_name) ? strrchr(image_name, '/') : NULL; + image_name_short = (image_name_short) ? image_name_short+1 : process_name; + if (image_name_short && !strcmp(image_name_short, process_name_short)) { + image_name_short = NULL; + } + + do { + /* check if we have any triggers/untriggers */ + if (num_untrigger_filters > 0 && triggered) { + int found = 0; + int i; + for (i = 0; i < num_untrigger_filters; i++) { + if (strstr(message, untrigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 1; + } else { + shall_print = 1; + trigger_off = 1; + } + } else if (num_trigger_filters > 0 && !triggered) { + int found = 0; + int i; + for (i = 0; i < num_trigger_filters; i++) { + if (strstr(message, trigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } + triggered = 1; + shall_print = 1; + } else if (num_trigger_filters == 0 && num_untrigger_filters > 0 && !triggered) { + shall_print = 0; + quit_flag++; + break; + } + + /* check message filters */ + shall_print = message_filter_matching(message); + if (!shall_print) { + break; + } + + /* check process filters */ + if (process_filter_matching(trace_hdr->pid, process_name_short, strlen(process_name_short))) { + shall_print = 1; + } else { + if (num_pid_filters > 0 || num_proc_filters > 0) { + shall_print = 0; + } + } + if (!shall_print) { + break; + } + } while (0); + + if (!shall_print) { + return; + } + + const char* level_str = "Unknown"; + const char* level_color = FG_YELLOW; + switch (trace_hdr->level) { + case 0: + level_str = "Notice"; + level_color = FG_GREEN; + break; + case 0x01: + level_str = "Info"; + level_color = FG_WHITE; + break; + case 0x02: + level_str = "Debug"; + level_color = FG_MAGENTA; + break; + case 0x10: + level_str = "Error"; + level_color = FG_RED; + break; + case 0x11: + level_str = "Fault"; + level_color = FG_RED; + default: + break; + } + + char datebuf[24]; + struct tm *tp; + time_t time_sec = (time_t)trace_hdr->time_sec; +#ifdef HAVE_LOCALTIME_R + struct tm tp_ = {0, }; + tp = localtime_r(&time_sec, &tp_); +#else + tp = localtime(&time_sec); +#endif +#ifdef _WIN32 + strftime(datebuf, 16, "%b %#d %H:%M:%S", tp); +#else + strftime(datebuf, 16, "%b %e %H:%M:%S", tp); +#endif + snprintf(datebuf+15, 9, ".%06u", trace_hdr->time_usec); + + /* write date and time */ + cprintf(FG_LIGHT_GRAY "%s ", datebuf); + + if (show_device_name) { + /* write device name TODO do we need this? */ + //cprintf(FG_DARK_YELLOW "%s ", device_name); + } + + /* write process name */ + cprintf(FG_BRIGHT_CYAN "%s" FG_CYAN, process_name_short); + if (image_name_short) { + cprintf("(%s)", image_name_short); + } + cprintf("[%d]" COLOR_RESET " ", trace_hdr->pid); + + /* write log level */ + cprintf(level_color); + cprintf("<%s>:" COLOR_RESET " ", level_str); + + /* write message */ + cprintf(FG_WHITE); + cprintf("%s" COLOR_RESET "\n", message); + fflush(stdout); + + if (trigger_off) { + triggered = 0; + } +} + +static plist_t get_pid_list() +{ + plist_t list = NULL; + ostrace_client_t ostrace_tmp = NULL; + ostrace_client_start_service(device, &ostrace_tmp, TOOL_NAME); + if (ostrace_tmp) { + ostrace_get_pid_list(ostrace_tmp, &list); + ostrace_client_free(ostrace_tmp); + } + return list; +} + +static int pid_valid(int pid) +{ + plist_t list = get_pid_list(); + if (!list) return 0; + char valbuf[16]; + snprintf(valbuf, 16, "%d", pid); + return (plist_dict_get_item(list, valbuf)) ? 1 : 0; +} + +static int pid_for_proc(const char* procname) +{ + int result = -1; + plist_t list = get_pid_list(); + if (!list) { + return result; + } + plist_dict_iter iter = NULL; + plist_dict_new_iter(list, &iter); + if (iter) { + plist_t node = NULL; + do { + char* key = NULL; + node = NULL; + plist_dict_next_item(list, iter, &key, &node); + if (!key) { + break; + } + if (PLIST_IS_DICT(node)) { + plist_t pname = plist_dict_get_item(node, "ProcessName"); + if (PLIST_IS_STRING(pname)) { + if (!strcmp(plist_get_string_ptr(pname, NULL), procname)) { + result = (int)strtol(key, NULL, 10); + } + } + } + free(key); + } while (node); + plist_mem_free(iter); + } + plist_free(list); + return result; +} + +static int connect_service(int ostrace_required) +{ + if (!device) { + idevice_error_t ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + fprintf(stderr, "Device with udid %s not found!?\n", udid); + return -1; + } + } + + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr); + idevice_free(device); + device = NULL; + return -1; + } + lockdownd_service_descriptor_t svc = NULL; + + const char* service_name = OSTRACE_SERVICE_NAME; + int use_ostrace = 1; + if (idevice_get_device_version(device) < IDEVICE_DEVICE_VERSION(9,0,0) || force_syslog_relay) { + service_name = SYSLOG_RELAY_SERVICE_NAME; + use_ostrace = 0; + } + if (ostrace_required && !use_ostrace) { + fprintf(stderr, "ERROR: This operation requires iOS 9 or later.\n"); + lockdownd_client_free(lockdown); + idevice_free(device); + device = NULL; + return -1; + } + + /* start syslog_relay/os_trace_relay service */ + lerr = lockdownd_start_service(lockdown, service_name, &svc); + if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) { + fprintf(stderr, "*** Device is passcode protected, enter passcode on the device to continue ***\n"); + while (!quit_flag) { + lerr = lockdownd_start_service(lockdown, service_name, &svc); + if (lerr != LOCKDOWN_E_PASSWORD_PROTECTED) { + break; + } + sleep(1); + } + } + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not start %s service: %s (%d)\n", service_name, lockdownd_strerror(lerr), lerr); + idevice_free(device); + device = NULL; + return -1; + } + lockdownd_client_free(lockdown); + + if (use_ostrace) { + /* connect to os_trace_relay service */ + ostrace_error_t serr = OSTRACE_E_UNKNOWN_ERROR; + serr = ostrace_client_new(device, svc, &ostrace); + lockdownd_service_descriptor_free(svc); + if (serr != OSTRACE_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to %s service (%d)\n", service_name, serr); + idevice_free(device); + device = NULL; + return -1; + } + } else { + /* connect to syslog_relay service */ + syslog_relay_error_t serr = SYSLOG_RELAY_E_UNKNOWN_ERROR; + serr = syslog_relay_client_new(device, svc, &syslog); + lockdownd_service_descriptor_free(svc); + if (serr != SYSLOG_RELAY_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not connect to %s service (%d)\n", service_name, serr); + idevice_free(device); + device = NULL; + return -1; + } + } + return 0; +} + +static int start_logging(void) +{ + if (connect_service(0) < 0) { + return -1; + } + + /* start capturing syslog */ + if (ostrace) { + plist_t options = plist_new_dict(); + if (num_proc_filters == 0 && num_pid_filters == 1 && !proc_filter_excluding) { + if (pid_filters[0] > 0) { + if (!pid_valid(pid_filters[0])) { + fprintf(stderr, "NOTE: A process with pid doesn't exists!\n"); + } + } + plist_dict_set_item(options, "Pid", plist_new_int(pid_filters[0])); + } else if (num_proc_filters == 1 && num_pid_filters == 0 && !proc_filter_excluding) { + int pid = pid_for_proc(proc_filters[0]); + if (!strcmp(proc_filters[0], "kernel")) { + pid = 0; } + if (pid >= 0) { + plist_dict_set_item(options, "Pid", plist_new_int(pid)); + } + } + ostrace_error_t serr = ostrace_start_activity(ostrace, options, ostrace_syslog_callback, NULL); + if (serr != OSTRACE_E_SUCCESS) { + fprintf(stderr, "ERROR: Unable to start capturing syslog.\n"); + ostrace_client_free(ostrace); + ostrace = NULL; + idevice_free(device); + device = NULL; + return -1; + } + } else if (syslog) { + syslog_relay_error_t serr = syslog_relay_start_capture_raw(syslog, syslog_callback, NULL); + if (serr != SYSLOG_RELAY_E_SUCCESS) { + fprintf(stderr, "ERROR: Unable to start capturing syslog.\n"); + syslog_relay_client_free(syslog); + syslog = NULL; + idevice_free(device); + device = NULL; + return -1; } - idevice_disconnect(conn); } else { - printf("ERROR: Could not start service com.apple.syslog_relay.\n"); + return -1; } - idevice_free(phone); + fprintf(stdout, "[connected:%s]\n", udid); + fflush(stdout); return 0; } -void print_usage(int argc, char **argv) +static void stop_logging(void) { - char *name = NULL; - - name = strrchr(argv[0], '/'); - printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); - printf("Relay syslog of a connected iPhone/iPod Touch.\n\n"); - printf(" -d, --debug\t\tenable communication debugging\n"); - printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); - printf(" -h, --help\t\tprints usage information\n"); - printf("\n"); + fflush(stdout); + + if (syslog) { + syslog_relay_client_free(syslog); + syslog = NULL; + } + if (ostrace) { + ostrace_stop_activity(ostrace); + ostrace_client_free(ostrace); + ostrace = NULL; + } + + if (device) { + idevice_free(device); + device = NULL; + } +} + +static int write_callback(const void* buf, size_t len, void *user_data) +{ + FILE* f = (FILE*)user_data; + ssize_t res = fwrite(buf, 1, len, f); + if (res < 0) { + return -1; + } + if (quit_flag > 0) { + return -1; + } + return 0; +} + +static void print_sorted_pidlist(plist_t list) +{ + struct listelem; + struct listelem { + int val; + struct listelem *next; + }; + struct listelem* sortedlist = NULL; + + plist_dict_iter iter = NULL; + plist_dict_new_iter(list, &iter); + if (iter) { + plist_t node = NULL; + do { + char* key = NULL; + node = NULL; + plist_dict_next_item(list, iter, &key, &node); + if (key) { + int pidval = (int)strtol(key, NULL, 10); + struct listelem* elem = (struct listelem*)malloc(sizeof(struct listelem)); + elem->val = pidval; + elem->next = NULL; + struct listelem* prev = NULL; + struct listelem* curr = sortedlist; + + while (curr && pidval > curr->val) { + prev = curr; + curr = curr->next; + } + + elem->next = curr; + if (prev == NULL) { + sortedlist = elem; + } else { + prev->next = elem; + } + free(key); + } + } while (node); + plist_mem_free(iter); + } + struct listelem *listp = sortedlist; + char pidstr[16]; + while (listp) { + snprintf(pidstr, 16, "%d", listp->val); + plist_t node = plist_dict_get_item(list, pidstr); + if (PLIST_IS_DICT(node)) { + plist_t pname = plist_dict_get_item(node, "ProcessName"); + if (PLIST_IS_STRING(pname)) { + printf("%d %s\n", listp->val, plist_get_string_ptr(pname, NULL)); + } + } + struct listelem *curr = listp; + listp = listp->next; + free(curr); + } +} + +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } + if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!syslog && !ostrace) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + if (start_logging() != 0) { + fprintf(stderr, "Could not start logger for udid %s\n", udid); + } + } + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if ((syslog || ostrace) && (strcmp(udid, event->udid) == 0)) { + stop_logging(); + fprintf(stdout, "[disconnected:%s]\n", udid); + if (exit_on_disconnect) { + quit_flag++; + } + } + } } +/** + * signal handler function for cleaning up properly + */ +static void clean_exit(int sig) +{ + fprintf(stderr, "\nExiting...\n"); + quit_flag++; +} + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Relay syslog of a connected device.\n" + "\n" + "OPTIONS:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -x, --exit exit when device disconnects\n" + " -h, --help prints usage information\n" + " -d, --debug enable communication debugging\n" + " -v, --version prints version information\n" + " --no-colors disable colored output\n" + " -o, --output FILE write to FILE instead of stdout\n" + " (existing FILE will be overwritten)\n" + " --colors force writing colored output, e.g. for --output\n" + " --syslog-relay force use of syslog_relay service\n" + "\n" + "COMMANDS:\n" + " pidlist Print pid and name of all running processes.\n" + " archive PATH Request a logarchive and write it to PATH.\n" + " Output can be piped to another process using - as PATH.\n" + " The file data will be in .tar format.\n" + " --start-time VALUE start time of the log data as UNIX timestamp\n" + " --age-limit VALUE maximum age of the log data\n" + " --size-limit VALUE limit the size of the archive\n" + "\n" + "FILTER OPTIONS:\n" + " -m, --match STRING only print messages that contain STRING\n" + " -M, --unmatch STRING print messages that not contain STRING\n" + " -t, --trigger STRING start logging when matching STRING\n" + " -T, --untrigger STRING stop logging when matching STRING\n" + " -p, --process PROCESS only print messages from matching process(es)\n" + " -e, --exclude PROCESS print all messages except matching process(es)\n" + " PROCESS is a process name or multiple process names\n" + " separated by \"|\".\n" + " -q, --quiet set a filter to exclude common noisy processes\n" + " --quiet-list prints the list of processes for --quiet and exits\n" + " -k, --kernel only print kernel messages\n" + " -K, --no-kernel suppress kernel messages\n" + "\n" + "For filter examples consult idevicesyslog(1) man page.\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +int main(int argc, char *argv[]) +{ + int include_filter = 0; + int exclude_filter = 0; + int include_kernel = 0; + int exclude_kernel = 0; + int force_colors = 0; + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "exit", no_argument, NULL, 'x' }, + { "trigger", required_argument, NULL, 't' }, + { "untrigger", required_argument, NULL, 'T' }, + { "match", required_argument, NULL, 'm' }, + { "process", required_argument, NULL, 'p' }, + { "exclude", required_argument, NULL, 'e' }, + { "quiet", no_argument, NULL, 'q' }, + { "kernel", no_argument, NULL, 'k' }, + { "no-kernel", no_argument, NULL, 'K' }, + { "quiet-list", no_argument, NULL, 1 }, + { "no-colors", no_argument, NULL, 2 }, + { "colors", no_argument, NULL, 3 }, + { "syslog_relay", no_argument, NULL, 4 }, + { "syslog-relay", no_argument, NULL, 4 }, + { "legacy", no_argument, NULL, 4 }, + { "start-time", required_argument, NULL, 5 }, + { "size-limit", required_argument, NULL, 6 }, + { "age-limit", required_argument, NULL, 7 }, + { "output", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + + signal(SIGINT, clean_exit); + signal(SIGTERM, clean_exit); +#ifndef _WIN32 + signal(SIGQUIT, clean_exit); + signal(SIGPIPE, SIG_IGN); +#endif + + while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:M:e:p:qkKo:v", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + free(udid); + udid = strdup(optarg); + break; + case 'n': + use_network = 1; + break; + case 'q': + exclude_filter++; + add_filter(QUIET_FILTER); + break; + case 'p': + case 'e': + if (c == 'p') { + include_filter++; + } else if (c == 'e') { + exclude_filter++; + } + if (!*optarg) { + fprintf(stderr, "ERROR: filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + add_filter(optarg); + break; + case 'm': + if (!*optarg) { + fprintf(stderr, "ERROR: message filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_msg_filters = realloc(msg_filters, sizeof(char*) * (num_msg_filters+1)); + if (!new_msg_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + msg_filters = new_msg_filters; + msg_filters[num_msg_filters] = strdup(optarg); + num_msg_filters++; + } + break; + case 'M': + if (!*optarg) { + fprintf(stderr, "ERROR: reverse message filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_msg_filters = realloc(msg_reverse_filters, sizeof(char*) * (num_msg_reverse_filters+1)); + if (!new_msg_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + msg_reverse_filters = new_msg_filters; + msg_reverse_filters[num_msg_reverse_filters] = strdup(optarg); + num_msg_reverse_filters++; + } + break; + case 't': + if (!*optarg) { + fprintf(stderr, "ERROR: trigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_trigger_filters = realloc(trigger_filters, sizeof(char*) * (num_trigger_filters+1)); + if (!new_trigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + trigger_filters = new_trigger_filters; + trigger_filters[num_trigger_filters] = strdup(optarg); + num_trigger_filters++; + } + break; + case 'T': + if (!*optarg) { + fprintf(stderr, "ERROR: untrigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_untrigger_filters = realloc(untrigger_filters, sizeof(char*) * (num_untrigger_filters+1)); + if (!new_untrigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + untrigger_filters = new_untrigger_filters; + untrigger_filters[num_untrigger_filters] = strdup(optarg); + num_untrigger_filters++; + } + break; + case 'k': + include_kernel++; + break; + case 'K': + exclude_kernel++; + break; + case 'x': + exit_on_disconnect = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 1: { + printf("%s\n", QUIET_FILTER); + return 0; + } + case 2: + term_colors_set_enabled(0); + break; + case 3: + force_colors = 1; + break; + case 4: + force_syslog_relay = 1; + break; + case 5: + start_time = strtoll(optarg, NULL, 10); + break; + case 6: + size_limit = strtoll(optarg, NULL, 10); + break; + case 7: + age_limit = strtoll(optarg, NULL, 10); + break; + case 'o': + if (!*optarg) { + fprintf(stderr, "ERROR: --output option requires an argument!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + if (freopen(optarg, "w", stdout) == NULL) { + fprintf(stderr, "ERROR: Failed to open output file '%s' for writing: %s\n", optarg, strerror(errno)); + return 1; + } + term_colors_set_enabled(0); + } + break; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + + if (force_colors) { + term_colors_set_enabled(1); + } + + if (include_kernel > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -k and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + + if (include_filter > 0 && exclude_filter > 0) { + fprintf(stderr, "ERROR: -p and -e/-q cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + if (include_filter > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -p and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + + if (exclude_filter > 0) { + proc_filter_excluding = 1; + if (include_kernel) { + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!strcmp(proc_filters[i], "kernel")) { + free(proc_filters[i]); + proc_filters[i] = NULL; + } + } + } else if (exclude_kernel) { + add_filter("kernel"); + } + } else { + if (include_kernel) { + add_filter("kernel"); + } else if (exclude_kernel) { + proc_filter_excluding = 1; + add_filter("kernel"); + } + } + + if (num_untrigger_filters > 0 && num_trigger_filters == 0) { + triggered = 1; + } + + argc -= optind; + argv += optind; + + if (argc > 0) { + if (!strcmp(argv[0], "pidlist")) { + if (connect_service(1) < 0) { + return 1; + } + plist_t list = NULL; + ostrace_get_pid_list(ostrace, &list); + ostrace_client_free(ostrace); + ostrace = NULL; + idevice_free(device); + device = NULL; + if (!list) { + return 1; + } + print_sorted_pidlist(list); + plist_free(list); + return 0; + } else if (!strcmp(argv[0], "archive")) { + if (force_syslog_relay) { + force_syslog_relay = 0; + } + if (argc < 2) { + fprintf(stderr, "Please specify an output filename.\n"); + return 1; + } + FILE* outf = NULL; + if (!strcmp(argv[1], "-")) { + if (isatty(1)) { + fprintf(stderr, "Refusing to directly write to stdout. Pipe the output to another process.\n"); + return 1; + } + outf = stdout; + } else { + outf = fopen(argv[1], "w"); + } + if (!outf) { + fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno)); + return 1; + } + if (connect_service(1) < 0) { + if (outf != stdout) { + fclose(outf); + } + return 1; + } + plist_t options = plist_new_dict(); + if (start_time > 0) { + plist_dict_set_item(options, "StartTime", plist_new_int(start_time)); + } + if (size_limit > 0) { + plist_dict_set_item(options, "SizeLimit", plist_new_int(size_limit)); + } + if (age_limit > 0) { + plist_dict_set_item(options, "AgeLimit", plist_new_int(age_limit)); + } + ostrace_create_archive(ostrace, options, write_callback, outf); + ostrace_client_free(ostrace); + ostrace = NULL; + idevice_free(device); + device = NULL; + if (outf != stdout) { + fclose(outf); + } + return 0; + } else { + fprintf(stderr, "Unknown command '%s'. See --help for valid commands.\n", argv[0]); + return 1; + } + } + + int num = 0; + idevice_info_t *devices = NULL; + idevice_get_device_list_extended(&devices, &num); + int count = 0; + for (int i = 0; i < num; i++) { + if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) { + count++; + } else if (devices[i]->conn_type == CONNECTION_USBMUXD) { + count++; + } + } + idevice_device_list_extended_free(devices); + if (count == 0) { + if (!udid) { + fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to become available.\n"); + return 1; + } + + fprintf(stderr, "Waiting for device with UDID %s to become available...\n", udid); + } + + line_buffer_size = 1024; + line = malloc(line_buffer_size); + + idevice_subscription_context_t context = NULL; + idevice_events_subscribe(&context, device_event_cb, NULL); + + while (!quit_flag) { + sleep(1); + } + idevice_events_unsubscribe(context); + stop_logging(); + + if (num_proc_filters > 0) { + int i; + for (i = 0; i < num_proc_filters; i++) { + free(proc_filters[i]); + } + free(proc_filters); + } + if (num_pid_filters > 0) { + free(pid_filters); + } + if (num_msg_filters > 0) { + int i; + for (i = 0; i < num_msg_filters; i++) { + free(msg_filters[i]); + } + free(msg_filters); + } + if (num_msg_reverse_filters > 0) { + int i; + for (i = 0; i < num_msg_reverse_filters; i++) { + free(msg_reverse_filters[i]); + } + free(msg_reverse_filters); + } + if (num_trigger_filters > 0) { + int i; + for (i = 0; i < num_trigger_filters; i++) { + free(trigger_filters[i]); + } + free(trigger_filters); + } + if (num_untrigger_filters > 0) { + int i; + for (i = 0; i < num_untrigger_filters; i++) { + free(untrigger_filters[i]); + } + free(untrigger_filters); + } + + free(line); + + free(udid); + + return 0; +} |
