| #include "git-compat-util.h" | |
| #include "fsmonitor-ll.h" | |
| #include "fsmonitor-path-utils.h" | |
| #include "gettext.h" | |
| #include "trace.h" | |
| #include <sys/statfs.h> | |
| #ifdef HAVE_LINUX_MAGIC_H | |
| #include <linux/magic.h> | |
| #endif | |
| /* | |
| * Filesystem magic numbers for remote filesystems. | |
| * Defined here if not available in linux/magic.h. | |
| */ | |
| #ifndef CIFS_SUPER_MAGIC | |
| #define CIFS_SUPER_MAGIC 0xff534d42 | |
| #endif | |
| #ifndef SMB_SUPER_MAGIC | |
| #define SMB_SUPER_MAGIC 0x517b | |
| #endif | |
| #ifndef SMB2_SUPER_MAGIC | |
| #define SMB2_SUPER_MAGIC 0xfe534d42 | |
| #endif | |
| #ifndef NFS_SUPER_MAGIC | |
| #define NFS_SUPER_MAGIC 0x6969 | |
| #endif | |
| #ifndef AFS_SUPER_MAGIC | |
| #define AFS_SUPER_MAGIC 0x5346414f | |
| #endif | |
| #ifndef CODA_SUPER_MAGIC | |
| #define CODA_SUPER_MAGIC 0x73757245 | |
| #endif | |
| #ifndef FUSE_SUPER_MAGIC | |
| #define FUSE_SUPER_MAGIC 0x65735546 | |
| #endif | |
| /* | |
| * Check if filesystem type is a remote filesystem. | |
| */ | |
| static int is_remote_fs(unsigned long f_type) | |
| { | |
| switch (f_type) { | |
| case CIFS_SUPER_MAGIC: | |
| case SMB_SUPER_MAGIC: | |
| case SMB2_SUPER_MAGIC: | |
| case NFS_SUPER_MAGIC: | |
| case AFS_SUPER_MAGIC: | |
| case CODA_SUPER_MAGIC: | |
| case FUSE_SUPER_MAGIC: | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| } | |
| /* | |
| * Map filesystem magic numbers to human-readable names as a fallback | |
| * when /proc/mounts is unavailable. This only covers the remote and | |
| * special filesystems in is_remote_fs() above; local filesystems are | |
| * never flagged as incompatible, so we do not need their names here. | |
| */ | |
| static const char *get_fs_typename(unsigned long f_type) | |
| { | |
| switch (f_type) { | |
| case CIFS_SUPER_MAGIC: | |
| return "cifs"; | |
| case SMB_SUPER_MAGIC: | |
| return "smb"; | |
| case SMB2_SUPER_MAGIC: | |
| return "smb2"; | |
| case NFS_SUPER_MAGIC: | |
| return "nfs"; | |
| case AFS_SUPER_MAGIC: | |
| return "afs"; | |
| case CODA_SUPER_MAGIC: | |
| return "coda"; | |
| case FUSE_SUPER_MAGIC: | |
| return "fuse"; | |
| default: | |
| return "unknown"; | |
| } | |
| } | |
| /* | |
| * Find the mount point for a given path by reading /proc/mounts. | |
| * | |
| * statfs(2) gives us f_type (the magic number) but not the human-readable | |
| * filesystem type string. We scan /proc/mounts to find the mount entry | |
| * whose path is the longest prefix of ours and whose f_fsid matches, | |
| * which gives us the fstype string (e.g. "nfs", "ext4") for logging. | |
| */ | |
| static char *find_mount(const char *path, const struct statfs *path_fs) | |
| { | |
| FILE *fp; | |
| struct strbuf line = STRBUF_INIT; | |
| struct strbuf match = STRBUF_INIT; | |
| struct strbuf fstype = STRBUF_INIT; | |
| char *result = NULL; | |
| fp = fopen("/proc/mounts", "r"); | |
| if (!fp) | |
| return NULL; | |
| while (strbuf_getline(&line, fp) != EOF) { | |
| char *fields[6]; | |
| char *p = line.buf; | |
| int i; | |
| /* Parse mount entry: device mountpoint fstype options dump pass */ | |
| for (i = 0; i < 6 && p; i++) { | |
| fields[i] = p; | |
| p = strchr(p, ' '); | |
| if (p) | |
| *p++ = '\0'; | |
| } | |
| if (i >= 3) { | |
| const char *mountpoint = fields[1]; | |
| const char *type = fields[2]; | |
| struct statfs mount_fs; | |
| /* Check if this mount point is a prefix of our path */ | |
| if (starts_with(path, mountpoint) && | |
| (path[strlen(mountpoint)] == '/' || | |
| path[strlen(mountpoint)] == '\0')) { | |
| /* Check if filesystem ID matches */ | |
| if (statfs(mountpoint, &mount_fs) == 0 && | |
| !memcmp(&mount_fs.f_fsid, &path_fs->f_fsid, | |
| sizeof(mount_fs.f_fsid))) { | |
| /* Keep the longest matching mount point */ | |
| if (strlen(mountpoint) > match.len) { | |
| strbuf_reset(&match); | |
| strbuf_addstr(&match, mountpoint); | |
| strbuf_reset(&fstype); | |
| strbuf_addstr(&fstype, type); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fclose(fp); | |
| strbuf_release(&line); | |
| strbuf_release(&match); | |
| if (fstype.len) | |
| result = strbuf_detach(&fstype, NULL); | |
| else | |
| strbuf_release(&fstype); | |
| return result; | |
| } | |
| int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info) | |
| { | |
| struct statfs fs; | |
| if (statfs(path, &fs) == -1) { | |
| int saved_errno = errno; | |
| trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s", | |
| path, strerror(saved_errno)); | |
| errno = saved_errno; | |
| return -1; | |
| } | |
| trace_printf_key(&trace_fsmonitor, | |
| "statfs('%s') [type 0x%08lx]", | |
| path, (unsigned long)fs.f_type); | |
| fs_info->is_remote = is_remote_fs(fs.f_type); | |
| /* | |
| * Try to get filesystem type from /proc/mounts for a more | |
| * descriptive name. | |
| */ | |
| fs_info->typename = find_mount(path, &fs); | |
| if (!fs_info->typename) | |
| fs_info->typename = xstrdup(get_fs_typename(fs.f_type)); | |
| trace_printf_key(&trace_fsmonitor, | |
| "'%s' is_remote: %d, typename: %s", | |
| path, fs_info->is_remote, fs_info->typename); | |
| return 0; | |
| } | |
| int fsmonitor__is_fs_remote(const char *path) | |
| { | |
| struct fs_info fs; | |
| if (fsmonitor__get_fs_info(path, &fs)) | |
| return -1; | |
| free(fs.typename); | |
| return fs.is_remote; | |
| } | |
| /* | |
| * No-op for Linux - we don't have firmlinks like macOS. | |
| */ | |
| int fsmonitor__get_alias(const char *path UNUSED, | |
| struct alias_info *info UNUSED) | |
| { | |
| return 0; | |
| } | |
| /* | |
| * No-op for Linux - we don't have firmlinks like macOS. | |
| */ | |
| char *fsmonitor__resolve_alias(const char *path UNUSED, | |
| const struct alias_info *info UNUSED) | |
| { | |
| return NULL; | |
| } |