by Andrew Zabolotny: This patch implements a "slave mode". What it does is that it allows two microdc2's to run with the same database of shared files. Right now microdc2 supports connecting just to one hub at a time, so this patch helps overcome this limit. To use it, just launch a second copy of microdc2 and set filelist_refresh_interval to 0. This will tell microdc2 to not refresh the file list, but rather to check the last modification time on file ~/.microdc2/filelist. When it changes (supposedly re-written by a "master" microdc2 process), it is re-read by the slave processes. 3. Another small patch is that I've added - the -c switch now accepts filenames without path, in which case they are looked for in the ~/.microdc2/ directory. For example, you can have two config files - config.hub1 and config.hub2 in that directory, then you just can run: "microdc2 -c config.hub1" rather than "microdc2 -c /home/user/config.hub1". 4. Also the later patch incorporates a important bugfix (I haven't separated it from the slave mode patch, my bad, but it's completely contained in the subdiff for the file main.c). While having microdc2 running for a couple of days I have observed that sometimes it goes in a tight loop in the master process (the one that manipulates the children) doing select() which quits immediately without sleeping inbetween. This causes 100% CPU load on the server which is a pretty bad thing. Some research has shown that sometimes (very seldom) client processes are left running although all the relevant structures in the main process (in the user_conns hashmap) are cleaned up, but their file handle is left in the read_fds/write_fds set. Since these strcutures are persistent for the lifetime of the master process, as soon as these handles becomes ready the master process won't be able to sleep in select() anymore, but still it won't do anything about these "orphan" handles. The 'bugfix' is rather a dirty workaround, but it works and it could be helpful at least in debug mode (when/if the real bug will be fixed). What it does is that it checks that all handles signalled as ready in the res_read_fds/res_write_fds filesets are processed. If there are handles left unprocessed at the end of loop, it emits a warning and closes those handles, then clears them from read_fds/write_fds. diff -ur orig/src/filelist-in.c src/filelist-in.c --- orig/src/filelist-in.c 2006-11-28 15:49:09.000000000 +0300 +++ src/filelist-in.c 2007-03-20 00:06:00.000000000 +0300 @@ -334,6 +334,7 @@ /* Inability to register these signals is not a fatal error. */ sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; + sigemptyset (&sigact.sa_mask); #ifdef HAVE_STRUCT_SIGACTION_SA_RESTORER sigact.sa_restorer = NULL; #endif diff -ur orig/src/hash.c src/hash.c --- orig/src/hash.c 2006-11-16 14:21:43.000000000 +0300 +++ src/hash.c 2007-03-20 00:04:46.000000000 +0300 @@ -58,6 +58,7 @@ /* Inability to register these signals is not a fatal error. */ sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; + sigemptyset (&sigact.sa_mask); #ifdef HAVE_STRUCT_SIGACTION_SA_RESTORER sigact.sa_restorer = NULL; #endif diff -ur orig/src/local_flist.c src/local_flist.c --- orig/src/local_flist.c 2006-12-09 12:01:22.000000000 +0300 +++ src/local_flist.c 2007-03-29 23:00:38.000000000 +0400 @@ -66,6 +66,7 @@ pid_t update_child; int incoming_update_type = -1; char* update_status = NULL; +time_t filelist_mtime = 0; static const char* filelist_name = "filelist"; static const char* new_filelist_name = "new-filelist"; @@ -78,6 +79,8 @@ #define ENOTFILELIST (1 << 16) #define EWRONGVERSION (ENOTFILELIST + 1) +bool report_error(MsgQ* status_mq, const char* fmt, ...); + int compare_pointers(void* p1, void* p2) { return p1 != p2; @@ -119,20 +122,61 @@ return is_already_shared_inode(root, st.st_dev, st.st_ino); } -DCFileList* read_local_file_list(const char* path) +void lock_file (const char* path) +{ + int fd, timeout = 20; + char fn [300]; + snprintf (fn, sizeof (fn), "%s.lock", path); + while ((fd = open (fn, O_RDONLY | O_CREAT | O_EXCL, 0600) < 0)) { + if (errno != EEXIST) { + /* It seems there's no way to report an error from here to parent? */ + /*report_error(result_mq, _("%s: Failed to create filelist lock, filelist will be not multiprocess-safe\n"), fn);*/ + break; + } + /* Wait some time for the lock to be released */ + sleep (1); + if (!--timeout) { + /*report_error(result_mq, _("%s: Filelist semaphore locked, but owner seems dead, breaking lock\n"), fn);*/ + break; + } + } + if (fd >= 0) + close (fd); +} + +void unlock_file (const char* path) +{ + char fn [300]; + snprintf (fn, sizeof (fn), "%s.lock", path); + unlink (fn); +} + +/* if old_root is not NULL, rereads the file list only if file changed */ +DCFileList* read_local_file_list(const char* path, DCFileList *old_root) { struct stat st; DCFileList *root = NULL; + /* First of all, check if filelist is not locked by other process */ + lock_file (path); + if (stat(path, &st) < 0) { if (errno != ENOENT) { TRACE(("cannot stat %s: %d, %s\n", path, errno, errstr)); + unlock_file (path); return NULL; } } else if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { + unlock_file (path); return NULL; } + if (old_root && filelist_mtime == st.st_mtime) { + unlock_file (path); + return old_root; + } + filelist_mtime = st.st_mtime; + int fd = open(path, O_RDONLY); if (fd >= 0) { void* mapped = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -158,12 +202,22 @@ root = new_file_node("", DC_TYPE_DIR, NULL); } + unlock_file (path); + + if (old_root) + filelist_free (old_root); + return root; } bool write_local_file_list(const char* path, DCFileList* root) { bool result = false; + struct stat st; + + /* Check if filelist is not locked by other process */ + lock_file (path); + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (fd >= 0) { unsigned char* data = NULL; @@ -194,8 +248,14 @@ result = (size == data_size); cleanup: + /* Update filelist mtime */ + if (stat(path, &st) == 0) + filelist_mtime = st.st_mtime; + close(fd); } + + unlock_file (path); return result; } @@ -487,6 +547,7 @@ /* Inability to register these signals is not a fatal error. */ sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; + sigemptyset (&sigact.sa_mask); #ifdef HAVE_STRUCT_SIGACTION_SA_RESTORER sigact.sa_restorer = NULL; #endif @@ -503,7 +564,7 @@ goto cleanup; } - if (NULL == (root = read_local_file_list(flist_filename))) { + if (NULL == (root = read_local_file_list(flist_filename, NULL))) { if (errno == ENOTFILELIST) { report_error(result_mq, "Cannot load FileList - %s: Invalid file format\n", flist_filename); } else if (errno == EWRONGVERSION) { @@ -527,7 +588,7 @@ max_fd = MAX(hash_result_mq->fd, max_fd); while (true) { - tv.tv_sec = filelist_refresh_timeout; + tv.tv_sec = FILELIST_SLAVE_MODE ? 60 : filelist_refresh_timeout; tv.tv_usec = 0; fd_set r_ready = readable, w_ready = writable; @@ -577,13 +638,19 @@ fflush(stderr); */ } + + if (FILELIST_SLAVE_MODE && update_hash) { + /* Unexpected hash update received while in slave filelist mode */ + update_hash = false; + } + time_t now = time(NULL); if (update_hash && ((hashing == NULL && hash_files->cur == 0) || (now - hash_start) > filelist_hash_refresh_timeout)) { hash_start = now; if (write_local_file_list(new_flist_filename, root)) { rename(new_flist_filename, flist_filename); } else { - unlink(new_filelist_name); + unlink(new_flist_filename); } if (!send_filelist(result_mq, root)) { @@ -607,11 +674,7 @@ msgq_get(request_mq, MSGQ_INT, &update_type, MSGQ_END); } else { if (update_type == FILELIST_UPDATE_REFRESH_INTERVAL) { - time_t interval = 0; - msgq_get(request_mq, MSGQ_INT, &interval, MSGQ_END); - if (interval != 0) { - filelist_refresh_timeout = interval; - } + msgq_get(request_mq, MSGQ_INT, &filelist_refresh_timeout, MSGQ_END); } else { char *name; int len = 0; @@ -626,13 +689,15 @@ case FILELIST_UPDATE_ADD_DIR_NAME: if (is_already_shared(root, name)) { // report error here - report_error(result_mq, "%s directory is already shared as subfolder of existing shared tree\n", name); - } else { + report_error(result_mq, _("%s directory is already shared as subfolder of existing shared tree\n"), name); + } else if (FILELIST_SLAVE_MODE) + report_error(result_mq, _("Cannot add directory %s to share list while in slave filelist mode\n"), name); + else { char* bname = xstrdup(base_name(name)); if (hmap_contains_key(root->dir.children, bname)) { /* we already have the shared directory with the same name */ - report_error(result_mq, "%s directory cannot be shared as %s because there is already shared directory with the same name\n", name, bname); + report_error(result_mq, _("%s directory cannot be shared as %s because there is already shared directory with the same name\n"), name, bname); } else { DCFileList* node = new_file_node(bname, DC_TYPE_DIR, root); node->dir.real_path = xstrdup(name); @@ -642,28 +707,30 @@ } break; case FILELIST_UPDATE_DEL_DIR_NAME: - //selected = 0; { char* bname = xstrdup(base_name(name)); DCFileList* node = hmap_get(root->dir.children, bname); - if (node != NULL && node->type == DC_TYPE_DIR) { - if (strcmp(node->dir.real_path, name) == 0) { + if (node != NULL && node->type == DC_TYPE_DIR && + strcmp(node->dir.real_path, name) == 0) { + if (FILELIST_SLAVE_MODE) + report_error(result_mq, _("Cannot remove directory %s from share list while in slave filelist mode\n"), name); + else { node = hmap_remove(root->dir.children, bname); filelist_free(node); if (write_local_file_list(new_flist_filename, root)) { rename(new_flist_filename, flist_filename); } else { - unlink(new_filelist_name); + unlink(new_flist_filename); } if (!send_filelist(result_mq, root)) { goto cleanup; } - } else { - report_error(result_mq, "%s directory is not shared\n"); } } + else + report_error(result_mq, _("%s directory is not shared\n"), name); free(bname); } break; @@ -703,18 +770,27 @@ } } } - if (selected == 0) { + + if (FILELIST_SLAVE_MODE && selected >= 0) { + // Check if filelist has been changed since we last read it + DCFileList *new_root = read_local_file_list(flist_filename, root); + if (new_root != root) { + root = new_root; + send_filelist(result_mq, root); + } + } + else if (selected == 0) { // just look through shared directories for new or deleted files if (hashing == NULL && !initial) - report_status(result_mq, "Refreshing FileList"); + report_status(result_mq, _("Refreshing FileList")); if (lookup_filelist_changes(root, hash_files)) { if (write_local_file_list(new_flist_filename, root)) { rename(new_flist_filename, flist_filename); } else { - unlink(new_filelist_name); + unlink(new_flist_filename); } - + if (!send_filelist(result_mq, root)) { break; } diff -ur orig/src/lookup.c src/lookup.c --- orig/src/lookup.c 2006-11-16 11:18:14.000000000 +0300 +++ src/lookup.c 2007-03-20 00:09:05.000000000 +0300 @@ -170,6 +170,7 @@ /* Inability to register these signals is not a fatal error. */ sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; + sigemptyset (&sigact.sa_mask); #ifdef HAVE_STRUCT_SIGACTION_SA_RESTORER sigact.sa_restorer = NULL; #endif diff -ur orig/src/main.c src/main.c --- orig/src/main.c 2006-12-24 21:23:51.000000000 +0300 +++ src/main.c 2007-03-31 17:26:33.000000000 +0400 @@ -55,6 +55,15 @@ #include "common/msgq.h" #include "microdc.h" +/* Define the macro below for orphan handle checking (useful for debugging) */ +#define CHECK_ORPHAN_HANDLES + +#ifdef CHECK_ORPHAN_HANDLES +#define IF_ORPHAN_HANDLES(x) x +#else +#define IF_ORPHAN_HANDLES(x) +#endif + enum { VERSION_OPT = 256, HELP_OPT @@ -489,7 +498,8 @@ FD_CLR(uc->get_mq->fd, &read_fds); FD_CLR(uc->put_mq->fd, &write_fds); - if (close(uc->get_mq->fd) != 0 || close(uc->put_mq->fd) != 0) + // using (close || close) here could cause one of close() to be skipped + if ((close(uc->get_mq->fd) | close(uc->put_mq->fd)) != 0) warn(_("Cannot close pipe - %s\n"), errstr); msgq_free(uc->get_mq); uc->get_mq = NULL; @@ -1109,9 +1119,11 @@ } /* Start of disable_search. */ if (search_socket >= 0) { - if (close(search_socket) < 0) + if (close(search_socket) < 0) warn(_("Cannot close socket - %s\n"), errstr); - search_socket = -1; + FD_CLR(search_socket, &read_fds); + FD_CLR(search_socket, &write_fds); + search_socket = -1; } /* End of disable_search. */ enable_search(); @@ -1170,6 +1182,11 @@ custom_config = true; free(config_file); config_file = xstrdup(optarg); + /* Check if file exists: if not consider it is a file name in package dir */ + if (strchr (config_file, '/') == NULL && access (config_file, R_OK) != 0) { + free(config_file); + get_package_file(optarg, &config_file); + } break; case 'n': /* --no-config */ free(config_file); @@ -1178,6 +1195,7 @@ case HELP_OPT: /* --help */ printf(_("Usage: %s [OPTION]...\n"), quotearg(argv[0])); puts(_("Start microdc, a command-line based Direct Connect client.\n")); + printf(_(" -c, --config=FILE use a custom config file\n")); printf(_(" -n, --no-config do not read config file on startup\n")); printf(_(" --help display this help and exit\n")); printf(_(" --version output version information and exit\n")); @@ -1306,34 +1324,60 @@ break; } - if (running && FD_ISSET(signal_pipe[0], &res_read_fds)) - read_signal_input(); - if (running && FD_ISSET(STDIN_FILENO, &res_read_fds)) + if (running && FD_ISSET(signal_pipe[0], &res_read_fds)) { + FD_CLR(signal_pipe[0], &res_read_fds); + read_signal_input(); + } + if (running && FD_ISSET(STDIN_FILENO, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(STDIN_FILENO, &res_read_fds)); screen_read_input(); - if (running && listen_socket >= 0 && FD_ISSET(listen_socket, &res_read_fds)) + } + if (running && listen_socket >= 0 && FD_ISSET(listen_socket, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(listen_socket, &res_read_fds)); handle_listen_connection(); - if (running && hub_socket >= 0 && FD_ISSET(hub_socket, &res_read_fds)) + } + if (running && hub_socket >= 0 && FD_ISSET(hub_socket, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(hub_socket, &res_read_fds)); hub_input_available(); - if (running && hub_socket >= 0 && FD_ISSET(hub_socket, &res_write_fds)) + } + if (running && hub_socket >= 0 && FD_ISSET(hub_socket, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(hub_socket, &res_write_fds)); hub_now_writable(); + } if (running) check_hub_activity(); - if (running && search_socket >= 0 && FD_ISSET(search_socket, &res_read_fds)) + if (running && search_socket >= 0 && FD_ISSET(search_socket, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(search_socket, &res_read_fds)); search_input_available(); - if (running && search_socket >= 0 && FD_ISSET(search_socket, &res_write_fds)) + } + if (running && search_socket >= 0 && FD_ISSET(search_socket, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(search_socket, &res_write_fds)); search_now_writable(); - if (running && FD_ISSET(lookup_request_mq->fd, &res_write_fds)) + } + if (running && FD_ISSET(lookup_request_mq->fd, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(lookup_request_mq->fd, &res_write_fds)); lookup_request_fd_writable(); - if (running && FD_ISSET(lookup_result_mq->fd, &res_read_fds)) + } + if (running && FD_ISSET(lookup_result_mq->fd, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(lookup_result_mq->fd, &res_read_fds)); lookup_result_fd_readable(); - if (running && FD_ISSET(parse_request_mq->fd, &res_write_fds)) + } + if (running && FD_ISSET(parse_request_mq->fd, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(parse_request_mq->fd, &res_write_fds)); parse_request_fd_writable(); - if (running && FD_ISSET(parse_result_mq->fd, &res_read_fds)) + } + if (running && FD_ISSET(parse_result_mq->fd, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(parse_result_mq->fd, &res_read_fds)); parse_result_fd_readable(); - if (running && FD_ISSET(update_request_mq->fd, &res_write_fds)) + } + if (running && FD_ISSET(update_request_mq->fd, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(update_request_mq->fd, &res_write_fds)); update_request_fd_writable(); - if (running && FD_ISSET(update_result_mq->fd, &res_read_fds)) + } + if (running && FD_ISSET(update_result_mq->fd, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(update_result_mq->fd, &res_read_fds)); update_result_fd_readable(); + } if (running) { HMapIterator it; @@ -1341,13 +1385,36 @@ hmap_iterator(user_conns, &it); while (running && it.has_next(&it)) { DCUserConn *uc = it.next(&it); - if (uc->put_mq != NULL && FD_ISSET(uc->put_mq->fd, &res_write_fds)) + if (uc->put_mq != NULL && FD_ISSET(uc->put_mq->fd, &res_write_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(uc->put_mq->fd, &res_write_fds)); user_request_fd_writable(uc); - if (uc->get_mq != NULL && FD_ISSET(uc->get_mq->fd, &res_read_fds)) - user_result_fd_readable(uc); + } + if (uc->get_mq != NULL && FD_ISSET(uc->get_mq->fd, &res_read_fds)) { + IF_ORPHAN_HANDLES (FD_CLR(uc->get_mq->fd, &res_read_fds)); + user_result_fd_readable(uc); + } } } - } + +#ifdef CHECK_ORPHAN_HANDLES + /* Check for orphan file handles */ + { + int i; + for (i = 0; i < FD_SETSIZE; i++) { + if (FD_ISSET (i, &res_read_fds)) { + warn(_("Orphan READ file handle %d, closing\n"), i); + close (i); + FD_CLR (i, &read_fds); + } + if (FD_ISSET (i, &res_write_fds)) { + warn(_("Orphan WRITE file handle %d, closing\n"), i); + close (i); + FD_CLR (i, &write_fds); + } + } + } +#endif + } cleanup: @@ -1365,6 +1432,9 @@ byteq_free(search_recvq); + /* Do this before freeing user_conn_unknown_free otherwise we crash */ + hmap_foreach_value(user_conns, user_conn_cancel); + ptrv_foreach(user_conn_unknown_free, free); ptrv_free(user_conn_unknown_free); @@ -1374,7 +1444,6 @@ ptrv_foreach(our_searches, (PtrVForeachCallback) free_search_request); ptrv_free(our_searches); - hmap_foreach_value(user_conns, user_conn_cancel); /* XXX: follow up and wait for user connections to die? */ hmap_free(user_conns); diff -ur orig/src/microdc.1 src/microdc.1 --- orig/src/microdc.1 2006-10-22 12:35:50.000000000 +0400 +++ src/microdc.1 2007-03-17 11:16:33.000000000 +0300 @@ -34,6 +34,8 @@ .TP \fB\-c, \-\-config\fR=\fIFILE\fR Read configuration script from FILE rather than ~/.microdc/config. +If FILE doees not exist and its name doesn't contain any slashes, +program will look for given file in ~/.microdc/. .TP \fB\-n, \-\-no\-config\fR Do not read config file on startup. @@ -43,6 +45,69 @@ .TP \fB\-\-version\fR Output version information and exit. +.SH SETTINGS +Most important options can be changed only by using the microdc \fBset\fR command. +.TP +\fBactive\fR=\fIon|1|off|0\fR +Enable if listening for remote connections. Uses the setting \fBlistenport\fR +for incoming connection port number. +.TP +\fBauto_reconnect\fR=\fIon|1|off|0\fR +Enable automatic reconnect to the last connected hub +.TP +\fBdescription\fR=\fISTRING\fR +This is the description which is visible to other users of the hub. +.TP +\fBdisplay\fR=\fIconnections{,debug,download,joinpart,publicchat,searchresults,upload}\fR +Types of messages to display on screen. +.TP +\fBdownloaddir\fR=\fISTRING\fR +Directory which files are downloaded to. +.TP +\fBemail\fR=\fISTRING\fR +The e-mail visible to other users of the hub. +.TP +\fBfilelist_refresh_interval\fR=\fINUMBER\fR +Local filelist refresh interval (in seconds). If set to zero, program runs in a +special \fIslave\fR mode: it never updates the file list, just checks every minute +if the filelist changes, and if so - reads it. This is pretty useful if you connect +to several hubs and use same filelist. +.TP +\fBfilesystem_charset\fR=\fICHARSET\fR +Character set used for chat on the hub. +.TP +\fBlistenaddr\fR=\fIHOST\fR +Address to send to clients. +.TP +\fBlistenport\fR=\fINUMBER\fR +Port to listen on for connections. +.TP +\fBlistingdir\fR=\fISTRING\fR +Directory where file listings are kept. If you set this, this makes startup faster. +.TP +\fBlog\fR=\fIconnections{,debug,download,joinpart,publicchat,searchresults,upload}\fR +Types of messages to log (if logfile set). +.TP +\fBlog_charset\fR=\fICHARSET\fR +Log charset (if it differs from local charset). +.TP +\fBlogfile\fR=\fIFILE\fR +File to log screen messages to (will be appeneded). +.TP +\fBnick\fR=\fISTRING\fR +This is the desired (but not necessarily the current) nick name. +.TP +\fBpassword\fR=\fISTRING\fR +The optional password to pass to the hub on connect. +.TP +\fBslots\fR=\fINUMBER\fR +Number of open upload slots. +.TP +\fBspeed\fR=\fISTRING\fR +The speed visible to other users of the hub. +.TP +\fBtag\fR=\fISTRING\fR +The user agent tag the hub uses to detect features .SH FILES The following files are used by microdc (~ represents the current user's home directory): .TP diff -ur orig/src/microdc.h src/microdc.h --- orig/src/microdc.h 2006-12-24 21:27:15.000000000 +0300 +++ src/microdc.h 2007-03-17 13:36:26.000000000 +0300 @@ -596,6 +596,7 @@ extern pid_t update_child; extern char* update_status; extern time_t filelist_refresh_timeout; +#define FILELIST_SLAVE_MODE (filelist_refresh_timeout <= 0) bool local_file_list_update_init(void); bool local_file_list_init(void); void local_file_list_update_finish(void); diff -ur orig/src/user.c src/user.c --- orig/src/user.c 2006-11-30 13:00:40.000000000 +0300 +++ src/user.c 2007-03-22 11:10:49.000000000 +0300 @@ -604,7 +604,7 @@ ucl->share_file/*UL*/ = share_file; } else { free(share_file); - ucl->share_file = xstrdup(base_name(local_file)); + ucl->share_file = xstrdup(local_file ? base_name(local_file) : ""); } ucl->local_file/*UL*/ = local_file; } else { @@ -612,7 +612,7 @@ } if (ucl->local_file/*UL*/ == NULL) { - flag_putf(DC_DF_CONNECTIONS, _("%s: File Not Available\n"), quotearg(ucl->local_file/*UL*/)); + flag_putf(DC_DF_CONNECTIONS, _("Non-existent file requested: Not Available\n")); user_putf(ucl, "$Error File Not Available|"); end_upload(ucl, false, _("no such shared file")); return -4; diff -ur orig/src/variables.c src/variables.c --- orig/src/variables.c 2006-12-24 21:42:07.000000000 +0300 +++ src/variables.c 2007-03-17 13:39:15.000000000 +0300 @@ -313,10 +313,27 @@ static void charset_completion_generator(DCCompletionInfo *ci) { - /* FIXME: NYI */ - /*run the command 'iconv --list' to get a list of completion alternatives - fork(); - exec('iconv', 'iconv', '--list');*/ + size_t wlen; + char line [100]; + FILE *f = popen ("iconv --list", "r"); + if (!f) + return; + + wlen = strlen(ci->word); + + // iconv detects when stdout is not a tty, and prints the list + // in a special format: CHARSET// + while (fgets (line, sizeof (line), f)) { + char *slash = strchr (line, '/'); + if (!slash) + continue; + *slash = 0; + + if (slash - line >= wlen && memcmp(ci->word, line, wlen) == 0) + ptrv_append(ci->results, new_completion_entry(xstrndup(line, slash - line), NULL)); + } + pclose (f); + ptrv_sort(ci->results, completion_entry_display_compare); }