/*
** Copyright 2014, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

#include <ctype.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#include <unistd.h>

#include <private/android_logger.h>

#include "log_portability.h"

static pthread_mutex_t lock_loggable = PTHREAD_MUTEX_INITIALIZER;

static int lock() {
  /*
   * If we trigger a signal handler in the middle of locked activity and the
   * signal handler logs a message, we could get into a deadlock state.
   */
  /*
   *  Any contention, and we can turn around and use the non-cached method
   * in less time than the system call associated with a mutex to deal with
   * the contention.
   */
  return pthread_mutex_trylock(&lock_loggable);
}

static void unlock() {
  pthread_mutex_unlock(&lock_loggable);
}

struct cache {
  const prop_info* pinfo;
  uint32_t serial;
};

struct cache_char {
  struct cache cache;
  unsigned char c;
};

static int check_cache(struct cache* cache) {
  return cache->pinfo && __system_property_serial(cache->pinfo) != cache->serial;
}

#define BOOLEAN_TRUE 0xFF
#define BOOLEAN_FALSE 0xFE

static void refresh_cache(struct cache_char* cache, const char* key) {
  char buf[PROP_VALUE_MAX];

  if (!cache->cache.pinfo) {
    cache->cache.pinfo = __system_property_find(key);
    if (!cache->cache.pinfo) {
      return;
    }
  }
  cache->cache.serial = __system_property_serial(cache->cache.pinfo);
  __system_property_read(cache->cache.pinfo, 0, buf);
  switch (buf[0]) {
    case 't':
    case 'T':
      cache->c = strcasecmp(buf + 1, "rue") ? buf[0] : BOOLEAN_TRUE;
      break;
    case 'f':
    case 'F':
      cache->c = strcasecmp(buf + 1, "alse") ? buf[0] : BOOLEAN_FALSE;
      break;
    default:
      cache->c = buf[0];
  }
}

static int __android_log_level(const char* tag, size_t len, int default_prio) {
  /* sizeof() is used on this array below */
  static const char log_namespace[] = "persist.log.tag.";
  static const size_t base_offset = 8; /* skip "persist." */
  /* calculate the size of our key temporary buffer */
  const size_t taglen = tag ? len : 0;
  /* sizeof(log_namespace) = strlen(log_namespace) + 1 */
  char key[sizeof(log_namespace) + taglen];
  char* kp;
  size_t i;
  char c = 0;
  /*
   * Single layer cache of four properties. Priorities are:
   *    log.tag.<tag>
   *    persist.log.tag.<tag>
   *    log.tag
   *    persist.log.tag
   * Where the missing tag matches all tags and becomes the
   * system global default. We do not support ro.log.tag* .
   */
  static char* last_tag;
  static size_t last_tag_len;
  static uint32_t global_serial;
  /* some compilers erroneously see uninitialized use. !not_locked */
  uint32_t current_global_serial = 0;
  static struct cache_char tag_cache[2];
  static struct cache_char global_cache[2];
  int change_detected;
  int global_change_detected;
  int not_locked;

  strcpy(key, log_namespace);

  global_change_detected = change_detected = not_locked = lock();

  if (!not_locked) {
    /*
     *  check all known serial numbers to changes.
     */
    for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
      if (check_cache(&tag_cache[i].cache)) {
        change_detected = 1;
      }
    }
    for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
      if (check_cache(&global_cache[i].cache)) {
        global_change_detected = 1;
      }
    }

    current_global_serial = __system_property_area_serial();
    if (current_global_serial != global_serial) {
      change_detected = 1;
      global_change_detected = 1;
    }
  }

  if (taglen) {
    int local_change_detected = change_detected;
    if (!not_locked) {
      if (!last_tag || !last_tag[0] || (last_tag[0] != tag[0]) ||
          strncmp(last_tag + 1, tag + 1, last_tag_len - 1)) {
        /* invalidate log.tag.<tag> cache */
        for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
          tag_cache[i].cache.pinfo = NULL;
          tag_cache[i].c = '\0';
        }
        if (last_tag) last_tag[0] = '\0';
        local_change_detected = 1;
      }
      if (!last_tag || !last_tag[0]) {
        if (!last_tag) {
          last_tag = calloc(1, len + 1);
          last_tag_len = 0;
          if (last_tag) last_tag_len = len + 1;
        } else if (len >= last_tag_len) {
          last_tag = realloc(last_tag, len + 1);
          last_tag_len = 0;
          if (last_tag) last_tag_len = len + 1;
        }
        if (last_tag) {
          strncpy(last_tag, tag, len);
          last_tag[len] = '\0';
        }
      }
    }
    strncpy(key + sizeof(log_namespace) - 1, tag, len);
    key[sizeof(log_namespace) - 1 + len] = '\0';

    kp = key;
    for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
      struct cache_char* cache = &tag_cache[i];
      struct cache_char temp_cache;

      if (not_locked) {
        temp_cache.cache.pinfo = NULL;
        temp_cache.c = '\0';
        cache = &temp_cache;
      }
      if (local_change_detected) {
        refresh_cache(cache, kp);
      }

      if (cache->c) {
        c = cache->c;
        break;
      }

      kp = key + base_offset;
    }
  }

  switch (toupper(c)) { /* if invalid, resort to global */
    case 'V':
    case 'D':
    case 'I':
    case 'W':
    case 'E':
    case 'F': /* Not officially supported */
    case 'A':
    case 'S':
    case BOOLEAN_FALSE: /* Not officially supported */
      break;
    default:
      /* clear '.' after log.tag */
      key[sizeof(log_namespace) - 2] = '\0';

      kp = key;
      for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
        struct cache_char* cache = &global_cache[i];
        struct cache_char temp_cache;

        if (not_locked) {
          temp_cache = *cache;
          if (temp_cache.cache.pinfo != cache->cache.pinfo) { /* check atomic */
            temp_cache.cache.pinfo = NULL;
            temp_cache.c = '\0';
          }
          cache = &temp_cache;
        }
        if (global_change_detected) {
          refresh_cache(cache, kp);
        }

        if (cache->c) {
          c = cache->c;
          break;
        }

        kp = key + base_offset;
      }
      break;
  }

  if (!not_locked) {
    global_serial = current_global_serial;
    unlock();
  }

  switch (toupper(c)) {
    /* clang-format off */
    case 'V': return ANDROID_LOG_VERBOSE;
    case 'D': return ANDROID_LOG_DEBUG;
    case 'I': return ANDROID_LOG_INFO;
    case 'W': return ANDROID_LOG_WARN;
    case 'E': return ANDROID_LOG_ERROR;
    case 'F': /* FALLTHRU */ /* Not officially supported */
    case 'A': return ANDROID_LOG_FATAL;
    case BOOLEAN_FALSE: /* FALLTHRU */ /* Not Officially supported */
    case 'S': return -1; /* ANDROID_LOG_SUPPRESS */
    /* clang-format on */
  }
  return default_prio;
}

