Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 | .. SPDX-License-Identifier: GPL-2.0 ==================================================================== Miscellaneous Device control operations for the autofs kernel module ==================================================================== The problem =========== There is a problem with active restarts in autofs (that is to say restarting autofs when there are busy mounts). During normal operation autofs uses a file descriptor opened on the directory that is being managed in order to be able to issue control operations. Using a file descriptor gives ioctl operations access to autofs specific information stored in the super block. The operations are things such as setting an autofs mount catatonic, setting the expire timeout and requesting expire checks. As is explained below, certain types of autofs triggered mounts can end up covering an autofs mount itself which prevents us being able to use open(2) to obtain a file descriptor for these operations if we don't already have one open. Currently autofs uses "umount -l" (lazy umount) to clear active mounts at restart. While using lazy umount works for most cases, anything that needs to walk back up the mount tree to construct a path, such as getcwd(2) and the proc file system /proc/<pid>/cwd, no longer works because the point from which the path is constructed has been detached from the mount tree. The actual problem with autofs is that it can't reconnect to existing mounts. Immediately one thinks of just adding the ability to remount autofs file systems would solve it, but alas, that can't work. This is because autofs direct mounts and the implementation of "on demand mount and expire" of nested mount trees have the file system mounted directly on top of the mount trigger directory dentry. For example, there are two types of automount maps, direct (in the kernel module source you will see a third type called an offset, which is just a direct mount in disguise) and indirect. Here is a master map with direct and indirect map entries:: /- /etc/auto.direct /test /etc/auto.indirect and the corresponding map files:: /etc/auto.direct: /automount/dparse/g6 budgie:/autofs/export1 /automount/dparse/g1 shark:/autofs/export1 and so on. /etc/auto.indirect:: g1 shark:/autofs/export1 g6 budgie:/autofs/export1 and so on. For the above indirect map an autofs file system is mounted on /test and mounts are triggered for each sub-directory key by the inode lookup operation. So we see a mount of shark:/autofs/export1 on /test/g1, for example. The way that direct mounts are handled is by making an autofs mount on each full path, such as /automount/dparse/g1, and using it as a mount trigger. So when we walk on the path we mount shark:/autofs/export1 "on top of this mount point". Since these are always directories we can use the follow_link inode operation to trigger the mount. But, each entry in direct and indirect maps can have offsets (making them multi-mount map entries). For example, an indirect mount map entry could also be:: g1 \ / shark:/autofs/export5/testing/test \ /s1 shark:/autofs/export/testing/test/s1 \ /s2 shark:/autofs/export5/testing/test/s2 \ /s1/ss1 shark:/autofs/export1 \ /s2/ss2 shark:/autofs/export2 and a similarly a direct mount map entry could also be:: /automount/dparse/g1 \ / shark:/autofs/export5/testing/test \ /s1 shark:/autofs/export/testing/test/s1 \ /s2 shark:/autofs/export5/testing/test/s2 \ /s1/ss1 shark:/autofs/export2 \ /s2/ss2 shark:/autofs/export2 One of the issues with version 4 of autofs was that, when mounting an entry with a large number of offsets, possibly with nesting, we needed to mount and umount all of the offsets as a single unit. Not really a problem, except for people with a large number of offsets in map entries. This mechanism is used for the well known "hosts" map and we have seen cases (in 2.4) where the available number of mounts are exhausted or where the number of privileged ports available is exhausted. In version 5 we mount only as we go down the tree of offsets and similarly for expiring them which resolves the above problem. There is somewhat more detail to the implementation but it isn't needed for the sake of the problem explanation. The one important detail is that these offsets are implemented using the same mechanism as the direct mounts above and so the mount points can be covered by a mount. The current autofs implementation uses an ioctl file descriptor opened on the mount point for control operations. The references held by the descriptor are accounted for in checks made to determine if a mount is in use and is also used to access autofs file system information held in the mount super block. So the use of a file handle needs to be retained. The Solution ============ To be able to restart autofs leaving existing direct, indirect and offset mounts in place we need to be able to obtain a file handle for these potentially covered autofs mount points. Rather than just implement an isolated operation it was decided to re-implement the existing ioctl interface and add new operations to provide this functionality. In addition, to be able to reconstruct a mount tree that has busy mounts, the uid and gid of the last user that triggered the mount needs to be available because these can be used as macro substitution variables in autofs maps. They are recorded at mount request time and an operation has been added to retrieve them. Since we're re-implementing the control interface, a couple of other problems with the existing interface have been addressed. First, when a mount or expire operation completes a status is returned to the kernel by either a "send ready" or a "send fail" operation. The "send fail" operation of the ioctl interface could only ever send ENOENT so the re-implementation allows user space to send an actual status. Another expensive operation in user space, for those using very large maps, is discovering if a mount is present. Usually this involves scanning /proc/mounts and since it needs to be done quite often it can introduce significant overhead when there are many entries in the mount table. An operation to lookup the mount status of a mount point dentry (covered or not) has also been added. Current kernel development policy recommends avoiding the use of the ioctl mechanism in favor of systems such as Netlink. An implementation using this system was attempted to evaluate its suitability and it was found to be inadequate, in this case. The Generic Netlink system was used for this as raw Netlink would lead to a significant increase in complexity. There's no question that the Generic Netlink system is an elegant solution for common case ioctl functions but it's not a complete replacement probably because its primary purpose in life is to be a message bus implementation rather than specifically an ioctl replacement. While it would be possible to work around this there is one concern that lead to the decision to not use it. This is that the autofs expire in the daemon has become far to complex because umount candidates are enumerated, almost for no other reason than to "count" the number of times to call the expire ioctl. This involves scanning the mount table which has proved to be a big overhead for users with large maps. The best way to improve this is try and get back to the way the expire was done long ago. That is, when an expire request is issued for a mount (file handle) we should continually call back to the daemon until we can't umount any more mounts, then return the appropriate status to the daemon. At the moment we just expire one mount at a time. A Generic Netlink implementation would exclude this possibility for future development due to the requirements of the message bus architecture. autofs Miscellaneous Device mount control interface ==================================================== The control interface is opening a device node, typically /dev/autofs. All the ioctls use a common structure to pass the needed parameter information and return operation results:: struct autofs_dev_ioctl { __u32 ver_major; __u32 ver_minor; __u32 size; /* total size of data passed in * including this struct */ __s32 ioctlfd; /* automount command fd */ /* Command parameters */ union { struct args_protover protover; struct args_protosubver protosubver; struct args_openmount openmount; struct args_ready ready; struct args_fail fail; struct args_setpipefd setpipefd; struct args_timeout timeout; struct args_requester requester; struct args_expire expire; struct args_askumount askumount; struct args_ismountpoint ismountpoint; }; char path[]; }; The ioctlfd field is a mount point file descriptor of an autofs mount point. It is returned by the open call and is used by all calls except the check for whether a given path is a mount point, where it may optionally be used to check a specific mount corresponding to a given mount point file descriptor, and when requesting the uid and gid of the last successful mount on a directory within the autofs file system. The union is used to communicate parameters and results of calls made as described below. The path field is used to pass a path where it is needed and the size field is used account for the increased structure length when translating the structure sent from user space. This structure can be initialized before setting specific fields by using the void function call init_autofs_dev_ioctl(``struct autofs_dev_ioctl *``). All of the ioctls perform a copy of this structure from user space to kernel space and return -EINVAL if the size parameter is smaller than the structure size itself, -ENOMEM if the kernel memory allocation fails or -EFAULT if the copy itself fails. Other checks include a version check of the compiled in user space version against the module version and a mismatch results in a -EINVAL return. If the size field is greater than the structure size then a path is assumed to be present and is checked to ensure it begins with a "/" and is NULL terminated, otherwise -EINVAL is returned. Following these checks, for all ioctl commands except AUTOFS_DEV_IOCTL_VERSION_CMD, AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD the ioctlfd is validated and if it is not a valid descriptor or doesn't correspond to an autofs mount point an error of -EBADF, -ENOTTY or -EINVAL (not an autofs descriptor) is returned. The ioctls ========== An example of an implementation which uses this interface can be seen in autofs version 5.0.4 and later in file lib/dev-ioctl-lib.c of the distribution tar available for download from kernel.org in directory /pub/linux/daemons/autofs/v5. The device node ioctl operations implemented by this interface are: AUTOFS_DEV_IOCTL_VERSION ------------------------ Get the major and minor version of the autofs device ioctl kernel module implementation. It requires an initialized struct autofs_dev_ioctl as an input parameter and sets the version information in the passed in structure. It returns 0 on success or the error -EINVAL if a version mismatch is detected. AUTOFS_DEV_IOCTL_PROTOVER_CMD and AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD ------------------------------------------------------------------ Get the major and minor version of the autofs protocol version understood by loaded module. This call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to a valid autofs mount point descriptor and sets the requested version number in version field of struct args_protover or sub_version field of struct args_protosubver. These commands return 0 on success or one of the negative error codes if validation fails. AUTOFS_DEV_IOCTL_OPENMOUNT and AUTOFS_DEV_IOCTL_CLOSEMOUNT ---------------------------------------------------------- Obtain and release a file descriptor for an autofs managed mount point path. The open call requires an initialized struct autofs_dev_ioctl with the path field set and the size field adjusted appropriately as well as the devid field of struct args_openmount set to the device number of the autofs mount. The device number can be obtained from the mount options shown in /proc/mounts. The close call requires an initialized struct autofs_dev_ioct with the ioctlfd field set to the descriptor obtained from the open call. The release of the file descriptor can also be done with close(2) so any open descriptors will also be closed at process exit. The close call is included in the implemented operations largely for completeness and to provide for a consistent user space implementation. AUTOFS_DEV_IOCTL_READY_CMD and AUTOFS_DEV_IOCTL_FAIL_CMD -------------------------------------------------------- Return mount and expire result status from user space to the kernel. Both of these calls require an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call and the token field of struct args_ready or struct args_fail set to the wait queue token number, received by user space in the foregoing mount or expire request. The status field of struct args_fail is set to the errno of the operation. It is set to 0 on success. AUTOFS_DEV_IOCTL_SETPIPEFD_CMD ------------------------------ Set the pipe file descriptor used for kernel communication to the daemon. Normally this is set at mount time using an option but when reconnecting to a existing mount we need to use this to tell the autofs mount about the new kernel pipe descriptor. In order to protect mounts against incorrectly setting the pipe descriptor we also require that the autofs mount be catatonic (see next call). The call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call and the pipefd field of struct args_setpipefd set to descriptor of the pipe. On success the call also sets the process group id used to identify the controlling process (eg. the owning automount(8) daemon) to the process group of the caller. AUTOFS_DEV_IOCTL_CATATONIC_CMD ------------------------------ Make the autofs mount point catatonic. The autofs mount will no longer issue mount requests, the kernel communication pipe descriptor is released and any remaining waits in the queue released. The call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call. AUTOFS_DEV_IOCTL_TIMEOUT_CMD ---------------------------- Set the expire timeout for mounts within an autofs mount point. The call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call. AUTOFS_DEV_IOCTL_REQUESTER_CMD ------------------------------ Return the uid and gid of the last process to successfully trigger a the mount on the given path dentry. The call requires an initialized struct autofs_dev_ioctl with the path field set to the mount point in question and the size field adjusted appropriately. Upon return the uid field of struct args_requester contains the uid and gid field the gid. When reconstructing an autofs mount tree with active mounts we need to re-connect to mounts that may have used the original process uid and gid (or string variations of them) for mount lookups within the map entry. This call provides the ability to obtain this uid and gid so they may be used by user space for the mount map lookups. AUTOFS_DEV_IOCTL_EXPIRE_CMD --------------------------- Issue an expire request to the kernel for an autofs mount. Typically this ioctl is called until no further expire candidates are found. The call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call. In addition an immediate expire that's independent of the mount timeout, and a forced expire that's independent of whether the mount is busy, can be requested by setting the how field of struct args_expire to AUTOFS_EXP_IMMEDIATE or AUTOFS_EXP_FORCED, respectively . If no expire candidates can be found the ioctl returns -1 with errno set to EAGAIN. This call causes the kernel module to check the mount corresponding to the given ioctlfd for mounts that can be expired, issues an expire request back to the daemon and waits for completion. AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD ------------------------------ Checks if an autofs mount point is in use. The call requires an initialized struct autofs_dev_ioctl with the ioctlfd field set to the descriptor obtained from the open call and it returns the result in the may_umount field of struct args_askumount, 1 for busy and 0 otherwise. AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD --------------------------------- Check if the given path is a mountpoint. The call requires an initialized struct autofs_dev_ioctl. There are two possible variations. Both use the path field set to the path of the mount point to check and the size field adjusted appropriately. One uses the ioctlfd field to identify a specific mount point to check while the other variation uses the path and optionally in.type field of struct args_ismountpoint set to an autofs mount type. The call returns 1 if this is a mount point and sets out.devid field to the device number of the mount and out.magic field to the relevant super block magic number (described below) or 0 if it isn't a mountpoint. In both cases the device number (as returned by new_encode_dev()) is returned in out.devid field. If supplied with a file descriptor we're looking for a specific mount, not necessarily at the top of the mounted stack. In this case the path the descriptor corresponds to is considered a mountpoint if it is itself a mountpoint or contains a mount, such as a multi-mount without a root mount. In this case we return 1 if the descriptor corresponds to a mount point and also returns the super magic of the covering mount if there is one or 0 if it isn't a mountpoint. If a path is supplied (and the ioctlfd field is set to -1) then the path is looked up and is checked to see if it is the root of a mount. If a type is also given we are looking for a particular autofs mount and if a match isn't found a fail is returned. If the located path is the root of a mount 1 is returned along with the super magic of the mount or 0 otherwise. |