REM (c) Copyright Barry Kauler, June 2013, bkhome.org
REM License GPL3 (refer: /usr/share/doc/legal)
REM InterProcess Communication for Puppy Linux, CLI utility using inotify.
REM vovchik: compile like this, reduces executable 34K to 9K...
REM bacon -o -s -o -Os -o -fdata-sections -o -ffunction-sections -o -Wl,--gc-sections -d /tmp $myfile
REM 130628 first version.

DECLARE helpflg,milliseconds TYPE int

'parse the commandline...
helpflg=0
milliseconds=0
sendstr$=""
IF argc==1 THEN helpflg=1
FOR x=1 TO argc-1
 arg$=argv[x]
 IF arg$=="--help" THEN
  helpflg=1
  CONTINUE
 ENDIF
 IF arg$=="-h" THEN
  helpflg=1
  CONTINUE
 ENDIF
 IF arg$=="-t" THEN
  INCR x
  milliseconds=VAL(argv[x])
  CONTINUE
 END IF
 sendstr$=arg$
NEXT

IF helpflg==1 THEN
 PRINT "(c) Copyright Barry Kauler, June 2013, license GPL3 (/usr/share/doc/legal)"
 PRINT "This is a CLI utility that may be used in scripts. Run like this:"
 PRINT " pup_event_ipc \"request:client[:message]\""
 PRINT " request: name of another application to send the message to*. Or,"
 PRINT " request: keyword to request information. Supported keywords:"
 PRINT "          block mailbox waitmail"
 PRINT " client:  any single-word to identify client (usually sender) application*."
 PRINT " message: any text string (that other side understands), up to 4000 bytes."
 PRINT "          any characters allowed, including \":\""
 PRINT "ex: pup_event_ipc \"otherapp:myapp:some data for otherapp\""
 PRINT "ex: pup_event_ipc \"mailbox:otherapp:some data for otherapp\""
 PRINT "ex: pup_event_ipc \"mailbox:myapp\"" 
 PRINT "ex: pup_event_ipc \"waitmail:myapp\""
 PRINT "ex: pup_event_ipc \"block:myapp\""
 PRINT "pup_event_ipc has an optional timeout (milliseconds),"
 PRINT "ex: pup_event_ipc -t 1000 \"otherapp:myapp:data for you\""
 PRINT "Will exit with non-zero value if an error:"
 PRINT " 1=timeout. 2=wrong msg format. 3-7=message-passing failure. "
 PRINT "*Any unique names that both clients agree on."
 PRINT "PLEASE SEE /usr/local/pup_event/pup_event_ipc-README.htm for more details."
 END 0
END IF


PROTO close, ftruncate, inotify_init, inotify_add_watch, inotify_rm_watch, lseek, open, poll, read, sizeof, strlen, write
DECLARE fd1,len1,leni,size_buf,thiswatchfile,otherwatchfile TYPE int
DECLARE arr1,thisapp,otherapp TYPE char*
DECLARE thisdescr,otherdescr TYPE int
size_char=sizeof(char)
size_inbuf=size_char*4096
DECLARE inbuf,bufinotify TYPE char ARRAY size_inbuf

SUB exitsub(NUMBER i)
 IF watchdir>0 THEN inotify_rm_watch(fd1,watchdir)
 IF fd1>0 THEN close(fd1)
 IF otherdescr>0 THEN close(otherdescr)
 IF thisdescr>0 THEN close(thisdescr)
 END i
END SUB

'struct pollfd, defined in asm-generic/poll.h...
RECORD pfd
 LOCAL fd TYPE int
 LOCAL events TYPE short
 LOCAL revents TYPE short
END RECORD
'linux/poll.h, only includes asm/poll.h, which only includes asm-generic/poll.h...
CONST POLLIN=1
pfd.events = POLLIN

SUB waitsub()
 'note, there is a very slight possibility of deadlocking, if reply comes in between the lseek and read/poll...
 offt=lseek(thisdescr,0,SEEK_END)
 IF offt==0 THEN
  IF milliseconds==0 THEN
   'this will block until the file is modified...
   leni=read(fd1,bufinotify,size_inbuf)
  ELSE
   pfd.fd=fd1
   eventstatus=poll(&pfd.fd,1,milliseconds)
   IF eventstatus==0 THEN exitsub(1)
   IF eventstatus<0 THEN exitsub(7)
  ENDIF
 ENDIF
 offt=lseek(thisdescr,0,SEEK_SET)
 numr=read(thisdescr,inbuf,size_inbuf)
 ft=ftruncate(thisdescr,0)
 IF numr>1 THEN
  'get rid of carriage-return char...
  inbuf[numr-1]=0
  inbuf$=inbuf
  PRINT inbuf$
 ENDIF
END SUB