LIBLOG_ABI_PUBLIC int __android_log_is_loggable_len(int prio, const char* tag,
                                                    size_t len,
                                                    int default_prio) {
  int logLevel = __android_log_level(tag, len, default_prio);
  return logLevel >= 0 && prio >= logLevel;
}

LIBLOG_ABI_PUBLIC int __android_log_is_loggable(int prio, const char* tag,
                                                int default_prio) {
  int logLevel =
      __android_log_level(tag, (tag && *tag) ? strlen(tag) : 0, default_prio);
  return logLevel >= 0 && prio >= logLevel;
}

LIBLOG_ABI_PUBLIC int __android_log_is_debuggable() {
  static uint32_t serial;
  static struct cache_char tag_cache;
  static const char key[] = "ro.debuggable";
  int ret;

  if (tag_cache.c) { /* ro property does not change after set */
    ret = tag_cache.c == '1';
  } else if (lock()) {
    struct cache_char temp_cache = { { NULL, -1 }, '\0' };
    refresh_cache(&temp_cache, key);
    ret = temp_cache.c == '1';
  } else {
    int change_detected = check_cache(&tag_cache.cache);
    uint32_t current_serial = __system_property_area_serial();
    if (current_serial != serial) {
      change_detected = 1;
    }
    if (change_detected) {
      refresh_cache(&tag_cache, key);
      serial = current_serial;
    }
    ret = tag_cache.c == '1';

    unlock();
  }

  return ret;
}

/*
 * For properties that are read often, but generally remain constant.
 * Since a change is rare, we will accept a trylock failure gracefully.
 * Use a separate lock from is_loggable to keep contention down b/25563384.
 */
struct cache2_char {
  pthread_mutex_t lock;
  uint32_t serial;
  const char* key_persist;
  struct cache_char cache_persist;
  const char* key_ro;
  struct cache_char cache_ro;
  unsigned char (*const evaluate)(const struct cache2_char* self);
};

static inline unsigned char do_cache2_char(struct cache2_char* self) {
  uint32_t current_serial;
  int change_detected;
  unsigned char c;

  if (pthread_mutex_trylock(&self->lock)) {
    /* We are willing to accept some race in this context */
    return self->evaluate(self);
  }

  change_detected = check_cache(&self->cache_persist.cache) ||
                    check_cache(&self->cache_ro.cache);
  current_serial = __system_property_area_serial();
  if (current_serial != self->serial) {
    change_detected = 1;
  }
  if (change_detected) {
    refresh_cache(&self->cache_persist, self->key_persist);
    refresh_cache(&self->cache_ro, self->key_ro);
    self->serial = current_serial;
  }
  c = self->evaluate(self);

  pthread_mutex_unlock(&self->lock);

  return c;
}

