| /* 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 <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/types.h> | |
| # include <sys/param.h> | |
| # 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 <sys/stat.h> | |
| # include <fs_info.h> | |
| # include <errno.h> | |
| #else /* Unknown OS */ | |
| # if defined SLOW_AND_OVERKILL | |
| # include <sys/stat.h> | |
| # include "mountlist.h" | |
| # include <errno.h> | |
| # endif | |
| #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 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 */ | |
| 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 (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 */ | |
| # if defined SLOW_AND_OVERKILL | |
| /* This makes many system calls, is therefore slow, and | |
| is also not multithread-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 <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[]) | |
| { | |
| 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 */ |