text: /*
   *
   * honggfuzz - file operations
   * -----------------------------------------
   *
   * Author: Robert Swiecki <swiecki@google.com>
   *
   * Copyright 2010-2018 by Google Inc. All Rights Reserved.
   *
   * 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 "libhfcommon/files.h"
  
  #include <arpa/inet.h>
  #include <dirent.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <inttypes.h>
  #include <limits.h>
  #include <netinet/in.h>
  #include <netinet/ip.h>
  #include <stddef.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/mman.h>
  #include <sys/socket.h>
  #include <sys/stat.h>
  #include <sys/un.h>
  #if defined(_HF_ARCH_LINUX)
  #include <sys/syscall.h>
  #endif /* defined(_HF_ARCH_LINUX) */
  #include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>
  
  #include "libhfcommon/common.h"
  #include "libhfcommon/log.h"
  #include "libhfcommon/util.h"
  
  ssize_t files_readFileToBufMax(const char* fileName, uint8_t* buf, size_t fileMaxSz) {
      int fd = TEMP_FAILURE_RETRY(open(fileName, O_RDONLY | O_CLOEXEC));
      if (fd == -1) {
          PLOG_W("Couldn't open '%s' for R/O", fileName);
          return -1;
      }
  
      ssize_t readSz = files_readFromFd(fd, buf, fileMaxSz);
      if (readSz < 0) {
          LOG_W("Couldn't read '%s' to a buf", fileName);
      }
      close(fd);
  
      LOG_D("Read '%zu' bytes from '%s'", readSz, fileName);
      return readSz;
  }
  
  bool files_writeBufToFile(const char* fileName, const uint8_t* buf, size_t fileSz, int flags) {
      int fd = TEMP_FAILURE_RETRY(open(fileName, flags, 0644));
      if (fd == -1) {
          PLOG_W("Couldn't create/open '%s' for R/W", fileName);
          return false;
      }
  
      bool ret = files_writeToFd(fd, buf, fileSz);
      if (ret == false) {
          PLOG_W("Couldn't write '%zu' bytes to file '%s' (fd='%d')", fileSz, fileName, fd);
          unlink(fileName);
      } else {
          LOG_D("Written '%zu' bytes to '%s'", fileSz, fileName);
      }
  
      close(fd);
      return ret;
  }
  
  int files_writeBufToTmpFile(const char* dir, const uint8_t* buf, size_t fileSz, int flags) {
      char template[PATH_MAX];
      snprintf(template, sizeof(template), "%s/hfuzz.XXXXXX", dir);
      int fd = mkostemp(template, flags);
      if (fd == -1) {
          PLOG_W("mkostemp('%s') failed", template);
          return -1;
      }
      if (unlink(template) == -1) {
          PLOG_W("unlink('%s')", template);
      }
      if (!files_writeToFd(fd, buf, fileSz)) {
          PLOG_W("Couldn't save data to the temporary file");
          close(fd);
          return -1;
      }
      if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
          PLOG_W("Couldn't rewind file '%s' fd=%d", template, fd);
          close(fd);
          return -1;
      }
      return fd;
  }
  
  bool files_writeToFd(int fd, const uint8_t* buf, size_t fileSz) {
      size_t writtenSz = 0;
      while (writtenSz < fileSz) {
          ssize_t sz = TEMP_FAILURE_RETRY(write(fd, &buf[writtenSz], fileSz - writtenSz));
          if (sz < 0) {
              return false;
          }
          writtenSz += sz;
      }
      return true;
  }
  
  bool files_writeStrToFd(int fd, const char* str) {
      return files_writeToFd(fd, (const uint8_t*)str, strlen(str));
  }
  
  ssize_t files_readFromFd(int fd, uint8_t* buf, size_t fileSz) {
      size_t readSz = 0;
      while (readSz < fileSz) {
          ssize_t sz = TEMP_FAILURE_RETRY(read(fd, &buf[readSz], fileSz - readSz));
          if (sz == 0) {
              break;
          }
          if (sz < 0) {
              return -1;
          }
          readSz += sz;
      }
      return (ssize_t)readSz;
  }
  
  ssize_t files_readFromFdSeek(int fd, uint8_t* buf, size_t fileSz, off_t off) {
      if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
          PLOG_W("lseek(fd=%d, %lld, SEEK_SET)", fd, (long long int)off);
          return -1;
      }
      return files_readFromFd(fd, buf, fileSz);
  }
  
  bool files_exists(const char* fileName) {
      return (access(fileName, F_OK) != -1);
  }
  
  bool files_writePatternToFd(int fd, off_t size, unsigned char p) {
      void* buf = malloc(size);
      if (!buf) {
          PLOG_W("Couldn't allocate memory");
          return false;
      }
  
      memset(buf, p, (size_t)size);
      int ret = files_writeToFd(fd, buf, size);
      free(buf);
  
      return ret;
  }
  
  bool files_sendToSocketNB(int fd, const uint8_t* buf, size_t fileSz) {
      size_t writtenSz = 0;
      while (writtenSz < fileSz) {
          ssize_t sz =
              TEMP_FAILURE_RETRY(send(fd, &buf[writtenSz], fileSz - writtenSz, MSG_DONTWAIT));
          if (sz < 0) {
              return false;
          }
          writtenSz += sz;
      }
      return true;
  }
  
  bool files_sendToSocket(int fd, const uint8_t* buf, size_t fileSz) {
      int sendFlags = 0;
  #ifdef _HF_ARCH_DARWIN
      sendFlags |= SO_NOSIGPIPE;
  #else
      sendFlags |= MSG_NOSIGNAL;
  #endif
  
      size_t writtenSz = 0;
      while (writtenSz < fileSz) {
          ssize_t sz = send(fd, &buf[writtenSz], fileSz - writtenSz, sendFlags);
          if (sz < 0 && errno == EINTR) continue;
  
          if (sz < 0) return false;
  
          writtenSz += sz;
      }
      return true;
  }
  
  const char* files_basename(const char* path) {
      const char* base = strrchr(path, '/');
      return base ? base + 1 : path;
  }
  
  /*
   * Reads symbols from src file (one per line) and append them to filterList. The
   * total number of added symbols is returned.
   *
   * Simple wildcard strings are also supported (e.g. mem*)
   */
  size_t files_parseSymbolFilter(const char* srcFile, char*** filterList) {
      FILE* f = fopen(srcFile, "rb");
      if (f == NULL) {
          PLOG_W("Couldn't open '%s' - R/O mode", srcFile);
          return 0;
      }
  
      char* lineptr = NULL;
      size_t symbolsRead = 0, n = 0;
      for (;;) {
          if (getline(&lineptr, &n, f) == -1) {
              break;
          }
  
          if (strlen(lineptr) < 3) {
              LOG_F("Input symbol '%s' too short (strlen < 3)", lineptr);
              symbolsRead = 0;
              break;
          }
          if ((*filterList = (char**)util_Realloc(
                   *filterList, (symbolsRead + 1) * sizeof((*filterList)[0]))) == NULL) {
              PLOG_W("realloc failed (sz=%zu)", (symbolsRead + 1) * sizeof((*filterList)[0]));
              symbolsRead = 0;
              break;
          }
          (*filterList)[symbolsRead] = malloc(strlen(lineptr));
          if (!(*filterList)[symbolsRead]) {
              PLOG_E("malloc(%zu) failed", strlen(lineptr));
              symbolsRead = 0;
              break;
          }
          snprintf((*filterList)[symbolsRead], strlen(lineptr), "%s", lineptr);
          symbolsRead++;
      }
  
      LOG_I("%zu filter symbols added to list", symbolsRead);
      fclose(f);
      free(lineptr);
      return symbolsRead;
  }
  
  uint8_t* files_mapFile(const char* fileName, off_t* fileSz, int* fd, bool isWritable) {
      int mmapProt = PROT_READ;
      if (isWritable) {
          mmapProt |= PROT_WRITE;
      }
  
      if ((*fd = TEMP_FAILURE_RETRY(open(fileName, O_RDONLY))) == -1) {
          PLOG_W("Couldn't open() '%s' file in R/O mode", fileName);
          return NULL;
      }
  
      struct stat st;
      if (fstat(*fd, &st) == -1) {
          PLOG_W("Couldn't stat() the '%s' file", fileName);
          close(*fd);
          return NULL;
      }
  
      uint8_t* buf;
      if ((buf = mmap(NULL, st.st_size, mmapProt, MAP_PRIVATE, *fd, 0)) == MAP_FAILED) {
          PLOG_W("Couldn't mmap() the '%s' file", fileName);
          close(*fd);
          return NULL;
      }
  
      *fileSz = st.st_size;
      return buf;
  }
  
  /* mmap flags for various OSs, when mmap'ing a temporary file or a shared mem */
  int files_getTmpMapFlags(int flag, bool nocore) {
  #if defined(MAP_NOSYNC)
      /*
       * Some kind of bug in FreeBSD kernel. Without this flag, the shm_open() memory will cause a lot
       * of troubles to the calling process when mmap()'d
       */
      flag |= MAP_NOSYNC;
  #endif /* defined(MAP_NOSYNC) */
  #if defined(MAP_HASSEMAPHORE)
      /* Our shared/mmap'd pages can have mutexes in them */
      flag |= MAP_HASSEMAPHORE;
  #endif /* defined(MAP_HASSEMAPHORE) */
         /* Avoid mapping the memory lazily */
  #if defined(MAP_PREFAULT_READ)
      flag |= MAP_PREFAULT_READ;
  #endif /* defined(MAP_PREFAULT_READ) */
  #if defined(MAP_POPULATE)
      flag |= MAP_POPULATE;
  #endif /* defined(MAP_POPULATE) */
      if (nocore) {
  #if defined(MAP_CONCEAL)
          flag |= MAP_CONCEAL;
  #endif /* defined(MAP_CONCEAL) */
  #if defined(MAP_NOCORE)
          flag |= MAP_NOCORE;
  #endif /* defined(MAP_NOCORE) */
      }
      return flag;
  }
  
  void* files_mapSharedMem(size_t sz, int* fd, const char* name, bool nocore, bool export) {
      *fd = -1;
  
      if (export) {
          char path[PATH_MAX];
          snprintf(path, sizeof(path), "./%s", name);
          if ((*fd = open(path, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)) == -1) {
              PLOG_W("open('%s')", path);
              return NULL;
          }
      }
  
  #if defined(_HF_ARCH_LINUX)
  
  #if !defined(MFD_CLOEXEC) /* sys/memfd.h is not always present */
  #define MFD_CLOEXEC 0x0001U
  #endif /* !defined(MFD_CLOEXEC) */
  
  #if !defined(__NR_memfd_create)
  #if defined(__x86_64__)
  #define __NR_memfd_create 319
  #endif /* defined(__x86_64__) */
  #endif /* !defined(__NR_memfd_create) */
  
  #if defined(__NR_memfd_create)
      if (*fd == -1) {
          *fd = syscall(__NR_memfd_create, name, (uintptr_t)MFD_CLOEXEC);
      }
  #endif /* defined__NR_memfd_create) */
  
  #endif /* defined(_HF_ARCH_LINUX) */
  
  /* SHM_ANON is available with some *BSD OSes */
  #if defined(SHM_ANON)
      if (*fd == -1) {
          if ((*fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600)) == -1) {
              PLOG_W("shm_open(SHM_ANON, O_RDWR|O_CLOEXEC, 0600)");
          }
      }
  #endif /* defined(SHM_ANON) */
  #if !defined(_HF_ARCH_DARWIN) && !defined(__ANDROID__)
      /* shm objects under MacOSX are 'a-typical' */
      if (*fd == -1) {
          char tmpname[PATH_MAX];
          struct timeval tv;
          gettimeofday(&tv, NULL);
          snprintf(tmpname, sizeof(tmpname), "/%s%lx%lx%d", name, (unsigned long)tv.tv_sec,
              (unsigned long)tv.tv_usec, (int)getpid());
          if ((*fd = shm_open(tmpname, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)) == -1) {
              PLOG_W("shm_open('%s', O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0600)", tmpname);
          } else {
              shm_unlink(tmpname);
          }
      }
  #endif /* !defined(_HF_ARCH_DARWIN) && !defined(__ANDROID__) */
      if (*fd == -1) {
          char template[PATH_MAX];
          snprintf(template, sizeof(template), "/tmp/%s.XXXXXX", name);
          if ((*fd = mkostemp(template, O_CLOEXEC)) == -1) {
              PLOG_W("mkstemp('%s')", template);
              return NULL;
          }
          unlink(template);
      }
      if (TEMP_FAILURE_RETRY(ftruncate(*fd, sz)) == -1) {
          PLOG_W("ftruncate(%d, %zu)", *fd, sz);
          close(*fd);
          *fd = -1;
          return NULL;
      }
      int mflags = files_getTmpMapFlags(MAP_SHARED, /* nocore= */ true);
      void* ret = mmap(NULL, sz, PROT_READ | PROT_WRITE, mflags, *fd, 0);
      if (ret == MAP_FAILED) {
          PLOG_W("mmap(sz=%zu, fd=%d)", sz, *fd);
          *fd = -1;
          close(*fd);
          return NULL;
      }
      if (posix_madvise(ret, sz, POSIX_MADV_RANDOM) == -1) {
          PLOG_W("posix_madvise(sz=%zu, POSIX_MADV_RANDOM)", sz);
      }
      if (nocore) {
  #if defined(MADV_DONTDUMP)
          if (madvise(ret, sz, MADV_DONTDUMP) == -1) {
              PLOG_W("madvise(sz=%zu, MADV_DONTDUMP)", sz);
          }
  #endif /* defined(MADV_DONTDUMP) */
  #if defined(MADV_NOCORE)
          if (madvise(ret, sz, MADV_NOCORE) == -1) {
              PLOG_W("madvise(sz=%zu, MADV_NOCORE)", sz);
          }
  #endif /* defined(MADV_NOCORE) */
      }
      return ret;
  }
  
  sa_family_t files_sockFamily(int sock) {
      struct sockaddr addr;
      socklen_t addrlen = sizeof(addr);
  
      if (getsockname(sock, &addr, &addrlen) == -1) {
          PLOG_W("getsockname(sock=%d)", sock);
          return AF_UNSPEC;
      }
  
      return addr.sa_family;
  }
  
  const char* files_sockAddrToStr(const struct sockaddr* sa, const socklen_t len) {
      static __thread char str[4096];
  
      if (sa->sa_family == AF_INET) {
          struct sockaddr_in* sin = (struct sockaddr_in*)sa;
          if (inet_ntop(sin->sin_family, &sin->sin_addr.s_addr, str, sizeof(str))) {
              util_ssnprintf(str, sizeof(str), "/%hd", ntohs(sin->sin_port));
          } else {
              snprintf(str, sizeof(str), "IPv4 addr conversion failed");
          }
          return str;
      }
      if (sa->sa_family == AF_INET6) {
          struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
          if (inet_ntop(sin6->sin6_family, sin6->sin6_addr.s6_addr, str, sizeof(str))) {
              util_ssnprintf(str, sizeof(str), "/%hd", ntohs(sin6->sin6_port));
          } else {
              snprintf(str, sizeof(str), "IPv6 addr conversion failed");
          }
          return str;
      }
  
      if (sa->sa_family == AF_UNIX) {
          if ((size_t)len <= offsetof(struct sockaddr_un, sun_path)) {
              snprintf(str, sizeof(str), "unix:<struct too short at %u bytes>", (unsigned)len);
              return str;
          }
  
          struct sockaddr_un* sun = (struct sockaddr_un*)sa;
          int pathlen;
  
          if (sun->sun_path[0] == '\0') {
              /* Abstract socket
               *
               * TODO: Handle null bytes in sun->sun_path (they have no
               * special significance unlike in C char arrays, see unix(7))
               */
              pathlen = strnlen(&sun->sun_path[1], len - offsetof(struct sockaddr_un, sun_path) - 1);
  
              snprintf(str, sizeof(str), "unix:abstract:%-*s", pathlen, &sun->sun_path[1]);
              return str;
          }
  
          pathlen = strnlen(sun->sun_path, len - offsetof(struct sockaddr_un, sun_path));
  
          snprintf(str, sizeof(str), "unix:%-*s", pathlen, sun->sun_path);
          return str;
      }
  
      snprintf(str, sizeof(str), "Unsupported sockaddr family=%d", (int)sa->sa_family);
      return str;
  }
  