static unsigned char evaluate_persist_ro(const struct cache2_char* self) {
  unsigned char c = self->cache_persist.c;

  if (c) {
    return c;
  }

  return self->cache_ro.c;
}

/*
 * Timestamp state generally remains constant, but can change at any time
 * to handle developer requirements.
 */
LIBLOG_ABI_PUBLIC clockid_t android_log_clockid() {
  static struct cache2_char clockid = {
    PTHREAD_MUTEX_INITIALIZER, 0,
    "persist.logd.timestamp",  { { NULL, -1 }, '\0' },
    "ro.logd.timestamp",       { { NULL, -1 }, '\0' },
    evaluate_persist_ro
  };

  return (tolower(do_cache2_char(&clockid)) == 'm') ? CLOCK_MONOTONIC
                                                    : CLOCK_REALTIME;
}

/*
 * Security state generally remains constant, but the DO must be able
 * to turn off logging should it become spammy after an attack is detected.
 */
static unsigned char evaluate_security(const struct cache2_char* self) {
  unsigned char c = self->cache_ro.c;

  return (c != BOOLEAN_FALSE) && c && (self->cache_persist.c == BOOLEAN_TRUE);
}

LIBLOG_ABI_PUBLIC int __android_log_security() {
  static struct cache2_char security = {
    PTHREAD_MUTEX_INITIALIZER, 0,
    "persist.logd.security",   { { NULL, -1 }, BOOLEAN_FALSE },
    "ro.device_owner",         { { NULL, -1 }, BOOLEAN_FALSE },
    evaluate_security
  };

  return do_cache2_char(&security);
}

/*
 * Interface that represents the logd buffer size determination so that others
 * need not guess our intentions.
 */

/* Property helper */
static bool check_flag(const char* prop, const char* flag) {
  const char* cp = strcasestr(prop, flag);
  if (!cp) {
    return false;
  }
  /* We only will document comma (,) */
  static const char sep[] = ",:;|+ \t\f";
  if ((cp != prop) && !strchr(sep, cp[-1])) {
    return false;
  }
  cp += strlen(flag);
  return !*cp || !!strchr(sep, *cp);
}

/* cache structure */
struct cache_property {
  struct cache cache;
  char property[PROP_VALUE_MAX];
};

static void refresh_cache_property(struct cache_property* cache,
                                   const char* key) {
  if (!cache->cache.pinfo) {
    cache->cache.pinfo = __system_property_find(key);
    if (!cache->cache.pinfo) {
      return;
    }
  }
  cache->cache.serial = __system_property_serial(cache->cache.pinfo);
  __system_property_read(cache->cache.pinfo, 0, cache->property);
}

/* get boolean with the logger twist that supports eng adjustments */
LIBLOG_ABI_PRIVATE bool __android_logger_property_get_bool(const char* key,
                                                           int flag) {
  struct cache_property property = { { NULL, -1 }, { 0 } };
  if (flag & BOOL_DEFAULT_FLAG_PERSIST) {
    char newkey[strlen("persist.") + strlen(key) + 1];
    snprintf(newkey, sizeof(newkey), "ro.%s", key);
    refresh_cache_property(&property, newkey);
    property.cache.pinfo = NULL;
    property.cache.serial = -1;
    snprintf(newkey, sizeof(newkey), "persist.%s", key);
    refresh_cache_property(&property, newkey);
    property.cache.pinfo = NULL;
    property.cache.serial = -1;
  }

  refresh_cache_property(&property, key);

  if (check_flag(property.property, "true")) {
    return true;
  }
  if (check_flag(property.property, "false")) {
    return false;
  }
  if (property.property[0]) {
    flag &= ~(BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
  }
  if (check_flag(property.property, "eng")) {
    flag |= BOOL_DEFAULT_FLAG_ENG;
  }
  /* this is really a "not" flag */
  if (check_flag(property.property, "svelte")) {
    flag |= BOOL_DEFAULT_FLAG_SVELTE;
  }

  /* Sanity Check */
  if (flag & (BOOL_DEFAULT_FLAG_SVELTE | BOOL_DEFAULT_FLAG_ENG)) {
    flag &= ~BOOL_DEFAULT_FLAG_TRUE_FALSE;
    flag |= BOOL_DEFAULT_TRUE;
  }

  if ((flag & BOOL_DEFAULT_FLAG_SVELTE) &&
      __android_logger_property_get_bool("ro.config.low_ram",
                                         BOOL_DEFAULT_FALSE)) {
    return false;
  }
  if ((flag & BOOL_DEFAULT_FLAG_ENG) && !__android_log_is_debuggable()) {
    return false;
  }

  return (flag & BOOL_DEFAULT_FLAG_TRUE_FALSE) != BOOL_DEFAULT_FALSE;
}

LIBLOG_ABI_PRIVATE bool __android_logger_valid_buffer_size(unsigned long value) {
  static long pages, pagesize;
  unsigned long maximum;

  if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) {
    return false;
  }

  if (!pages) {
    pages = sysconf(_SC_PHYS_PAGES);
  }
  if (pages < 1) {
    return true;
  }

  if (!pagesize) {
    pagesize = sysconf(_SC_PAGESIZE);
    if (pagesize <= 1) {
      pagesize = PAGE_SIZE;
    }
  }

  /* maximum memory impact a somewhat arbitrary ~3% */
  pages = (pages + 31) / 32;
  maximum = pages * pagesize;

  if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) {
    return true;
  }

  return value <= maximum;
}

