/* * Copyright 1995, 2009 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ /* * This is a client program to tail a server journal using * the export command. Those journal records can go to a file * and to a child process or stdout. Position state is maintained * in a file. Some care is taken to identify when the journal * is complete in the sense of no outstanding transactions. */ /* $Id: //guest/perforce_software/p4jtail/p4jtail.cc#3 $ */ # define NEED_SLEEP # define NEED_FILE # include <clientapi.h> # include <debug.h> # include <error.h> # include <errorlog.h> # include <options.h> # include <runcmd.h> # include <i18napi.h> # include <enviro.h> static ErrorId intervalerror = { ErrorOf( 0, 0, E_FATAL, 0, 0 ), "interval must be zero or positive" }; class ClientUserJournal : public ClientUser { public: ClientUserJournal() : outfd(-1), journalno(0), sequence(0), errorseen(0), dataseen(0), rotateseen(0), rc(NULL), savedjournal(NULL) {} ~ClientUserJournal() { delete rc; delete savedjournal; } void OutputStat( StrDict * ); void OutputError( const char * ); void SetStateFile( const StrPtr *, Error * ); void UpdateStateFile( Error *, const StrPtr * = NULL ); void SetOutputCommand( int ac, char **av ); void Output( const StrPtr &, Error *e ); void EndCommand(); int outfd; int journalno; long sequence; int errorseen; int dataseen; int rotateseen; StrBuf statefile; RunArgs rargs; RunCommand *rc; FileSys *savedjournal; }; void ClientUserJournal::Output( const StrPtr &dat, Error *e ) { if( savedjournal ) savedjournal->Write( dat.Text(), dat.Length(), e ); if( rc ) { if( outfd == -1 ) { int fds[2]; rc->RunChild( rargs, RCO_AS_SHELL|RCO_USE_STDOUT, fds, e ); if( e->Test() ) { errorseen = 1; return; } outfd = fds[1]; } write( outfd, dat.Text(), dat.Length() ); } else printf( "%.*s", dat.Length(), dat.Text() ); dataseen = 1; } void ClientUserJournal::EndCommand() { if( outfd < 0 ) return; close( outfd ); outfd = -1; rc->WaitChild(); } void ClientUserJournal::OutputStat( StrDict *vars ) { Error e; StrPtr *dat; dat = vars->GetVar( P4Tag::v_data ); if( dat && dat->Length() > 0 ) { StrPtr *complete = vars->GetVar( "complete" ); if( complete ) { int s = complete->Atoi(); Output( StrRef( dat->Text(), s ), &e ); StrPtr *pos = vars->GetVar( "pos" ); if( pos ) { // journal no should not change, but we'll // take it anyways journalno = pos->Atoi(); const char *cp = pos->Contains( StrRef( "/" ) ); sequence = cp ? StrPtr::Atoi( ++cp ) : 0; UpdateStateFile( &e, pos ); } if( s < dat->Length() ) Output( StrRef( dat->Text() + s, dat->Length() - s ), &e ); } else Output( *dat, &e ); if( e.Test() ) { AssertLog.Report( &e ); errorseen = 2; return; } } dat = vars->GetVar( P4Tag::v_counter ); if( dat ) { // skip to next journal journalno = dat->Atoi(); sequence = 0; rotateseen = 1; } } void ClientUserJournal::OutputError( const char *msg ) { ClientUser::OutputError( msg ); errorseen = 1; } void ClientUserJournal::SetStateFile( const StrPtr *fname, Error *e ) { statefile.Set( fname ); FileSys *f = FileSys::Create( FST_TEXT ); f->Set( statefile ); f->Open( FOM_READ, e ); if( !e->Test() ) { StrBuf buf; f->ReadFile( &buf, e ); f->Close( e ); journalno = buf.Atoi(); const char *cp; if( cp = buf.Contains( StrRef( "/" ) ) ) sequence = StrPtr::Atoi( ++cp ); } e->Clear(); delete f; } void ClientUserJournal::UpdateStateFile( Error *e, const StrPtr *pos ) { if( !dataseen || !statefile.Length() ) return; FileSys *f = FileSys::Create( FST_TEXT ); f->Set( statefile ); f->Open( FOM_WRITE, e ); if( e->Test() ) errorseen = 3; else { StrBuf buf; if( !pos ) { buf << journalno << "/" << sequence << "\n"; pos = &buf; } f->Write( pos->Text(), pos->Length(), e ); f->Perms( FPM_RW ); f->Close( e ); } delete f; } void ClientUserJournal::SetOutputCommand( int ac, char **av ) { // setup command invocations rargs.SetArgs( ac, av ); rc = new RunCommand; } int jtail( int ac, char **av, Options &preopts, Options &opts, const char *progname ) { Error e; StrPtr *s; int interval = 2; int keepopen = 0; int exitOnRotate = 0; // Debugging for( int i = 0; s = preopts.GetValue( 'v', i ); i++ ) p4debug.SetLevel( s->Text() ); ClientApi client; ClientUserJournal ui; Enviro enviro; const char *lc; enviro.Config( client.GetCwd() ); if( s = preopts[ 'C' ] ) lc = s->Text(); else lc = enviro.Get( "P4CHARSET" ); if( lc ) { CharSetApi::CharSet cs = CharSetApi::Lookup( lc ); if( (int)cs == -1 ) { // bad charset specification printf( "Character set must be one of:\n" "none, utf8, utf8-bom, iso8859-1, shiftjis, eucjp, iso8859-15,\n" "iso8859-5, macosroman, winansi, koi8-r, cp949, cp1251,\n" "utf16, utf16-nobom, utf16le, utf16le-bom, utf16be,\n" "utf16be-bom, utf32, utf32-nobom, utf32le, utf32le-bom, utf32be,\n" "utf32be-bom, utf8unchecked or utf8unchecked-bom\n" "Check P4CHARSET and your '-C' option.\n" ); return 1; } else { CharSetApi::CharSet ws = cs; if( ( s = preopts[ 'Q' ] ) || ( lc = enviro.Get( "P4COMMANDCHARSET" ) ) ) { if( s ) lc = s->Text(); cs = CharSetApi::Lookup( lc ); if( (int)cs == -1 ) { printf( "P4COMMANDCHARSET unknown\n" ); return 1; } } if( CharSetApi::Granularity( cs ) != 1 ) { printf( "p4 can not support a wide charset unless\n" "P4COMMANDCHARSET is set to another charset.\n" ); return 1; } client.SetTrans( cs, ws, cs, cs ); } } if( s = preopts[ 'u' ] ) client.SetUser( s ); if( s = preopts[ 'p' ] ) client.SetPort( s ); if( s = preopts[ 'P' ] ) { client.SetPassword( s ); memset( s->Text(), '\0', s->Length() ); } const StrPtr *statefile = opts[ 's' ]; if( statefile ) ui.SetStateFile( statefile, &e ); const StrPtr *jop = opts[ 'j' ]; if( jop ) { ui.journalno = jop->Atoi(); const char *cp; if( cp = jop->Contains( StrRef( "/" ) ) ) ui.sequence = StrPtr::Atoi( ++cp ); if( ui.journalno < 0 ) ui.journalno = 0; if( ui.sequence < 0 ) ui.sequence = 0; } if( opts[ 'i' ] ) interval = opts[ 'i' ]->Atoi(); if( interval < 0 ) { e.Set( intervalerror ); AssertLog.Report( &e ); return 1; } if( opts[ 'k' ] ) keepopen = 1; if( opts[ 'x' ] ) exitOnRotate = 1; if( opts[ 'o' ] ) { ui.savedjournal = FileSys::Create( FST_ATEXT ); ui.savedjournal->Set( *opts[ 'o' ] ); ui.savedjournal->Open( FOM_WRITE, &e ); if( e.Test() ) { delete ui.savedjournal; ui.savedjournal = NULL; AssertLog.Report( &e ); return 1; } } if( ac > 0 ) ui.SetOutputCommand( ac, av ); const StrPtr *prefixop = opts[ 'J' ]; client.Init( &e ); if( e.Test() ) { AssertLog.Report( &e ); return 1; } client.SetProg( progname ); for( ;; ) { StrBuf buf; buf << ui.journalno << "/" << ui.sequence; client.SetVar( StrRef::Null(), StrRef( "-r" ) ); client.SetVar( StrRef::Null(), StrRef( "-j" ) ); client.SetVar( StrRef::Null(), buf ); if( prefixop ) { client.SetVar( StrRef::Null(), StrRef( "-J" ) ); client.SetVar( StrRef::Null(), *prefixop ); } client.Run( "export", &ui ); if( ui.errorseen || !keepopen || interval == 0 || ( exitOnRotate && ui.rotateseen ) ) ui.EndCommand(); if( ui.errorseen ) break; ui.UpdateStateFile( &e ); if( e.Test() || interval == 0 || ( exitOnRotate && ui.rotateseen ) ) break; sleep( interval ); } client.Final( &e ); if( e.Test() ) { ui.EndCommand(); AssertLog.Report( &e ); } return ui.errorseen; } #ifdef BUILD_JTAIL static ErrorId usage = { ErrorOf( 0, 0, 0, 0, 0), "p4jtail [ -p port ][ -u user ][ -P password ][ -C charset ]\n" " [ -j token ][ -s statefile ][ -i interval ][ -k -x ]\n" " [ -J prefix ][ -o output ][ command ]\n" }; int main( int ac, char **av ) { AssertLog.SetTag( "p4jtail" ); Error e; Options opts; opts.Parse( --ac, ++av, "xkj:i:J:s:u:p:P:v:o:C:Q:", OPT_ANY, usage, &e ); if( e.Test() ) { AssertLog.Report( &e ); return 1; } return jtail( ac, av, opts, opts, "p4jtail" ); } #endif
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 7533 | scommon | Updating with RCS $Id$ keyword for version tracking. | ||
#2 | 7532 | scommon |
Updates to p4jtail to enable support for unicode servers, and a new option (-x) to allow p4jtail to exit when a journal rotation is detected. See the CHANGES file for further details. |
||
#1 | 7426 | michael |
Initial p4jtail code and README w/disclaimer. Builds with Jam. |