// Copyright (c) 2019-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by caikelun on 2019-03-07.

#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/system_properties.h>
#include "xcc_util.h"
#include "xcc_errno.h"
#include "xcc_fmt.h"
#include "xcc_version.h"
#include "xcc_libc_support.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
#pragma clang diagnostic ignored "-Wcast-align"
#pragma clang diagnostic ignored "-Wformat-nonliteral"

#define XCC_UTIL_TIME_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%03ld%c%02ld%02ld"

const char* xcc_util_get_signame(const siginfo_t* si)
{
    switch (si->si_signo)
    {
    case SIGABRT:   return "SIGABRT";
    case SIGBUS:    return "SIGBUS";
    case SIGFPE:    return "SIGFPE";
    case SIGILL:    return "SIGILL";
    case SIGSEGV:   return "SIGSEGV";
    case SIGTRAP:   return "SIGTRAP";
    case SIGSYS:    return "SIGSYS";
    case SIGSTKFLT: return "SIGSTKFLT";
    default:        return "?";
    }
}

const char* xcc_util_get_sigcodename(const siginfo_t* si)
{
    // Try the signal-specific codes...
    switch (si->si_signo) {
    case SIGBUS:
        switch(si->si_code)
        {
        case BUS_ADRALN:    return "BUS_ADRALN";
        case BUS_ADRERR:    return "BUS_ADRERR";
        case BUS_OBJERR:    return "BUS_OBJERR";
        case BUS_MCEERR_AR: return "BUS_MCEERR_AR";
        case BUS_MCEERR_AO: return "BUS_MCEERR_AO";
        default:            break;
        }
        break;
    case SIGFPE:
        switch(si->si_code)
        {
        case FPE_INTDIV:   return "FPE_INTDIV";
        case FPE_INTOVF:   return "FPE_INTOVF";
        case FPE_FLTDIV:   return "FPE_FLTDIV";
        case FPE_FLTOVF:   return "FPE_FLTOVF";
        case FPE_FLTUND:   return "FPE_FLTUND";
        case FPE_FLTRES:   return "FPE_FLTRES";
        case FPE_FLTINV:   return "FPE_FLTINV";
        case FPE_FLTSUB:   return "FPE_FLTSUB";
        default:           break;
        }
        break;
    case SIGILL:
        switch(si->si_code)
        {
        case ILL_ILLOPC:   return "ILL_ILLOPC";
        case ILL_ILLOPN:   return "ILL_ILLOPN";
        case ILL_ILLADR:   return "ILL_ILLADR";
        case ILL_ILLTRP:   return "ILL_ILLTRP";
        case ILL_PRVOPC:   return "ILL_PRVOPC";
        case ILL_PRVREG:   return "ILL_PRVREG";
        case ILL_COPROC:   return "ILL_COPROC";
        case ILL_BADSTK:   return "ILL_BADSTK";
        default:           break;
        }
        break;
    case SIGSEGV:
        switch(si->si_code)
        {
        case SEGV_MAPERR:  return "SEGV_MAPERR";
        case SEGV_ACCERR:  return "SEGV_ACCERR";
        case SEGV_BNDERR:  return "SEGV_BNDERR";
        case SEGV_PKUERR:  return "SEGV_PKUERR";
        default:           break;
        }
        break;
    case SIGTRAP:
        switch(si->si_code)
        {
        case TRAP_BRKPT:   return "TRAP_BRKPT";
        case TRAP_TRACE:   return "TRAP_TRACE";
        case TRAP_BRANCH:  return "TRAP_BRANCH";
        case TRAP_HWBKPT:  return "TRAP_HWBKPT";
        default:           break;
        }
        if((si->si_code & 0xff) == SIGTRAP)
        {
            switch((si->si_code >> 8) & 0xff)
            {
            case PTRACE_EVENT_FORK:       return "PTRACE_EVENT_FORK";
            case PTRACE_EVENT_VFORK:      return "PTRACE_EVENT_VFORK";
            case PTRACE_EVENT_CLONE:      return "PTRACE_EVENT_CLONE";
            case PTRACE_EVENT_EXEC:       return "PTRACE_EVENT_EXEC";
            case PTRACE_EVENT_VFORK_DONE: return "PTRACE_EVENT_VFORK_DONE";
            case PTRACE_EVENT_EXIT:       return "PTRACE_EVENT_EXIT";
            case PTRACE_EVENT_SECCOMP:    return "PTRACE_EVENT_SECCOMP";
            case PTRACE_EVENT_STOP:       return "PTRACE_EVENT_STOP";
            default:                      break;
            }
        }
        break;
    case SIGSYS:
        switch(si->si_code)
        {
        case SYS_SECCOMP: return "SYS_SECCOMP";
        default:          break;
        }
        break;
    default:
        break;
    }
    
    // Then the other codes...
    switch (si->si_code) {
    case SI_USER:     return "SI_USER";
    case SI_KERNEL:   return "SI_KERNEL";
    case SI_QUEUE:    return "SI_QUEUE";
    case SI_TIMER:    return "SI_TIMER";
    case SI_MESGQ:    return "SI_MESGQ";
    case SI_ASYNCIO:  return "SI_ASYNCIO";
    case SI_SIGIO:    return "SI_SIGIO";
    case SI_TKILL:    return "SI_TKILL";
    case SI_DETHREAD: return "SI_DETHREAD";
    }
    
    // Then give up...
    return "?";
}

