/*
 *  SPDX-FileCopyrightText: Copyright © 2006 Keith Packard <keithp@keithp.com>
 *
 *  SPDX-License-Identifier: GPL-2.0+
 */

#ifndef _CVS_H_
#define _CVS_H_

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

/*
 * Condition in various optimization hacks.  You almost certainly
 * don't want to turn any of these off; the condition symbols are
 * present more as documentation of the program structure than
 * anything else
 */
#define REDBLACK /* Use red-black trees for faster symbol loookup */
#define TREEPACK /* Use tree-packing optimization */
#define USE_MMAP /* Memory-mapped I/O */

#include "cvstypes.h"
/*
 * CVS_MAX_BRANCHWIDTH should match the number in the longrev test.
 * If it goes above 128 some bitfield widths in rev_ref must increase.
 */
#define CVS_MAX_DIGITS 10 /* max digits in decimal numbers */
#define CVS_MAX_BRANCHWIDTH 10
#define CVS_MAX_DEPTH (2 * CVS_MAX_BRANCHWIDTH + 2)
#define CVS_MAX_REV_LEN (CVS_MAX_DEPTH * (CVS_MAX_DIGITS + 1))

/*
 * Structures built by master file parsing begin.
 */

typedef struct _cvs_number {
	/* digested form of a CVS revision */
	short c;
	short n[CVS_MAX_DEPTH];
} cvs_number;

extern const cvs_number cvs_zero;

struct _cvs_version;
struct _cvs_patch;

typedef struct node {
	struct node *hash_next;
	struct _cvs_version *version;
	struct _cvs_patch *patch;
	struct _cvs_commit *commit;
	struct node *next;
	struct node *to;
	struct node *down;
	struct node *sib;
	const cvs_number *number;
	flag starts;
} node_t;

#define NODE_HASH_SIZE 97

typedef struct nodehash {
	node_t *table[NODE_HASH_SIZE];
	int nentries;
	node_t *head_node;
} nodehash_t;

typedef struct _cvs_symbol {
	/* a CVS symbol-to-revision association */
	struct _cvs_symbol *next;
	const char *symbol_name;
	const cvs_number *number;
} cvs_symbol;

typedef struct _cvs_branch {
	/* a CVS branch name */
	struct _cvs_branch *next;
	const cvs_number *number;
	node_t *node;
} cvs_branch;

typedef struct _cvs_version {
	/* metadata of a delta within a CVS file */
	struct _cvs_version *next;
	const char *restrict author;
	const char *restrict state;
	const char *restrict commitid;
	cvs_branch *branches;
	node_t *node;
	const cvs_number *restrict number;
	cvstime_t date;
	const cvs_number *restrict parent; /* next in ,v file */
	flag dead;
} cvs_version;

typedef struct _cvs_text {
	/* a reference to a @-encoded text fragment in an rcs file */
	const char *filename;
	size_t length; /* includes terminating '@' */
	off_t offset;  /* position of initial '@' */
} cvs_text;

typedef struct _cvs_patch {
	/* a CVS patch structure */
	struct _cvs_patch *next;
	const cvs_number *number;
	const char *log;
	cvs_text text;
	node_t *node;
} cvs_patch;

struct out_buffer_type {
	char *text, *ptr, *end_of_text;
	size_t size;
};

struct in_buffer_type {
	unsigned char *buffer;
	unsigned char *ptr;
	int read_count;
};

typedef struct _edit_line {
	unsigned char *ptr;
	size_t length;
	int has_stringdelim;
} editline_t;

/* Don't modify this without syncing expand_names in generate.c */
enum expand_mode {
	EXPANDKKV,  /* default form, $<key>: <value>$ */
	EXPANDKKVL, /* like KKV but with locker's name inserted */
	EXPANDKK,   /* keyword-only expansion, $<key>$ */
	EXPANDKV,   /* value-only expansion, $<value>$ */
	EXPANDKO,   /* old-value expansion */
	EXPANDKB,   /* old-value with no EOL normalization */
};

