1 /*
2  * Copyright (C) 2002 2003 2004 2005 2006 2008 2010, Magnus Hjorth
3  *
4  * This file is part of mhWaveEdit.
5  *
6  * mhWaveEdit is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * mhWaveEdit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with mhWaveEdit; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 
22 #include <config.h>
23 #include <gdk/gdkkeysyms.h>
24 
25 #include <unistd.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <signal.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 
34 #include <gtk/gtk.h>
35 
36 #include "main.h"
37 #include "pipedialog.h"
38 #include "um.h"
39 #include "inifile.h"
40 #include "tempfile.h"
41 #include "effectbrowser.h"
42 #include "gettext.h"
43 
44 struct pipe_data {
45      int fds[3];
46      gchar *command;
47      GtkWidget *error_log;
48 };
49 
50 extern char **environ;
51 
pipe_dialog_apply_proc(Chunk * chunk,StatusBar * bar,gpointer user_data)52 static Chunk *pipe_dialog_apply_proc(Chunk *chunk, StatusBar *bar,
53 				     gpointer user_data)
54 {
55      gchar *cmd;
56      gboolean sendwav;
57      PipeDialog *pd = PIPE_DIALOG(user_data);
58      Chunk *r;
59      off_t clipcount = 0;
60      cmd = history_box_get_value(pd->cmd);
61      sendwav = gtk_toggle_button_get_active(pd->sendwav);
62      inifile_set_gboolean("PipeDialog_sendWav",sendwav);
63      r = pipe_dialog_pipe_chunk(chunk,cmd,sendwav,dither_editing,bar,
64 				&clipcount);
65      if (r != NULL && clipwarn(clipcount,TRUE)) {
66 	  gtk_object_sink(GTK_OBJECT(r));
67 	  r = NULL;
68      }
69 
70      return r;
71 }
72 
pipe_dialog_apply(EffectDialog * ed)73 static gboolean pipe_dialog_apply(EffectDialog *ed)
74 {
75      gboolean b;
76      PipeDialog *pd = PIPE_DIALOG(ed);
77      b = document_apply_cb(EFFECT_BROWSER(ed->eb)->dl->selected,
78 			   pipe_dialog_apply_proc,TRUE,pd);
79      history_box_rotate_history(pd->cmd);
80      return b;
81 }
82 
pipe_dialog_init(PipeDialog * pd)83 static void pipe_dialog_init(PipeDialog *pd)
84 {
85      GtkWidget *a,*b,*c;
86      c = gtk_vbox_new(FALSE,3);
87      gtk_container_add(EFFECT_DIALOG(pd)->input_area,c);
88      gtk_widget_show(c);
89      a = gtk_hbox_new(FALSE,0);
90      gtk_box_pack_start(GTK_BOX(c),a,FALSE,FALSE,0);
91      gtk_widget_show(a);
92      b = gtk_label_new(_("Command line: "));
93      gtk_box_pack_start(GTK_BOX(a),b,FALSE,FALSE,0);
94      gtk_widget_show(b);
95      b = history_box_new("PipeDialog","");
96      gtk_box_pack_start(GTK_BOX(a),b,TRUE,TRUE,0);
97      gtk_widget_show(b);
98      pd->cmd = HISTORY_BOX(b);
99      a = gtk_check_button_new_with_label(_("Send wav header"));
100      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a),
101 				  inifile_get_gboolean("PipeDialog_sendWav",
102 						       FALSE));
103      gtk_box_pack_start(GTK_BOX(c),a,FALSE,FALSE,0);
104      gtk_widget_show(a);
105      pd->sendwav = GTK_TOGGLE_BUTTON(a);
106 
107 }
108 
pipe_dialog_class_init(EffectDialogClass * edc)109 static void pipe_dialog_class_init(EffectDialogClass *edc)
110 {
111      edc->apply = pipe_dialog_apply;
112 }
113 
pipe_dialog_get_type(void)114 GtkType pipe_dialog_get_type(void)
115 {
116      static GtkType id = 0;
117      if (!id) {
118 	  GtkTypeInfo info = {
119 	       "PipeDialog",
120 	       sizeof(PipeDialog),
121 	       sizeof(PipeDialogClass),
122 	       (GtkClassInitFunc) pipe_dialog_class_init,
123 	       (GtkObjectInitFunc) pipe_dialog_init
124 	  };
125 	  id = gtk_type_unique(effect_dialog_get_type(),&info);
126      }
127      return id;
128 }
129 
add_error_text(GtkWidget * t,gchar * text)130 static void add_error_text(GtkWidget *t,gchar *text)
131 {
132 #if GTK_MAJOR_VERSION < 2
133      gtk_text_insert(GTK_TEXT(t),gtk_style_get_font(t->style),t->style->fg,
134 		     &(t->style->white),text,-1);
135 #else
136      GtkTextBuffer *tb;
137      GtkTextIter iter;
138      tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(t));
139      gtk_text_buffer_get_end_iter(tb,&iter);
140      gtk_text_buffer_insert(tb, &iter, text, -1);
141 #endif
142 }
143 
create_error_window(gchar * command)144 static GtkWidget *create_error_window(gchar *command)
145 {
146      GtkWidget *a,*b,*c;
147      GtkWidget *t;
148      gchar *q;
149      GtkAccelGroup* ag;
150 
151      ag = gtk_accel_group_new();
152      a = gtk_window_new(GTK_WINDOW_DIALOG);
153      gtk_window_set_title(GTK_WINDOW(a),_("Process output"));
154      gtk_window_set_position (GTK_WINDOW (a), GTK_WIN_POS_CENTER);
155      gtk_window_set_default_size(GTK_WINDOW(a),470,200);
156      b = gtk_vbox_new(FALSE,0);
157      gtk_container_add(GTK_CONTAINER(a),b);
158 #if GTK_MAJOR_VERSION < 2
159      c = gtk_text_new(NULL,NULL);
160 #else
161      c = gtk_text_view_new();
162 #endif
163      t = c;
164      gtk_container_add(GTK_CONTAINER(b),c);
165      c = gtk_button_new_with_label(_("Close"));
166      gtk_widget_add_accelerator (c, "clicked", ag, GDK_KP_Enter, 0,
167 				 (GtkAccelFlags) 0);
168      gtk_widget_add_accelerator (c, "clicked", ag, GDK_Return, 0,
169 				 (GtkAccelFlags) 0);
170      gtk_widget_add_accelerator (c, "clicked", ag, GDK_Escape, 0,
171 				 (GtkAccelFlags) 0);
172 
173      gtk_signal_connect_object(GTK_OBJECT(c),"clicked",
174 			       GTK_SIGNAL_FUNC(gtk_widget_destroy),
175 			       GTK_OBJECT(a));
176      gtk_box_pack_start(GTK_BOX(b),c,FALSE,FALSE,0);
177 
178      q = g_strdup_printf(_("Output from command '%s':\n\n"),command);
179      add_error_text(t,q);
180      g_free(q);
181 
182      gtk_widget_show_all(a);
183      gtk_window_add_accel_group(GTK_WINDOW (a), ag);
184 
185      return t;
186 }
187 
pipe_dialog_open_pipe(gchar * command,int * fds,gboolean open_out)188 gpointer pipe_dialog_open_pipe(gchar *command, int *fds, gboolean open_out)
189 {
190      int cmd_in[2]={},cmd_out[2]={},cmd_err[2]={};
191      unsigned char syncbyte;
192      struct pipe_data *pd;
193      pid_t p;
194      gchar *c;
195      char *argv[4];
196      int i;
197 
198      /* Create pipes */
199      if (pipe(cmd_in)==-1 || (open_out && pipe(cmd_out)==-1) ||
200 	 pipe(cmd_err)==-1) {
201 	  c = g_strdup_printf(_("Could not create pipe: %s"),strerror(errno));
202 	  user_error(c);
203 	  g_free(c);
204 	  if (!cmd_in[0]) close(cmd_in[0]);
205 	  if (!cmd_in[1]) close(cmd_in[1]);
206 	  if (!cmd_out[0]) close(cmd_out[0]);
207 	  if (!cmd_out[1]) close(cmd_out[1]);
208 	  if (!cmd_err[0]) close(cmd_err[0]);
209 	  return NULL;
210      }
211      /* Fork */
212      p = fork();
213 
214      if (p == -1) {
215 	  /* Error (parent process) */
216 	  c = g_strdup_printf(_("Error: fork: %s"),strerror(errno));
217 	  user_error(c);
218 	  g_free(c);
219 	  close(cmd_in[0]);
220 	  close(cmd_in[1]);
221 	  if (open_out) {
222 	       close(cmd_out[0]);
223 	       close(cmd_out[1]);
224 	  }
225 	  close(cmd_err[0]);
226 	  close(cmd_err[1]);
227 	  return NULL;
228      }
229 
230      if (p == 0) {
231 	  /* Child process */
232 	  /* Redirect streams */
233 	  /* puts("Child: redirecting streams"); */
234 	  /* i = dup(1); */
235 	  dup2(cmd_in[0],0);
236 	  if (open_out)
237 	       dup2(cmd_out[1],1);
238 	  dup2(cmd_err[1],2);
239 	  /* Close the streams we don't use */
240 	  /* write(i,"Child: closing streams\n",23); */
241 	  close(cmd_in[0]);
242 	  close(cmd_in[1]);
243 	  if (open_out) {
244 	       close(cmd_out[0]);
245 	       close(cmd_out[1]);
246 	  }
247 	  close(cmd_err[0]);
248 	  close(cmd_err[1]);
249 	  /* Synchronize */
250 	  if (open_out) {
251 	       i = read(0,&syncbyte,1);
252 	       g_assert(i == 1 && syncbyte == 0xAB);
253 	       syncbyte = 0xCD;
254 	       i = write(1,&syncbyte,1);
255 	       g_assert(i == 1);
256 	  }
257 	  /* Execute subprocess */
258 	  argv[0] = "sh";
259 	  argv[1] = "-c";
260 	  argv[2] = command;
261 	  argv[3] = NULL;
262 	  /* write(i,"Child: execve!\n",15); */
263 	  fflush(stdout);
264 	  close_all_files();
265 	  if (execve("/bin/sh",argv,environ) == -1) {
266 	       console_perror("/bin/sh");
267 	  } else
268 	       console_message(_("Should not reach this point!"));
269 	  _exit(1);
270      }
271 
272      /* Parent process */
273      /* Close the streams we don't use. */
274      close(cmd_in[0]);
275      if (open_out)
276 	  close(cmd_out[1]);
277      close(cmd_err[1]);
278 
279      /* synchronize to child */
280      if (open_out) {
281 	  syncbyte = 0xAB;
282 	  i = write(cmd_in[1],&syncbyte,1);
283 	  g_assert(i == 1);
284 	  i = read(cmd_out[0],&syncbyte,1);
285 	  g_assert(i == 1 && syncbyte == 0xCD);
286      }
287 
288      pd = g_malloc(sizeof(*pd));
289      pd->fds[0] = fds[0] = cmd_in[1];
290      pd->fds[1] = fds[1] = cmd_out[0];
291      pd->fds[2] = fds[2] = cmd_err[0];
292      pd->error_log = NULL;
293      pd->command = g_strdup(command);
294      return (gpointer)pd;
295 }
296 
pipe_dialog_close(gpointer handle)297 void pipe_dialog_close(gpointer handle)
298 {
299      struct pipe_data *pd = (struct pipe_data *)handle;
300      if (pd->fds[0] != 0) close(pd->fds[0]);
301      if (pd->fds[1] != 0) close(pd->fds[1]);
302      close(pd->fds[2]);
303      g_free(pd->command);
304      g_free(pd);
305      wait(NULL);
306 }
307 
pipe_dialog_error_check(gpointer handle)308 gboolean pipe_dialog_error_check(gpointer handle)
309 {
310      struct pipe_data *pd = (struct pipe_data *)handle;
311      fd_set set;
312      int i;
313      struct timeval tv = {};
314      gchar c[512];
315      FD_ZERO(&set);
316      FD_SET(pd->fds[2],&set);
317      i = select(pd->fds[2]+1,&set,NULL,NULL,&tv);
318      if (i < 1) return FALSE;
319      i = read(pd->fds[2],c,sizeof(c)-1);
320      if (i == 0) return FALSE;
321      c[i] = 0;
322      if (!pd->error_log) pd->error_log = create_error_window(pd->command);
323      add_error_text(pd->error_log,c);
324      return TRUE;
325 }
326 
pipe_dialog_close_input(gpointer handle)327 void pipe_dialog_close_input(gpointer handle)
328 {
329      struct pipe_data *pd = (struct pipe_data *)handle;
330      close(pd->fds[0]);
331      pd->fds[0] = 0;
332 }
333 
334 #define BZ 65536
335 
pipe_dialog_pipe_chunk_main(Chunk * chunk,gchar * command,gboolean sendwav,gboolean do_read,StatusBar * bar,gboolean * sent_all,int dither_mode,off_t * clipcount)336 static Chunk *pipe_dialog_pipe_chunk_main(Chunk *chunk, gchar *command,
337 					  gboolean sendwav, gboolean do_read,
338 					  StatusBar *bar, gboolean *sent_all,
339 					  int dither_mode, off_t *clipcount)
340 {
341      int fds[3],bs,bp,i;
342      gboolean writing=TRUE;
343      gboolean send_header = sendwav;
344      off_t ui,read_bytes=0;
345      gchar *inbuf=NULL,*outbuf,*c;
346      ChunkHandle *ch=NULL;
347      TempFile ct=0;
348      fd_set rset,wset;
349      gpointer pipehandle;
350 
351      /*
352      puts("---");
353      puts(command);
354      puts("///");
355      */
356 
357      if (sent_all) *sent_all = FALSE;
358 
359      pipehandle = pipe_dialog_open_pipe(command, fds, do_read);
360      if (pipehandle == NULL) return NULL;
361 
362      /* Open chunk and create buffer */
363      ch = chunk_open(chunk);
364      if (!ch) {
365 	  pipe_dialog_close(pipehandle);
366 	  return NULL;
367      }
368      if (do_read) {
369 	  ct = tempfile_init(&(chunk->format),FALSE);
370 	  if (!ct) {
371 	       chunk_close(ch);
372 	       pipe_dialog_close(pipehandle);
373 	       return NULL;
374 	  }
375      }
376      status_bar_begin_progress(bar,ch->size,NULL);
377      if (do_read) inbuf = g_malloc(BZ);
378      outbuf = g_malloc(BZ);
379      ui = 0;
380      bs = bp = 0;
381      /* We want to handle broken pipes ourselves. */
382      signal(SIGPIPE,SIG_IGN);
383      /* Read/write data... */
384      /* If do_read is TRUE, this loop will keep running until the
385       * child process closes it's standard output (=our data
386       * input). Otherwise, the loop will keep running until all data has
387       * been sent to the program */
388      while (do_read || writing) {
389 	  /* Wait for IO */
390 	  FD_ZERO(&rset);
391 	  if (do_read) FD_SET(fds[1],&rset);
392 	  FD_SET(fds[2],&rset);
393 	  FD_ZERO(&wset);
394 	  if (writing) FD_SET(fds[0],&wset);
395 	  /* printf("Waiting (writing=%d)\n",writing); */
396 	  i = select(FD_SETSIZE,&rset,&wset,NULL,NULL);
397 	  /* printf("Wait finished.\n"); */
398 	  if (i == -1) {
399 	       if (errno == EINTR) continue;
400 	       goto errno_error_exit;
401 	  }
402 
403 	  /* Read data from standard output */
404 	  if (FD_ISSET(fds[1],&rset)) {
405 	       /* printf("Reading from stdin..\n"); */
406 	       i = read(fds[1],inbuf,BZ);
407 	       /* printf("Read finished\n"); */
408 	       if (i == -1) { if (errno != EINTR) goto errno_error_exit; }
409 	       else if (i == 0) { break; }
410 	       else { if (tempfile_write(ct,inbuf,i)) goto error_exit; }
411 	       read_bytes += i;
412 	       continue;
413 	  }
414 
415 	  /* Read data from standard error... */
416 	  if (FD_ISSET(fds[2],&rset))
417 	       pipe_dialog_error_check(pipehandle);
418 
419 
420 	  /* Send data to process's stdin */
421 	  if (writing && FD_ISSET(fds[0],&wset)) {
422 	       /* Make sure the buffer contains data */
423 	       if (bs == bp) {
424 		    if (send_header) {
425 			 /* FIXME: Broken for float on bigendian machines */
426 			 bs = get_wav_header(&(chunk->format),chunk->size,
427 					     outbuf);
428 			 bp = 0;
429 			 send_header = FALSE;
430 		    } else {
431 			 if (ui == chunk->length) {
432 			      writing = FALSE;
433 			      pipe_dialog_close_input(pipehandle);
434 			      continue;
435 			 }
436 			 i = chunk_read_array(ch,ui,BZ,outbuf, dither_mode,
437 					      clipcount);
438 			 if (!i) goto error_exit;
439 			 bs = i;
440 			 bp = 0;
441 			 /* if (IS_BIGENDIAN && sendwav)
442 			    byteswap(c,chunk->format.samplesize,bs); */
443 			 ui += i / chunk->format.samplebytes;
444 		    }
445 	       }
446 	       /* printf("Writing data...\n"); */
447 	       /* Write data */
448 #ifdef PIPE_BUF
449 	       i = write(fds[0],outbuf+bp,MIN(bs-bp,PIPE_BUF));
450 #else
451 	       i = write(fds[0],outbuf+bp,MIN(bs-bp,fpathconf(fds[0], _PC_PIPE_BUF)));
452 #endif
453 	       /* i = write(fds[0],outbuf+bp,1); */
454 	       /* printf("Finished writing.\n"); */
455 	       if (i == -1) {
456 		    /* Broken pipe - stop writing */
457 		    if (errno == EPIPE) {
458 			 /* printf("Writing: Broken pipe.\n"); */
459 			 writing = FALSE;
460 			 continue;
461 		    }
462 		    if (errno == EINTR) continue;
463 		    goto errno_error_exit;
464 	       }
465 	       bp += i;
466 	       if (status_bar_progress(bar, i))
467 		    goto error_exit;
468 	  }
469      }
470 
471      /* Check for any remaining data sent to standard error */
472      while (pipe_dialog_error_check(pipehandle)) { }
473 
474      /* Finish */
475      g_free(inbuf);
476      g_free(outbuf);
477      chunk_close(ch);
478      pipe_dialog_close(pipehandle);
479      status_bar_end_progress(bar);
480 
481      if (do_read && !read_bytes) {
482 	  user_error(_("Command failed without returning any data"));
483 	  tempfile_abort(ct);
484 	  return NULL;
485      }
486 
487      if (sent_all) *sent_all = !writing;
488 
489      return do_read ? tempfile_finished(ct) : NULL;
490 
491 errno_error_exit:
492      c = g_strdup_printf(_("Error: %s"),strerror(errno));
493      user_error(c);
494      g_free(c);
495 error_exit:
496      g_free(inbuf);
497      g_free(outbuf);
498      chunk_close(ch);
499      if (do_read) tempfile_abort(ct);
500      pipe_dialog_close(pipehandle);
501      status_bar_end_progress(bar);
502      return NULL;
503 }
504 
pipe_dialog_pipe_chunk(Chunk * chunk,gchar * command,gboolean sendwav,int dither_mode,StatusBar * bar,off_t * clipcount)505 Chunk *pipe_dialog_pipe_chunk(Chunk *chunk, gchar *command, gboolean sendwav,
506 			      int dither_mode, StatusBar *bar,
507 			      off_t *clipcount)
508 {
509      return pipe_dialog_pipe_chunk_main(chunk,command,sendwav,TRUE,
510 					bar,NULL,dither_mode,clipcount);
511 }
512 
pipe_dialog_send_chunk(Chunk * chunk,gchar * command,gboolean sendwav,int dither_mode,StatusBar * bar,off_t * clipcount)513 gboolean pipe_dialog_send_chunk(Chunk *chunk, gchar *command, gboolean sendwav,
514 				int dither_mode, StatusBar *bar,
515 				off_t *clipcount)
516 {
517      gboolean sent_all;
518      pipe_dialog_pipe_chunk_main(chunk,command,sendwav,FALSE,
519 				 bar,&sent_all,dither_mode,clipcount);
520      return !sent_all;
521 }
522