/*
 * Copyright (c) 2018, SUSE LLC.
 *
 * This program is licensed under the BSD license, read LICENSE.BSD
 * for further information
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>

#include "pool.h"
#include "repo.h"

#ifdef ENABLE_RPMPKG
#include "repo_rpmdb.h"
#endif

#ifdef ENABLE_RPMMD
#include "repo_repomdxml.h"
#include "repo_rpmmd.h"
#include "repo_updateinfoxml.h"
#include "repo_deltainfoxml.h"
#endif

#ifdef ENABLE_SUSEREPO
#include "repo_content.h"
#include "repo_susetags.h"
#endif

#ifdef SUSE
#include "repo_autopattern.h"
#endif
#ifdef ENABLE_APPDATA
#include "repo_appdata.h"
#endif
#include "common_write.h"
#include "solv_xfopen.h"


#ifdef SUSE
int add_auto = 0;
#endif
#ifdef ENABLE_APPDATA
int add_appdata = 0;
#endif
int recursive = 0;
int add_filelist = 0;
int add_changelog = 0;
int filtered_filelist = 0;


#define REPO_PLAINDIR		1
#define REPO_RPMMD		2
#define REPO_RPMMD_REPODATA	3
#define REPO_SUSETAGS		4

int
autodetect_repotype(Pool *pool, const char *dir)
{
  struct stat stb;
  char *tmp;
  FILE *fp;

  tmp = pool_tmpjoin(pool, dir, "/repomd.xml", 0);
  if (stat(tmp, &stb) == 0)
    return REPO_RPMMD;
  tmp = pool_tmpjoin(pool, dir, "/repodata/repomd.xml", 0);
  if (stat(tmp, &stb) == 0)
    return REPO_RPMMD_REPODATA;
  tmp = pool_tmpjoin(pool, dir, "/content", 0);
  if ((fp = fopen(tmp, "r")) != 0)
    {
      char buf[512], *descrdir = 0;
      while (fgets(buf, sizeof(buf), fp))
	{
	  int l = strlen(buf);
	  char *bp = buf;
	  if (buf[l - 1] != '\n')
	    {
	      int c;
	      while ((c = getc(fp)) != EOF && c != '\n')
		;
	      continue;
	    }
	  while (l && (buf[l - 1] == '\n' || buf[l - 1] == ' ' || buf[l - 1] == '\t'))
	    l--;
	  buf[l] = 0;
	  while (*bp == ' ' || *bp == '\t')
	    bp++;
	  if (strncmp(bp, "DESCRDIR", 8) != 0 || (bp[8] != ' ' && bp[8] != '\t'))
	    continue;
	  bp += 9;
	  while (*bp == ' ' || *bp == '\t')
	    bp++;
	  descrdir = bp;
	  break;
	}
      fclose(fp);
      if (descrdir)
	{
	  tmp = pool_tmpjoin(pool, dir, "/", descrdir);
	  if (stat(tmp, &stb) == 0 && S_ISDIR(stb.st_mode))
	    return REPO_SUSETAGS;
	}
    }
  tmp = pool_tmpjoin(pool, dir, "/suse/setup/descr", 0);
  if (stat(tmp, &stb) == 0 && S_ISDIR(stb.st_mode))
    return REPO_SUSETAGS;
  return REPO_PLAINDIR;
}


#ifdef ENABLE_RPMPKG

int
read_plaindir_repo(Repo *repo, const char *dir)
{
  Pool *pool = repo->pool;
  Repodata *data;
  int c;
  FILE *fp;
  int wstatus;
  int fds[2];
  pid_t pid;
  char *buf = 0;
  char *buf_end = 0;
  char *bp = 0;
  char *rpm;
  int res = 0;
  Id p;

  /* run find command */
  if (pipe(fds))
    {
      perror("pipe");
      exit(1);
    }
  while ((pid = fork()) == (pid_t)-1)
    {
      if (errno != EAGAIN)
	{
	  perror("fork");
	  exit(1);
	}
      sleep(3);
    }
  if (pid == 0)
    {
      if (chdir(dir))
	{
	  perror(dir);
	  _exit(1);
	}
      close(fds[0]);
      if (fds[1] != 1)
	{
	  if (dup2(fds[1], 1) == -1)
	    {
	      perror("dup2");
	      _exit(1);
	    }
	  close(fds[1]);
	}
      if (recursive)
	execl("/usr/bin/find", "/usr/bin/find", ".", "-name", ".", "-o", "-name", ".*", "-prune", "-o", "-name", "*.delta.rpm", "-o", "-name", "*.patch.rpm", "-o", "-name", "*.rpm", "-a", "-type", "f", "-print0", (char *)0);
      else
	execl("/usr/bin/find", "/usr/bin/find", ".", "-maxdepth", "1", "-name", ".", "-o", "-name", ".*", "-prune", "-o", "-name", "*.delta.rpm", "-o", "-name", "*.patch.rpm", "-o", "-name", "*.rpm", "-a", "-type", "f", "-print0", (char *)0);
      perror("/usr/bin/find");
      _exit(1);
    }
  close(fds[1]);
  if ((fp = fdopen(fds[0], "r")) == 0)
    {
      perror("fdopen");
      exit(1);
    }
  data = repo_add_repodata(repo, 0);
  bp = buf;
  while ((c = getc(fp)) != EOF)
    {
      if (bp == buf_end)
	{
	  size_t len = bp - buf;
	  buf = solv_realloc(buf, len + 4096);
	  bp = buf + len;
	  buf_end = bp + 4096;
	}
      *bp++ = c;
      if (c)
	continue;
      bp = buf;
      rpm = solv_dupjoin(dir, "/", bp[0] == '.' && bp[1] == '/' ? bp + 2 : bp);
      if ((p = repo_add_rpm(repo, rpm, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION|(filtered_filelist ? RPM_ADD_FILTERED_FILELIST : 0))) == 0)
	{
	  fprintf(stderr, "%s: %s\n", rpm, pool_errstr(pool));
#if 0
	  res = 1;
#endif
	}
      else
	repodata_set_location(data, p, 0, 0, bp[0] == '.' && bp[1] == '/' ? bp + 2 : bp);
      solv_free(rpm);
    }
  solv_free(buf);
  fclose(fp);
  while (waitpid(pid, &wstatus, 0) == -1)
    {
      if (errno == EINTR)
	continue;
      perror("waitpid");
      exit(1);
    }
  if (wstatus)
    {
      fprintf(stderr, "find: exit status %d\n", (wstatus >> 8) | (wstatus & 255) << 8);
#if 0
      res = 1;
#endif
    }
  repo_internalize(repo);
  return res;
}