int xcc_util_signal_has_si_addr(const siginfo_t* si)
{
    //manually sent signals won't have si_addr
    if(si->si_code == SI_USER || si->si_code == SI_QUEUE || si->si_code == SI_TKILL) return 0;

    switch (si->si_signo)
    {
    case SIGBUS:
    case SIGFPE:
    case SIGILL:
    case SIGSEGV:
    case SIGTRAP:
        return 1;
    default:
        return 0;
    }
}

int xcc_util_signal_has_sender(const siginfo_t* si, pid_t caller_pid)
{
    return (SI_FROMUSER(si) && (si->si_pid != 0) && (si->si_pid != caller_pid)) ? 1 : 0;
}

int xcc_util_atoi(const char *str, int *i)
{
    //We have to do this job very carefully for some unusual version of stdlib.
    
    long  val = 0;
    char *endptr = NULL;
    const char *p = str;

    //check
    if(NULL == str || NULL == i) return XCC_ERRNO_INVAL;
    if((*p < '0' || *p > '9') && *p != '-') return XCC_ERRNO_INVAL;
    p++;
    while(*p)
    {
        if(*p < '0' || *p > '9') return XCC_ERRNO_INVAL;
        p++;
    }

    //convert
    errno = 0;
    val = strtol(str, &endptr, 10);

    //check
    if((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || (errno != 0 && val == 0))
        return XCC_ERRNO_INVAL;
    if(endptr == str)
        return XCC_ERRNO_INVAL;
    if(val > INT_MAX || val < INT_MIN)
        return XCC_ERRNO_INVAL;

    //OK
    *i = (int)val;
    return 0;
}

char *xcc_util_trim(char *start)
{
    char *end;

    if(NULL == start) return NULL;
    
    end = start + strlen(start);
    if(start == end) return start;
    
    while(start < end && isspace((int)(*start))) start++;
    if(start == end) return start;

    while(start < end && isspace((int)(*(end - 1)))) end--;
    *end = '\0';
    return start;
}

int xcc_util_write(int fd, const char *buf, size_t len)
{
    size_t      nleft;
    ssize_t     nwritten;
    const char *ptr;

    if(fd < 0) return XCC_ERRNO_INVAL;

    ptr   = buf;
    nleft = len;

    while(nleft > 0)
    {
        errno = 0;
        if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
                nwritten = 0; /* call write() again */
            else
                return XCC_ERRNO_SYS;    /* error */
        }

        nleft -= (size_t)nwritten;
        ptr   += nwritten;
    }

    return 0;
}

int xcc_util_write_str(int fd, const char *str)
{
    const char *tmp = str;
    size_t      len = 0;

    if(fd < 0) return XCC_ERRNO_INVAL;
    
    while(*tmp) tmp++;
    len = (size_t)(tmp - str);
    if(0 == len) return 0;

    return xcc_util_write(fd, str, len);
}

int xcc_util_write_format(int fd, const char *format, ...)
{
    va_list ap;
    char    buf[1024];
    ssize_t len;

    if(fd < 0) return XCC_ERRNO_INVAL;
    
    va_start(ap, format);
    len = vsnprintf(buf, sizeof(buf), format, ap);    
    va_end(ap);
    
    if(len <= 0) return 0;
    
    return xcc_util_write(fd, buf, (size_t)len);
}

int xcc_util_write_format_safe(int fd, const char *format, ...)
{
    va_list ap;
    char    buf[1024];
    size_t  len;

    if(fd < 0) return XCC_ERRNO_INVAL;
    
    va_start(ap, format);
    len = xcc_fmt_vsnprintf(buf, sizeof(buf), format, ap);
    va_end(ap);
    if(0 == len) return 0;
    
    return xcc_util_write(fd, buf, len);
}

