2021-04-03 11:58:30 +02:00
|
|
|
#include "config.h"
|
|
|
|
#ifndef FXLINK_DISABLE_UDISKS2
|
|
|
|
|
|
|
|
#include "ud2.h"
|
|
|
|
#include "fxlink.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "properties.h"
|
|
|
|
#include "filter.h"
|
|
|
|
|
|
|
|
#include <udisks/udisks.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
|
|
|
//---
|
|
|
|
// UDisks2 utility functions
|
|
|
|
//---
|
|
|
|
|
|
|
|
int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
UDisksClient *udc = udisks_client_new_sync(NULL, &error);
|
|
|
|
if(error)
|
|
|
|
return err("cannot open udisks2 client: %s", error->message);
|
|
|
|
|
|
|
|
UDisksManager *udm = udisks_client_get_manager(udc);
|
|
|
|
if(!udm) {
|
|
|
|
g_object_unref(udc);
|
|
|
|
return err("udisks2 daemon does not seem to be running");
|
|
|
|
}
|
|
|
|
|
|
|
|
*udc_ptr = udc;
|
|
|
|
*udm_ptr = udm;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ud2_end(UDisksClient *udc, __attribute__((unused)) UDisksManager *udm)
|
|
|
|
{
|
|
|
|
g_object_unref(udc);
|
|
|
|
}
|
|
|
|
|
|
|
|
gchar **ud2_block_devices(UDisksManager *udm)
|
|
|
|
{
|
|
|
|
gchar **blocks = NULL;
|
|
|
|
GVariant *args = g_variant_new("a{sv}", NULL);
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
udisks_manager_call_get_block_devices_sync(udm,args,&blocks,NULL,&error);
|
|
|
|
if(error) {
|
|
|
|
err("cannot list udisks2 block devices: %s", error->message);
|
|
|
|
}
|
|
|
|
|
|
|
|
return blocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
UDisksBlock *ud2_block(UDisksClient *udc, gchar const *name)
|
|
|
|
{
|
|
|
|
UDisksObject *obj = udisks_client_get_object(udc, name);
|
|
|
|
return obj ? udisks_object_get_block(obj) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UDisksDrive *ud2_drive(UDisksClient *udc, gchar const *name)
|
|
|
|
{
|
|
|
|
UDisksObject *obj = udisks_client_get_object(udc, name);
|
|
|
|
return obj ? udisks_object_get_drive(obj) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UDisksFilesystem *ud2_filesystem(UDisksClient *udc, gchar const *name)
|
|
|
|
{
|
|
|
|
UDisksObject *obj = udisks_client_get_object(udc, name);
|
|
|
|
return obj ? udisks_object_get_filesystem(obj) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---
|
|
|
|
// Matching and properties
|
|
|
|
//---
|
|
|
|
|
|
|
|
bool is_casio_drive(UDisksDrive *drive)
|
|
|
|
{
|
|
|
|
return strstr(udisks_drive_get_vendor(drive), "CASIO") != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
properties_t ud2_properties(UDisksDrive *drive)
|
|
|
|
{
|
|
|
|
properties_t props = { 0 };
|
|
|
|
props.p7 = false;
|
|
|
|
props.mass_storage = true;
|
|
|
|
|
|
|
|
if(!strcmp(udisks_drive_get_model(drive), "ColorGraph"))
|
|
|
|
props.series_cg = true;
|
|
|
|
else if(!strcmp(udisks_drive_get_model(drive), "Calculator"))
|
|
|
|
props.series_g3 = true;
|
|
|
|
|
|
|
|
gchar const *s = udisks_drive_get_serial(drive);
|
|
|
|
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
|
|
|
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s+= 4;
|
|
|
|
props.serial_number = (char *)s;
|
|
|
|
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
|
|
|
UDisksManager *udm, UDisksBlock **block_ptr, UDisksDrive **drive_ptr,
|
|
|
|
UDisksFilesystem **fs_ptr)
|
|
|
|
{
|
|
|
|
int status = FILTER_NONE;
|
|
|
|
bool error;
|
|
|
|
|
|
|
|
UDisksBlock *block = NULL;
|
|
|
|
UDisksDrive *drive = NULL;
|
|
|
|
UDisksFilesystem *fs = NULL;
|
|
|
|
|
|
|
|
for_udisks2_devices(it, udc, udm, &error) {
|
|
|
|
if(!filter_match(&it.props, filter)) continue;
|
|
|
|
|
|
|
|
/* Already found a device before */
|
|
|
|
if(status == FILTER_UNIQUE) {
|
|
|
|
status = FILTER_MULTIPLE;
|
|
|
|
g_object_unref(fs);
|
|
|
|
g_object_unref(drive);
|
|
|
|
g_object_unref(block);
|
|
|
|
block = NULL;
|
|
|
|
drive = NULL;
|
|
|
|
fs = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First device: record it */
|
|
|
|
block = g_object_ref(it.block);
|
|
|
|
drive = g_object_ref(it.drive);
|
|
|
|
fs = g_object_ref(it.fs);
|
|
|
|
status = FILTER_UNIQUE;
|
|
|
|
}
|
|
|
|
if(error)
|
|
|
|
return FILTER_ERROR;
|
|
|
|
|
|
|
|
if(block_ptr) *block_ptr = block;
|
|
|
|
else g_object_unref(block);
|
|
|
|
|
|
|
|
if(drive_ptr) *drive_ptr = drive;
|
|
|
|
else g_object_unref(drive);
|
|
|
|
|
|
|
|
if(fs_ptr) *fs_ptr = fs;
|
|
|
|
else g_object_unref(fs);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
|
|
|
|
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
|
|
|
UDisksFilesystem **fs)
|
|
|
|
{
|
|
|
|
while(true) {
|
|
|
|
int rc = ud2_unique_matching(filter, udc, udm, block, drive, fs);
|
|
|
|
if(rc != FILTER_NONE) return rc;
|
|
|
|
if(delay_cycle(delay)) return FILTER_NONE;
|
|
|
|
udisks_client_settle(udc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---
|
|
|
|
// Iteration on UDisks2 devices
|
|
|
|
//---
|
|
|
|
|
|
|
|
ud2_iterator_t ud2_iter_start(UDisksClient *udc, UDisksManager *udm,
|
|
|
|
bool *error)
|
|
|
|
{
|
|
|
|
ud2_iterator_t it = { .udc = udc };
|
|
|
|
|
|
|
|
it.devices = ud2_block_devices(udm);
|
|
|
|
if(!it.devices) {
|
|
|
|
it.done = true;
|
|
|
|
if(error) *error = true;
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
|
|
|
|
it.index = -1;
|
|
|
|
ud2_iter_next(&it);
|
|
|
|
if(error) *error = false;
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ud2_iter_next(ud2_iterator_t *it)
|
|
|
|
{
|
|
|
|
if(it->done == true) return;
|
|
|
|
|
|
|
|
/* Free the resources from the previous iteration */
|
|
|
|
if(it->fs) g_object_unref(it->fs);
|
|
|
|
if(it->drive) g_object_unref(it->drive);
|
|
|
|
if(it->block) g_object_unref(it->block);
|
|
|
|
it->block = NULL;
|
|
|
|
it->drive = NULL;
|
|
|
|
it->fs = NULL;
|
|
|
|
|
|
|
|
/* Load the next device */
|
|
|
|
if(!it->devices[++it->index]) {
|
|
|
|
it->done = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
gchar const *path = it->devices[it->index];
|
|
|
|
|
|
|
|
it->block = ud2_block(it->udc, path);
|
|
|
|
if(!it->block) return ud2_iter_next(it);
|
|
|
|
|
|
|
|
/* Skip non-CASIO devices right away */
|
|
|
|
it->drive = ud2_drive(it->udc, udisks_block_get_drive(it->block));
|
|
|
|
if(!it->drive || !is_casio_drive(it->drive))
|
|
|
|
return ud2_iter_next(it);
|
|
|
|
|
|
|
|
/* Only consider file systems (not partition tables) */
|
|
|
|
it->fs = ud2_filesystem(it->udc, path);
|
|
|
|
if(!it->fs) return ud2_iter_next(it);
|
|
|
|
|
|
|
|
it->props = ud2_properties(it->drive);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(it->done)
|
|
|
|
g_strfreev(it->devices);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---
|
|
|
|
// Main functions
|
|
|
|
//---
|
|
|
|
|
|
|
|
int main_blocks(filter_t *filter, delay_t *delay)
|
|
|
|
{
|
|
|
|
filter_clean_udisks2(filter);
|
|
|
|
|
|
|
|
UDisksClient *udc = NULL;
|
|
|
|
UDisksManager *udm = NULL;
|
|
|
|
if(ud2_start(&udc, &udm)) return 1;
|
|
|
|
|
|
|
|
ud2_unique_wait(filter, delay, udc, udm, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
int total_devices = 0;
|
|
|
|
bool error;
|
|
|
|
|
|
|
|
for_udisks2_devices(it, udc, udm, &error) {
|
|
|
|
if(!filter_match(&it.props, filter)) continue;
|
|
|
|
|
|
|
|
if(total_devices > 0) printf("\n");
|
|
|
|
|
|
|
|
if(it.props.series_cg)
|
|
|
|
printf("fx-CG series USB Mass Storage filesystem\n");
|
|
|
|
else if(it.props.series_g3)
|
|
|
|
printf("G-III series USB Mass Storage filesystem\n");
|
|
|
|
else
|
|
|
|
printf("Unknown USB Mass Storage filesystem\n");
|
|
|
|
|
|
|
|
printf(" Block device: %s\n",
|
|
|
|
udisks_block_get_device(it.block));
|
|
|
|
printf(" Identification: Vendor: %s, Model: %s\n",
|
|
|
|
udisks_drive_get_vendor(it.drive),
|
|
|
|
udisks_drive_get_model(it.drive));
|
|
|
|
|
|
|
|
if(it.props.serial_number)
|
|
|
|
printf(" Serial number: %s\n", it.props.serial_number);
|
|
|
|
|
|
|
|
gchar const * const * mount_points =
|
|
|
|
udisks_filesystem_get_mount_points(it.fs);
|
|
|
|
if(!mount_points || !mount_points[0]) {
|
|
|
|
printf(" Mounted: no\n");
|
|
|
|
}
|
|
|
|
else for(int i = 0; mount_points[i]; i++) {
|
|
|
|
printf(" Mounted at: %s\n", mount_points[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf(" Properties: ");
|
|
|
|
properties_print(stdout, &it.props);
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
total_devices++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!error && !total_devices)
|
|
|
|
printf("No%s device found.\n", filter ? " matching" : "");
|
|
|
|
|
|
|
|
ud2_end(udc, udm);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main_send(filter_t *filter, delay_t *delay, char **files)
|
|
|
|
{
|
|
|
|
filter_clean_udisks2(filter);
|
|
|
|
GError *error = NULL;
|
|
|
|
char **argv = NULL;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
UDisksClient *udc = NULL;
|
|
|
|
UDisksManager *udm = NULL;
|
|
|
|
if(ud2_start(&udc, &udm)) return 1;
|
|
|
|
|
|
|
|
UDisksBlock *block = NULL;
|
|
|
|
UDisksDrive *drive = NULL;
|
|
|
|
UDisksFilesystem *fs = NULL;
|
|
|
|
rc = ud2_unique_wait(filter, delay, udc, udm, &block, &drive, &fs);
|
|
|
|
if(rc != FILTER_UNIQUE) {
|
|
|
|
rc = 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine a mount folder, mounting the volume if needed */
|
|
|
|
gchar *folder = NULL;
|
|
|
|
bool mounted_here = false;
|
|
|
|
|
|
|
|
gchar const *dev = udisks_block_get_device(block);
|
|
|
|
gchar const * const * mount_points =
|
|
|
|
udisks_filesystem_get_mount_points(fs);
|
|
|
|
|
|
|
|
if(!mount_points || !mount_points[0]) {
|
|
|
|
GVariant *args = g_variant_new("a{sv}", NULL);
|
|
|
|
udisks_filesystem_call_mount_sync(fs, args, &folder, NULL, &error);
|
|
|
|
if(error) {
|
|
|
|
rc = err("cannot mount %s: %s", dev, error->message);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
printf("Mounted %s to %s.\n", dev, folder);
|
|
|
|
mounted_here = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
folder = strdup(mount_points[0]);
|
|
|
|
printf("Already mounted at %s.\n", folder);
|
|
|
|
mounted_here = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy files with external cp(1) */
|
|
|
|
int file_count = 0;
|
|
|
|
while(files[file_count]) file_count++;
|
|
|
|
|
|
|
|
argv = malloc((file_count + 3) * sizeof *argv);
|
|
|
|
if(!argv) {
|
|
|
|
rc = err("cannot allocate argv array for cp(1)");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
argv[0] = "cp";
|
|
|
|
for(int i = 0; files[i]; i++)
|
|
|
|
argv[i+1] = files[i];
|
|
|
|
argv[file_count+1] = folder;
|
|
|
|
argv[file_count+2] = NULL;
|
|
|
|
|
|
|
|
/* Print command */
|
|
|
|
printf("Running cp");
|
|
|
|
for(int i = 1; argv[i]; i++) printf(" '%s'", argv[i]);
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
|
|
|
if(pid == 0) {
|
|
|
|
execvp("cp", argv);
|
|
|
|
}
|
|
|
|
else if(pid == -1) {
|
|
|
|
rc = err("failed to fork to invoke cp");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else {
|
2022-08-03 22:49:42 +02:00
|
|
|
int wstatus;
|
|
|
|
waitpid(pid, &wstatus, 0);
|
|
|
|
|
|
|
|
if(!WIFEXITED(wstatus))
|
|
|
|
err("process did not terminate normally");
|
|
|
|
else if(WEXITSTATUS(wstatus) != 0) {
|
|
|
|
err("process terminated with error %d",
|
|
|
|
WEXITSTATUS(wstatus));
|
|
|
|
}
|
2021-04-03 11:58:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Unmount the filesystem and eject the device if we mounted it */
|
2022-05-01 17:20:47 +02:00
|
|
|
if(mounted_here || options.force_unmount) {
|
2021-04-03 11:58:30 +02:00
|
|
|
GVariant *args = g_variant_new("a{sv}", NULL);
|
|
|
|
udisks_filesystem_call_unmount_sync(fs, args, NULL, &error);
|
|
|
|
if(error) err("while unmounting %s: %s", dev, error->message);
|
|
|
|
else printf("Unmounted %s.\n", dev);
|
|
|
|
|
|
|
|
args = g_variant_new("a{sv}", NULL);
|
|
|
|
udisks_drive_call_power_off_sync(drive, args, NULL, &error);
|
|
|
|
if(error) err("while ejecting %s: %s", dev, error->message);
|
|
|
|
else printf("Ejected %s.\n", dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(folder);
|
|
|
|
if(argv) free(argv);
|
|
|
|
if(fs) g_object_unref(fs);
|
|
|
|
if(drive) g_object_unref(drive);
|
|
|
|
if(block) g_object_unref(block);
|
|
|
|
if(udc && udm) ud2_end(udc, udm);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* FXLINK_DISABLE_UDISKS2 */
|