This patch adds a module for determining whether a file (or directory) is on a local or a remote file system.
2025-11-12 Bruno Haible <[email protected]> file-remote: New module. * lib/file-remote.h: New file. * lib/file-remote.c: New file. * modules/file-remote: New file.
>From 23afd19a4ff4fe9141dadd65d5c2bc1ccbfb9cc6 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Wed, 12 Nov 2025 13:57:50 +0100 Subject: [PATCH] file-remote: New module. * lib/file-remote.h: New file. * lib/file-remote.c: New file. * modules/file-remote: New file. --- ChangeLog | 7 ++ lib/file-remote.c | 287 ++++++++++++++++++++++++++++++++++++++++++++ lib/file-remote.h | 37 ++++++ modules/file-remote | 25 ++++ 4 files changed, 356 insertions(+) create mode 100644 lib/file-remote.c create mode 100644 lib/file-remote.h create mode 100644 modules/file-remote diff --git a/ChangeLog b/ChangeLog index 9d6b483bb6..8a9b8d3721 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2025-11-12 Bruno Haible <[email protected]> + + file-remote: New module. + * lib/file-remote.h: New file. + * lib/file-remote.c: New file. + * modules/file-remote: New file. + 2025-11-11 Bruno Haible <[email protected]> pthread-cond, cond, cnd tests: Fix spurious failure on Linux/SPARC. diff --git a/lib/file-remote.c b/lib/file-remote.c new file mode 100644 index 0000000000..bad7ce011d --- /dev/null +++ b/lib/file-remote.c @@ -0,0 +1,287 @@ +/* Determination whether a file is local or remote. + Copyright (C) 2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#include <config.h> + +/* Specification. */ +#include "file-remote.h" + +#include <string.h> + +#if defined __linux__ || defined __ANDROID__ /* Linux */ +# include <sys/statfs.h> +#elif defined __gnu_hurd__ /* GNU/Hurd */ +# include <sys/statfs.h> +# include <hurd/hurd_types.h> +#elif defined __APPLE__ && defined __MACH__ /* macOS */ +# include <sys/mount.h> +#elif defined __FreeBSD__ || defined __DragonFly__ || defined __FreeBSD_kernel__ + /* FreeBSD, GNU/kFreeBSD */ +# include <sys/mount.h> +#elif defined __NetBSD__ /* NetBSD */ +# include <sys/statvfs.h> +#elif defined __OpenBSD__ /* OpenBSD */ +# include <sys/types.h> +# include <sys/mount.h> +#elif defined _AIX /* AIX */ +# include <sys/statvfs.h> +#elif defined __sun /* Solaris */ +# include <sys/statvfs.h> +#elif defined __CYGWIN__ /* Cygwin */ +# include "cygpath.h" +#elif defined __HAIKU__ /* Haiku */ +# include <fs_info.h> +# include <errno.h> +#endif + +#if defined _WIN32 || defined __CYGWIN__ /* Windows */ +# define WIN32_LEAN_AND_MEAN /* avoid including junk */ +# include <windows.h> +# include <errno.h> +/* Don't assume that UNICODE is not defined. */ +# undef GetFullPathName +# define GetFullPathName GetFullPathNameA +# undef GetDriveType +# define GetDriveType GetDriveTypeA + +/* Here, FILE is a file or directory name in native Windows syntax. */ +static int +windows_file_is_remote (const char *file) +{ + char buf[MAX_PATH]; + /* Documentation: + <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamea> */ + if (! GetFullPathName (file, sizeof (buf), buf, NULL)) + { + switch (GetLastError ()) + { + case ERROR_FILE_NOT_FOUND: /* The last component of file does not exist. */ + case ERROR_PATH_NOT_FOUND: /* Some directory component in file does not exist. */ + case ERROR_BAD_PATHNAME: /* file is such as '\\server'. */ + case ERROR_BAD_NET_NAME: /* file is such as '\\server\nonexistentshare'. */ + case ERROR_INVALID_NAME: /* file contains wildcards, misplaced colon, etc. */ + errno = ENOENT; + break; + case ERROR_ACCESS_DENIED: /* file is such as 'C:\System Volume Information\foo'. */ + case ERROR_SHARING_VIOLATION: /* file is such as 'C:\pagefile.sys'. */ + /* XXX map to EACCES or EPERM? */ + errno = EACCES; + break; + default: + errno = EINVAL; + break; + } + return -1; + } + const char *root = buf; + if (root[0] == '\\' && root[1] == '\\') + { + if (root[2] == '?' && root[3] == '\\') + { + if (strncmp (root + 4, "Volume", 6) == 0) + /* '\\?\Volume{GUID}\' designates a local volume. */ + return 0; + if (strncmp (root + 4, "UNC\\", 4) == 0) + /* '\\?\UNC\server\share\' designates a remote mount. */ + return 1; + root += 4; + } + else + /* '\\server\share\' designates a remote mount. */ + return 1; + } + if (root[0] != '\0' && root[1] == ':') + { + /* Documentation: + <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea> */ + UINT type = GetDriveType (root); + return type == DRIVE_REMOTE; + } + /* ROOT does not look like a remote mount. */ + return 0; +} + +#endif + +int +file_is_remote (const char *file) +{ +#if defined __linux__ || defined __ANDROID__ /* Linux */ + struct statfs fs; + if (statfs (file, &fs) < 0) + return -1; + /* See coreutils/src/fs.h and coreutils/src/fs-is-local.h. */ + switch (fs.f_type) + { + case 0x61636673 /* S_MAGIC_ACFS */: + case 0x5346414F /* S_MAGIC_AFS */: + case 0x00C36400 /* S_MAGIC_CEPH */: + case 0xFF534D42 /* S_MAGIC_CIFS */: + case 0x73757245 /* S_MAGIC_CODA */: + case 0x19830326 /* S_MAGIC_FHGFS */: + case 0x65735546 /* S_MAGIC_FUSE */: + case 0x65735543 /* S_MAGIC_FUSECTL */: + case 0x01161970 /* S_MAGIC_GFS */: + case 0x47504653 /* S_MAGIC_GPFS */: + case 0x013111A8 /* S_MAGIC_IBRIX */: + case 0x6B414653 /* S_MAGIC_KAFS */: + case 0x0BD00BD0 /* S_MAGIC_LUSTRE */: + case 0x0000564C /* S_MAGIC_NCP */: + case 0x00006969 /* S_MAGIC_NFS */: + case 0x6E667364 /* S_MAGIC_NFSD */: + case 0x7461636F /* S_MAGIC_OCFS2 */: + case 0x794C7630 /* S_MAGIC_OVERLAYFS */: + case 0xAAD7AAEA /* S_MAGIC_PANFS */: + case 0x50495045 /* S_MAGIC_PIPEFS */: + case 0x7C7C6673 /* S_MAGIC_PRL_FS */: + case 0x0000517B /* S_MAGIC_SMB */: + case 0xFE534D42 /* S_MAGIC_SMB2 */: + case 0xBEEFDEAD /* S_MAGIC_SNFS */: + case 0x786F4256 /* S_MAGIC_VBOXSF */: + case 0xBACBACBC /* S_MAGIC_VMHGFS */: + case 0xA501FCF5 /* S_MAGIC_VXFS */: + return 1; + default: + return 0; + } +#elif defined __gnu_hurd__ /* GNU/Hurd */ + /* On this platform, we could equally use 'statvfs', as + it's identical to 'statfs'. */ + struct statfs fs; + if (statfs (file, &fs) < 0) + return -1; + /* See <hurd/hurd_types.h>. + Note: The only ones of these that are currently implemented in hurd.git + are nfs and ftpfs. */ + switch (fs.f_type) + { + case FSTYPE_NFS: /* Network File System ala Sun */ + case FSTYPE_FTP: /* Transparent FTP */ + case FSTYPE_GRFS: /* GNU Remote File System */ + case FSTYPE_AFS: /* Andrew File System 3.xx */ + case FSTYPE_DFS: /* Distributed File Sys (OSF) == AFS 4.xx */ + case FSTYPE_SOCKET: /* io_t that isn't a file but a socket */ + case FSTYPE_HTTP: /* Transparent HTTP */ + return 1; + default: + return 0; + } +#elif defined __APPLE__ && defined __MACH__ /* macOS */ + struct statfs fs; + if (statfs (file, &fs) < 0) + return -1; + return (fs.f_flags & MNT_LOCAL) == 0 + && !(strcmp (fs.f_fstypename, "fdesc") == 0); +#elif defined __FreeBSD__ || defined __DragonFly__ || defined __FreeBSD_kernel__ + /* FreeBSD, GNU/kFreeBSD */ + struct statfs fs; + if (statfs (file, &fs) < 0) + return -1; + return (fs.f_flags & MNT_LOCAL) == 0 + && !(strcmp (fs.f_fstypename, "devfs") == 0 + || strcmp (fs.f_fstypename, "fdescfs") == 0); +#elif defined __NetBSD__ /* NetBSD */ + struct statvfs fs; + if (statvfs1 (file, &fs, ST_NOWAIT) < 0) + return -1; + return (fs.f_flag & ST_LOCAL) == 0; +#elif defined __OpenBSD__ /* OpenBSD */ + struct statfs fs; + if (statfs (file, &fs) < 0) + return -1; + return (fs.f_flags & MNT_LOCAL) == 0; +#elif defined _AIX /* AIX */ + struct statvfs fs; + if (statvfs (file, &fs) < 0) + return -1; + /* See /etc/vfs. */ + return (strcmp (fs.f_basetype, "nfs") == 0 + || strcmp (fs.f_basetype, "nfs3") == 0 + || strcmp (fs.f_basetype, "nfs4") == 0 + || strcmp (fs.f_basetype, "stnfs") == 0 + || strcmp (fs.f_basetype, "cachefs") == 0); +#elif defined __sun /* Solaris */ + struct statvfs fs; + if (statvfs (file, &fs) < 0) + return -1; + /* See /etc/dfs/fstypes. */ + return (strcmp (fs.f_basetype, "nfs") == 0 + || strcmp (fs.f_basetype, "smb") == 0 + || strcmp (fs.f_basetype, "smbfs") == 0 + || strcmp (fs.f_basetype, "autofs") == 0); +#elif defined __CYGWIN__ /* Cygwin */ + /* The 'struct statfs' member 'f_type' does not have the + necessary information. We need to use the native Windows API. */ + char *windows_file = cygpath_w (file); + int result = windows_file_is_remote (windows_file); + free (windows_file); + return result; +#elif defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ + return windows_file_is_remote (file); +#elif defined __HAIKU__ /* Haiku */ + /* Documentation: + https://www.haiku-os.org/legacy-docs/bebook/TheStorageKit_Functions.html#dev_for_path + out-of-date: This function actually sets errno when it fails. */ + dev_t device = dev_for_path (file); + if (device < 0) + return -1; + /* Documentation: + https://www.haiku-os.org/legacy-docs/bebook/TheStorageKit_Functions.html#fs_stat_dev + https://www.haiku-os.org/legacy-docs/bebook/TheStorageKit_DefinedTypes.html#fs_info + This function too actually sets errno when it fails. */ + struct fs_info fs; + if (fs_stat_dev (device, &fs) != B_OK) + return -1; + return (strcmp (fs.fsh_name, "userlandfs") == 0 + || strcmp (fs.fsh_name, "nfs") == 0 + || strcmp (fs.fsh_name, "netfs") == 0 + || strcmp (fs.fsh_name, "websearchfs") == 0); +#else /* Unknown OS */ + /* Assume all file systems are local. */ + return 0; +#endif +} + + +#if TEST + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* Test program that prints the result for a file name given on the command + line. */ +int +main (int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + const char *file = argv[i]; + int ret = file_is_remote (file); + if (ret == 0) + printf ("%s => local\n", file); + else if (ret > 0) + printf ("%s => remote\n", file); + else + printf ("%s => error: %s\n", file, strerror (errno)); + } + + return 0; +} + +#endif /* TEST */ diff --git a/lib/file-remote.h b/lib/file-remote.h new file mode 100644 index 0000000000..af9b4030c3 --- /dev/null +++ b/lib/file-remote.h @@ -0,0 +1,37 @@ +/* Determination whether a file is local or remote. + Copyright (C) 2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#ifndef _FILE_REMOTE_H +#define _FILE_REMOTE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Determines whether a file (or directory) is remote, that is, whether + a file system operation on the file, such as mkdir() or open(), can + take an unlimited amount of time, due to network issues. + Returns + - 0, if it is local, + - 1, if it is remote, + - -1 with errno set, upon error (e.g. if the file is not accessible). */ +extern int file_is_remote (const char *file); + +#ifdef __cplusplus +} +#endif + +#endif /* _FILE_REMOTE_H */ diff --git a/modules/file-remote b/modules/file-remote new file mode 100644 index 0000000000..6090103934 --- /dev/null +++ b/modules/file-remote @@ -0,0 +1,25 @@ +Description: +Determine whether a file is local or remote. + +Files: +lib/file-remote.h +lib/file-remote.c + +Depends-on: +cygpath + +configure.ac: + +Makefile.am: +lib_SOURCES += file-remote.c + +Include: +"file-remote.h" + +Link: + +License: +GPL + +Maintainer: +all -- 2.51.0