char *xcc_util_gets(char *s, size_t size, int fd)
{
    ssize_t i, nread;
    char    c, *p;

    if(fd < 0 || NULL == s || size < 2) return NULL;
    
    s[0] = '\0';
    p = s;
    
    for(i = 0; i < (ssize_t)(size - 1); i++)
    {
        if(1 == (nread = read(fd, &c, 1)))
        {
            *p++ = c;
            if('\n' == c) break;
        }
        else if(0 == nread) //EOF
        {
            break;
        }
        else
        {
            if (errno != EINTR) return NULL;
        }
    }

    *p = '\0';
    return ('\0' == s[0] ? NULL : s);
}

int xcc_util_read_file_line(const char *path, char *buf, size_t len)
{
    int fd = -1;
    int r = 0;
    
    if(0 > (fd = XCC_UTIL_TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC))))
    {
        r = XCC_ERRNO_SYS;
        goto end;
    }
    if(NULL == xcc_util_gets(buf, len, fd))
    {
        r = XCC_ERRNO_SYS;
        goto end;
    }

 end:
    if(fd >= 0) close(fd);
    return r;
}

static int xcc_util_get_process_thread_name(const char *path, char *buf, size_t len)
{
    char    tmp[256], *data;
    size_t  data_len, cpy_len;
    int     r;

    //read a line
    if(0 != (r = xcc_util_read_file_line(path, tmp, sizeof(tmp)))) return r;

    //trim
    data = xcc_util_trim(tmp);

    //return data
    if(0 == (data_len = strlen(data))) return XCC_ERRNO_MISSING;
    cpy_len = XCC_UTIL_MIN(len - 1, data_len);
    memcpy(buf, data, cpy_len);
    buf[cpy_len] = '\0';

    return 0;
}

void xcc_util_get_process_name(pid_t pid, char *buf, size_t len)
{
    char path[128];

    xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
    
    if(0 != xcc_util_get_process_thread_name(path, buf, len))
        strncpy(buf, "unknown", len);
}

void xcc_util_get_thread_name(pid_t tid, char *buf, size_t len)
{
    char path[128];
    
    xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/comm", tid);
    
    if(0 != xcc_util_get_process_thread_name(path, buf, len))
        strncpy(buf, "unknown", len);
}

int xcc_util_record_sub_section_from(int fd, const char *path, const char *title, size_t limit)
{
    FILE   *fp = NULL;
    char    line[512];
    char   *p;
    int     r = 0;
    size_t  n = 0;

    if(NULL == (fp = fopen(path, "r"))) goto end;

    if(0 != (r = xcc_util_write_str(fd, title))) goto end;
    while(NULL != fgets(line, sizeof(line), fp))
    {
        p = xcc_util_trim(line);
        if(strlen(p) > 0)
        {
            n++;
            if(0 == limit || n <= limit)
                if(0 != (r = xcc_util_write_format_safe(fd, "  %s\n", p))) goto end;
        }
    }
    if(limit > 0 && n > limit)
    {
        if(0 != (r = xcc_util_write_str(fd, "  ......\n"))) goto end;
        if(0 != (r = xcc_util_write_format_safe(fd, "  (number of records: %zu)\n", n))) goto end;
    }
    if(0 != (r = xcc_util_write_str(fd, "-\n"))) goto end;

 end:
    if(NULL != fp) fclose(fp);
    return r;
}

static const char *xcc_util_su_pathnames[] =
{
    "/data/local/su",
    "/data/local/bin/su",
    "/data/local/xbin/su",
    "/system/xbin/su",
    "/system/bin/su",
    "/system/bin/.ext/su",
    "/system/bin/failsafe/su",
    "/system/sd/xbin/su",
    "/system/usr/we-need-root/su",
    "/sbin/su",
    "/su/bin/su"
};
static int xcc_util_is_root_saved = -1;

int xcc_util_is_root(void)
{
    size_t i;

    if(xcc_util_is_root_saved >= 0) return xcc_util_is_root_saved;
    
    for(i = 0; i < sizeof(xcc_util_su_pathnames) / sizeof(xcc_util_su_pathnames[0]); i++)
    {
        if(0 == access(xcc_util_su_pathnames[i], F_OK))
        {
            xcc_util_is_root_saved = 1;
            return 1;
        }
    }

    xcc_util_is_root_saved = 0;
    return 0;
}

