/* $Id: download.c,v 1.53 2012/06/02 11:18:32 khorben Exp $ */ /* Copyright (c) 2009-2012 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Surfer */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* TODO: * - let the checkbox to close window be a global option * - also use the proxy settings * - use the "Last modified" header (if available?) to futimes() the file */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_WEBKIT # include #else # define GNET_EXPERIMENTAL # include #endif #include "download.h" #include "../config.h" #include "common/url.c" #define _(string) gettext(string) #define N_(string) (string) /* constants */ #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef DATADIR # define DATADIR PREFIX "/share" #endif #ifndef LOCALEDIR # define LOCALEDIR DATADIR "/locale" #endif /* Download */ /* private */ /* types */ struct _Download { DownloadPrefs prefs; char * url; struct timeval tv; #ifdef WITH_WEBKIT WebKitDownload * conn; #else FILE * fp; GConnHttp * conn; #endif guint64 content_length; guint64 data_received; /* widgets */ GtkWidget * window; GtkWidget * address; GtkWidget * filename; GtkWidget * status; GtkWidget * received; GtkWidget * remaining; GtkWidget * progress; GtkWidget * check; GtkWidget * browse; GtkWidget * cancel; guint timeout; int pulse; }; /* constants */ #ifdef WITH_MAIN # define PROGNAME "download" #endif /* variables */ #ifdef WITH_MAIN static unsigned int _download_cnt = 0; #endif /* prototypes */ static int _download_error(Download * download, char const * message, int ret); static int _download_set_proxy(Download * download, char const * http, unsigned int http_port); static void _download_refresh(Download * download); #ifndef WITH_WEBKIT static int _download_write(Download * download); #endif /* callbacks */ static void _download_on_browse(gpointer data); static void _download_on_cancel(gpointer data); static gboolean _download_on_closex(gpointer data); #ifndef WITH_WEBKIT static void _download_on_http(GConnHttp * conn, GConnHttpEvent * event, gpointer data); #endif static gboolean _download_on_idle(gpointer data); static gboolean _download_on_timeout(gpointer data); /* public */ /* functions */ /* download_new */ static void _download_label(GtkWidget * vbox, PangoFontDescription * bold, GtkSizeGroup * left, char const * label, GtkWidget ** widget, char const * text); Download * download_new(DownloadPrefs * prefs, char const * url) { Download * download; char * p; char buf[256]; GtkWidget * vbox; GtkWidget * hbox; GtkSizeGroup * left; GtkWidget * widget; PangoFontDescription * bold; /* verify arguments */ if(prefs == NULL || url == NULL) { errno = EINVAL; _download_error(NULL, NULL, 1); return NULL; } if((download = malloc(sizeof(*download))) == NULL) { _download_error(NULL, "malloc", 1); return NULL; } /* initialize structure */ download->prefs.output = (prefs->output != NULL) ? strdup(prefs->output) : NULL; download->prefs.user_agent = (prefs->user_agent != NULL) ? strdup(prefs->user_agent) : NULL; if((p = _ghtml_make_url(NULL, url)) != NULL) url = p; download->url = strdup(url); free(p); if(download->url != NULL && prefs->output == NULL) download->prefs.output = strdup(basename(download->url)); download->conn = NULL; download->data_received = 0; download->content_length = 0; download->timeout = 0; download->pulse = 0; /* verify initialization */ if((prefs->output != NULL && download->prefs.output == NULL) || (prefs->user_agent != NULL && download->prefs.user_agent == NULL) || download->url == NULL || gettimeofday(&download->tv, NULL) != 0) { _download_error(NULL, "gettimeofday", 1); download_delete(download); return NULL; } /* window */ download->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); snprintf(buf, sizeof(buf), "%s %s", _("Download"), download->url); #if GTK_CHECK_VERSION(2, 6, 0) gtk_window_set_icon_name(GTK_WINDOW(download->window), "stock_download"); #endif gtk_window_set_resizable(GTK_WINDOW(download->window), FALSE); gtk_window_set_title(GTK_WINDOW(download->window), buf); g_signal_connect_swapped(G_OBJECT(download->window), "delete-event", G_CALLBACK(_download_on_closex), download); vbox = gtk_vbox_new(FALSE, 2); bold = pango_font_description_new(); pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD); left = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); /* address */ hbox = gtk_hbox_new(FALSE, 4); widget = gtk_label_new(_("Address: ")); gtk_widget_modify_font(widget, bold); gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5); gtk_size_group_add_widget(left, widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); download->address = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(download->address), download->url); gtk_editable_set_editable(GTK_EDITABLE(download->address), FALSE); gtk_box_pack_start(GTK_BOX(hbox), download->address, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); /* labels */ _download_label(vbox, bold, left, _("File: "), &download->filename, download->prefs.output); _download_label(vbox, bold, left, _("Status: "), &download->status, _("Resolving...")); _download_label(vbox, bold, left, _("Received: "), &download->received, _("0.0 kB")); _download_label(vbox, bold, left, _("Remaining: "), &download->remaining, _("Unknown")); /* progress bar */ download->progress = gtk_progress_bar_new(); gtk_box_pack_start(GTK_BOX(vbox), download->progress, FALSE, FALSE, 0); /* checkbox */ download->check = gtk_check_button_new_with_label( _("Close window when the download is complete")); gtk_box_pack_start(GTK_BOX(vbox), download->check, FALSE, FALSE, 0); /* button */ hbox = gtk_hbox_new(FALSE, 4); download->cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(G_OBJECT(download->cancel), "clicked", G_CALLBACK(_download_on_cancel), download); gtk_box_pack_end(GTK_BOX(hbox), download->cancel, FALSE, TRUE, 0); download->browse = gtk_button_new_with_mnemonic("_Open folder"); gtk_widget_set_no_show_all(download->browse, TRUE); widget = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(download->browse), widget); g_signal_connect_swapped(G_OBJECT(download->browse), "clicked", G_CALLBACK(_download_on_browse), download); gtk_box_pack_end(GTK_BOX(hbox), download->browse, FALSE, TRUE, 0); gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(download->window), 4); gtk_container_add(GTK_CONTAINER(download->window), vbox); download->timeout = g_idle_add(_download_on_idle, download); _download_refresh(download); gtk_widget_show_all(download->window); _download_cnt++; return download; } static void _download_label(GtkWidget * vbox, PangoFontDescription * bold, GtkSizeGroup * left, char const * label, GtkWidget ** widget, char const * text) { GtkWidget * hbox; hbox = gtk_hbox_new(FALSE, 4); *widget = gtk_label_new(label); gtk_widget_modify_font(*widget, bold); gtk_misc_set_alignment(GTK_MISC(*widget), 0.0, 0.5); gtk_size_group_add_widget(left, *widget); gtk_box_pack_start(GTK_BOX(hbox), *widget, FALSE, TRUE, 0); *widget = gtk_label_new(text); gtk_misc_set_alignment(GTK_MISC(*widget), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(hbox), *widget, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); } /* download_delete */ void download_delete(Download * download) { if(download->timeout != 0) g_source_remove(download->timeout); #ifdef WITH_WEBKIT if(download->conn != NULL) { webkit_download_cancel(download->conn); if(unlink(download->prefs.output) != 0) _download_error(download, download->prefs.output, 1); /* XXX should also unlink the (temporary) output file */ } #else if(download->conn != NULL) gnet_conn_http_delete(download->conn); if(download->fp != NULL) { if(fclose(download->fp) != 0) _download_error(download, download->prefs.output, 1); if(unlink(download->prefs.output) != 0) _download_error(download, download->prefs.output, 1); } #endif free(download->url); free(download->prefs.user_agent); free(download->prefs.output); gtk_widget_destroy(download->window); free(download); if(--_download_cnt == 0) gtk_main_quit(); } /* accessors */ void download_set_close(Download * download, gboolean close) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(download->check), close); } /* useful */ /* download_cancel */ int download_cancel(Download * download) { download_delete(download); return 0; } /* private */ /* functions */ /* download_error */ static int _download_error(Download * download, char const * message, int ret) { GtkWidget * dialog; if(ret < 0) { fputs(PROGNAME ": ", stderr); perror(message); return -ret; } dialog = gtk_message_dialog_new((download != NULL) ? GTK_WINDOW(download->window) : NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif "%s: %s", message, strerror(errno)); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } /* download_set_proxy */ static int _download_set_proxy(Download * download, char const * http, unsigned int http_port) { #ifdef WITH_WEBKIT # if WEBKIT_CHECK_VERSION(1, 1, 0) SoupSession * session; char buf[32]; struct hostent * he; struct in_addr addr; SoupURI * uri = NULL; session = webkit_get_default_session(); if(strlen(http) > 0) { if((he = gethostbyname(http)) == NULL) return -error_set_code(1, "%s: %s", http, hstrerror( h_errno)); memcpy(&addr.s_addr, he->h_addr, sizeof(addr.s_addr)); snprintf(buf, sizeof(buf), "http://%s:%u/", inet_ntoa(addr), http_port); uri = soup_uri_new(buf); } g_object_set(session, "proxy-uri", uri, NULL); return 0; # else /* FIXME really implement */ return -error_set_code(1, "%s", strerror(ENOSYS)); # endif #else /* FIXME really implement */ return -error_set_code(1, "%s", strerror(ENOSYS)); #endif } /* download_refresh */ static void _refresh_unit(guint64 total, double * fraction, char const ** unit, double * current); static void _refresh_remaining(Download * download, guint64 rate); static void _download_refresh(Download * download) { char buf[256]; struct timeval tv; double current_fraction; guint64 rate = 0; double rate_fraction = 0.0; char const * rate_unit = N_("kB"); double total_fraction; char const * total_unit = N_("kB"); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %lu/%lu\n", __func__, download->data_received, download->content_length); #endif /* XXX should check gettimeofday() return value explicitly */ if(download->data_received > 0 && gettimeofday(&tv, NULL) == 0) { if((tv.tv_sec = tv.tv_sec - download->tv.tv_sec) < 0) tv.tv_sec = 0; if((tv.tv_usec = tv.tv_usec - download->tv.tv_usec) < 0) { tv.tv_sec--; tv.tv_usec += 1000000; } rate = (download->data_received * 1024) / ((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); _refresh_unit(rate, &rate_fraction, &rate_unit, NULL); } if(download->content_length == 0) { /* the total size is not known */ _refresh_unit(download->data_received, &total_fraction, &total_unit, NULL); snprintf(buf, sizeof(buf), _("%.1f %s (%.1f %s/s)"), total_fraction, total_unit, rate_fraction, rate_unit); gtk_label_set_text(GTK_LABEL(download->received), buf); snprintf(buf, sizeof(buf), " "); /* pulse the progress bar if any data was received */ if(download->pulse != 0) { gtk_progress_bar_pulse(GTK_PROGRESS_BAR( download->progress)); download->pulse = 0; } } else { /* the total size is known */ current_fraction = download->data_received; _refresh_unit(download->content_length, &total_fraction, &total_unit, ¤t_fraction); snprintf(buf, sizeof(buf), _("%.1f of %.1f %s (%.1f %s/s)"), current_fraction, total_fraction, _(total_unit), rate_fraction, _(rate_unit)); gtk_label_set_text(GTK_LABEL(download->received), buf); total_fraction = download->data_received; total_fraction /= download->content_length; snprintf(buf, sizeof(buf), "%.1f%%", total_fraction * 100); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR( download->progress), total_fraction); _refresh_remaining(download, rate); } gtk_progress_bar_set_text(GTK_PROGRESS_BAR(download->progress), buf); } static void _refresh_remaining(Download * download, guint64 rate) { char buf[32]; guint64 remaining; struct tm tm; if(rate == 0) return; remaining = (download->content_length - download->data_received) / rate; memset(&tm, 0, sizeof(tm)); tm.tm_sec = remaining; /* minutes */ if(tm.tm_sec > 60) { tm.tm_min = tm.tm_sec / 60; tm.tm_sec = tm.tm_sec - (tm.tm_min * 60); } /* hours */ if(tm.tm_min > 60) { tm.tm_hour = tm.tm_min / 60; tm.tm_min = tm.tm_min - (tm.tm_hour * 60); } strftime(buf, sizeof(buf), "%H:%M:%S", &tm); gtk_label_set_text(GTK_LABEL(download->remaining), buf); } static void _refresh_unit(guint64 size, double * fraction, char const ** unit, double * current) { /* bytes */ *fraction = size; *unit = _("bytes"); if(*fraction < 1024) return; /* kilobytes */ *fraction /= 1024; if(current != NULL) *current /= 1024; *unit = _("kB"); if(*fraction < 1024) return; /* megabytes */ *fraction /= 1024; if(current != NULL) *current /= 1024; *unit = _("MB"); } /* download_write */ #ifndef WITH_WEBKIT static int _download_write(Download * download) { gchar * buf; gsize size; gsize s; if(gnet_conn_http_steal_buffer(download->conn, &buf, &size) != TRUE) return 0; /* FIXME use a GIOChannel instead */ s = fwrite(buf, sizeof(*buf), size, download->fp); g_free(buf); if(s == size) { download->pulse = 1; return 0; } _download_error(download, download->prefs.output, 0); download_cancel(download); return 1; } #endif /* callbacks */ /* download_on_browse */ static void _download_on_browse(gpointer data) { Download * download = data; GError * error = NULL; char * argv[] = { "browser", NULL, NULL }; if(download->prefs.output == NULL) return; argv[1] = dirname(download->prefs.output); if(g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error) != TRUE) _download_error(download, error->message, 1); } /* download_on_cancel */ static void _download_on_cancel(gpointer data) { Download * download = data; gtk_widget_hide(download->window); download_cancel(download); } /* download_on_closex */ static gboolean _download_on_closex(gpointer data) { Download * download = data; gtk_widget_hide(download->window); download_cancel(download); return FALSE; } /* download_on_http */ #ifndef WITH_WEBKIT static void _http_connected(Download * download); static void _http_error(GConnHttpEventError * event, Download * download); static void _http_data_complete(GConnHttpEventData * event, Download * download); static void _http_data_partial(GConnHttpEventData * event, Download * download); static void _http_redirect(GConnHttpEventRedirect * event, Download * download); static void _http_resolved(GConnHttpEventResolved * event, Download * download); static void _http_response(GConnHttpEventResponse * event, Download * download); static void _http_timeout(Download * download); static void _download_on_http(GConnHttp * conn, GConnHttpEvent * event, gpointer data) { Download * download = data; if(download->conn != conn) return; /* FIXME report error */ switch(event->type) { case GNET_CONN_HTTP_CONNECTED: _http_connected(download); break; case GNET_CONN_HTTP_ERROR: _http_error((GConnHttpEventError*)event, download); break; case GNET_CONN_HTTP_DATA_COMPLETE: _http_data_complete((GConnHttpEventData*)event, download); break; case GNET_CONN_HTTP_DATA_PARTIAL: _http_data_partial((GConnHttpEventData*)event, download); break; case GNET_CONN_HTTP_REDIRECT: _http_redirect((GConnHttpEventRedirect*)event, download); break; case GNET_CONN_HTTP_RESOLVED: _http_resolved((GConnHttpEventResolved*)event, download); break; case GNET_CONN_HTTP_RESPONSE: _http_response((GConnHttpEventResponse*)event, download); break; case GNET_CONN_HTTP_TIMEOUT: _http_timeout(download); break; } } static void _http_connected(Download * download) { gtk_label_set_text(GTK_LABEL(download->status), _("Connected")); /* FIXME implement */ } static void _http_error(GConnHttpEventError * event, Download * download) { char buf[10]; snprintf(buf, sizeof(buf), "%s %u", _("Error "), event->code); gtk_label_set_text(GTK_LABEL(download->status), buf); /* FIXME implement */ } static void _http_data_complete(GConnHttpEventData * event, Download * download) { g_source_remove(download->timeout); download->timeout = 0; if(_download_write(download) != 0) return; download->data_received = event->data_received; if(event->content_length == 0) download->content_length = (event->content_length != 0) ? event->content_length : event->data_received; if(fclose(download->fp) != 0) _download_error(download, download->prefs.output, 0); download->fp = NULL; _download_refresh(download); if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(download->check))) { g_idle_add(_download_on_closex, download); return; } gtk_label_set_text(GTK_LABEL(download->status), _("Complete")); gtk_widget_set_sensitive(download->check, FALSE); gtk_button_set_label(GTK_BUTTON(download->cancel), GTK_STOCK_CLOSE); gtk_widget_show(download->browse); } static void _http_data_partial(GConnHttpEventData * event, Download * download) { if(download->content_length == 0 && download->data_received == 0) gtk_label_set_text(GTK_LABEL(download->status), _("Downloading")); download->data_received = event->data_received; download->content_length = event->content_length; _download_write(download); } static void _http_redirect(GConnHttpEventRedirect * event, Download * download) { char buf[256]; if(event->new_location != NULL) snprintf(buf, sizeof(buf), "%s %s", _("Redirected to"), event->new_location); else strcpy(buf, _("Redirected")); gtk_label_set_text(GTK_LABEL(download->status), buf); /* FIXME implement */ } static void _http_resolved(GConnHttpEventResolved * event, Download * download) { gtk_label_set_text(GTK_LABEL(download->status), _("Resolved")); /* FIXME implement */ } static void _http_response(GConnHttpEventResponse * event, Download * download) { char buf[10]; snprintf(buf, sizeof(buf), "%s %u", _("Code "), event->response_code); gtk_label_set_text(GTK_LABEL(download->status), buf); /* FIXME implement */ } static void _http_timeout(Download * download) { gtk_label_set_text(GTK_LABEL(download->status), _("Timeout")); /* FIXME implement */ } #endif /* download_on_idle */ static gboolean _download_on_idle(gpointer data) { Download * download = data; DownloadPrefs * prefs = &download->prefs; #ifdef WITH_WEBKIT char * p = NULL; char * cwd = NULL; size_t len; WebKitNetworkRequest * request; download->timeout = 0; if(prefs->output[0] != '/' && (cwd = getcwd(NULL, 0)) == NULL) { _download_error(download, prefs->output, 0); download_cancel(download); return FALSE; } len = ((cwd != NULL) ? strlen(cwd) : 0) + strlen(prefs->output) + 7; if((p = malloc(len)) == NULL) { _download_error(download, prefs->output, 0); download_cancel(download); free(cwd); return FALSE; } snprintf(p, len, "%s%s%s%s", "file:", (cwd != NULL) ? cwd : "", (cwd != NULL) ? "/" : "", prefs->output); request = webkit_network_request_new(download->url); download->conn = webkit_download_new(request); webkit_download_set_destination_uri(download->conn, p); free(p); free(cwd); webkit_download_start(download->conn); #else download->timeout = 0; if((download->fp = fopen(prefs->output, "w")) == NULL) { _download_error(download, prefs->output, 0); download_cancel(download); return FALSE; } download->conn = gnet_conn_http_new(); if(gnet_conn_http_set_method(download->conn, GNET_CONN_HTTP_METHOD_GET, NULL, 0) != TRUE) return _download_error(download, _("Unknown error"), FALSE); gnet_conn_http_set_uri(download->conn, download->url); if(prefs->user_agent != NULL) gnet_conn_http_set_user_agent(download->conn, prefs->user_agent); gnet_conn_http_run_async(download->conn, _download_on_http, download); #endif download->timeout = g_timeout_add(250, _download_on_timeout, download); return FALSE; } /* download_on_timeout */ static gboolean _download_on_timeout(gpointer data) { gboolean ret = TRUE; Download * d = data; #ifdef WITH_WEBKIT WebKitDownloadStatus status; guint64 received = d->data_received; /* FIXME not very efficient */ status = webkit_download_get_status(d->conn); switch(status) { case WEBKIT_DOWNLOAD_STATUS_ERROR: ret = FALSE; gtk_label_set_text(GTK_LABEL(d->status), _("Error")); break; case WEBKIT_DOWNLOAD_STATUS_FINISHED: /* XXX pasted from _http_data_complete */ ret = FALSE; gtk_label_set_text(GTK_LABEL(d->status), _("Complete")); gtk_widget_set_sensitive(d->check, FALSE); gtk_button_set_label(GTK_BUTTON(d->cancel), GTK_STOCK_CLOSE); gtk_widget_show(d->browse); d->data_received = webkit_download_get_current_size( d->conn); g_object_unref(d->conn); d->conn = NULL; if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( d->check))) { g_idle_add(_download_on_closex, d); break; } break; case WEBKIT_DOWNLOAD_STATUS_STARTED: gtk_label_set_text(GTK_LABEL(d->status), _("Downloading")); d->data_received = webkit_download_get_current_size( d->conn); d->content_length = webkit_download_get_total_size( d->conn); if(d->content_length == d->data_received) { d->pulse = (d->data_received > received) ? 1 : 0; d->content_length = 0; } break; default: /* XXX anything else to handle here? */ break; } #endif _download_refresh(d); if(ret != TRUE) d->timeout = 0; return ret; } #ifdef WITH_MAIN /* usage */ static int _usage(void) { fputs(_("Usage: download [-O output][-U user-agent] URL...\n" " -O File to write document to\n" " -U User-agent string to send\n"), stderr); return 1; } /* main */ int main(int argc, char * argv[]) { DownloadPrefs prefs; int o; int cnt; char const * p; char http[256] = ""; unsigned int port; Download * download; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); memset(&prefs, 0, sizeof(prefs)); if(g_thread_supported() == FALSE) g_thread_init(NULL); gtk_init(&argc, &argv); while((o = getopt(argc, argv, "O:U:")) != -1) switch(o) { case 'O': prefs.output = optarg; break; case 'U': prefs.user_agent = optarg; break; default: return _usage(); } if((cnt = argc - optind) == 0) return _usage(); if((p = getenv("http_proxy")) != NULL && sscanf(p, "http://%255[^:]:%u", http, &port) == 2) http[sizeof(http) - 1] = '\0'; for(o = 0; o < cnt; o++) if((download = download_new(&prefs, argv[optind + o])) != NULL) _download_set_proxy(download, http, port); gtk_main(); return 0; } #endif /* WITH_MAIN */