1 /*
2 **
3 ** bplay/shmbuf.c (C) David Monro 1996
4 **
5 ** Copyright under the GPL - see the file COPYING in this directory
6 **
7 ** Adapted by J.A. Bezemer for use with GramoFile - July, August 1998
8 **
9 ** Patch to compile with egcs from Daniel Kobras, applied by J.A. Bezemer
10 ** - October, 1998
11 */
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <errno.h>
17 #include <signal.h>
18 #include <sys/types.h>
19 #include <sys/ipc.h>
20 #include <sys/sem.h>
21 #include <sys/shm.h>
22 #include <sys/wait.h>
23 
24 #include "../errorwindow.h"
25 #include "../clrscr.h"
26 #include "../playwav.h"
27 #include "../secshms.h"
28 #include "../reclp_main.h"
29 #include "../boxes.h"
30 #include "../buttons.h"
31 #include <string.h>
32 #ifndef OLD_CURSES
33 #include <ncurses.h>
34 #else
35 #include <curses.h>
36 #endif
37 
38 #ifndef SEMMSL
39 #ifdef __FreeBSD__
40 /*
41  * you may want to adjust this to whats configured into your kernel,
42  * 30 is just the current default. (see <sys/sem.h>)  -nox
43  */
44 #define SEMMSL  30
45 #else
46 #define SEMMSL  32
47 #endif
48 #endif
49 
50 #ifdef _SEM_SEMUN_UNDEFINED
51 union semun				/* This has gone out of standard  */
52 {					/* libc headers as of glibc2.1,   */
53 	int val;			/* we need to define it ourselves.*/
54 	struct semid_ds *buf;
55 	unsigned short int *array;
56 	struct seminfo *__buf;
57 };
58 #endif
59 
60 extern void finish_curses(int sig);
61 
62 /* The size of the big array */
63 /* (currently 256K - nearly 1.5 sec at max rate) */
64 #define BIGBUFFSIZE 0x040000
65 
66 /* Types */
67 typedef struct blockinf_t
68 {
69     int count;	/* How many bytes in this buffer */
70     int last;	/* Should we terminate after this buffer? */
71     int setit;	/* Should we re-set the audio parameters to be the ones here? */
72     int speed;
73     int bits;
74     int stereo;
75 } blockinf_t;
76 
77 /* Statics - mostly shared memory etc */
78 static int shmid, shmid2, *disksemid, *sndsemid;
79 static char *bigbuff;
80 static char **buffarr;
81 static int numbuffs, numsemblks;
82 static blockinf_t *buffinf;
83 static volatile int stoprecording;
84 
85 /* prototypes */
86 void cleanupsems(void);
87 static void sighandler(int i);
88 static void childerrhandler(int i);
89 
90 /* Extern globals */
91 extern int abuf_size;
92 extern int audio;
93 extern char *progname;
94 
95 
96 /* extern prototypes */
97 extern void ErrDie(char *err);
98 extern void snd_parm(int speed, int bits, int stereo);
99 extern void sync_audio(void);
100 
init_shm(void)101 void init_shm(void)
102 {
103     int i;
104 
105 	/* Create, attach and mark for death the big buffer */
106     shmid = shmget(IPC_PRIVATE, BIGBUFFSIZE,
107 	IPC_EXCL | IPC_CREAT | 0600);
108     if (shmid == -1)
109 	ErrDie("shmget");
110     bigbuff = shmat(shmid, IPC_RMID, SHM_RND);
111     if (bigbuff == (char*)-1)
112     {
113 	perror("shmat");
114 	if(shmctl(shmid, IPC_RMID, NULL))
115 		perror("shmctl");
116 	exit(-1);
117     }
118     if(shmctl(shmid, IPC_RMID, NULL))
119 	ErrDie("shmctl");
120 
121     /* Create an array of pointers. Point them at equally spaced
122     ** chunks in the main buffer, to give lots of smaller buffers
123     */
124     numbuffs = BIGBUFFSIZE/abuf_size;
125     buffarr = (char**)malloc(numbuffs*sizeof(char*));
126     for (i=0; i<numbuffs; i++)
127 	buffarr[i] = bigbuff + i * abuf_size;
128 
129     /* Create a small amount of shared memory to hold the info
130     ** for each buffer.
131     */
132     shmid2 = shmget(IPC_PRIVATE, numbuffs*sizeof(blockinf_t),
133 	IPC_EXCL | IPC_CREAT | 0600);
134     if (shmid2 == -1)
135 	ErrDie("shmget");
136     buffinf = (blockinf_t*)shmat(shmid2, IPC_RMID, SHM_RND);
137     if (buffinf == (blockinf_t*)((char*)-1))
138     {
139 	perror("shmat");
140 	if(shmctl(shmid2, IPC_RMID, NULL))
141 		perror("shmctl");
142 	exit(-1);
143     }
144     if(shmctl(shmid2, IPC_RMID, NULL))
145 	ErrDie("shmctl");
146 
147 #ifndef __DragonFly__
148 #if USEBUFFLOCK
149 	/* Ok, go root to lock the buffers down */
150     if(setreuid(geteuid(), getuid()) == -1)
151     {
152 #ifndef LP2CD
153 	fprintf(stderr, "%s: setreuid: %s: continuing anyway\n",
154 	    progname, strerror(errno));
155 	fprintf(stderr, "real uid = %d, effective uid = %d\n",
156 	    getuid(), geteuid());
157 #endif
158     }
159 
160     if(shmctl(shmid, SHM_LOCK, NULL) || shmctl(shmid2, SHM_LOCK, NULL))
161 #ifndef LP2CD
162 	fprintf(stderr,
163 	    "%s: shmctl: %s: continuing with unlocked buffers\n",
164 		progname, strerror(errno))
165 #endif
166 		;
167 
168     if(setreuid(geteuid(), getuid()) == -1)
169     {
170 #ifndef LP2CD
171 	fprintf(stderr, "%s: setreuid: %s: continuing anyway\n",
172 	    progname, strerror(errno));
173 	fprintf(stderr, "real uid = %d, effective uid = %d\n",
174 	    getuid(), geteuid());
175 #endif
176     }
177 
178 #endif
179 #endif
180     /* Set up the appropriate number of semaphore blocks */
181     numsemblks = numbuffs/SEMMSL;
182     if((numsemblks * SEMMSL) < numbuffs)
183 	numsemblks++;
184     /* Malloc arrays of semaphore ids (ints) for the semaphores */
185     if ((disksemid = (int*)malloc(sizeof(int)*numsemblks)) == NULL)
186 	ErrDie("malloc");
187     if ((sndsemid = (int*)malloc(sizeof(int)*numsemblks)) == NULL)
188 	ErrDie("malloc");
189     /* Create the semaphores */
190     for (i=0;i<numsemblks;i++)
191     {
192 	if ((disksemid[i] = semget(IPC_PRIVATE, SEMMSL,
193 	    IPC_EXCL | IPC_CREAT | 0600)) == -1)
194 	    ErrDie("semget");
195 	if ((sndsemid[i] = semget(IPC_PRIVATE, SEMMSL,
196 	    IPC_EXCL | IPC_CREAT | 0600)) == -1)
197 	    ErrDie("semget");
198     }
199     /* Catch some signals, so we clean up semaphores */
200     signal(SIGINT, sighandler);
201     /* Out of disk space errors are handled explicitly already. */
202     signal(SIGXFSZ, SIG_IGN);
203 }
204 
205 
206 /* Does an up on the appropriate semaphore */
up(int * semblk,int xsemnum)207 void up(int *semblk, int xsemnum)
208 {
209     struct sembuf sbuf;
210 
211     sbuf.sem_num = xsemnum%SEMMSL;
212     sbuf.sem_op = 1;
213     sbuf.sem_flg = 0;
214 
215     if (semop(semblk[xsemnum/SEMMSL], &sbuf, 1) == -1)
216 	perror("semop");
217 }
218 
219 /* Does a down on the appropriate semaphore */
down(int * semblk,int xsemnum)220 void down(int *semblk, int xsemnum)
221 {
222     struct sembuf sbuf;
223 
224     sbuf.sem_num = xsemnum%SEMMSL;
225     sbuf.sem_op = -1;
226     sbuf.sem_flg = 0;
227 
228     if (semop(semblk[xsemnum/SEMMSL], &sbuf, 1) == -1)
229 	perror("semop");
230 }
231 
232 /* The recording function */
shmrec(int outfd,long totalcount,int terminate)233 void shmrec(int outfd, long totalcount, int terminate)
234 {
235     pid_t pid;
236     int i;
237     button_t ok_button;
238     char timestring[100];
239 
240     nodelay(stdscr, TRUE);
241 
242     sync();
243 
244     pid = fork();
245     if (pid == 0)
246     {
247 	int cbuff = 0;
248 	long totalrd = 0; /* we need it here too... */
249 	int err = 0;
250 
251 #ifdef VUMETER
252 	signed short *ssptr;
253 	signed short leftvalue, rightvalue, maxleft, maxright;
254 	long samplesabove50pct = 0;
255 	long samplesabove90pct = 0;
256 	long samplesabove99pct = 0;
257 	long samplestooloud = 0;
258 #endif
259 
260 	/* Uncatch the signals */
261 	signal(SIGINT, SIG_DFL);
262 
263 	/* Child process writes the disk */
264 	while(1)
265 	{
266 	    long count, numwr, trgt;
267 	    char *tmpptr;
268 
269 	    /* Grab the buffer. Blocks till it is OK to do so. */
270 	    down(disksemid, cbuff);
271 	    /* Spit it out */
272 	    tmpptr = buffarr[cbuff];
273 #ifdef VUMETER
274 	    ssptr = (signed short *) tmpptr;
275 #endif
276 	    numwr = 0;
277 	    trgt = buffinf[cbuff].count;
278 	    while ( (numwr < trgt) &&
279 	      ((count = write(outfd, tmpptr, trgt - numwr)) > 0) )
280 	    {
281 		numwr += count;
282 		tmpptr += count;
283                 totalrd += count;
284 	    }
285 
286 	    if (count == -1 && errno != EINTR) {
287 		    err = errno;
288 		    for (i = 0; i < numbuffs; i++)
289 			    up(sndsemid, i);
290 		    break;
291 	    }
292 
293 #ifdef VUMETER
294 	    maxleft = 0;
295 	    maxright = 0;
296 	    trgt = buffinf[cbuff].count;
297 
298 	    for (numwr = 0; numwr < trgt; numwr += 4 )
299 	    {
300 		leftvalue=abs(*ssptr);
301 		if (leftvalue > maxleft) maxleft = leftvalue;
302 		ssptr ++;
303 
304 		rightvalue=abs(*ssptr);
305 		if (rightvalue > maxright) maxright = rightvalue;
306 		ssptr ++;
307 
308 		if (leftvalue > 16383 || rightvalue > 16383)
309 		  samplesabove50pct ++;
310 		if (leftvalue > 29490 || rightvalue > 29490)
311 		  samplesabove90pct ++;
312 		if (leftvalue > 32439 || rightvalue > 32439)
313 		  samplesabove99pct ++;
314 		if (leftvalue > 32764 || rightvalue > 32764)
315 		  samplestooloud ++;
316 	    }
317 	    move(ERROR_WINDOW_Y + 2,ERROR_WINDOW_X +1);
318 	    addstr("L: =");
319 	    leftvalue = maxleft / (32768/(ERROR_WINDOW_W-6));
320 	    for (numwr = 0; numwr < leftvalue; numwr ++)
321 		addch(numwr >= ERROR_WINDOW_W-8 ? '#' : '=');
322 	    for (; numwr < ERROR_WINDOW_W-6; numwr ++)
323 		addch(' ');
324 
325 	    move(ERROR_WINDOW_Y + 3,ERROR_WINDOW_X +1);
326 	    addstr("R: =");
327 	    rightvalue = maxright / (32768/(ERROR_WINDOW_W-6));
328 	    for (numwr = 0; numwr < rightvalue; numwr ++)
329 		addch(numwr >= ERROR_WINDOW_W-8 ? '#' : '=');
330 	    for (; numwr < ERROR_WINDOW_W-6; numwr ++)
331 		addch(' ');
332 
333 	    move(0,79);
334 	    refresh();
335 #endif
336 
337 	    /* Mark the buffer as clean */
338 	    up(sndsemid, cbuff);
339 
340 	    /* If the block was marked as the last one, stop */
341 	    if (buffinf[cbuff].last)
342 		break;
343 	    /* Advance the pointer */
344 	    cbuff++;
345 	    cbuff%=numbuffs;
346 	}
347 	/* Tidy up and exit, we are being waited for */
348 	close(outfd);
349 
350 #ifdef VUMETER
351 	/* Display some informative data. This is really weird: we
352            display it here (in the child), then exit() and wait for a key
353            in the _parent_ process. But it's the only simple way to get it
354            working */
355 	clearscreen(RECLP_HEADERTEXT);
356 
357 	printw("\n\n");
358 	printw("      Recording information:\n\n\n");
359 
360 	if (!err) {
361 
362 	  fsec2hmsf ( (double) totalrd / (4 * 44100) , timestring);
363 	  printw("    Recorded time    : %s\n", timestring);
364 	  printw("    Recorded samples : %11ld\n", totalrd / 4);
365 	  printw("    Recorded bytes   : %11ld  (excl. header)\n", totalrd);
366 	  printw("\n");
367 	  printw("    Samples above 50%% of max. volume  : %9ld  (%5.1f%%)\n",
368             samplesabove50pct, samplesabove50pct * 100. / (totalrd/4));
369 	  printw("    Samples above 90%% of max. volume  : %9ld  (%5.1f%%)\n",
370             samplesabove90pct, samplesabove90pct * 100. / (totalrd/4));
371 	  printw("    Samples above 99%% of max. volume  : %9ld  (%5.1f%%)\n",
372             samplesabove99pct, samplesabove99pct * 100. / (totalrd/4));
373 	  printw("    Really too loud (clipped) samples : %9ld  (%5.1f%%)\n",
374             samplestooloud, samplestooloud * 100. / (totalrd/4));
375 
376 	} else {
377 		printw("    Recording has terminated due to an error.\n");
378 		printw("    Operating system reports: %s\n", strerror(err));
379 	}
380 
381 #if 0
382 	/* The computation of the avg volume is not simple. One approach
383            is totalvolume+=abs(sampleleft)+abs(sampleright) for each
384            sample, but if totalvolume gets too big, nothing is added any
385            more (lack of precision). If anyone has a better (working)
386            idea, please tell me! */
387 	printw("\n");
388 	printw("    Average volume : %7.1f  (%5.1f%% of max.)\n",
389           totalvolume / (totalrd/2),
390           (totalvolume / (totalrd/2) * 100) / 32768);
391 					/* (totalrd/2)=((totalrd/4)*2) */
392 #endif /* 0 */
393 
394 	ok_button.text = " OK ";
395 	ok_button.y = 20;
396 	ok_button.x = 71;
397 	ok_button.selected = TRUE;
398 
399         button_display (&ok_button);
400         mybox (ok_button.y - 1, ok_button.x - 1,
401                3, strlen (ok_button.text) + 2);
402 	move (0, 79);
403 	refresh();
404 
405 	/* Tell parent to stop recording */
406 	if (err)
407 		kill(getppid(), SIGUSR1);
408 
409 #endif
410 
411 	exit(0);
412     }
413     else
414     {
415 	/* Parent reads audio */
416 	int cbuff = 0;
417 	long totalrd = 0;
418 
419 	int in_ch;
420 
421 	signal(SIGUSR1, childerrhandler);
422 
423 	while (totalrd < totalcount && !stoprecording)
424 	{
425 	    long trgt, count, numrd;
426 	    char *tmpptr;
427 	    trgt = totalcount - totalrd;
428 	    if (trgt > abuf_size)
429 		trgt = abuf_size;
430 	    /* Get the buffer. Blocks until OK to do so */
431 	    down(sndsemid, cbuff);
432 	    if (stoprecording)
433 		    break;
434 
435 	    /* Read a block of data */
436 	    numrd = 0;
437 	    tmpptr = buffarr[cbuff];
438 	    while( (numrd < trgt) &&
439 		((count = read(audio, tmpptr, trgt - numrd)) > 0) &&
440 		!stoprecording)
441 	    {
442 		numrd += count;
443 		tmpptr += count;
444 	    }
445 	    /* Update the count for this block */
446 	    buffinf[cbuff].count = numrd;
447 	    /* Mark the buffer dirty */
448 	    up(disksemid, cbuff);
449 	    /* Update the amount done */
450 	    totalrd += numrd;
451 	    /* Tell the reader to stop if needed */
452 
453 	    in_ch=getch();
454 #ifdef DEBUG
455 printw(" %d",cbuff);
456 #endif
457 	    if (in_ch==KEY_ENTER || in_ch==13 || in_ch==27)
458 		stoprecording=1;
459 	    if ( ((totalrd >= totalcount) && terminate) || stoprecording)
460 		buffinf[cbuff].last = 1;
461 	    /* Update the counter */
462 	    cbuff++;
463 	    cbuff%=numbuffs;
464 	}
465 	/* Tidy up and wait for the child */
466 	close(audio);
467 
468 	/* XXX fix the occasional deadlock in the following wait()  -nox */
469 	for (i = 0; i < numbuffs; i++)
470 	    up(disksemid, i);
471 
472 	wait(NULL);
473 
474 	/* Free all the semaphores */
475 	cleanupsems();
476 
477 #ifdef VUMETER
478 	nodelay(stdscr, FALSE);
479 
480 	/* child has displayed informative data */
481 	do
482 	  i = getch ();
483 	while (i != 13 && i != KEY_ENTER && i != 27);
484 #endif
485     }
486 }
487 
diskread(int infd,long totalplay,long skipped,char hd_buf[20],int terminate,int speed,int bits,int stereo)488 void diskread(int infd, long totalplay, long skipped, char hd_buf[20],
489     int terminate, int speed, int bits, int stereo)
490 {
491 
492     int count, i, limited = 0;
493     char *tmppt;
494     long numread, totalread = 0;
495     int first = 1;
496 
497     static int triggered = 0;	/* Have we let the writer go? */
498     static int cbuff = 0;	/* Which buffer */
499 
500 char tempstring[50];
501 int in_ch;
502 
503     if (totalplay) limited = 1;
504     if (totalplay == -1)
505     {
506 	totalplay = 0;
507 	limited = 1;
508     }
509 
510     clearscreen(PLAYWAV_HEADERTEXT);
511     error_window_display("Playing...", " Stop ");
512     nodelay(stdscr, TRUE);
513     refresh();
514 
515     while (1)
516     {
517 	int trgt;
518 
519 	/* Wait for a clean buffer */
520 	down(disksemid, cbuff);
521 	/* Read from the input */
522 	numread = 0;
523 	trgt = abuf_size;
524 	if (limited && (totalread + trgt > totalplay))
525 	    trgt = totalplay - totalread;
526 	tmppt = buffarr[cbuff];
527 	if(first && trgt)
528 	{
529 	    buffinf[cbuff].setit = 1;
530 	    buffinf[cbuff].speed = speed;
531 	    buffinf[cbuff].bits = bits;
532 	    buffinf[cbuff].stereo = stereo;
533 	    if(hd_buf)
534 	    {
535 		memcpy(tmppt, hd_buf, 20);
536 		tmppt += 20; numread = 20;
537 	    }
538 	    first = 0;
539 	}
540 	while ( (numread < trgt) &&
541 	    ((count = read(infd, tmppt, trgt - numread)) != 0) )
542 	{
543 	    tmppt += count; numread += count;
544 	}
545 #ifdef DEBUG
546 	fprintf(stderr, "in:%d, %d\n", cbuff, numread);
547 #endif
548 	/* Update the count for this block */
549 	buffinf[cbuff].count = numread;
550 	totalread += numread;
551 
552 	in_ch = getch();
553 	/* Was it our last block? */
554 	if (numread < abuf_size )
555 	{
556 	    break;
557 	}
558 	if ( in_ch == 27 || in_ch == KEY_ENTER || in_ch == 13 )
559 	{
560 	    mvprintw(ERROR_WINDOW_Y+2, ERROR_WINDOW_X+1,
561 		"Time:                 ");
562 	    move(0,79);
563 	    refresh();
564 	    break;
565 	}
566 
567 	if(triggered)
568 	{
569 	    up(sndsemid, cbuff);
570 
571 	    fsec2hmsf( (skipped + totalread - BIGBUFFSIZE - 65536.) /
572 		(speed * (bits/8) * (stereo+1)), tempstring);
573 
574 	    mvprintw(ERROR_WINDOW_Y+2, ERROR_WINDOW_X+1,
575 		"Time: %s", tempstring);
576 	    move(0,79);
577 	    refresh();
578 
579 /* fprintf(stderr,"\nbyte(1): %ld", skipped + totalread - BIGBUFFSIZE - 65536);
580 */	}
581 	else
582 	    if(cbuff == numbuffs-1)
583 	    {
584 #ifdef DEBUG
585 fprintf(stderr, "Triggering (in loop)\n");
586 #endif
587 		for(i = 0; i < numbuffs; i++)
588 		    up(sndsemid,i);
589 		    triggered = 1;
590 	    }
591 	/* Update counter */
592 	cbuff++;
593 	cbuff %= numbuffs;
594     }
595     /* Finish off this set of buffers */
596     if(terminate)
597     {
598 	buffinf[cbuff].last = 1;
599 	if(!triggered)
600 #ifdef DEBUG
601 fprintf(stderr, "Triggering (after loop, partial)\n");
602 #endif
603 	    /* If it wasn't triggered, we haven't filled past cbuff */
604 	    for(i = 0; i < cbuff; i++)
605 		up(sndsemid, i);
606 	up(sndsemid, cbuff);
607     }
608     else if((!triggered) && (cbuff == numbuffs-1))
609     {
610 #ifdef DEBUG
611 fprintf(stderr, "Triggering (after loop, full)\n");
612 #endif
613 	for(i = 0; i < numbuffs; i++)
614 	    up(sndsemid,i);
615 	    triggered = 1;
616     }
617     else if(triggered)
618 	up(sndsemid,cbuff);
619     cbuff++;
620     cbuff %= numbuffs;
621 }
622 
audiowrite(void)623 volatile void audiowrite(void)
624 {
625     int cbuff = 0, count, numwr, trgt;
626     char *tmpptr;
627 
628     /* Uncatch the signals, so we don't clean up twice */
629     signal(SIGINT, SIG_DFL);
630 
631     /* Child process writes the audio */
632     while(1)
633     {
634 	/* Wait for dirty buffer */
635 	down(sndsemid, cbuff);
636 	/* Spit to the audio device */
637 	if(buffinf[cbuff].setit)
638 	{
639 	    snd_parm(buffinf[cbuff].speed, buffinf[cbuff].bits,
640 		buffinf[cbuff].stereo);
641 	    buffinf[cbuff].setit = 0;
642 	}
643 	trgt = buffinf[cbuff].count;
644 	numwr = 0;
645 	tmpptr = buffarr[cbuff];
646 	while ( (numwr < trgt) &&
647 	    ((count = write(audio, tmpptr, trgt - numwr)) > 0) )
648 	{
649 	    if (count == -1)
650 		ErrDie("write");
651 	    numwr += count;
652 	    tmpptr += count;
653 	}
654 #ifdef DEBUG
655 fprintf(stderr, "out:%d, %d\n", cbuff, numwr);
656 #endif
657 	/* Was it the last buffer? */
658 	if (buffinf[cbuff].last)
659 	{
660 	    up(disksemid, cbuff);	/* Not really needed */
661 	    break;
662 	}
663 	/* Mark as clean */
664 	up(disksemid, cbuff);
665 	/* Update counter */
666 	cbuff++;
667 	cbuff %= numbuffs;
668     }
669     /* Tidy up and be reaped */
670     sync_audio();
671     close(audio);
672     exit(0);
673 }
674 
initsems(int disks,int snds)675 void initsems(int disks, int snds)
676 {
677     int i,j;
678 
679     for (i=0;i<numsemblks;i++)
680 	for (j=0; j<SEMMSL;j++)
681 	{
682 	    if(semctl(disksemid[i], j, SETVAL, (union semun) disks) == -1)
683 		ErrDie("semctl");
684 	    if(semctl(sndsemid[i], j, SETVAL, (union semun) snds) == -1)
685 		ErrDie("semctl");
686 	}
687 }
688 
cleanupsems(void)689 void cleanupsems(void)
690 {
691     int i;
692 
693     for (i = 0; i < numsemblks; i++)
694     {
695 	semctl(disksemid[i], 0, IPC_RMID, (union semun) 0);
696 	semctl(sndsemid[i], 0, IPC_RMID, (union semun) 0);
697     }
698 }
699 
sighandler(int i)700 static void sighandler(int i)
701 {
702 #ifndef LP2CD
703     fprintf(stderr, "signal %d received, cleaning up.\n", i);
704 #endif
705     cleanupsems();
706     finish_curses(1);
707     exit(1);
708 }
709 
childerrhandler(int i)710 static void childerrhandler(int i)
711 {
712 	stoprecording = 1;
713 }
714