| 1 | #include "git-compat-util.h" |
| 2 | #include "fsmonitor-ll.h" |
| 3 | #include "fsmonitor-path-utils.h" |
| 4 | #include "gettext.h" |
| 5 | #include "trace.h" |
| 6 | |
| 7 | #include <sys/statfs.h> |
| 8 | |
| 9 | #ifdef HAVE_LINUX_MAGIC_H |
| 10 | #include <linux/magic.h> |
| 11 | #endif |
| 12 | |
| 13 | /* |
| 14 | * Filesystem magic numbers for remote filesystems. |
| 15 | * Defined here if not available in linux/magic.h. |
| 16 | */ |
| 17 | #ifndef CIFS_SUPER_MAGIC |
| 18 | #define CIFS_SUPER_MAGIC 0xff534d42 |
| 19 | #endif |
| 20 | #ifndef SMB_SUPER_MAGIC |
| 21 | #define SMB_SUPER_MAGIC 0x517b |
| 22 | #endif |
| 23 | #ifndef SMB2_SUPER_MAGIC |
| 24 | #define SMB2_SUPER_MAGIC 0xfe534d42 |
| 25 | #endif |
| 26 | #ifndef NFS_SUPER_MAGIC |
| 27 | #define NFS_SUPER_MAGIC 0x6969 |
| 28 | #endif |
| 29 | #ifndef AFS_SUPER_MAGIC |
| 30 | #define AFS_SUPER_MAGIC 0x5346414f |
| 31 | #endif |
| 32 | #ifndef CODA_SUPER_MAGIC |
| 33 | #define CODA_SUPER_MAGIC 0x73757245 |
| 34 | #endif |
| 35 | #ifndef FUSE_SUPER_MAGIC |
| 36 | #define FUSE_SUPER_MAGIC 0x65735546 |
| 37 | #endif |
| 38 | |
| 39 | /* |
| 40 | * Check if filesystem type is a remote filesystem. |
| 41 | */ |
| 42 | static int is_remote_fs(unsigned long f_type) |
| 43 | { |
| 44 | switch (f_type) { |
| 45 | case CIFS_SUPER_MAGIC: |
| 46 | case SMB_SUPER_MAGIC: |
| 47 | case SMB2_SUPER_MAGIC: |
| 48 | case NFS_SUPER_MAGIC: |
| 49 | case AFS_SUPER_MAGIC: |
| 50 | case CODA_SUPER_MAGIC: |
| 51 | case FUSE_SUPER_MAGIC: |
| 52 | return 1; |
| 53 | default: |
| 54 | return 0; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /* |
| 59 | * Map filesystem magic numbers to human-readable names as a fallback |
| 60 | * when /proc/mounts is unavailable. This only covers the remote and |
| 61 | * special filesystems in is_remote_fs() above; local filesystems are |
| 62 | * never flagged as incompatible, so we do not need their names here. |
| 63 | */ |
| 64 | static const char *get_fs_typename(unsigned long f_type) |
| 65 | { |
| 66 | switch (f_type) { |
| 67 | case CIFS_SUPER_MAGIC: |
| 68 | return "cifs"; |
| 69 | case SMB_SUPER_MAGIC: |
| 70 | return "smb"; |
| 71 | case SMB2_SUPER_MAGIC: |
| 72 | return "smb2"; |
| 73 | case NFS_SUPER_MAGIC: |
| 74 | return "nfs"; |
| 75 | case AFS_SUPER_MAGIC: |
| 76 | return "afs"; |
| 77 | case CODA_SUPER_MAGIC: |
| 78 | return "coda"; |
| 79 | case FUSE_SUPER_MAGIC: |
| 80 | return "fuse"; |
| 81 | default: |
| 82 | return "unknown"; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | /* |
| 87 | * Find the mount point for a given path by reading /proc/mounts. |
| 88 | * |
| 89 | * statfs(2) gives us f_type (the magic number) but not the human-readable |
| 90 | * filesystem type string. We scan /proc/mounts to find the mount entry |
| 91 | * whose path is the longest prefix of ours and whose f_fsid matches, |
| 92 | * which gives us the fstype string (e.g. "nfs", "ext4") for logging. |
| 93 | */ |
| 94 | static char *find_mount(const char *path, const struct statfs *path_fs) |
| 95 | { |
| 96 | FILE *fp; |
| 97 | struct strbuf line = STRBUF_INIT; |
| 98 | struct strbuf match = STRBUF_INIT; |
| 99 | struct strbuf fstype = STRBUF_INIT; |
| 100 | char *result = NULL; |
| 101 | |
| 102 | fp = fopen("/proc/mounts", "r"); |
| 103 | if (!fp) |
| 104 | return NULL; |
| 105 | |
| 106 | while (strbuf_getline(&line, fp) != EOF) { |
| 107 | char *fields[6]; |
| 108 | char *p = line.buf; |
| 109 | int i; |
| 110 | |
| 111 | /* Parse mount entry: device mountpoint fstype options dump pass */ |
| 112 | for (i = 0; i < 6 && p; i++) { |
| 113 | fields[i] = p; |
| 114 | p = strchr(p, ' '); |
| 115 | if (p) |
| 116 | *p++ = '\0'; |
| 117 | } |
| 118 | |
| 119 | if (i >= 3) { |
| 120 | const char *mountpoint = fields[1]; |
| 121 | const char *type = fields[2]; |
| 122 | struct statfs mount_fs; |
| 123 | |
| 124 | /* Check if this mount point is a prefix of our path */ |
| 125 | if (starts_with(path, mountpoint) && |
| 126 | (path[strlen(mountpoint)] == '/' || |
| 127 | path[strlen(mountpoint)] == '\0')) { |
| 128 | /* Check if filesystem ID matches */ |
| 129 | if (statfs(mountpoint, &mount_fs) == 0 && |
| 130 | !memcmp(&mount_fs.f_fsid, &path_fs->f_fsid, |
| 131 | sizeof(mount_fs.f_fsid))) { |
| 132 | /* Keep the longest matching mount point */ |
| 133 | if (strlen(mountpoint) > match.len) { |
| 134 | strbuf_reset(&match); |
| 135 | strbuf_addstr(&match, mountpoint); |
| 136 | strbuf_reset(&fstype); |
| 137 | strbuf_addstr(&fstype, type); |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | fclose(fp); |
| 145 | strbuf_release(&line); |
| 146 | strbuf_release(&match); |
| 147 | |
| 148 | if (fstype.len) |
| 149 | result = strbuf_detach(&fstype, NULL); |
| 150 | else |
| 151 | strbuf_release(&fstype); |
| 152 | |
| 153 | return result; |
| 154 | } |
| 155 | |
| 156 | int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info) |
| 157 | { |
| 158 | struct statfs fs; |
| 159 | |
| 160 | if (statfs(path, &fs) == -1) { |
| 161 | int saved_errno = errno; |
| 162 | trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s", |
| 163 | path, strerror(saved_errno)); |
| 164 | errno = saved_errno; |
| 165 | return -1; |
| 166 | } |
| 167 | |
| 168 | trace_printf_key(&trace_fsmonitor, |
| 169 | "statfs('%s') [type 0x%08lx]", |
| 170 | path, (unsigned long)fs.f_type); |
| 171 | |
| 172 | fs_info->is_remote = is_remote_fs(fs.f_type); |
| 173 | |
| 174 | /* |
| 175 | * Try to get filesystem type from /proc/mounts for a more |
| 176 | * descriptive name. |
| 177 | */ |
| 178 | fs_info->typename = find_mount(path, &fs); |
| 179 | if (!fs_info->typename) |
| 180 | fs_info->typename = xstrdup(get_fs_typename(fs.f_type)); |
| 181 | |
| 182 | trace_printf_key(&trace_fsmonitor, |
| 183 | "'%s' is_remote: %d, typename: %s", |
| 184 | path, fs_info->is_remote, fs_info->typename); |
| 185 | |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | int fsmonitor__is_fs_remote(const char *path) |
| 190 | { |
| 191 | struct fs_info fs; |
| 192 | |
| 193 | if (fsmonitor__get_fs_info(path, &fs)) |
| 194 | return -1; |
| 195 | |
| 196 | free(fs.typename); |
| 197 | |
| 198 | return fs.is_remote; |
| 199 | } |
| 200 | |
| 201 | /* |
| 202 | * No-op for Linux - we don't have firmlinks like macOS. |
| 203 | */ |
| 204 | int fsmonitor__get_alias(const char *path UNUSED, |
| 205 | struct alias_info *info UNUSED) |
| 206 | { |
| 207 | return 0; |
| 208 | } |
| 209 | |
| 210 | /* |
| 211 | * No-op for Linux - we don't have firmlinks like macOS. |
| 212 | */ |
| 213 | char *fsmonitor__resolve_alias(const char *path UNUSED, |
| 214 | const struct alias_info *info UNUSED) |
| 215 | { |
| 216 | return NULL; |
| 217 | } |