/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright (c) 2013 D. Richard Hipp This program is free software; you can redistribute it and/or modify it under the terms of the Simplified BSD License (also known as the "2-Clause License" or "FreeBSD License".) This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. Author contact information: drh@hwaci.com http://www.hwaci.com/drh/ ***************************************************************************** */ #include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */ #include "fossil-scm/fossil-internal.h" #include #ifndef _WIN32 #include /*isatty()*/ #endif static struct App_{ char doCrosslink; char checkRCard; } App = { 0/*doCrosslink*/, 0/*checkRCard*/ }; /** Just experimenting with fsl_xlink_listener() and friends. */ static int my_xlink_f(fsl_cx * f, fsl_deck const * d, void * state){ FCLI_V(("Crosslink callback for %s artifact [%.12s] (RID %"FSL_ID_T_PFMT")\n", fsl_catype_cstr(d->type), d->uuid, d->rid)); #if 0 return *((char const *)state) ? fsl_cx_err_set(f, FSL_RC_IO, "Demonstrating what happens when crosslinking fails.") : 0; #else return *((char const *)state) /* demonstrate what happens when crosslinking fails. */ ? FSL_RC_IO : 0; #endif } static int test_parse_1( char const * mfile ){ fsl_buffer buf = fsl_buffer_empty; fsl_buffer bout = fsl_buffer_empty; int rc; fsl_deck mf = fsl_deck_empty; fsl_cx * f = fcli.f; char const * ofile = "mf.out"; char seemsSafeEnough = 1; rc = fsl_buffer_fill_from_filename(&buf, mfile); assert(!rc); assert(buf.used); f_out("Parsing this manifest: %s\n",mfile); mf.f = f /* this allows fsl_deck_parse() to populate mf with more data. */; rc = fsl_deck_parse(&mf, &buf); fcli_err_report(1); assert(!rc); assert(f == mf.f); f_out("Artifact type=%s, rid=%"FSL_ID_T_PFMT", uuid=%s\n", fsl_catype_cstr(mf.type), mf.rid, mf.uuid); if(mf.B.uuid){ if(mf.rid>0){ f_out("Trying to fetch baseline manifest [%s]\n", mf.B.uuid); rc = fsl_deck_baseline_fetch(&mf); f_out("rc=%s, Baseline@%p\n", fsl_rc_cstr(rc), (void const *)mf.B.baseline); if(0){ fsl_deck_output( mf.B.baseline, fsl_output_f_FILE, stdout, fcli_error()); } /* assert(mf.B.baseline->F.list.used > 0); */ }else{ f_out("manifest rid is not set - " "assuming this comes from another repo?\n"); } } if(mf.rid>0 && mf.R){ fsl_card_F const * fc = NULL; int i; assert(!rc); /* f_out("File list:\n"); */ for( i = 0; !(rc=fsl_deck_F_next(&mf, &fc)) && fc; ++i ){ /* f_out("File: %.*s %s\n", 8, fc->uuid, fc->name); */ } f_out("%d files seen in manifest(s).\n",i); assert(i>0 || 1==mf.rid /* special case! */); } if(App.checkRCard && mf.R){ char * uuidCheck = fsl_strdup(mf.R); assert(mf.R); f_out("Trying to re-calculate R-card: original=[%s]\n", mf.R); rc = fsl_deck_R_set(&mf, NULL); assert(!rc); assert(NULL == mf.R); rc = fsl_deck_unshuffle(&mf, 1); fcli_err_report(1); assert(!rc); assert( mf.R ); f_out("Re-calculated R-card: [%s]\n", mf.R); f_out("R-card match? %s\n", (0==fsl_strcmp(uuidCheck, mf.R)) ? "yes" : "NO!"); fsl_free(uuidCheck); } rc = fsl_deck_output(&mf, fsl_output_f_buffer, &bout, fcli_error()); fcli_err_report(1); assert(!rc); f_out("Round-trip re-generated artifact (type=%s) from input file:\n", fsl_catype_cstr(mf.type)); if(bout.used<2000){ f_out("%b", &bout); }else{ f_out("Rather large - not dumping to console.\n"); } f_out("Dumping mf to file [%s]\n", ofile); rc = fsl_buffer_to_filename(&bout, ofile); assert(!rc); { fsl_buffer sha = fsl_buffer_empty; rc = fsl_sha1sum_filename(ofile, &sha); assert(!rc); f_out("SHA of [%s] = [%b]\n", ofile, &sha); seemsSafeEnough = (0==fsl_strcmp(fsl_buffer_cstr(&sha), mf.uuid)); f_out("SHA match? %s\n", seemsSafeEnough ? "yes" : "NO!"); fsl_buffer_clear(&sha); if(!seemsSafeEnough){ /* i would like to show a diff here, but parsing the manifest modifies buf, replacing spaces AND \n with \0 because doing so simplifies tokenization and provides a basis for a memory reuse case. Because it replaces both, we have no simple way of knowing which replacements we would need to make. So... we just load the modified buffer again. */ rc = fsl_buffer_fill_from_filename(&buf, mfile); assert(!rc); if(buf.used){ assert(bout.used); assert(0!=fsl_buffer_compare(&buf,&bout)); f_out("Diff of mismatched output (small timestamp " "diffs and PGP sigs are normal):\n"); rc = fsl_diff_text( &buf, &bout, fsl_output_f_FILE, stdout, 2, 0, #if 1 0 #else FSL_DIFF_SIDEBYSIDE | FSL_DIFF_LINENO #endif ); assert(!rc); }/*else it came from stdin and cannot be read again*/ } } if(!mf.rid){ f_out("No matching RID found: this is only an error if the artifact " "came from the current repo.\n"); } if(App.doCrosslink){ if(seemsSafeEnough){ assert((mf.rid>0) && mf.uuid); f_out("Crosslinking manifest...\n"); rc = fsl_deck_crosslink( &mf ); f_out("Crosslink says: %s\n", fsl_rc_cstr(rc)); fcli_err_report(1); } } fsl_buffer_clear(&buf); fsl_buffer_clear(&bout); fsl_deck_finalize(&mf); return rc; } static void fcli_local_help(){ printf("Usage:\n\t%s [options]\n\n", fcli.appName); puts("Tests the libfossil artifact/manifest parser.\n"); puts("\t--file|-f=manifest_file_to_test\n"); puts("\t--r-card|-r enables validation of the R-card\n"); puts("\t--crosslink enables manifest crosslinking.\n"); puts("\t--fail-xlink|-fx causes manifest crosslinking (if enabled) to fail.\n"); puts("By default, on non-Windows sytems (those with isatty(3)) " "it will read from stdin if stdin is not a terminal and " "neither --file nor any non-flag arguments are provided."); } int main(int argc, char * const * argv ){ int rc = 0; char * mfile = NULL; fsl_cx * f; fsl_db * db; char failCrosslink = 0; fcli.appHelp = fcli_local_help; rc = fcli_setup(argc, argv); if(FSL_RC_BREAK==rc) /* --help */ return 0; else if(rc) goto end; f = fcli.f; db = fsl_cx_db_repo(f); if(!db){ rc = fsl_cx_err_set(f, FSL_RC_MISUSE, "This app requires a repository db."); goto end; } failCrosslink = fcli_flag2("fx", "fail-xlink", NULL); fsl_xlink_listener( f, fcli.appName, my_xlink_f, &failCrosslink ); App.checkRCard = fcli_flag2("r", "r-card", NULL); App.doCrosslink = fcli_flag("crosslink", NULL); fcli_flag2("f", "file", &mfile); if(fcli_has_unused_flags(0)) goto end; if(!mfile){ mfile = fcli_next_arg(1); #ifndef _WIN32 if(!mfile && !isatty(0)){ mfile = fsl_strdup("-"); }else #endif if(!mfile){ fcli_help(); rc = FSL_RC_MISUSE; goto end; } } rc = test_parse_1(mfile); end: fsl_free(mfile); return (fcli_err_report(1) || rc) ? EXIT_FAILURE : EXIT_SUCCESS; }