3 * $Id: follow_dlg.c,v 1.28 2002/09/09 20:38:58 guy Exp $
5 * Ethereal - Network traffic analyzer
6 * By Gerald Combs <gerald@ethereal.com>
7 * Copyright 2000 Gerald Combs
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
34 #include <io.h> /* open/close on win32 */
41 #ifdef NEED_SNPRINTF_H
42 # include "snprintf.h"
48 #include "color_utils.h"
50 #include "follow_dlg.h"
52 #include "dlg_utils.h"
55 #include "gtkglobals.h"
57 #include "simple_dialog.h"
58 #include "packet-ipv6.h"
60 #include <epan/resolv.h>
63 #include <epan/epan_dissect.h>
64 #include <epan/filesystem.h>
81 show_stream_t show_stream;
82 show_type_t show_type;
83 char data_out_filename[128 + 1];
87 GtkWidget *hexdump_bt;
88 GtkWidget *follow_save_as_w;
92 static void follow_destroy_cb(GtkWidget * win, gpointer data);
93 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
94 static void follow_load_text(follow_info_t *follow_info);
95 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
96 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
97 static void follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs);
98 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
99 static void follow_stream_om_both(GtkWidget * w, gpointer data);
100 static void follow_stream_om_client(GtkWidget * w, gpointer data);
101 static void follow_stream_om_server(GtkWidget * w, gpointer data);
104 extern FILE *data_out_file;
107 #define E_FOLLOW_INFO_KEY "follow_info_key"
109 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
110 so we can redraw them all if the colors or font changes. */
111 static GList *follow_infos;
113 /* Add a "follow_info_t" structure to the list. */
115 remember_follow_info(follow_info_t *follow_info)
117 follow_infos = g_list_append(follow_infos, follow_info);
120 /* Remove a "follow_info_t" structure from the list. */
122 forget_follow_info(follow_info_t *follow_info)
124 follow_infos = g_list_remove(follow_infos, follow_info);
128 follow_redraw(gpointer data, gpointer user_data _U_)
130 follow_load_text((follow_info_t *)data);
133 /* Redraw the text in all "Follow TCP Stream" windows. */
135 follow_redraw_all(void)
137 g_list_foreach(follow_infos, follow_redraw, NULL);
140 /* Follow the TCP stream, if any, to which the last packet that we called
141 a dissection routine on belongs (this might be the most recently
142 selected packet, or it might be the last packet in the file). */
144 follow_stream_cb(GtkWidget * w, gpointer data _U_)
146 GtkWidget *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
147 GtkWidget *hbox, *button, *radio_bt;
148 GtkWidget *stream_om, *stream_menu, *stream_mi;
150 gchar *follow_filter;
151 const char *hostname0, *hostname1;
154 follow_tcp_stats_t stats;
155 follow_info_t *follow_info;
157 /* we got tcp so we can follow */
158 if (cfile.edt->pi.ipproto != 6) {
159 simple_dialog(ESD_TYPE_CRIT, NULL,
160 "Error following stream. Please make\n"
161 "sure you have a TCP packet selected.");
165 follow_info = g_new0(follow_info_t, 1);
167 /* Create a temporary file into which to dump the reassembled data
168 from the TCP stream, and set "data_out_file" to refer to it, so
169 that the TCP code will write to it.
171 XXX - it might be nicer to just have the TCP code directly
172 append stuff to the text widget for the TCP stream window,
173 if we can arrange that said window not pop up until we're
175 tmp_fd = create_tempfile(follow_info->data_out_filename,
176 sizeof follow_info->data_out_filename, "follow");
179 simple_dialog(ESD_TYPE_WARN, NULL,
180 "Could not create temporary file %s: %s",
181 follow_info->data_out_filename, strerror(errno));
186 data_out_file = fdopen(tmp_fd, "wb");
187 if (data_out_file == NULL) {
188 simple_dialog(ESD_TYPE_WARN, NULL,
189 "Could not create temporary file %s: %s",
190 follow_info->data_out_filename, strerror(errno));
192 unlink(follow_info->data_out_filename);
197 /* Create a new filter that matches all packets in the TCP stream,
198 and set the display filter entry accordingly */
199 reset_tcp_reassembly();
200 follow_filter = build_follow_filter(&cfile.edt->pi);
202 /* Set the display filter entry accordingly */
203 filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
204 gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
206 /* Run the display filter so it goes in effect. */
207 filter_packets(&cfile, follow_filter);
209 /* Free the filter string, as we're done with it. */
210 g_free(follow_filter);
212 /* The data_out_file should now be full of the streams information */
213 fclose(data_out_file);
215 /* The data_out_filename file now has all the text that was in the session */
216 streamwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
217 gtk_widget_set_name(streamwindow, "TCP stream window");
219 gtk_signal_connect(GTK_OBJECT(streamwindow), "destroy",
220 GTK_SIGNAL_FUNC(follow_destroy_cb), NULL);
222 gtk_signal_connect (GTK_OBJECT (streamwindow), "realize",
223 GTK_SIGNAL_FUNC (window_icon_realize_cb), NULL);
224 if (incomplete_tcp_stream) {
225 gtk_window_set_title(GTK_WINDOW(streamwindow),
226 "Contents of TCP stream (incomplete)");
228 gtk_window_set_title(GTK_WINDOW(streamwindow),
229 "Contents of TCP stream");
231 gtk_widget_set_usize(GTK_WIDGET(streamwindow), DEF_WIDTH,
233 gtk_container_border_width(GTK_CONTAINER(streamwindow), 2);
235 /* setup the container */
236 vbox = gtk_vbox_new(FALSE, 0);
237 gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
239 /* create a scrolled window for the text */
240 txt_scrollw = scrolled_window_new(NULL, NULL);
241 gtk_box_pack_start(GTK_BOX(vbox), txt_scrollw, TRUE, TRUE, 0);
242 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scrollw),
246 /* create a text box */
247 text = gtk_text_new(NULL, NULL);
248 gtk_text_set_editable(GTK_TEXT(text), FALSE);
249 gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
250 follow_info->text = text;
253 hbox = gtk_hbox_new(FALSE, 1);
254 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
258 follow_tcp_stats(&stats);
261 struct e_in6_addr ipaddr;
262 memcpy(&ipaddr, stats.ip_address[0], 16);
263 hostname0 = get_hostname6(&ipaddr);
264 memcpy(&ipaddr, stats.ip_address[0], 16);
265 hostname1 = get_hostname6(&ipaddr);
268 memcpy(&ipaddr, stats.ip_address[0], 4);
269 hostname0 = get_hostname(ipaddr);
270 memcpy(&ipaddr, stats.ip_address[1], 4);
271 hostname1 = get_hostname(ipaddr);
274 port0 = get_tcp_port(stats.tcp_port[0]);
275 port1 = get_tcp_port(stats.tcp_port[1]);
277 follow_info->is_ipv6 = stats.is_ipv6;
279 stream_om = gtk_option_menu_new();
280 stream_menu = gtk_menu_new();
283 snprintf(string, sizeof(string),
284 "Entire conversation (%u bytes)",
285 stats.bytes_written[0] + stats.bytes_written[1]);
286 stream_mi = gtk_menu_item_new_with_label(string);
287 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
288 GTK_SIGNAL_FUNC(follow_stream_om_both), follow_info);
289 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
290 gtk_widget_show(stream_mi);
291 follow_info->show_stream = BOTH_HOSTS;
293 /* Host 0 --> Host 1 */
294 snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
295 hostname0, port0, hostname1, port1,
296 stats.bytes_written[0]);
297 stream_mi = gtk_menu_item_new_with_label(string);
298 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
299 GTK_SIGNAL_FUNC(follow_stream_om_client), follow_info);
300 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
301 gtk_widget_show(stream_mi);
303 /* Host 1 --> Host 0 */
304 snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
305 hostname1, port1, hostname0, port0,
306 stats.bytes_written[1]);
307 stream_mi = gtk_menu_item_new_with_label(string);
308 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
309 GTK_SIGNAL_FUNC(follow_stream_om_server), follow_info);
310 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
311 gtk_widget_show(stream_mi);
313 gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
314 /* Set history to 0th item, i.e., the first item. */
315 gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
316 gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
318 /* ASCII radio button */
319 radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
320 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
321 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
322 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
323 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
325 follow_info->ascii_bt = radio_bt;
326 follow_info->show_type = SHOW_ASCII;
328 /* EBCDIC radio button */
329 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
330 (GTK_RADIO_BUTTON(radio_bt)),
332 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
333 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
334 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
335 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
337 follow_info->ebcdic_bt = radio_bt;
339 /* HEX DUMP radio button */
340 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
341 (GTK_RADIO_BUTTON(radio_bt)),
343 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
344 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
345 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
346 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
348 follow_info->hexdump_bt = radio_bt;
350 /* Create Close Button */
351 button = gtk_button_new_with_label("Close");
352 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
353 GTK_SIGNAL_FUNC(gtk_widget_destroy),
354 GTK_OBJECT(streamwindow));
355 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
357 /* Catch the "key_press_event" signal in the window, so that we can catch
358 the ESC key being pressed and act as if the "Cancel" button had
360 dlg_set_cancel(streamwindow, button);
362 /* Create Save As Button */
363 button = gtk_button_new_with_label("Save As");
364 gtk_signal_connect(GTK_OBJECT(button), "clicked",
365 GTK_SIGNAL_FUNC(follow_save_as_cmd_cb),
367 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
369 /* Create Print Button */
370 button = gtk_button_new_with_label("Print");
371 gtk_signal_connect(GTK_OBJECT(button), "clicked",
372 GTK_SIGNAL_FUNC(follow_print_stream), follow_info);
373 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
376 /* Tuck away the follow_info object into the window */
377 gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_INFO_KEY,
380 follow_load_text(follow_info);
381 remember_follow_info(follow_info);
383 data_out_file = NULL;
385 /* Make sure this widget gets destroyed if we quit the main loop,
386 so that if we exit, we clean up any temporary files we have
387 for "Follow TCP Stream" windows. */
388 gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
389 gtk_widget_show_all(streamwindow);
392 /* The destroy call back has the responsibility of
393 * unlinking the temporary file */
395 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
397 follow_info_t *follow_info;
399 follow_info = gtk_object_get_data(GTK_OBJECT(w), E_FOLLOW_INFO_KEY);
400 unlink(follow_info->data_out_filename);
401 gtk_widget_destroy(w);
402 forget_follow_info(follow_info);
406 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
407 * 3 different functions here? */
409 follow_stream_om_both(GtkWidget *w _U_, gpointer data)
411 follow_info_t *follow_info = data;
412 follow_info->show_stream = BOTH_HOSTS;
413 follow_load_text(follow_info);
417 follow_stream_om_client(GtkWidget *w _U_, gpointer data)
419 follow_info_t *follow_info = data;
420 follow_info->show_stream = FROM_CLIENT;
421 follow_load_text(follow_info);
425 follow_stream_om_server(GtkWidget *w _U_, gpointer data)
427 follow_info_t *follow_info = data;
428 follow_info->show_stream = FROM_SERVER;
429 follow_load_text(follow_info);
433 /* Handles the ASCII/EBCDIC toggling */
435 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
437 follow_info_t *follow_info = data;
439 if (GTK_TOGGLE_BUTTON(follow_info->ebcdic_bt)->active)
440 follow_info->show_type = SHOW_EBCDIC;
441 else if (GTK_TOGGLE_BUTTON(follow_info->hexdump_bt)->active)
442 follow_info->show_type = SHOW_HEXDUMP;
443 else if (GTK_TOGGLE_BUTTON(follow_info->ascii_bt)->active)
444 follow_info->show_type = SHOW_ASCII;
446 g_assert_not_reached();
448 follow_load_text(follow_info);
451 #define FLT_BUF_SIZE 1024
453 follow_read_stream(follow_info_t *follow_info,
454 void (*print_line) (char *, int, gboolean, void *),
459 guint8 client_addr[MAX_IPADDR_LEN];
460 guint16 client_port = 0;
462 guint16 current_pos, global_client_pos = 0, global_server_pos = 0;
466 iplen = (follow_info->is_ipv6) ? 16 : 4;
468 data_out_file = fopen(follow_info->data_out_filename, "rb");
470 char buffer[FLT_BUF_SIZE];
472 while (fread(&sc, 1, sizeof(sc), data_out_file)) {
473 if (client_port == 0) {
474 memcpy(client_addr, sc.src_addr, iplen);
475 client_port = sc.src_port;
478 if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
479 client_port == sc.src_port) {
481 global_pos = &global_client_pos;
482 if (follow_info->show_stream == FROM_SERVER) {
488 global_pos = &global_server_pos;
489 if (follow_info->show_stream == FROM_CLIENT) {
494 while (sc.dlen > 0) {
495 bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
496 nchars = fread(buffer, 1, bcount, data_out_file);
501 switch (follow_info->show_type) {
503 /* If our native arch is ASCII, call: */
504 EBCDIC_to_ASCII(buffer, nchars);
505 (*print_line) (buffer, nchars, is_server, arg);
508 /* If our native arch is EBCDIC, call:
509 * ASCII_TO_EBCDIC(buffer, nchars);
511 (*print_line) (buffer, nchars, is_server, arg);
515 while (current_pos < nchars) {
517 gchar hexchars[] = "0123456789abcdef";
519 /* is_server indentation : put 63 spaces at the begenning
521 sprintf(hexbuf, (is_server &&
522 follow_info->show_stream == BOTH_HOSTS) ?
525 "%08X ", *global_pos);
526 cur = strlen(hexbuf);
527 for (i = 0; i < 16 && current_pos + i < nchars;
530 hexchars[(buffer[current_pos + i] & 0xf0)
533 hexchars[buffer[current_pos + i] & 0x0f];
540 /* Fill it up if column isn't complete */
544 for (j = i; j < 16; j++) {
554 /* Now dump bytes as text */
555 for (i = 0; i < 16 && current_pos + i < nchars;
558 (isprint((guchar)buffer[current_pos + i]) ?
559 buffer[current_pos + i] : '.' );
566 hexbuf[cur++] = '\n';
568 (*print_line) (hexbuf, strlen(hexbuf), is_server, arg);
575 if (ferror(data_out_file)) {
576 simple_dialog(ESD_TYPE_WARN, NULL,
577 "Error reading temporary file %s: %s", follow_info->data_out_filename,
580 fclose(data_out_file);
581 data_out_file = NULL;
583 simple_dialog(ESD_TYPE_WARN, NULL,
584 "Could not open temporary file %s: %s", follow_info->data_out_filename,
590 * XXX - for text printing, we probably want to wrap lines at 80 characters;
591 * for PostScript printing, we probably want to wrap them at the appropriate
592 * width, and perhaps put some kind of dingbat (to use the technical term)
593 * to indicate a wrapped line, along the lines of what's done when displaying
594 * this in a window, as per Warren Young's suggestion.
596 * For now, we support only text printing.
599 follow_print_text(char *buffer, int nchars, gboolean is_server _U_, void *arg)
603 fwrite(buffer, nchars, 1, fh);
607 follow_print_stream(GtkWidget * w _U_, gpointer data)
612 follow_info_t *follow_info = data;
614 switch (prefs.pr_dest) {
616 print_dest = prefs.pr_cmd;
621 print_dest = prefs.pr_file;
624 default: /* "Can't happen" */
625 simple_dialog(ESD_TYPE_CRIT, NULL,
626 "Couldn't figure out where to send the print "
627 "job. Check your preferences.");
631 fh = open_print_dest(to_file, print_dest);
635 simple_dialog(ESD_TYPE_WARN, NULL,
636 "Couldn't run print command %s.", prefs.pr_cmd);
640 simple_dialog(ESD_TYPE_WARN, NULL,
641 file_write_error_message(errno), prefs.pr_file);
647 print_preamble(fh, PR_FMT_TEXT);
648 follow_read_stream(follow_info, follow_print_text, fh);
649 print_finale(fh, PR_FMT_TEXT);
650 close_print_dest(to_file, fh);
654 follow_add_to_gtk_text(char *buffer, int nchars, gboolean is_server,
657 GtkWidget *text = arg;
661 /* While our isprint() hack is in place, we
662 * have to use convert some chars to '.' in order
663 * to be able to see the data we *should* see
664 * in the GtkText widget.
668 for (i = 0; i < nchars; i++) {
669 if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
672 else if (! isprint(buffer[i])) {
679 color_t_to_gdkcolor(&fg, &prefs.st_server_fg);
680 color_t_to_gdkcolor(&bg, &prefs.st_server_bg);
682 color_t_to_gdkcolor(&fg, &prefs.st_client_fg);
683 color_t_to_gdkcolor(&bg, &prefs.st_client_bg);
685 gtk_text_insert(GTK_TEXT(text), m_r_font, &fg, &bg, buffer, nchars);
689 follow_load_text(follow_info_t *follow_info)
693 /* Delete any info already in text box */
694 bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
695 if (bytes_already > 0) {
696 gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
697 gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
700 /* stop the updates while we fill the text box */
701 gtk_text_freeze(GTK_TEXT(follow_info->text));
702 follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
703 gtk_text_thaw(GTK_TEXT(follow_info->text));
708 * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
709 * any, so that if somebody tries to do "Save"
710 * while there's already a "Save TCP Follow Stream" window up, we just pop
711 * up the existing one, rather than creating a new one.
714 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
716 GtkWidget *ok_bt, *new_win;
717 follow_info_t *follow_info = data;
719 if (follow_info->follow_save_as_w != NULL) {
720 /* There's already a dialog box; reactivate it. */
721 reactivate_window(follow_info->follow_save_as_w);
725 new_win = gtk_file_selection_new("Ethereal: Save TCP Follow Stream As");
726 follow_info->follow_save_as_w = new_win;
727 gtk_signal_connect(GTK_OBJECT(new_win), "destroy",
728 GTK_SIGNAL_FUNC(follow_save_as_destroy_cb), follow_info);
730 /* Tuck away the follow_info object into the window */
731 gtk_object_set_data(GTK_OBJECT(new_win), E_FOLLOW_INFO_KEY,
734 /* If we've opened a file, start out by showing the files in the directory
735 in which that file resided. */
737 gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win),
740 /* Connect the ok_button to file_save_as_ok_cb function and pass along a
741 pointer to the file selection box widget */
742 ok_bt = GTK_FILE_SELECTION(new_win)->ok_button;
743 gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
744 (GtkSignalFunc) follow_save_as_ok_cb,
747 /* Connect the cancel_button to destroy the widget */
748 gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION
749 (new_win)->cancel_button),
751 (GtkSignalFunc) gtk_widget_destroy,
752 GTK_OBJECT(new_win));
754 /* Catch the "key_press_event" signal in the window, so that we can catch
755 the ESC key being pressed and act as if the "Cancel" button had
757 dlg_set_cancel(new_win,
758 GTK_FILE_SELECTION(new_win)->cancel_button);
760 gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
761 gtk_widget_show_all(new_win);
766 follow_save_as_ok_cb(GtkWidget * w _U_, GtkFileSelection * fs)
769 follow_info_t *follow_info;
773 to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
775 /* Perhaps the user specified a directory instead of a file.
776 Check whether they did. */
777 if (test_for_directory(to_name) == EISDIR) {
778 /* It's a directory - set the file selection box to display that
779 directory, and leave the selection box displayed. */
780 set_last_open_dir(to_name);
782 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs),
787 fh = fopen(to_name, "wb");
789 simple_dialog(ESD_TYPE_WARN, NULL,
790 file_write_error_message(errno), to_name);
795 gtk_widget_hide(GTK_WIDGET(fs));
796 follow_info = gtk_object_get_data(GTK_OBJECT(fs), E_FOLLOW_INFO_KEY);
797 gtk_widget_destroy(GTK_WIDGET(fs));
799 follow_read_stream(follow_info, follow_print_text, fh);
802 /* Save the directory name for future file dialogs. */
803 dirname = get_dirname(to_name); /* Overwrites to_name */
804 set_last_open_dir(dirname);
809 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
811 follow_info_t *follow_info = data;
813 /* Note that we no longer have a dialog box. */
814 follow_info->follow_save_as_w = NULL;