typedef struct _editbuffer {
	const char *Glog;
	int Gkvlen;
	char *Gkeyval;
	char const *Gfilename;
	char *Gabspath;
	cvs_version *Gversion;
	char Gversion_number[CVS_MAX_REV_LEN];
	struct out_buffer_type *Goutbuf;
	struct in_buffer_type in_buffer_store;
	int line_len; /* temporary used for insertline */
	int has_stringdelim;
	enum expand_mode Gexpand;
	/*
	 * Gline contains pointers to the lines in the current edit buffer
	 * It is a 0-origin array that represents Glinemax-Ggapsize lines.
	 * Gline[0 .. Ggap-1] and Gline[Ggap+Ggapsize .. Glinemax-1] hold
	 * pointers to lines.  Gline[Ggap .. Ggap+Ggapsize-1] contains garbage.
	 * Any @s in lines are duplicated.
	 * Lines are terminated by \n, or(for a last partial line only) by
	 * single @.
	 */
	struct frame {
		node_t *next_branch;
		node_t *node;
		unsigned char *node_text;
		editline_t *line;
		size_t gap, gapsize, linemax;
	} stack[CVS_MAX_DEPTH / 2], *current;
#ifdef USE_MMAP
	/* A recently used list of mmapped files */
	struct text_map {
		const char *filename;
		unsigned char *base;
		size_t size;
	} text_map;
#endif /* USE_MMAP */
} editbuffer_t;

#define Gline(eb) eb->current->line
#define Ggap(eb) eb->current->gap
#define Ggapsize(eb) eb->current->gapsize
#define Glinemax(eb) eb->current->linemax
#define Gnode_text(eb) eb->current->node_text
#define Ginbuf(eb) (&eb->in_buffer_store)

typedef struct _generator {
	/* isolate parts of a CVS file context required for snapshot generation
	 */
	const char *master_name;
	enum expand_mode expand;
	cvs_version *versions;
	cvs_patch *patches;
	nodehash_t nodehash;
	editbuffer_t editbuffer;
} generator_t;

typedef struct {
	/* this represents the entire metadata content of a CVS master file */
	const char *export_name;
	cvs_symbol *symbols;
#ifdef REDBLACK
	struct rbtree_node *symbols_by_name;
#endif /* REDBLACK */
	const char *description;
	generator_t gen;
	const cvs_number *head;
	const cvs_number *branch;
	cvstime_t skew_vulnerable;
	serial_t nversions;
	mode_t mode;
	unsigned short verbose;
	bool vendor_warning;
} cvs_file;

typedef struct _master_dir {
	/* directory reference for a master */
	const char *name;
	const struct _master_dir *parent;
} master_dir;

typedef struct _rev_master {
	/* information shared by all revisions of a master */
	const char *name;
	const char *fileop_name;
	const master_dir *dir;
	struct _cvs_commit *commits;
	serial_t ncommits;
	mode_t mode;
} rev_master;

/*
 * Structures built by master file parsing end.
 */

/* revdir should be considered an opaque type outside of revdir.c, but
 * we want to take advantage of being able to pack the struct
 * in a parent struct. Represents a packed list of files.
 * manipulate using the interface defined in revdir.h
 */
#ifdef TREEPACK

typedef struct _rev_pack rev_pack;

typedef struct _revdir {
	const rev_pack *revpack;
} revdir;

#else

typedef struct _file_list file_list;

typedef struct _revdir {
	/* dirs is slightly misleading, as there may be subdirs in the same
	 * entry */
	unsigned short ndirs;
	file_list **dirs;
} revdir;

#endif /* TREEPACK */

/*
 * Tricky polymorphism hack to reduce working set size for large repos
 * begins here.
 *
 * In Keith's original code, cvs_commit and git_commit were the same struct,
 * with different semantics at different points in the program's lifetime.
 * Early, the struct described a CVS revision of one file.  Later, it
 * described a gitspace commit potentially pointing at several files.  The
 * first semantics held for instances created while scanning CVS master files;
 * the second for those generated by a git_commit_build() call during
 * global branch collation.
 *
 * The problem with this is that some members in the late interpretation
 * are unused in the earlier one; we want to get rid of as many as possible to
 * reduce working-set size.  So we exploit the fact that all instances are
 * malloced by splitting the struct into two types.
 *
 * Because the rev_ref structure contains a pointer to the early
 * version, a few casts are needed at points in the code that require
 * the late-version fields.  (This is mainly in export.c and
 * graph.c).  All these casts are marked with PUNNING in the code.
 *
 * If the common fields in these structures don't remain in the same order,
 * bad things will happen.
 */