'parse the message...
'SPLIT sendstr$ BY ":" TO fields$ SIZE dims
'IF dims==0 THEN exitsub(2)
field1$=""
field2$=""
field3$=""
field4$=""
message$=""
arr1=sendstr$
off1=INSTR(arr1,":")
IF off1=0 THEN exitsub(2)
arr1[off1-1]=0
field1$=arr1
off2=INSTR(arr1+off1,":")
arr1[off1+off2-1]=0
field2$=arr1+off1
IF off2>0 THEN
 off3=INSTR(arr1+off1+off2,":")
 'the message-part can have ":" chars in it...
 message$=arr1+off1+off2
 arr1[off1+off2+off3-1]=0
 field3$=arr1+off1+off2
 IF off3>0 THEN
  off4=INSTR(arr1+off1+off2+off3,":")
  arr1[off1+off2+off3+off4-1]=0
  field4$=arr1+off1+off2+off3
 ENDIF
ENDIF
'need to extract names of this-app and other-app...
otherapp=0
thisapp=0
thiscreateflag=1
SELECT field1$
CASE "mailbox"
 IF message$!="" THEN
  otherapp=field2$
 ELSE
  thisapp=field2$
 ENDIF
CASE "waitmail"
 thisapp=field2$
CASE "block"
 thisapp$=CONCAT$("block_",field2$)
 thisapp=thisapp$
' 'thisfile$ is not created here, it is created by a daemon that monitors kernel uevents...
' thiscreateflag=0
DEFAULT
 otherapp=field1$
 thisapp=field2$
END SELECT

'create the files...
otherdescr=0
thisdescr=0
watchdir=0
IF FILEEXISTS("/tmp/pup_event_ipc")==0 THEN MAKEDIR "/tmp/pup_event_ipc"
IF otherapp!=0 THEN
 otherfile$=CONCAT$("/tmp/pup_event_ipc/",otherapp)
 USEC
  /*defined in /usr/include/asm-generic/fcntl.h, and i386-linux-gnu/bits/fcntl-linux.h...*/
  #define O_WRONLY	00000001
  #define O_CREAT	00000100
  # define O_SYNC	04010000
  #define O_FSYNC	O_SYNC
  #define O_APPEND	00002000
  /*otherdescr=open(otherfile$,O_WRONLY | O_CREAT, O_FSYNC);*/
  /*otherdescr=open(otherfile$, O_WRONLY | O_CREAT, O_APPEND);*/
  otherdescr=open(otherfile$, O_WRONLY | O_CREAT | O_APPEND);
 END USEC
 IF otherdescr<=0 THEN exitsub(3)
ENDIF
IF thisapp!=0 THEN
 closedflag=0
 thisfile$=CONCAT$("/tmp/pup_event_ipc/",thisapp)
 IF thiscreateflag=1 THEN
  USEC
   #define O_RDONLY	00000000
   #define O_CREAT	00000100
   #define O_RDWR	00000002
   thisdescr=open(thisfile$,O_RDWR | O_CREAT);
  END USEC
 ELSE
  IF FILEEXISTS(thisfile$)==1 THEN
   USEC
    #define O_RDONLY	00000000
    #define O_RDWR	00000002
    thisdescr=open(thisfile$,O_RDWR);
   END USEC
  ELSE
   closedflag=1
  ENDIF
 ENDIF
 IF closedflag==0 THEN
  IF thisdescr<=0 THEN exitsub(4)
  fd1=inotify_init()
  IF fd1<=0 THEN exitsub(5)
  CONST IN_MODIFY=2
  watchdir=inotify_add_watch(fd1,thisfile$,IN_MODIFY)
  IF watchdir<=0 THEN exitsub(6)
 ENDIF
ENDIF

'now decide what to do...
SELECT field1$
CASE "mailbox"
 IF otherapp!=0 THEN
  'need to post message to otherapp... format: "mailbox:otherapp:message"
  outstr$=CONCAT$(message$,"\n")
  'position to end of file, so msgs can queue...
  'lseek(otherdescr,0,SEEK_END)
  wr=write(otherdescr,outstr$,strlen(outstr$))
  PRINT "Mailbox acknowledge"
 ELSE
  'need to check if waiting mail for thisapp...
  offt=lseek(thisdescr,0,SEEK_END)
  IF offt==0 THEN
   PRINT "Mailbox empty"
  ELSE
   offt=lseek(thisdescr,0,SEEK_SET)
   numr=read(thisdescr,inbuf,size_inbuf)
   ft=ftruncate(thisdescr,0)
   IF numr>1 THEN
    'get rid of carriage-return char...
    inbuf[numr-1]=0
    inbuf$=inbuf
    PRINT inbuf$
   ENDIF
  ENDIF
 ENDIF
CASE "waitmail"
 'need to wait for mail for thisapp... format: "waitmail:thisapp"
 waitsub()
CASE "block"
 'need to wait for block-drive hotplug event, for thisapp... format: "block:thisapp"
 IF FILEEXISTS(thisfile$)==0 THEN
  PRINT "Not implemented"
 ELSE
  waitsub()
 ENDIF
DEFAULT
 'post msg to otherapp, wait for reply to thisapp... format: "otherapp:thisapp:message"
 outstr$=CONCAT$(message$,"\n")
 wr=write(otherdescr,outstr$,strlen(outstr$))
 waitsub()
END SELECT


exitsub(0)