size_t xcc_util_get_dump_header(char *buf,
                                size_t buf_len,
                                const char *crash_type,
                                long time_zone,
                                uint64_t start_time,
                                uint64_t crash_time,
                                const char *app_id,
                                const char *app_version,
                                int api_level,
                                const char *os_version,
                                const char *kernel_version,
                                const char *abi_list,
                                const char *manufacturer,
                                const char *brand,
                                const char *model,
                                const char *build_fingerprint)
{
    time_t       start_sec  = (time_t)(start_time / 1000000);
    suseconds_t  start_usec = (time_t)(start_time % 1000000);
    struct tm    start_tm;
    time_t       crash_sec   = (time_t)(crash_time / 1000000);
    suseconds_t  crash_usec = (time_t)(crash_time % 1000000);
    struct tm    crash_tm;

    //convert times
    xcc_libc_support_memset(&start_tm, 0, sizeof(start_tm));
    xcc_libc_support_memset(&crash_tm, 0, sizeof(crash_tm));
    xcc_libc_support_localtime_r(&start_sec, time_zone, &start_tm);
    xcc_libc_support_localtime_r(&crash_sec, time_zone, &crash_tm);

    return xcc_fmt_snprintf(buf, buf_len,
                            XCC_UTIL_TOMB_HEAD
                            "Tombstone maker: '"XCC_VERSION_STR"'\n"
                            "Crash type: '%s'\n"
                            "Start time: '"XCC_UTIL_TIME_FORMAT"'\n"
                            "Crash time: '"XCC_UTIL_TIME_FORMAT"'\n"
                            "App ID: '%s'\n"
                            "App version: '%s'\n"
                            "Rooted: '%s'\n"
                            "API level: '%d'\n"
                            "OS version: '%s'\n"
                            "Kernel version: '%s'\n"
                            "ABI list: '%s'\n"
                            "Manufacturer: '%s'\n"
                            "Brand: '%s'\n"
                            "Model: '%s'\n"
                            "Build fingerprint: '%s'\n"
                            "ABI: '"XCC_UTIL_ABI_STRING"'\n",
                            crash_type,
                            start_tm.tm_year + 1900, start_tm.tm_mon + 1, start_tm.tm_mday,
                            start_tm.tm_hour, start_tm.tm_min, start_tm.tm_sec, start_usec / 1000,
                            time_zone < 0 ? '-' : '+', labs(time_zone / 3600), labs(time_zone % 3600),
                            crash_tm.tm_year + 1900, crash_tm.tm_mon + 1, crash_tm.tm_mday,
                            crash_tm.tm_hour, crash_tm.tm_min, crash_tm.tm_sec, crash_usec / 1000,
                            time_zone < 0 ? '-' : '+', labs(time_zone / 3600), labs(time_zone % 3600),
                            app_id,
                            app_version,
                            xcc_util_is_root() ? "Yes" : "No",
                            api_level,
                            os_version,
                            kernel_version,
                            abi_list,
                            manufacturer,
                            brand,
                            model,
                            build_fingerprint);
}

static int xcc_util_record_logcat_buffer(int fd, pid_t pid, int api_level,
                                         const char *buffer, unsigned int lines, char priority)
{
    FILE *fp;
    char  cmd[128];
    char  buf[1025];
    int   with_pid;
    char  pid_filter[64] = "";
    char  pid_label[32] = "";
    int   r = 0;

    //Since Android 7.0 Nougat (API level 24), logcat has --pid filter option.
    with_pid = (api_level >= 24 ? 1 : 0);

    if(with_pid)
    {
        //API level >= 24, filtered by --pid option
        xcc_fmt_snprintf(pid_filter, sizeof(pid_filter), "--pid %d ", pid);
    }
    else
    {
        //API level < 24, filtered by ourself, so we need to read more lines
        lines = (unsigned int)(lines * 1.2);
        xcc_fmt_snprintf(pid_label, sizeof(pid_label), " %d ", pid);
    }
    
    xcc_fmt_snprintf(cmd, sizeof(cmd), "/system/bin/logcat -b %s -d -v threadtime -t %u %s*:%c",
                     buffer, lines, pid_filter, priority);

    if(0 != (r = xcc_util_write_format_safe(fd, "--------- tail end of log %s (%s)\n", buffer, cmd))) return r;

    if(NULL != (fp = popen(cmd, "r")))
    {
        buf[sizeof(buf) - 1] = '\0';
        while(NULL != fgets(buf, sizeof(buf) - 1, fp))
            if(with_pid || NULL != strstr(buf, pid_label))
                if(0 != (r = xcc_util_write_str(fd, buf))) break;
        pclose(fp);
    }
    
    return r;
}

