static gboolean _ostree_bootloader_grub2_write_config (OstreeBootloader *bootloader, int bootversion, GCancellable *cancellable, GError **error) { OstreeBootloaderGrub2 *self = OSTREE_BOOTLOADER_GRUB2 (bootloader); gboolean ret = FALSE; gs_unref_object GFile *efi_new_config_temp = NULL; gs_unref_object GFile *efi_orig_config = NULL; gs_unref_object GFile *new_config_path = NULL; gs_unref_object GSSubprocessContext *procctx = NULL; gs_unref_object GSSubprocess *proc = NULL; gs_strfreev char **child_env = g_get_environ (); gs_free char *bootversion_str = g_strdup_printf ("%u", (guint)bootversion); gs_unref_object GFile *config_path_efi_dir = NULL; if (self->is_efi) { config_path_efi_dir = g_file_get_parent (self->config_path_efi); new_config_path = g_file_get_child (config_path_efi_dir, "grub.cfg.new"); /* We use grub2-mkconfig to write to a temporary file first */ if (!ot_gfile_ensure_unlinked (new_config_path, cancellable, error)) goto out; } else { new_config_path = ot_gfile_resolve_path_printf (self->sysroot->path, "boot/loader.%d/grub.cfg", bootversion); } procctx = gs_subprocess_context_newv ("grub2-mkconfig", "-o", gs_file_get_path_cached (new_config_path), NULL); child_env = g_environ_setenv (child_env, "_OSTREE_GRUB2_BOOTVERSION", bootversion_str, TRUE); /* We have to pass our state to the child */ if (self->is_efi) child_env = g_environ_setenv (child_env, "_OSTREE_GRUB2_IS_EFI", "1", TRUE); gs_subprocess_context_set_environment (procctx, child_env); gs_subprocess_context_set_stdout_disposition (procctx, GS_SUBPROCESS_STREAM_DISPOSITION_NULL); if (g_getenv ("OSTREE_DEBUG_GRUB2")) gs_subprocess_context_set_stderr_disposition (procctx, GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT); else gs_subprocess_context_set_stderr_disposition (procctx, GS_SUBPROCESS_STREAM_DISPOSITION_NULL); /* In the current Fedora grub2 package, this script doesn't even try to be atomic; it just does: cat ${grub_cfg}.new > ${grub_cfg} rm -f ${grub_cfg}.new Upstream is fixed though. */ proc = gs_subprocess_new (procctx, cancellable, error); if (!proc) goto out; if (!gs_subprocess_wait_sync_check (proc, cancellable, error)) goto out; /* Now let's fdatasync() for the new file */ if (!gs_file_sync_data (new_config_path, cancellable, error)) goto out; if (self->is_efi) { gs_unref_object GFile *config_path_efi_old = g_file_get_child (config_path_efi_dir, "grub.cfg.old"); /* copy current to old */ if (!ot_gfile_ensure_unlinked (config_path_efi_old, cancellable, error)) goto out; if (!g_file_copy (self->config_path_efi, config_path_efi_old, G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error)) goto out; if (!ot_gfile_ensure_unlinked (config_path_efi_old, cancellable, error)) goto out; /* NOTE: NON-ATOMIC REPLACEMENT; WE can't do anything else on FAT; * see https://bugzilla.gnome.org/show_bug.cgi?id=724246 */ if (!ot_gfile_ensure_unlinked (self->config_path_efi, cancellable, error)) goto out; if (!gs_file_rename (new_config_path, self->config_path_efi, cancellable, error)) goto out; } ret = TRUE; out: return ret; }
static gboolean linkcopy_internal_attempt (GFile *src, GFile *dest, GFile *dest_parent, GFileCopyFlags flags, gboolean sync_data, gboolean enable_guestfs_fuse_workaround, gboolean *out_try_again, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int res; char *tmp_name = NULL; GFile *tmp_dest = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto out; tmp_name = gs_fileutil_gen_tmp_name (NULL, NULL); tmp_dest = g_file_get_child (dest_parent, tmp_name); res = link (gs_file_get_path_cached (src), gs_file_get_path_cached (tmp_dest)); if (res == -1) { if (errno == EEXIST) { /* Nothing, fall through */ *out_try_again = TRUE; ret = TRUE; goto out; } else if (errno == EXDEV || errno == EMLINK || errno == EPERM || (enable_guestfs_fuse_workaround && errno == ENOENT)) { if (!g_file_copy (src, tmp_dest, flags, cancellable, NULL, NULL, error)) goto out; } else { gs_set_prefix_error_from_errno (error, errno, "link"); goto out; } } if (sync_data) { /* Now, we need to fsync */ if (!gs_file_sync_data (tmp_dest, cancellable, error)) goto out; } if (!gs_file_rename (tmp_dest, dest, cancellable, error)) goto out; ret = TRUE; *out_try_again = FALSE; out: g_clear_pointer (&tmp_name, g_free); g_clear_object (&tmp_dest); return ret; }