Initial commit master
authorRobert Pengelly <robertapengelly@hotmail.com>
Thu, 26 Feb 2026 18:57:14 +0000 (18:57 +0000)
committerRobert Pengelly <robertapengelly@hotmail.com>
Thu, 26 Feb 2026 18:57:14 +0000 (18:57 +0000)
LICENSE [new file with mode: 0755]
Makefile.unix [new file with mode: 0755]
Makefile.w32 [new file with mode: 0755]
README.md [new file with mode: 0755]
lib.c [new file with mode: 0755]
lib.h [new file with mode: 0755]
report.c [new file with mode: 0755]
report.h [new file with mode: 0755]
tar.c [new file with mode: 0755]
tar.h [new file with mode: 0755]

diff --git a/LICENSE b/LICENSE
new file mode 100755 (executable)
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/Makefile.unix b/Makefile.unix
new file mode 100755 (executable)
index 0000000..5b21898
--- /dev/null
@@ -0,0 +1,26 @@
+#******************************************************************************
+# @file             Makefile.unix
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -O2 -Wall -Werror -Wextra -ansi -pedantic -std=c90
+
+CSRC                :=  lib.c tar.c report.c
+
+ifeq ($(OS), Windows_NT)
+all: tar.exe
+
+tar.exe: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+else
+all: tar
+
+tar: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+endif
+
+clean:
+       if [ -f tar.exe ]; then rm -rf tar.exe; fi
+       if [ -f tar ]; then rm -rf tar; fi
diff --git a/Makefile.w32 b/Makefile.w32
new file mode 100755 (executable)
index 0000000..50641d5
--- /dev/null
@@ -0,0 +1,19 @@
+#******************************************************************************
+# @file             Makefile.w32
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -O2 -Wall -Werror -Wextra
+
+CSRC                :=  lib.c tar.c report.c
+
+all: tar.exe
+
+clean:
+       if exist tar.exe ( del /q tar.exe )
+       if exist tar ( del /q tar )
+
+tar.exe: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
diff --git a/README.md b/README.md
new file mode 100755 (executable)
index 0000000..c192a0a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+All source code is Public Domain.
+
+## Obtain the source code
+
+    git clone https://git.candlhat.org/tar.git
+
+## Building
+
+    BSD:
+    
+        Make sure you have gcc and gmake installed then run gmake -f Makefile.unix.
+    
+    Linux:
+    
+        Make sure you have gcc and make installed then run make -f Makefile.unix.
+    
+    macOS:
+    
+        Make sure you have xcode command line tools installed then run make -f Makefile.unix.
+    
+    Windows:
+    
+        Make sure you have mingw installed and the location within your PATH variable then run mingw32-make.exe -f Makefile.w32.
diff --git a/lib.c b/lib.c
new file mode 100755 (executable)
index 0000000..0e13aa4
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,340 @@
+/******************************************************************************
+ * @file            lib.c
+ *****************************************************************************/
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "lib.h"
+#include    "report.h"
+#include    "tar.h"
+
+struct option {
+
+    const char *name;
+    int index, flags;
+
+};
+
+#define     OPTION_NO_ARG               0x0001
+#define     OPTION_HAS_ARG              0x0002
+
+enum options {
+
+    OPTION_IGNORED = 0,
+    OPTION_APPEND,
+    OPTION_CREATE,
+    OPTION_FILE,
+    OPTION_HELP,
+    OPTION_MODE,
+    OPTION_ROOT
+
+};
+
+static struct option opts[] = {
+
+    {   "-C",                       OPTION_ROOT,            OPTION_HAS_ARG  },
+    
+    {   "-c",                       OPTION_CREATE,          OPTION_NO_ARG   },
+    {   "-f",                       OPTION_FILE,            OPTION_HAS_ARG  },
+    {   "-r",                       OPTION_APPEND,          OPTION_NO_ARG   },
+    
+    {   "--append",                 OPTION_APPEND,          OPTION_NO_ARG   },
+    {   "--create",                 OPTION_CREATE,          OPTION_NO_ARG   },
+    {   "--file",                   OPTION_FILE,            OPTION_HAS_ARG  },
+    {   "--help",                   OPTION_HELP,            OPTION_NO_ARG   },
+    {   "--mode",                   OPTION_MODE,            OPTION_HAS_ARG  },
+    
+    {   "--directory",              OPTION_ROOT,            OPTION_HAS_ARG  },
+    {   0,                          0,                      0               }
+
+};
+
+static char *format_path (char *__str) {
+
+    unsigned long len, i;
+    char *p = __str;
+    
+    len = strlen (p);
+    
+    for (i = 0; i < len; i++) {
+    
+        if (p[i] == '\\') {
+            p[i] = '/';
+        }
+        
+        if (p[i] == '/') {
+        
+            i++;
+            
+            while (p[i] == '/' || p[i] == '\\') {
+                memmove (p + i, p + i + 1, strlen (p + i));
+            }
+        
+        }
+    
+    }
+    
+    return xstrdup (p);
+
+}
+
+static int strstart (const char *val, const char **str) {
+
+    const char *p = val;
+    const char *q = *str;
+    
+    while (*p != '\0') {
+    
+        if (*p != *q) {
+            return 0;
+        }
+        
+        ++p;
+        ++q;
+    
+    }
+    
+    *str = q;
+    return 1;
+
+}
+
+static void print_help (int exitval) {
+
+    if (!program_name) {
+        goto _exit;
+    }
+    
+    fprintf (stderr, "Usage: %s [options] [file]...\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    fprintf (stderr, "    -c, --create                  Create a new archive.\n");
+    /*fprintf (stderr, "    -r, --append                  Append files to the end of the archive.\n");*/
+    fprintf (stderr, "\n");
+    fprintf (stderr, "    -f ARCHIVE, --file ARCHIVE    Use archive file or device ARCHIVE.\n");
+    fprintf (stderr, "    --help                        Print this help information.\n");
+    fprintf (stderr, "\n");
+    
+_exit:
+    
+    exit (exitval);
+
+}
+
+char *xstrdup (const char *__str) {
+
+    char *ptr = xmalloc (strlen (__str) + 1);
+    strcpy (ptr, __str);
+    
+    return ptr;
+
+}
+
+void dynarray_add (void *ptab, long *nb_ptr, void *data) {
+
+    int nb, nb_alloc;
+    void **pp;
+    
+    nb = *nb_ptr;
+    pp = *(void ***) ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) ptab = pp;
+    
+    }
+    
+    pp[nb++] = data;
+    *nb_ptr = nb;
+
+}
+
+void parse_args (int argc, char **argv, int optind) {
+
+    const char *optarg, *r;
+    struct option *popt;
+    
+    if (optind >= argc) {
+        print_help (EXIT_FAILURE);
+    }
+    
+    while (optind < argc) {
+    
+        r = argv[optind++];
+        
+        if (r[0] != '-' || r[1] == '\0') {
+        
+            char *p = xstrdup (r);
+            
+            dynarray_add (&state->paths, &state->nb_paths, format_path (p));
+            free (p);
+            
+            continue;
+        
+        }
+        
+        for (popt = opts; popt; popt++) {
+        
+            const char *p1 = popt->name;
+            const char *r1 = r;
+            
+            if (!p1) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "invalid option -- '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+            
+            if (!strstart (p1, &r1)) {
+                continue;
+            }
+            
+            optarg = r1;
+            
+            if (popt->flags & OPTION_HAS_ARG) {
+            
+                if (*optarg == '\0') {
+                
+                    if (optind >= argc) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "argument to '%s' is missing", r);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    optarg = argv[optind++];
+                
+                }
+            
+            } else if (*optarg != '\0') {
+                continue;
+            }
+            
+            break;
+        
+        }
+        
+        switch (popt->index) {
+        
+            case OPTION_APPEND: {
+            
+                state->append = 1;
+                break;
+            
+            }
+            
+            case OPTION_CREATE: {
+            
+                state->create = 1;
+                break;
+            
+            }
+            
+            case OPTION_FILE: {
+            
+                if (state->target) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "multiple archive files provided");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                state->target = xstrdup (optarg);
+                break;
+            
+            }
+            
+            case OPTION_HELP: {
+            
+                print_help (EXIT_SUCCESS);
+                break;
+            
+            }
+            
+            case OPTION_MODE: {
+            
+                if (strlen (optarg) != 3 && strlen (optarg) != 4) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "invalid mode provided");
+                    exit (EXIT_FAILURE);
+                
+                }
+                
+                if (state->mode) { free (state->mode); }
+                state->mode = xmalloc (5);
+                
+                if (strlen (optarg) == 3) {
+                
+                    state->mode[0] = '0';
+                    memcpy (state->mode + 1, optarg, 3);
+                
+                } else {
+                    memcpy (state->mode, optarg, 4);
+                }
+                
+                break;
+            
+            }
+            
+            case OPTION_ROOT: {
+            
+                char *p = xstrdup (optarg);
+                
+                if (state->root) { free (state->root); }
+                state->root = format_path (p);
+                
+                free (p);
+                break;
+            
+            }
+            
+            default: {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unsupported option '%s'", r);
+                exit (EXIT_FAILURE);
+            
+            }
+        
+        }
+    
+    }
+
+}
+
+void *xmalloc (unsigned long __size) {
+
+    void *p = malloc (__size);
+    
+    if (!p && __size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed to allocate %ld bytes of memory", __size);
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (p, 0, __size);
+    return p;
+
+}
+
+void *xrealloc (void *__ptr, unsigned long __size) {
+
+    void *p = realloc (__ptr, __size);
+    
+    if (!p && __size) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed to allocate %ld bytes of memory", __size);
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return p;
+
+}
diff --git a/lib.h b/lib.h
new file mode 100755 (executable)
index 0000000..5f1506f
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,15 @@
+/******************************************************************************
+ * @file            lib.h
+ *****************************************************************************/
+#ifndef     _LIB_H
+#define     _LIB_H
+
+char *xstrdup (const char *__str);
+
+void dynarray_add (void *ptab, long *nb_ptr, void *data);
+void parse_args (int argc, char **argv, int optind);
+
+void *xmalloc (unsigned long __size);
+void *xrealloc (void *__ptr, unsigned long __size);
+
+#endif      /* _LIB_H */
diff --git a/report.c b/report.c
new file mode 100755 (executable)
index 0000000..8e128ef
--- /dev/null
+++ b/report.c
@@ -0,0 +1,150 @@
+/******************************************************************************
+ * @file            report.c
+ *****************************************************************************/
+#include    <stdarg.h>
+#include    <stdio.h>
+#include    <string.h>
+
+#include    "report.h"
+
+unsigned long errors = 0;
+
+#ifndef     __PDOS__
+#if     defined (_WIN32)
+# include   <windows.h>
+static int OriginalConsoleColor = -1;
+#endif
+
+static void reset_console_color (void) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    
+    if (OriginalConsoleColor == -1) { return; }
+    
+    SetConsoleTextAttribute (hStdError, OriginalConsoleColor);
+    OriginalConsoleColor = -1;
+
+#else
+
+    fprintf (stderr, "\033[0m");
+
+#endif
+
+}
+
+static void set_console_color (int color) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    WORD wColor;
+    
+    if (OriginalConsoleColor == -1) {
+    
+        CONSOLE_SCREEN_BUFFER_INFO csbi;
+        
+        if (!GetConsoleScreenBufferInfo (hStdError, &csbi)) {
+            return;
+        }
+        
+        OriginalConsoleColor = csbi.wAttributes;
+    
+    }
+    
+    wColor = (OriginalConsoleColor & 0xF0) + (color & 0xF);
+    SetConsoleTextAttribute (hStdError, wColor);
+
+#else
+
+    fprintf (stderr, "\033[%dm", color);
+
+#endif
+
+}
+#endif
+
+static void output_message (const char *filename, unsigned long lineno, unsigned long idx, enum report_type type, const char *fmt, va_list ap) {
+
+    if (filename) {
+    
+        if (lineno == 0 && idx == 0) {
+            fprintf (stderr, "%s: ", filename);
+        } else {
+            fprintf (stderr, "%s:", filename);
+        }
+    
+    }
+    
+    if (lineno > 0) {
+    
+        if (idx == 0) {
+            fprintf (stderr, "%lu: ", lineno);
+        } else {
+            fprintf (stderr, "%lu:", lineno);
+        }
+    
+    }
+    
+    if (idx > 0) {
+        fprintf (stderr, "%lu: ", idx);
+    }
+    
+    if (type == REPORT_ERROR || type == REPORT_FATAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_ERROR);
+#endif
+        
+        if (type == REPORT_ERROR) {
+            fprintf (stderr, "error:");
+        } else {
+            fprintf (stderr, "fatal error:");
+        }
+    
+    } else if (type == REPORT_INTERNAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_INTERNAL_ERROR);
+#endif
+        
+        fprintf (stderr, "internal error:");
+    
+    } else if (type == REPORT_WARNING) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_WARNING);
+#endif
+        
+        fprintf (stderr, "warning:");
+    
+    }
+    
+#ifndef     __PDOS__
+    reset_console_color ();
+#endif
+    
+    fprintf (stderr, " ");
+    vfprintf (stderr, fmt, ap);
+    fprintf (stderr, "\n");
+    
+    if (type != REPORT_WARNING) {
+        ++errors;
+    }
+
+}
+
+unsigned long get_error_count (void) {
+    return errors;
+}
+
+void report_at (const char *filename, unsigned long lineno, enum report_type type, const char *fmt, ...) {
+
+    va_list ap;
+    
+    va_start (ap, fmt);
+    output_message (filename, lineno, 0, type, fmt, ap);
+    va_end (ap);
+
+}
diff --git a/report.h b/report.h
new file mode 100755 (executable)
index 0000000..8fc8758
--- /dev/null
+++ b/report.h
@@ -0,0 +1,29 @@
+/******************************************************************************
+ * @file            report.h
+ *****************************************************************************/
+#ifndef     _REPORT_H
+#define     _REPORT_H
+
+enum report_type {
+
+    REPORT_ERROR = 0,
+    REPORT_FATAL_ERROR,
+    REPORT_INTERNAL_ERROR,
+    REPORT_WARNING
+
+};
+
+#if     defined (_WIN32)
+# define    COLOR_ERROR                 12
+# define    COLOR_WARNING               13
+# define    COLOR_INTERNAL_ERROR        19
+#else
+# define    COLOR_ERROR                 91
+# define    COLOR_INTERNAL_ERROR        94
+# define    COLOR_WARNING               95
+#endif
+
+unsigned long get_error_count (void);
+void report_at (const char *filename, unsigned long line_number, enum report_type type, const char *fmt, ...);
+
+#endif      /* _REPORT_H */
diff --git a/tar.c b/tar.c
new file mode 100755 (executable)
index 0000000..8ec2cb3
--- /dev/null
+++ b/tar.c
@@ -0,0 +1,594 @@
+/******************************************************************************
+ * @file            tar.c
+ *****************************************************************************/
+#include    <limits.h>
+#include    <stddef.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "lib.h"
+#include    "report.h"
+#include    "tar.h"
+
+struct tar_state *state = 0;
+const char *program_name = 0;
+
+struct tar_raw_header {
+
+    char name[100];
+    char mode[8];
+    char owner[8];
+    char group[8];
+    char size[12];
+    char mtime[12];
+    char checksum[8];
+    char type;
+    char linkname[100];
+    char _padding[255];
+
+};
+
+static unsigned int checksum (struct tar_raw_header *header) {
+
+    unsigned char *p = (unsigned char *) header;
+    unsigned int res = 256, i;
+    
+    for (i = 0; i < offsetof (struct tar_raw_header, checksum); i++) {
+        res += p[i];
+    }
+    
+    for (i = offsetof (struct tar_raw_header, type); i < sizeof (*header); i++) {
+        res += p[i];
+    }
+    
+    return res;
+
+}
+
+static int write_null_bytes (FILE *fp, int n) {
+
+    char nul = '\0';
+    int i;
+    
+    for (i = 0; i < n; i++) {
+    
+        if (fwrite (&nul, 1, 1, fp) != 1) {
+            return 1;
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+#if     defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
+# include       <sys/stat.h>
+# include       <dirent.h>
+# include       <unistd.h>
+
+#ifndef     S_IFDIR
+# define        S_IFDIR                 0040000
+#endif
+
+#ifndef     S_IFREG
+# define        S_IFREG                 0100000
+#endif
+
+static void add_entry (FILE *tarfp, const char *root, const char *path) {
+
+    struct tar_raw_header header;
+    struct stat stbuf;
+    
+    const char *p = path;
+    char *p2;
+    
+    unsigned long len;
+    int mode;
+    
+    while (*p == '/') {
+        p++;
+    }
+    
+    if (root) {
+    
+        len = strlen (root);
+        
+        if (root[len - 1] == '/') {
+        
+            len += (strlen (p) + 1);
+            
+            p2 = xmalloc (len);
+            sprintf (p2, "%s%s", root, p);
+        
+        } else {
+        
+            len += (1 + strlen (p) + 1);
+            
+            p2 = xmalloc (len);
+            sprintf (p2, "%s/%s", root, p);
+        
+        }
+    
+    } else {
+        p2 = xstrdup (path);
+    }
+    
+    if (stat (p2, &stbuf) < 0) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed to stat '%s'", p);
+        return;
+    
+    }
+    
+    if (state->mode) {
+    
+        int o1, o2, o3;
+        
+        o1 = state->mode[1] - '0';
+        o2 = state->mode[2] - '0';
+        o3 = state->mode[3] - '0';
+        
+        mode = (o1 << 6) | (o2 << 3) | o3;
+    
+    } else {
+        mode = stbuf.st_mode & 07777;
+    }
+    
+    memset (&header, 0, sizeof (header));
+    strcpy (header.name, p);
+    
+    sprintf (header.mode, "%o", mode);
+    sprintf (header.owner, "%o", 0);
+    sprintf (header.mtime, "%o", 0);
+    
+    if (stbuf.st_mode & S_IFDIR) {
+    
+        DIR *dirp;
+        
+        struct dirent *de;
+        char *subpath;
+        
+        sprintf (header.size, "%o", 0);
+        header.type = '5';              /* MTAR_TDIR */
+        
+        sprintf (header.checksum, "%06o", checksum (&header));
+        header.checksum[7] = ' ';
+        
+        if (fwrite (&header, 1, sizeof (header), tarfp) != sizeof (header)) {
+            report_at (program_name, 0, REPORT_ERROR, "failed to write header to '%s'", state->target);
+        }
+        
+        if (!(dirp = opendir (p2))) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to open directory '%s'", p);
+            return;
+        
+        }
+        
+        while ((de = readdir (dirp))) {
+        
+            if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) {
+                continue;
+            }
+            
+            len = strlen (path);
+            
+            if (path[len - 1] == '/') {
+            
+                len += (strlen (de->d_name) + 1);
+                
+                if (!(subpath = malloc (len))) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "failed to allocate memory for '%s'", de->d_name);
+                    continue;
+                
+                }
+                
+                sprintf (subpath, "%s%s", path, de->d_name);
+            
+            } else {
+            
+                len += (1 + strlen (de->d_name) + 1);
+                
+                if (!(subpath = malloc (len))) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "failed to allocate memory for '%s'", de->d_name);
+                    continue;
+                
+                }
+                
+                sprintf (subpath, "%s/%s", path, de->d_name);
+            
+            }
+            
+            add_entry (tarfp, root, subpath);
+            free (subpath);
+        
+        }
+        
+        closedir (dirp);
+    
+    } else if (stbuf.st_mode & S_IFREG) {
+    
+        FILE *fp;
+        
+        unsigned char *data;
+        unsigned long bytes, read;
+        
+        if (!(fp = fopen (p2, "r+b"))) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to open '%s' for reading", path);
+            goto _end;
+        
+        }
+        
+        fseek (fp, 0, SEEK_END);
+        bytes = ftell (fp);
+        
+        fseek (fp, 0, SEEK_SET);
+        
+        sprintf (header.size, "%lo", bytes);
+        header.type = '0';              /* MTAR_TREG */
+        
+        sprintf (header.checksum, "%06o", checksum (&header));
+        header.checksum[7] = ' ';
+        
+        if (fwrite (&header, 1, sizeof (header), tarfp) != sizeof (header)) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to write header to '%s'", state->target);
+            fclose (fp);
+            
+            goto _end;
+        
+        }
+        
+        data = xmalloc (512);
+        
+        for (;;) {
+        
+            if (bytes == 0 || feof (fp)) {
+                break;
+            }
+            
+            read = (bytes > 512 ? 512 : bytes);
+            
+            if (fread (data, 1, read, fp) != read) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "failed to read %lu bytes from %s", read, path);
+                break;
+            
+            }
+            
+            if (fwrite (data, 1, read, tarfp) != read) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "failed to write %lu bytes to %s", read, state->target);
+                break;
+            
+            }
+            
+            bytes -= read;
+        
+        }
+        
+        bytes = (ftell (fp) % 512);
+        fclose (fp);
+        
+        if (bytes != 0) {
+            write_null_bytes (tarfp, 512 - bytes);
+        }
+    
+    }
+    
+_end:
+    
+    free (p2);
+
+}
+#elif       defined (_WIN32)
+# include       <windows.h>
+
+static BOOL IsDirectory (LPCTSTR szPath) {
+
+    DWORD dwAttrib = GetFileAttributes (szPath);
+    return (dwAttrib != INVALID_FILE_ATTRIBUTES &&  (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
+
+}
+
+static void add_entry (FILE *tarfp, const char *root, const char *path) {
+
+    struct tar_raw_header header;
+    
+    const char *p = path;
+    char *p2;
+    
+    unsigned long len;
+    int mode;
+    
+    if (isalpha ((int) p[0]) && p[1] == ':') {
+        p += 2;
+    }
+    
+    while (*p == '/') {
+        p++;
+    }
+    
+    if (root) {
+    
+        len = strlen (root);
+        
+        if (root[len - 1] == '/') {
+        
+            len += (strlen (p) + 1);
+            
+            p2 = xmalloc (len);
+            sprintf (p2, "%s%s", root, p);
+        
+        } else {
+        
+            len += (1 + strlen (p) + 1);
+            
+            p2 = xmalloc (len);
+            sprintf (p2, "%s/%s", root, p);
+        
+        }
+    
+    } else {
+        p2 = xstrdup (path);
+    }
+    
+    memset (&header, 0, sizeof (header));
+    strcpy (header.name, p);
+    
+    sprintf (header.owner, "%o", 0);
+    sprintf (header.mtime, "%o", 0);
+    
+    if (state->mode) {
+    
+        int o1, o2, o3;
+        
+        o1 = state->mode[1] - '0';
+        o2 = state->mode[2] - '0';
+        o3 = state->mode[3] - '0';
+        
+        mode = (o1 << 6) | (o2 << 3) | o3;
+    
+    } else {
+        mode = (IsDirectory (p2) ? 775 : 644);
+    }
+    
+    sprintf (header.mode, "%o", mode);
+    
+    if (IsDirectory (p2)) {
+    
+        char *subpath, *filename;
+        
+        HANDLE hFind;
+        WIN32_FIND_DATA FindFileData;
+        
+        sprintf (header.size, "%o", 0);
+        header.type = '5';              /* MTAR_TDIR */
+        
+        sprintf (header.checksum, "%06o", checksum (&header));
+        header.checksum[7] = ' ';
+        
+        if (fwrite (&header, 1, sizeof (header), tarfp) != sizeof (header)) {
+            report_at (program_name, 0, REPORT_ERROR, "failed to write header to '%s'", state->target);
+        }
+        
+        len = strlen (p2);
+        
+        if (p2[len - 1] == '/') {
+        
+            subpath = xmalloc (len + 5);
+            sprintf (subpath, "%s*.*", p2);
+        
+        } else {
+        
+            subpath = xmalloc (len + 6);
+            sprintf (subpath, "%s/*.*", p2);
+        
+        }
+        
+        if ((hFind = FindFirstFile (subpath, &FindFileData)) == INVALID_HANDLE_VALUE) {
+        
+            free (subpath);
+            return;
+        
+        }
+        
+        free (subpath);
+        
+        do {
+        
+            filename = FindFileData.cFileName;
+            
+            if (filename[0] == '.' && (filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0'))) {
+                continue;
+            }
+            
+            len = strlen (path);
+            
+            if (path[len - 1] == '/') {
+            
+                len += (strlen (filename) + 1);
+                
+                if (!(subpath = malloc (len))) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "failed to allocate memory for '%s'", filename);
+                    continue;
+                
+                }
+                
+                sprintf (subpath, "%s%s", path, filename);
+            
+            } else {
+            
+                len += (1 + strlen (filename) + 1);
+                
+                if (!(subpath = malloc (len))) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "failed to allocate memory for '%s'", filename);
+                    continue;
+                
+                }
+                
+                sprintf (subpath, "%s/%s", path, filename);
+            
+            }
+            
+            add_entry (tarfp, root, subpath);
+            free (subpath);
+        
+        } while (FindNextFile (hFind, &FindFileData) != 0);
+        
+        FindClose (hFind);
+    
+    } else {
+    
+        FILE *fp;
+        
+        unsigned char *data;
+        unsigned long bytes, read;
+        
+        if (!(fp = fopen (p2, "r+b"))) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to open '%s' for reading", path);
+            goto _end;
+        
+        }
+        
+        fseek (fp, 0, SEEK_END);
+        bytes = ftell (fp);
+        
+        fseek (fp, 0, SEEK_SET);
+        
+        sprintf (header.size, "%lo", bytes);
+        header.type = '0';              /* MTAR_TREG */
+        
+        sprintf (header.checksum, "%06o", checksum (&header));
+        header.checksum[7] = ' ';
+        
+        if (fwrite (&header, 1, sizeof (header), tarfp) != sizeof (header)) {
+        
+            report_at (program_name, 0, REPORT_ERROR, "failed to write header to '%s'", state->target);
+            fclose (fp);
+            
+            goto _end;
+        
+        }
+        
+        data = xmalloc (512);
+        
+        for (;;) {
+        
+            if (bytes == 0 || feof (fp)) {
+                break;
+            }
+            
+            read = (bytes > 512 ? 512 : bytes);
+            
+            if (fread (data, 1, read, fp) != read) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "failed to read %lu bytes from %s", read, path);
+                break;
+            
+            }
+            
+            if (fwrite (data, 1, read, tarfp) != read) {
+            
+                report_at (program_name, 0, REPORT_ERROR, "failed to write %lu bytes to %s", read, state->target);
+                break;
+            
+            }
+            
+            bytes -= read;
+        
+        }
+        
+        bytes = ftell (fp) % 512;
+        fclose (fp);
+        
+        if (bytes != 0) {
+            write_null_bytes (tarfp, 512 - bytes);
+        }
+    
+    }
+    
+_end:
+    
+    free (p2);
+
+}
+#endif
+
+
+int main (int argc, char **argv) {
+
+    FILE *tarfp;
+    long i;
+    
+    char *mode;
+    int action;
+    
+    if (argc && *argv) {
+    
+        const char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (argc, argv, 1);
+    
+    if (!state->target) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "missing -f");
+        return EXIT_FAILURE;
+    
+    }
+    
+    action = (state->create + state->append);
+    
+    if (action != 1) {
+    
+        if (action < 1) {
+            report_at (program_name, 0, REPORT_ERROR, "you must specify one of the '-cr' options");
+        } else {
+            report_at (program_name, 0, REPORT_ERROR, "you may not specify more than one '-cr' option");
+        }
+        
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->create) {
+        mode = "w+b";
+    } else {
+        mode = "r+b";
+    }
+    
+    if (!(tarfp = fopen (state->target, mode))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed to open '%s'", state->target);
+        return EXIT_FAILURE;
+    
+    }
+    
+    if (state->append) { fseek (tarfp, 0, SEEK_END); }
+    
+    for (i = 0; i < state->nb_paths; i++) {
+        add_entry (tarfp, state->root, state->paths[i]);
+    }
+    
+    write_null_bytes (tarfp, sizeof (struct tar_raw_header) * 2);
+    
+    fclose (tarfp);
+    return (get_error_count () > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+
+}
diff --git a/tar.h b/tar.h
new file mode 100755 (executable)
index 0000000..bf05a87
--- /dev/null
+++ b/tar.h
@@ -0,0 +1,20 @@
+/******************************************************************************
+ * @file            tar.h
+ *****************************************************************************/
+#ifndef     _TAR_H
+#define     _TAR_H
+
+struct tar_state {
+
+    char **paths;
+    long nb_paths;
+    
+    char *target, *root, *mode;
+    int append, create;
+
+};
+
+extern struct tar_state *state;
+extern const char *program_name;
+
+#endif      /* _TAR_H */