You don’t need to have it copy the file over on next boot. Instead, this sequence will work fine:
- Copy new executable to a local file.
- Verify local file.
- unlink() existing executable.
- rename() new executable to the correct name.
The application will keep running after the unlink() – the kernel won’t release the underlying data until all executing copies are finished.
You can then even just use execve() to have the currently-running process replace itself with the newly uploaded version.