typedef struct _cvs_commit {
	/* a CVS revision */
	struct _cvs_commit *parent;
	const char *restrict log;
	const char *restrict author;
	const char *restrict commitid;
	cvstime_t date;
	serial_t serial;
	branchcount_t refcount;
	unsigned
	    tail : 1; // True if this is the first on a branch (after the root)
	unsigned
	    tailed : 1;    // True if this commit has a child wyth tail bit set
	unsigned dead : 1; // Carries forward CVS "dead" state
	/* CVS-only members begin here */
	bool emitted : 1;
	hash_t hash;
	/* Shortcut to master->dir, more space but less dereferences
	 * in the hottest inner loop in revdir
	 */
	const master_dir *dir;
	const rev_master *master;
	struct _git_commit *gitspace;
	const cvs_number *number;
} cvs_commit;

typedef struct _git_commit {
	/* a gitspace changeset */
	struct _git_commit *parent;
	const char *restrict log;
	const char *restrict author;
	const char *restrict commitid;
	cvstime_t date;
	serial_t serial;
	branchcount_t refcount;
	unsigned tail : 1;
	unsigned tailed : 1;
	unsigned dead : 1;
	/* gitspace-only members begin here. */
	revdir revdir;
} git_commit;

typedef struct _rev_ref {
	/* a reference to a branch head */
	struct _rev_ref *next;
	struct _rev_ref *parent;  /* link into tree */
	cvs_commit *commit;       /* or a git_commit in gitspace */
	const char *ref_name;     /* symboilic name of the branch */
	const cvs_number *number; /* not used in gitspace */
	unsigned depth : 7;       /* branch depth in tree (1 is trunk) */
	unsigned degree : 7;      /* # of digits in original CVS version */
	flag shown : 1;           /* only used in graph emission */
	flag tail : 1;
} rev_ref;

/* Type punning, previously cvs_master and git_repo were rev_lists
 * underneath, now cvs_master is backed by an array. We still have
 * some general functions that just operate on the heads member, so
 * this lets us use them from both git and cvs sides.
 */
typedef struct _head_list {
	rev_ref *heads;
} head_list;

typedef struct _rev_list {
	rev_ref *heads;
	struct _rev_list *next;
} rev_list;

/*
 * These are created only as an attempt to make the code more readable.
 * The problem they address is that a rev_list pointer can have
 * different semantics depending on whether code is going to iterate
 * through its next pointer or its heads, and what stage of the program
 * we're in.  This makes functions with undifferentiated rev_list arguments
 * hard to read.  The convention we use is that a rev_list variable, member
 * or formal argument can accept any of these, but we try to be more
 * specific in order to express the domain of a function.
 */
typedef head_list cvs_master; /* represents a single cvs master */
/* a CVS repo is represented by an array of such */
typedef rev_list git_repo; /* represents a gitspace DAG */

typedef struct _cvs_commit_list {
	struct _cvs_commit_list *next;
	cvs_commit *file;
} cvs_commit_list;

typedef struct _rev_diff {
	cvs_commit_list *restrict del;
	cvs_commit_list *restrict add;
	int ndel;
	int nadd;
} rev_diff;

typedef struct _cvs_author {
	struct _cvs_author *next;
	const char *name;
	const char *full;
	const char *email;
	const char *timezone;
} cvs_author;

/*
 * Use _printflike(M, N) to mark functions that take printf-like formats
 * and parameter lists.  M refers to the format arg, and N refers to
 * the first variable arg (typically M+1), or N = 0 if the function takes
 * a va_list.
 *
 * If the compiler is GCC version 2.7 or later, this is implemented
 * using __attribute__((__format__(...))).
 *
 * OS X lacks __alloc_size__,
 */
#if defined(__GNUC__) &&                                                       \
    ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)) &&              \
    !defined(__APPLE__)
#define _printflike(fmtarg, firstvararg)                                       \
	__attribute__((__format__(__printf__, fmtarg, firstvararg)))
#define _alloclike(sizearg) __attribute__((__alloc_size__(sizearg)))
#define _alloclike2(sizearg1, sizearg2)                                        \
	__attribute__((__alloc_size__(sizearg1, sizearg2)))
#define _malloclike __attribute__((__malloc__))
#define _noreturn __attribute__((__noreturn__))
#define _pure __attribute__((__noreturn__))
#define _alignof(T) __alignof__(T)
#define _unused __attribute__((unused))
#else
#define _printflike(fmtarg, firstvararg) /* nothing */
#define _alloclike(sizearg)              /* nothing */
#define _alloclike2(sizearg1, sizearg2)  /* nothing */
#define _malloclike                      /* nothing */
#define _noreturn                        /* nothing */
#define _pure                            /* nothing */
#define _alignof(T) sizeof(long double)
#define _unused /* nothing */
#endif

