/* Determination whether a file is local or remote.
Copyright (C) 2025-2026 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 . */
#include
/* Specification. */
#include "file-remote.h"
#include
#if defined __linux__ || defined __ANDROID__ /* Linux */
# include
#elif defined __gnu_hurd__ /* GNU/Hurd */
# include
# include
#elif defined __APPLE__ && defined __MACH__ /* macOS */
# include
#elif defined __FreeBSD__ || defined __DragonFly__ || defined __FreeBSD_kernel__
/* FreeBSD, GNU/kFreeBSD */
# include
# include
# include
#elif defined __NetBSD__ /* NetBSD */
# include
#elif defined __OpenBSD__ /* OpenBSD */
# include
# include
#elif defined _AIX /* AIX */
# include
#elif defined __sun /* Solaris */
# include
#elif defined __CYGWIN__ /* Cygwin */
# include "cygpath.h"
#elif defined __HAIKU__ /* Haiku */
# include
# include
# include
#else /* Unknown OS */
# if defined SLOW_AND_OVERKILL
# include
# include "mountlist.h"
# include
# endif
#endif
#if defined _WIN32 || defined __CYGWIN__ /* Windows */
# define WIN32_LEAN_AND_MEAN /* avoid including junk */
# include
# include
/* 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:
*/
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:
*/
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 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 .
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
&& !streq (fs.f_fstypename, "fdesc");
#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
&& !(streq (fs.f_fstypename, "devfs")
|| streq (fs.f_fstypename, "fdescfs"));
#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 (streq (fs.f_basetype, "nfs")
|| streq (fs.f_basetype, "nfs3")
|| streq (fs.f_basetype, "nfs4")
|| streq (fs.f_basetype, "stnfs")
|| streq (fs.f_basetype, "cachefs"));
#elif defined __sun /* Solaris */
struct statvfs fs;
if (statvfs (file, &fs) < 0)
return -1;
/* See /etc/dfs/fstypes. */
return (streq (fs.f_basetype, "nfs")
|| streq (fs.f_basetype, "smb")
|| streq (fs.f_basetype, "smbfs")
|| streq (fs.f_basetype, "autofs"));
#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 */
struct stat statbuf;
if (stat (file, &statbuf) < 0)
return -1;
dev_t device = statbuf.st_dev;
/* 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 actually sets errno when it fails. */
struct fs_info fs;
if (fs_stat_dev (device, &fs) != B_OK)
return -1;
return (streq (fs.fsh_name, "userlandfs")
|| streq (fs.fsh_name, "nfs")
|| streq (fs.fsh_name, "netfs")
|| streq (fs.fsh_name, "websearchfs"));
#else /* Unknown OS */
# if defined SLOW_AND_OVERKILL
/* This makes many system calls, is therefore slow, and
is also not thread-safe. */
struct stat statbuf;
if (stat (file, &statbuf) < 0)
return -1;
dev_t device = statbuf.st_dev;
struct mount_entry *me_list = read_file_system_list (true);
if (me_list == NULL)
{
errno = EIO;
return -1;
}
int result = 0;
for (struct mount_entry *me = me_list; me != NULL; )
{
if (me->me_dev == device)
{
result = me->me_remote;
break;
}
me = me->me_next;
}
for (struct mount_entry *me = me_list; me != NULL; )
{
struct mount_entry *next = me->me_next;
free_mount_entry (me);
me = next;
}
return result;
# else
/* Assume all file systems are local. */
return 0;
# endif
#endif
}
#if TEST
#include
#include
#include
/* Test program that prints the result for a file name given on the command
line. */
int
main (int argc, char *argv[])
{
for (int 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 */