-
Notifications
You must be signed in to change notification settings - Fork 98
builder-utils: Rewrite locale migration to use fd-based traversal #733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -283,78 +283,228 @@ directory_is_empty (const char *path) | |
| return empty; | ||
| } | ||
|
|
||
| static char * | ||
| locale_name_to_language (const char *name) | ||
| { | ||
| char *language = g_strdup (name); | ||
| char *c; | ||
|
|
||
| if ((c = strchr (language, '@'))) *c = '\0'; | ||
| if ((c = strchr (language, '_'))) *c = '\0'; | ||
| if ((c = strchr (language, '.'))) *c = '\0'; | ||
|
|
||
| return language; | ||
| } | ||
|
|
||
| static gboolean | ||
| migrate_locale_dir (GFile *source_dir, | ||
| GFile *separate_dir, | ||
| const char *subdir, | ||
| GError **error) | ||
| migrate_locale_dir_contents (int src_dfd, | ||
| int dst_dfd, | ||
| GError **error) | ||
| { | ||
| g_autoptr(GFileEnumerator) dir_enum = NULL; | ||
| GFileInfo *next; | ||
| GError *temp_error = NULL; | ||
| g_auto(GLnxDirFdIterator) iter = { 0, }; | ||
| struct dirent *dent; | ||
|
|
||
| dir_enum = g_file_enumerate_children (source_dir, "standard::name,standard::type", | ||
| G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||
| NULL, NULL); | ||
| if (!dir_enum) | ||
| return TRUE; | ||
| if (!glnx_dirfd_iterator_init_at (src_dfd, ".", FALSE, &iter, error)) | ||
| return FALSE; | ||
|
|
||
| while ((next = g_file_enumerator_next_file (dir_enum, NULL, &temp_error))) | ||
| while (TRUE) | ||
| { | ||
| g_autoptr(GFileInfo) child_info = next; | ||
| g_autoptr(GFile) locale_subdir = NULL; | ||
| glnx_autofd int chase_fd = -1; | ||
| struct glnx_statx stx; | ||
| char tmp_name[] = ".locale-XXXXXX"; | ||
|
|
||
| if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) | ||
| if (!glnx_dirfd_iterator_next_dent (&iter, &dent, NULL, error)) | ||
| return FALSE; | ||
|
|
||
| if (dent == NULL) | ||
| break; | ||
|
|
||
| chase_fd = glnx_chase_and_statxat (src_dfd, dent->d_name, | ||
| GLNX_CHASE_NOFOLLOW | | ||
| GLNX_CHASE_RESOLVE_BENEATH, | ||
| GLNX_STATX_TYPE, | ||
| &stx, | ||
| error); | ||
| if (chase_fd < 0) | ||
| return FALSE; | ||
|
|
||
| glnx_gen_temp_name (tmp_name); | ||
|
|
||
| if (!glnx_renameat (src_dfd, dent->d_name, src_dfd, tmp_name, error)) | ||
| return FALSE; | ||
|
|
||
| if (S_ISREG (stx.stx_mode) || S_ISLNK (stx.stx_mode)) | ||
| { | ||
| g_autoptr(GFile) child = NULL; | ||
| const char *name = g_file_info_get_name (child_info); | ||
| g_autofree char *language = g_strdup (name); | ||
| g_autofree char *relative = NULL; | ||
| g_autofree char *target = NULL; | ||
| char *c; | ||
|
|
||
| c = strchr (language, '@'); | ||
| if (c != NULL) | ||
| *c = 0; | ||
| c = strchr (language, '_'); | ||
| if (c != NULL) | ||
| *c = 0; | ||
| c = strchr (language, '.'); | ||
| if (c != NULL) | ||
| *c = 0; | ||
|
|
||
| /* We ship english and C locales always */ | ||
| if (strcmp (language, "C") == 0 || | ||
| strcmp (language, "en") == 0) | ||
| continue; | ||
|
|
||
| child = g_file_get_child (source_dir, g_file_info_get_name (child_info)); | ||
|
|
||
| relative = g_build_filename (language, subdir, name, NULL); | ||
| locale_subdir = g_file_resolve_relative_path (separate_dir, relative); | ||
| if (!flatpak_mkdir_p (locale_subdir, NULL, error)) | ||
| if (!glnx_file_copy_at (src_dfd, tmp_name, NULL, | ||
| dst_dfd, dent->d_name, | ||
| GLNX_FILE_COPY_OVERWRITE | | ||
| GLNX_FILE_COPY_NOCHOWN | | ||
| GLNX_FILE_COPY_NOXATTRS, | ||
| NULL, error)) | ||
| return FALSE; | ||
|
|
||
| if (!flatpak_cp_a (child, locale_subdir, NULL, | ||
| FLATPAK_CP_FLAGS_MERGE | FLATPAK_CP_FLAGS_MOVE, | ||
| NULL, NULL, error)) | ||
| if (unlinkat (src_dfd, tmp_name, 0) < 0) | ||
| return glnx_throw_errno_prefix (error, "unlinkat %s", tmp_name); | ||
| } | ||
| else if (S_ISDIR (stx.stx_mode)) | ||
| { | ||
| glnx_autofd int src_child_dfd = -1; | ||
| glnx_autofd int child_dst_dfd = -1; | ||
|
|
||
| src_child_dfd = glnx_fd_reopen (chase_fd, O_RDONLY | O_DIRECTORY, error); | ||
| if (src_child_dfd < 0) | ||
| return FALSE; | ||
|
|
||
| target = g_build_filename ("../../share/runtime/locale", relative, NULL); | ||
| if (!glnx_ensure_dir (dst_dfd, dent->d_name, 0755, error)) | ||
| return FALSE; | ||
|
|
||
| if (!g_file_make_symbolic_link (child, target, | ||
| NULL, error)) | ||
| if (!glnx_opendirat (dst_dfd, dent->d_name, FALSE, | ||
| &child_dst_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| if (!migrate_locale_dir_contents (src_child_dfd, child_dst_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| if (unlinkat (src_dfd, tmp_name, AT_REMOVEDIR) < 0) | ||
| return glnx_throw_errno_prefix (error, "unlinkat %s", tmp_name); | ||
| } | ||
| else | ||
| return glnx_throw (error, "unexpected entry type %s", dent->d_name); | ||
| } | ||
| return TRUE; | ||
| } | ||
|
|
||
| static gboolean | ||
| migrate_lang_dir (int source_dfd, | ||
| const char *lang_name, | ||
| const char *src_tmp_name, | ||
| int lang_dfd, | ||
| int separate_dfd, | ||
| const char *subdir, | ||
| GError **error) | ||
| { | ||
| glnx_autofd int locale_subdir_dfd = -1; | ||
| g_autofree char *language = locale_name_to_language (lang_name); | ||
| g_autofree char *target = NULL; | ||
|
|
||
| if (temp_error != NULL) | ||
| const char *components[] = { language, subdir, lang_name, NULL }; | ||
|
|
||
| if (!builder_ensure_dirs_at (separate_dfd, components, &locale_subdir_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| if (!migrate_locale_dir_contents (lang_dfd, locale_subdir_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| target = g_build_filename ("../../share/runtime/locale", | ||
| language, subdir, lang_name, NULL); | ||
|
|
||
| if (unlinkat (source_dfd, src_tmp_name, AT_REMOVEDIR) < 0) | ||
| return glnx_throw_errno_prefix (error, "unlinkat %s", src_tmp_name); | ||
|
|
||
| if (symlinkat (target, source_dfd, lang_name) < 0) | ||
| return glnx_throw_errno_prefix (error, "symlinkat %s", lang_name); | ||
|
|
||
| return TRUE; | ||
| } | ||
|
|
||
| static gboolean | ||
| migrate_locale_dir (int root_dfd, | ||
| const char *source_rel, | ||
| int root_dfd_separate, | ||
| const char *subdir, | ||
| GError **error) | ||
| { | ||
| glnx_autofd int chase_fd = -1; | ||
| glnx_autofd int source_dfd = -1; | ||
| glnx_autofd int separate_dfd = -1; | ||
| g_auto(GLnxDirFdIterator) iter = { 0, }; | ||
| struct dirent *dent; | ||
| const char *separate_components[] = { "share", "runtime", "locale", NULL }; | ||
| g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func (g_free); | ||
|
|
||
| chase_fd = glnx_chaseat (root_dfd, source_rel, | ||
| GLNX_CHASE_RESOLVE_BENEATH | | ||
| GLNX_CHASE_MUST_BE_DIRECTORY, | ||
| error); | ||
| if (chase_fd < 0) | ||
| { | ||
| g_propagate_error (error, temp_error); | ||
| if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) | ||
| { | ||
| g_clear_error (error); | ||
| return TRUE; | ||
| } | ||
| return FALSE; | ||
| } | ||
|
|
||
| source_dfd = glnx_fd_reopen (chase_fd, O_RDONLY | O_DIRECTORY, error); | ||
| if (source_dfd < 0) | ||
| return glnx_prefix_error (error, "failed to reopen %s", source_rel); | ||
|
|
||
| if (!glnx_dirfd_iterator_init_at (source_dfd, ".", FALSE, &iter, error)) | ||
| return FALSE; | ||
|
|
||
| while (TRUE) | ||
| { | ||
| if (!glnx_dirfd_iterator_next_dent (&iter, &dent, NULL, error)) | ||
| return FALSE; | ||
|
|
||
| if (dent == NULL) | ||
| break; | ||
|
|
||
| { | ||
| g_autofree char *language = locale_name_to_language (dent->d_name); | ||
|
|
||
| /* We ship english and C locales always */ | ||
| if (strcmp (language, "C") == 0 || | ||
| strcmp (language, "en") == 0) | ||
| continue; | ||
| } | ||
|
|
||
| g_ptr_array_add (names, g_strdup (dent->d_name)); | ||
| } | ||
|
|
||
| if (names->len == 0) | ||
| return TRUE; | ||
|
|
||
| if (!builder_ensure_dirs_at (root_dfd_separate, separate_components, | ||
| &separate_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| for (size_t i = 0; i < names->len; i++) | ||
| { | ||
| const char *name = names->pdata[i]; | ||
| glnx_autofd int lang_dfd = -1; | ||
| glnx_autofd int reopened_lang_dfd = -1; | ||
| char tmp_name[] = ".locale-XXXXXX"; | ||
|
|
||
| lang_dfd = glnx_chaseat (source_dfd, name, | ||
| GLNX_CHASE_RESOLVE_BENEATH | | ||
| GLNX_CHASE_MUST_BE_DIRECTORY, | ||
| error); | ||
| if (lang_dfd < 0) | ||
| { | ||
| if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY)) | ||
| { | ||
| g_clear_error (error); | ||
| continue; | ||
| } | ||
| return FALSE; | ||
| } | ||
|
|
||
| reopened_lang_dfd = glnx_fd_reopen (lang_dfd, O_RDONLY | O_DIRECTORY, error); | ||
| if (reopened_lang_dfd < 0) | ||
| return glnx_prefix_error (error, "failed to reopen %s", name); | ||
|
|
||
| glnx_gen_temp_name (tmp_name); | ||
|
|
||
| if (!glnx_renameat (source_dfd, name, source_dfd, tmp_name, error)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sligtlty worried about this, as there is no guarantees that the generated tmp name doesn't already exist as an empty dir which would make the renameat not error. However, I have not been able to come up with some case where this could end up causing problems. However, in some ideal world we would pass RENAME_NOREPLACE here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
there is glnx_renameat2_noreplace which we can use I guess but I too find it unlikely... also empty directories shouldn't cause problem here even if someone managed to place one. |
||
| return FALSE; | ||
|
|
||
| if (!migrate_lang_dir (source_dfd, name, tmp_name, reopened_lang_dfd, | ||
| separate_dfd, subdir, error)) | ||
| return FALSE; | ||
| } | ||
|
|
||
| return TRUE; | ||
| } | ||
|
|
||
|
|
@@ -395,18 +545,17 @@ gboolean | |
| builder_migrate_locale_dirs (GFile *root_dir, | ||
| GError **error) | ||
| { | ||
| g_autoptr(GFile) separate_dir = NULL; | ||
| g_autoptr(GFile) lib_locale_dir = NULL; | ||
| g_autoptr(GFile) share_locale_dir = NULL; | ||
| glnx_autofd int root_dfd = -1; | ||
|
|
||
| lib_locale_dir = g_file_resolve_relative_path (root_dir, "lib/locale"); | ||
| share_locale_dir = g_file_resolve_relative_path (root_dir, "share/locale"); | ||
| separate_dir = g_file_resolve_relative_path (root_dir, "share/runtime/locale"); | ||
| if (!glnx_opendirat (AT_FDCWD, | ||
| flatpak_file_get_path_cached (root_dir), | ||
| FALSE, &root_dfd, error)) | ||
| return FALSE; | ||
|
|
||
| if (!migrate_locale_dir (lib_locale_dir, separate_dir, "lib", error)) | ||
| if (!migrate_locale_dir (root_dfd, "lib/locale", root_dfd, "lib", error)) | ||
| return FALSE; | ||
|
|
||
| if (!migrate_locale_dir (share_locale_dir, separate_dir, "share", error)) | ||
| if (!migrate_locale_dir (root_dfd, "share/locale", root_dfd, "share", error)) | ||
| return FALSE; | ||
|
|
||
| return TRUE; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.