struct cache2_property_size {
  pthread_mutex_t lock;
  uint32_t serial;
  const char* key_persist;
  struct cache_property cache_persist;
  const char* key_ro;
  struct cache_property cache_ro;
  unsigned long (*const evaluate)(const struct cache2_property_size* self);
};

static inline unsigned long do_cache2_property_size(
    struct cache2_property_size* self) {
  uint32_t current_serial;
  int change_detected;
  unsigned long v;

  if (pthread_mutex_trylock(&self->lock)) {
    /* We are willing to accept some race in this context */
    return self->evaluate(self);
  }

  change_detected = check_cache(&self->cache_persist.cache) ||
                    check_cache(&self->cache_ro.cache);
  current_serial = __system_property_area_serial();
  if (current_serial != self->serial) {
    change_detected = 1;
  }
  if (change_detected) {
    refresh_cache_property(&self->cache_persist, self->key_persist);
    refresh_cache_property(&self->cache_ro, self->key_ro);
    self->serial = current_serial;
  }
  v = self->evaluate(self);

  pthread_mutex_unlock(&self->lock);

  return v;
}

static unsigned long property_get_size_from_cache(
    const struct cache_property* cache) {
  char* cp;
  unsigned long value = strtoul(cache->property, &cp, 10);

  switch (*cp) {
    case 'm':
    case 'M':
      value *= 1024;
    /* FALLTHRU */
    case 'k':
    case 'K':
      value *= 1024;
    /* FALLTHRU */
    case '\0':
      break;

    default:
      value = 0;
  }

  if (!__android_logger_valid_buffer_size(value)) {
    value = 0;
  }

  return value;
}

static unsigned long evaluate_property_get_size(
    const struct cache2_property_size* self) {
  unsigned long size = property_get_size_from_cache(&self->cache_persist);
  if (size) {
    return size;
  }
  return property_get_size_from_cache(&self->cache_ro);
}

LIBLOG_ABI_PRIVATE unsigned long __android_logger_get_buffer_size(log_id_t logId) {
  static const char global_tunable[] = "persist.logd.size"; /* Settings App */
  static const char global_default[] = "ro.logd.size";      /* BoardConfig.mk */
  static struct cache2_property_size global = {
    /* clang-format off */
    PTHREAD_MUTEX_INITIALIZER, 0,
    global_tunable, { { NULL, -1 }, {} },
    global_default, { { NULL, -1 }, {} },
    evaluate_property_get_size
    /* clang-format on */
  };
  char key_persist[strlen(global_tunable) + strlen(".security") + 1];
  char key_ro[strlen(global_default) + strlen(".security") + 1];
  struct cache2_property_size local = {
    /* clang-format off */
    PTHREAD_MUTEX_INITIALIZER, 0,
    key_persist, { { NULL, -1 }, {} },
    key_ro,      { { NULL, -1 }, {} },
    evaluate_property_get_size
    /* clang-format on */
  };
  unsigned long property_size, default_size;

  default_size = do_cache2_property_size(&global);
  if (!default_size) {
    default_size = __android_logger_property_get_bool("ro.config.low_ram",
                                                      BOOL_DEFAULT_FALSE)
                       ? LOG_BUFFER_MIN_SIZE /* 64K  */
                       : LOG_BUFFER_SIZE;    /* 256K */
  }

  snprintf(key_persist, sizeof(key_persist), "%s.%s", global_tunable,
           android_log_id_to_name(logId));
  snprintf(key_ro, sizeof(key_ro), "%s.%s", global_default,
           android_log_id_to_name(logId));
  property_size = do_cache2_property_size(&local);

  if (!property_size) {
    property_size = default_size;
  }

  if (!property_size) {
    property_size = LOG_BUFFER_SIZE;
  }

  return property_size;
}
