wireshark/wiretap/logcat.c

714 lines
21 KiB
C
Raw Normal View History

/* logcat.c
*
* Copyright 2014, Michal Labedzki for Tieto Corporation
*
* This program 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 Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <string.h>
#include <time.h>
#include "wtap-int.h"
#include "file_wrappers.h"
#include <wsutil/buffer.h>
#include "logcat.h"
enum dump_type_t {
DUMP_BINARY,
DUMP_BRIEF,
DUMP_PROCESS,
DUMP_TAG,
DUMP_TIME,
DUMP_THREAD,
DUMP_THREADTIME,
DUMP_LONG
};
struct dumper_t {
enum dump_type_t type;
};
/* The log format can be found on:
* https://android.googlesource.com/platform/system/core/+/master/include/log/logger.h
* Log format is assumed to be little-endian (Android platform).
*/
/* maximum size of a message payload in a log entry */
#define LOGGER_ENTRY_MAX_PAYLOAD 4076
struct logger_entry {
guint16 len; /* length of the payload */
guint16 __pad; /* no matter what, we get 2 bytes of padding */
gint32 pid; /* generating process's pid */
gint32 tid; /* generating process's tid */
gint32 sec; /* seconds since Epoch */
gint32 nsec; /* nanoseconds */
char msg[0]; /* the entry's payload */
};
struct logger_entry_v2 {
guint16 len; /* length of the payload */
guint16 hdr_size; /* sizeof(struct logger_entry_v2) */
gint32 pid; /* generating process's pid */
gint32 tid; /* generating process's tid */
gint32 sec; /* seconds since Epoch */
gint32 nsec; /* nanoseconds */
union {
/* v1: not present */
guint32 euid; /* v2: effective UID of logger */
guint32 lid; /* v3: log id of the payload */
};
char msg[0]; /* the entry's payload */
};
/* Returns '?' for invalid priorities */
static gchar get_priority(const guint8 priority) {
static gchar priorities[] = "??VDIWEFS";
if (priority >= (guint8) sizeof(priorities))
return '?';
return priorities[priority];
}
static gchar *logcat_log(const struct dumper_t *dumper, guint32 seconds,
gint milliseconds, gint pid, gint tid, gchar priority, const gchar *tag,
const gchar *log)
{
gchar time_buffer[15];
time_t datetime;
datetime = (time_t) seconds;
switch (dumper->type) {
case DUMP_BRIEF:
return g_strdup_printf("%c/%-8s(%5i): %s\n",
priority, tag, pid, log);
case DUMP_PROCESS:
/* NOTE: Last parameter should be "process name", not tag;
Unfortunately, we do not have process name */
return g_strdup_printf("%c(%5i) %s (%s)\n",
priority, pid, log, "");
case DUMP_TAG:
return g_strdup_printf("%c/%-8s: %s\n",
priority, tag, log);
case DUMP_THREAD:
return g_strdup_printf("%c(%5i:0x%02x) %s\n",
priority, pid, tid, log);
case DUMP_TIME:
strftime(time_buffer, sizeof(time_buffer), "%m-%d %H:%M:%S",
gmtime(&datetime));
return g_strdup_printf("%s.%03i %c/%-8s(%5i): %s\n",
time_buffer, milliseconds, priority, tag, pid, log);
case DUMP_THREADTIME:
strftime(time_buffer, sizeof(time_buffer), "%m-%d %H:%M:%S",
gmtime(&datetime));
return g_strdup_printf("%s.%03i %5i %5i %c %-8s: %s\n",
time_buffer, milliseconds, pid, tid, priority, tag, log);
case DUMP_LONG:
strftime(time_buffer, sizeof(time_buffer), "%m-%d %H:%M:%S",
gmtime(&datetime));
return g_strdup_printf("[ %s.%03i %5i:0x%02x %c/%s ]\n%s\n\n",
time_buffer, milliseconds, pid, tid, priority, tag, log);
default:
return NULL;
}
}
static gint detect_version(wtap *wth, int *err, gchar **err_info)
{
gint bytes_read;
guint16 payload_length;
guint16 hdr_size;
guint16 read_sofar;
guint16 entry_len;
gint version;
struct logger_entry *log_entry;
struct logger_entry_v2 *log_entry_v2;
guint8 *buffer;
guint16 tmp;
guint8 *msg_payload, *msg_part, *msg_end;
guint16 msg_len;
/* 16-bit payload length */
bytes_read = file_read(&tmp, 2, wth->fh);
if (bytes_read != 2) {
*err = file_error(wth->fh, err_info);
if (*err == 0 && bytes_read != 0)
*err = WTAP_ERR_SHORT_READ;
return -1;
}
payload_length = pletoh16(&tmp);
/* 16-bit header length (or padding, equal to 0x0000) */
bytes_read = file_read(&tmp, 2, wth->fh);
if (bytes_read != 2) {
*err = file_error(wth->fh, err_info);
if (*err == 0 && bytes_read != 0)
*err = WTAP_ERR_SHORT_READ;
return -1;
}
hdr_size = pletoh16(&tmp);
read_sofar = 4;
/* must contain at least priority and two nulls as separator */
if (payload_length < 3)
return -1;
/* payload length may not exceed the maximum payload size */
if (payload_length > LOGGER_ENTRY_MAX_PAYLOAD)
return -1;
/* ensure buffer is large enough for all versions */
buffer = (guint8 *) g_malloc(sizeof(*log_entry_v2) + payload_length);
log_entry_v2 = (struct logger_entry_v2 *) buffer;
log_entry = (struct logger_entry *) buffer;
/* cannot rely on __pad being 0 for v1, use heuristics to find out what
* version is in use. First assume the smallest msg. */
for (version = 1; version <= 2; ++version) {
if (version == 1) {
msg_payload = log_entry->msg;
entry_len = sizeof(*log_entry) + payload_length;
} else if (version == 2) {
/* v2 is 4 bytes longer */
msg_payload = log_entry_v2->msg;
entry_len = sizeof(*log_entry_v2) + payload_length;
if (hdr_size != sizeof(*log_entry_v2))
continue;
}
bytes_read = file_read(buffer + read_sofar, entry_len - read_sofar,
wth->fh);
if (bytes_read != entry_len - read_sofar) {
*err = file_error(wth->fh, err_info);
if (*err == 0 && bytes_read != 0)
*err = WTAP_ERR_SHORT_READ;
/* short read, end of file? Whatever, this cannot be valid. */
break;
}
read_sofar += bytes_read;
/* A v2 msg has a 32-bit userid instead of v1 priority */
if (get_priority(msg_payload[0]) == '?')
continue;
/* Is there a terminating '\0' for the tag? */
msg_part = (guint8 *) memchr(msg_payload, '\0', payload_length - 1);
if (msg_part == NULL)
continue;
/* if msg is '\0'-terminated, is it equal to the payload len? */
++msg_part;
msg_len = (guint16)(payload_length - (msg_part - msg_payload));
msg_end = (guint8 *) memchr(msg_part, '\0', msg_len);
/* is the end of the buffer (-1) equal to the end of msg? */
if (msg_end && (msg_payload + payload_length - 1 != msg_end))
continue;
g_free(buffer);
return version;
}
g_free(buffer);
return -1;
}
static gint buffered_detect_version(const guint8 *pd)
{
struct logger_entry *log_entry;
struct logger_entry_v2 *log_entry_v2;
gint version;
guint8 *msg_payload;
guint8 *msg_part;
guint8 *msg_end;
guint16 msg_len;
log_entry_v2 = (struct logger_entry_v2 *) pd;
log_entry = (struct logger_entry *) pd;
/* must contain at least priority and two nulls as separator */
if (log_entry->len < 3)
return -1;
/* payload length may not exceed the maximum payload size */
if (log_entry->len > LOGGER_ENTRY_MAX_PAYLOAD)
return -1;
/* cannot rely on __pad being 0 for v1, use heuristics to find out what
* version is in use. First assume the smallest msg. */
for (version = 1; version <= 2; ++version) {
if (version == 1) {
msg_payload = log_entry->msg;
} else if (version == 2) {
/* v2 is 4 bytes longer */
msg_payload = log_entry_v2->msg;
if (log_entry_v2->hdr_size != sizeof(*log_entry_v2))
continue;
}
/* A v2 msg has a 32-bit userid instead of v1 priority */
if (get_priority(msg_payload[0]) == '?')
continue;
/* Is there a terminating '\0' for the tag? */
msg_part = (guint8 *) memchr(msg_payload, '\0', log_entry->len - 1);
if (msg_part == NULL)
continue;
/* if msg is '\0'-terminated, is it equal to the payload len? */
++msg_part;
msg_len = (guint16)(log_entry->len - (msg_part - msg_payload));
msg_end = (guint8 *) memchr(msg_part, '\0', msg_len);
/* is the end of the buffer (-1) equal to the end of msg? */
if (msg_end && (msg_payload + log_entry->len - 1 != msg_end))
continue;
return version;
}
return -1;
}
static gint exported_pdu_length(const guint8 *pd) {
guint16 *tag;
guint16 *tag_length;
gint length = 0;
tag = (guint16 *) pd;
while(GINT16_FROM_BE(*tag)) {
tag_length = (guint16 *) (pd + 2);
length += 2 + 2 + GINT16_FROM_BE(*tag_length);
pd += 2 + 2 + GINT16_FROM_BE(*tag_length);
tag = (guint16 *) pd;
}
length += 2 + 2;
return length;
}
static gboolean logcat_read_packet(struct logcat_phdr *logcat, FILE_T fh,
struct wtap_pkthdr *phdr, Buffer *buf, int *err, gchar **err_info)
{
gint bytes_read;
gint packet_size;
guint16 payload_length;
guint tmp[2];
guint8 *pd;
struct logger_entry *log_entry;
bytes_read = file_read(&tmp, 2, fh);
if (bytes_read != 2) {
*err = file_error(fh, err_info);
if (*err == 0 && bytes_read != 0)
*err = WTAP_ERR_SHORT_READ;
return FALSE;
}
payload_length = pletoh16(tmp);
if (logcat->version == 1) {
packet_size = (gint)sizeof(struct logger_entry) + payload_length;
} else if (logcat->version == 2) {
packet_size = (gint)sizeof(struct logger_entry_v2) + payload_length;
} else {
return FALSE;
}
buffer_assure_space(buf, packet_size);
pd = buffer_start_ptr(buf);
log_entry = (struct logger_entry *) pd;
/* Copy the first two bytes of the packet. */
memcpy(pd, tmp, 2);
/* Read the rest of the packet. */
bytes_read = file_read(pd + 2, packet_size - 2, fh);
if (bytes_read != packet_size - 2) {
*err = file_error(fh, err_info);
if (*err == 0)
*err = WTAP_ERR_SHORT_READ;
return FALSE;
}
phdr->rec_type = REC_TYPE_PACKET;
phdr->presence_flags = WTAP_HAS_TS;
phdr->ts.secs = (time_t) GINT32_FROM_LE(log_entry->sec);
phdr->ts.nsecs = GINT32_FROM_LE(log_entry->nsec);
phdr->caplen = packet_size;
phdr->len = packet_size;
phdr->pseudo_header.logcat.version = logcat->version;
return TRUE;
}
static gboolean logcat_read(wtap *wth, int *err, gchar **err_info,
gint64 *data_offset)
{
*data_offset = file_tell(wth->fh);
return logcat_read_packet((struct logcat_phdr *) wth->priv, wth->fh,
&wth->phdr, wth->frame_buffer, err, err_info);
}
static gboolean logcat_seek_read(wtap *wth, gint64 seek_off,
struct wtap_pkthdr *phdr, Buffer *buf,
int *err, gchar **err_info)
{
if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
return FALSE;
if (!logcat_read_packet((struct logcat_phdr *) wth->priv, wth->random_fh,
phdr, buf, err, err_info)) {
if (*err == 0)
*err = WTAP_ERR_SHORT_READ;
return FALSE;
}
return TRUE;
}
int logcat_open(wtap *wth, int *err, gchar **err_info _U_)
{
int local_err;
gchar *local_err_info;
gint version;
gint tmp_version;
struct logcat_phdr *logcat;
/* check first 3 packets (or 2 or 1 if EOF) versions to check file format is correct */
version = detect_version(wth, &local_err, &local_err_info);
if (version <= 0)
return 0;
tmp_version = detect_version(wth, &local_err, &local_err_info);
if (tmp_version < 0 && !file_eof(wth->fh)) {
return 0;
} else if (tmp_version > 0) {
if (tmp_version != version)
return 0;
tmp_version = detect_version(wth, &local_err, &local_err_info);
if (tmp_version != version && !file_eof(wth->fh))
return 0;
}
if (file_seek(wth->fh, 0, SEEK_SET, err) == -1)
return -1;
logcat = (struct logcat_phdr *) g_malloc(sizeof(struct logcat_phdr));
logcat->version = version;
wth->priv = logcat;
wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_LOGCAT;
wth->file_encap = WTAP_ENCAP_LOGCAT;
wth->snapshot_length = 0;
wth->subtype_read = logcat_read;
wth->subtype_seek_read = logcat_seek_read;
wth->tsprecision = WTAP_FILE_TSPREC_USEC;
return 1;
}
int logcat_dump_can_write_encap(int encap)
{
if (encap == WTAP_ENCAP_PER_PACKET)
return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
if (encap != WTAP_ENCAP_LOGCAT && encap != WTAP_ENCAP_WIRESHARK_UPPER_PDU)
return WTAP_ERR_UNSUPPORTED_ENCAP;
return 0;
}
static gboolean logcat_binary_dump(wtap_dumper *wdh,
const struct wtap_pkthdr *phdr,
const guint8 *pd, int *err)
{
int caplen;
/* We can only write packet records. */
if (phdr->rec_type != REC_TYPE_PACKET) {
*err = WTAP_ERR_REC_TYPE_UNSUPPORTED;
return FALSE;
}
caplen = phdr->caplen;
/* Skip EXPORTED_PDU*/
if (wdh->encap == WTAP_ENCAP_WIRESHARK_UPPER_PDU) {
gint skipped_length;
skipped_length = exported_pdu_length(pd);
pd += skipped_length;
caplen -= skipped_length;
}
if (!wtap_dump_file_write(wdh, pd, caplen, err))
return FALSE;
wdh->bytes_dumped += caplen;
return TRUE;
}
gboolean logcat_binary_dump_open(wtap_dumper *wdh, int *err)
{
wdh->subtype_write = logcat_binary_dump;
wdh->subtype_close = NULL;
switch (wdh->encap) {
case WTAP_ENCAP_LOGCAT:
case WTAP_ENCAP_WIRESHARK_UPPER_PDU:
wdh->tsprecision = WTAP_FILE_TSPREC_USEC;
break;
default:
*err = WTAP_ERR_UNSUPPORTED_FILE_TYPE;
return FALSE;
}
return TRUE;
}
static gboolean logcat_dump_text(wtap_dumper *wdh,
const struct wtap_pkthdr *phdr,
const guint8 *pd, int *err)
{
gchar *buf;
gint length;
gchar priority;
const struct logger_entry *log_entry;
const struct logger_entry_v2 *log_entry_v2;
gint payload_length;
const gchar *tag;
gint32 pid;
gint32 tid;
gint32 seconds;
gint32 milliseconds;
const gchar *msg_begin;
gint msg_pre_skip;
gchar *log;
gchar *log_part;
gchar *log_next;
gint logcat_version;
const struct dumper_t *dumper = (const struct dumper_t *) wdh->priv;
/* We can only write packet records. */
if (phdr->rec_type != REC_TYPE_PACKET) {
*err = WTAP_ERR_REC_TYPE_UNSUPPORTED;
return FALSE;
}
/* Skip EXPORTED_PDU*/
if (wdh->encap == WTAP_ENCAP_WIRESHARK_UPPER_PDU) {
gint skipped_length;
skipped_length = exported_pdu_length(pd);
pd += skipped_length;
logcat_version = buffered_detect_version(pd);
} else {
const union wtap_pseudo_header *pseudo_header = &phdr->pseudo_header;
logcat_version = pseudo_header->logcat.version;
}
log_entry = (struct logger_entry *) pd;
log_entry_v2 = (struct logger_entry_v2 *) pd;
payload_length = GINT32_FROM_LE(log_entry->len);
pid = GINT32_FROM_LE(log_entry->pid);
tid = GINT32_FROM_LE(log_entry->tid);
seconds = GINT32_FROM_LE(log_entry->sec);
milliseconds = GINT32_FROM_LE(log_entry->nsec) / 1000000;
/* msg: <prio:1><tag:N>\0<msg:N>\0 with N >= 0, last \0 can be missing */
if (logcat_version == 1) {
priority = get_priority(log_entry->msg[0]);
tag = log_entry->msg + 1;
msg_pre_skip = 1 + (gint) strlen(tag) + 1;
msg_begin = log_entry->msg + msg_pre_skip;
} else if (logcat_version == 2) {
priority = get_priority(log_entry_v2->msg[0]);
tag = log_entry_v2->msg + 1;
msg_pre_skip = 1 + (gint) strlen(tag) + 1;
msg_begin = log_entry_v2->msg + msg_pre_skip;
} else {
*err = WTAP_ERR_UNSUPPORTED;
return FALSE;
}
/* copy the message part. If a nul byte was missing, it will be added. */
log = g_strndup(msg_begin, payload_length - msg_pre_skip);
/* long format: display one header followed by the whole message (which may
* contain new lines). Other formats: include tag, etc. with each line */
log_next = log;
do {
log_part = log_next;
if (dumper->type == DUMP_LONG) {
/* read until end, there is no next string */
log_next = NULL;
} else {
/* read until next newline */
log_next = strchr(log_part, '\n');
if (log_next != NULL) {
*log_next = '\0';
++log_next;
/* ignore trailing newline */
if (*log_next == '\0') {
log_next = NULL;
}
}
}
buf = logcat_log(dumper, seconds, milliseconds, pid, tid,
priority, tag, log_part);
if (!buf) {
g_free(log);
return FALSE;
}
length = (guint32)strlen(buf);
if (!wtap_dump_file_write(wdh, buf, length, err)) {
g_free(log);
return FALSE;
}
wdh->bytes_dumped += length;
} while (log_next != NULL);
g_free(log);
return TRUE;
}
gboolean logcat_text_brief_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_BRIEF;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_process_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_PROCESS;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_tag_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_TAG;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_time_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_TIME;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_thread_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_THREAD;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_threadtime_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_THREADTIME;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
gboolean logcat_text_long_dump_open(wtap_dumper *wdh, int *err _U_)
{
struct dumper_t *dumper;
dumper = (struct dumper_t *) g_malloc(sizeof(struct dumper_t));
dumper->type = DUMP_LONG;
wdh->priv = dumper;
wdh->subtype_write = logcat_dump_text;
wdh->subtype_close = NULL;
return TRUE;
}
/*
* Editor modelines - http://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/