+/* -*-c-*-
+ *
+ * $Id: quine.c,v 1.1 1999/04/28 19:58:07 mdw Exp $
+ *
+ * Output a program's source code as a quine file
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Quine
+ *
+ * Quine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Quine is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Quine; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: quine.c,v $
+ * Revision 1.1 1999/04/28 19:58:07 mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- POSIX headers --- */
+
+#ifndef QUINE_PORTABLE
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+/* --- Local headers --- */
+
+#include "mdwopt.h"
+#include "quine.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @die@ --- *
+ *
+ * Arguments: @const char *f@ = a @printf@-style format string
+ * @...@ = other arguments
+ *
+ * Returns: Never.
+ *
+ * Use: Reports an error and hari-kiris. Like @moan@ above, only
+ * more permanent.
+ */
+
+void die(const char *f, ...)
+{
+ va_list ap;
+ va_start(ap, f);
+ fprintf(stderr, "%s: ", optprog);
+ vfprintf(stderr, f, ap);
+ va_end(ap);
+ putc('\n', stderr);
+ exit(EXIT_FAILURE);
+}
+
+/* --- @quine__banner@ --- *
+ *
+ * Arguments: @FILE *fp@ = file to write on
+ *
+ * Returns: ---
+ *
+ * Use: Writes a banner to the given file.
+ */
+
+static void quine__banner(FILE *fp)
+{
+ fprintf(fp, "%s quine generator v. 1.1\n", optprog);
+}
+
+/* --- @quine__usage@ --- *
+ *
+ * Arguments: @FILE *fp@ = file to write on
+ *
+ * Returns: ---
+ *
+ * Use: Writes a usage note to the given file.
+ */
+
+static void quine__usage(FILE *fp)
+{
+ fprintf(fp, "Usage: %s [-hv0n] [-f FILE] [FILE...]\n", optprog);
+}
+
+/* --- @quine__help@ --- *
+ *
+ * Arguments: @FILE *fp@ = file to write on
+ *
+ * Returns: ---
+ *
+ * Use: Writes a help message to the given file.
+ */
+
+static void quine__help(FILE *fp)
+{
+ quine__banner(fp);
+ putc('\n', fp);
+ quine__usage(fp);
+ putc('\n', fp);
+ fputs(
+"Given a list of source files, constructs a C source file which will write\n"
+"the sources, and itself, when requested. In this way, it is easy to\n"
+"create programs which contain their own source distributions, thus\n"
+"fulfilling the requirements of the GNU General Public License with a\n"
+"single binary program.\n"
+"\n"
+"The following options are provided:\n"
+"\n"
+"-h, --help\t\tdisplay this help message\n"
+"-v, --version\t\tshow the program's version number\n"
+"-0, --null\t\tread null terminated filenames\n"
+"-l, --qqlib\t\tused while bootstrapping `quine'\n"
+"-f, --file=FILE\t\tread filenames from FILE\n"
+"-o, --output=FILE\twrite output quine data to FILE\n"
+"-q, --quine\t\tbuild `quine's source distribution\n"
+"-Q, --quine-dump\twrite `quine's source code to standard output\n",
+fp);
+}
+
+/* --- @quine__readLine@ --- *
+ *
+ * Arguments: @FILE *fp@ = pointer to input file
+ * @char **p@ = address of pointer to set up
+ * @size_t *sz@ = address of array length
+ * @const char *delim@ = array of delimiters
+ *
+ * Returns: Pointer to allocated line if all OK, otherwise zero.
+ *
+ * Use: Reads a line of input from the file.
+ */
+
+static char *quine__readLine(FILE *fp, char **p, size_t *sz,
+ const char *delim)
+{
+ size_t len = 0;
+ char *q;
+ const char *d;
+ int ch;
+
+ /* --- If I couldn't make my initial buffer, there's no hope --- */
+
+ if (!*p) {
+ *sz = 64;
+ *p = malloc(*sz);
+ if (!*p)
+ die("out of memory");
+ }
+
+ /* --- Skip over initial delimiters --- */
+
+skip_delims:
+ ch = getc(fp);
+ if (ch == EOF)
+ return (0);
+ d = delim;
+ do {
+ if (ch == (unsigned char)*d)
+ goto skip_delims;
+ } while (*d++);
+
+ /* --- Read characters into the buffer --- */
+
+ for (;;) {
+
+ /* --- If not enough buffer space, double the buffer --- *
+ *
+ * The plus-one is for the null byte on the end.
+ */
+
+ if (len + 1 >= *sz) {
+ *sz <<= 1;
+ q = realloc(p, *sz);
+ if (!q)
+ die("out of memory");
+ *p = q;
+ }
+ (*p)[len++] = ch;
+
+ /* --- Read and check the next byte --- */
+
+ ch = getc(fp);
+ if (ch == EOF)
+ goto done;
+ d = delim;
+ do {
+ if (ch == (unsigned char)*d)
+ goto done;
+ } while (*d++);
+ }
+
+ /* --- Terminate the string and return --- */
+
+done:
+ (*p)[len++] = 0;
+ return (*p);
+}
+
+/* --- @quine__doFile@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file handle
+ * @FILE *ifp@ = input file
+ *
+ * Returns: ---
+ *
+ * Use: Outputs the contents of a quine block containing the given
+ * file.
+ */
+
+static void quine__doFile(FILE *fp, FILE *ifp)
+{
+ char *p;
+ char buff[80];
+ int ch;
+
+ for (;;) {
+ p = buff;
+ ch = getc(ifp);
+ if (ch == EOF)
+ break;
+ for (;;) {
+ *p++ = ch;
+ if (ch == '\n' || p >= buff + sizeof(buff))
+ break;
+ ch = getc(ifp);
+ }
+ fputc('\"', fp);
+ for (p = buff; p < buff + sizeof(buff); p++) {
+ switch (*p) {
+ case '\"': fputs("%q", fp); break;
+ case '\\': fputs("%b", fp); break;
+ case '\t': fputs("%t", fp); break;
+ case '\n': fputs("%n", fp); break;
+ case '\a': fputs("%a", fp); break;
+ case '\r': fputs("%r", fp); break;
+ case '\0': fputs("%0", fp); break;
+ case '%': fputs("%%", fp); break;
+ default:
+ if (isprint((unsigned char)*p))
+ fputc(*p, fp);
+ else
+ fprintf(fp, "%%x%02x%%", (unsigned char)*p);
+ break;
+ }
+ if (*p == '\n')
+ break;
+ }
+ fputs("\",\n", fp);
+ }
+ fclose(ifp);
+
+ fputs("0\n};\n\n", fp);
+}
+
+/* --- @quine__file@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file handle
+ * @const char *name@ = name of input file
+ * @size_t fno@ = number of this file
+ *
+ * Returns: ---
+ *
+ * Use: Outputs a quine block containing the given file.
+ */
+
+static void quine__file(FILE *fp, const char *name, size_t fno)
+{
+ FILE *ifp;
+
+ fprintf(fp, "static const char *qq__c_%lx[] = {\n", (unsigned long)fno);
+
+ if ((ifp = fopen(name, "r")) == 0)
+ die("couldn't read `%s': %s", name, strerror(errno));
+ fprintf(fp, "/* Filename: */ \"%s\",\n", name);
+
+ /* --- Write file's mode to the output --- */
+
+#ifndef QUINE_PORTABLE
+ {
+ struct stat st;
+ if (stat(name, &st) == 0)
+ fprintf(fp, "/* Mode: */ \"%%!0%03o\",\n", st.st_mode & 07777);
+ }
+#endif
+
+ quine__doFile(fp, ifp);
+}
+
+/* --- Dummy quine library --- */
+
+#ifdef QQ_XQUINE
+
+const char *qq_qqlib[] = {
+ "/* --- No quine library available in xquine --- */%n",
+ 0
+};
+
+#endif
+
+/* --- @main@ --- *
+ *
+ * Arguments: @int argc@ = number of command line arguments
+ * @char *argv[]@ = pointer to the command line arguments
+ *
+ * Returns: Zero if successful, @EXIT_FAILURE@ if not.
+ *
+ * Use: Given a number of source files, outputs C code which prints
+ * them (and itself).
+ */
+
+int main(int argc, char *argv[])
+{
+ char *inf = 0;
+ char *outf = 0;
+ char *qqlibf = 0;
+ unsigned int f = 0;
+ FILE *infp = 0, *outfp = 0;
+ size_t files = 0;
+
+ enum {
+ f_duff = 1,
+ f_null = 2,
+ f_noFiles = 4
+ };
+
+ /* --- Scan the command line arguments --- */
+
+ for (;;) {
+ int i;
+
+ static struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "null", 0, 0, '0' },
+ { "file", gFlag_argReq, 0, 'f' },
+ { "nofiles", 0, 0, 'n' },
+ { "output", gFlag_argReq, 0, 'o' },
+ { "qqlib", gFlag_argReq, 0, 'l' },
+#if !defined(QQ_XQUINE) && !defined(QQ_YQUINE)
+ { "quine", 0, 0, 'q' },
+ { "quine-dump", 0, 0, 'Q' },
+#endif
+ { 0, 0, 0, 0 }
+ };
+
+#if !defined(QQ_XQUINE) && !defined(QQ_YQUINE)
+# define OPTS "hv0nf:o:lqQ"
+#else
+# define OPTS "hv0nf:o:l"
+#endif
+
+ i = mdwopt(argc, argv, OPTS, opts, 0, 0, 0);
+
+ if (i < 0)
+ break;
+
+ switch (i) {
+ case 'h':
+ quine__help(stdout);
+ exit(0);
+ case 'v':
+ quine__banner(stdout);
+ exit(0);
+ case '0':
+ f |= f_null;
+ break;
+ case 'f':
+ inf = optarg;
+ break;
+ case 'o':
+ outf = optarg;
+ break;
+ case 'l':
+ qqlibf = optarg;
+ break;
+ case 'n':
+ f |= f_noFiles;
+ break;
+#if !defined(QQ_XQUINE) && !defined(QQ_YQUINE)
+ case 'q':
+ if (qq_create())
+ die("couldn't build source distribution: %s", strerror(errno));
+ exit(0);
+ case 'Q':
+ qq_dump(stdout);
+ exit(0);
+ break;
+#endif
+ default:
+ f |= f_duff;
+ break;
+ }
+ }
+
+ if (f & f_duff) {
+ quine__usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ /* --- Output a file as the qqlib library --- */
+
+ if (qqlibf) {
+ infp = fopen(qqlibf, "r");
+ if (!infp)
+ die("couldn't open input file `%s': %s", inf, strerror(errno));
+
+ if (!outf)
+ outf = "qqlout.c";
+ if (strcmp(outf, "-") == 0)
+ outfp = stdout;
+ else
+ outfp = fopen(outf, "w");
+ if (!outfp)
+ die("couldn't open output file `%s': %s", outf, strerror(errno));
+
+ fputs("/* quine library data */\n\n"
+ "const char *qq_qqlib[] = {\n", outfp);
+ quine__doFile(outfp, infp);
+ fclose(infp);
+ fclose(outfp);
+ exit(0);
+ }
+
+ /* --- Time to start work --- */
+
+ if (~f & f_noFiles && (inf || optind == argc)) {
+ if (!inf || strcmp(inf, "-") == 0)
+ infp = stdin;
+ else
+ infp = fopen(inf, "r");
+ if (!infp)
+ die("couldn't open input file `%s': %s", inf, strerror(errno));
+ }
+
+ if (!outf)
+ outf = "qqout.c";
+ if (strcmp(outf, "-") == 0)
+ outfp = stdout;
+ else
+ outfp = fopen(outf, "w");
+ if (!outfp)
+ die("couldn't open output file `%s': %s", outf, strerror(errno));
+
+ /* --- Output a header --- */
+
+ qq_head(outfp);
+ fputs("/* --- file contents tables --- */\n\n", outfp);
+
+ /* --- Scan the command line first --- */
+
+ while (~f & f_noFiles && optind < argc)
+ quine__file(outfp, argv[optind++], files++);
+
+ /* --- Now read the input file --- */
+
+ if (infp) {
+ char *p = 0;
+ char *q;
+ const char *del;
+ size_t sz = 0;
+
+ if (f & f_null)
+ del = "";
+ else
+ del = " \n\t";
+
+ while ((q = quine__readLine(infp, &p, &sz, del)) != 0)
+ quine__file(outfp, q, files++);
+
+ if (fclose(infp))
+ die("error reading input: %s", strerror(errno));
+ }
+
+ /* --- And output the trailer --- */
+
+ qq_tail(outfp, qq_qqlib, files,
+ strcmp(outf, "-") == 0 ? "<stdout>" : outf);
+
+ /* --- Done --- */
+
+ if (outfp != stdout && fclose(outfp))
+ die("error writing output: %s", strerror(errno));
+
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/