Re: [PATCH] libgloss/arm: report semihosted directories correctly

Jonathan Wakely <[email protected]>
Newsgroups gmane.comp.lib.newlib
Message-ID <CAH6eHdSe1nauinhNxJZBn0Na1Mbwuz_PZK1+5GudBbba6C4Rzg@mail.gmail.com>
On Fri, 14 Aug 2026, 09:51 Torbjörn SVENSSON, <[email protected]>
wrote:

> In GCC r17-2048-gcc195f7b11a406, support for resolving /etc/localtime
> symlinks
> was added.  As a consequense, when using semihosting for arm-none-eabi, the
> resolution fails due to that semihosting unconditionally consider all
> paths as
> character devices.  This commit tries to work around that by inferring as
> much
> stat details as possible from available APIs in the semihosting
> specification.
>
> With this change, I see that the failure is resolved, but there are a few
> tests that have been marked as xfail that now passes.  I will follow up
> with a patch for GCC as soon as this change is merged.
>
> These are the changes when testing with GCC r17-2833-g32657f29f91871.
>
> Without patch:
> PASS: 27_io/basic_filebuf/sgetn/char/1-in.cc  -std=gnu++20 (test for
> excess errors)
> XFAIL: 27_io/basic_filebuf/sgetn/char/1-in.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/1-io.cc  -std=gnu++20 (test for
> excess errors)
> XFAIL: 27_io/basic_filebuf/sgetn/char/1-io.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/2-in.cc  -std=gnu++20 (test for
> excess errors)
> XFAIL: 27_io/basic_filebuf/sgetn/char/2-in.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/2-io.cc  -std=gnu++20 (test for
> excess errors)
> XFAIL: 27_io/basic_filebuf/sgetn/char/2-io.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/underflow/wchar_t/11603.cc  -std=gnu++20 (test
> for excess errors)
> FAIL: 27_io/basic_filebuf/underflow/wchar_t/11603.cc  -std=gnu++20
> execution test
> PASS: 27_io/basic_istream/readsome/char/6746-2.cc  -std=gnu++20 (test for
> excess errors)
> XFAIL: 27_io/basic_istream/readsome/char/6746-2.cc  -std=gnu++20 execution
> test
> PASS: 27_io/basic_istream/readsome/wchar_t/6746-2.cc  -std=gnu++20 (test
> for excess errors)
> XFAIL: 27_io/basic_istream/readsome/wchar_t/6746-2.cc  -std=gnu++20
> execution test
> PASS: std/time/tzdb/1.cc  -std=gnu++20 (test for excess errors)
> FAIL: std/time/tzdb/1.cc  -std=gnu++20 execution test
> PASS: std/time/zoned_time/custom.cc  -std=gnu++20 (test for excess errors)
> FAIL: std/time/zoned_time/custom.cc  -std=gnu++20 execution test
>
> With patch:
> PASS: 27_io/basic_filebuf/sgetn/char/1-in.cc  -std=gnu++20 (test for
> excess errors)
> XPASS: 27_io/basic_filebuf/sgetn/char/1-in.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/1-io.cc  -std=gnu++20 (test for
> excess errors)
> XPASS: 27_io/basic_filebuf/sgetn/char/1-io.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/2-in.cc  -std=gnu++20 (test for
> excess errors)
> XPASS: 27_io/basic_filebuf/sgetn/char/2-in.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/sgetn/char/2-io.cc  -std=gnu++20 (test for
> excess errors)
> XPASS: 27_io/basic_filebuf/sgetn/char/2-io.cc  -std=gnu++20 execution test
> PASS: 27_io/basic_filebuf/underflow/wchar_t/11603.cc  -std=gnu++20 (test
> for excess errors)
> PASS: 27_io/basic_filebuf/underflow/wchar_t/11603.cc  -std=gnu++20
> execution test
> PASS: 27_io/basic_istream/readsome/char/6746-2.cc  -std=gnu++20 (test for
> excess errors)
> XPASS: 27_io/basic_istream/readsome/char/6746-2.cc  -std=gnu++20 execution
> test
> PASS: 27_io/basic_istream/readsome/wchar_t/6746-2.cc  -std=gnu++20 (test
> for excess errors)
> XPASS: 27_io/basic_istream/readsome/wchar_t/6746-2.cc  -std=gnu++20
> execution test
> PASS: std/time/tzdb/1.cc  -std=gnu++20 (test for excess errors)
> PASS: std/time/tzdb/1.cc  -std=gnu++20 execution test
> PASS: std/time/zoned_time/custom.cc  -std=gnu++20 (test for excess errors)
> PASS: std/time/zoned_time/custom.cc  -std=gnu++20 execution test
>
>
> These is also an implementation choise to be made.
> Would it be prefered to use stack (like I do in this patch) or would it be
> better to allocated the temporary string in path_is_dir on the heap using
> malloc?  Regardless of solution, semihosting is expensive, so I don't think
> it really matters what solution is used. I choose stack based for the first
> version as it does not pull in any extra functions.
>
> Ok to push to master as-is or should I change to a heap based
> implementation?
>
> Kind regards,
> Torbjörn
>
> --
>
> Arm semihosting does not provide a real stat operation, so _stat has to
> infer file information from the operations that are available.
>
> The previous code always treated an opened path as a regular file, and
> _swistat then added S_IFCHR unconditionally.  That meant directories were
> not reported as directories, and the file type bits could end up
> describing more than one kind of file at once.
>
> Use _isatty to identify character devices, use SYS_FLEN for regular
> files, and probe "path/." so _stat can recognize directories.
>
> This gives callers such as std::filesystem::canonical enough information
> to handle semihosted paths correctly.
>
> Discovered, and reported, in
> https://gcc.gnu.org/pipermail/gcc-patches/2026-August/727185.html
>
> Signed-off-by: Torbjörn SVENSSON <[email protected]>
> ---
>  libgloss/arm/syscalls.c | 79 ++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 74 insertions(+), 5 deletions(-)
>
> diff --git a/libgloss/arm/syscalls.c b/libgloss/arm/syscalls.c
> index 710a741ee..162059fc0 100644
> --- a/libgloss/arm/syscalls.c
> +++ b/libgloss/arm/syscalls.c
> @@ -15,6 +15,7 @@
>  #include <reent.h>
>  #include <unistd.h>
>  #include <sys/wait.h>
> +#include <limits.h>
>  #include "swi.h"
>
>  /* Forward prototypes.  */
> @@ -46,6 +47,7 @@ void  initialise_monitor_handles (void);
>  static int     checkerror      (int);
>  static int     error           (int);
>  static int     get_errno       (void);
> +static int     path_is_dir     (const char *);
>
>  /* Semihosting utilities.  */
>  static void initialise_semihosting_exts (void);
> @@ -346,6 +348,46 @@ checkerror (int result)
>    return result;
>  }
>
> +/* Check if the given path is likely a directory. */
> +static int
> +path_is_dir (const char *path)
> +{
> +  size_t path_len;
> +  size_t dir_len;
> +  char dir_path[PATH_MAX + 3];
> +  int fd;
> +
> +  path_len = strlen (path);
> +  if (path_len == 0)
> +    return 0;
> +
> +  if (path_len + 3 > sizeof (dir_path))
> +    {
> +      errno = ENAMETOOLONG;
> +      return -1;
> +    }
> +
> +  /* Build a new string that ends with "/.". */
> +  memcpy (dir_path, path, path_len);
> +  dir_len = path_len;
> +  if (dir_path[dir_len - 1] != '/')
>

Just an observation...

This check means you will add /. to a path ending with /.. which is
redundant, but I think that's OK.

If strlen(dir_path) == sizeof(dir_path) and it ends with /.. then there's
no need to append anything, but this function will return ENAMETOOLONG.
Again, I think that's OK. It's an edge case and the path is already longer
than PATH_MAX.


+    dir_path[dir_len++] = '/';
> +  dir_path[dir_len++] = '.';
> +  dir_path[dir_len] = '\0';
> +
> +  /* Try to open the directory. */
> +  fd = _open (dir_path, O_RDONLY);
> +
> +  /* Error means that path either does not exist
> +   * or is not a directory. */
> +  if (fd == -1)
> +    return 0;
> +
> +  /* Not interested in the error.  */
> +  _close (fd);
> +  return 1;
> +}
> +
>  /* fh, is a valid internal file handle.
>     ptr, is a null terminated string.
>     len, is the length in bytes to read.
> @@ -738,10 +780,14 @@ _swistat (int fd, struct stat * st)
>        return -1;
>      }
>
> -  /* Always assume a character device,
> -     with 1024 byte blocks. */
> -  st->st_mode |= S_IFCHR;
>    st->st_blksize = 1024;
> +  if (_isatty (fd))
> +    {
> +      /* Assume character device. */
> +      st->st_mode |= S_IFCHR;
> +      return 0;
> +    }
> +
>  #ifdef ARM_RDI_MONITOR
>    res = checkerror (do_AngelSWI (AngelSWI_Reason_FLen, &pfd->handle));
>  #else
> @@ -752,7 +798,19 @@ _swistat (int fd, struct stat * st)
>    checkerror (res);
>  #endif
>    if (res == -1)
> -    return -1;
> +    {
> +      if (errno == EISDIR)
> +       {
> +         /* Path is a directory. */
> +         st->st_mode |= S_IFDIR;
> +         return 0;
> +       }
> +      return -1;
> +    }
> +
> +  /* Assume regular file. */
> +  st->st_mode |= S_IFREG;
> +
>    /* Return the file size. */
>    st->st_size = res;
>    return 0;
> @@ -770,12 +828,23 @@ _stat (const char *fname, struct stat *st)
>  {
>    int fd, res;
>    memset (st, 0, sizeof (* st));
> +  res = path_is_dir (fname);
> +  if (res == 1)
> +    {
> +      st->st_mode = S_IFDIR | S_IREAD | S_IEXEC;
> +      st->st_blksize = 1024;
> +      return 0;
> +    }
> +  else if (res == -1)
> +    return -1;
> +
>    /* The best we can do is try to open the file readonly.  If it exists,
>       then we can guess a few things about it.  */
>    if ((fd = _open (fname, O_RDONLY)) == -1)
>      return -1;
> -  st->st_mode |= S_IFREG | S_IREAD;
>    res = _swistat (fd, st);
> +  if (res == 0)
> +    st->st_mode |= S_IREAD;
>    /* Not interested in the error.  */
>    _close (fd);
>    return res;
> --
> 2.43.0
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.