#else

int
read_plaindir_repo(Repo *repo, const char *dir)
{
  fprintf(stderr, "plaindir repo type is not supported\n");
  exit(1);
}

#endif

#ifdef ENABLE_SUSEREPO

static const char *
susetags_find(char **files, int nfiles, const char *what)
{
  int i;
  size_t l = strlen(what);
  for (i = 0; i < nfiles; i++)
    {
      char *fn = files[i];
      if (strncmp(fn, what, l) != 0)
	continue;
      if (fn[l] == 0)
	return fn;
      if (fn[l] != '.')
	continue;
      if (strchr(fn + l + 1, '.') != 0)
	continue;
      if (solv_xfopen_iscompressed(fn) <= 0)
	continue;
      return fn;
    }
  return 0;
}

static FILE *
susetags_open(const char *dir, const char *filename, char **tmpp, int missingok)
{
  FILE *fp;
  if (!filename)
    {
      *tmpp = 0;
      return 0;
    }
  *tmpp = solv_dupjoin(dir, "/", filename);
  if ((fp = solv_xfopen(*tmpp, "r")) == 0)
    {
      if (!missingok)
	{
	  perror(*tmpp);
	  exit(1);
	}
      *tmpp = solv_free(*tmpp);
      return 0;
    }
  return fp;
}

static void
susetags_extend(Repo *repo, const char *dir, char **files, int nfiles, char *what, Id defvendor, char *language, int missingok)
{
  const char *filename;
  FILE *fp;
  char *tmp;

  filename = susetags_find(files, nfiles, what);
  if (!filename)
    return;
  if ((fp = susetags_open(dir, filename, &tmp, missingok)) != 0)
    {
      if (repo_add_susetags(repo, fp, defvendor, language, REPO_EXTEND_SOLVABLES))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
	  exit(1);
	}
      fclose(fp);
      solv_free(tmp);
    }
}

static void
susetags_extend_languages(Repo *repo, const char *dir, char **files, int nfiles, Id defvendor, int missingok)
{
  int i;
  for (i = 0; i < nfiles; i++)
    {
      char *fn = files[i];
      char lang[64], *p;
      if (strncmp(fn, "packages.", 9) != 0)
	continue;
      if (strlen(fn + 9) + 1 >= sizeof(lang))
	continue;
      strncpy(lang, fn + 9, sizeof(lang) - 1);
      lang[sizeof(lang) - 1] = 0;
      p = strrchr(lang, '.');
      if (p)
	{
          if (solv_xfopen_iscompressed(lang) <= 0)
	    continue;
	  *p = 0;
	}
      if (strchr(lang, '.'))
	continue;
      if (!strcmp(lang, "en"))
	continue;	/* already did that one */
      if (!strcmp(lang, "DU"))
	continue;	/* disk usage */
      if (!strcmp(lang, "FL"))
	continue;	/* file list */
      if (!strcmp(lang, "DL"))
	continue;	/* deltas */
      susetags_extend(repo, dir, files, nfiles, fn, defvendor, lang, missingok);
    }
}