/* For versions of Mac OS X before Sierra */
#if defined(__APPLE__) && !defined(CLOCK_REALTIME)
/* we mock this in utils.c; the CLOCK_REALTIME value is not used */
typedef int clockid_t;
int clock_gettime(clockid_t clock_id, struct timespec *tp);
#define CLOCK_REALTIME 0
#endif

cvs_author *fullname(const char *);

bool load_author_map(const char *);

char *cvstime2rfc3339(const cvstime_t date);

cvs_number lex_number(const char *);

cvstime_t lex_date(const cvs_number *n, void *, cvs_file *cvs);

void atom_dir_init(void);

cvs_commit *cvs_master_digest(cvs_file *cvs, cvs_master *cm,
                              rev_master *master);

git_repo *collate_to_changesets(cvs_master *masters, size_t nmasters,
                                int verbose);

enum { Ncommits = 256 };

typedef struct _chunk {
	struct _chunk *next;
	cvs_commit *v[Ncommits];
} chunk_t;

typedef struct _tag {
	struct _tag *next;
	struct _tag *hash_next;
	const char *name;
	chunk_t *commits;
	int count;
	int left;
	git_commit *commit;
	rev_ref *parent;
	const char *last;
} tag_t;

/*
 * Be careful when modifying this structure.
 * It has been observed that moving vendor_warming
 * to the end breaks regression tests, somehow
 * interfering with the setting of is_cvs.
 */
typedef struct _forest {
	int filecount;
	off_t textsize;
	int errcount;
	bool is_cvs;
	bool vendor_warning;
	cvs_master *cvs;
	git_repo *git;
	generator_t *generators;
	cvstime_t skew_vulnerable;
	unsigned int total_revisions;
} forest_t;

extern tag_t *all_tags;
extern size_t tag_count;
extern const master_dir *root_dir;

void tag_commit(cvs_commit *c, const char *name, cvs_file *cvsfile);
cvs_commit **tagged(tag_t *tag);
void discard_tags(void);

typedef struct _import_options {
	bool promiscuous;
	int verbose;
	ssize_t striplen;
} import_options_t;

typedef struct _export_options {
	struct timespec start_time;
	char *branch_prefix;
	time_t fromtime;
	FILE *revision_map;
	bool reposurgeon;
	bool embed_ids;
	bool force_dates;
	bool authorlist;
	bool progress;
} export_options_t;

typedef struct _export_stats {
	long export_total_commits;
	double snapsize;
} export_stats_t;

void analyze_masters(int argc, const char *argv[0], import_options_t *options,
                     forest_t *forest);

enum expand_mode expand_override(char const *s);

bool cvs_is_head(const cvs_number *n);

bool cvs_same_branch(const cvs_number *a, const cvs_number *b);

bool cvs_number_equal(const cvs_number *n1, const cvs_number *n2);

int cvs_number_compare(const cvs_number *a, const cvs_number *b);

int cvs_number_compare_n(const cvs_number *a, const cvs_number *b, int l);

bool cvs_is_branch_of(const cvs_number *trunk, const cvs_number *branch);

int cvs_number_degree(const cvs_number *a);

cvs_number cvs_previous_rev(const cvs_number *n);

cvs_number cvs_master_rev(const cvs_number *n);

cvs_number cvs_branch_head(cvs_file *f, const cvs_number *branch);

cvs_number cvs_branch_parent(cvs_file *f, const cvs_number *branch);

node_t *cvs_find_version(const cvs_file *cvs, const cvs_number *number);

bool cvs_is_trunk(const cvs_number *number);

bool cvs_is_vendor(const cvs_number *number);

void cvs_file_free(cvs_file *cvs);

void generator_free(generator_t *gen);

char *cvs_number_string(const cvs_number *n, char *str, size_t maxlen);

void dump_ref_name(FILE *f, rev_ref *ref);

char *stringify_revision(const char *name, const char *sep,
                         const cvs_number *number, char *buf, size_t bufsz);

void dump_number_file(FILE *f, const char *name, const cvs_number *number);

void dump_number(const char *name, const cvs_number *number);

void dump_log(FILE *f, const char *log);

void dump_git_commit(const git_commit *e, FILE *);

void dump_rev_head(rev_ref *h, FILE *);

void dump_rev_graph(git_repo *rl, const char *title);

void dump_cvs_graph(forest_t *, int);

char *atom(const char *string);

cvs_number *atom_cvs_number(const cvs_number n);

