Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,11 @@ debian/files
*.debhelper

obj-x86_64-linux-gnu

# Ignore additional dev environment files
.vscode/
.clang-format
.clangd

#Ignore .md files with "MY_" prefix
MY_*.md
1 change: 1 addition & 0 deletions libnemo-private/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ nemo_private_sources = [
'nemo-vfs-file.c',
'nemo-widget-action.c',
'nemo-widget-menu-item.c',
'nemo-gfile.c'
]

nemo_private_deps = [
Expand Down
223 changes: 203 additions & 20 deletions libnemo-private/nemo-file-operations.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <mntent.h>

#include "nemo-file-operations.h"

Expand Down Expand Up @@ -70,6 +73,7 @@
#include "nemo-file-undo-operations.h"
#include "nemo-file-undo-manager.h"
#include "nemo-job-queue.h"
#include "nemo-gfile.h"

/* TODO: TESTING!!! */

Expand Down Expand Up @@ -186,6 +190,13 @@ typedef struct {
int last_reported_files_left;
} TransferInfo;

typedef struct {
CopyMoveJob *job;
SourceInfo *source_info;
TransferInfo *transfer_info;
goffset last_size;
} ProgressData;

#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
#define US_PER_MS 1000
#define PROGRESS_UPDATE_THRESHOLD 250
Expand Down Expand Up @@ -4174,13 +4185,6 @@ remove_target_recursively (CommonJob *job,

}

typedef struct {
CopyMoveJob *job;
goffset last_size;
SourceInfo *source_info;
TransferInfo *transfer_info;
} ProgressData;

static void
copy_file_progress_callback (goffset current_num_bytes,
goffset total_num_bytes,
Expand Down Expand Up @@ -4397,6 +4401,160 @@ get_target_file_for_display_name (GFile *dir,
return dest;
}

/* Determine if a given source file is a regular file e.g. no link and
* if the target device is a consumer grade USB block device */
static gboolean
is_regular_gfile_and_dest_is_consumer_usb_blk_device (GFile *src, GFile *dest)
{
/* 1. Check if src is a regular file */
GFileType type =
g_file_query_file_type (src, G_FILE_QUERY_INFO_NONE, NULL);
if (type != G_FILE_TYPE_REGULAR) {
return FALSE;
}

/* 2. Get the path of dest */
gchar *dest_path = g_file_get_path (dest);
if (!dest_path) {
return FALSE;
}

/* 3. Find the longest matching mount entry for dest_path */
FILE *mounts = setmntent ("/proc/mounts", "r");
if (!mounts) {
g_free (dest_path);
return FALSE;
}

struct mntent *ent;
gchar *best_mnt_fsname = NULL;
size_t best_mnt_len = 0;

while ((ent = getmntent (mounts)) != NULL) {
size_t mnt_len = strlen (ent->mnt_dir);

/* mount point must be a prefix of dest_path and either
* be the root "/" or be followed by "/" or end of string
* to avoid partial directory name matches */
if (strncmp (dest_path, ent->mnt_dir, mnt_len) == 0 &&
(dest_path[mnt_len] == '/' || dest_path[mnt_len] == '\0') &&
mnt_len > best_mnt_len) {
struct stat st;
if (stat (ent->mnt_fsname, &st) == 0 &&
S_ISBLK (st.st_mode)) {
g_free (best_mnt_fsname);
best_mnt_fsname = g_strdup (ent->mnt_fsname);
best_mnt_len = mnt_len;
}
}
}
endmntent (mounts);
g_free (dest_path);

if (!best_mnt_fsname) {
return FALSE;
}

/* 4. Get major:minor of the block device */
struct stat dev_st;
if (stat (best_mnt_fsname, &dev_st) != 0) {
g_free (best_mnt_fsname);
return FALSE;
}
g_free (best_mnt_fsname);

unsigned int maj = major (dev_st.st_rdev);
unsigned int min = minor (dev_st.st_rdev);

/* 5. Resolve the sysfs path for this device via /sys/dev/block/maj:min */
gchar *sys_block_link =
g_strdup_printf ("/sys/dev/block/%u:%u", maj, min);
char resolved_buf[PATH_MAX];
gchar *resolved = realpath (sys_block_link, resolved_buf);
g_free (sys_block_link);

if (!resolved) {
return FALSE;
}

/* resolved now points to resolved_buf — no need to free */
gchar *disk_sys_path;

/* 6. If this is a partition, go up one level to the whole disk */
gchar *partition_file =
g_build_filename (resolved_buf, "partition", NULL);
if (g_file_test (partition_file, G_FILE_TEST_EXISTS)) {
disk_sys_path = g_path_get_dirname (resolved_buf);
} else {
disk_sys_path = g_strdup (resolved_buf);
}
g_free (partition_file);

/* 7. Check removable flag */
gchar *removable_path =
g_build_filename (disk_sys_path, "removable", NULL);
gchar *removable_str = NULL;
gboolean is_removable = FALSE;

if (g_file_get_contents (removable_path, &removable_str, NULL, NULL)) {
is_removable = (removable_str[0] == '1');
g_free (removable_str);
}
g_free (removable_path);

if (!is_removable) {
g_free (disk_sys_path);
return FALSE;
}

/* 8. Walk up the sysfs tree looking for a 'subsystem' symlink
* that resolves to 'usb' */
gboolean is_usb = FALSE;
gchar *curr_path = g_strdup (disk_sys_path);
g_free (disk_sys_path);

while (curr_path != NULL && strlen (curr_path) > strlen ("/sys")) {
gchar *subsystem_path =
g_build_filename (curr_path, "subsystem", NULL);
char sub_resolved_buf[PATH_MAX];
gchar *sub_resolved =
realpath (subsystem_path, sub_resolved_buf);
g_free (subsystem_path);

if (sub_resolved) {
gchar *subsystem_name =
g_path_get_basename (sub_resolved_buf);

if (g_str_equal (subsystem_name, "usb")) {
is_usb = TRUE;
g_free (subsystem_name);
g_free (curr_path);
curr_path = NULL;
break;
}
g_free (subsystem_name);
}

gchar *parent_dir = g_path_get_dirname (curr_path);

if (g_str_equal (parent_dir, curr_path)) {
g_free (parent_dir);
g_free (curr_path);
curr_path = NULL;
break;
}

g_free (curr_path);
curr_path = parent_dir;
}

if (curr_path) {
g_free (curr_path);
}

return is_usb;
}

/* Debuting files is non-NULL only for toplevel items */
static void
copy_move_file (CopyMoveJob *copy_job,
Expand Down Expand Up @@ -4549,20 +4707,45 @@ copy_move_file (CopyMoveJob *copy_job,
pdata.source_info = source_info;
pdata.transfer_info = transfer_info;

if (copy_job->is_move) {
res = g_file_move (src, dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
if (is_regular_gfile_and_dest_is_consumer_usb_blk_device (src, dest)) {
if (copy_job->is_move) {
res = nemo_g_file_move_to_blk_sync (
src,
dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
} else {
res = nemo_g_file_copy_to_blk_sync (
src,
dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
}
} else {
res = g_file_copy (src, dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
// Use GIO's copy and move file operations (uses streams/pipes with splice)
if (copy_job->is_move) {
res = g_file_move (src,
dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
} else {
res = g_file_copy (src,
dest,
flags,
job->cancellable,
copy_file_progress_callback,
&pdata,
&error);
}
}

if (res) {
Expand Down
Loading