static int
susetags_dircmp(const void *ap, const void *bp, void *dp)
{
  return strcmp(*(const char **)ap, *(const char **)bp);
}

int
read_susetags_repo(Repo *repo, const char *dir)
{
  Pool *pool = repo->pool;
  const char *filename;
  char *ddir;
  char *tmp;
  FILE *fp;
  Id defvendor = 0;
  const char *descrdir = 0;
  char **files = 0;
  int nfiles = 0;
  DIR *dp;
  struct dirent *de;

  /* read content file */
  repo_add_repodata(repo, 0);
  tmp = solv_dupjoin(dir, "/content", 0);
  if ((fp = fopen(tmp, "r")) != 0)
    {
      if (repo_add_content(repo, fp, REPO_REUSE_REPODATA))
        {
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
        }
      fclose(fp);
      descrdir = repo_lookup_str(repo, SOLVID_META, SUSETAGS_DESCRDIR);
      defvendor = repo_lookup_id(repo, SOLVID_META, SUSETAGS_DEFAULTVENDOR);
    }
  if (!descrdir)
    descrdir = "suse/setup/descr";
  tmp = solv_free(tmp);

  /* get content of descrdir directory */
  ddir = solv_dupjoin(dir, "/", descrdir);
  if ((dp = opendir(ddir)) == 0)
    {
      perror(ddir);
      exit(1);
    }
  while ((de = readdir(dp)) != 0)
    {
      if (de->d_name[0] == 0 || de->d_name[0] == '.')
	continue;
      files = solv_extend(files, nfiles, 1, sizeof(char *), 63);
      files[nfiles++] = solv_strdup(de->d_name);
    }
  closedir(dp);
  if (nfiles > 1)
    solv_sort(files, nfiles, sizeof(char *), susetags_dircmp, 0);
  
  /* add packages */
  filename = susetags_find(files, nfiles, "packages");
  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
    {
      if (repo_add_susetags(repo, fp, defvendor, 0, SUSETAGS_RECORD_SHARES))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);

      /* now extend the packages */
      susetags_extend(repo, ddir, files, nfiles, "packages.DU", defvendor, 0, 1);
      susetags_extend(repo, ddir, files, nfiles, "packages.en", defvendor, 0, 1);
      susetags_extend_languages(repo, ddir, files, nfiles, defvendor, 1);
      if (add_filelist)
        susetags_extend(repo, ddir, files, nfiles, "packages.FL", defvendor, 0, 1);
    }

  /* add deltas */
  filename = susetags_find(files, nfiles, "packages.DL");
  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
    {
      if (repo_add_susetags(repo, fp, defvendor, 0, 0))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }

  /* add legacy patterns */
  tmp = solv_dupjoin(ddir, "/patterns", 0);
  if ((fp = fopen(tmp, "r")) != 0)
    {
      char pbuf[4096];

      repo_add_repodata(repo, 0);
      while (fgets(pbuf, sizeof(pbuf), fp))
	{
	  char *p;
	  FILE *pfp;
	  if (strchr(pbuf, '/') != 0)
	    continue;
	  if ((p = strchr(pbuf, '\n')) != 0)
	    *p = 0;
	  if (*pbuf == 0)
	    continue;
	  solv_free(tmp);
	  tmp = solv_dupjoin(ddir, "/", pbuf);
	  if ((pfp = solv_xfopen(tmp, "r")) != 0)
	    {
	      if (repo_add_susetags(repo, pfp, defvendor, 0, REPO_NO_INTERNALIZE|REPO_REUSE_REPODATA))
		{
		  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
		  exit(1);
		}
	      fclose(pfp);
	    }
	}
      fclose(fp);
    }
  tmp = solv_free(tmp);
 
#ifdef ENABLE_APPDATA
  /* appdata */
  filename = add_appdata ? susetags_find(files, nfiles, "appdata.xml") : 0;
  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
    {
      if (repo_add_appdata(repo, fp, 0))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }
#endif

  while (nfiles > 0)
    solv_free(files[--nfiles]);
  solv_free(files);
  solv_free(ddir);
  repo_internalize(repo);
  return 0;
}

#else

int
read_susetags_repo(Repo *repo, const char *dir)
{
  fprintf(stderr, "susetags repo type is not supported\n");
  exit(1);
}

#endif


#ifdef ENABLE_RPMMD

static const char *
repomd_find(Repo *repo, const char *what)
{
  Pool *pool = repo->pool;
  Dataiterator di;
  const char *filename;

  filename = 0;
  dataiterator_init(&di, pool, repo, SOLVID_META, REPOSITORY_REPOMD_TYPE, what, SEARCH_STRING);
  dataiterator_prepend_keyname(&di, REPOSITORY_REPOMD);
  if (dataiterator_step(&di))
    {
      dataiterator_setpos_parent(&di);
      filename = pool_lookup_str(pool, SOLVID_POS, REPOSITORY_REPOMD_LOCATION);
    }
  dataiterator_free(&di);
  if (filename && strncmp(filename, "repodata/", 9) == 0)
    filename += 9;
  return filename;
}

static FILE *
repomd_open(const char *dir, const char *filename, char **tmpp, int missingok)
{
  FILE *fp;
  if (!filename)
    {
      *tmpp = 0;
      return 0;
    }
  *tmpp = solv_dupjoin(dir, "/", filename);
  if ((fp = solv_xfopen(*tmpp, "r")) == 0)
    {
      if (!missingok)
	{
	  perror(*tmpp);
	  exit(1);
	}
      *tmpp = solv_free(*tmpp);
      return 0;
    }
  return fp;
}

static void
repomd_extend(Repo *repo, const char *dir, const char *what, const char *language, int missingok)
{
  const char *filename;
  FILE *fp;
  char *tmp;

  filename = repomd_find(repo, what);
  if (!filename)
    return;
  fp = repomd_open(dir, filename, &tmp, missingok);
  if (fp)
    {
      if (repo_add_rpmmd(repo, fp, language, REPO_EXTEND_SOLVABLES))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
	  exit(1);
	}
      fclose(fp);
    }
  solv_free(tmp);
}

static void
repomd_extend_languages(Repo *repo, const char *dir, int missingok)
{
  char **susedatas = 0;
  int nsusedatas = 0, i;
  Dataiterator di;
  dataiterator_init(&di, repo->pool, repo, SOLVID_META, REPOSITORY_REPOMD_TYPE, "susedata.", SEARCH_STRINGSTART);
  dataiterator_prepend_keyname(&di, REPOSITORY_REPOMD);
  while (dataiterator_step(&di))
    {
      susedatas = solv_extend(susedatas, nsusedatas, 1, sizeof(char *), 15);
      susedatas[nsusedatas++] = solv_strdup(di.kv.str);
    }
  dataiterator_free(&di);
  for (i = 0; i < nsusedatas; i++)
    {
      repomd_extend(repo, dir, susedatas[i], susedatas[i] + 9, missingok);
      susedatas[i] = solv_free(susedatas[i]);
    }
  solv_free(susedatas);
}

static void
add_rpmmd_file(Repo *repo, const char *dir, const char *filename, int missingok)
{
  FILE *fp;
  char *tmp;

  fp = repomd_open(dir, filename, &tmp, missingok);
  if (!fp)
    return;
  if (repo_add_rpmmd(repo, fp, 0, 0))
    {
      fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
      exit(1);
    }
  fclose(fp);
  solv_free(tmp);
}