int xcc_util_record_logcat(int fd,
                           pid_t pid,
                           int api_level,
                           unsigned int logcat_system_lines,
                           unsigned int logcat_events_lines,
                           unsigned int logcat_main_lines)
{
    int r;
    
    if(0 == logcat_system_lines && 0 == logcat_events_lines && 0 == logcat_main_lines) return 0;
    
    if(0 != (r = xcc_util_write_str(fd, "logcat:\n"))) return r;

    if(logcat_main_lines > 0)
        if(0 != (r = xcc_util_record_logcat_buffer(fd, pid, api_level, "main", logcat_main_lines, 'D'))) return r;
    
    if(logcat_system_lines > 0)
        if(0 != (r = xcc_util_record_logcat_buffer(fd, pid, api_level, "system", logcat_system_lines, 'W'))) return r;

    if(logcat_events_lines > 0)
        if(0 != (r = xcc_util_record_logcat_buffer(fd, pid, api_level, "events", logcat_events_lines, 'I'))) return r;

    if(0 != (r = xcc_util_write_str(fd, "\n"))) return r;

    return 0;
}

int xcc_util_record_fds(int fd, pid_t pid)
{
    int                fd2 = -1;
    char               path[128];
    char               fd_path[512];
    char               buf[512];
    long               n, i;
    int                fd_num;
    size_t             total = 0;
    xcc_util_dirent_t *ent;
    ssize_t            len;
    int                r = 0;

    if(0 != (r = xcc_util_write_str(fd, "open files:\n"))) return r;

    xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/fd", pid);
    if((fd2 = XCC_UTIL_TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC))) < 0) goto end;
    
    while((n = syscall(XCC_UTIL_SYSCALL_GETDENTS, fd2, buf, sizeof(buf))) > 0)
    {
        for(i = 0; i < n;)
        {
            ent = (xcc_util_dirent_t *)(buf + i);

            //get the fd
            if('\0' == ent->d_name[0]) goto next;
            if(0 == memcmp(ent->d_name, ".", 1)) goto next;
            if(0 == memcmp(ent->d_name, "..", 2)) goto next;
            if(0 != xcc_util_atoi(ent->d_name, &fd_num)) goto next;
            if(fd_num < 0) goto next;

            //count
            total++;
            if(total > 1024) goto next;

            //read link of the path
            xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/fd/%d", pid, fd_num);
            len = readlink(path, fd_path, sizeof(fd_path) - 1);
            if(len <= 0 || len > (ssize_t)(sizeof(fd_path) - 1))
                strncpy(fd_path, "???", sizeof(fd_path));
            else
                fd_path[len] = '\0';
            
            //dump
            if(0 != (r = xcc_util_write_format_safe(fd, "    fd %d: %s\n", fd_num, fd_path))) goto clean;
            
        next:
            i += ent->d_reclen;
        }
    }

 end:
    if(total > 1024)
        if(0 != (r = xcc_util_write_str(fd, "    ......\n"))) goto clean;
    if(0 != (r = xcc_util_write_format_safe(fd, "    (number of FDs: %zu)\n", total))) goto clean;
    r = xcc_util_write_str(fd, "\n");

 clean:
    if(fd2 >= 0) close(fd2);
    return r;
}

int xcc_util_record_network_info(int fd, pid_t pid, int api_level)
{
    int  r;
    char path[128];

    if(0 != (r = xcc_util_write_str(fd, "network info:\n"))) return r;

    if(api_level >= 29)
    {
        if(0 != (r = xcc_util_write_str(fd, "Not supported on Android Q (API level 29) and later.\n"))) return r;
    }
    else
    {
        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/tcp", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " TCP over IPv4 (From: /proc/PID/net/tcp)\n", 1024))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/tcp6", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " TCP over IPv6 (From: /proc/PID/net/tcp6)\n", 1024))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/udp", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " UDP over IPv4 (From: /proc/PID/net/udp)\n", 1024))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/udp6", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " UDP over IPv6 (From: /proc/PID/net/udp6)\n", 1024))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/icmp", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " ICMP in IPv4 (From: /proc/PID/net/icmp)\n", 256))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/icmp6", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " ICMP in IPv6 (From: /proc/PID/net/icmp6)\n", 256))) return r;

        xcc_fmt_snprintf(path, sizeof(path), "/proc/%d/net/unix", pid);
        if(0 != (r = xcc_util_record_sub_section_from(fd, path, " UNIX domain (From: /proc/PID/net/unix)\n", 256))) return r;
    }

    if(0 != (r = xcc_util_write_str(fd, "\n"))) return r;
    return 0;
}

#pragma clang diagnostic pop
