I'm attempting to build a utility to automatically copy files from removable drives (specifically, an SD card from my camera) when they are attached. I've built most of the plumbing and it successfully detects when a card is present, scans it for files, and copies them to my desired location.
I want to have copy progress notifications as well, and that's not possible with the standard File.CopyTo
implementation provided in .NET so I built my own platform specific copy providers that report progress updates.
I found a lovely library provided by Miguel de Icaza that exposes Mac's native copyfile
function here: https://github.com/migueldeicaza/Darwin.CopyFile
The library copies files just fine, but the second I try to set a state callback, the process just exits with return code 0.
The code in question looks like this:
public async Task Copy(IngestionOperation operation, CancellationToken cancellationToken){ using var state = new State(); state.SetStatusCallback((Progress what, Stage stage, string source, string dest, State state) => { if (cancellationToken.IsCancellationRequested) { return NextStep.Quit; } if (CopyProgress == null) { return NextStep.Continue; } if (stage == Stage.Progress) { CopyProgress.Invoke(this, new CopyProgressEventArgs(operation.Source.FullName, operation.Destination, operation.Source.Length, state.Copied)); } return NextStep.Continue; }); var flags = Flags.Clone; if (operation.Overwrite) { flags |= Flags.Unlink; } var destinationFileInfo = FileSystem.FileInfo.FromFileName(operation.Destination); destinationFileInfo.Directory.Create(); var status = await Task.Run(() => Darwin.CopyFile.Copy(operation.Source.FullName, operation.Destination, Flags.Clone, state)); if (status != Status.Ok) { if (status == Status.EACCESS) { throw new UnauthorizedAccessException("CopyFile returned EACCESS"); } throw new MacOsCopyException(status); }}
The only way I can get this code to execute at the moment, is to comment out the state.SetStatusCallback
call and live without progress notifications.
I copied the CopyFile source into my project to try debug and see what the problem is. The crashing line is the native call into copyfile_set_state
.
public void SetStatusCallback(ProgressCallback callback){ this.callback = callback; if (!gch.IsAllocated) gch = GCHandle.Alloc(this); copyfile_state_set(handle, Option.StatusCB, Callback); // crash IntPtr h = GCHandle.ToIntPtr(gch); copyfile_state_set(handle, Option.StatusCtx, ref h);}
That is defined as follows:
using copy_file_state_t = System.IntPtr;delegate NextStep RawProgressCallback(int what, int stage, IntPtr state, IntPtr str, IntPtr dst, IntPtr ctx);[DllImport("libc")]extern static int copyfile_state_set(copy_file_state_t s, Option flag, RawProgressCallback callback);
The only feedback I get is The program '[62659] CardIngestor.Daemon.dll' has exited with code 0 (0x0).
I've never seen a dotnet app crash in this manner. The return code 0 tells me it isn't even a normal "crash" but something is making this happen. But I also haven't worked much with interop, especially on Mac.
I'm using .NET 6.0.100, MacOS 12.0.1 on an M1 chip. My project is on Github here: https://github.com/biltongza/CardIngestor
Does anyone have any ideas why this would be happening?