git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

gitoff.c (86037B)


      1 // TODO
      2 // use the path function
      3 // handle wrong config like getLog
      4 // check parameters from CLI
      5 //
      6 // parse params
      7 // use logger
      8 //
      9 // How to add a new config
     10 //   add variable in runtimeConfig
     11 //   add default settings in offDEFAULTS
     12 //   add a helper in offHelpers
     13 //   add setting of default in offCommands.install
     14 //   add a command for setting value in offCommands
     15 //   print value in offCommands.env
     16 //   add command in CLI parser
     17 //   update help
     18 //
     19 // How to add a new transport
     20 //   add transport functions in offHelpers.setTransport
     21 //   add transport in offHelpers.mkdirStore and offHelpers.rmAllStore
     22 //   add transport in offCommands.clearAll
     23 //   update git off mode help
     24 //   if needed, add store creation in offCommands.install
     25 //
     26 // How to add a new command
     27 //   add command in offCommands
     28 //   add command in CLI parser
     29 //   update help
     30 //
     31 //////
     32 // CODE
     33 //
     34 // modules
     35 // defaults
     36 // helpers
     37 // core
     38 // main
     39 //
     40 //
     41 // modules
     42 //   all dependencies
     43 //
     44 // defaults
     45 //   objInfo fields:  for indexing cat-diff response in push command
     46 //   externalHelpers: shell commands to be used with the exec function
     47 //   runtimeConfig:   config built by offHelpers
     48 //   offDEFAULTS:     default configuration for first time install
     49 //
     50 // helpers
     51 //   expandHome:      expands ~/
     52 //   gitConfig:       handles global git config
     53 //   offLog:          appends log to git config off.log
     54 //   offLogRepo:      log for commands run in git repo
     55 //   exec:            runs an externalHelpers with parameters
     56 //   mkdirParents:    recursive mkdir
     57 //   rmAll:           delete recursively files and directories
     58 //   copy:            copies files
     59 //   offHelpers:      git-off helpers
     60 //
     61 // core
     62 //   transport:       transport functions for git off store
     63 //   offCommands:     command line functions
     64 //
     65 // main
     66 //   parse CLI arguments
     67 //////
     68 
     69 //////
     70 // modules
     71 //////
     72 // includes and function prototypes
     73  #ifdef unitTest
     74 // gitoff.h is used in unit tests
     75 #include "gitoff.h"
     76  #else
     77 typedef void (*gConfig_set_t)(char*, char*);
     78 typedef void (*cmdSetF_t)(gConfig_set_t);
     79  #endif
     80 
     81 #define internal static
     82 
     83 #include <sys/stat.h>
     84 #include <inttypes.h>
     85 #include <stdio.h>
     86 #include <dirent.h>
     87 #include <libgen.h>
     88 #if (!__OpenBSD__)
     89 #include <wordexp.h>
     90 #endif
     91 #include <stdbool.h>
     92 #include <string.h>
     93 #include <stdlib.h>
     94 #include <unistd.h>
     95 #include <stdint.h>
     96 
     97 char *expandHome(char *p);
     98 void offLog(char* s);
     99 void offLogRepo(char* s);
    100 char *exec(int cmdName, char* paramsArray);
    101 bool strEq(char *string1, char *string2);
    102 void gitConfig_set(char* key, char* value);
    103 void gitConfig_setLocal(char* key, char* value);
    104 void gitConfig_setThisRepo(char* key, char* value);
    105 char *gitConfig_getDefault(char* key);
    106 char *gitConfig_get(char *key);
    107 void mkdirParents(char* p);
    108 void rmAll(char* p);
    109 void copy(char* src, char* dst);
    110 char **execOut(char *cmd);
    111 char **walkDir(char* dir);
    112 void freeList(void **list);
    113 char **split(char *string, char* delim);
    114 char *join(char **list, char* delim);
    115 char *offHelpers_gitRepoRoot();
    116 char *offHelpers_objectPath();
    117 char *offHelpers_offStore();
    118 char *offHelpers_offHttp();
    119 char *offHelpers_offCurlOptions();
    120 char *offHelpers_offMode();
    121 char *offHelpers_offIntegrity();
    122 char *offHelpers_offPem();
    123 char *offHelpers_offSshOptions();
    124 char *offHelpers_offScpOptions();
    125 char *offHelpers_offRsyncOptions();
    126 char *offHelpers_offScp();
    127 char *offHelpers_offScpUser();
    128 char *offHelpers_log();
    129 char *offHelpers_getLog();
    130 char *offHelpers_offConfigAlways();
    131 char *offHelpers_s3Region();
    132 char *offHelpers_s3Bucket();
    133 char *offHelpers_transform();
    134 char *offHelpers_transformTo();
    135 char *offHelpers_transformFrom();
    136 char *offHelpers_userAt();
    137 char **offHelpers_getSSHConfig();
    138 void offHelpers_mkdirStore(char *p);
    139 void offHelpers_rmAllStore(char *p);
    140 void offHelpers_copyTo();
    141 bool offHelpers_checkIntegrity(char *p);
    142 void transportCopySend(char *file);
    143 void transportCopyReceive(char *file);
    144 void transportRsyncSend(char *file);
    145 void transportRsyncReceive(char *file);
    146 void transportScpSend(char *file);
    147 void transportScpReceive(char *file);
    148 void transportHttpSend(char *file);
    149 void transportHttpReceive(char *file);
    150 void offHelpers_setTransport(char *mode );
    151 #define offHelpers_setTransport_mode() offHelpers_setTransport( "config")
    152 char **offHelpers_getOffFilePath(char *offFile);
    153 void send(char *src);
    154 void receive(char *src);
    155 void transport_transformFrom(char *file);
    156 void thisrepo(cmdSetF_t cmd);
    157 int findCommand(char *p);
    158 void showAllCommandsHelp();
    159 void offCommands_localSetup();
    160 void offCommands_install(gConfig_set_t setF );
    161 #define offCommands_install_setF() offCommands_install( gitConfig_set)
    162 void offCommands_track();
    163 void offCommands_configAlways();
    164 void offCommands_setGetGitConfig(gConfig_set_t setF);
    165 void offCommands_clean();
    166 void offCommands_prepush();
    167 void offCommands_push(char *line);
    168 void offCommands_smudge();
    169 void offCommands_copyTo();
    170 void offCommands_pushTo();
    171 void offCommands_clearAll();
    172 void offCommands_clearCache();
    173 void offCommands_clearStore();
    174 void offCommands_clearTmp();
    175 void offCommands_defaults();
    176 void offCommands_env();
    177 void offCommands_help();
    178 void installF();
    179 void modeF();
    180 void storeF();
    181 void scpF();
    182 void httpF();
    183 void curlF();
    184 void integrityF();
    185 void pemF();
    186 void sshoptionsF();
    187 void scpoptionsF();
    188 void rsyncoptionsF();
    189 void scpuserF();
    190 void trackF();
    191 void configAlwaysF();
    192 void s3regionF();
    193 void s3bucketF();
    194 void transformF();
    195 void transformToF();
    196 void transformFromF();
    197 void cleanF();
    198 void prepushF();
    199 void smudgeF();
    200 void copyToF();
    201 void pushF();
    202 void clearAllF();
    203 void caF();
    204 void clearCacheF();
    205 void ccF();
    206 void clearStoreF();
    207 void csF();
    208 void clearTmpF();
    209 void ctF();
    210 void defaultsF();
    211 void envF();
    212 void helpF();
    213 void initCOMMAND_FUNC();
    214 int MAIN(int ARGC, char** ARGV);
    215 
    216 int argc; char **argv;
    217 
    218 //////
    219 // defaults
    220 //////
    221 
    222 // objInfo fields for indexing cat-diff response in push command
    223 #define oiPREVIOUSPERMISSIONS   0
    224 #define oiPERMISSIONS           1
    225 #define oiPREVIOUSOID           2
    226 #define oiOID                   3
    227 #define oiNAME                  4
    228 
    229 // shell commands to be used with the exec function
    230 // example: exec 'gitRepoRoot'
    231 enum { gitConfigGlobal, gitConfig, gitRepoRoot, gitList, gitDff, gitCat, sha, listAttr, ssh, scp, rsync, curl, mv, mkdirE, cp, rm };
    232 char* externalHelpers[16] = { "git config --global", "git config", "git rev-parse --show-toplevel", "git rev-list", "git show --raw --format=\"\" --no-abbrev", "git cat-file -p", "git hash-object --no-filters", "git check-attr -a", "ssh", "scp", "rsync", "curl", "mv", "mkdir -p", "cp -u", "rm -rf" };
    233 
    234 // config built by offHelpers
    235 // use offHelpers to access runtimeConfig
    236 enum { currentRepoRoot, objectPath, offStore, offHttp, offMode, offIntegrity, offScp, offScpUser, offPem, offSshOptions, offScpOptions, offRsyncOptions, offCurlOptions, log, offConfigAlways, s3Region, s3Bucket, transform, transformTo, transformFrom, scpHost, scpPath, scpPort };
    237 char* runtimeConfig[23] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
    238 
    239 // default configuration for first time install
    240 // objectPath is git off cache in repo
    241 enum { objectPathD, modeD, integrityD, pemD, scpHostD, scpUserD, sshOptionsD, scpOptionsD, rsyncOptionsD, storeD, httpD, curlOptionsD, logD, configAlwaysD, s3RegionD, s3BucketD, transformD, transformToD, transformFromD, prePushD, offSignatureD, lastD };
    242 char* offDEFAULTS[22] = { "/.git/off/objects", "copy", "disable", "offNoValue", "offNoValue", "", "-C -o StrictHostKeyChecking=no -o ConnectTimeout=3", "-C -o StrictHostKeyChecking=no -o ConnectTimeout=3 -p", "-az -e \"ssh -i _i\"", "~/.git-off/offStore", "offNoValue", "-o", "~/.git-off/log", "offNoValue", "offNoValue", "offNoValue", "disable", "pbzip2 -9 -c _1 > _2", "pbzip2 -d -c _1 > _2", "#!/bin/sh\ncommand -v git-off >/dev/null 2>&1 || { echo >&2 \"\\nThis repository is configured for Git off but \"git-off\" was not found on your path. If you no longer wish to use git off, remove this hook by deleting .git/hooks/pre-push.\\n\"; exit 2; }\ngit off pre-push \"$@\"", "### git-off v1 sha:", "not used" };
    243 
    244 #define offDEFAULTS_shaLength   40
    245 
    246 //////
    247 // helpers
    248 //////
    249 
    250 #if (!__OpenBSD__)
    251 // expands ~/
    252 char *expandHome(char *p) {
    253   wordexp_t exp_result;
    254   size_t len;
    255 
    256   if (p == NULL) {
    257     return(NULL);
    258   }
    259   wordexp(p, &exp_result, 0);
    260   p = realloc(p, strlen(exp_result.we_wordv[0])+1);
    261   strcpy(p, exp_result.we_wordv[0]);
    262   wordfree(&exp_result);
    263   return(p);
    264 }
    265 #else
    266 #define and &&
    267 #define or ||
    268 #define not !
    269 #define elif else if
    270 #define TOKENPASTE2(a, b) a ## b
    271 #define TOKENPASTE(a, b) TOKENPASTE2(a, b)
    272 #define UNIQVAR(name) TOKENPASTE(name, __LINE__)
    273 #define forEachS(list, element) \
    274   ;size_t UNIQVAR(libsheepyInternalIndex) = 0; \
    275   for (char *element = list[0]; list[UNIQVAR(libsheepyInternalIndex)]!= NULL ; UNIQVAR(libsheepyInternalIndex)++, element = list[UNIQVAR(libsheepyInternalIndex)])
    276 char *findS(const char *string, const char *needle) {
    277 
    278   // sanity checks
    279   if (!string || !needle) {
    280     return(NULL);
    281   }
    282   return(strstr(string, needle));
    283 }
    284 char *strLCpy(char *dst, size_t dstSize, const char *src) {
    285 
    286   if (!dst || !src) {
    287     return(NULL);
    288   }
    289 
    290   char *r = strncpy(dst, src, dstSize);;
    291   if (dstSize) {
    292     r[dstSize-1] = 0;
    293   }
    294   return(r);
    295 }
    296 char *strLNCat(char *dst, size_t dstSize, const char *src, size_t srcLen) {
    297 
    298   if (!dst || !src) {
    299     return(NULL);
    300   }
    301 
    302   size_t dL     = strnlen(dst, dstSize);
    303   size_t dstLen = dstSize-1;
    304 
    305   if (dL >= dstLen) {
    306     // buffer is full
    307     return(dst);
    308   }
    309 
    310   size_t sL     = strnlen(src, srcLen);
    311 
    312   if (dL+sL > dstLen) {
    313     // truncate
    314     return(strncat(dst, src, dstLen - dL));
    315   }
    316   else {
    317     return(strncat(dst, src, srcLen));
    318 }
    319   }
    320 #define forEachCharP(list, element) \
    321   for (char **element=list ; *element != NULL ; element++)
    322 size_t listLengthS(char **list) {
    323   size_t r = 0;;
    324 
    325   // sanity checks
    326   if (!list) {
    327     return(0);
    328   }
    329   forEachCharP(list, i) {
    330     r++;
    331   }
    332   return(r);
    333 }
    334 char **listPushS(char ***list, const char *s) {
    335 
    336   // sanity checks
    337   if (!list) {
    338     return(NULL);
    339   }
    340 
    341   if (!*list) {
    342     *list = malloc(2 * sizeof(char *));
    343     if (!s) {
    344       (*list)[0] = NULL;
    345     }
    346     else {
    347       (*list)[0] = strdup(s);
    348     }
    349     (*list)[1] = NULL;
    350   }
    351   else {
    352     // realloc list and copy s to last element
    353     size_t len;
    354     len  = listLengthS(*list);
    355     char **tmp = realloc(*list, (len+2) * sizeof(char *));
    356     if (!tmp) {
    357       return(NULL);
    358     }
    359     else {
    360       *list = tmp;
    361       (*list)[len+1] = NULL;
    362       if (!s) {
    363         // s is NULL, add NULL in list
    364         (*list)[len] = NULL;
    365       }
    366       else {
    367         (*list)[len] = strdup(s);
    368   }
    369     }
    370       }
    371   return(*list);
    372 }
    373 #define listEmptyS(list) \
    374   do {\
    375     list = malloc(1 * sizeof(char *)); \
    376     if (list) list[0] = NULL; \
    377   } while(0);
    378 char **readText(const char *filePath) {
    379   FILE *fp = NULL;
    380   size_t len;
    381   char* line = NULL;
    382   ssize_t read;
    383   char **list = NULL;
    384 
    385   // sanity checks
    386   if (!filePath) {
    387     return(NULL);
    388   }
    389   fp = fopen(filePath, "r");
    390   if (!fp) {
    391     //pFuncError
    392     //shEPrintfS("The path was: \"%s\"\n", filePath);
    393     return(NULL);
    394   }
    395   // read all lines
    396   read = getline(&line, &len, fp);
    397   while (read != -1) {
    398     {
    399         char* pos = NULL;
    400         pos = strchr(line, '\n');
    401         if (pos != NULL)
    402             *pos = '\0';
    403     }
    404     listPushS(&list, line);
    405     read = getline(&line, &len, fp);
    406   }
    407   fclose(fp);
    408   free(line);
    409   if (!list) {
    410     // nothing was read
    411     listEmptyS(list);
    412   }
    413   return(list);
    414 }
    415 bool startsWithS(const char *string1, const char *string2) {
    416 
    417   // sanity checks
    418   if (!string1 || !string2) {
    419     return(false);
    420   }
    421   return(strncmp(string1, string2, strlen(string2)) == 0);;
    422 }
    423 void listFreeS(char **list) {
    424 
    425   // sanity checks
    426   if (list) {
    427     forEachCharP(list, e) {
    428       free(*e);
    429     }
    430     free(list);
    431 }
    432   }
    433 char *iAppendS(char **string1, const char *string2) {
    434   char *tmp = NULL;
    435   size_t l1;
    436   size_t l2;
    437 
    438   if (!string1 || !string2) {
    439     return(NULL);
    440   }
    441 
    442   if (!*string1) {
    443     *string1 = strdup(string2);
    444     return(*string1);
    445   }
    446 
    447   l1 = strlen(*string1);
    448   l2 = strlen(string2);
    449 
    450   if (!l2) {
    451     // empty string
    452     return(*string1);
    453   }
    454 
    455   tmp = realloc(*string1, l1 + l2 + 1);
    456   if (!tmp) {
    457     return(NULL);
    458   }
    459 
    460   *string1 = tmp;
    461   strcat(*string1, string2);
    462   return(*string1);
    463 }
    464 char *expandHome(char *path) {
    465 
    466   // sanity checks
    467   if (!path) {
    468     return(NULL);
    469   }
    470 
    471 
    472   if (path[0] == 0) {
    473     // path is empty
    474     return(path);
    475   }
    476 
    477   // steps
    478   // determine path format
    479   // set user name, either current user or user name in path
    480   // add : at the end of username to match line in the /etc/passwd file
    481   // find home path in the passwd file
    482 
    483   enum {noExpand, currentUser, otherUser};
    484 
    485   // determine path format
    486   int status = noExpand;
    487   if (path[0] == '~' and (path[1] == '/' or path[1] == 0)) {
    488     // path="~/..."
    489     status = currentUser;
    490   }
    491   elif (path[0] == '~' and path[1] != '/') {
    492     // path="~USER..."
    493     status = otherUser;
    494   }
    495 
    496   // there is no tilde to expand, return path unchanged
    497   if (status == noExpand) {
    498     return(path);
    499   }
    500 
    501   // set user name, either current user or user name in path
    502   char   user[33]  = {0};
    503   size_t pathStart = 0;
    504 
    505   if (status == currentUser) {
    506     // use getlogin to find current username
    507     // sizeof(user)-1 to keep a char tp append final ':'
    508     if (getlogin_r(user, sizeof(user)-1)) {
    509       // error
    510       //logE("getlogin for current user");
    511     } {
    512     pathStart = 1;
    513   }
    514       }
    515   else {
    516     // username is in path
    517     // from index 1 (skip ~) to the first /
    518     char *slash = findS(path, "/");
    519     if (!slash) {
    520       // no slash found, path is in the form: ~USER
    521       // copy string after ~ to the end of the string
    522       strLCpy(user,  sizeof(user)-1, path+1);
    523       pathStart = strlen(path);
    524     }
    525     else {
    526       // copy string after ~ to first slash
    527       pathStart = slash - path;
    528       strLNCat(user,  sizeof(user)-1, path+1, pathStart-1);
    529   }
    530     }
    531 
    532   // add : at the end of username to match line in passwd
    533   size_t len  = strlen(user);
    534   user[len]   = ':';
    535   user[len+1] = 0;
    536 
    537 
    538   // find home path in the passwd file
    539   char **pwd = readText("/etc/passwd");
    540 
    541   if (!pwd) {
    542     return(NULL);
    543   }
    544 
    545   // result
    546   char *r = NULL;
    547 
    548   forEachS(pwd, l) {
    549     if (startsWithS(l, user)) {
    550       // found the user
    551       char **l_l = split(l, ":");
    552       // copy home path to result
    553       r = strdup(l_l[5]);
    554       listFreeS(l_l);
    555       break;
    556   }
    557     }
    558 
    559   listFreeS(pwd);
    560 
    561   if (!r) {
    562     // user not found in this system
    563     return(NULL);
    564   }
    565 
    566   // append rest of the path to the result
    567   iAppendS(&r, path + pathStart);
    568 
    569   free(path);
    570 
    571   return(r);
    572 }
    573 #endif
    574 
    575 
    576 // appends log to git config off.log
    577 void offLog(char* s) {
    578   char* r = NULL;
    579   char* dirName = NULL;
    580   DIR* d = NULL;
    581   FILE* f = NULL;
    582 
    583   if (runtimeConfig[log] == NULL) {
    584     // set runtimeConfig.log and get config in git
    585     r = exec(gitConfigGlobal, "off.log");
    586     r = expandHome(r);
    587     runtimeConfig[log] = r;
    588     // error handling
    589     if (r == NULL) {
    590       printf("Missing off.log config. Run \"git config --global off.log ~/.git-off/log\".");
    591       printf("\n");
    592       exit(EXIT_FAILURE);
    593   }
    594     }
    595   dirName = strdup(runtimeConfig[log]);
    596   dirname(dirName);
    597   d = opendir(dirName);
    598   if (d == NULL) {
    599     mkdirParents(dirName);
    600   }
    601   free(dirName);
    602   free(d);
    603 
    604   f = fopen(runtimeConfig[log], "a");
    605   fprintf(f, "%s\n", s);
    606   fclose(f);
    607 }
    608 
    609 // log for commands run in git repo
    610 // adds current repo path to string s in log
    611 void offLogRepo(char* s) {
    612   // depends on offHelpers
    613   char* ss = NULL;
    614 
    615   ss = malloc(strlen(offHelpers_gitRepoRoot()) + strlen(s) + 1 + 12);
    616   sprintf(ss, "REPO path: %s %s", offHelpers_gitRepoRoot(), s);
    617   offLog(ss);
    618   free(ss);
    619 }
    620 
    621 
    622 // runs an externalHelpers with parameters
    623 // cmdName is string
    624 // paramsArray is an array of strings
    625 // returns line
    626 char *exec(int cmdName, char* paramsArray) {
    627   char* cmd = NULL;
    628   FILE* fp = NULL;
    629   size_t len;
    630   char* line = NULL;
    631   ssize_t read;
    632 
    633   cmd = malloc(strlen(externalHelpers[cmdName]) + strlen(paramsArray) + 1 + 1);
    634   sprintf(cmd, "%s %s", externalHelpers[cmdName], paramsArray);
    635   //print cmd
    636 
    637   fp = popen(cmd, "r");
    638   if (fp == NULL) {
    639      exit(EXIT_FAILURE);
    640   }
    641   free(cmd);
    642 
    643   read = getline(&line, &len, fp);
    644   pclose(fp);
    645 
    646   if (read == -1) {
    647     // nothing was read
    648     return(NULL);
    649   }
    650 
    651   {
    652     char* pos = NULL;
    653     pos = strchr(line, '\n');
    654     if (pos != NULL)
    655       *pos = '\0';
    656   }
    657   //print line
    658   return(line);
    659 }
    660 
    661 // compare strings
    662 bool strEq(char *string1, char *string2) {
    663 
    664   if (string1 == NULL) {
    665     return(false);
    666   }
    667   if (string2 == NULL) {
    668     return(false);
    669   }
    670   return(strcmp(string1,string2) == 0);;
    671 }
    672 
    673 
    674 // handles global git config
    675 void gitConfig_set(char* key, char* value) {
    676   char* p = NULL;
    677   char* r = NULL;
    678 
    679   // dont use double quote, problem with rsyncOptions - p = '%s \"%s\"', key, value
    680   p = malloc(strlen(key) + strlen(value) + 1 + 5);;
    681   sprintf(p, "%s '%s'", key, value);
    682   r = exec(gitConfigGlobal, p);
    683   free(p);
    684   free(r);
    685 }
    686 
    687 void gitConfig_setLocal(char* key, char* value) {
    688   char* p = NULL;
    689   char* r = NULL;
    690 
    691   p = malloc(strlen(key) + strlen(value) + 1 + 5);
    692   sprintf(p, "%s \"%s\"", key, value);
    693   r = exec(gitConfig, p);
    694   free(p);
    695   free(r);
    696 }
    697 
    698 void gitConfig_setThisRepo(char* key, char* value) {
    699   char* p = NULL;
    700   char* r = NULL;
    701 
    702   p = malloc(strlen(offHelpers_gitRepoRoot()) + strlen(key) + strlen(value) + 1 + 22);
    703   sprintf(p, "--file %s/.git-off %s \"%s\"", offHelpers_gitRepoRoot() , key, value);
    704   r = exec(gitConfig, p);
    705   free(p);
    706   free(r);
    707 }
    708 
    709 char *gitConfig_getDefault(char* key) {
    710 
    711   return(exec(gitConfig, key));
    712 }
    713 
    714 char *gitConfig_get(char *key) {
    715     // use configAlways setting or
    716     // return value from
    717     // GIT_OFF_CONFIG if found
    718     // repo config if found
    719     // global config
    720     char* r = NULL;
    721     char* p = NULL;
    722     char* gitOffConfigEnv = NULL;
    723 
    724     // configAlways
    725     if (strEq(offHelpers_offConfigAlways(), "GIT_OFF_CONFIG")) {
    726       gitOffConfigEnv = getenv("GIT_OFF_CONFIG");
    727       if ((gitOffConfigEnv != NULL) && (access(gitOffConfigEnv, F_OK) != -1)) {
    728         p = malloc(strlen(gitOffConfigEnv) + strlen(key) + 1 + 8);
    729         sprintf(p, "--file %s %s", gitOffConfigEnv, key);
    730         r = exec(gitConfig, p);
    731         free(p);
    732       }
    733       return(r);
    734     }
    735     if (strEq(offHelpers_offConfigAlways(), "repo")) {
    736       p = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 9);
    737       sprintf(p, "%s/.git-off", offHelpers_gitRepoRoot());
    738       if (access(p, F_OK) != -1) {
    739         free(p);
    740         p = malloc(strlen(offHelpers_gitRepoRoot()) + strlen(key) + 1 + 17);
    741         sprintf(p, "--file %s/.git-off %s", offHelpers_gitRepoRoot() , key);
    742         r = exec(gitConfig, p);
    743       }
    744       free(p);
    745       return(r);
    746     }
    747     if (strEq(offHelpers_offConfigAlways(), "global")) {
    748       r = gitConfig_getDefault(key);
    749       return(r);
    750     }
    751 
    752     // interpolate
    753     gitOffConfigEnv = getenv("GIT_OFF_CONFIG");
    754     if ((gitOffConfigEnv != NULL) && (access(gitOffConfigEnv, F_OK) != -1)) {
    755       p = malloc(strlen(gitOffConfigEnv) + strlen(key) + 1 + 8);
    756       sprintf(p, "--file %s %s", gitOffConfigEnv, key);
    757       r = exec(gitConfig, p);
    758       free(p);
    759       if (r != NULL) {
    760         return(r);
    761     }
    762       }
    763 
    764     if (offHelpers_gitRepoRoot()) {
    765       // skip repo config when running outside a git repo
    766       p = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 9);
    767       sprintf(p, "%s/.git-off", offHelpers_gitRepoRoot());
    768       if (access(p, F_OK) != -1) {
    769         free(p);
    770         p = malloc(strlen(offHelpers_gitRepoRoot()) + strlen(key) + 1 + 17);
    771         sprintf(p, "--file %s/.git-off %s", offHelpers_gitRepoRoot() , key);
    772         r = exec(gitConfig, p);
    773         if (r != NULL) {
    774           free(p);
    775           return(r);
    776       }
    777         }
    778       free(p);
    779     }
    780     r = gitConfig_getDefault(key);
    781     return(r);
    782 }
    783 
    784 
    785 // recursive mkdir
    786 void mkdirParents(char* p) {
    787   char* r = NULL;
    788 
    789   r = exec(mkdirE, p);
    790   free(r);
    791 }
    792 
    793 // delete recursively files and directories
    794 void rmAll(char* p) {
    795   char* r = NULL;
    796 
    797   r = exec(rm, p);
    798   free(r);
    799 }
    800 
    801 // copies files
    802 // file modes are not kept
    803 void copy(char* src, char* dst) {
    804   char* r = NULL;
    805   char* p = NULL;
    806 
    807   p = malloc(strlen(src) + strlen(dst) + 1 + 1);
    808   sprintf(p, "%s %s", src, dst);
    809   r = exec(cp, p);
    810   free(p);
    811   free(r);
    812 }
    813 
    814 // exec
    815 // return stdout from cmd
    816 char **execOut(char *cmd) {
    817   FILE* fp = NULL;
    818   size_t len;
    819   char* line = NULL;
    820   ssize_t read;
    821   char **list = NULL;
    822   int count = 0;;
    823 
    824   //print cmd
    825 
    826   fp = popen(cmd, "r");
    827   if (fp == NULL) {
    828      exit(EXIT_FAILURE);
    829   }
    830 
    831   read = getline(&line, &len, fp);
    832   while (read != -1) {
    833     {
    834         char* pos = NULL;
    835         pos = strchr(line, '\n');
    836         if (pos != NULL)
    837             *pos = '\0';
    838     }
    839     if (list == NULL) {
    840       list          = malloc(2 * sizeof(int *));
    841       list[1]       = NULL;
    842     }
    843     else {
    844       list          = realloc(list, (count+2) * sizeof(int *));
    845       if (list == NULL) {
    846         exit(EXIT_FAILURE);
    847       }
    848       list[count+1] = NULL;
    849     }
    850     list[count] = strdup(line);
    851     read = getline(&line, &len, fp);
    852     count++;
    853   }
    854 
    855   pclose(fp);
    856   return(list);
    857 }
    858 
    859 // List all files in a directory
    860 char **walkDir(char* dir) {
    861   char* cmd = NULL;
    862   char **list = NULL;
    863 
    864   cmd  = malloc(strlen(dir) + 1 + 13);
    865   sprintf(cmd, "find %s -type f", dir);
    866   list = execOut(cmd);
    867   free(cmd);
    868   return(list);
    869 }
    870 
    871 void freeList(void **list) {
    872   int i = 0;;
    873 
    874   if (list != NULL) {
    875     while (list[i] != NULL) {
    876       free(list[i]);
    877       i++;
    878     }
    879     free(list);
    880 }
    881   }
    882 
    883 
    884 char **split(char *string, char* delim) {
    885   char *startString = NULL;
    886   char *workingString = NULL;
    887   char *line = NULL;
    888   char *token = NULL;
    889   int count = 0;;
    890   char **r = NULL;
    891 
    892   startString   = strdup(string);
    893   workingString = startString;
    894   while (strlen(workingString) != 0) {
    895     line  = workingString;
    896     token = strtok_r(line, delim, &workingString);
    897     if (r == NULL) {
    898       r          = malloc(2 * sizeof(int *));
    899       r[1]       = NULL;
    900     }
    901     else {
    902       r          = realloc(r, (count+2) * sizeof(int *));
    903       if (r == NULL) {
    904         exit(EXIT_FAILURE);
    905       }
    906       r[count+1] = NULL;
    907     }
    908     r[count] = strdup(token);
    909     count++;
    910     // macOS sets workingString to NULL at the end
    911     if (!workingString) {
    912       break;
    913   }
    914     }
    915 
    916   free(startString);
    917   return(r);
    918 }
    919 
    920 char *join(char **list, char* delim) {
    921   char *r = NULL;
    922 
    923   while (*list != NULL) {
    924     if (r == NULL) {
    925       r = strdup(*list);
    926     }
    927     else {
    928       char *tmp;
    929       tmp = realloc(r, strlen(r) + strlen(delim) + 1);
    930       if (tmp == NULL) {
    931         exit(EXIT_FAILURE);
    932       }
    933       r = tmp;
    934       strcat(r, delim);
    935       tmp = realloc(r, strlen(r) + strlen(*list) + 1);
    936       if (tmp == NULL) {
    937         exit(EXIT_FAILURE);
    938       }
    939       r = tmp;
    940       strcat(r, *list);
    941     }
    942     list++;
    943   }
    944   return(r);
    945 }
    946 
    947 
    948 // transport
    949 
    950 typedef void (*transportSend_t)(char *);
    951 typedef void (*transportReceive_t)(char *);
    952 typedef struct {transportSend_t send; transportReceive_t receive;} transport_t;
    953 transport_t transport;
    954 
    955 // git-off helpers
    956 // gitRepoRoot:    sets and returns runtimeConfig.currentRepoRoot
    957 // objectPath:     sets and returns runtimeConfig.objectPath
    958 // offStore:       sets and returns runtimeConfig.offStore
    959 // offMode:        sets and returns runtimeConfig.offMode
    960 // offIntegrity    sets and returns runtimeConfig.offIntegrity
    961 // offScp:         sets and returns runtimeConfig.offScp
    962 // offScpUser:     sets and returns runtimeConfig.offScpUser
    963 // log:            sets and returns runtimeConfig.log
    964 // getLog:         sets and returns runtimeConfig.log with error checking
    965 // userAt:         returns user@ or ''
    966 // getSSHConfig    extracts host, port and path from off.sshhost
    967 // mkdirStore      mkdir in remote store
    968 // rmAllStore      rm in remote store
    969 // checkIntegrity  check integrity of files coming from the store
    970 // setTransport:   set send and receive functions for transport
    971 // getOffFilePath: creates path for offFile
    972 
    973 char *offHelpers_gitRepoRoot() {
    974 
    975   if (runtimeConfig[currentRepoRoot] == NULL) {
    976     runtimeConfig[currentRepoRoot] = exec(gitRepoRoot, "");
    977   }
    978   return(runtimeConfig[currentRepoRoot]);
    979 }
    980 
    981 char *offHelpers_objectPath() {
    982 
    983     if (runtimeConfig[objectPath] == NULL) {
    984       runtimeConfig[objectPath] = malloc(strlen(offHelpers_gitRepoRoot()) + strlen(offDEFAULTS[objectPathD]) + 1 + 0);
    985       sprintf(runtimeConfig[objectPath], "%s%s", offHelpers_gitRepoRoot(), offDEFAULTS[objectPathD]);
    986     }
    987     return(runtimeConfig[objectPath]);
    988 }
    989 
    990 char *offHelpers_offStore() {
    991 
    992     if (runtimeConfig[offStore] == NULL) {
    993       runtimeConfig[offStore] = gitConfig_get("off.store");
    994     }
    995     return(runtimeConfig[offStore]);
    996 }
    997 
    998 char *offHelpers_offHttp() {
    999 
   1000     if (runtimeConfig[offHttp] == NULL) {
   1001       runtimeConfig[offHttp] = gitConfig_get("off.http");
   1002     }
   1003     return(runtimeConfig[offHttp]);
   1004 }
   1005 
   1006 char *offHelpers_offCurlOptions() {
   1007 
   1008     if (runtimeConfig[offCurlOptions] == NULL) {
   1009       runtimeConfig[offCurlOptions] = gitConfig_get("off.curloptions");
   1010     }
   1011     return(runtimeConfig[offCurlOptions]);
   1012 }
   1013 
   1014 char *offHelpers_offMode() {
   1015 
   1016     if (runtimeConfig[offMode] == NULL) {
   1017       runtimeConfig[offMode] = gitConfig_get("off.mode");
   1018     }
   1019     return(runtimeConfig[offMode]);
   1020 }
   1021 
   1022 char *offHelpers_offIntegrity() {
   1023 
   1024     if (runtimeConfig[offIntegrity] == NULL) {
   1025       runtimeConfig[offIntegrity] = gitConfig_get("off.integrity");
   1026     }
   1027     return(runtimeConfig[offIntegrity]);
   1028 }
   1029 
   1030 char *offHelpers_offPem() {
   1031 
   1032     if (runtimeConfig[offPem] == NULL) {
   1033       runtimeConfig[offPem] = gitConfig_get("off.pem");
   1034     }
   1035     return(runtimeConfig[offPem]);
   1036 }
   1037 
   1038 char *offHelpers_offSshOptions() {
   1039 
   1040     if (runtimeConfig[offSshOptions] == NULL) {
   1041       runtimeConfig[offSshOptions] = gitConfig_get("off.sshoptions");
   1042     }
   1043     return(runtimeConfig[offSshOptions]);
   1044 }
   1045 
   1046 char *offHelpers_offScpOptions() {
   1047 
   1048     if (runtimeConfig[offScpOptions] == NULL) {
   1049       runtimeConfig[offScpOptions] = gitConfig_get("off.scpoptions");
   1050     }
   1051     return(runtimeConfig[offScpOptions]);
   1052 }
   1053 
   1054 char *offHelpers_offRsyncOptions() {
   1055 
   1056     if (runtimeConfig[offRsyncOptions] == NULL) {
   1057       runtimeConfig[offRsyncOptions] = gitConfig_get("off.rsyncoptions");
   1058     }
   1059     return(runtimeConfig[offRsyncOptions]);
   1060 }
   1061 
   1062 char *offHelpers_offScp() {
   1063 
   1064     if (runtimeConfig[offScp] == NULL) {
   1065       runtimeConfig[offScp] = gitConfig_get("off.scphost");
   1066     }
   1067     return(runtimeConfig[offScp]);
   1068 }
   1069 
   1070 char *offHelpers_offScpUser() {
   1071 
   1072     if (runtimeConfig[offScpUser] == NULL) {
   1073       runtimeConfig[offScpUser] = gitConfig_get("off.scpuser");
   1074     }
   1075     return(runtimeConfig[offScpUser]);
   1076 }
   1077 
   1078 char *offHelpers_log() {
   1079 
   1080     if (runtimeConfig[log] == NULL) {
   1081       runtimeConfig[log] = gitConfig_getDefault("off.log");
   1082       runtimeConfig[log] = expandHome(runtimeConfig[log]);
   1083     }
   1084     return(runtimeConfig[log]);
   1085 }
   1086 
   1087 char *offHelpers_getLog() {
   1088 
   1089     offHelpers_log();
   1090     // error handling
   1091     if (runtimeConfig[log] == NULL) {
   1092       printf("Missing off.log config. Run \"git config --global off.log ~/.git-off/log\".");
   1093       printf("\n");
   1094       exit(EXIT_FAILURE);
   1095     }
   1096     return(runtimeConfig[log]);
   1097 }
   1098 
   1099 char *offHelpers_offConfigAlways() {
   1100 
   1101     if (runtimeConfig[offConfigAlways] == NULL) {
   1102       runtimeConfig[offConfigAlways] = gitConfig_getDefault("off.configAlways");
   1103     }
   1104     return(runtimeConfig[offConfigAlways]);
   1105 }
   1106 
   1107 char *offHelpers_s3Region() {
   1108 
   1109     if (runtimeConfig[s3Region] == NULL) {
   1110       runtimeConfig[s3Region] = gitConfig_get("off.s3Region");
   1111     }
   1112     return(runtimeConfig[s3Region]);
   1113 }
   1114 
   1115 char *offHelpers_s3Bucket() {
   1116 
   1117     if (runtimeConfig[s3Bucket] == NULL) {
   1118       runtimeConfig[s3Bucket] = gitConfig_get("off.s3Bucket");
   1119     }
   1120     return(runtimeConfig[s3Bucket]);
   1121 }
   1122 
   1123 char *offHelpers_transform() {
   1124 
   1125     if (runtimeConfig[transform] == NULL) {
   1126       runtimeConfig[transform] = gitConfig_get("off.transform");
   1127     }
   1128     return(runtimeConfig[transform]);
   1129 }
   1130 
   1131 char *offHelpers_transformTo() {
   1132 
   1133     if (runtimeConfig[transformTo] == NULL) {
   1134       runtimeConfig[transformTo] = gitConfig_get("off.transformTo");
   1135     }
   1136     return(runtimeConfig[transformTo]);
   1137 }
   1138 
   1139 char *offHelpers_transformFrom() {
   1140 
   1141     if (runtimeConfig[transformFrom] == NULL) {
   1142       runtimeConfig[transformFrom] = gitConfig_get("off.transformFrom");
   1143     }
   1144     return(runtimeConfig[transformFrom]);
   1145 }
   1146 
   1147 char *offHelpers_userAt() {
   1148 
   1149     if ((offHelpers_offScpUser() != NULL) && (!strEq(offHelpers_offScpUser(), ""))) {
   1150       char *u;
   1151       u = malloc(strlen(offHelpers_offScpUser()) + 1 + 1);
   1152       sprintf(u, "%s@", offHelpers_offScpUser());
   1153       return(u);
   1154     }
   1155     else {
   1156       return "";
   1157 }
   1158     }
   1159 
   1160 char **offHelpers_getSSHConfig() {
   1161   char *user = NULL;
   1162   char **h_l = NULL;
   1163   char *port = NULL;
   1164   char *host = NULL;
   1165   char *storePath = NULL;
   1166   char **portAndPath_l = NULL;
   1167   char **r = NULL;
   1168 
   1169   // extract host, port and path from off.sshhost
   1170 
   1171   user     = offHelpers_userAt();
   1172   h_l      = split(offHelpers_offScp(), ":");
   1173   host     = strdup(h_l[0]);
   1174   if (h_l[1] != NULL) {
   1175     int i;
   1176 
   1177     portAndPath_l = split(h_l[1], "/");
   1178     i             = strtoumax(portAndPath_l[0], NULL, 10);
   1179     if (i != 0) {
   1180       port          = strdup(portAndPath_l[0]);
   1181   }
   1182     }
   1183   if (port == NULL) {
   1184     // use default port
   1185     storePath = strdup(h_l[1]);
   1186   }
   1187   else {
   1188     storePath = strdup("/");
   1189     char *tmp;
   1190     tmp       = join(portAndPath_l + 1, "/");
   1191     char *tmpr;
   1192     tmpr      = realloc(storePath, strlen(storePath) + strlen(tmp) + 1);
   1193     if (tmpr == NULL) {
   1194       exit(EXIT_FAILURE);
   1195     }
   1196     storePath = tmpr;
   1197     strcat(storePath, tmp);
   1198     free(tmp);
   1199   }
   1200   freeList((void **) h_l);
   1201   freeList((void **) portAndPath_l);
   1202 
   1203   // result [user + host, storePath, port]
   1204   r    = malloc(4 * sizeof(int *));
   1205   r[3] = NULL;
   1206   r[0] = strdup(user);
   1207   char *t;
   1208   t    = realloc(r[0], strlen(r[0]) + strlen(host) + 1);
   1209   if (t == NULL) {
   1210     exit(EXIT_FAILURE);
   1211   }
   1212   r[0] = t;
   1213   strcat(r[0], host);
   1214   r[1] = storePath;
   1215   r[2] = port;
   1216   free(host);
   1217   return(r);
   1218 }
   1219 
   1220 
   1221 void offHelpers_mkdirStore(char *p) {
   1222     // mkdir in remote store
   1223     char *params = NULL;
   1224     char *sshCmd = NULL;
   1225     char *pem = NULL;
   1226     char **h_l = NULL;
   1227 
   1228     // TODO handle multiple transports
   1229     h_l = offHelpers_getSSHConfig();
   1230 
   1231     sshCmd = malloc(strlen(h_l[1]) + strlen(p) + 1 + 10);
   1232     sprintf(sshCmd, "mkdir -p %s/%s", h_l[1], p);
   1233 
   1234     // setup ssh/scp private key
   1235     if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue")) || (offHelpers_offPem() == NULL)) {
   1236       pem = strdup("");
   1237     }
   1238     else {
   1239       pem = malloc(strlen(offHelpers_offPem()) + 1 + 3);
   1240       sprintf(pem, "-i %s", offHelpers_offPem());
   1241     }
   1242 
   1243     // ignore error from mkdir for already existing store
   1244     if (h_l[2] == NULL) {
   1245       params = malloc(strlen(offHelpers_offSshOptions()) + strlen(pem) + strlen(h_l[0]) + strlen(sshCmd) + 1 + 7);
   1246       sprintf(params, "%s %s %s \"%s\"", offHelpers_offSshOptions(), pem, h_l[0], sshCmd);
   1247     }
   1248     else {
   1249       params = malloc(strlen(offHelpers_offSshOptions()) + strlen(pem) + strlen(h_l[2]) + strlen(h_l[0]) + strlen(sshCmd) + 1 + 11);
   1250       sprintf(params, "%s %s -p %s %s \"%s\"", offHelpers_offSshOptions(), pem, h_l[2], h_l[0], sshCmd);
   1251     }
   1252 
   1253     freeList((void **) h_l);
   1254     free(sshCmd);
   1255     free(pem);
   1256     exec(ssh, params);
   1257     free(params);
   1258 }
   1259 
   1260 
   1261 void offHelpers_rmAllStore(char *p) {
   1262     // rm in remote store
   1263     char *params = NULL;
   1264     char *sshCmd = NULL;
   1265     char *pem = NULL;
   1266     char **h_l = NULL;
   1267 
   1268     // setup ssh/scp private key
   1269     if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue")) || (offHelpers_offPem() == NULL)) {
   1270       pem = strdup("");
   1271     }
   1272     else {
   1273       pem = malloc(strlen(offHelpers_offPem()) + 1 + 3);
   1274       sprintf(pem, "-i %s", offHelpers_offPem());
   1275     }
   1276 
   1277     // scphost format is host:path
   1278     h_l = offHelpers_getSSHConfig();
   1279 
   1280     sshCmd = malloc(strlen(h_l[1]) + strlen(p) + 1 + 8);
   1281     sprintf(sshCmd, "rm -rf %s/%s", h_l[1], p);
   1282 
   1283     // ignore error from rm
   1284     if (h_l[2] == NULL) {
   1285       params = malloc(strlen(offHelpers_offSshOptions()) + strlen(pem) + strlen(h_l[0]) + strlen(sshCmd) + 1 + 7);
   1286       sprintf(params, "%s %s %s \"%s\"", offHelpers_offSshOptions(), pem, h_l[0], sshCmd);
   1287     }
   1288     else {
   1289       params = malloc(strlen(offHelpers_offSshOptions()) + strlen(pem) + strlen(h_l[2]) + strlen(h_l[0]) + strlen(sshCmd) + 1 + 11);
   1290       sprintf(params, "%s %s -p %s %s \"%s\"", offHelpers_offSshOptions(), pem, h_l[2], h_l[0], sshCmd);
   1291     }
   1292 
   1293     freeList((void **) h_l);
   1294     free(sshCmd);
   1295     free(pem);
   1296     exec(ssh, params);
   1297     free(params);
   1298 }
   1299 
   1300 void offHelpers_copyTo() {
   1301     // list file in cache
   1302     // copy to store
   1303     char **files = NULL;
   1304     int i;
   1305 
   1306     // list file in cache
   1307 
   1308     files = walkDir(offHelpers_objectPath());
   1309     if (files == NULL) {
   1310       return;
   1311     }
   1312 
   1313     i = 0;
   1314     while (files[i] != NULL) {
   1315       char *tmp;
   1316       tmp = strdup(files[i] + strlen(offHelpers_objectPath()) + 1);
   1317       free(files[i]);
   1318       files[i] = tmp;
   1319       i++;
   1320     }
   1321 
   1322     // copy to store
   1323 
   1324     i = 0;
   1325     while (files[i] != NULL) {
   1326       transport.send(files[i]);
   1327       i++;
   1328     }
   1329     freeList((void **) files);
   1330 }
   1331 
   1332 bool offHelpers_checkIntegrity(char *p) {
   1333     // check integrity of files coming from the store
   1334     bool result = true;;
   1335     char *receivedSha = NULL;
   1336     char *base = NULL;
   1337 
   1338     if (strEq(offHelpers_offIntegrity(), "disable")) {
   1339       return(true);
   1340     }
   1341 
   1342     receivedSha = exec(sha, p);
   1343 
   1344     base = strdup(basename(p));
   1345     if (!strEq(base, receivedSha)) {
   1346       // the file received is different from the one that was sent.
   1347       printf("git-off: The file %s differs from the one that was pushed.", p); {
   1348       printf("\n");
   1349       result = false;
   1350     }
   1351       }
   1352     free(base);
   1353 
   1354     return(result);
   1355 }
   1356 
   1357 void transportCopySend(char *file) {
   1358   // create file directories in store
   1359   char **f_l = NULL;
   1360   char *p = NULL;
   1361   DIR *d = NULL;
   1362 
   1363   f_l = split(file, "/");
   1364   p = malloc(strlen(offHelpers_offStore()) + strlen(f_l[0]) + strlen(f_l[1]) + 1 + 2);
   1365   sprintf(p, "%s/%s/%s", offHelpers_offStore(), f_l[0], f_l[1]);
   1366   d = opendir(p);;
   1367   if (d == NULL) {
   1368     mkdirParents(p);
   1369   }
   1370   free(p);
   1371 
   1372   p    = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1373   sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1374   char *dest;
   1375   dest = malloc(strlen(offHelpers_offStore()) + strlen(file) + 1 + 1);
   1376   sprintf(dest, "%s/%s", offHelpers_offStore(), file);
   1377   copy(p, dest);
   1378   free(dest);
   1379   free(p);
   1380   freeList((void **)f_l);
   1381 }
   1382 
   1383 void transportCopyReceive(char *file) {
   1384   // transfer and transform
   1385   char *p = NULL;
   1386   int sR;
   1387   char b[1024];
   1388   FILE *f = NULL;
   1389 
   1390   if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformFrom() != NULL)) {
   1391     // create file directories in objectPath
   1392     char **f_l;
   1393     DIR *d;
   1394     f_l = split(file, "/");
   1395     p = malloc(strlen(offHelpers_objectPath()) + strlen(f_l[0]) + strlen(f_l[1]) + 1 + 2);
   1396     sprintf(p, "%s/%s/%s", offHelpers_objectPath(), f_l[0], f_l[1]);
   1397     d = opendir(p);;
   1398     if (d == NULL) {
   1399       mkdirParents(p);
   1400     }
   1401     free(p);
   1402 
   1403     p    = malloc(strlen(offHelpers_offStore()) + strlen(file) + 1 + 1);
   1404     sprintf(p, "%s/%s", offHelpers_offStore(), file);
   1405     char *dest;
   1406     dest = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1407     sprintf(dest, "%s/%s", offHelpers_objectPath(), file);
   1408     copy(p, dest);
   1409 
   1410     transport_transformFrom(file);
   1411 
   1412     free(p);
   1413     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 8);
   1414     sprintf(p, "%s/../tmp/%s", offHelpers_objectPath(), file);
   1415     if (offHelpers_checkIntegrity(p)) {
   1416       f = fopen(p, "r");
   1417       sR = fread(b, 1, 1024, f);
   1418       while (sR != 0) {
   1419         fwrite(b, 1, sR, stdout);
   1420         sR = fread(b, 1, 1024, f);
   1421       }
   1422       fclose(f);
   1423     }
   1424 
   1425     free(dest);
   1426     freeList((void **)f_l);
   1427   }
   1428   else {
   1429     p = malloc(strlen(offHelpers_offStore()) + strlen(file) + 1 + 1);
   1430     sprintf(p, "%s/%s", offHelpers_offStore(), file);
   1431     if (offHelpers_checkIntegrity(p)) {
   1432       f = fopen(p, "r");
   1433       sR = fread(b, 1, 1024, f);
   1434       while (sR != 0) {
   1435         fwrite(b, 1, sR, stdout);
   1436         sR = fread(b, 1, 1024, f);
   1437       }
   1438       fclose(f);
   1439   }
   1440     }
   1441 
   1442   free(p);
   1443 }
   1444 
   1445 
   1446 void transportRsyncSend(char *file) {
   1447   // create file directories in store
   1448   char **f_l = NULL;
   1449   char *p = NULL;
   1450   char *pem = NULL;
   1451   char *params = NULL;
   1452   char *options = NULL;
   1453   char *options2 = NULL;
   1454   char *tmp = NULL;
   1455 
   1456   f_l = split(file, "/");
   1457   p   = malloc(strlen(f_l[0]) + strlen(f_l[1]) + 1 + 1);
   1458   sprintf(p, "%s/%s", f_l[0], f_l[1]);
   1459   offHelpers_mkdirStore(p);
   1460   free(p);
   1461 
   1462   options = strdup(offHelpers_offRsyncOptions());
   1463 
   1464   // set pem or not? check rsyncoptions
   1465   // find _i index in options
   1466   tmp  = strstr(options, " _i");
   1467   if (tmp != NULL) {
   1468     if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue"))) {
   1469       options2 = strdup(options);
   1470     }
   1471     else {
   1472       tmp[1] = 0;
   1473       options2 = malloc(strlen(options) + strlen(offHelpers_offPem()) + strlen(tmp+3) + 1 + 0);
   1474       sprintf(options2, "%s%s%s", options, offHelpers_offPem(), tmp+3);
   1475   }
   1476     }
   1477   else {
   1478     options2 = strdup(options);
   1479   }
   1480 
   1481   char *dest;
   1482 
   1483   p    = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1484   sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1485   dest = malloc(strlen(offHelpers_offScp()) + strlen(file) + 1 + 1);
   1486   sprintf(dest, "%s/%s", offHelpers_offScp(), file);
   1487 
   1488   params = malloc(strlen(options2) + strlen(p) + strlen(dest) + 1 + 2);
   1489   sprintf(params, "%s %s %s", options2, p, dest);
   1490 
   1491   exec(rsync, params);
   1492 
   1493   free(p);
   1494   free(dest);
   1495   free(pem);
   1496   free(params);
   1497   free(options);
   1498   free(options2);
   1499 }
   1500 
   1501 void transportRsyncReceive(char *file) {
   1502   char *p = NULL;
   1503   char **f_l = NULL;
   1504   DIR *d = NULL;
   1505   char *pem = NULL;
   1506   char *params = NULL;
   1507   char *options = NULL;
   1508   char *options2 = NULL;
   1509   char *tmp = NULL;
   1510 
   1511   // create file directories in cache
   1512   f_l = split(file, "/");
   1513   p = malloc(strlen(offHelpers_objectPath()) + strlen(f_l[0]) + strlen(f_l[1]) + 1 + 2);
   1514   sprintf(p, "%s/%s/%s", offHelpers_objectPath(), f_l[0], f_l[1]);
   1515   d = opendir(p);;
   1516   if (d == NULL) {
   1517     mkdirParents(p);
   1518   }
   1519   free(p);
   1520   freeList((void **)f_l);
   1521 
   1522 
   1523   options = strdup(offHelpers_offRsyncOptions());
   1524 
   1525   // set pem or not? check rsyncoptions
   1526   // find _i index in options
   1527   tmp  = strstr(options, " _i");
   1528   if (tmp != NULL) {
   1529     if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue"))) {
   1530       options2 = strdup(options);
   1531     }
   1532     else {
   1533       tmp[1] = 0;
   1534       options2 = malloc(strlen(options) + strlen(offHelpers_offPem()) + strlen(tmp+3) + 1 + 0);
   1535       sprintf(options2, "%s%s%s", options, offHelpers_offPem(), tmp+3);
   1536   }
   1537     }
   1538   else {
   1539     options2 = strdup(options);
   1540   }
   1541 
   1542   char *dest;
   1543 
   1544   dest = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1545   sprintf(dest, "%s/%s", offHelpers_objectPath(), file);
   1546   p    = malloc(strlen(offHelpers_offScp()) + strlen(file) + 1 + 1);
   1547   sprintf(p, "%s/%s", offHelpers_offScp(), file);
   1548 
   1549   params = malloc(strlen(options2) + strlen(p) + strlen(dest) + 1 + 2);
   1550   sprintf(params, "%s %s %s", options2, p, dest);
   1551 
   1552   exec(rsync, params);
   1553 
   1554   free(p);
   1555   free(dest);
   1556   free(pem);
   1557   free(params);
   1558   free(options);
   1559   free(options2);
   1560 
   1561   // transform
   1562   int sR;
   1563   char b[1024];
   1564   FILE *f;
   1565 
   1566   if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformFrom() != NULL)) {
   1567     transport_transformFrom(file);
   1568     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 8);
   1569     sprintf(p, "%s/../tmp/%s", offHelpers_objectPath(), file);
   1570     if (offHelpers_checkIntegrity(p)) {
   1571       f = fopen(p, "r");
   1572       sR = fread(b, 1, 1024, f);
   1573       while (sR != 0) {
   1574         fwrite(b, 1, sR, stdout);
   1575         sR = fread(b, 1, 1024, f);
   1576       }
   1577       fclose(f);
   1578   }
   1579     }
   1580   else {
   1581     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1582     sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1583     if (offHelpers_checkIntegrity(p)) {
   1584       f = fopen(p, "r");
   1585       sR = fread(b, 1, 1024, f);
   1586       while (sR != 0) {
   1587         fwrite(b, 1, sR, stdout);
   1588         sR = fread(b, 1, 1024, f);
   1589       }
   1590       fclose(f);
   1591   }
   1592     }
   1593 
   1594   free(p);
   1595 }
   1596 
   1597 
   1598 void transportScpSend(char *file) {
   1599   // create file directories in store
   1600   char **f_l = NULL;
   1601   char *p = NULL;
   1602   char *pem = NULL;
   1603   char *params = NULL;
   1604   char **h_l = NULL;
   1605 
   1606   f_l = split(file, "/");
   1607   p   = malloc(strlen(f_l[0]) + strlen(f_l[1]) + 1 + 1);
   1608   sprintf(p, "%s/%s", f_l[0], f_l[1]);
   1609   offHelpers_mkdirStore(p);
   1610   free(p);
   1611 
   1612   h_l = offHelpers_getSSHConfig();
   1613 
   1614   // setup ssh/scp private key
   1615   if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue"))) {
   1616     pem = strdup("");
   1617   }
   1618   else {
   1619     pem = malloc(strlen(offHelpers_offPem()) + 1 + 3);
   1620     sprintf(pem, "-i %s", offHelpers_offPem());
   1621   }
   1622 
   1623   char *dest;
   1624 
   1625   p    = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1626   sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1627   dest = malloc(strlen(h_l[0]) + strlen(h_l[1]) + strlen(file) + 1 + 2);
   1628   sprintf(dest, "%s:%s/%s", h_l[0], h_l[1], file);
   1629 
   1630   if (h_l[2] == NULL) {
   1631     params = malloc(strlen(offHelpers_offScpOptions()) + strlen(pem) + strlen(p) + strlen(dest) + 1 + 3);
   1632     sprintf(params, "%s %s %s %s", offHelpers_offScpOptions(), pem, p, dest);
   1633   }
   1634   else {
   1635     params = malloc(strlen(offHelpers_offScpOptions()) + strlen(pem) + strlen(h_l[2]) + strlen(p) + strlen(dest) + 1 + 7);
   1636     sprintf(params, "%s %s -P %s %s %s", offHelpers_offScpOptions(), pem, h_l[2], p, dest);
   1637   }
   1638 
   1639   exec(scp, params);
   1640 
   1641   free(p);
   1642   free(dest);
   1643   freeList((void **) h_l);
   1644   free(pem);
   1645   free(params);
   1646 }
   1647 
   1648 void transportScpReceive(char *file) {
   1649   char *p = NULL;
   1650   char **f_l = NULL;
   1651   DIR *d = NULL;
   1652   char **h_l = NULL;
   1653   char *pem = NULL;
   1654   char *params = NULL;
   1655 
   1656   // create file directories in cache
   1657   f_l = split(file, "/");
   1658   p = malloc(strlen(offHelpers_objectPath()) + strlen(f_l[0]) + strlen(f_l[1]) + 1 + 2);
   1659   sprintf(p, "%s/%s/%s", offHelpers_objectPath(), f_l[0], f_l[1]);
   1660   d = opendir(p);;
   1661   if (d == NULL) {
   1662     mkdirParents(p);
   1663   }
   1664   free(p);
   1665   freeList((void **)f_l);
   1666 
   1667   h_l = offHelpers_getSSHConfig();
   1668 
   1669   // setup ssh/scp private key
   1670   if ((offHelpers_offPem() == NULL) || (strEq(offHelpers_offPem(), "offNoValue"))) {
   1671     pem = strdup("");
   1672   }
   1673   else {
   1674     pem = malloc(strlen(offHelpers_offPem()) + 1 + 3);
   1675     sprintf(pem, "-i %s", offHelpers_offPem());
   1676   }
   1677 
   1678   char *dest;
   1679 
   1680   dest = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1681   sprintf(dest, "%s/%s", offHelpers_objectPath(), file);
   1682   p    = malloc(strlen(h_l[0]) + strlen(h_l[1]) + strlen(file) + 1 + 2);
   1683   sprintf(p, "%s:%s/%s", h_l[0], h_l[1], file);
   1684 
   1685   if (h_l[2] == NULL) {
   1686     params = malloc(strlen(offHelpers_offScpOptions()) + strlen(pem) + strlen(p) + strlen(dest) + 1 + 3);
   1687     sprintf(params, "%s %s %s %s", offHelpers_offScpOptions(), pem, p, dest);
   1688   }
   1689   else {
   1690     params = malloc(strlen(offHelpers_offScpOptions()) + strlen(pem) + strlen(h_l[2]) + strlen(p) + strlen(dest) + 1 + 7);
   1691     sprintf(params, "%s %s -P %s %s %s", offHelpers_offScpOptions(), pem, h_l[2], p, dest);
   1692   }
   1693 
   1694   exec(scp, params);
   1695 
   1696   free(p);
   1697   free(dest);
   1698   freeList((void **) h_l);
   1699   free(pem);
   1700   free(params);
   1701 
   1702   // transform
   1703   int sR;
   1704   char b[1024];
   1705   FILE *f;
   1706 
   1707   if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformFrom() != NULL)) {
   1708     transport_transformFrom(file);
   1709     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 8);
   1710     sprintf(p, "%s/../tmp/%s", offHelpers_objectPath(), file);
   1711     if (offHelpers_checkIntegrity(p)) {
   1712       f = fopen(p, "r");
   1713       sR = fread(b, 1, 1024, f);
   1714       while (sR != 0) {
   1715         fwrite(b, 1, sR, stdout);
   1716         sR = fread(b, 1, 1024, f);
   1717       }
   1718       fclose(f);
   1719   }
   1720     }
   1721   else {
   1722     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1723     sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1724     if (offHelpers_checkIntegrity(p)) {
   1725       f = fopen(p, "r");
   1726       sR = fread(b, 1, 1024, f);
   1727       while (sR != 0) {
   1728         fwrite(b, 1, sR, stdout);
   1729         sR = fread(b, 1, 1024, f);
   1730       }
   1731       fclose(f);
   1732   }
   1733     }
   1734 
   1735   free(p);
   1736 }
   1737 
   1738 
   1739 void transportHttpSend(char *file) {
   1740   char *p = NULL;
   1741 
   1742   p = malloc(strlen(file) + 1 + 48);
   1743   sprintf(p, "Http mode is read-only: %s is stored in cache only", file);
   1744   offLogRepo(p);
   1745   free(p);
   1746 }
   1747 
   1748 void transportHttpReceive(char *file) {
   1749   char *p = NULL;
   1750   char **f_l = NULL;
   1751   DIR *d = NULL;
   1752 
   1753   // create file directories in cache
   1754   f_l = split(file, "/");
   1755   p = malloc(strlen(offHelpers_objectPath()) + strlen(f_l[0]) + strlen(f_l[1]) + 1 + 2);
   1756   sprintf(p, "%s/%s/%s", offHelpers_objectPath(), f_l[0], f_l[1]);
   1757   d = opendir(p);;
   1758   if (d == NULL) {
   1759     mkdirParents(p);
   1760   }
   1761   free(p);
   1762   freeList((void **)f_l);
   1763 
   1764   p = malloc(strlen(offHelpers_offCurlOptions()) + strlen(offHelpers_objectPath()) + strlen(file) + strlen(offHelpers_offHttp()) + strlen(file) + 1 + 4);
   1765   sprintf(p, "%s %s/%s %s/%s", offHelpers_offCurlOptions(), offHelpers_objectPath(), file, offHelpers_offHttp(), file);
   1766   exec(curl, p);
   1767   free(p);
   1768 
   1769   // transform
   1770   int sR;
   1771   char b[1024];
   1772   FILE *f;
   1773 
   1774   if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformFrom() != NULL)) {
   1775     transport_transformFrom(file);
   1776     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 8);
   1777     sprintf(p, "%s/../tmp/%s", offHelpers_objectPath(), file);
   1778     if (offHelpers_checkIntegrity(p)) {
   1779       f = fopen(p, "r");
   1780       sR = fread(b, 1, 1024, f);
   1781       while (sR != 0) {
   1782         fwrite(b, 1, sR, stdout);
   1783         sR = fread(b, 1, 1024, f);
   1784       }
   1785       fclose(f);
   1786   }
   1787     }
   1788   else {
   1789     p = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1790     sprintf(p, "%s/%s", offHelpers_objectPath(), file);
   1791     if (offHelpers_checkIntegrity(p)) {
   1792       f = fopen(p, "r");
   1793       sR = fread(b, 1, 1024, f);
   1794       while (sR != 0) {
   1795         fwrite(b, 1, sR, stdout);
   1796         sR = fread(b, 1, 1024, f);
   1797       }
   1798       fclose(f);
   1799   }
   1800     }
   1801 
   1802   free(p);
   1803 }
   1804 
   1805 void offHelpers_setTransport(char *mode ) {
   1806   char *m = NULL;
   1807 
   1808   // set send and receive functions for transport
   1809   // copy, scp
   1810 
   1811   // use mode from config or from parameter
   1812   if (strEq(mode, "config")) {
   1813     m = strdup(offHelpers_offMode());
   1814   }
   1815   else {
   1816     m = mode;
   1817   }
   1818 
   1819   // copy mode
   1820   if (strEq(m, "copy")) {
   1821     transport.send    = transportCopySend;
   1822     transport.receive = transportCopyReceive;
   1823   }
   1824 
   1825   // rsync mode
   1826   else if (strEq(m, "rsync")) {
   1827     transport.send    = transportRsyncSend;
   1828     transport.receive = transportRsyncReceive;
   1829   }
   1830 
   1831   // scp mode
   1832   else if (strEq(m, "scp")) {
   1833     transport.send    = transportScpSend;
   1834     transport.receive = transportScpReceive;
   1835   }
   1836 
   1837   // http mode
   1838   else if (strEq(m, "http")) {
   1839     transport.send    = transportHttpSend;
   1840     transport.receive = transportHttpReceive;
   1841   }
   1842 
   1843   if (m != mode) {
   1844     free(m);
   1845 }
   1846   }
   1847 //
   1848 //  // s3 mode
   1849 //  else if mode == 's3'
   1850 //    transport['send'] = (file) ->
   1851 //      AWS = require 'aws-sdk'
   1852 //      s3 = new AWS.S3({region: offHelpers.s3Region(), signatureVersion: 'v4'})
   1853 //
   1854 //      // upload to s3
   1855 //      upParams = {Bucket: offHelpers.s3Bucket(), Key: file, Body: ''}
   1856 //      fileStream = fs.createReadStream(offHelpers.objectPath() + '/' + file)
   1857 //      upParams.Body = fileStream
   1858 //      s3.upload(upParams, (err, data) ->
   1859 //        // TODO log eventual error
   1860 //        return
   1861 //      )
   1862 //
   1863 //      return
   1864 //    transport['receive'] = (file) ->
   1865 //      AWS = require 'aws-sdk'
   1866 //      s3 = new AWS.S3({region: offHelpers.s3Region(), signatureVersion: 'v4'})
   1867 //
   1868 //      // create file directories in cache
   1869 //      f_l = file.split '/'
   1870 //      if fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) == false
   1871 //        mkdirParents offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]
   1872 //
   1873 //      // download from s3
   1874 //      dlParams = {Bucket: offHelpers.s3Bucket(), Key: file}
   1875 //      fileStream = fs.createWriteStream(offHelpers.objectPath() + '/' + file)
   1876 //      fileStream.on('finish', ->
   1877 //        // transfer is finished, the complete file is in the cache
   1878 //        // transform
   1879 //        if offHelpers.transform() == 'enable' and offHelpers.transformFrom() != ''
   1880 //          transport['transformFrom'](file)
   1881 //          if offHelpers.checkIntegrity offHelpers.objectPath() + '/../tmp/' + file
   1882 //            readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file)
   1883 //            readStream.pipe(process.stdout)
   1884 //        else
   1885 //          if offHelpers.checkIntegrity offHelpers.objectPath() + '/' + file
   1886 //            readStream = fs.createReadStream(offHelpers.objectPath() + '/' + file)
   1887 //            readStream.pipe(process.stdout)
   1888 //        return
   1889 //      )
   1890 //      s3.getObject(dlParams, (err, data) ->
   1891 //        // TODO log eventual error
   1892 //        return
   1893 //      ).on('httpData', (chunk) ->
   1894 //        fileStream.write chunk
   1895 //        return
   1896 //      ).on('httpDone', ->
   1897 //        fileStream.end()
   1898 //        return
   1899 //      )
   1900 //      return
   1901 //  return
   1902 
   1903 char **offHelpers_getOffFilePath(char *offFile) {
   1904   char **r = NULL;
   1905   char tmp[6];
   1906 
   1907   tmp[0] = offFile[0];
   1908   tmp[1] = offFile[1];
   1909   tmp[2] = '/';
   1910   tmp[3] = offFile[2];
   1911   tmp[4] = offFile[3];
   1912   tmp[5] = 0;
   1913 
   1914   // [offFile.slice(0,2) + '/' + offFile.slice(2,4) + '/' + offFile, offFile.slice(0,2) + '/' + offFile.slice(2,4)]
   1915   r    = malloc(3 * sizeof(int *));
   1916   r[2] = NULL;
   1917   r[0] = malloc(strlen(tmp) + strlen(offFile) + 1 + 1);
   1918   sprintf(r[0], "%s/%s", tmp, offFile);
   1919   r[1] = strdup(tmp);
   1920   return(r);
   1921 }
   1922 
   1923 
   1924 
   1925 //////
   1926 // core
   1927 //////
   1928 
   1929 // transport functions
   1930 
   1931 void send(char *src) {
   1932 
   1933   // to be initialized by setTransport
   1934   printf("SEND %s", src);
   1935   printf("\n");
   1936 }
   1937 
   1938 void receive(char *src) {
   1939 
   1940   // to be initialized by setTransport
   1941   printf("RECEIVE %s", src);
   1942   printf("\n");
   1943 }
   1944 
   1945 void transport_transformFrom(char *file) {
   1946   // transform file in objectPath to objectPath/../tmp/file
   1947   // create directories in tmp
   1948   char *offFile = NULL;
   1949   char **offFilePath = NULL;
   1950   char *tmp = NULL;
   1951   DIR *d = NULL;
   1952   char *cmd = NULL;
   1953   char *cmd2 = NULL;
   1954   char *p1 = NULL;
   1955   char *p2 = NULL;
   1956 
   1957   offFile = strdup(basename(file));
   1958   offFilePath = offHelpers_getOffFilePath(offFile);
   1959 
   1960   tmp  = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[1]) + 1 + 8);
   1961   sprintf(tmp, "%s/../tmp/%s", offHelpers_objectPath(), offFilePath[1]);
   1962   d = opendir(tmp);
   1963   if (d == NULL) {
   1964     // create the file directory
   1965     mkdirParents(tmp);
   1966   }
   1967   free(tmp);
   1968 
   1969   cmd  = strdup(offHelpers_transformFrom());
   1970 
   1971   p1   = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 1);
   1972   sprintf(p1, "%s/%s", offHelpers_objectPath(), file);
   1973   // find _1 index in cmd
   1974   tmp  = strstr(cmd, " _1");
   1975   if (tmp != NULL) {
   1976     tmp[1] = 0;
   1977   }
   1978   cmd2 = malloc(strlen(cmd) + strlen(p1) + strlen(tmp+3) + 1 + 0);
   1979   sprintf(cmd2, "%s%s%s", cmd, p1, tmp+3);
   1980   free(p1);
   1981   free(cmd);
   1982 
   1983   p2   = malloc(strlen(offHelpers_objectPath()) + strlen(file) + 1 + 8);
   1984   sprintf(p2, "%s/../tmp/%s", offHelpers_objectPath(), file);
   1985   // find _2 and replace
   1986   tmp  = strstr(cmd2, " _2");
   1987   if (tmp != NULL) {
   1988     tmp[1] = 0;
   1989   }
   1990   cmd  = malloc(strlen(cmd2) + strlen(p2) + strlen(tmp+3) + 1 + 0);
   1991   sprintf(cmd, "%s%s%s", cmd2, p2, tmp+3);
   1992   free(p2);
   1993   //print cmd
   1994   free(cmd2);
   1995   freeList((void **)offFilePath);
   1996   offFilePath = execOut(cmd);
   1997   freeList((void **)offFilePath);
   1998   free(offFile);
   1999   free(cmd);
   2000 }
   2001 
   2002 
   2003 
   2004 
   2005 
   2006 
   2007 
   2008 
   2009 
   2010 void thisrepo(cmdSetF_t cmd) {
   2011 
   2012   if ((argc > 2) && (strEq(argv[2], "thisrepo"))) {
   2013     cmd(gitConfig_setThisRepo);
   2014     return;
   2015   }
   2016   cmd(gitConfig_set);
   2017 }
   2018 
   2019 
   2020 
   2021 enum { installH, modeH, storeH, scpH, httpH, curlH, integrityH, pemH, sshoptionsH, scpoptionsH, rsyncoptionsH, scpuserH, trackH, configAlwaysH, s3regionH, s3bucketH, transformH, transformToH, transformFromH, cleanH, prepushH, smudgeH, copyToH, pushH, clearAllH, caH, clearCacheH, ccH, clearStoreH, csH, clearTmpH, ctH, defaultsH, envH, helpH, lastH };
   2022 char* COMMAND_HELP[36] = { "git off install [thisrepo]\n  setup git config (default global)\n  thisrepo sets up config in current repo", "git off mode [thisrepo] [copy|rsync|scp|http|s3]\n  set/show git off mode", "git off store [thisrepo] [path]\n  set/show git off store path for copy mode", "git off scp [thisrepo] [host]\n  setup scp config\n  host has format host:path, user@host:path, user@host:port/path\n  Example: localhost:/tmp/offStore", "git off http [thisrepo] [host]\n  setup http config\n  host has format http://host/path\n  Example http://localhost/offStore", "git off curl [thisrepo] [options]\n  setup curl config", "git off integrity [thisrepo] [enable|disable]\n  set/show git off integrity.\n  when enabled, the SHA of the file received from the store is\n  checked again the SHA of the original file", "git off pem [thisrepo] [pathToPrivateKey]\n  set/show git off pem.\n  off.pem is the private key for ssh and scp\n  set \"offNoValue\" to set an empty value (useful when there are multiple configs)", "git off sshoptions [thisrepo] [options]\n  set/show git off sshoptions", "git off scpoptions [thisrepo] [options]\n  set/show git off scpoptions", "git off rsyncoptions [thisrepo] [options]\n  set/show git off rsyncoptions", "git off scpuser [thisrepo] [username]\n  setup scp username config", "git off track\n  setup gitattribute filters\n  example: git off track \"*.bin\"\n  without parameter, list git off attributes\n  calls git off install", "git off configAlways [\"\"|GIT_OFF_CONFIG|repo|global]\n  \"\" disable configAlways\n  GIT_OFF_CONFIG load all configurations from $GIT_OFF_CONFIG\n  repo load all configurations from current git repo\n  global load all configurations from global git config\n  set \"offNoValue\" to set an empty value", "git off s3region [thisrepo] [region]\n  setup amazon s3 region for the bucket", "git off s3bucket [thisrepo] [bucket]\n  setup amazon s3 bucket", "git off transform [thisrepo] [enable|disable]\n  enable transform in clean and smudge filters", "git off transformTo [thisrepo] [\"cmd _1 _2\"]\n  setup transform command for clear filter\n  When the command is empty the regular transport is performed", "git off transformFrom [thisrepo] [\"cmd _1 _2\"]\n  setup transform command for smudge filter\n  When the command is empty the regular transport is performed", "git off clean\n  internal filter\n  dont use directly", "git off pre-push\n  internal filter\n  dont use directly", "git off smudge\n  internal filter\n  dont use directly", "git off copyTo [copy|rsync|scp|s3]\n  copy cache to store for specified mode", "git off push\n  copy cache to store for selected mode", "git off clearAll\n  delete store, cache and log", "git off ca\n  delete store, cache and log", "git off clearCache\n  delete cache in current git", "git off cc\n  delete cache in current git", "git off clearStore\n  delete store", "git off cs\n  delete store", "git off clearTmp\n  delete tmp in git off cache\n  Useful when transform is enabled", "git off ct\n  delete tmp in git off cache\n  Useful when transform is enabled", "git off defaults\n  shows first time config", "git off env\n  shows config", "git off help [cmd]\n  git off help. Run git off help command to get help for a specific command.", "not a command" };
   2023 
   2024 enum { installC, modeC, storeC, scpC, httpC, curlC, integrityC, pemC, sshoptionsC, scpoptionsC, rsyncoptionsC, scpuserC, trackC, configAlwaysC, s3regionC, s3bucketC, transformC, transformToC, transformFromC, cleanC, prepushC, smudgeC, copyToC, pushC, clearAllC, caC, clearCacheC, ccC, clearStoreC, csC, clearTmpC, ctC, defaultsC, envC, helpC, lastC };
   2025 char* COMMAND_MAP[36] = { "install", "mode", "store", "scp", "http", "curl", "integrity", "pem", "sshoptions", "scpoptions", "rsyncoptions", "scpuser", "track", "configAlways", "s3region", "s3bucket", "transform", "transformTo", "transformFrom", "clean", "pre-push", "smudge", "copyTo", "push", "clearAll", "ca", "clearCache", "cc", "clearStore", "cs", "clearTmp", "ct", "defaults", "env", "help", "not a command" };
   2026 
   2027 enum { installG, modeG, storeG, scpG, httpG, curlG, integrityG, pemG, sshoptionsG, scpoptionsG, rsyncoptionsG, scpuserG, trackG, configAlwaysG, s3regionG, s3bucketG, transformG, transformToG, transformFromG, cleanG, prepushG, smudgeG, copyToG, pushG, clearAllG, caG, clearCacheG, ccG, clearStoreG, csG, clearTmpG, ctG, defaultsG, envG, helpG, lastG };
   2028 char* COMMAND_GITCONFIG[36] = { "empty", "off.mode", "off.store", "off.scphost", "off.http", "off.curloptions", "off.integrity", "off.pem", "off.sshoptions", "off.scpoptions", "off.rsyncoptions", "off.scpuser", "empty", "off.configAlways", "off.s3region", "off.s3bucket", "off.transform", "off.transformTo", "off.transformFrom", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "not a command" };
   2029 
   2030 // functions called for commands
   2031 typedef void (*cmdF_t)();
   2032 
   2033 // lastC is the index of last element
   2034 cmdF_t COMMAND_FUNC[lastC + 1 ];
   2035 
   2036 // helpers called for commands that read a git config
   2037 typedef char *(*cmdH_t)();
   2038 cmdH_t COMMAND_HELPERS[lastC + 1 ];
   2039 
   2040 int findCommand(char *p) {
   2041   int i;
   2042 
   2043   for (i=0; i < lastC ;i++) {
   2044     if (strEq(p, COMMAND_MAP[i])) {
   2045       break;
   2046   }
   2047     }
   2048   if (i == lastC) {
   2049     i = -1;
   2050   }
   2051   return(i);
   2052 }
   2053 
   2054 void showAllCommandsHelp() {
   2055   int i;
   2056 
   2057   for (i=0; i < lastH ;i++) {
   2058     printf("%s\n", COMMAND_HELP[i]);
   2059 }
   2060   }
   2061 
   2062 
   2063 
   2064 
   2065 // command line functions
   2066 // localSetup: setup current git
   2067 // install:    setup git config (default global)
   2068 // mode:       set/show git off mode
   2069 // scp:        setup scp config
   2070 // scpuser:    setup scp username config
   2071 // track:      setup gitattribute filters
   2072 // clean:      replace files handled by git off with reference
   2073 // prepush:    read stdin and calls push
   2074 // push:       copy objects to off.store
   2075 // smudge:     copy objects from off.store for files handled by git off
   2076 // clearAll:   delete store, cache in current git and log
   2077 // clearCache: delete cache in current git
   2078 // clearStore: delete store
   2079 // defaults:   show first time config (offDEFAULTS)
   2080 // env:        show config
   2081 void offCommands_localSetup() {
   2082     // setup current git
   2083     // create off directories in current .git
   2084     // install pre-push hooks
   2085     char *hook = NULL;
   2086 
   2087     mkdirParents(offHelpers_objectPath());
   2088 
   2089     hook = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 20);
   2090     sprintf(hook, "%s/.git/hooks/pre-push", offHelpers_gitRepoRoot());
   2091     if (access(hook, F_OK) == -1) {
   2092       FILE *f;
   2093       f = fopen(hook, "w");
   2094       fprintf(f, "%s\n", offDEFAULTS[prePushD]);
   2095       fclose(f);
   2096       chmod(hook, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
   2097     }
   2098     // error disabled because localSetup is run many times (called from clean and smudge)
   2099     // else
   2100     //   console.error hook.red.bold + ' already exists. Skipping pre-push hook setup.'.red.bold
   2101     //   process.exit(1)
   2102     free(hook);
   2103 }
   2104 
   2105 void offCommands_install(gConfig_set_t setF ) {
   2106 
   2107   // setup git config
   2108   // create off.store
   2109 
   2110   offCommands_localSetup();
   2111 
   2112   // setup config only if not already set
   2113   if ((gitConfig_get("filter.off.clean") == NULL) || (setF != gitConfig_set)) {
   2114     if (setF == gitConfig_setThisRepo) {
   2115       gitConfig_setLocal("filter.off.clean","git off clean %f");
   2116     }
   2117     else {
   2118       setF("filter.off.clean","git off clean %f");
   2119   }
   2120     }
   2121   if ((gitConfig_get("filter.off.smudge") == NULL) || (setF != gitConfig_set)) {
   2122     if (setF == gitConfig_setThisRepo) {
   2123       gitConfig_setLocal("filter.off.smudge","git off smudge %f");
   2124     }
   2125     else {
   2126       setF("filter.off.smudge","git off smudge %f");
   2127   }
   2128     }
   2129   if ((offHelpers_log() == NULL) || (setF != gitConfig_set)) {
   2130       // log always in global config
   2131       gitConfig_set("off.log",offDEFAULTS[logD]);
   2132   }
   2133 
   2134   if ((offHelpers_offMode() == NULL) || (setF != gitConfig_set)) {
   2135     setF("off.mode", offDEFAULTS[modeD]);
   2136   }
   2137   if ((offHelpers_offIntegrity() == NULL) || (setF != gitConfig_set)) {
   2138     setF("off.integrity", offDEFAULTS[integrityD]);
   2139   }
   2140   if ((offHelpers_offPem() == NULL) || (setF != gitConfig_set)) {
   2141     setF("off.pem", offDEFAULTS[pemD]);
   2142   }
   2143   if ((offHelpers_offScp() == NULL) || (setF != gitConfig_set)) {
   2144     setF("off.scphost", offDEFAULTS[scpHostD]);
   2145   }
   2146   if ((offHelpers_offScpUser() == NULL) || (setF != gitConfig_set)) {
   2147     setF("off.scpuser", offDEFAULTS[scpUserD]);
   2148   }
   2149   if ((offHelpers_offSshOptions() == NULL) || (setF != gitConfig_set)) {
   2150     setF("off.sshoptions", offDEFAULTS[sshOptionsD]);
   2151   }
   2152   if ((offHelpers_offScpOptions() == NULL) || (setF != gitConfig_set)) {
   2153     setF("off.scpoptions", offDEFAULTS[scpOptionsD]);
   2154   }
   2155   if ((offHelpers_offRsyncOptions() == NULL) || (setF != gitConfig_set)) {
   2156     setF("off.rsyncoptions", offDEFAULTS[rsyncOptionsD]);
   2157   }
   2158   if ((offHelpers_offStore() == NULL) || (setF != gitConfig_set)) {
   2159     setF("off.store", offDEFAULTS[storeD]);
   2160   }
   2161   if ((offHelpers_offHttp() == NULL) || (setF != gitConfig_set)) {
   2162     setF("off.http", offDEFAULTS[httpD]);
   2163   }
   2164   if ((offHelpers_offCurlOptions() == NULL) || (setF != gitConfig_set)) {
   2165     setF("off.curloptions", offDEFAULTS[curlOptionsD]);
   2166   }
   2167   if ((offHelpers_offConfigAlways() == NULL) || (setF != gitConfig_set)) {
   2168     setF("off.configAlways", offDEFAULTS[configAlwaysD]);
   2169   }
   2170   if ((offHelpers_s3Region() == NULL) || (setF != gitConfig_set)) {
   2171     setF("off.s3Region", offDEFAULTS[s3RegionD]);
   2172   }
   2173   if ((offHelpers_s3Bucket() == NULL) || (setF != gitConfig_set)) {
   2174     setF("off.s3Bucket", offDEFAULTS[s3BucketD]);
   2175   }
   2176   if ((offHelpers_transform() == NULL) || (setF != gitConfig_set)) {
   2177     setF("off.transform", offDEFAULTS[transformD]);
   2178   }
   2179   if ((offHelpers_transformTo() == NULL) || (setF != gitConfig_set)) {
   2180     setF("off.transformTo", offDEFAULTS[transformToD]);
   2181   }
   2182   if ((offHelpers_transformFrom() == NULL) || (setF != gitConfig_set)) {
   2183     setF("off.transformFrom", offDEFAULTS[transformFromD]);
   2184   }
   2185 
   2186   // create off.store
   2187 
   2188   if (strEq(runtimeConfig[offMode], "copy")) {
   2189     mkdirParents(runtimeConfig[offStore]);
   2190   }
   2191 
   2192   if (strEq(runtimeConfig[offMode], "scp") || strEq(runtimeConfig[offMode], "rsync")) {
   2193     offHelpers_mkdirStore("");
   2194 }
   2195   }
   2196 
   2197 void offCommands_track() {
   2198   FILE* f = NULL;
   2199 
   2200   // setup gitattribute filters in current folder
   2201   // list current git off attributes when there is no parameter
   2202   if (argc > 2) {
   2203     char *fileName;
   2204     char *line;
   2205     offCommands_install_setF();
   2206     fileName = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 15);
   2207     sprintf(fileName, "%s/.gitattributes", offHelpers_gitRepoRoot());
   2208     f = fopen(fileName, "a");
   2209     free(fileName);
   2210     line = malloc(strlen(argv[2]) + 1 + 17);
   2211     sprintf(line, "%s filter=off -text", argv[2]);
   2212     fprintf(f, "%s\n", line);
   2213     free(line);
   2214     fclose(f);
   2215   }
   2216   else {
   2217     // list current git off attributes
   2218     char *cmd;
   2219     char **list;
   2220     int i;
   2221 
   2222     cmd = malloc(strlen(externalHelpers[listAttr]) + strlen(offHelpers_gitRepoRoot()) + 1 + 20);
   2223     sprintf(cmd, "%s `cd %s; git ls-files`", externalHelpers[listAttr], offHelpers_gitRepoRoot());
   2224     list = execOut(cmd);
   2225     free(cmd);
   2226 
   2227     // gff/b.bin: filter: off
   2228     if (list != NULL) {
   2229       i = 0;
   2230       while (list[i] != NULL) {
   2231         // find string
   2232         if (strstr(list[i], ": filter: off") != NULL) {
   2233           printf("%s\n", list[i]);
   2234         }
   2235         i++;
   2236     }
   2237       }
   2238     freeList((void **)list);
   2239 }
   2240   }
   2241 
   2242 void offCommands_configAlways() {
   2243 
   2244   if (!strEq(argv[argc-1], "configAlways")) {
   2245     gitConfig_set(COMMAND_GITCONFIG[configAlwaysC], argv[argc-1]);
   2246   }
   2247   else {
   2248     if (COMMAND_HELPERS[configAlwaysC]()) {
   2249       // print when config is available
   2250       printf("%s %s", COMMAND_GITCONFIG[configAlwaysC], COMMAND_HELPERS[configAlwaysC]());
   2251       printf("\n");
   2252     }
   2253     else {
   2254       printf("Not in config: %s", COMMAND_GITCONFIG[configAlwaysC]);
   2255       printf("\n");
   2256 }
   2257   }
   2258     }
   2259 
   2260 void offCommands_setGetGitConfig(gConfig_set_t setF) {
   2261   int i;
   2262 
   2263   i = findCommand(argv[1]);
   2264   if ((argc > 2) && (!strEq(argv[argc-1], "thisrepo"))) {
   2265     setF(COMMAND_GITCONFIG[i], argv[argc-1]);
   2266   }
   2267   else {
   2268     if (COMMAND_HELPERS[i]()) {
   2269       printf("%s %s", COMMAND_GITCONFIG[i], COMMAND_HELPERS[i]());
   2270       printf("\n");
   2271     }
   2272     else {
   2273       printf("Not in config: %s", COMMAND_GITCONFIG[i]);
   2274       printf("\n");
   2275 }
   2276   }
   2277     }
   2278 
   2279 void offCommands_clean() {
   2280   char *file = NULL;
   2281   uint64_t size;
   2282   char **offFilePath = NULL;
   2283   char *offFile = NULL;
   2284   char *fileDir = NULL;
   2285   char *filePath = NULL;
   2286   DIR *d = NULL;
   2287   char b[1024];
   2288   FILE *f = NULL;
   2289   int sR;
   2290   char *quotedFile = NULL;
   2291 
   2292   // replace files handled by git off with reference
   2293   // stdin is data from the working directory file
   2294   //
   2295   // create local setup in case the repo is freshly cloned
   2296   // create file information (size, sha = git off filename)
   2297   // copy stdin to git off cache in current git
   2298   //   transform input file
   2299   // print git off ref to stdout for git
   2300 
   2301   // create local setup in case the repo is freshly cloned
   2302   offCommands_localSetup();
   2303 
   2304   // create file information (size, sha)
   2305   file              = argv[2];
   2306   //print file
   2307   struct stat st;
   2308   stat(file, &st);
   2309   size              = st.st_size;
   2310   quotedFile  = malloc(strlen(file) + 1 + 4);
   2311   sprintf(quotedFile, "\"%s\"", file);
   2312   offFile           = exec(sha, quotedFile);
   2313   free(quotedFile);
   2314   offFilePath       = offHelpers_getOffFilePath(offFile);
   2315 
   2316   fileDir           = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[1]) + 1 + 1);
   2317   sprintf(fileDir, "%s/%s", offHelpers_objectPath(), offFilePath[1]);
   2318   d                 = opendir(fileDir);
   2319   if (d == NULL) {
   2320     // create the file directory
   2321     mkdirParents(fileDir);
   2322   }
   2323 
   2324   // copy stdin to git off cache in current git
   2325   // clean runs during git add and git commit, do work only once
   2326   filePath    = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[0]) + 1 + 1);
   2327   sprintf(filePath, "%s/%s", offHelpers_objectPath(), offFilePath[0]);
   2328   if (access(filePath, F_OK) == -1) {
   2329     // stream stdin to sha in git off cache
   2330     f = fopen(filePath, "w");
   2331 
   2332     sR = fread(b, 1, 1024, stdin);
   2333     while (sR != 0) {
   2334       fwrite(b, 1, sR, f);
   2335       sR = fread(b, 1, 1024, stdin);
   2336     }
   2337     fclose(f);
   2338 
   2339     // all objects in cache are read-only
   2340     chmod(filePath, S_IRUSR | S_IRGRP | S_IROTH);
   2341 
   2342     // transform input file
   2343     if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformTo() != NULL)) {
   2344       char *tmpDir;
   2345       tmpDir  = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[1]) + 1 + 8);
   2346       sprintf(tmpDir, "%s/../tmp/%s", offHelpers_objectPath(), offFilePath[1]);
   2347       d = opendir(tmpDir);
   2348       if (d == NULL) {
   2349         // create the file directory
   2350         mkdirParents(tmpDir);
   2351       }
   2352 
   2353       // original file is moved to tmp
   2354       // transformed file is stored in objectPath under the same name
   2355       char *p;
   2356       p = malloc(strlen(filePath) + strlen(offHelpers_objectPath()) + strlen(offFilePath[0]) + 1 + 9);
   2357       sprintf(p, "%s %s/../tmp/%s", filePath, offHelpers_objectPath(), offFilePath[0]);
   2358       exec(mv, p);
   2359       free(p);
   2360 
   2361       char *cmd;
   2362       char *cmd2;
   2363       char *p1;
   2364       char *tmp;
   2365 
   2366       cmd = strdup(offHelpers_transformTo());
   2367 
   2368       p1   = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[0]) + 1 + 8);
   2369       sprintf(p1, "%s/../tmp/%s", offHelpers_objectPath(), offFilePath[0]);
   2370       // find _1 index in cmd
   2371       tmp  = strstr(cmd, " _1");
   2372       if (tmp != NULL) {
   2373         tmp[1] = 0;
   2374       }
   2375       cmd2 = malloc(strlen(cmd) + strlen(p1) + strlen(tmp+3) + 1 + 0);
   2376       sprintf(cmd2, "%s%s%s", cmd, p1, tmp+3);
   2377       free(p1);
   2378       free(cmd);
   2379 
   2380       // find _2 and replace
   2381       tmp  = strstr(cmd2, " _2");
   2382       if (tmp != NULL) {
   2383         tmp[1] = 0;
   2384       }
   2385       cmd  = malloc(strlen(cmd2) + strlen(filePath) + strlen(tmp+3) + 1 + 0);
   2386       sprintf(cmd, "%s%s%s", cmd2, filePath, tmp+3);
   2387       freeList((void **)offFilePath);
   2388       offFilePath = execOut(cmd);
   2389       free(cmd);
   2390       free(tmpDir);
   2391   }
   2392     }
   2393   else {
   2394     // file already exists, discard data
   2395     f = fopen("/dev/null", "w");
   2396 
   2397     sR = fread(b, 1, 1024, stdin);
   2398     while (sR != 0) {
   2399       fwrite(b, 1, sR, f);
   2400       sR = fread(b, 1, 1024, stdin);
   2401     }
   2402     fclose(f);
   2403   }
   2404 
   2405   free(fileDir);
   2406   free(filePath);
   2407 
   2408   // print git off ref to stdout for git
   2409   // ### git-off v1 sha:be3e02b60effe3eab232d5590a6a2e2c2c2f443b size:119
   2410   char sst[20];
   2411   sprintf(sst,"%lu", size);
   2412   char *tmp;
   2413   tmp = malloc(strlen(offDEFAULTS[offSignatureD]) + strlen(offFile) + strlen(sst) + 1 + 6);
   2414   sprintf(tmp, "%s%s size:%s", offDEFAULTS[offSignatureD], offFile, sst);
   2415   printf("%s\n", tmp);
   2416   free(tmp);
   2417   freeList((void **)offFilePath);
   2418   free(offFile);
   2419   sR = 0;
   2420 }
   2421 
   2422 void offCommands_prepush() {
   2423   size_t len;
   2424   char* line = NULL;
   2425   ssize_t read;
   2426   char **list = NULL;
   2427   int count = 0;;
   2428 
   2429   // add offCommands_push function
   2430   // read stdin and calls push
   2431   // stdin (data from git) is a line with remoteName url localRef localSha remoteRef remoteSha
   2432 
   2433   offHelpers_setTransport_mode();
   2434 
   2435   read = getline(&line, &len, stdin);
   2436   if (read == -1) {
   2437     // exit directly when there is nothing to do
   2438     exit(EXIT_SUCCESS);
   2439   }
   2440   while (read != -1) {
   2441     {
   2442         char* pos = NULL;
   2443         pos = strchr(line, '\n');
   2444         if (pos != NULL)
   2445             *pos = '\0';
   2446     }
   2447     if (list == NULL) {
   2448       list          = malloc(2 * sizeof(int *));
   2449       list[1]       = NULL;
   2450     }
   2451     else {
   2452       list          = realloc(list, (count+2) * sizeof(int *));
   2453       if (list == NULL) {
   2454         exit(EXIT_FAILURE);
   2455       }
   2456       list[count+1] = NULL;
   2457     }
   2458     list[count] = strdup(line);
   2459     read = getline(&line, &len, stdin);
   2460     count++;
   2461   }
   2462 
   2463   count = 0;
   2464   while (list[count] != NULL) {
   2465     offCommands_push(list[count]);
   2466     count++;
   2467   }
   2468   free(line);
   2469   freeList((void **)list);
   2470   count = 0;
   2471 }
   2472 
   2473 void offCommands_push(char *line) {
   2474   // copy objects to off.store
   2475   //
   2476   // set remoteName url localRef localSha remoteRef remoteSha
   2477   // list missing commits in remote
   2478   // list changed objects in missing commits
   2479   // check header with git cat
   2480   // copy git off objects to off.store
   2481   char *remoteName = NULL;
   2482   char *url = NULL;
   2483   char *localRef = NULL;
   2484   char *localSha = NULL;
   2485   char *remoteRef = NULL;
   2486   char *remoteSha = NULL;
   2487   char **line_l = NULL;
   2488   char **commitListToPush = NULL;
   2489   char *cmd = NULL;
   2490   int i;
   2491 
   2492   // set remoteName url localRef localSha remoteRef remoteSha
   2493   remoteName = argv[argc-2];
   2494   url        = argv[argc-1];
   2495 
   2496   line_l     = split(line, " ");
   2497   localRef   = line_l[0];
   2498   localSha   = line_l[1];
   2499   remoteRef  = line_l[2];
   2500   remoteSha  = line_l[3];
   2501 
   2502   // list missing commits in remote
   2503   // handle empty cloned repo
   2504   if (strEq(remoteSha, "0000000000000000000000000000000000000000")) {
   2505     // run git rev-list --max-parents=0 localSha
   2506     // when an empty git was cloned.
   2507     char *parent = "--max-parents=0";;
   2508     cmd = malloc(strlen(externalHelpers[gitList]) + strlen(parent) + strlen(localSha) + 1 + 2);
   2509     sprintf(cmd, "%s %s %s", externalHelpers[gitList], parent, localSha);
   2510   }
   2511   else {
   2512     cmd = malloc(strlen(externalHelpers[gitList]) + strlen(localSha) + strlen(remoteSha) + 1 + 3);
   2513     sprintf(cmd, "%s %s ^%s", externalHelpers[gitList], localSha, remoteSha);
   2514   }
   2515   commitListToPush = execOut(cmd);
   2516   free(cmd);
   2517 
   2518   // list changed objects in missing commits
   2519   int commitListIndex = 0;
   2520   while (commitListToPush[commitListIndex] != NULL) {
   2521 
   2522     // list changes for commit sha
   2523     char **objInfoList;
   2524     cmd         = malloc(strlen(externalHelpers[gitDff]) + strlen(commitListToPush[commitListIndex]) + 1 + 1);
   2525     sprintf(cmd, "%s %s", externalHelpers[gitDff], commitListToPush[commitListIndex]);
   2526     objInfoList = execOut(cmd);
   2527     free(cmd);
   2528     // :000000 100644 0000000000000000000000000000000000000000 23636079ef005f53e81066aaf092521d8f2346df A      dsd
   2529     // TODO filter according to .gitattributes, check files in filter only
   2530 
   2531     int oi = 0;
   2532     while (objInfoList[oi] != NULL) {
   2533       char **objInfo;
   2534       objInfo = split(objInfoList[oi], " ");
   2535 
   2536       // only lines starting with : (the first line from git diff-tree is the sha)
   2537       // dont consider deleted files
   2538       if ((objInfoList[oi][0] == ':') && (objInfo[oiNAME][0] != 'D')) {
   2539 
   2540         // check header with git cat
   2541 
   2542         char *offRef;
   2543         offRef = exec(gitCat, objInfo[oiOID]);
   2544 
   2545         if (offRef && (strstr(offRef, offDEFAULTS[offSignatureD]) != NULL)) {
   2546 
   2547           // copy git off objects to off.store
   2548           // found an off reference
   2549           // ### git-off v1 sha:be3e02b60effe3eab232d5590a6a2e2c2c2f443b size:119 b.bin
   2550 
   2551           char **offRefS1;
   2552           char **offRefS2;
   2553           char *offFile;
   2554           char **offFilePath;
   2555           offRefS1    = split(offRef, ":");
   2556           offRefS2    = split(offRefS1[1], " ");
   2557           offFile     = offRefS2[0];
   2558           offFilePath = offHelpers_getOffFilePath(offFile);
   2559 
   2560           transport.send(offFilePath[0]);
   2561           freeList((void **)offFilePath);
   2562           freeList((void **)offRefS1);
   2563           freeList((void **)offRefS2);
   2564         }
   2565         free(offRef);
   2566       }
   2567       freeList((void **)objInfo);
   2568       oi++;
   2569     }
   2570     freeList((void **)objInfoList);
   2571     commitListIndex++;
   2572   }
   2573 
   2574   freeList((void **)commitListToPush);
   2575   freeList((void **)line_l);
   2576 
   2577   // test prevent push by issuing an error
   2578   //exit(EXIT_FAILURE);
   2579 }
   2580 
   2581 void offCommands_smudge() {
   2582 
   2583   // copy objects from off.store for files handled by git off
   2584   // stdin is the data from the git repo
   2585   //
   2586   // load header
   2587   // for git off object, replace git off reference with data from cache or off.store
   2588   // for normal object, copy data from repo to stdout
   2589 
   2590   offHelpers_setTransport_mode();
   2591   offCommands_localSetup();
   2592 
   2593   // load header
   2594   char b[1024];
   2595   int sR;
   2596   sR = fread(b, 1, strlen(offDEFAULTS[offSignatureD]) + offDEFAULTS_shaLength, stdin);
   2597   if (sR == 0) {
   2598     // error no data
   2599     offLogRepo("smudge error: cant get data from stdin.");
   2600     exit(EXIT_FAILURE);
   2601   }
   2602 
   2603   if (strncmp(b, offDEFAULTS[offSignatureD], strlen(offDEFAULTS[offSignatureD])) == 0) {
   2604     // for git off object, replace git off reference with data from cache or off.store
   2605 
   2606     b[strlen(offDEFAULTS[offSignatureD])  + offDEFAULTS_shaLength] = 0;
   2607     char **header_l;
   2608     char *offFile;
   2609     char **offFilePath;
   2610     header_l    = split(b, ":");
   2611     offFile     = header_l[1];
   2612     offFilePath = offHelpers_getOffFilePath(offFile);
   2613 
   2614     // detect if file is already in cache
   2615     char *filePath;
   2616     filePath    = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[0]) + 1 + 1);
   2617     sprintf(filePath, "%s/%s", offHelpers_objectPath(), offFilePath[0]);
   2618     if (access(filePath, F_OK) != -1) {
   2619       // copy from cache
   2620       FILE *fCache;
   2621       if ((strEq(offHelpers_transform(), "enable")) && (offHelpers_transformFrom() != NULL)) {
   2622         // transform file
   2623 
   2624         transport_transformFrom(offFilePath[0]);
   2625 
   2626         char *p;
   2627         p     = malloc(strlen(offHelpers_objectPath()) + strlen(offFilePath[0]) + 1 + 8);
   2628         sprintf(p, "%s/../tmp/%s", offHelpers_objectPath(), offFilePath[0]);
   2629 
   2630         fCache = fopen(p, "r");
   2631 
   2632         free(p);
   2633       }
   2634       else {
   2635         fCache = fopen(filePath, "r");
   2636       }
   2637 
   2638       sR = fread(b, 1, 1024, fCache);
   2639       while (sR != 0) {
   2640         fwrite(b, 1, sR, stdout);
   2641         sR = fread(b, 1, 1024, fCache);
   2642       }
   2643       fclose(fCache);
   2644     }
   2645     else {
   2646       // copy from off.store
   2647       transport.receive(offFilePath[0]);
   2648     }
   2649 
   2650     free(filePath);
   2651     freeList((void **)header_l);
   2652     freeList((void **)offFilePath);
   2653   }
   2654   else {
   2655     // for normal object, copy data from repo to stdout
   2656 
   2657     //store header
   2658     fwrite(b, 1, sR, stdout);
   2659 
   2660     sR = fread(b, 1, 1024, stdin);
   2661     while (sR != 0) {
   2662       fwrite(b, 1, sR, stdout);
   2663       sR = fread(b, 1, 1024, stdin);
   2664   }
   2665     }
   2666   sR = 0;
   2667 }
   2668 
   2669 void offCommands_copyTo() {
   2670 
   2671   // copy cache to store for argv[2] mode
   2672   if (argc < 3) {
   2673     printf("Choose a mode where to copy the cache");
   2674     printf("\n");
   2675     return;
   2676   }
   2677 
   2678   offHelpers_setTransport(argv[2]);
   2679   offHelpers_copyTo();
   2680 }
   2681 
   2682 void offCommands_pushTo() {
   2683 
   2684   if (offHelpers_offMode() != NULL) {
   2685     printf("mode: %s", offHelpers_offMode());
   2686     printf("\n");
   2687     offHelpers_setTransport(offHelpers_offMode());
   2688     offHelpers_copyTo();
   2689 }
   2690   }
   2691 
   2692 void offCommands_clearAll() {
   2693 
   2694   // delete store, cache in current git and log
   2695   offCommands_clearStore();
   2696   offCommands_clearCache();
   2697   rmAll(offHelpers_getLog());
   2698 }
   2699 
   2700 void offCommands_clearCache() {
   2701   char *p = NULL;
   2702 
   2703   // delete cache in current git
   2704   p = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 9);
   2705   sprintf(p, "%s/.git/off", offHelpers_gitRepoRoot());
   2706   rmAll(p);
   2707   free(p);
   2708 }
   2709 
   2710 void offCommands_clearStore() {
   2711 
   2712   // delete store
   2713   if (strEq(offHelpers_offMode(), "copy")) {
   2714     rmAll(offHelpers_offStore());
   2715   }
   2716   if (strEq(offHelpers_offMode(), "scp") || strEq(offHelpers_offMode(), "rsync")) {
   2717     offHelpers_rmAllStore("");
   2718 }
   2719   }
   2720 
   2721 void offCommands_clearTmp() {
   2722   char *p = NULL;
   2723 
   2724   // delete tmp in cache in current git
   2725   p = malloc(strlen(offHelpers_gitRepoRoot()) + 1 + 13);
   2726   sprintf(p, "%s/.git/off/tmp", offHelpers_gitRepoRoot());
   2727   rmAll(p);
   2728   free(p);
   2729 }
   2730 
   2731 void offCommands_defaults() {
   2732   int i;
   2733 
   2734   // TODO add default key strings
   2735   // show first time config (offDEFAULTS)
   2736   for (i=0; i < lastD ; i++) {
   2737     printf("%s\n", offDEFAULTS[i]);
   2738 }
   2739   }
   2740 
   2741 void offCommands_env() {
   2742   char *s = NULL;
   2743 
   2744   s = offHelpers_offMode();
   2745   if (s != NULL) {
   2746     printf("off.mode %s", offHelpers_offMode());
   2747     printf("\n");
   2748   }
   2749   s = offHelpers_offIntegrity();
   2750   if (s != NULL) {
   2751     printf("off.integrity %s", offHelpers_offIntegrity());
   2752     printf("\n");
   2753   }
   2754   s = offHelpers_offPem();
   2755   if (s != NULL) {
   2756     printf("off.pem %s", offHelpers_offPem());
   2757     printf("\n");
   2758   }
   2759   s = offHelpers_offSshOptions();
   2760   if (s != NULL) {
   2761     printf("off.sshoptions %s", offHelpers_offSshOptions());
   2762     printf("\n");
   2763   }
   2764   s = offHelpers_offScpOptions();
   2765   if (s != NULL) {
   2766     printf("off.scpoptions %s", offHelpers_offScpOptions());
   2767     printf("\n");
   2768   }
   2769   s = offHelpers_offRsyncOptions();
   2770   if (s != NULL) {
   2771     printf("off.rsyncoptions %s", offHelpers_offRsyncOptions());
   2772     printf("\n");
   2773   }
   2774   s = offHelpers_offStore();
   2775   if (s != NULL) {
   2776     printf("off.store %s", offHelpers_offStore());
   2777     printf("\n");
   2778   }
   2779   s = offHelpers_offHttp();
   2780   if (s != NULL) {
   2781     printf("off.http %s", offHelpers_offHttp());
   2782     printf("\n");
   2783   }
   2784   s = offHelpers_offCurlOptions();
   2785   if (s != NULL) {
   2786     printf("off.curloptions %s", offHelpers_offCurlOptions());
   2787     printf("\n");
   2788   }
   2789   s = offHelpers_offScp();
   2790   if (s != NULL) {
   2791     printf("off.scphost %s", offHelpers_offScp());
   2792     printf("\n");
   2793   }
   2794   s = offHelpers_offScpUser();
   2795   if (s != NULL) {
   2796     printf("off.scpuser %s", offHelpers_offScpUser());
   2797     printf("\n");
   2798   }
   2799   s = offHelpers_getLog();
   2800   if (s != NULL) {
   2801     printf("off.log %s", offHelpers_getLog());
   2802     printf("\n");
   2803   }
   2804   s = offHelpers_offConfigAlways();
   2805   if (s != NULL) {
   2806     printf("off.configAlways %s", offHelpers_offConfigAlways());
   2807     printf("\n");
   2808   }
   2809   s = offHelpers_s3Region();
   2810   if (s != NULL) {
   2811     printf("off.s3region %s", offHelpers_s3Region());
   2812     printf("\n");
   2813   }
   2814   s = offHelpers_s3Bucket();
   2815   if (s != NULL) {
   2816     printf("off.s3bucket %s", offHelpers_s3Bucket());
   2817     printf("\n");
   2818   }
   2819   s = offHelpers_transform();
   2820   if (s != NULL) {
   2821     printf("off.transform %s", offHelpers_transform());
   2822     printf("\n");
   2823   }
   2824   s = offHelpers_transformTo();
   2825   if (s != NULL) {
   2826     printf("off.transformTo %s", offHelpers_transformTo());
   2827     printf("\n");
   2828   }
   2829   s = offHelpers_transformFrom();
   2830   if (s != NULL) {
   2831     printf("off.transformFrom %s", offHelpers_transformFrom());
   2832     printf("\n");
   2833 }
   2834   }
   2835 
   2836 void offCommands_help() {
   2837 
   2838   printf("git-off help\n");
   2839   printf("\n");
   2840   if (argc < 3) {
   2841     printf("# Quick Start");
   2842     printf("\n");
   2843     printf("Setup:");
   2844     printf("\n");
   2845     printf("git off track '*.bin'");
   2846     printf("\n");
   2847     printf("git add .");
   2848     printf("\n");
   2849     printf("git commit");
   2850     printf("\n");
   2851     printf("git push");
   2852     printf("\n");
   2853     printf("git checkout master");
   2854     printf("\n");
   2855     printf("\n# Other");
   2856     printf("\n");
   2857     printf("git off install");
   2858     printf("\n");
   2859     printf("git off mode scp");
   2860     printf("\n");
   2861     printf("git off scp localhost:/tmp/offStore");
   2862     printf("\n");
   2863     printf("git off scpuser username");
   2864     printf("\n");
   2865     printf("git off cc");
   2866     printf("\n");
   2867     printf("git off ca");
   2868     printf("\n");
   2869     printf("git off env");
   2870     printf("\n");
   2871     printf("git off defaults\n");
   2872     printf("\n");
   2873 
   2874     showAllCommandsHelp();
   2875 
   2876     printf("More information at https://noulin.net/git-off/file/README.md.html");
   2877     printf("\n");
   2878   }
   2879   else {
   2880     int cmd;
   2881     cmd = findCommand(argv[2]);
   2882     if (cmd == -1) {
   2883       // show help for help
   2884       printf("git-off command \"%s\" not found.\n", argv[2]);
   2885       printf("\n");
   2886       printf("%s\n", COMMAND_HELP[helpC]);
   2887     }
   2888     else {
   2889       printf("%s\n", COMMAND_HELP[cmd]);
   2890 }
   2891   }
   2892     }
   2893 
   2894 // --------------------------------------------------------------------------------------------
   2895 void installF() {
   2896 
   2897   thisrepo(offCommands_install);
   2898 }
   2899 
   2900 void modeF() {
   2901 
   2902   thisrepo(offCommands_setGetGitConfig);
   2903 }
   2904 
   2905 void storeF() {
   2906 
   2907   thisrepo(offCommands_setGetGitConfig);
   2908 }
   2909 
   2910 void scpF() {
   2911 
   2912   thisrepo(offCommands_setGetGitConfig);
   2913 }
   2914 
   2915 void httpF() {
   2916 
   2917   thisrepo(offCommands_setGetGitConfig);
   2918 }
   2919 
   2920 void curlF() {
   2921 
   2922   thisrepo(offCommands_setGetGitConfig);
   2923 }
   2924 
   2925 void integrityF() {
   2926 
   2927   thisrepo(offCommands_setGetGitConfig);
   2928 }
   2929 
   2930 void pemF() {
   2931 
   2932   thisrepo(offCommands_setGetGitConfig);
   2933 }
   2934 
   2935 void sshoptionsF() {
   2936 
   2937   thisrepo(offCommands_setGetGitConfig);
   2938 }
   2939 
   2940 void scpoptionsF() {
   2941 
   2942   thisrepo(offCommands_setGetGitConfig);
   2943 }
   2944 
   2945 void rsyncoptionsF() {
   2946 
   2947   thisrepo(offCommands_setGetGitConfig);
   2948 }
   2949 
   2950 void scpuserF() {
   2951 
   2952   thisrepo(offCommands_setGetGitConfig);
   2953 }
   2954 
   2955 void trackF() {
   2956 
   2957   offCommands_track();
   2958 }
   2959 
   2960 void configAlwaysF() {
   2961 
   2962   offCommands_configAlways();
   2963 }
   2964 
   2965 void s3regionF() {
   2966 
   2967   thisrepo(offCommands_setGetGitConfig);
   2968 }
   2969 
   2970 void s3bucketF() {
   2971 
   2972   thisrepo(offCommands_setGetGitConfig);
   2973 }
   2974 
   2975 void transformF() {
   2976 
   2977   thisrepo(offCommands_setGetGitConfig);
   2978 }
   2979 
   2980 void transformToF() {
   2981 
   2982   thisrepo(offCommands_setGetGitConfig);
   2983 }
   2984 
   2985 void transformFromF() {
   2986 
   2987   thisrepo(offCommands_setGetGitConfig);
   2988 }
   2989 
   2990 void cleanF() {
   2991 
   2992   offCommands_clean();
   2993 }
   2994 
   2995 void prepushF() {
   2996 
   2997   offCommands_prepush();
   2998 }
   2999 
   3000 void smudgeF() {
   3001 
   3002   offCommands_smudge();
   3003 }
   3004 
   3005 void copyToF() {
   3006 
   3007   offCommands_copyTo();
   3008 }
   3009 
   3010 void pushF() {
   3011 
   3012   offCommands_pushTo();
   3013 }
   3014 
   3015 void clearAllF() {
   3016 
   3017   offCommands_clearAll();
   3018 }
   3019 
   3020 void caF() {
   3021 
   3022   offCommands_clearAll();
   3023 }
   3024 
   3025 void clearCacheF() {
   3026 
   3027   offCommands_clearCache();
   3028 }
   3029 
   3030 void ccF() {
   3031 
   3032   offCommands_clearCache();
   3033 }
   3034 
   3035 void clearStoreF() {
   3036 
   3037   offCommands_clearStore();
   3038 }
   3039 
   3040 void csF() {
   3041 
   3042   offCommands_clearStore();
   3043 }
   3044 
   3045 void clearTmpF() {
   3046 
   3047   offCommands_clearTmp();
   3048 }
   3049 
   3050 void ctF() {
   3051 
   3052   offCommands_clearTmp();
   3053 }
   3054 
   3055 void defaultsF() {
   3056 
   3057   offCommands_defaults();
   3058 }
   3059 
   3060 void envF() {
   3061 
   3062   offCommands_env();
   3063 }
   3064 
   3065 void helpF() {
   3066 
   3067   offCommands_help();
   3068 }
   3069 
   3070 // Initialize commands
   3071 void initCOMMAND_FUNC() {
   3072 
   3073   COMMAND_FUNC[0]     = installF;
   3074   COMMAND_FUNC[1]     = modeF;
   3075   COMMAND_FUNC[2]     = storeF;
   3076   COMMAND_FUNC[3]     = scpF;
   3077   COMMAND_FUNC[4]     = httpF;
   3078   COMMAND_FUNC[5]     = curlF;
   3079   COMMAND_FUNC[6]     = integrityF;
   3080   COMMAND_FUNC[7]     = pemF;
   3081   COMMAND_FUNC[8]     = sshoptionsF;
   3082   COMMAND_FUNC[9]     = scpoptionsF;
   3083   COMMAND_FUNC[10]    = rsyncoptionsF;
   3084   COMMAND_FUNC[11]    = scpuserF;
   3085   COMMAND_FUNC[12]    = trackF;
   3086   COMMAND_FUNC[13]    = configAlwaysF;
   3087   COMMAND_FUNC[14]    = s3regionF;
   3088   COMMAND_FUNC[15]    = s3bucketF;
   3089   COMMAND_FUNC[16]    = transformF;
   3090   COMMAND_FUNC[17]    = transformToF;
   3091   COMMAND_FUNC[18]    = transformFromF;
   3092   COMMAND_FUNC[19]    = cleanF;
   3093   COMMAND_FUNC[20]    = prepushF;
   3094   COMMAND_FUNC[21]    = smudgeF;
   3095   COMMAND_FUNC[22]    = copyToF;
   3096   COMMAND_FUNC[23]    = pushF;
   3097   COMMAND_FUNC[24]    = clearAllF;
   3098   COMMAND_FUNC[25]    = caF;
   3099   COMMAND_FUNC[26]    = clearCacheF;
   3100   COMMAND_FUNC[27]    = ccF;
   3101   COMMAND_FUNC[28]    = clearStoreF;
   3102   COMMAND_FUNC[29]    = csF;
   3103   COMMAND_FUNC[30]    = clearTmpF;
   3104   COMMAND_FUNC[31]    = ctF;
   3105   COMMAND_FUNC[32]    = defaultsF;
   3106   COMMAND_FUNC[33]    = envF;
   3107   COMMAND_FUNC[34]    = helpF;
   3108   COMMAND_FUNC[lastC] = NULL;
   3109 
   3110   COMMAND_HELPERS[0]     = NULL;
   3111   COMMAND_HELPERS[1]     = offHelpers_offMode;
   3112   COMMAND_HELPERS[2]     = offHelpers_offStore;
   3113   COMMAND_HELPERS[3]     = offHelpers_offScp;
   3114   COMMAND_HELPERS[4]     = offHelpers_offHttp;
   3115   COMMAND_HELPERS[5]     = offHelpers_offCurlOptions;
   3116   COMMAND_HELPERS[6]     = offHelpers_offIntegrity;
   3117   COMMAND_HELPERS[7]     = offHelpers_offPem;
   3118   COMMAND_HELPERS[8]     = offHelpers_offSshOptions;
   3119   COMMAND_HELPERS[9]     = offHelpers_offScpOptions;
   3120   COMMAND_HELPERS[10]    = offHelpers_offRsyncOptions;
   3121   COMMAND_HELPERS[11]    = offHelpers_offScpUser;
   3122   COMMAND_HELPERS[12]    = NULL;
   3123   COMMAND_HELPERS[13]    = offHelpers_offConfigAlways;
   3124   COMMAND_HELPERS[14]    = offHelpers_s3Region;
   3125   COMMAND_HELPERS[15]    = offHelpers_s3Bucket;
   3126   COMMAND_HELPERS[16]    = offHelpers_transform;
   3127   COMMAND_HELPERS[17]    = offHelpers_transformTo;
   3128   COMMAND_HELPERS[18]    = offHelpers_transformFrom;
   3129   COMMAND_HELPERS[19]    = NULL;
   3130   COMMAND_HELPERS[20]    = NULL;
   3131   COMMAND_HELPERS[21]    = NULL;
   3132   COMMAND_HELPERS[22]    = NULL;
   3133   COMMAND_HELPERS[23]    = NULL;
   3134   COMMAND_HELPERS[24]    = NULL;
   3135   COMMAND_HELPERS[25]    = NULL;
   3136   COMMAND_HELPERS[26]    = NULL;
   3137   COMMAND_HELPERS[27]    = NULL;
   3138   COMMAND_HELPERS[28]    = NULL;
   3139   COMMAND_HELPERS[29]    = NULL;
   3140   COMMAND_HELPERS[30]    = NULL;
   3141   COMMAND_HELPERS[31]    = NULL;
   3142   COMMAND_HELPERS[32]    = NULL;
   3143   COMMAND_HELPERS[33]    = NULL;
   3144   COMMAND_HELPERS[34]    = NULL;
   3145   COMMAND_HELPERS[lastC] = NULL;
   3146 }
   3147 
   3148 #ifndef unitTest
   3149 // Remove main when running the unit tests
   3150 #define MAIN   main
   3151 #endif
   3152 int MAIN(int ARGC, char** ARGV) {
   3153   int dummy;
   3154   // TODO remove dummy
   3155 
   3156   argc = ARGC; argv = ARGV;;// init transport functions
   3157   transport.send    = send;
   3158   transport.receive = receive;
   3159   initCOMMAND_FUNC();
   3160 
   3161   // parse CLI arguments
   3162 
   3163   if (argc == 1) {
   3164     offCommands_help();
   3165   }
   3166   else {
   3167     // call argv[1] command
   3168     int i;
   3169     i = findCommand(argv[1]);
   3170     if (i == -1) {
   3171       offCommands_help();
   3172     }
   3173     else {
   3174       COMMAND_FUNC[i]();
   3175 }
   3176   }
   3177     }