/* $Id$ */ /* Copyright (c) 2007-2020 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Browser */ /* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Browser/vfs.h" #include "../config.h" #define _(string) gettext(string) /* constants */ #ifndef PROGNAME_MOVE # define PROGNAME_MOVE "move" #endif #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef DATADIR # define DATADIR PREFIX "/share" #endif #ifndef LOCALEDIR # define LOCALEDIR DATADIR "/locale" #endif /* Move */ /* private */ /* types */ typedef int Prefs; #define PREFS_f 0x1 #define PREFS_i 0x2 typedef struct _Move { Prefs * prefs; unsigned int filec; char ** filev; unsigned int cur; GtkWidget * window; GtkWidget * label; GtkWidget * progress; } Move; /* prototypes */ static int _move_error(Move * move, char const * message, int ret); static int _move_filename_confirm(Move * move, char const * filename); static int _move_filename_error(Move * move, char const * filename, int ret); /* functions */ /* move */ static void _move_refresh(Move * move); /* callbacks */ static void _move_on_closex(void); static void _move_on_cancel(void); static gboolean _move_idle_first(gpointer data); static int _move(Prefs * prefs, unsigned int filec, char * filev[]) { static Move move; GtkWidget * vbox; GtkWidget * hbox; GtkSizeGroup * left; GtkSizeGroup * right; GtkWidget * widget; PangoFontDescription * bold; if(filec < 2 || filev == NULL) return 1; /* FIXME report error */ move.prefs = prefs; move.filec = filec; move.filev = filev; move.cur = 0; /* graphical interface */ move.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_icon_name(GTK_WINDOW(move.window), "stock_cut"); gtk_window_set_resizable(GTK_WINDOW(move.window), FALSE); gtk_window_set_title(GTK_WINDOW(move.window), _("Move file(s)")); g_signal_connect(G_OBJECT(move.window), "delete-event", G_CALLBACK( _move_on_closex), NULL); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); left = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); right = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); /* current argument */ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); widget = gtk_label_new(_("Moving: ")); bold = pango_font_description_new(); pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD); #if GTK_CHECK_VERSION(3, 0, 0) gtk_widget_override_font(widget, bold); g_object_set(widget, "halign", GTK_ALIGN_START, NULL); #else gtk_widget_modify_font(widget, bold); gtk_misc_set_alignment(GTK_MISC(widget), 0, 0); #endif gtk_size_group_add_widget(left, widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); move.label = gtk_label_new(""); #if GTK_CHECK_VERSION(2, 6, 0) gtk_label_set_ellipsize(GTK_LABEL(move.label), PANGO_ELLIPSIZE_END); gtk_label_set_width_chars(GTK_LABEL(move.label), 25); #endif #if GTK_CHECK_VERSION(3, 0, 0) g_object_set(move.label, "halign", GTK_ALIGN_START, NULL); #else gtk_misc_set_alignment(GTK_MISC(move.label), 0.0, 0.5); #endif gtk_size_group_add_widget(right, move.label); gtk_box_pack_start(GTK_BOX(hbox), move.label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); /* progress bar */ move.progress = gtk_progress_bar_new(); #if GTK_CHECK_VERSION(3, 0, 0) gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(move.progress), TRUE); #endif gtk_progress_bar_set_text(GTK_PROGRESS_BAR(move.progress), ""); gtk_box_pack_start(GTK_BOX(vbox), move.progress, TRUE, TRUE, 0); hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK( _move_on_cancel), NULL); gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(move.window), 4); gtk_container_add(GTK_CONTAINER(move.window), vbox); g_idle_add(_move_idle_first, &move); _move_refresh(&move); gtk_widget_show_all(move.window); pango_font_description_free(bold); return 0; } static void _move_refresh(Move * move) { char const * filename = move->filev[move->cur]; char * p; char buf[64]; double fraction; if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; gtk_label_set_text(GTK_LABEL(move->label), filename); free(p); snprintf(buf, sizeof(buf), _("File %u of %u"), move->cur + 1, move->filec - 1); fraction = move->cur; fraction /= move->filec - 1; gtk_progress_bar_set_text(GTK_PROGRESS_BAR(move->progress), buf); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(move->progress), fraction); } static void _move_on_closex(void) { gtk_main_quit(); } static void _move_on_cancel(void) { gtk_main_quit(); } static int _move_single(Move * move, char const * src, char const * dst); static gboolean _move_idle_multiple(gpointer data); static gboolean _move_idle_first(gpointer data) { Move * move = data; char const * filename = move->filev[move->filec - 1]; struct stat st; if(browser_vfs_stat(move->filev[move->filec - 1], &st) != 0) { if(errno != ENOENT) _move_filename_error(move, filename, 0); else if(move->filec > 2) { errno = ENOTDIR; _move_filename_error(move, filename, 0); } else _move_single(move, move->filev[0], move->filev[1]); } else if(S_ISDIR(st.st_mode)) { g_idle_add(_move_idle_multiple, move); return FALSE; } else if(move->filec > 2) { errno = ENOTDIR; _move_filename_error(move, filename, 0); } else _move_single(move, move->filev[0], move->filev[1]); gtk_main_quit(); return FALSE; } /* move_single */ static int _single_dir(Move * move, char const * src, char const * dst, mode_t mode); static int _single_fifo(Move * move, char const * src, char const * dst, mode_t mode); static int _single_nod(Move * move, char const * src, char const * dst, mode_t mode, dev_t rdev); static int _single_symlink(Move * move, char const * src, char const * dst); static int _single_regular(Move * move, char const * src, char const * dst, mode_t mode); static int _single_p(Move * move, char const * dst, struct stat const * st); static int _move_single(Move * move, char const * src, char const * dst) { int ret; struct stat st; if(browser_vfs_lstat(src, &st) != 0 && errno == ENOENT) /* XXX TOCTOU */ return _move_filename_error(move, src, 1); if(*(move->prefs) & PREFS_i && (browser_vfs_lstat(dst, &st) == 0 || errno != ENOENT) && _move_filename_confirm(move, dst) != 1) return 0; if(rename(src, dst) == 0) return 0; if(errno != EXDEV) return _move_filename_error(move, src, 1); if(unlink(dst) != 0 && errno != ENOENT) return _move_filename_error(move, dst, 1); if(browser_vfs_lstat(src, &st) != 0) return _move_filename_error(move, dst, 1); if(S_ISDIR(st.st_mode)) ret = _single_dir(move, src, dst, st.st_mode & 0777); else if(S_ISFIFO(st.st_mode)) ret = _single_fifo(move, src, dst, st.st_mode & 0666); else if(S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) ret = _single_nod(move, src, dst, st.st_mode, st.st_rdev); else if(S_ISLNK(st.st_mode)) ret = _single_symlink(move, src, dst); else if(S_ISREG(st.st_mode)) ret = _single_regular(move, src, dst, st.st_mode & 0777); else { errno = ENOSYS; return _move_filename_error(move, src, 1); } if(ret != 0) return ret; _single_p(move, dst, &st); return 0; } /* single_dir */ static int _single_recurse(Move * move, char const * src, char const * dst, mode_t mode); static int _single_dir(Move * move, char const * src, char const * dst, mode_t mode) { if(_single_recurse(move, src, dst, mode) != 0) return 1; if(rmdir(src) != 0) /* FIXME probably gonna fail, recurse before */ _move_filename_error(move, src, 0); return 0; } static int _single_recurse(Move * move, char const * src, char const * dst, mode_t mode) { int ret = 0; size_t srclen; size_t dstlen; DIR * dir; struct dirent * de; char * ssrc = NULL; char * sdst = NULL; char * p; if(browser_vfs_mkdir(dst, mode) != 0) return _move_filename_error(move, dst, 1); srclen = strlen(src); dstlen = strlen(dst); if((dir = browser_vfs_opendir(src, NULL)) == NULL) return _move_filename_error(move, src, 1); while((de = browser_vfs_readdir(dir)) != NULL) { if(de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) continue; if((p = realloc(ssrc, srclen + strlen(de->d_name) + 2)) == NULL) { ret |= _move_filename_error(move, src, 1); continue; } ssrc = p; if((p = realloc(sdst, dstlen + strlen(de->d_name) + 2)) == NULL) { ret |= _move_filename_error(move, src, 1); continue; } sdst = p; sprintf(ssrc, "%s/%s", src, de->d_name); sprintf(sdst, "%s/%s", dst, de->d_name); ret |= _move_single(move, ssrc, sdst); } browser_vfs_closedir(dir); free(ssrc); free(sdst); return ret; } static int _single_fifo(Move * move, char const * src, char const * dst, mode_t mode) { if(mkfifo(dst, mode) != 0) return _move_filename_error(move, dst, 1); if(unlink(src) != 0) _move_filename_error(move, src, 0); return 0; } static int _single_nod(Move * move, char const * src, char const * dst, mode_t mode, dev_t rdev) { if(mknod(dst, mode, rdev) != 0) return _move_filename_error(move, dst, 1); if(unlink(src) != 0) _move_filename_error(move, src, 0); return 0; } static int _single_symlink(Move * move, char const * src, char const * dst) { char buf[PATH_MAX]; ssize_t i; if((i = readlink(src, buf, sizeof(buf) - 1)) == -1) return _move_filename_error(move, src, 1); buf[i] = '\0'; if(symlink(buf, dst) != 0) return _move_filename_error(move, dst, 1); if(unlink(src) != 0) _move_filename_error(move, src, 0); return 0; } static int _single_regular(Move * move, char const * src, char const * dst, mode_t mode) { int ret = 0; FILE * fsrc; int fd; FILE * fdst; char buf[BUFSIZ]; size_t size; if((fsrc = fopen(src, "r")) == NULL) return _move_filename_error(move, dst, 1); if((fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode)) < 0 || (fdst = fdopen(fd, "w")) == NULL) { ret |= _move_filename_error(move, dst, 1); if(fd >= 0) close(fd); fclose(fsrc); return ret; } while((size = fread(buf, sizeof(char), sizeof(buf), fsrc)) > 0) if(fwrite(buf, sizeof(char), size, fdst) != size) break; if(!feof(fsrc)) ret |= _move_filename_error(move, (size == 0) ? src : dst, 1); if(fclose(fsrc) != 0) ret |= _move_filename_error(move, src, 1); if(fclose(fdst) != 0) ret |= _move_filename_error(move, dst, 1); if(unlink(src) != 0) _move_filename_error(move, src, 0); return ret; } static int _single_p(Move * move, char const * dst, struct stat const * st) { struct timeval tv[2]; if(lchown(dst, st->st_uid, st->st_gid) != 0) /* XXX TOCTOU */ { _move_filename_error(move, dst, 0); if(lchmod(dst, st->st_mode & ~(S_ISUID | S_ISGID)) != 0) _move_filename_error(move, dst, 0); } else if(lchmod(dst, st->st_mode) != 0) _move_filename_error(move, dst, 0); tv[0].tv_sec = st->st_atime; tv[0].tv_usec = 0; tv[1].tv_sec = st->st_mtime; tv[1].tv_usec = 0; if(lutimes(dst, tv) != 0) _move_filename_error(move, dst, 0); return 0; } /* move_idle_multiple */ static int _move_multiple(Move * move, char const * src, char const * dst); static gboolean _move_idle_multiple(gpointer data) { Move * move = data; _move_multiple(move, move->filev[move->cur], move->filev[move->filec - 1]); move->cur++; if(move->cur == move->filec - 1) { gtk_main_quit(); return FALSE; } _move_refresh(move); return TRUE; } static int _move_multiple(Move * move, char const * src, char const * dst) { int ret; char * to; size_t len; char * p; char * q; if((p = strdup(src)) == NULL) return _move_filename_error(move, src, 1); to = basename(p); len = strlen(dst) + strlen(to) + 2; if((q = malloc(len)) == NULL) { free(p); return _move_filename_error(move, src, 1); } snprintf(q, len, "%s/%s", dst, to); ret = _move_single(move, src, q); free(p); free(q); return ret; } /* move_error */ static int _error_text(char const * message, int ret); static int _move_error(Move * move, char const * message, int ret) { GtkWidget * dialog; int error = errno; if(move == NULL) return _error_text(message, ret); dialog = gtk_message_dialog_new(GTK_WINDOW(move->window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif "%s: %s", message, strerror(error)); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } static int _error_text(char const * message, int ret) { fputs(PROGNAME_MOVE ": ", stderr); perror(message); return ret; } /* move_filename_confirm */ static int _move_filename_confirm(Move * move, char const * filename) { char * p; GtkWidget * dialog; int res; if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; dialog = gtk_message_dialog_new(GTK_WINDOW(move->window), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Question")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif _("%s will be overwritten\nProceed?"), filename); gtk_window_set_title(GTK_WINDOW(dialog), _("Question")); res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); free(p); return (res == GTK_RESPONSE_YES) ? 1 : 0; } /* move_filename_error */ static int _move_filename_error(Move * move, char const * filename, int ret) { char * p; int error = errno; if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; errno = error; ret = _move_error(move, filename, ret); free(p); return ret; } /* usage */ static int _usage(void) { fprintf(stderr, _("Usage: %s [-fi] source target\n" " %s [-fi] source... directory\n" " -f Do not prompt for confirmation if the destination path exists\n" " -i Prompt for confirmation if the destination path exists\n"), PROGNAME_MOVE, PROGNAME_MOVE); return 1; } /* public */ /* functions */ /* main */ int main(int argc, char * argv[]) { Prefs prefs; int o; if(setlocale(LC_ALL, "") == NULL) _move_error(NULL, "setlocale", 1); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); memset(&prefs, 0, sizeof(prefs)); gtk_init(&argc, &argv); while((o = getopt(argc, argv, "fi")) != -1) switch(o) { case 'f': prefs -= prefs & PREFS_i; prefs |= PREFS_f; break; case 'i': prefs -= prefs & PREFS_f; prefs |= PREFS_i; break; default: return _usage(); } if(argc - optind < 2) return _usage(); _move(&prefs, argc - optind, &argv[optind]); gtk_main(); return 0; }