Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 208 additions & 59 deletions src/builder-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment thread
swick marked this conversation as resolved.
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))

@alexlarsson alexlarsson Jun 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

@bbhtt bbhtt Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, in some ideal world we would pass RENAME_NOREPLACE here.

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;
}

Expand Down Expand Up @@ -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;
Expand Down