Skip to content
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