diff --git a/src/Makefile b/src/Makefile index 6edf2f9..ec077d2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -TARGETS = browser delete desktop open properties view +TARGETS = browser delete desktop move open properties view PREFIX = /usr/local DESTDIR = BINDIR = $(PREFIX)/bin @@ -33,6 +33,12 @@ desktop_CFLAGS = $(CPPFLAGS) $(CFLAGSF) $(CFLAGS) desktop: $(desktop_OBJS) $(CC) -o desktop $(desktop_OBJS) $(LDFLAGSF) $(LDFLAGS) -L $(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib -l System mime.o +move_OBJS = move.o +move_CFLAGS = $(CPPFLAGS) $(CFLAGSF) $(CFLAGS) + +move: $(move_OBJS) + $(CC) -o move $(move_OBJS) $(LDFLAGSF) $(LDFLAGS) + open_OBJS = open.o open_CFLAGS = $(CPPFLAGS) $(CFLAGSF) $(CFLAGS) @@ -69,6 +75,9 @@ delete.o: delete.c desktop.o: desktop.c mime.o $(CC) $(desktop_CFLAGS) -c desktop.c +move.o: move.c + $(CC) $(move_CFLAGS) -c move.c + open.o: open.c mime.o $(CC) $(open_CFLAGS) -c open.c @@ -79,7 +88,7 @@ view.o: view.c mime.o $(CC) $(view_CFLAGS) -c view.c clean: - $(RM) $(browser_OBJS) $(delete_OBJS) $(desktop_OBJS) $(open_OBJS) $(properties_OBJS) $(view_OBJS) + $(RM) $(browser_OBJS) $(delete_OBJS) $(desktop_OBJS) $(move_OBJS) $(open_OBJS) $(properties_OBJS) $(view_OBJS) distclean: clean $(RM) $(TARGETS) @@ -89,6 +98,7 @@ install: all $(INSTALL) -m 0755 browser $(DESTDIR)$(BINDIR)/browser $(INSTALL) -m 0755 delete $(DESTDIR)$(BINDIR)/delete $(INSTALL) -m 0755 desktop $(DESTDIR)$(BINDIR)/desktop + $(INSTALL) -m 0755 move $(DESTDIR)$(BINDIR)/move $(INSTALL) -m 0755 open $(DESTDIR)$(BINDIR)/open $(INSTALL) -m 0755 properties $(DESTDIR)$(BINDIR)/properties $(INSTALL) -m 0755 view $(DESTDIR)$(BINDIR)/view @@ -97,6 +107,7 @@ uninstall: $(RM) $(DESTDIR)$(BINDIR)/browser $(RM) $(DESTDIR)$(BINDIR)/delete $(RM) $(DESTDIR)$(BINDIR)/desktop + $(RM) $(DESTDIR)$(BINDIR)/move $(RM) $(DESTDIR)$(BINDIR)/open $(RM) $(DESTDIR)$(BINDIR)/properties $(RM) $(DESTDIR)$(BINDIR)/view diff --git a/src/move.c b/src/move.c new file mode 100644 index 0000000..2a41923 --- /dev/null +++ b/src/move.c @@ -0,0 +1,377 @@ +/* $Id$ */ +/* Copyright (c) 2007 Pierre Pronchery */ +/* This file is part of DeforaOS Desktop Browser */ +/* Browser is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * Browser 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 + * Browser; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* types */ +typedef int Prefs; +#define PREFS_f 0x1 +#define PREFS_i 0x2 + + +/* Move */ +/* types */ +typedef struct _Move +{ + Prefs * prefs; + unsigned int filec; + char ** filev; + unsigned int cur; + GtkWidget * window; + GtkWidget * label; + GtkWidget * progress; +} Move; + +/* functions */ +static void _move_refresh(Move * move); + +/* callbacks */ +static void _move_on_closex(GtkWidget * widget, GdkEvent * event, + gpointer data); +static gboolean _move_idle_first(gpointer data); + +static int _move(Prefs * prefs, int filec, char * filev[]) +{ + static Move move; + GtkWidget * vbox; + GtkWidget * hbox; + GtkWidget * widget; + + move.prefs = prefs; + move.filec = filec; + move.filev = filev; + move.cur = 0; + move.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + 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_vbox_new(FALSE, 4); + move.label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(vbox), move.label, TRUE, TRUE, 4); + move.progress = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), move.progress, TRUE, TRUE, 4); + hbox = gtk_hbox_new(FALSE, 4); + widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL); + gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, FALSE, 4); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 4); + 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); + return 0; +} + +static void _move_refresh(Move * move) +{ + char buf[256]; + double fraction; + + snprintf(buf, sizeof(buf), "Moving file: %s", move->filev[move->cur]); + gtk_label_set_text(GTK_LABEL(move->label), buf); + snprintf(buf, sizeof(buf), "File %u of %u", move->cur, move->filec); + fraction = (double)(move->cur) / (double)move->filec; + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(move->progress), buf); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(move->progress), + fraction); +} + +static int _move_error(Move * move, char const * message, int ret) +{ + GtkWidget * dialog; + + dialog = gtk_message_dialog_new(GTK_WINDOW(move->window), + GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, "%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; +} + +static int _move_confirm(Move * move, char const * dst) +{ + int ret; + GtkWidget * dialog; + + dialog = gtk_message_dialog_new(GTK_WINDOW(move->window), + GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, "%s will be overwritten\n" + "Proceed?", dst); + gtk_window_set_title(GTK_WINDOW(dialog), "Question"); + ret = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return ret == GTK_RESPONSE_YES ? 1 : 0; +} + +static void _move_on_closex(GtkWidget * widget, GdkEvent * event, + gpointer data) +{ + 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; + struct stat st; + + if(stat(move->filev[move->filec - 1], &st) != 0) + { + if(errno != ENOENT) + _move_error(move, move->filev[move->filec - 1], 0); + else if(move->filec > 2) + { + errno = ENOTDIR; + _move_error(move, move->filev[move->filec - 1], 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_error(move, move->filev[move->filec - 1], 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); +static int _single_fifo(Move * move, char const * src, char const * dst); +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); +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(lstat(src, &st) != 0 && errno == ENOENT) /* XXX TOCTOU */ + return _move_error(move, src, 1); + if(*(move->prefs) & PREFS_i + && (lstat(dst, &st) == 0 || errno != ENOENT) + && _move_confirm(move, dst) != 1) + return 0; + if(rename(src, dst) == 0) + return 0; + if(errno != EXDEV) + return _move_error(move, src, 1); + if(unlink(dst) != 0 + && errno != ENOENT) + return _move_error(move, dst, 1); + if(lstat(src, &st) != 0) + return _move_error(move, dst, 1); + if(S_ISDIR(st.st_mode)) + ret = _single_dir(move, src, dst); + else if(S_ISFIFO(st.st_mode)) + ret = _single_fifo(move, src, dst); + 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)) /* FIXME not implemented */ + { + errno = ENOSYS; + return _move_error(move, src, 1); + } + else + ret = _single_regular(move, src, dst); + if(ret != 0) + return ret; + _single_p(move, dst, &st); + return 0; +} + +static int _single_dir(Move * move, char const * src, char const * dst) +{ + if(mkdir(dst, 0777) != 0) + return _move_error(move, dst, 1); + if(rmdir(src) != 0) /* FIXME probably gonna fail, recurse before */ + _move_error(move, src, 0); + return 0; +} + +static int _single_fifo(Move * move, char const * src, char const * dst) +{ + if(mkfifo(dst, 0666) != 0) + return _move_error(move, dst, 1); + if(unlink(src) != 0) + _move_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_error(move, dst, 1); + if(unlink(src) != 0) + _move_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_error(move, src, 1); + buf[i] = '\0'; + if(symlink(buf, dst) != 0) + return _move_error(move, dst, 1); + if(unlink(src) != 0) + _move_error(move, src, 0); + return 0; +} + +static int _single_regular(Move * move, char const * src, char const * dst) +{ + FILE * fp; + char buf[BUFSIZ]; + size_t i; + + if((fp = fopen(dst, "w+")) == NULL) + return _move_error(move, dst, 1); + while((i = fread(buf, sizeof(char), sizeof(buf), fp)) > 0) + if(fwrite(buf, sizeof(char), i, fp) != i) + break; + if(fclose(fp) != 0 + || i != 0) + return _move_error(move, dst, 1); + if(unlink(src) != 0) + _move_error(move, src, 0); + return 0; +} + +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_error(move, dst, 0); + if(chmod(dst, st->st_mode & ~(S_ISUID | S_ISGID)) != 0) + _move_error(move, dst, 0); + } + else if(chmod(dst, st->st_mode) != 0) + _move_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(utimes(dst, tv) != 0) + _move_error(move, dst, 0); + return 0; +} + +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; + + to = basename(src); /* XXX src is const */ + len = strlen(src + strlen(to) + 2); + if((p = malloc(len * sizeof(char))) == NULL) + return _move_error(move, src, 1); + sprintf(p, "%s/%s", dst, to); + ret = _move_single(move, src, p); + free(p); + return ret; +} + + +/* usage */ +static int _usage(void) +{ + fputs("Usage: move [-fi] file...\n\ + -f Do not prompt for confirmation if the destination path exists\n\ + -i Prompt for confirmation if the destination path exists\n", stderr); + return 1; +} + + +/* main */ +int main(int argc, char * argv[]) +{ + Prefs prefs; + int o; + + 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; +} diff --git a/src/project.conf b/src/project.conf index 676117b..bf21ea0 100644 --- a/src/project.conf +++ b/src/project.conf @@ -1,4 +1,4 @@ -targets=browser,delete,desktop,open,properties,view +targets=browser,delete,desktop,move,open,properties,view cflags_force=-W `pkg-config gtk+-2.0 --cflags` cflags=-Wall -g -O2 ldflags_force=`pkg-config gtk+-2.0 --libs` @@ -33,6 +33,10 @@ ldflags=-L $(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib -l System mime.o [desktop.c] depends=mime.o +[move] +type=binary +sources=move.c + [open] type=binary sources=open.c