Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtksh / ksh93 / src / cmd / ksh93 / edit / history.c
1 /* $XConsortium: history.c /main/4 1996/10/04 15:56:08 drk $ */
2 /***************************************************************
3 *                                                              *
4 *                      AT&T - PROPRIETARY                      *
5 *                                                              *
6 *        THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF        *
7 *                    AT&T BELL LABORATORIES                    *
8 *         AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN         *
9 *            ACCORDANCE WITH APPLICABLE AGREEMENTS             *
10 *                                                              *
11 *                Copyright (c) 1995 AT&T Corp.                 *
12 *              Unpublished & Not for Publication               *
13 *                     All Rights Reserved                      *
14 *                                                              *
15 *       The copyright notice above does not evidence any       *
16 *      actual or intended publication of such source code      *
17 *                                                              *
18 *               This software was created by the               *
19 *           Advanced Software Technology Department            *
20 *                    AT&T Bell Laboratories                    *
21 *                                                              *
22 *               For further information contact                *
23 *                    {research,attmail}!dgk                    *
24 *                                                              *
25 ***************************************************************/
26
27 /* : : generated by proto : : */
28
29 #if !defined(__PROTO__)
30 #if defined(__STDC__) || defined(__cplusplus) || defined(_proto) || defined(c_plusplus)
31 #if defined(__cplusplus)
32 #define __MANGLE__      "C"
33 #else
34 #define __MANGLE__
35 #endif
36 #define __STDARG__
37 #define __PROTO__(x)    x
38 #define __OTORP__(x)
39 #define __PARAM__(n,o)  n
40 #if !defined(__STDC__) && !defined(__cplusplus)
41 #if !defined(c_plusplus)
42 #define const
43 #endif
44 #define signed
45 #define void            int
46 #define volatile
47 #define __V_            char
48 #else
49 #define __V_            void
50 #endif
51 #else
52 #define __PROTO__(x)    ()
53 #define __OTORP__(x)    x
54 #define __PARAM__(n,o)  o
55 #define __MANGLE__
56 #define __V_            char
57 #define const
58 #define signed
59 #define void            int
60 #define volatile
61 #endif
62 #if defined(__cplusplus) || defined(c_plusplus)
63 #define __VARARG__      ...
64 #else
65 #define __VARARG__
66 #endif
67 #if defined(__STDARG__)
68 #define __VA_START__(p,a)       va_start(p,a)
69 #else
70 #define __VA_START__(p,a)       va_start(p)
71 #endif
72 #endif
73 #define HIST_MAX        (sizeof(int)*HIST_BSIZE)
74 #define HIST_BIG        (0100000-1024)  /* 1K less than maximum short */
75 #define HIST_LINE       32              /* typical length for history line */
76 #define HIST_MARKSZ     6
77 #define HIST_RECENT     600
78 #define HIST_UNDO       0201            /* invalidate previous command */
79 #define HIST_CMDNO      0202            /* next 3 bytes give command number */
80 #define HIST_BSIZE      1024            /* size of history file buffer */
81 #define HIST_DFLT       128             /* default size of history list */
82
83 #define _HIST_PRIVATE \
84         off_t   histcnt;        /* offset into history file */\
85         off_t   histmarker;     /* offset of last command marker */ \
86         int     histflush;      /* set if flushed outside of hflush() */\
87         int     histmask;       /* power of two mask for histcnt */ \
88         char    histbuff[HIST_BSIZE+1]; /* history file buffer */ \
89         off_t   histcmds[2];    /* byte offset for recent commands */
90
91 #define hist_ind(hp,c)  ((int)((c)&(hp)->histmask))
92
93 #include        <ast.h>
94 #include        <sfio.h>
95 #include        "FEATURE/time"
96 #include        <error.h>
97 #include        <ctype.h>
98 #include        <ls.h>
99 #ifdef KSHELL
100 #   include     "defs.h"
101 #   include     "variables.h"
102 #   include     "path.h"
103 #   include     "builtins.h"
104 #   include     "io.h"
105 #endif  /* KSHELL */
106 #include        "history.h"
107 #include        "national.h"
108
109 #ifndef KSHELL
110 #   define new_of(type,x)       ((type*)malloc((unsigned)sizeof(type)+(x)))
111 #   define NIL(type)            ((type)0)
112 #   define path_relative(x)     (x)
113 #   ifdef __STDC__
114 #if defined(__STDC__) || defined(__STDPP__)
115 #       define nv_getval(s)     getenv(#s)
116 #else
117 #       define nv_getval(s)     getenv("s")
118 #endif
119 #   else
120 #       define nv_getval(s)     getenv("s")
121 #   endif /* __STDC__ */
122 #   define e_unknown            "unknown"
123     char login_sh =             0;
124     char hist_fname[] =         "/.history";
125 #endif  /* KSHELL */
126
127 #ifndef O_BINARY
128 #   define O_BINARY     0
129 #endif /* O_BINARY */
130
131 int     _Hist;
132 static void     hist_marker __PROTO__((char*,long));
133 static void     hist_trim __PROTO__((int));
134 static int      hist_nearend __PROTO__((Sfio_t*, off_t));
135 static int      hist_check __PROTO__((int));
136 static int      hist_clean __PROTO__((int));
137 static int      hist_write __PROTO__((Sfio_t*, const __V_*, int, Sfdisc_t*));
138 static int      hist_exceptf __PROTO__((Sfio_t*, int, Sfdisc_t*));
139 static int      histflush;
140 static int      histinit;
141 static mode_t   histmode;
142 static int      histwfail;
143 static History_t *wasopen;
144 static History_t *hist_ptr;
145
146 #ifdef SHOPT_ACCTFILE
147     static int  acctfd;
148     static char *logname;
149 #   include <pwd.h>
150     
151     int  acctinit __PARAM__((void), ()){
152         register char *cp, *acctfile;
153         Namval_t *np = nv_search("ACCTFILE",sh.var_tree,0);
154
155         if(!np || !(acctfile=nv_getval(np)))
156                 return(0);
157         if(!(cp = getlogin()))
158         {
159                 struct passwd *userinfo = getpwuid(getuid());
160                 if(userinfo)
161                         cp = userinfo->pw_name;
162                 else
163                         cp = "unknown";
164         }
165         logname = strdup(cp);
166
167         if((acctfd=sh_open(acctfile,
168                 O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 &&
169             (unsigned)acctfd < 10)
170         {
171                 int n;
172                 if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0)
173                 {
174                         close(acctfd);
175                         acctfd = n;
176                 }
177         }
178         if(acctfd < 0)
179         {
180                 acctfd = 0;
181                 return(0);
182         }
183         if(strmatch(acctfile,e_devfdNN))
184         {
185                 char newfile[16];
186                 sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd);
187                 nv_putval(np,newfile,NV_RDONLY);
188         }
189         else
190                 fcntl(acctfd,F_SETFD,FD_CLOEXEC);
191         return(1);
192     }
193 #endif /* SHOPT_ACCTFILE */
194
195 static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
196 static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
197
198 static void hist_touch __PARAM__((__V_ *handle), (handle)) __OTORP__(__V_ *handle;){
199         touch((char*)handle, (time_t)0, (time_t)0, 0);
200 }
201
202 /*
203  * open the history file
204  * if HISTNAME is not given and userid==0 then no history file.
205  * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is
206  * cleaned up.
207  * hist_open() returns 1, if history file is open
208  */
209 int  sh_histinit __PARAM__((void), ()){
210         register int fd;
211         register History_t *hp;
212         register char *histname;
213         char *fname=0;
214         int histmask, maxlines, hist_start;
215         register char *cp;
216         register off_t hsize = 0;
217
218         if(sh.hist_ptr=hist_ptr)
219                 return(1);
220         if(!(histname = nv_getval(HISTFILE)))
221         {
222                 int offset = staktell();
223                 if(cp=nv_getval(HOME))
224                         stakputs(cp);
225                 stakputs(hist_fname);
226                 stakputc(0);
227                 stakseek(offset);
228                 histname = stakptr(offset);
229         }
230 #ifdef future
231         if(hp=wasopen)
232         {
233                 /* reuse history file if same name */
234                 wasopen = 0;
235                 sh.hist_ptr = hist_ptr = hp;
236                 if(strcmp(histname,hp->histname)==0)
237                         return(1);
238                 else
239                         hist_free();
240         }
241 #endif
242 retry:
243         cp = path_relative(histname);
244         if(!histinit)
245                 histmode = S_IRUSR|S_IWUSR;
246         if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0)
247         {
248                 hsize=lseek(fd,(off_t)0,SEEK_END);
249         }
250         if((unsigned)fd <=2)
251         {
252                 int n;
253                 if((n=fcntl(fd,F_DUPFD,10))>=0)
254                 {
255                         close(fd);
256                         fd=n;
257                 }
258         }
259         /* make sure that file has history file format */
260         if(hsize && hist_check(fd))
261         {
262                 close(fd);
263                 hsize = 0;
264                 if(unlink(cp)>=0)
265                         goto retry;
266                 fd = -1;
267         }
268         if(fd < 0)
269         {
270 #ifdef KSHELL
271                 /* don't allow root a history_file in /tmp */
272                 if(sh.userid)
273 #endif  /* KSHELL */
274                 {
275                         if(!(fname = pathtemp(NIL(char*),0,0)))
276                                 return(0);
277                         fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
278                 }
279         }
280         if(fd<0)
281                 return(0);
282         /* set the file to close-on-exec */
283         fcntl(fd,F_SETFD,FD_CLOEXEC);
284         if(cp=nv_getval(HISTSIZE))
285                 maxlines = (unsigned)atoi(cp);
286         else
287                 maxlines = HIST_DFLT;
288         for(histmask=16;histmask <= maxlines; histmask <<=1 );
289         if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t))))
290         {
291                 close(fd);
292                 return(0);
293         }
294         sh.hist_ptr = hist_ptr = hp;
295         hp->histsize = maxlines;
296         hp->histmask = histmask;
297         hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPEND|SF_SHARE);
298         memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1));
299         hp->histind = 1;
300         hp->histcmds[1] = 2;
301         hp->histcnt = 2;
302         hp->histname = strdup(histname);
303         hp->histdisc = hist_disc;
304         if(hsize==0)
305         {
306                 /* put special characters at front of file */
307                 sfwrite(hp->histfp,(char*)hist_stamp,2);
308                 sfsync(hp->histfp);
309         }
310         /* initialize history list */
311         else
312         {
313                 int first,last;
314                 off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE;
315                 hp->histind = first = hist_nearend(hp->histfp,hsize-size);
316                 hist_eof(hp);    /* this sets histind to last command */
317                 if((hist_start = (last=(int)hp->histind)-maxlines) <=0)
318                         hist_start = 1;
319                 mark = hp->histmarker;
320                 while(first > hist_start)
321                 {
322                         size += size;
323                         first = hist_nearend(hp->histfp,hsize-size);
324                         hp->histind = first;
325                 }
326                 histinit = hist_start;
327                 hist_eof(hp);
328                 if(!histinit)
329                 {
330                         sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET);
331                         hp->histind = last;
332                         hp->histmarker = mark;
333                 }
334                 histinit = 0;
335         }
336         if(fname)
337         {
338                 unlink(fname);
339                 free((__V_*)fname);
340         }
341         if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX)
342         {
343 #ifdef DEBUG
344                 sfprintf(sfstderr,"%ld: hist_trim hsize=%ld\n",(long)getpid(),(long)hsize);
345                 sfsync(sfstderr);
346 #endif /* DEBUG */
347                 hist_trim((int)hp->histind-maxlines);
348         }
349         sfdisc(hp->histfp,&hp->histdisc);
350 #ifdef KSHELL
351         (HISTCUR)->nvalue.lp = (&hp->histind);
352 #endif /* KSHELL */
353         timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (__V_*)hp->histname);
354 #ifdef SHOPT_ACCTFILE
355         if(sh_isstate(SH_INTERACTIVE))
356                 acctinit();
357 #endif /* SHOPT_ACCTFILE */
358         return(1);
359 }
360
361 /*
362  * close the history file and free the space
363  */
364
365 void hist_close __PARAM__((register History_t *hp), (hp)) __OTORP__(register History_t *hp;){
366         sfclose(hp->histfp);
367         free((char*)hp);
368         hist_ptr = 0;
369         sh.hist_ptr = 0;
370 #ifdef SHOPT_ACCTFILE
371         if(acctfd)
372         {
373                 close(acctfd);
374                 acctfd = 0;
375         }
376 #endif /* SHOPT_ACCTFILE */
377 }
378
379 /*
380  * check history file format to see if it begins with special byte
381  */
382 static int hist_check __PARAM__((register int fd), (fd)) __OTORP__(register int fd;){
383         unsigned char magic[2];
384         lseek(fd,(off_t)0,SEEK_SET);
385         if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO))
386                 return(1);
387         return(0);
388 }
389
390 /*
391  * clean out history file OK if not modified in HIST_RECENT seconds
392  */
393 static int hist_clean __PARAM__((int fd), (fd)) __OTORP__(int fd;){
394         struct stat statb;
395         return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT);
396 }
397
398 /*
399  * Copy the last <n> commands to a new file and make this the history file
400  */
401
402 static void hist_trim __PARAM__((int n), (n)) __OTORP__(int n;){
403         register char *cp;
404         register int incmd=1, c=0;
405         register History_t *hist_new, *hist_old = hist_ptr;
406         char *buff, *endbuff;
407         off_t oldp,newp;
408         struct stat statb;
409         unlink(hist_old->histname);
410         hist_ptr = 0;
411         if(fstat(sffileno(hist_old->histfp),&statb)>=0)
412         {
413                 histinit = 1;
414                 histmode =  statb.st_mode;
415         }
416         if(!sh_histinit())
417         {
418                 /* use the old history file */
419                 hist_ptr = hist_old;
420                 return;
421         }
422         hist_new = hist_ptr;
423         hist_ptr = hist_old;
424         if(--n < 0)
425                 n = 0;
426         newp = hist_seek(hist_old,++n);
427         while(1)
428         {
429                 if(!incmd)
430                 {
431                         c = hist_ind(hist_new,++hist_new->histind);
432                         hist_new->histcmds[c] = hist_new->histcnt;
433                         if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
434                         {
435                                 char locbuff[HIST_MARKSZ];
436                                 hist_marker(locbuff,hist_new->histind);
437                                 sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
438                                 hist_new->histcnt += HIST_MARKSZ;
439                                 hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
440                         }
441                         oldp = newp;
442                         newp = hist_seek(hist_old,++n);
443                         if(newp <=oldp)
444                                 break;
445                 }
446                 if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
447                         break;
448                 *(endbuff=(cp=buff)+sfslen()) = 0;
449                 /* copy to null byte */
450                 incmd = 0;
451                 while(*cp++);
452                 if(cp > endbuff)
453                         incmd = 1;
454                 else if(*cp==0)
455                         cp++;
456                 if(cp > endbuff)
457                         cp = endbuff;
458                 c = cp-buff;
459                 hist_new->histcnt += c;
460                 sfwrite(hist_new->histfp,buff,c);
461         }
462         hist_ptr = hist_new;
463         hist_cancel(hist_ptr);
464         sfclose(hist_old->histfp);
465         free((char*)hist_old);
466 }
467
468 /*
469  * position history file at size and find next command number 
470  */
471 static int hist_nearend __PARAM__((Sfio_t *iop, register off_t size), (iop, size)) __OTORP__(Sfio_t *iop; register off_t size;){
472         register unsigned char *cp, *endbuff;
473         register int n, incmd=1;
474         unsigned char *buff, marker[4];
475         if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
476                 goto begin;
477         /* skip to marker command and return the number */
478         /* numbering commands occur after a null and begin with HIST_CMDNO */
479         while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,1))
480         {
481                 n = sfslen();
482                 *(endbuff=cp+n) = 0;
483                 while(1)
484                 {
485                         /* check for marker */
486                         if(!incmd && *cp++==HIST_CMDNO && *cp==0)
487                         {
488                                 n = cp+1 - buff;
489                                 incmd = -1;
490                                 break;
491                         }
492                         incmd = 0;
493                         while(*cp++);
494                         if(cp>endbuff)
495                         {
496                                 incmd = 1;
497                                 break;
498                         }
499                         if(*cp==0 && ++cp>endbuff)
500                                 break;
501                 }
502                 size += n;
503                 sfread(iop,(char*)buff,n);
504                 if(incmd < 0)
505                 {
506                         if((n=sfread(iop,(char*)marker,4))==4)
507                         {
508                                 n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
509                                 if(n < size/2)
510                                 {
511                                         hist_ptr->histmarker = hist_ptr->histcnt = size+4;
512                                         return(n);
513                                 }
514                                 n=4;
515                         }
516                         if(n >0)
517                                 size += n;
518                         incmd = 0;
519                 }
520         }
521 begin:
522         sfseek(iop,(off_t)2,SEEK_SET);
523         hist_ptr->histmarker = hist_ptr->histcnt = 2L;
524         return(1);
525 }
526
527 /*
528  * This routine reads the history file from the present position
529  * to the end-of-file and puts the information in the in-core
530  * history table
531  * Note that HIST_CMDNO is only recognized at the beginning of a command
532  * and that HIST_UNDO as the first character of a command is skipped
533  * unless it is followed by 0.  If followed by 0 then it cancels
534  * the previous command.
535  */
536
537 void hist_eof __PARAM__((register History_t *hp), (hp)) __OTORP__(register History_t *hp;){
538         register char *cp,*first,*endbuff;
539         register int incmd = 0;
540         register off_t count = hp->histcnt;
541         int n,skip=0;
542         char *buff;
543         sfseek(hp->histfp,count,SEEK_SET);
544         while(cp=buff=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
545         {
546                 n = sfslen();
547                 *(endbuff = cp+n) = 0;
548                 first = cp += skip;
549                 while(1)
550                 {
551                         while(!incmd)
552                         {
553                                 if(cp>first)
554                                 {
555                                         count += (cp-first);
556                                         n = hist_ind(hp, ++hp->histind);
557 #ifdef future
558                                         if(count==hp->histcmds[n])
559                                         {
560         sfprintf(sfstderr,"count match n=%d\n",n);
561                                                 if(histinit)
562                                                 {
563                                                         histinit = 0;
564                                                         return;
565                                                 }
566                                         }
567                                         else if(n>=histinit)
568 #endif
569                                                 hp->histcmds[n] = count;
570                                         first = cp;
571                                 }
572                                 switch(*((unsigned char*)(cp++)))
573                                 {
574                                         case HIST_CMDNO:
575                                                 if(*cp==0)
576                                                 {
577                                                         hp->histmarker=count+2;
578                                                         cp += (HIST_MARKSZ-1);
579                                                         hp->histind--;
580 #ifdef future
581                                                         if(cp <= endbuff)
582                                                         {
583                                                                 unsigned char *marker = (unsigned char*)(cp-4);
584                                                                 int n = ((marker[0]<<16)
585 |(marker[1]<<8)|marker[2]);
586                                                                 if((n<count/2) && n !=  (hp->histind+1))
587                                                                         error(2,"index=%d marker=%d", hp->histind, n);
588                                                         }
589 #endif
590                                                 }
591                                                 break;
592                                         case HIST_UNDO:
593                                                 if(*cp==0)
594                                                 {
595                                                         cp+=1;
596                                                         hp->histind-=2;
597                                                 }
598                                                 break;
599                                         default:
600                                                 cp--;
601                                                 incmd = 1;
602                                 }
603                                 if(cp > endbuff)
604                                 {
605                                         cp++;
606                                         goto refill;
607                                 }
608                         }
609                         first = cp;
610                         while(*cp++);
611                         if(cp > endbuff)
612                                 break;
613                         incmd = 0;
614                         while(*cp==0)
615                         {
616                                 if(++cp > endbuff)
617                                         goto refill;
618                         }
619                 }
620         refill:
621                 count += (--cp-first);
622                 skip = (cp-endbuff);
623                 if(!incmd && !skip)
624                         hp->histcmds[hist_ind(hp,++hp->histind)] = count;
625         }
626         hp->histcnt = count;
627 }
628
629 /*
630  * This routine will cause the previous command to be cancelled
631  */
632
633 void hist_cancel __PARAM__((register History_t *hp), (hp)) __OTORP__(register History_t *hp;){
634         register int c;
635         if(!hp)
636                 return;
637         sfputc(hp->histfp,HIST_UNDO);
638         sfputc(hp->histfp,0);
639         sfsync(hp->histfp);
640         hp->histcnt += 2;
641         c = hist_ind(hp,--hp->histind);
642         hp->histcmds[c] = hp->histcnt;
643 }
644
645 /*
646  * flush the current history command
647  */
648
649 void hist_flush __PARAM__((register History_t *hp), (hp)) __OTORP__(register History_t *hp;){
650         register char *buff;
651         if(hp)
652         {
653                 if(buff=(char*)sfreserve(hp->histfp,0,1))
654                 {
655                         histflush = sfslen()+1;
656                         sfwrite(hp->histfp,buff,0);
657                 }
658                 else
659                         histflush=0;
660                 if(sfsync(hp->histfp)<0)
661                 {
662                         hist_close(hp);
663                         if(!sh_histinit())
664                                 sh_offoption(SH_HISTORY);
665                 }
666                 histflush = 0;
667         }
668 }
669
670 /*
671  * This is the write discipline for the history file
672  * When called from hist_flush(), trailing newlines are deleted and
673  * a zero byte.  Line sequencing is added as required
674  */
675
676 static int hist_write __PARAM__((Sfio_t *iop,const __V_ *buff,register int insize,Sfdisc_t* handle), (iop, buff, insize, handle)) __OTORP__(Sfio_t *iop;const __V_ *buff;register int insize;Sfdisc_t* handle;){
677         register char *bufptr = ((char*)buff)+insize;
678         register History_t *hp = hist_ptr;
679         register int c,size = insize;
680         register off_t cur;
681         NOT_USED(handle);
682         if(!histflush)
683                 return(write(sffileno(iop),(char*)buff,size));
684         if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
685         {
686                 error(2,"hist_flush: EOF seek failed errno=%d",errno);
687                 return(-1);
688         }
689         hp->histcnt = cur;
690         /* remove whitespace from end of commands */
691         while(--bufptr >= (char*)buff)
692         {
693                 c= *bufptr;
694                 if(!isspace(c))
695                 {
696                         if(c=='\\' && *(bufptr+1)!='\n')
697                                 bufptr++;
698                         break;
699                 }
700         }
701         /* don't count empty lines */
702         if(++bufptr <= (char*)buff)
703                 return(insize);
704         *bufptr++ = '\n';
705         *bufptr++ = 0;
706         size = bufptr - (char*)buff;
707 #ifdef  SHOPT_ACCTFILE
708         if(acctfd)
709         {
710                 int timechars, offset;
711                 offset = staktell();
712                 stakputs(buff);
713                 stakseek(staktell() - 1);
714                 timechars = sfprintf(staksp, "\t%s\t%lx\n",logname,(long)time(NIL(long *)));
715                 lseek(acctfd, (off_t)0, SEEK_END);
716                 write(acctfd, stakptr(offset), size - 2 + timechars);
717                 stakseek(offset);
718
719         }
720 #endif /* SHOPT_ACCTFILE */
721         if(size&01)
722         {
723                 size++;
724                 *bufptr++ = 0;
725         }
726         hp->histcnt +=  size;
727         c = hist_ind(hp,++hp->histind);
728         hp->histcmds[c] = hp->histcnt;
729         if(histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
730         {
731                 hp->histcnt += HIST_MARKSZ;
732                 hist_marker(bufptr,hp->histind);
733                 hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
734                 size += HIST_MARKSZ;
735         }
736         errno = 0;
737         if(write(sffileno(iop),(char*)buff,size)>=0)
738         {
739                 histwfail = 0;
740                 return(insize);
741         }
742         return(-1);
743 }
744
745 /*
746  * Put history sequence number <n> into buffer <buff>
747  * The buffer must be large enough to hold HIST_MARKSZ chars
748  */
749
750 static void hist_marker __PARAM__((register char *buff,register long cmdno), (buff, cmdno)) __OTORP__(register char *buff;register long cmdno;){
751         *buff++ = HIST_CMDNO;
752         *buff++ = 0;
753         *buff++ = (cmdno>>16);
754         *buff++ = (cmdno>>8);
755         *buff++ = cmdno;
756         *buff++ = 0;
757 }
758
759 /*
760  * return byte offset in history file for command <n>
761  */
762 off_t hist_tell __PARAM__((register History_t *hp, int n), (hp, n)) __OTORP__(register History_t *hp; int n;){
763         return(hp->histcmds[hist_ind(hp,n)]);
764 }
765
766 /*
767  * seek to the position of command <n>
768  */
769 off_t hist_seek __PARAM__((register History_t *hp, int n), (hp, n)) __OTORP__(register History_t *hp; int n;){
770         return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
771 }
772
773 /*
774  * write the command starting at offset <offset> onto file <outfile>.
775  * if character <last> appears before newline it is deleted
776  * each new-line character is replaced with string <nl>.
777  */
778
779 void hist_list __PARAM__((register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl), (hp, outfile, offset, last, nl)) __OTORP__(register History_t *hp;Sfio_t *outfile; off_t offset;int last; char *nl;){
780         register int oldc=0;
781         register int c;
782         if(offset<0 || !hp)
783         {
784                 sfputr(outfile,e_unknown,'\n');
785                 return;
786         }
787         sfseek(hp->histfp,offset,SEEK_SET);
788         while((c = sfgetc(hp->histfp)) != EOF)
789         {
790                 if(c && oldc=='\n')
791                         sfputr(outfile,nl,-1);
792                 else if(last && (c==0 || (c=='\n' && oldc==last)))
793                         return;
794                 else if(oldc)
795                         sfputc(outfile,oldc);
796                 oldc = c;
797                 if(c==0)
798                         return;
799         }
800         return;
801 }
802                  
803 /*
804  * find index for last line with given string
805  * If flag==0 then line must begin with string
806  * direction < 1 for backwards search
807 */
808
809 Histloc_t hist_find __PARAM__((register History_t*hp,char *string,register int index1,int flag,int direction), (hp, string, index1, flag, direction)) __OTORP__(register History_t*hp;char *string;register int index1;int flag;int direction;){
810         register int index2;
811         off_t offset;
812         int *coffset=0;
813         Histloc_t location;
814         location.hist_command = -1;
815         location.hist_char = 0;
816         if(!hp)
817                 return(location);
818         /* leading ^ means beginning of line unless escaped */
819         if(flag)
820         {
821                 index2 = *string;
822                 if(index2=='\\')
823                         string++;
824                 else if(index2=='^')
825                 {
826                         flag=0;
827                         string++;
828                 }
829         }
830         if(flag)
831                 coffset = &location.hist_char;
832         index2 = (int)hp->histind;
833         if(direction<0)
834         {
835                 index2 -= hp->histsize;
836                 if(index2<1)
837                         index2 = 1;
838                 if(index1 <= index2)
839                         return(location);
840         }
841         else if(index1 >= index2)
842                 return(location);
843         while(index1!=index2)
844         {
845                 direction>0?++index1:--index1;
846                 offset = hist_tell(hp,index1);
847                 if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
848                 {
849                         location.hist_command = index1;
850                         return(location);
851                 }
852 #ifdef KSHELL
853                 /* allow a search to be aborted */
854                 if(sh.trapnote&SH_SIGSET)
855                         break;
856 #endif /* KSHELL */
857         }
858         return(location);
859 }
860
861 /*
862  * search for <string> in history file starting at location <offset>
863  * If coffset==0 then line must begin with string
864  * returns the line number of the match if successful, otherwise -1
865  */
866
867 int hist_match __PARAM__((register History_t *hp,off_t offset,char *string,int *coffset), (hp, offset, string, coffset)) __OTORP__(register History_t *hp;off_t offset;char *string;int *coffset;){
868         register unsigned char *cp;
869         register int c;
870         register off_t count;
871         int line = 0;
872         int chrs=0;
873 #ifdef SHOPT_MULTIBYTE
874         int nbytes = 0;
875 #endif /* SHOPT_MULTIBYTE */
876         do
877         {
878                 if(offset>=0)
879                 {
880                         sfseek(hp->histfp,offset,SEEK_SET);
881 #ifdef SHOPT_MULTIBYTE
882                         mblen(NIL(char*),MB_CUR_MAX);
883                         nbytes = 0;
884 #endif /* SHOPT_MULTIBYTE */
885                         count = offset;
886                 }
887                 offset = -1;
888                 for(cp=(unsigned char*)string;*cp;cp++)
889                 {
890                         if((c=sfgetc(hp->histfp)) == EOF || c ==0)
891                                 break;
892                         count++;
893 #ifdef SHOPT_MULTIBYTE
894                         /* always position at character boundary */
895                         if(--nbytes > 0)
896                         {
897                                 if(cp==(unsigned char*)string)
898                                 {
899                                         cp--;
900                                         continue;
901                                 }
902                         }
903                         else if((nbytes = mblen((char*)cp,MB_CUR_MAX)) <=0)
904                                 nbytes=1;
905 #endif /* SHOPT_MULTIBYTE */
906                         if(c == '\n')
907                                 line++;
908                         /* save earliest possible matching character */
909                         if(coffset && c == *(unsigned char*)string && offset<0)
910                         {
911 #ifdef SHOPT_MULTIBYTE
912                                 offset = count + (nbytes - 1);
913 #else
914                                 offset = count;
915 #endif /* SHOPT_MULTIBYTE */
916                                 *coffset = chrs;
917                         }
918                         if(*cp != c )
919                                 break;
920                 }
921                 if(*cp==0) /* match found */
922                         return(line);
923                 chrs++;
924         }
925         while(coffset && c && c != EOF);
926         return(-1);
927 }
928
929
930 #if SHOPT_ESH || SHOPT_VSH
931 /*
932  * copy command <command> from history file to s1
933  * at most <size> characters copied
934  * if s1==0 the number of lines for the command is returned
935  * line=linenumber  for emacs copy and only this line of command will be copied
936  * line < 0 for full command copy
937  * -1 returned if there is no history file
938  */
939
940 int hist_copy __PARAM__((char *s1,int size,int command,int line), (s1, size, command, line)) __OTORP__(char *s1;int size;int command;int line;){
941         register int c;
942         register History_t *hp = hist_ptr;
943         register int count = 0;
944         register char *s1max = s1+size;
945         off_t offset;
946         if(!hp)
947                 return(-1);
948         offset =  hist_seek(hp,command);
949         while ((c = sfgetc(hp->histfp)) && c!=EOF)
950         {
951                 if(c=='\n')
952                 {
953                         if(count++ ==line)
954                                 break;
955                         else if(line >= 0)      
956                                 continue;
957                 }
958                 if(s1 && (line<0 || line==count))
959                 {
960                         if(s1 >= s1max)
961                         {
962                                 *--s1 = 0;
963                                 break;
964                         }
965                         *s1++ = c;
966                 }
967                         
968         }
969         sfseek(hp->histfp,(off_t)0,SEEK_END);
970         if(s1==0)
971                 return(count);
972         if(count && (c= *(s1-1)) == '\n')
973                 s1--;
974         *s1 = '\0';
975         return(count);
976 }
977
978 /*
979  * return word number <word> from command number <command>
980  */
981
982 char *hist_word __PARAM__((char *string,int size,int word), (string, size, word)) __OTORP__(char *string;int size;int word;){
983         register int c;
984         register char *s1 = string;
985         register unsigned char *cp = (unsigned char*)s1;
986         register int flag = 0;
987         if(!hist_ptr)
988 #ifdef KSHELL
989         {
990                 strncpy(string,sh.lastarg,size);
991                 return(string);
992         }
993 #else
994                 return(NIL(char*));
995 #endif /* KSHELL */
996         hist_copy(string,size,(int)hist_ptr->histind-1,-1);
997         for(;c = *cp;cp++)
998         {
999                 c = isspace(c);
1000                 if(c && flag)
1001                 {
1002                         *cp = 0;
1003                         if(--word==0)
1004                                 break;
1005                         flag = 0;
1006                 }
1007                 else if(c==0 && flag==0)
1008                 {
1009                         s1 = (char*)cp;
1010                         flag++;
1011                 }
1012         }
1013         *cp = 0;
1014         if(s1 != string)
1015                 strcpy(string,s1);
1016         return(string);
1017 }
1018
1019 #endif  /* SHOPT_ESH */
1020
1021 #ifdef SHOPT_ESH
1022 /*
1023  * given the current command and line number,
1024  * and number of lines back or foward,
1025  * compute the new command and line number.
1026  */
1027
1028 Histloc_t hist_locate __PARAM__((History_t *hp,register int command,register int line,int lines), (hp, command, line, lines)) __OTORP__(History_t *hp;register int command;register int line;int lines;){
1029         Histloc_t next;
1030         line += lines;
1031         if(!hp)
1032         {
1033                 command = -1;
1034                 goto done;
1035         }
1036         if(lines > 0)
1037         {
1038                 register int count;
1039                 while(command <= hp->histind)
1040                 {
1041                         count = hist_copy(NIL(char*),0, command,-1);
1042                         if(count > line)
1043                                 goto done;
1044                         line -= count;
1045                         command++;
1046                 }
1047         }
1048         else
1049         {
1050                 register int least = (int)hp->histind-hp->histsize;
1051                 while(1)
1052                 {
1053                         if(line >=0)
1054                                 goto done;
1055                         if(--command < least)
1056                                 break;
1057                         line += hist_copy(NIL(char*),0, command,-1);
1058                 }
1059                 command = -1;
1060         }
1061         next.hist_command = command;
1062         return(next);
1063 done:
1064         next.hist_line = line;
1065         next.hist_command = command;
1066         return(next);
1067 }
1068 #endif  /* SHOPT_ESH */
1069
1070
1071 /*
1072  * Handle history file exceptions
1073  */
1074 static int hist_exceptf __PARAM__((Sfio_t* fp, int type, Sfdisc_t *handle), (fp, type, handle)) __OTORP__(Sfio_t* fp; int type; Sfdisc_t *handle;){
1075         register int newfd,oldfd;
1076         History_t *hp = (History_t*)handle;
1077         if(type==SF_WRITE)
1078         {
1079                 if(errno==ENOSPC || histwfail++ >= 10)
1080                         return(0);
1081                 /* write failure could be NFS problem, try to re-open */
1082                 close(oldfd=sffileno(fp));
1083                 if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
1084                 {
1085                         if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
1086                                 return(-1);
1087                         fcntl(oldfd,F_SETFD,FD_CLOEXEC);
1088                         close(newfd);
1089                         if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
1090                         {
1091                                 register int index = hp->histind;
1092                                 lseek(oldfd,(off_t)2,SEEK_SET);
1093                                 hp->histcnt = 2;
1094                                 hp->histind = 1;
1095                                 hp->histcmds[1] = 2;
1096                                 hist_eof(hp);
1097                                 hp->histmarker = hp->histcnt;
1098                                 hp->histind = index;
1099                         }
1100                         return(1);
1101                 }
1102                 error(2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
1103                 return(-1);
1104         }
1105         return(0);
1106 }