static void vfs_file_task_link( char* src_file, VFSFileTask* task ) { struct stat src_stat; int result; gchar* dest_file; gchar* file_name = g_path_get_basename( src_file ); if ( should_abort( task ) ) return ; file_name = g_path_get_basename( src_file ); dest_file = g_build_filename( task->dest_dir, file_name, NULL ); g_free( file_name ); call_progress_callback( task ); if ( stat( src_file, &src_stat ) == -1 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } /* FIXME: Check overwrite!! */ result = symlink( src_file, dest_file ); g_free( dest_file ); if ( result ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } task->progress += src_stat.st_size; call_progress_callback( task ); }
static void vfs_file_task_do_move ( VFSFileTask* task, const char* src_file, const char* dest_file ) { gchar* new_dest_file = NULL; gboolean dest_exists; struct stat file_stat; int result; if ( should_abort( task ) ) return ; /* g_debug( "move \"%s\" to \"%s\"\n", src_file, dest_file ); */ if ( lstat( src_file, &file_stat ) == -1 ) { task->error = errno; /* Error occurred */ call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } task->current_file = src_file; task->current_dest = dest_file; if ( should_abort( task ) ) return ; call_progress_callback( task ); if ( ! check_overwrite( task, dest_file, &dest_exists, &new_dest_file ) ) return ; if ( new_dest_file ) task->current_dest = dest_file = new_dest_file; result = rename( src_file, dest_file ); if ( result != 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) { g_free( new_dest_file ); return ; } } else chmod( dest_file, file_stat.st_mode ); task->progress += file_stat.st_size; call_progress_callback( task ); g_free( new_dest_file ); }
void rendez_sleep(struct rendez *rv, int (*cond)(void*), void *arg) { int8_t irq_state = 0; struct cv_lookup_elm cle; /* Do a quick check before registering and sleeping. this is the 'check, * signal, check again' pattern, where the first check is an optimization. * Many rendezes will already be satisfied, so we want to avoid excessive * locking associated with reg/dereg. */ cv_lock_irqsave(&rv->cv, &irq_state); if (cond(arg)) { cv_unlock_irqsave(&rv->cv, &irq_state); return; } __reg_abortable_cv(&cle, &rv->cv); /* Mesa-style semantics, which is definitely what you want. See the * discussion at the end of the URL above. */ while (!cond(arg)) { /* it's okay if we miss the ABORT flag; we hold the cv lock, so an * aborter's broadcast is waiting until we unlock. */ if (should_abort(&cle)) { cv_unlock_irqsave(&rv->cv, &irq_state); dereg_abortable_cv(&cle); error(EINTR, "syscall aborted"); } cv_wait(&rv->cv); cpu_relax(); } cv_unlock_irqsave(&rv->cv, &irq_state); dereg_abortable_cv(&cle); }
static void vfs_file_task_move( char* src_file, VFSFileTask* task ) { struct stat src_stat; struct stat dest_stat; gchar* file_name; gchar* dest_file; GKeyFile* kf; /* for trash info */ int tmpfd = -1; if ( should_abort( task ) ) return ; task->current_file = src_file; file_name = g_path_get_basename( src_file ); if( task->type == VFS_FILE_TASK_TRASH ) { dest_file = g_strconcat( task->dest_dir, "/", file_name, "XXXXXX", NULL ); tmpfd = mkstemp( dest_file ); if( tmpfd < 0 ) { goto on_error; } g_debug( dest_file ); } else dest_file = g_build_filename( task->dest_dir, file_name, NULL ); g_free( file_name ); if ( lstat( src_file, &src_stat ) == 0 && lstat( task->dest_dir, &dest_stat ) == 0 ) { /* Not on the same device */ if ( src_stat.st_dev != dest_stat.st_dev ) { /* g_print("not on the same dev: %s\n", src_file); */ vfs_file_task_do_copy( task, src_file, dest_file ); } else { /* g_print("on the same dev: %s\n", src_file); */ vfs_file_task_do_move( task, src_file, dest_file ); } } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } on_error: if( tmpfd >= 0 ) { close( tmpfd ); unlink( dest_file ); } g_free( dest_file ); }
/* * void get_total_size_of_dir( const char* path, off_t* size ) * Recursively count total size of all files in the specified directory. * If the path specified is a file, the size of the file is directly returned. * cancel is used to cancel the operation. This function will check the value * pointed by cancel in every iteration. If cancel is set to TRUE, the * calculation is cancelled. * NOTE: *size should be set to zero before calling this function. */ void get_total_size_of_dir( VFSFileTask* task, const char* path, off_t* size ) { GDir * dir; const char* name; char* full_path; struct stat file_stat; if ( should_abort( task ) ) return; lstat( path, &file_stat ); *size += file_stat.st_size; if ( S_ISLNK( file_stat.st_mode ) ) /* Don't follow symlinks */ return ; dir = g_dir_open( path, 0, NULL ); if ( dir ) { while ( (name = g_dir_read_name( dir )) ) { if ( should_abort( task ) ) break; full_path = g_build_filename( path, name, NULL ); lstat( full_path, &file_stat ); if ( S_ISDIR( file_stat.st_mode ) ) { get_total_size_of_dir( task, full_path, size ); } else { *size += file_stat.st_size; } g_free( full_path ); } g_dir_close( dir ); } }
/* Like sleep, but it will timeout in 'usec' microseconds. */ void rendez_sleep_timeout(struct rendez *rv, int (*cond)(void*), void *arg, uint64_t usec) { int8_t irq_state = 0; struct alarm_waiter awaiter; struct cv_lookup_elm cle; struct timer_chain *pcpui_tchain = &per_cpu_info[core_id()].tchain; if (!usec) return; /* Doing this cond check early, but then unlocking again. Mostly just to * avoid weird issues with the CV lock and the alarm tchain lock. */ cv_lock_irqsave(&rv->cv, &irq_state); if (cond(arg)) { cv_unlock_irqsave(&rv->cv, &irq_state); return; } cv_unlock_irqsave(&rv->cv, &irq_state); /* The handler will call rendez_wake, but won't mess with the condition * state. It's enough to break us out of cv_wait() to see .on_tchain. */ init_awaiter(&awaiter, rendez_alarm_handler); awaiter.data = rv; set_awaiter_rel(&awaiter, usec); /* Set our alarm on this cpu's tchain. Note that when we sleep in cv_wait, * we could be migrated, and later on we could be unsetting the alarm * remotely. */ set_alarm(pcpui_tchain, &awaiter); cv_lock_irqsave(&rv->cv, &irq_state); __reg_abortable_cv(&cle, &rv->cv); /* We could wake early for a few reasons. Legit wakeups after a changed * condition (and we should exit), other alarms with different timeouts (and * we should go back to sleep), etc. Note it is possible for our alarm to * fire immediately upon setting it: before we even cv_lock. */ while (!cond(arg) && awaiter.on_tchain) { if (should_abort(&cle)) { cv_unlock_irqsave(&rv->cv, &irq_state); unset_alarm(pcpui_tchain, &awaiter); dereg_abortable_cv(&cle); error(EINTR, "syscall aborted"); } cv_wait(&rv->cv); cpu_relax(); } cv_unlock_irqsave(&rv->cv, &irq_state); dereg_abortable_cv(&cle); /* Turn off our alarm. If it already fired, this is a no-op. Note this * could be cross-core. */ unset_alarm(pcpui_tchain, &awaiter); }
static gpointer vfs_file_task_thread ( VFSFileTask* task ) { GList * l; struct stat file_stat; dev_t dest_dev = 0; GFunc funcs[] = {( GFunc ) vfs_file_task_move, ( GFunc ) vfs_file_task_copy, ( GFunc ) vfs_file_task_move, /* trash */ ( GFunc ) vfs_file_task_delete, ( GFunc ) vfs_file_task_link, ( GFunc ) vfs_file_task_chown_chmod}; if ( task->type < VFS_FILE_TASK_MOVE || task->type >= VFS_FILE_TASK_LAST ) goto _exit_thread; task->state = VFS_FILE_TASK_RUNNING; task->current_file = ( char* ) task->src_paths->data; task->total_size = 0; if( task->type == VFS_FILE_TASK_TRASH ) { task->dest_dir = g_build_filename( vfs_get_trash_dir(), "files", NULL ); /* Create the trash dir if it doesn't exist */ if( ! g_file_test( task->dest_dir, G_FILE_TEST_EXISTS ) ) g_mkdir_with_parents( task->dest_dir, 0700 ); } call_progress_callback( task ); /* Calculate total size of all files */ if ( task->recursive ) { for ( l = task->src_paths; l; l = l->next ) { if( task->dest_dir && g_str_has_prefix( task->dest_dir, (char*)l->data ) ) { /* FIXME: This has serious problems */ /* Check if source file is contained in destination dir */ /* The src file is already in dest dir */ if( lstat( (char*)l->data, &file_stat ) == 0 && S_ISDIR(file_stat.st_mode) ) { /* It's a dir */ if ( task->state_cb ) { char* err; char* disp_src; char* disp_dest; disp_src = g_filename_display_name( (char*)l->data ); disp_dest = g_filename_display_name( task->dest_dir ); err = g_strdup_printf( _("Destination directory \"%1$s\" is contained in source \"%2$s\""), disp_dest, disp_src ); if ( task->state_cb( task, VFS_FILE_TASK_ERROR, err, task->state_cb_data ) ) task->state = VFS_FILE_TASK_RUNNING; else task->state = VFS_FILE_TASK_ABORTED; g_free( disp_src ); g_free( disp_dest ); g_free( err ); } else task->state = VFS_FILE_TASK_ABORTED; if ( should_abort( task ) ) goto _exit_thread; } } get_total_size_of_dir( task, ( char* ) l->data, &task->total_size ); } } else { if ( task->type != VFS_FILE_TASK_CHMOD_CHOWN ) { if ( task->dest_dir && lstat( task->dest_dir, &file_stat ) < 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) goto _exit_thread; } dest_dev = file_stat.st_dev; } for ( l = task->src_paths; l; l = l->next ) { if ( lstat( ( char* ) l->data, &file_stat ) == -1 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) goto _exit_thread; } if ( S_ISLNK( file_stat.st_mode ) ) /* Don't do deep copy for symlinks */ task->recursive = FALSE; else if ( task->type == VFS_FILE_TASK_MOVE || task->type == VFS_FILE_TASK_TRASH ) task->recursive = ( file_stat.st_dev != dest_dev ); if ( task->recursive ) { get_total_size_of_dir( task, ( char* ) l->data, &task->total_size ); } else { task->total_size += file_stat.st_size; } } } g_list_foreach( task->src_paths, funcs[ task->type ], task ); _exit_thread: if ( task->state_cb ) call_state_callback( task, VFS_FILE_TASK_FINISH ); else { /* FIXME: Will this cause any problem? */ task->thread = NULL; vfs_file_task_free( task ); } return NULL; }
static void vfs_file_task_chown_chmod( char* src_file, VFSFileTask* task ) { struct stat src_stat; int i; GDir* dir; gchar* sub_src_file; const gchar* file_name; mode_t new_mode; int result; if( should_abort( task ) ) return ; task->current_file = src_file; /* g_debug("chmod_chown: %s\n", src_file); */ call_progress_callback( task ); if ( lstat( src_file, &src_stat ) == 0 ) { /* chown */ if ( task->uid != -1 || task->gid != -1 ) { result = chown( src_file, task->uid, task->gid ); if ( result != 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } } /* chmod */ if ( task->chmod_actions ) { new_mode = src_stat.st_mode; for ( i = 0; i < N_CHMOD_ACTIONS; ++i ) { if ( task->chmod_actions[ i ] == 2 ) /* Don't change */ continue; if ( task->chmod_actions[ i ] == 0 ) /* Remove this bit */ new_mode &= ~chmod_flags[ i ]; else /* Add this bit */ new_mode |= chmod_flags[ i ]; } if ( new_mode != src_stat.st_mode ) { result = chmod( src_file, new_mode ); if ( result != 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } } } task->progress += src_stat.st_size; call_progress_callback( task ); if ( S_ISDIR( src_stat.st_mode ) && task->recursive ) { if ( (dir = g_dir_open( src_file, 0, NULL )) ) { while ( (file_name = g_dir_read_name( dir )) ) { if ( should_abort( task ) ) break; sub_src_file = g_build_filename( src_file, file_name, NULL ); vfs_file_task_chown_chmod( sub_src_file, task ); g_free( sub_src_file ); } g_dir_close( dir ); } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } } } }
static void vfs_file_task_delete( char* src_file, VFSFileTask* task ) { GDir * dir; const gchar* file_name; gchar* sub_src_file; struct stat file_stat; int result; task->current_file = src_file; if ( should_abort( task ) ) return ; if ( lstat( src_file, &file_stat ) == -1 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); return ; /* Error occurred */ } task->current_file = src_file; call_progress_callback( task ); if ( S_ISDIR( file_stat.st_mode ) ) { dir = g_dir_open( src_file, 0, NULL ); while ( (file_name = g_dir_read_name( dir )) ) { if ( should_abort( task ) ) break; sub_src_file = g_build_filename( src_file, file_name, NULL ); vfs_file_task_delete( sub_src_file, task ); g_free( sub_src_file ); } g_dir_close( dir ); if ( should_abort( task ) ) return ; result = rmdir( src_file ); if ( result != 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); return ; } task->progress += file_stat.st_size; call_progress_callback( task ); } else { result = unlink( src_file ); if ( result != 0 ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); return ; } task->progress += file_stat.st_size; call_progress_callback( task ); } }
static void vfs_file_task_do_copy( VFSFileTask* task, const char* src_file, const char* dest_file ) { GDir * dir; const gchar* file_name; gchar* sub_src_file; gchar* sub_dest_file; struct stat file_stat; char buffer[ 4096 ]; int rfd; int wfd; ssize_t rsize; char* new_dest_file = NULL; gboolean dest_exists; int result; if ( should_abort( task ) ) return ; if ( lstat( src_file, &file_stat ) == -1 ) { /* Error occurred */ call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) return ; } task->current_file = src_file; task->current_dest = dest_file; call_progress_callback( task ); result = 0; if ( S_ISDIR( file_stat.st_mode ) ) { if ( ! check_overwrite( task, dest_file, &dest_exists, &new_dest_file ) ) goto _return_; if ( new_dest_file ) task->current_dest = dest_file = new_dest_file; if ( ! dest_exists ) result = mkdir( dest_file, file_stat.st_mode | 0700 ); if ( result == 0 ) { struct utimbuf times; task->progress += file_stat.st_size; call_progress_callback( task ); dir = g_dir_open( src_file, 0, NULL ); while ( (file_name = g_dir_read_name( dir )) ) { if ( should_abort( task ) ) break; sub_src_file = g_build_filename( src_file, file_name, NULL ); sub_dest_file = g_build_filename( dest_file, file_name, NULL ); vfs_file_task_do_copy( task, sub_src_file, sub_dest_file ); g_free( sub_dest_file ); g_free( sub_src_file ); } g_dir_close( dir ); chmod( dest_file, file_stat.st_mode ); times.actime = file_stat.st_atime; times.modtime = file_stat.st_mtime; utime( dest_file, × ); /* Move files to different device: Need to delete source files */ if ( ( task->type == VFS_FILE_TASK_MOVE || task->type == VFS_FILE_TASK_TRASH ) && !should_abort( task ) ) { if ( (result = rmdir( src_file )) ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); if ( should_abort( task ) ) goto _return_; } } } else { /* result != 0, error occurred */ task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } else if ( S_ISLNK( file_stat.st_mode ) ) { if ( ( rfd = readlink( src_file, buffer, sizeof( buffer ) ) ) > 0 ) { if ( ! check_overwrite( task, dest_file, &dest_exists, &new_dest_file ) ) goto _return_; if ( new_dest_file ) task->current_dest = dest_file = new_dest_file; if ( ( wfd = symlink( buffer, dest_file ) ) == 0 ) { chmod( dest_file, file_stat.st_mode ); /* Move files to different device: Need to delete source files */ if( task->type == VFS_FILE_TASK_MOVE || task->type == VFS_FILE_TASK_TRASH ) { result = unlink( src_file ); if ( result ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } task->progress += file_stat.st_size; call_progress_callback( task ); } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } else { if ( ( rfd = open( src_file, O_RDONLY ) ) >= 0 ) { if ( ! check_overwrite( task, dest_file, &dest_exists, &new_dest_file ) ) goto _return_; if ( new_dest_file ) task->current_dest = dest_file = new_dest_file; if ( ( wfd = creat( dest_file, file_stat.st_mode | S_IWUSR ) ) >= 0 ) { struct utimbuf times; while ( ( rsize = read( rfd, buffer, sizeof( buffer ) ) ) > 0 ) { if ( should_abort( task ) ) break; if ( write( wfd, buffer, rsize ) > 0 ) task->progress += rsize; else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); close( wfd ); } call_progress_callback( task ); } close( wfd ); chmod( dest_file, file_stat.st_mode ); times.actime = file_stat.st_atime; times.modtime = file_stat.st_mtime; utime( dest_file, × ); /* Move files to different device: Need to delete source files */ if ( (task->type == VFS_FILE_TASK_MOVE || task->type == VFS_FILE_TASK_TRASH) && !should_abort( task ) ) { result = unlink( src_file ); if ( result ) { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } close( rfd ); } else { task->error = errno; call_state_callback( task, VFS_FILE_TASK_ERROR ); } } _return_: g_free( new_dest_file ); }
/* * Check if the destination file exists. * If the dest_file exists, let the user choose a new destination, * skip this file, overwrite existing file, or cancel all file operation. * The returned string is the new destination file choosed by the user */ static gboolean check_overwrite( VFSFileTask* task, const gchar* dest_file, gboolean* dest_exists, char** new_dest_file ) { char * new_dest; new_dest = *new_dest_file = NULL; struct stat dest_stat; if ( task->overwrite_mode == VFS_FILE_TASK_OVERWRITE_ALL ) { *dest_exists = !lstat( dest_file, &dest_stat ); return TRUE; } if ( task->overwrite_mode == VFS_FILE_TASK_SKIP_ALL ) { *dest_exists = !lstat( dest_file, &dest_stat ); return FALSE; } *dest_exists = FALSE; if ( task->state_cb ) { while ( lstat( dest_file, &dest_stat ) != -1 ) { *dest_exists = TRUE; /* destination file exists */ task->state = VFS_FILE_TASK_QUERY_OVERWRITE; if ( ! task->state_cb( task, VFS_FILE_TASK_QUERY_OVERWRITE, &new_dest, task->state_cb_data ) ) { task->state = VFS_FILE_TASK_ABORTED; } else { task->state = VFS_FILE_TASK_RUNNING; } if ( should_abort( task ) ) return FALSE; if( task->overwrite_mode != VFS_FILE_TASK_RENAME ) { g_free( new_dest ); *new_dest_file = NULL; if( task->overwrite_mode == VFS_FILE_TASK_OVERWRITE || task->overwrite_mode == VFS_FILE_TASK_OVERWRITE_ALL ) { return TRUE; } else return FALSE; } if ( new_dest ) dest_file = new_dest; } *dest_exists = FALSE; *new_dest_file = new_dest; } return ! should_abort( task ); }