Add Russian translation provided by Валерий Крувялис <valkru@mail.ru>
[xiph-mirror.git] / snatch / libsnatch.c
bloba9fdf0be20bc2075d27619209572f97f09b0bebe
1 /* top layer of subversion library to intercept RealPlayer socket and
2 device I/O. --20011101 */
4 #define _GNU_SOURCE
5 #define _REENTRANT
6 #define _LARGEFILE_SOURCE
7 #define _LARGEFILE64_SOURCE
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <math.h>
13 #include <sys/uio.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <dlfcn.h>
19 #include <sys/time.h>
20 #include <sys/types.h>
21 #include <sys/ioctl.h>
22 #include <sys/socket.h>
23 #include <sys/un.h>
24 #include <netinet/in.h>
25 #include <stdarg.h>
26 #include <pthread.h>
27 #include <X11/Xlib.h>
28 #include <X11/extensions/XShm.h>
30 static pthread_mutex_t output_mutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
31 static pthread_cond_t event_cond=PTHREAD_COND_INITIALIZER;
32 static pthread_mutex_t event_mutex=PTHREAD_MUTEX_INITIALIZER;
33 static pthread_cond_t display_cond=PTHREAD_COND_INITIALIZER;
34 static pthread_mutex_t display_mutex=PTHREAD_MUTEX_INITIALIZER;
36 static int (*libc_open)(const char *,int,mode_t);
37 static int (*libc_connect)(int sockfd, const struct sockaddr *serv_addr,
38 socklen_t addrlen);
39 static int (*libc_close)(int);
40 static size_t (*libc_read)(int,void *,size_t);
41 static size_t (*libc_write)(int,const void *,size_t);
42 static int (*libc_readv)(int,struct iovec *,int);
43 static int (*libc_writev)(int,const struct iovec *,int);
44 static int (*libc_ioctl)(int,int,void *);
45 static pid_t (*libc_fork)(void);
47 static int (*xlib_xclose)(Display *);
48 static Display *(*xlib_xopen)(const char *);
49 static int (*xlib_xdrawsegments)(Display *,Drawable,GC,XSegment *,int);
50 static Window (*xlib_xcreatewindow)(Display *,Window,int,int,unsigned int,unsigned int,
51 unsigned int,int,unsigned int,Visual*,unsigned long,
52 XSetWindowAttributes *);
53 static int (*xlib_xconfigurewindow)(Display *,Window,unsigned int,XWindowChanges *);
54 static int (*xlib_xchangeproperty)(Display *,Window,Atom,Atom,int,int,const unsigned char *,int);
55 static int (*xlib_xputimage)(Display *,Drawable,GC,XImage *,int,int,int,int,
56 unsigned int,unsigned int);
57 static int (*xlib_xshmputimage)(Display *,Drawable,GC,XImage *,int,int,int,int,
58 unsigned int,unsigned int,Bool);
60 static int (*xlib_xresizewindow)(Display *,Window,unsigned int,unsigned int);
62 static Display *Xdisplay;
64 static int debug;
65 static char *outpath;
66 static FILE *backchannel_fd=NULL;
68 static int audio_fd=-1;
69 static int audio_channels=-1;
70 static int audio_rate=-1;
71 static int audio_format=-1;
72 static long long audio_samplepos=0;
73 static double audio_timezero=0;
75 static pthread_t snatch_backchannel_thread;
76 static pthread_t snatch_event_thread;
78 static char *username=NULL;
79 static char *password=NULL;
80 static char *openfile=NULL;
81 static char *location=NULL;
83 static int snatch_active=1;
84 static int fake_audiop=0;
85 static int fake_videop=0;
87 static int output_audio_p=0;
88 static int output_video_p=0;
90 static void (*QueuedTask)(void);
92 static int outfile_fd=-1;
94 static void TempCloseOutputFile();
95 static void CloseOutputFile(int preserve);
96 static void OpenOutputFile();
98 static char *audio_fmts[]={"unknown format",
99 "8 bit mu-law",
100 "8 bit A-law",
101 "ADPCM",
102 "unsigned, 8 bit",
103 "signed, 16 bit, little endian",
104 "signed, 16 bit, big endian",
105 "signed, 8 bit",
106 "unsigned, 16 bit, little endian",
107 "unsigned, 16 bit, big endian"};
109 static int audio_fmt_bytesps[]={0,1,1,0,1,2,2,1,2,2};
111 static char *nstrdup(char *s){
112 if(s)return strdup(s);
113 return NULL;
116 static int gwrite(int fd, void *buf, int n){
117 if(fd>=0){
118 while(n){
119 int ret=(*libc_write)(fd,buf,n);
120 if(ret<0){
121 if(errno==EAGAIN)
122 ret=0;
123 else{
124 fprintf(stderr,"**ERROR: Write error on capture file!\n"
125 " : %s\n",strerror(errno));
126 TempCloseOutputFile(); /* if the error is the 2GB limit on Linux 2.2,
127 this will result in a new file getting opened */
128 return(ret);
131 buf+=ret;
132 n-=ret;
135 return(0);
138 static double bigtime(long *seconds,long *micros){
139 static struct timeval tp;
141 (void)gettimeofday(&tp, (struct timezone *)NULL);
142 if(seconds)*seconds=tp.tv_sec;
143 if(micros)*micros=tp.tv_usec;
144 return(tp.tv_sec+tp.tv_usec*.000001);
147 /* yeah, ugly, but I don't want to leak symbols. Oh and I'm lazy. */
148 #include "x11.c"
149 #include "oss.c"
150 #include "esd.c"
152 void *get_me_symbol(char *symbol){
153 void *ret=dlsym(RTLD_NEXT,symbol);
154 if(ret==NULL){
155 char *dlerr=dlerror();
156 fprintf(stderr,
157 "**ERROR: libsnatch.so could not find the function '%s()'.\n"
158 " This shouldn't happen and I'm not going to\n"
159 " make any wild guesses as to what caused it. The\n"
160 " error returned by dlsym() was:\n %s",
161 symbol,(dlerr?dlerr:"no such symbol"));
162 fprintf (stderr,"\n\nCannot continue. exit(1)ing...\n\n");
163 exit(1);
164 }else
165 if(debug)
166 fprintf(stderr," ...: symbol '%s()' found and subverted.\n",symbol);
168 return(ret);
171 void *event_thread(void *dummy){
172 if(debug)
173 fprintf(stderr," ...: Event thread %lx reporting for duty!\n",
174 (unsigned long)pthread_self());
176 while(1){
177 pthread_cond_wait(&event_cond,&event_mutex);
178 if(QueuedTask){
179 (*QueuedTask)();
180 QueuedTask=NULL;
181 }else{
182 fprintf(stderr,
183 "**ERROR: Internal fault! event thread awoke without an event\n"
184 " to process!\n");
189 void *backchannel_thread(void *dummy){
190 if(debug)
191 fprintf(stderr," ...: Backchannel thread %lx reporting for duty!\n",
192 (unsigned long)pthread_self());
194 pthread_mutex_lock(&display_mutex);
195 if(!Xdisplay)
196 pthread_cond_wait(&display_cond,&display_mutex);
197 pthread_mutex_unlock(&display_mutex);
199 while(1){
200 char rq;
201 size_t ret=fread(&rq,1,1,backchannel_fd);
202 short length;
203 char *buf=NULL;
205 if(ret<=0){
206 fprintf(stderr,"**ERROR: Backchannel lost! exit(1)ing...\n");
207 exit(1);
209 rpauth_already=0;
211 switch(rq){
212 case 'K':
214 unsigned char sym;
215 unsigned short mod;
216 ret=fread(&sym,1,1,backchannel_fd);
217 ret=fread(&mod,2,1,backchannel_fd);
218 if(ret==1)
219 FakeKeySym(sym,mod,rpplay_window);
221 CloseOutputFile(0); /* it will only happen on Robot commands that would
222 be starting a new file */
223 break;
224 case 'E':
225 case 'U':
226 case 'P':
227 case 'L':
228 case 'O':
229 case 'F':
230 case 'D':
231 ret=fread(&length,2,1,backchannel_fd);
232 if(ret==1){
233 if(length)buf=calloc(length+1,1);
234 if(length)ret=fread(buf,1,length,backchannel_fd);
235 if(length && (int)ret==length)
236 switch(rq){
237 case 'U':
238 if(username)free(username);
239 username=buf;
240 break;
241 case 'P':
242 if(password)free(password);
243 password=buf;
244 break;
245 case 'L':
246 if(location)free(location);
247 location=buf;
248 break;
249 case 'O':
250 if(openfile)free(openfile);
251 openfile=buf;
252 break;
253 case 'E':
254 if(esdsocket)free(esdsocket);
255 esdsocket=buf;
256 break;
257 case 'F':
258 if(outpath)free(outpath);
259 outpath=buf;
260 break;
261 case 'D':
262 if(ossname)free(ossname);
263 ossname=buf;
264 break;
267 break;
268 case 'T':
269 snatch_active=2;
270 FakeExposeRPPlay();
271 break;
272 case 'A':
273 snatch_active=1;
274 FakeExposeRPPlay();
275 break;
276 case 'C':
277 CloseOutputFile(1);
278 break;
279 case 'I':
280 CloseOutputFile(1);
281 snatch_active=0;
282 FakeExposeRPPlay();
283 break;
284 case 's':
285 fake_audiop=1;
286 break;
287 case 'S':
288 fake_audiop=0;
289 break;
290 case 'v':
291 fake_videop=1;
292 break;
293 case 'V':
294 fake_videop=0;
295 FakeExposeRPPlay();
296 break;
301 void initialize(void){
302 if(!libc_open){
304 /* be chatty? */
305 if(getenv("SNATCH_DEBUG"))debug=1;
306 if(debug)fprintf(stderr,
307 "----env: SNATCH_DEBUG\n"
308 " set\n");
310 /* get handles to the libc symbols we're subverting */
311 libc_open=get_me_symbol("open");
312 libc_connect=get_me_symbol("connect");
313 libc_close=get_me_symbol("close");
314 libc_read=get_me_symbol("read");
315 libc_write=get_me_symbol("write");
316 libc_readv=get_me_symbol("readv");
317 libc_writev=get_me_symbol("writev");
318 libc_ioctl=get_me_symbol("ioctl");
319 libc_fork=get_me_symbol("fork");
320 xlib_xopen=get_me_symbol("XOpenDisplay");
321 xlib_xclose=get_me_symbol("XCloseDisplay");
322 xlib_xdrawsegments=get_me_symbol("XDrawSegments");
323 xlib_xcreatewindow=get_me_symbol("XCreateWindow");
324 xlib_xconfigurewindow=get_me_symbol("XConfigureWindow");
325 xlib_xchangeproperty=get_me_symbol("XChangeProperty");
326 xlib_xputimage=get_me_symbol("XPutImage");
327 xlib_xshmputimage=get_me_symbol("XShmPutImage");
328 xlib_xresizewindow=get_me_symbol("XResizeWindow");
330 /* output path? */
331 outpath=nstrdup(getenv("SNATCH_OUTPUT_PATH"));
332 if(!outpath){
333 if(debug)
334 fprintf(stderr,
335 "----env: SNATCH_OUTPUT_PATH\n"
336 " not set. Using current working directory.\n");
337 outpath=nstrdup(".");
338 }else{
339 if(debug)
340 fprintf(stderr,
341 "----env: SNATCH_OUTPUT_PATH\n"
342 " set (%s)\n",outpath);
345 oss_hook_init();
346 esd_hook_init();
348 if(getenv("SNATCH_AUDIO_FAKE")){
349 if(debug)
350 fprintf(stderr,
351 "----env: SNATCH_AUDIO_FAKE\n"
352 " set. Faking audio operations.\n");
353 fake_audiop=1;
354 }else
355 if(debug)
356 fprintf(stderr,
357 "----env: SNATCH_AUDIO_FAKE\n"
358 " not set.\n");
360 if(getenv("SNATCH_VIDEO_FAKE")){
361 if(debug)
362 fprintf(stderr,
363 "----env: SNATCH_VIDEO_FAKE\n"
364 " set. Faking video operations.\n");
365 fake_videop=1;
366 }else
367 if(debug)
368 fprintf(stderr,
369 "----env: SNATCH_VIDEO_FAKE\n"
370 " not set.\n");
372 if(debug)
373 fprintf(stderr," ...: Now watching for RealPlayer audio/video output.\n");
377 int ret;
378 struct sockaddr_un addr;
379 char backchannel_socket[sizeof(addr.sun_path)];
380 int temp_fd;
382 memset(backchannel_socket,0,sizeof(backchannel_socket));
383 if(getenv("SNATCH_COMM_SOCKET"))
384 strncpy(backchannel_socket,getenv("SNATCH_COMM_SOCKET"),
385 sizeof(addr.sun_path)-1);
387 if(backchannel_socket[0]){
389 if(debug)
390 fprintf(stderr,
391 "----env: SNATCH_COMM_SOCKET\n"
392 " set to %s; trying to connect...\n",
393 backchannel_socket);
394 temp_fd=socket(AF_UNIX,SOCK_STREAM,0);
395 if(temp_fd<0){
396 fprintf(stderr,
397 "**ERROR: socket() call for backchannel failed.\n"
398 " returned error %d:%s\n"
399 " exit(1)ing...\n\n",errno,strerror(errno));
400 exit(1);
403 addr.sun_family=AF_UNIX;
404 strcpy(addr.sun_path,backchannel_socket);
406 if((*libc_connect)(temp_fd,(struct sockaddr *)&addr,sizeof(addr))<0){
407 fprintf(stderr,
408 "**ERROR: connect() call for backchannel failed.\n"
409 " returned error %d: %s\n"
410 " exit(1)ing...\n\n",errno,strerror(errno));
411 exit(1);
413 if(debug)
414 fprintf(stderr,
415 " ...: connected to backchannel\n");
417 backchannel_fd=fdopen(temp_fd,"w+");
418 if(backchannel_fd==NULL){
419 fprintf(stderr,
420 "**ERROR: fdopen() failed on backchannel fd. error returned:\n"
421 " %s\n exit(1)ing...\n\n",strerror(errno));
422 exit(1);
425 if(debug)
426 fprintf(stderr,
427 " ...: starting backchannel/fake event threads...\n");
429 if((ret=pthread_create(&snatch_backchannel_thread,NULL,
430 backchannel_thread,NULL))){
431 fprintf(stderr,
432 "**ERROR: could not create backchannel worker thread.\n"
433 " Error code returned: %d\n"
434 " exit(1)ing...\n\n",ret);
435 exit(1);
436 }else{
437 pthread_mutex_lock(&event_mutex);
438 if((ret=pthread_create(&snatch_event_thread,NULL,
439 event_thread,NULL))){
440 fprintf(stderr,
441 "**ERROR: could not create event worker thread.\n"
442 " Error code returned: %d\n"
443 " exit(1)ing...\n\n",ret);
444 exit(1);
447 }else
448 if(debug)
449 fprintf(stderr,
450 "----env: SNATCH_COMM_SOCKET\n"
451 " not set \n");
458 pid_t fork(void){
459 initialize();
460 return((*libc_fork)());
463 int open(const char *pathname,int flags,...){
464 va_list ap;
465 mode_t mode;
467 initialize();
469 if(oss_identify(pathname))return oss_open_hook(pathname);
471 if(flags|O_CREAT){
472 va_start(ap,flags);
473 mode=va_arg(ap,mode_t);
474 va_end(ap);
477 return ((*libc_open)(pathname,flags,mode));
480 int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen){
481 initialize();
483 if(esd_identify(serv_addr,addrlen))
484 return esd_connect_hook(sockfd,serv_addr,addrlen);
486 return ((*libc_connect)(sockfd,serv_addr,addrlen));
489 int close(int fd){
490 int ret;
492 initialize();
494 oss_close_hook(fd);
495 esd_close_hook(fd);
497 ret=(*libc_close)(fd);
499 if(fd==audio_fd){
500 audio_fd=-1;
501 audio_samplepos=0;
502 audio_channels=0;
503 audio_timezero=bigtime(NULL,NULL);
504 if(debug)
505 fprintf(stderr,
506 " ...: RealPlayer closed audio playback fd %d\n",fd);
507 CloseOutputFile(0);
510 return(ret);
513 ssize_t read(int fd, void *buf,size_t count){
514 if(esd_rw_hook_p(fd))return(esd_read_hook(fd,buf,count));
515 return((*libc_read)(fd,buf,count));
518 ssize_t write(int fd, const void *buf,size_t count){
520 if(fd==audio_fd){
522 /* track audio sync regardless of record activity or fake setting;
523 we could have record suddenly activated */
525 long a,b;
526 double now=bigtime(NULL,NULL),fa;
527 double stime=audio_samplepos/(double)audio_rate+audio_timezero;
529 /* do it first so we know how many bytes actually succeed */
530 if(!fake_audiop)
531 count=(*libc_write)(fd,buf,count);
532 if(count<=0)return(count);
534 /* video and audio buffer differently; video is always 'just
535 in time' and thus uses absolute time positioning. Video
536 frames can also arrive 'late' because of X server
537 congestion, but frames will never arrive early. Sound is
538 the opposite; samples must be queued ahead of time (and
539 will queue arbitrarily deep) and always must arrive early,
540 not late. For this reason, we need to pay attention to when
541 the current sample buffer will begin playing, not when it
542 is queued */
544 if(stime<now){
545 /* queue starved; player will need to skip or stretch. Advance
546 the absolute position to maintain sync. Note that this is
547 normal at beginning of capture or after pause */
548 audio_samplepos=(now-audio_timezero)*audio_rate;
549 stime=audio_samplepos/(double)audio_rate+audio_timezero;
552 b=modf(stime,&fa)*1000000.;
553 a=fa;
555 if(count>0 && snatch_active==1){
557 pthread_mutex_lock(&output_mutex);
558 if(outfile_fd>=0 && !output_audio_p)CloseOutputFile(1);
559 if(outfile_fd<0)OpenOutputFile();
560 pthread_mutex_unlock(&output_mutex);
562 if(outfile_fd>=0){ /* always be careful */
563 char cbuf[80];
564 int len;
566 len=sprintf(cbuf,"AUDIO %ld %ld %d %d %d %d:",a,b,audio_channels,
567 audio_rate,audio_format,count);
569 pthread_mutex_lock(&output_mutex);
570 gwrite(outfile_fd,cbuf,len);
571 gwrite(outfile_fd,(void *)buf,count);
572 pthread_mutex_unlock(&output_mutex);
576 audio_samplepos+=(count/(audio_channels*audio_fmt_bytesps[audio_format]));
578 return(count);
581 if(esd_rw_hook_p(fd))return(esd_write_hook(fd,buf,count));
583 return((*libc_write)(fd,buf,count));
586 int writev(int fd,const struct iovec *v,int n){
587 int i,ret,count=0;
588 for(i=0;i<n;i++){
589 ret=write(fd,v[i].iov_base,v[i].iov_len);
590 if(ret<0 && count==0)return(ret);
591 if(ret<0)return(count);
592 count+=ret;
593 if(ret<(int)(v[i].iov_len))return(count);
595 return(count);
598 int readv(int fd,const struct iovec *v,int n){
599 int i,ret,count=0;
600 for(i=0;i<n;i++){
601 ret=read(fd,v[i].iov_base,v[i].iov_len);
602 if(ret<0 && count==0)return(ret);
603 if(ret<0)return(count);
604 count+=ret;
605 if(ret<(int)(v[i].iov_len))return(count);
607 return(count);
610 int ioctl(int fd,unsigned long int rq, ...){
611 va_list optional;
612 void *arg;
613 initialize();
615 va_start(optional,rq);
616 arg=va_arg(optional,void *);
617 va_end(optional);
619 if(oss_ioctl_hook_p(fd))return(oss_ioctl_hook(fd,rq,arg));
621 return((*libc_ioctl)(fd,rq,arg));
624 static void queue_task(void (*f)(void)){
625 pthread_mutex_lock(&event_mutex);
626 QueuedTask=f;
627 pthread_cond_signal(&event_cond);
628 pthread_mutex_unlock(&event_mutex);
631 static void OpenOutputFile(){
632 if(outfile_fd!=-2){
633 if(!strcmp(outpath,"-")){
634 outfile_fd=STDOUT_FILENO;
635 if(debug)fprintf(stderr," ...: Capturing to stdout\n");
637 if(videocount || output_video_p){
638 output_video_p=1;
639 if(audio_channels){
640 output_audio_p=1;
641 gwrite(outfile_fd,"SNATCHAV---\n",12);
642 }else{
643 gwrite(outfile_fd,"SNATCH-V---\n",12);
645 }else{
646 if(audio_channels){
647 output_audio_p=1;
648 gwrite(outfile_fd,"SNATCHA----\n",12);
649 }else{
650 gwrite(outfile_fd,"SNATCH-----\n",12);
654 }else{
655 struct stat buf;
656 int ret=stat(outpath,&buf);
657 if(!ret && S_ISDIR(buf.st_mode)){
658 /* construct a new filename */
659 struct tm *now;
660 char buf2[4096];
661 char buf1[256];
662 time_t nows;
663 nows=time(NULL);
664 now=localtime(&nows);
665 strftime(buf1,256,"%Y%m%d_%H:%M:%S",now);
666 if(videocount || output_video_p){
667 if(audio_channels){
668 sprintf(buf2,"%s/%s_%s%dHz_%dx%d_AV.snatch",
669 outpath,
670 buf1,
671 (audio_channels==1?"mono":"stereo"),
672 audio_rate,
673 video_width,
674 video_height);
675 }else{
676 sprintf(buf2,"%s/%s_%dx%d_V.snatch",
677 outpath,
678 buf1,
679 video_width,
680 video_height);
682 }else{
683 if(audio_channels){
684 sprintf(buf2,"%s/%s_%s%dHz_A.snatch",
685 outpath,
686 buf1,
687 (audio_channels==1?"mono":"stereo"),
688 audio_rate);
689 }else{
690 return;
694 outfile_fd=open64(buf2,O_RDWR|O_CREAT|O_APPEND,0770);
695 if(outfile_fd<0){
696 fprintf(stderr,"**ERROR: Could not stat requested output path!\n"
697 " %s: %s\n\n",buf2,strerror(errno));
698 outfile_fd=-2;
699 }else{
700 if(debug)fprintf(stderr," ...: Capturing to file %s\n",buf2);
703 if(videocount || output_video_p){
704 output_video_p=1;
705 if(audio_channels){
706 output_audio_p=1;
707 gwrite(outfile_fd,"SNATCHAV---\n",12);
708 }else{
709 gwrite(outfile_fd,"SNATCH-V---\n",12);
711 }else{
712 if(audio_channels){
713 output_audio_p=1;
714 gwrite(outfile_fd,"SNATCHA----\n",12);
715 }else{
716 gwrite(outfile_fd,"SNATCH-----\n",12);
720 }else{
721 outfile_fd=open64(outpath,O_RDWR|O_CREAT|O_APPEND,0770);
722 if(outfile_fd<0){
723 fprintf(stderr,"**ERROR: Could not stat requested output path!\n"
724 " %s: %s\n\n",outpath,strerror(errno));
725 outfile_fd=-2;
726 }else{
727 if(debug)fprintf(stderr," ...: Capturing to file %s\n",outpath);
729 if(videocount || output_video_p){
730 output_video_p=1;
731 if(audio_channels){
732 output_audio_p=1;
733 gwrite(outfile_fd,"SNATCHAV---\n",12);
734 }else{
735 gwrite(outfile_fd,"SNATCH-V---\n",12);
737 }else{
738 if(audio_channels){
739 output_audio_p=1;
740 gwrite(outfile_fd,"SNATCHA----\n",12);
741 }else{
742 gwrite(outfile_fd,"SNATCH-----\n",12);
751 static void CloseOutputFile(int preserve){
752 pthread_mutex_lock(&output_mutex);
753 if(outfile_fd>=0){
754 videocount=0;
755 if(debug)fprintf(stderr," ...: Capture stopped.\n");
756 if(outfile_fd!=STDOUT_FILENO)
757 close(outfile_fd);
758 outfile_fd=-1;
759 if(!preserve){
760 output_audio_p=0;
761 output_video_p=0;
764 pthread_mutex_unlock(&output_mutex);
767 static void TempCloseOutputFile(void){
768 pthread_mutex_lock(&output_mutex);
769 if(outfile_fd>=0){
770 if(debug)fprintf(stderr," ...: Capture file closed; trying to continue...\n");
771 if(outfile_fd!=STDOUT_FILENO)
772 close(outfile_fd);
773 outfile_fd=-1;
775 pthread_mutex_unlock(&output_mutex);