int
read_rpmmd_repo(Repo *repo, const char *dir)
{
  Pool *pool = repo->pool;
  FILE *fp;
  char *tmp = 0;
  const char *filename;

  /* add repomd.xml and suseinfo.xml */
  fp = repomd_open(dir, "repomd.xml", &tmp, 0);
  if (repo_add_repomdxml(repo, fp, 0))
    {
      fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
      exit(1);
    }
  fclose(fp);
  tmp = solv_free(tmp);
  filename = repomd_find(repo, "suseinfo");
  if (filename && (fp = repomd_open(dir, filename, &tmp, 0)) != 0)
    {
      if (repo_add_repomdxml(repo, fp, REPO_REUSE_REPODATA))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }
  
  /* first all primary packages */
  filename = repomd_find(repo, "primary");
  if (filename)
    {
      add_rpmmd_file(repo, dir, filename, 0);
      repomd_extend(repo, dir, "susedata", 0, 1);
      repomd_extend_languages(repo, dir, 1);
      if (add_filelist)
        repomd_extend(repo, dir, "filelists", 0, 1);
      if (add_changelog)
        repomd_extend(repo, dir, "other", 0, 1);
    }

  /* some legacy stuff */
  filename = repomd_find(repo, "products");
  if (!filename)
    filename = repomd_find(repo, "product");
  if (filename)
    add_rpmmd_file(repo, dir, filename, 1);
  filename = repomd_find(repo, "patterns");
    add_rpmmd_file(repo, dir, filename, 1);
  
  /* updateinfo */
  filename = repomd_find(repo, "updateinfo");
  if (filename && (fp = repomd_open(dir, filename, &tmp, 0)) != 0)
    {
      if (repo_add_updateinfoxml(repo, fp, 0))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }

  /* deltainfo */
  filename = repomd_find(repo, "deltainfo");
  if (!filename)
    filename = repomd_find(repo, "prestodelta");
  if (filename && (fp = repomd_open(dir, filename, &tmp, 1)) != 0)
    {
      if (repo_add_deltainfoxml(repo, fp, 0))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }

#ifdef ENABLE_APPDATA
  /* appdata */
  filename = add_appdata ? repomd_find(repo, "appdata") : 0;
  if (filename && (fp = repomd_open(dir, filename, &tmp, 1)) != 0)
    {
      if (repo_add_appdata(repo, fp, 0))
	{
	  fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
	  exit(1);
	}
      fclose(fp);
      tmp = solv_free(tmp);
    }
#endif

  repo_internalize(repo);
  return 0;
}

#else

int
read_rpmmd_repo(Repo *repo, const char *dir)
{
  fprintf(stderr, "rpmmd repo type is not supported\n");
  exit(1);
}

#endif

static void
usage(int status)
{
  fprintf(stderr, "\nUsage:\n"
          "repo2solv [-R] [-X] [-A] [-o <out.solv>] <dir>\n"
	  "  Convert a repository in <dir> to a solv file\n"
	  "  -h : print help & exit\n"
	  "  -o <out.solv>: write to this file instead of stdout\n"
	  "  -F : add filelist\n"
	  "  -R : also search subdirectories for rpms\n"
	  "  -X : generate pattern/product pseudo packages\n"
	  "  -A : add appdata packages\n"
	 );
   exit(status);
}

int
main(int argc, char **argv)
{
  int c, res;
  int repotype = 0;
  char *outfile = 0;
  char *dir;
  struct stat stb;
  
  Pool *pool = pool_create();
  Repo *repo = repo_create(pool, "<repo>");

  while ((c = getopt(argc, argv, "hAXRFCo:")) >= 0)
    {
      switch(c)
	{
        case 'h':
          usage(0);
          break;
	case 'X':
#ifdef SUSE
	  add_auto = 1;
#endif
	  break;
	case 'A':
#ifdef ENABLE_APPDATA
	  add_appdata = 1;
#endif
	  break;
	case 'R':
	  repotype = REPO_PLAINDIR;
	  recursive = 1;
	  break;
	case 'F':
	  add_filelist = 1;
	  break;
	case 'C':
	  add_changelog = 1;
	  break;
	case 'o':
	  outfile = optarg;
	  break;
        default:
          usage(1);
          break;
	}
    }
  if (optind + 1 != argc)
    usage(1);
  dir = argv[optind];
  if (stat(dir, &stb))
    {
      perror(dir);
      exit(1);
    }
  if (!S_ISDIR(stb.st_mode))
    {
      fprintf(stderr, "%s: not a directory\n", dir);
      exit(1);
    }
  dir = solv_strdup(dir);
  if (repotype == 0)
    repotype = autodetect_repotype(pool, dir);

  switch (repotype)
    {
    case REPO_RPMMD:
      res = read_rpmmd_repo(repo, dir);
      break;
    case REPO_RPMMD_REPODATA:
      dir = solv_dupappend(dir, "/repodata", 0);
      res = read_rpmmd_repo(repo, dir);
      break;
    case REPO_SUSETAGS:
      res = read_susetags_repo(repo, dir);
      break;
    case REPO_PLAINDIR:
      res = read_plaindir_repo(repo, dir);
      break;
    default:
      fprintf(stderr, "unknown repotype %d\n", repotype);
      exit(1);
    }
  if (outfile && freopen(outfile, "w", stdout) == 0)
    {
      perror(outfile);
      exit(1);
    }
#ifdef SUSE
  if (add_auto)
    repo_add_autopattern(repo, 0);
#endif
  tool_write(repo, 0, 0);
  if (fflush(stdout))
    {
      perror("fflush");
      exit(1);
    }
  pool_free(pool);
  solv_free(dir);
  exit(res);
}