unsigned long hash_cvs_number(const cvs_number *const key);

void discard_atoms(void);

rev_ref *rev_list_add_head(head_list *rl, cvs_commit *commit, const char *name,
                           int degree);

rev_diff *git_commit_diff(git_commit *old, git_commit *new);

void rev_diff_free(rev_diff *d);

void rev_list_set_tail(head_list *rl);

bool rev_list_validate(head_list *rl);

int path_deep_compare(const void *a, const void *b);

#define time_compare(a, b) ((long)(a) - (long)(b))

void export_commits(forest_t *forest, export_options_t *opts,
                    export_stats_t *stats);

void export_authors(forest_t *forest, export_options_t *opts);

void free_author_map(void);

void generate_files(generator_t *gen, export_options_t *opts,
                    void (*hook)(node_t *node, void *buf, size_t len,
                                 export_options_t *popts));

/* xnew(T) allocates aligned (packed) storage. It never returns NULL */
#define xnew(T, legend) xnewf(T, 0, legend)
/* xnewf(T,x) allocates storage with a flexible array member */
#define xnewf(T, extra, legend)                                                \
	(T *)xmemalign(_alignof(T), sizeof(T) + (extra), legend)
/* xnewa(T,n) allocates storage for an array of types */
#define xnewa(T, n, legend) (T *)xmemalign(_alignof(T), (n) * sizeof(T), legend)

#if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
void *xmemalign(size_t align, size_t size, char const *legend)
    _alloclike(2) _malloclike;
#else
#define xmemalign(align, size, legend) xmalloc(size, legend)
#endif

void *xmalloc(size_t size, char const *legend) _alloclike(1) _malloclike;

void *xcalloc(size_t, size_t, char const *legend) _alloclike2(1, 2) _malloclike;

void *xrealloc(void *ptr, size_t size, char const *legend) _alloclike(2);

void announce(char const *format, ...) _printflike(1, 2);

void warn(char const *format, ...) _printflike(1, 2);

void debugmsg(char const *format, ...) _printflike(1, 2);

bool enable_debug(char);

void progress_interrupt(void);

void fatal_error(char const *format, ...) _printflike(1, 2) _noreturn;

void fatal_system_error(char const *format, ...) _printflike(1, 2) _noreturn;

void hash_version(nodehash_t *, cvs_version *);
void hash_patch(nodehash_t *, cvs_patch *);
void hash_branch(nodehash_t *, cvs_branch *);
void clean_hash(nodehash_t *);
void build_branches(nodehash_t *);

void progress_begin(const char * /*msg*/, const int /*max*/);
void progress_step(void);
void progress_jump(const int /*count*/);
void progress_end(const char * /*format*/, ...) _printflike(1, 2);

#define NANOSCALE 1000000000.0
#define nanosec(ts) ((ts)->tv_nsec + NANOSCALE * (ts)->tv_sec)
#define seconds_diff(a, b) ((nanosec(a) - nanosec(b)) / NANOSCALE)

/* Work around glitches in Bison and Flex */

/* FIXME: remove once the Bison bug requiring this is fixed */
#define YY_DECL                                                                \
	int yylex(YYSTYPE *yylval_param, yyscan_t yyscanner, cvs_file *cvs)

/*
 * Debug enables
 */
#define DEBUG_ORDER1 'o'   /* order debugging - phase 1 */
#define DEBUG_EXPORT 'e'   /* export debugging */
#define DEBUG_SORT 's'     /* instrument commit sorting */
#define DEBUG_GITSPACE 'g' /* instrument gitspace side of commit-buildimg */
#define DEBUG_FILES 'f'    /* debug file and blob operations */
#define DEBUG_ORDER2 'p'   /* order debugging in export */
#define DEBUG_CVSDEBUG 'c' /* CVS analysis */

#define MAXDOPTS 16

/*
 * Statistics gathering.
 */
extern unsigned int warncount;
extern unsigned int natoms;

/*
 * Global options
 */

extern bool nowarn, noignores;
extern int commit_time_window;
extern bool trust_commitids;
extern FILE *logfile;
extern bool difffiles;
extern char debugopts[MAXDOPTS];

extern bool progress;
#define STATUS stderr
#define NO_MAX -1

#ifdef THREADS
extern int threads;
#endif /* THREADS */

#endif /* _CVS_H_ */

/* Local Variables:    */
/* mode: c             */
/* c-basic-offset: 8   */
/* indent-tabs-mode: t */
/* End:                */
