+/******************************************************************************
+ * @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);
+
+}