From 0ecb60c1307f51edcc41a70b041e2a524cdf0415 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Thu, 8 Jun 2017 10:50:59 +0200 Subject: [PATCH] Add autofs 5.1.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` $ wget https://www.kernel.org/pub/linux/daemons/autofs/v5/autofs-5.1.3.tar.xz --2017-06-08 10:49:14-- https://www.kernel.org/pub/linux/daemons/autofs/v5/autofs-5.1.3.tar.xz Resolving www.kernel.org... 2604:1380:2000:f000::7, 147.75.205.195 Connecting to www.kernel.org|2604:1380:2000:f000::7|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 300632 (294K) [application/x-xz] Saving to: ‘autofs-5.1.3.tar.xz’ autofs-5.1.3.tar.xz 100%[=====================================================================================================================>] 293.59K --.-KB/s in 0.1s 2017-06-08 10:49:14 (2.59 MB/s) - ‘autofs-5.1.3.tar.xz’ saved [300632/300632] ``` --- .autofs-5.1.3 | 1 + .version | 1 + CHANGELOG | 1314 ++++ COPYING | 340 + COPYRIGHT | 18 + CREDITS | 15 + INSTALL | 181 + Makefile | 64 + Makefile.conf.in | 117 + Makefile.rules | 73 + README | 62 + README.active-restart | 114 + README.amd-maps | 162 + README.autofs-schema | 18 + README.changer | 32 + README.ncpfs | 85 + README.replicated-server | 47 + README.smbfs | 11 + README.v5.release | 78 + aclocal.m4 | 449 ++ autofs.spec | 199 + configure | 6950 +++++++++++++++++ configure.in | 408 + daemon/Makefile | 43 + daemon/automount.c | 2627 +++++++ daemon/direct.c | 1514 ++++ daemon/flag.c | 192 + daemon/indirect.c | 903 +++ daemon/lookup.c | 1563 ++++ daemon/module.c | 347 + daemon/mount.c | 82 + daemon/spawn.c | 676 ++ daemon/state.c | 1235 +++ include/automount.h | 761 ++ include/base64.h | 11 + include/config.h.in | 166 + include/dclist.h | 14 + include/defaults.h | 209 + include/dev-ioctl-lib.h | 62 + include/linux/auto_dev-ioctl.h | 229 + include/linux/auto_fs.h | 74 + include/linux/auto_fs4.h | 164 + include/list.h | 158 + include/log.h | 97 + include/lookup_ldap.h | 137 + include/macros.h | 45 + include/master.h | 132 + include/mounts.h | 125 + include/nsswitch.h | 65 + include/parse_amd.h | 72 + include/parse_subs.h | 131 + include/replicated.h | 79 + include/rpc_subs.h | 78 + include/state.h | 98 + lib/Makefile | 83 + lib/alarm.c | 250 + lib/args.c | 194 + lib/cache.c | 1290 +++ lib/cat_path.c | 98 + lib/defaults.c | 2166 +++++ lib/dev-ioctl-lib.c | 777 ++ lib/log.c | 355 + lib/macros.c | 519 ++ lib/master.c | 1949 +++++ lib/master_parse.y | 912 +++ lib/master_tok.l | 498 ++ lib/mount.x | 345 + lib/mounts.c | 2436 ++++++ lib/nss_parse.y | 200 + lib/nss_tok.l | 142 + lib/nsswitch.c | 186 + lib/parse_subs.c | 1358 ++++ lib/rpc_subs.c | 1344 ++++ man/Makefile | 24 + man/auto.master.5.in | 374 + man/autofs.5 | 611 ++ man/autofs.8.in | 81 + man/autofs.conf.5.in | 518 ++ man/autofs_ldap_auth.conf.5.in | 118 + man/automount.8 | 192 + modules/Makefile | 140 + modules/amd_parse.y | 702 ++ modules/amd_tok.l | 438 ++ modules/base64.c | 225 + modules/cyrus-sasl-extern.c | 117 + modules/cyrus-sasl.c | 1098 +++ modules/dclist.c | 584 ++ modules/lookup_dir.c | 248 + modules/lookup_file.c | 1326 ++++ modules/lookup_hesiod.c | 502 ++ modules/lookup_hosts.c | 416 + modules/lookup_ldap.c | 3828 +++++++++ modules/lookup_multi.c | 584 ++ modules/lookup_nisplus.c | 861 ++ modules/lookup_program.c | 718 ++ modules/lookup_sss.c | 850 ++ modules/lookup_userhome.c | 109 + modules/lookup_yp.c | 965 +++ modules/mount_afs.c | 64 + modules/mount_autofs.c | 356 + modules/mount_bind.c | 244 + modules/mount_changer.c | 191 + modules/mount_ext2.c | 158 + modules/mount_generic.c | 116 + modules/mount_nfs.c | 418 + modules/parse_amd.c | 2025 +++++ modules/parse_hesiod.c | 331 + modules/parse_sun.c | 1757 +++++ modules/replicated.c | 1178 +++ .../autofs4-2.6.18-v5-update-20090903.patch | 3928 ++++++++++ .../autofs4-2.6.18-v5-update-20100114.patch | 3929 ++++++++++ .../autofs4-2.6.19-v5-update-20090903.patch | 3667 +++++++++ .../autofs4-2.6.19-v5-update-20100114.patch | 3668 +++++++++ .../autofs4-2.6.20-v5-update-20090903.patch | 3621 +++++++++ .../autofs4-2.6.21-v5-update-20090903.patch | 3564 +++++++++ .../autofs4-2.6.22-v5-update-20090903.patch | 3564 +++++++++ ...autofs4-2.6.22.17-v5-update-20090903.patch | 3564 +++++++++ .../autofs4-2.6.23-v5-update-20090903.patch | 3539 +++++++++ .../autofs4-2.6.24-v5-update-20090903.patch | 3539 +++++++++ .../autofs4-2.6.24.4-v5-update-20090903.patch | 3505 +++++++++ .../autofs4-2.6.25-v5-update-20090903.patch | 3541 +++++++++ .../autofs4-2.6.26-v5-update-20090903.patch | 3519 +++++++++ .../autofs4-2.6.27-v5-update-20090903.patch | 2041 +++++ .../autofs4-2.6.28-v5-update-20090903.patch | 908 +++ .../autofs4-2.6.29-v5-update-20090903.patch | 240 + patches/util-linux-2.12a-flock.patch | 30 + patches/util-linux-2.12q-flock.patch | 29 + redhat/Makefile | 26 + redhat/autofs.conf.default.in | 407 + redhat/autofs.init.in | 222 + redhat/autofs.sysconfig | 14 + samples/Makefile | 219 + samples/auto.master | 28 + samples/auto.master.ldap | 22 + samples/auto.misc | 15 + samples/auto.net | 36 + samples/auto.smb | 85 + samples/autofs.conf.default.in | 406 + samples/autofs.init.conf | 14 + samples/autofs.schema | 23 + samples/autofs.service.in | 15 + samples/autofs_ldap_auth.conf | 11 + samples/ldap-automount-auto.direct | 46 + samples/ldap-automount-auto.indirect | 26 + samples/ldap-automount-auto.master | 16 + .../ldap-automount-rfc2307-bis-auto.direct | 46 + .../ldap-automount-rfc2307-bis-auto.indirect | 30 + .../ldap-automount-rfc2307-bis-auto.master | 19 + ...utomount-rfc2307-bis-old-style-auto.master | 19 + samples/ldap-nis-auto.direct | 54 + samples/ldap-nis-auto.indirect | 30 + samples/ldap-nis-auto.master | 18 + samples/rc.autofs.in | 156 + 153 files changed, 112796 insertions(+) create mode 100644 .autofs-5.1.3 create mode 100644 .version create mode 100644 CHANGELOG create mode 100644 COPYING create mode 100644 COPYRIGHT create mode 100644 CREDITS create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 Makefile.conf.in create mode 100644 Makefile.rules create mode 100644 README create mode 100644 README.active-restart create mode 100644 README.amd-maps create mode 100644 README.autofs-schema create mode 100644 README.changer create mode 100644 README.ncpfs create mode 100644 README.replicated-server create mode 100644 README.smbfs create mode 100644 README.v5.release create mode 100644 aclocal.m4 create mode 100644 autofs.spec create mode 100755 configure create mode 100644 configure.in create mode 100644 daemon/Makefile create mode 100644 daemon/automount.c create mode 100644 daemon/direct.c create mode 100644 daemon/flag.c create mode 100644 daemon/indirect.c create mode 100644 daemon/lookup.c create mode 100644 daemon/module.c create mode 100644 daemon/mount.c create mode 100644 daemon/spawn.c create mode 100644 daemon/state.c create mode 100644 include/automount.h create mode 100644 include/base64.h create mode 100644 include/config.h.in create mode 100644 include/dclist.h create mode 100644 include/defaults.h create mode 100644 include/dev-ioctl-lib.h create mode 100644 include/linux/auto_dev-ioctl.h create mode 100644 include/linux/auto_fs.h create mode 100644 include/linux/auto_fs4.h create mode 100644 include/list.h create mode 100644 include/log.h create mode 100644 include/lookup_ldap.h create mode 100644 include/macros.h create mode 100644 include/master.h create mode 100644 include/mounts.h create mode 100644 include/nsswitch.h create mode 100644 include/parse_amd.h create mode 100644 include/parse_subs.h create mode 100644 include/replicated.h create mode 100644 include/rpc_subs.h create mode 100644 include/state.h create mode 100644 lib/Makefile create mode 100755 lib/alarm.c create mode 100644 lib/args.c create mode 100644 lib/cache.c create mode 100644 lib/cat_path.c create mode 100644 lib/defaults.c create mode 100644 lib/dev-ioctl-lib.c create mode 100644 lib/log.c create mode 100644 lib/macros.c create mode 100644 lib/master.c create mode 100644 lib/master_parse.y create mode 100644 lib/master_tok.l create mode 100644 lib/mount.x create mode 100644 lib/mounts.c create mode 100644 lib/nss_parse.y create mode 100644 lib/nss_tok.l create mode 100644 lib/nsswitch.c create mode 100644 lib/parse_subs.c create mode 100644 lib/rpc_subs.c create mode 100644 man/Makefile create mode 100644 man/auto.master.5.in create mode 100644 man/autofs.5 create mode 100644 man/autofs.8.in create mode 100644 man/autofs.conf.5.in create mode 100644 man/autofs_ldap_auth.conf.5.in create mode 100644 man/automount.8 create mode 100644 modules/Makefile create mode 100644 modules/amd_parse.y create mode 100644 modules/amd_tok.l create mode 100644 modules/base64.c create mode 100644 modules/cyrus-sasl-extern.c create mode 100644 modules/cyrus-sasl.c create mode 100644 modules/dclist.c create mode 100644 modules/lookup_dir.c create mode 100644 modules/lookup_file.c create mode 100644 modules/lookup_hesiod.c create mode 100644 modules/lookup_hosts.c create mode 100644 modules/lookup_ldap.c create mode 100644 modules/lookup_multi.c create mode 100644 modules/lookup_nisplus.c create mode 100644 modules/lookup_program.c create mode 100644 modules/lookup_sss.c create mode 100644 modules/lookup_userhome.c create mode 100644 modules/lookup_yp.c create mode 100644 modules/mount_afs.c create mode 100644 modules/mount_autofs.c create mode 100644 modules/mount_bind.c create mode 100644 modules/mount_changer.c create mode 100644 modules/mount_ext2.c create mode 100644 modules/mount_generic.c create mode 100644 modules/mount_nfs.c create mode 100644 modules/parse_amd.c create mode 100644 modules/parse_hesiod.c create mode 100644 modules/parse_sun.c create mode 100644 modules/replicated.c create mode 100644 patches/autofs4-2.6.18-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.18-v5-update-20100114.patch create mode 100644 patches/autofs4-2.6.19-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.19-v5-update-20100114.patch create mode 100644 patches/autofs4-2.6.20-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.21-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.22-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.22.17-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.23-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.24-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.24.4-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.25-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.26-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.27-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.28-v5-update-20090903.patch create mode 100644 patches/autofs4-2.6.29-v5-update-20090903.patch create mode 100644 patches/util-linux-2.12a-flock.patch create mode 100644 patches/util-linux-2.12q-flock.patch create mode 100644 redhat/Makefile create mode 100644 redhat/autofs.conf.default.in create mode 100644 redhat/autofs.init.in create mode 100644 redhat/autofs.sysconfig create mode 100644 samples/Makefile create mode 100644 samples/auto.master create mode 100644 samples/auto.master.ldap create mode 100644 samples/auto.misc create mode 100755 samples/auto.net create mode 100755 samples/auto.smb create mode 100644 samples/autofs.conf.default.in create mode 100644 samples/autofs.init.conf create mode 100644 samples/autofs.schema create mode 100644 samples/autofs.service.in create mode 100644 samples/autofs_ldap_auth.conf create mode 100644 samples/ldap-automount-auto.direct create mode 100644 samples/ldap-automount-auto.indirect create mode 100644 samples/ldap-automount-auto.master create mode 100644 samples/ldap-automount-rfc2307-bis-auto.direct create mode 100644 samples/ldap-automount-rfc2307-bis-auto.indirect create mode 100644 samples/ldap-automount-rfc2307-bis-auto.master create mode 100644 samples/ldap-automount-rfc2307-bis-old-style-auto.master create mode 100644 samples/ldap-nis-auto.direct create mode 100644 samples/ldap-nis-auto.indirect create mode 100644 samples/ldap-nis-auto.master create mode 100644 samples/rc.autofs.in diff --git a/.autofs-5.1.3 b/.autofs-5.1.3 new file mode 100644 index 0000000..587be6b --- /dev/null +++ b/.autofs-5.1.3 @@ -0,0 +1 @@ +x diff --git a/.version b/.version new file mode 100644 index 0000000..cdb98d2 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +5.1.3 diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..f999eed --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,1314 @@ +24/05/2016 autofs-5.1.3 +======================= +- fix release date in CHANGELOG. +- build: check for clock_gettime in librt. +- fix compiler warning in try_remount(). +- drop redundant \n in logerr(). +- Fix size arg of fgets(3). +- fix libtirpc detection with -Wl,--as-needed. +- Fix a typo in CREDITS. +- Change .requestor to .requester for consistency. +- fix file map changed check. +- Remove unused local 2KB buffer. +- Fix typos in error messages. +- Fix fgets(3) size argument (another one). +- fix short memory allocation in lookup_amd_instance(). +- fix count_mounts() function. +- configure: add cache variable for Linux proc filesystem check. +- Avoid local variable name shadowing another. +- fix typo in MOUNT_FLAG_GHOST comment. +- fix cachefs parse message not being logged. +- fix argc off by one in mount_autofs.c. +- fix _strncmp() usage. +- fix create_client() RPC client handling. +- update and add README for old autofs schema. +- wait for master map available at start. +- add master read wait option. +- work around sss startup delay. +- add sss master map wait config option. +- fix quoted key handling in sanitize_path(). +- fix included master map not found return. +- dont exit on master map read fail timeout. +- set sane default master read wait timeout. +- don't return until after master map retry read. +- make lookup_nss_read_master() return nss status. +- check NFS server availability on local mount fallback. +- make set_direct_mount_catatonic() more general. +- set autofs mounts catatonic at exit. +- honor last rw in mount options when doing a bind mount. +- fix typos in README.amd-maps. +- add ref counting to struct map_source. +- add support for amd browsable option. +- add function conf_amd_get_map_name(). +- add function conf_amd_get_mount_paths(). +- include amd mount sections mounts in master mounts list. +- check for conflicting amd section mounts. +- add function conf_get_map_options(). +- capture cache option and its settings during parsing. +- handle map_option cache for top level mounts. +- handle amd cache option all in amd type auto mounts. +- fix bogus check in expire_cleanup(). +- delay submount exit for amd submounts. +- add the mount requestor's pid to pending_args. +- create thread-local ID for mount attempts. +- log functions to prefix messages with attempt_id if available. +- factor out set_thread_mount_request_log_id(). +- add config option to use mount request log id. +- use autofs_point to store expire timeout where possibe. +- fix possible NULL derefernce. +- fix work around sss startup delay. +- fix invalid reference in remount_active_mount(). +- use malloc for expanded map location. +- fix offset mount location multiple expansion. +- increase worker thread per-thread stack size. +- limit getgrgid_r() buffer size. +- add congigure option for limiting getgrgid_r() stack usage. + +15/06/2016 autofs-5.1.2 +======================= +- update libtirpc workaround for new soname. +- revert fix libtirpc name clash. +- fix left mount count return from umount_multi_triggers(). +- fix return handling in sss lookup module. +- move query dn calculation from do_bind() to do_connect(). +- make do_connect() return a status. +- make connect_to_server() return a status. +- make find_dc_server() return a status. +- make find_server() return a status. +- fix return handling of do_reconnect() in ldap module. +- fix rwlock unlock crash. +- fix config old name lookup. +- fix error handling on ldap bind fail. +- fix direct mount stale instance flag reset. +- fix direct map expire not set for initail empty map. +- fix missing source sss in multi map lookup. +- fix update_hosts_mounts() return. +- change lookup to use reinit instead of reopen. +- update map_hash_table_size description. +- add configuration option to use fqdn in mounts. +- fix out of order call in program map lookup. +- fix error handling of is_mounted(). +- Add a mode option for master map entries. +- define monotonic clock helper functions. +- use monotonic clock for alarm thread condition wait. +- define pending condition init helper function. +- use monotonic clock for direct mount condition. +- use monotonic clock for indirect mount condition. +- change remaining gettimeofday() to use clock_gettime(). +- change time() to use monotonic_clock(). +- remove unused function elapsed(). +- fix unbind sasl external mech. +- fix sasl connection concurrancy problem. +- fix memory leak in nisplus lookup_reinit(). +- fix memory leak in ldap do_init(). +- fix use after free in sun parser parse_init(). +- fix use after free in open_lookup(). +- fix typo in autofs_sasl_bind(). +- fix memory leak in get_network_proximity(). +- fix use after free in match_my_name(). +- improve scalability of direct mount path component. +- always set direct mounts catatonic at exit. +- fix use-after-free in st_queue_handler(). +- log pipe read errors. +- fix handle_mounts() termination condition check. +- fix Makefile linking dependencies. +- fix modules make clean target. +- fix autofs(5) description of supported map sources. +- add autofs(5) note of IPv6 libtirpc requirement. +- add remote-fs.target systemd dependency. +- fix typo in autofs.conf. +- fix yp map age not updated during map lookup. +- add config option to supress not found log message. +- fix possible memory leak in nfs mount. + +21/04/2015 autofs-5.1.1 +======================= +- fix compile error in defaults.c. +- add serialization to sasl init. +- dont allocate dev_ctl_ops too early. +- fix incorrect round robin host detection. +- fix race accessing qdn in get_query_dn(). +- fix leak in cache_push_mapent(). +- fix config entry read buffer not checked. +- fix FILE pointer check in defaults_read_config(). +- fix memory leak in conf_amd_get_log_options(). +- fix signed comparison in inet_fill_net(). +- fix buffer size checks in get_network_proximity(). +- fix leak in get_network_proximity(). +- fix buffer size checks in merge_options(). +- check amd lex buffer len before copy. +- add return check in ldap check_map_indirect(). +- check host macro is set before use. +- check options length before use in parse_amd.c. +- fix some out of order evaluations in parse_amd.c. +- fix copy and paste error in dup_defaults_entry(). +- fix leak in parse_mount(). +- add mutex call return check in defaults.c. +- force disable browse mode for amd format maps. +- fix hosts map options check in lookup_amd_instance(). +- fix memory leak in create_client(). +- fix memory leak in get_exports(). +- fix memory leak in get_defaults_entry(). +- fix out of order clearing of options buffer. +- fix reset amd lexer scan buffer. +- ignore multiple commas in options strings. +- fix typo in flagdir configure option. +- clarify multiple mounts description. +- gaurd against incorrect umount return. +- update man page autofs(8) for systemd. +- dont pass sloppy option for other than nfs mounts. +- make service want network-online. +- fix fix master map type check. +- init qdn before use in get_query_dn(). +- fix typo in update_hosts_mounts(). +- fix hosts map update on reload. +- make negative cache update consistent for all lookup modules. +- ensure negative cache isn't updated on remount. +- dont add wildcard to negative cache. +- add a prefix to program map stdvars. +- add config option to force use of program map stdvars. +- fix incorrect check in parse_mount(). +- handle duplicates in multi mounts. +- revert special case cifs escapes. +- fix map option parsing for 'strictatime'. +- fix showmount search in auto.net. +- remove obsolete comment in auto.net. +- fix macro usage in lookup_program.c. +- fix gcc5 complaints. +- remove unused offset handling code. +- fix mount as you go offset selection. +- link daemon with pthread library (Debian patch). +- manpage corrections (Debian patch). +- fix manpages hyphenation (Debian patch). + +04/06/2014 autofs-5.1.0 +======================= +- fix mistake in assignment. +- add amd map format parser. +- check for non existent negative entries in lookup_ghost(). +- fix reset flex scan buffer on init. +- fix fix negative status being reset on map read. +- amd lookup update lookup ldap to handle amd keys. + - inadvertantly dropped from initial series. +- amd lookup update lookup hesiod to handle amd keys. + - inadvertantly dropped from initial series. +- fix wildcard key lookup. +- fix out of order amd timestamp lookup. +- fix ldap default schema config. +- fix ldap default master map name config. +- fix map format init in lookup_init(). +- fix incorrect max key length in defaults get_hash(). +- fix xfn sets incorrect lexer state. +- fix old style key lookup. +- fix expire when server not responding. +- fix ldap_uri config update. +- fix typo in conf_load_autofs_defaults(). +- fix hash on confg option add and delete. +- add plus to path match pattern. +- fix multi entry ldap option handling. +- cleanup options in amd_parse.c +- allow empty value for some map options. +- allow empty value in macro selectors. + +28/03/2014 autofs-5.0.9 +======================= +- fix undefined authtype_requires_creds err if ldap enabled but without sasl. +- fix master map type check. +- fix task manager not getting signaled. +- allow --with-systemd to take a path arg. +- fix WITH_LIBTIRPC function name. +- fix ipv6 libtirpc getport. +- fix ipv6 link local address handling. +- fix fix ipv6 libtirpc getport. +- get_nfs_info() should query portmapper if port is not given. +- fix rpc_portmap_getport() proto not set. +- fix protmap not trying proto v2. +- fix rpc_getport() when libtirpc is disabled. +- fix rpc_getrpcbport() when libtirpc is disabled. +- don't reset errno. +- extend fix for crash due to thread unsafe use of libldap. +- fix deadlock in init_ldap_connection. +- fix options compare. +- fix negative status being reset on map read. +- check for existing offset mount before mounting. +- fix max() declaration. +- fix symlink fail message in mount_bind.c. +- fix cache readlock not taken on lookup. +- pass map_source as function paramter where possible. +- check for bind onto self in mount_bind.c. +- fix symlink expire. +- dont clobber mapent for negative cache. +- fix macro_addvar() and move init to main thread. +- change walk_tree() to take ap. +- add negative cache lookup to hesiod lookup. +- fix external env configure. +- make autofs(5) consistent with auto.master(5). +- fix map source with type lookup. +- fix lookup_nss_mount() map lookup. +- dont ignore null cache entries on multi mount umount. +- fix inconsistent error returns in handle_packet_missing_direct(). +- simple coverity fixes. +- fix fix options compare. +- use open(2) instead of access(2) to trigger dependent mounts. +- fix fix map source with type lookup. +- fixes for samples/auto.master. +- fix variable substitution description. +- fix incorrect append options description in README.v5-release. +- add amd map format parser. + +17/10/2013 autofs-5.0.8 +======================= +- fix nobind sun escaped map entries. +- fix use cache entry after free in lookup_prune_one_cache(). +- fix ipv6 proximity calculation. +- fix parse buffer initialization. +- fix typo in automount(8). +- dont wait forever to restart. +- add timeout option description to man page. +- fix null map entry order handling. +- make description of default MOUNT_WAIT setting clear. +- configure.in: allow cross compilation. +- README: update mailing list subscription info. +- allow non root user to check status. +- fix recursive mount deadlock. +- increase file map read buffer size. +- handle new location of systemd. +- fix map entry duplicate offset detection. +- Allow nsswitch.conf to not contain "automount:" lines. +- fix nobind man page description. +- fix submount offset delete. +- fix init script status return. +- fix use get_proximity() without libtirpc. +- don't use dirent d_type to filter out files in scandir() +- don't schedule new alarms after readmap. +- use numeric protocol ids instead of protoent structs. +- lib/defaults.c: use WITH_LDAP conditional around LDAP types. +- make yellow pages support optional. +- modules/replicated.c: use sin6_addr.s6_addr32. +- workaround missing GNU versionsort extension. +- dont fail on master map self include. +- fix wildcard multi map regression. +- fix file descriptor leak when reloading the daemon. +- depricate nosymlink pseudo option. +- add symlink pseudo option. +- fix requires in spec file. +- fix libtirpc build option to require libtirpc-devel if needed. +- fix systemd unidir in spec file. +- document browse option in man page. +- fix some automount(8) typos. +- syncronize handle_mounts() shutdown. +- fix submount tree not all expiring. +- make dump maps check for duplicate indirect mounts. +- document allowed map sources in auto.master. +- add enable sloppy mount option to configure. +- fix interface address null check. +- dont probe rdma mounts. +- fix master map mount options matching. +- fix master map bogus keywork match. +- fix fix map entry duplicate offset detection. +- probe each nfs version in turn for singleton mounts. +- add changlog entry for coverity fixes. +- fix probe each nfs version in turn for singleton mounts. +- misc man page fixes. +- fix add null check in parse_server_string(). +- don't override LDFLAGS in make rules. +- fix a couple of compiler warnings. +- add after sssd dependency to unit file. +- dont start readmap unless ready. +- fix crash due to thread unsafe use of libldap. +- fix compile error with heimdal support enabled. +- fix typo forced-shutdown should be force-shutdown. +- fix hesiod check error and use correct $(LIBS) setting. +- fix dead LDAP symbolic link when LDAP support is disabled. +- add missing libtirpc lib to mount_nfs.so when TIRPC enabled. +- use compiler determined by configure instead of hard-coded ones. +- remove hard-coded STRIP variable. +- use LIBS for link libraries. +- unbundle NOTSTRIP from DEBUG so they dont depend on each other. +- fix compilation of lookup_ldap.c without sasl. +- fix dumpmaps multi output. +- try and cleanup after dumpmaps. +- teach dumpmaps to output simple key value pairs. +- fix syncronize handle_mounts() shutdown. +- fix fix wildcard multi map regression. +- improve timeout option description. +- only probe specific nfs version if requested. +- fix bad mkdir permission on create. +- setup program map env from macro table. +- add short host name standard marco variable. +- fix get_nfs_info() probe. +- fix portmap lookup. +- add std vars to program map invocation. +- samples/auto.smb: add logic to obtain credentials. + +25/07/2012 autofs-5.0.7 +======================= +- fix ipv6 name for lookup fix. +- improve mount location error reporting. +- fix paged query more results check. +- fix dumpmaps not reading maps. +- fix result null check in read_one_map(). +- fix LDAP result leaks on error paths. +- fix fix LDAP result leaks on error paths. +- code analysis fixes part 1. +- fix not bind mounting local filesystem. +- add "dir" map-type. +- fix wait for master source mutex. +- fix submount shutdown race. +- fix fix map source check in file lookup. +- add disable move mount configure option. +- fix ipv6 name lookup check. +- fix ipv6 rpc calls. +- fix ipv6 configure check. +- add piddir to configure. +- add systemd unit support. +- remove empty command line arguments (passed by systemd). +- fix rpc build error. +- fix improve mount location error reporting. +- fix fix wait for master source mutex. +- add sss lookup module. +- teach automount about sss source. +- fix init script usage message. +- ignore duplicate exports in auto.net. +- add kernel verion check function. +- add function to check mount.nfs version. +- reinstate singleton mount probe. +- rework error return handling in rpc code. +- catch EHOSTUNREACH and bail out early. +- systemd support fixes. +- check scandir() return value. +- allow for kernel packet size change (in kernel 3.3.0+). +- fix function to check mount.nfs version. +- fix typo in libtirpc file name. +- fix rework error return handling in rpc code. +- allow MOUNT_WAIT to override probe. +- improve UDP RPC timeout handling. +- use strtok_r() in linux_version_code(). +- fix sss wildcard match. +- fix dlopen() error handling in sss module. +- fix configure string length tests for sss library. +- report map not read when debug logging. +- duplicate parent options for included maps. +- update ->timeout() function to not return timeout. +- move timeout to map_source (allow per direct map timeout). +- fix kernel verion check of version components. +- dont retry ldap connect if not required. +- fix initialization in rpc create_client(). +- fix libtirpc name clash. +- check if /etc/mtab is a link to /proc/self/mounts. +- fix nfs4 contacts portmap. +- make autofs wait longer for shutdown completion. +- fix sss map age not updated. +- fix remount deadlock. +- fix umount recovery of busy direct mount. +- fix offset mount point directory removal. +- fix remount of multi mount. +- fix devce ioctl alloc path check. +- add hup signal handling to hosts map. +- fix systemd argument passing. +- fix get_nfs_info() can incorrectly fail. +- fix offset directory removal. +- check negative cache much earlier. +- dont use pthread_rwlock_tryrdlock(). +- mount_nfs.so to honor explicit NFSv4 requests. +- mount_nfs.so fix port=0 option behavior v3. +- documentation fix some typos and misleading comments. + +28/06/2011 autofs-5.0.6 +----------------------- +- fix included map read fail handling. +- refactor ldap sasl bind handling. +- add mount wait timeout parameter. +- special case cifs escapes. +- fix compile fail with when LDAP is excluded. +- more code analysis corrections (and fix a typo in an init script). +- fix backwards #ifndef INET6. +- fix stale initialization for file map instance. +- add "preen" fsck for ext4 mounts. +- don't use master_lex_destroy() to clear parse buffer. +- make documentation for set-log-priority clearer. +- fix timeout in connect_nb(). +- fix pidof init script usage. +- check for path mount location in generic module. +- dont fail mount on access fail. +- fix rpc fail on large export list. +- fix memory leak on reload. +- update kernel patches for 2.6.18 and 2.6.19. +- dont connect at ldap lookup module init. +- fix random selection option. +- fix disable timeout. +- fix strdup() return value check (Leonardo Chiquitto). +- fix reconnect get base dn. +- add missing sasl mutex callbacks. +- fix get query dn failure. +- fix ampersand escape in auto.smb. +- add locality as valid ldap master map attribute. +- add locality as valid ldap master map attribute fix. +- add simple bind authentication. +- fix master map source server unavailable handling. +- add autofs_ldap_auth.conf man page. +- fix random selection for host on different network. +- make redhat init script more lsb compliant. +- don't hold lock for simple mounts. +- fix remount locking. +- fix wildcard map entry match. +- fix parse_sun() module init. +- dont check null cache on expire. +- fix null cache race. +- fix cache_init() on source re-read. +- fix mapent becomes negative during lookup. +- check each dc server individually. +- fix negative cache included map lookup. +- remove state machine timed wait. +- remove extra read master map call. +- fix error handing in do_mount_indirect(). +- expire thread use pending mutex. +- remove ERR_remove_state() openssl call. +- fix init script restart option. +- fix init script status privilege error. +- always read file maps mount lookup map read fix. +- fix direct map not updating on reread. +- add external bind method. +- fix add simple bind auth. +- add option to dump configured automount maps. +- use weight only for server selection. +- fix isspace() wild card substition. +- auto adjust ldap page size. +- fix prune cache valid check. +- fix mountd vers retry. +- fix expire race. +- replace GPLv3 code. +- fix paged ldap map read. +- fix next task list update. +- fix stale map read. +- fix null cache clean. +- automount(8) man page correction. +- fix out of order locking in readmap. +- include ip address in debug logging. +- mount using address for DNS round robin host names. +- reset negative status on cache prune. +- remove master_mutex_unlock() leftover. +- fix sanity checks for brackets in server name. +- fix lsb service name in init script. +- fix map source check in file lookup. +- fix simple bind without SASL support. +- fix sasl bind host name selection. +- add nobind option. +- add base64 password encode. +- fix ipv6 name for lookup. +- fix libtirpc ipv6 check. + +03/09/2009 autofs-5.0.5 +----------------------- +- fix dumb libxml2 check +- fix nested submount expire deadlock. +- fix negative caching for non-existent map keys. +- use CLOEXEC flag. +- fix select(2) fd limit. +- make hash table scale to thousands of entries (Paul Wankadia, + Valerie Aurora Henson). +- clear the quoted flag after each character from program map input. +- use CLOEXEC flag for setmntent also. +- fix hosts map use after free. +- fix uri list locking (again). +- check for stale SASL credentials upon connect fail. +- add "forcestart" and "forcerestart" init script options to allow + use of 5.0.3 strartup behavior if required. +- always read entire file map into cache to speed lookups. +- make MAX_ERR_BUF and PARSE_MAX_BUF use easier to audit. +- make some easy alloca replacements (Valerie Aurora Henson). +- update to configure libtirpc if present. +- update to provide ipv6 name and address support. +- update to provide ipv6 address parsing. +- easy alloca replacements fix. +- add check for alternate libxml2 library for libxml2 tsd workaround. +- add check for alternate libtirpc library for libtirpc tsd workaround. +- cleanup configure defines for libtirpc. +- add WITH_LIBTIRPC to -V status report. +- add nfs mount protocol default configuration option. +- fix bad token declaration in master map parser. +- fix return start status on fail. +- fix double free in expire_proc(). +- another easy alloca replacements fix. +- add LSB init script parameter block. +- fix file map lookup when reading included or nsswitch sources. +- use misc device ioctl interface by default, if available. +- fix call restorecon when misc device file doesn't exist. +- clear rpc client on lookup fail. +- fix lsb init script header. +- fix memory leak reading master map. +- fix st_remove_tasks() locking. +- reset flex scanner when setting buffer. +- zero s_magic is valid. +- use percent hack for master map keys. +- use intr option as hosts mount default. +- fix kernel includes. +- dont umount existing direct mount on master re-read. +- fix incorrect shutdown introduced by library relaod fixes. +- improve manual umount recovery. +- dont fail on ipv6 address when adding host. +- always read file maps multi map fix. +- always read file maps key lookup fixes. +- use srv query for domain dn. +- fix not releasing resources when using submounts. +- fix notify mount message path. +- remount we created mount point fix. +- fix double free in sasl_bind(). +- mannual umount recovery fixes. +- fix map type info parse error. +- fix an RPC fd leak. +- don't block signals we expect to dump core. +- fix pthread push order in expire_proc_direct(). +- fix libxml2 non-thread-safe calls. +- fix direct map cache locking. +- fix dont umount existing direct mount on reread. +- update kernel patches. + +4/11/2008 autofs-5.0.4 +----------------------- +- correct configure test for ldapr page control functions. +- catch "-xfn" map type and issue "no supported" message. +- correction for handling of LDAP base dns with spaces. +- avoid using UDP for probing NFSv4 mount requests. +- use libldap instead of libldap_r (Guillaume Rousse). +- another fix for don't fail on empty master map. +- fix expire working harder than needed. +- fix unlink of mount tree incorrectly causing autofs mount fail. +- update kernel header file linux/auto_fs4.h. +- update fix expire working harder than needed. +- add missing check for zero length NIS key (Wengang Wang). +- init SASL callbacks on every ldap lookup library load. +- fix incorrect match of map type name when included in map name. +- fix incorrect pthreads condition handling for mount requests. +- add check for exports automatically mounted by NFS kernel client. +- update nsswitch parser to ignore nsswitch sources that aren't supported. +- check for map key in (possible) alternate map sources when doing lookup. +- eliminate redundant DNS name lookups. +- additional fix incorrect pthreads condition handling for mount requests. +- allow mount point directory creation for clients with an NFS root. +- fix direct mount path length not being checked. +- fix incorrect if check in get user info. +- fix couple of memory leaks. +- add command line option to override check for daemon already running. +- don't use proc file system when checking if the daemon is running. +- make handle_mounts startup condition distinct. +- fix submount shutdown recovery handling. +- avoid stat of possibly dead mount points and limit time to wait for + umount during expire. +- make mount of multi-mounts wuth a root offset atomic. +- add replicated server selection debug logging. +- update replicated server selection documentation. +- use /dev/urandom instead of /dev/random. +- check for mtab pointing to /proc/mounts. +- dynamically allocate interface config buffer. +- update kernel patches. +- fix fd leak at multi-mount non-fatal mount fail. +- fix incorrect multi-mount mountpoint calcualtion. +- fix map out of order map re-read on hup signal. +- fix nisplus error return check and use after free error. +- fix rootless direct multi-mount expire. +- wait submount expire thread completion. +- add missing uris list locking. +- fix segv during library re-open. +- fix incorrect pthreads condition handling for expire requests. +- fix $mandir definition in Makefile.conf.in +- fix init script stop function. +- fix master map lexer eval order. +- fix bad alloca usage. +- add miscellaneous device node interface library. +- use miscellaneous device node, if available, for active restart. +- make is_mounted() use new ioctl interface, if available. + +14/01/2008 autofs-5.0.3 +----------------------- +- include krb5.h in lookup_ldap.h (some openssl doesn't implicitly include it). +- correct initialization of local var in parse_server_string. +- add missing "multi" map support. +- add multi nsswitch lookup. +- change random multiple server selection option name to be consistent + with existing downstream version 4 naming. +- fix mount point directory creation for bind mounts. +- add quoting for exports gathered by hosts map. +- fix wait time resolution in alarm and state queue handlers. +- fix handling of quoted slash alone. +- fix parse confusion between attribute and attribute value. +- fix version passed to get_supported_ver_and_cost. +- mark map instances stale so they aren't "cleaned" during updates. +- fix large file compile time option. +- don't fail on empty master map. +- add support for the "%" hack for case insensitive attribute schemas. +- fix "nosymlink" option handling and add desription to man page. +- fix don't fail on empty master map. +- if there's no "automount" entry in nsswitch.conf use "files" source. +- add LDAP schema discovery if no schema is configured. +- add random selection as a master map entry option. +- fix couple of edge case parse fails of timeout option. +- check for "*" when looking up wildcard in LDAP. +- fix LDAP schema discovery. +- add SEARCH_BASE configuration option. +- work around segv at exit due to libxml2 tsd usage. +- re-read config on HUP signal. +- add LDAP_URI, LDAP_TIMEOUT and LDAP_NETWORK_TIMEOUT configuration options. +- fix forground logging and add option to man page. +- remove unjustified, nasty comment about krb5 package. +- fix deadlock in submount mount module. +- fix lack of ferror() checking when reading files. +- fix typo in autofs(5) man page. +- fix map entry expansion when undefined macro is present. +- remove unused export validation code. +- add dynamic logging (adapted from v4 patch from Jeff Moyer). +- fix recursive loopback mounts (Matthias Koenig). +- add map re-load to verbose logging. +- fix handling of LDAP base dns with spaces. +- handle MTAB_NOTUPDATED status return from mount. +- when default master map, auto.master, is used also check for auto_master. +- fix schema selection in LDAP schema discovery. +- update negative mount timeout handling. +- fix large group handling (Ryan Thomas). +- fix for dynamic logging breaking non-sasl build (Guillaume Rousse) +- eliminate NULL proc ping for singleton host or local mounts. +- fix incorrect read/write size of startup status token (Matthias Koenig). +- fix off-by-one error for lookup of map keys exactly 255 characters long. +- improve handling of server not available. +- fix LDAP_URI server selection. +- add authentication option for using an external credential cache. +- expand support for the "%" hack. +- fix to quoting for exports gathered by hosts map. +- use mount option "nosuid" for "-hosts" map unless "suid" is explicily specified. +- second attempt fixing quoting for exports gathered by hosts map. +- quell annoying "cannot open mount module" message. +- fix for improve handling of server not available. +- use mount option "nodev" for "-hosts" map unless "dev" is explicily specified. +- add LDAP paged query handling to deal with query size restrictions (Edward Newman). +- add additional case for "mark map instances stale so they aren't "cleaned" during updates". +- fix race during sub-mount shutdown. +- fix add SEARCH_BASE configuration option. +- update kernel patches. + +18/06/2007 autofs-5.0.2 +----------------------- +- fix return check for getpwuid_r and getgrgid_r. +- give up trying to update exports list while host is mounted. +- fix to "@network" matching. +- check for fstab update and retry if not updated. +- change file map lexer to allow white-space only blank lines. +- remove macro substitution in automount.8 man page (Guillaume Rousse). +- correct hesiod library check in configure (Guillaume Rousse). +- drop "DEFAULT_" prefix from configuration names (Guillaume Rousse). +- remove redundant ident macros. +- various configure cleanups (Richard Daniel). +- various code cleanups (Richard Daniel). +- fixed numeric export match. +- add option to select replicated server at random (instead of response time). +- fix incorrect cast in directory cleanup routines. +- fix directory creation for browse mounts. +- fix wildcard map handling and improve nsswitch source map re-reading. +- fix "null" domain netgroup match for "-hosts" map. +- update kernel patches. +- add configuration variable to control appending of global options. +- add command option to set a global mount options string. +- add check for labeled local filesystems (Matthias Koenig). +- disable exports check for "-hosts" map. +- fix memory allocation problem with global options patch. +- fix master map lexer to admit "." in macro values (Mike Matera). +- make ldap attribute match case insensitive. +- add missed man page update for APPEND_OPTIONS config option. +- add ldaps protocol support. + - note: it's no longer possible to multiple hosts in an ldap map spec. + - note: if this is needed use only the map name and configure the URI + entry in the ldap client configuration. +- correct mistake in logic test in wildcard lookup. +- fix deadlock in alarm manager module. +- allow for older schemas that allow "*" as a key value. +- update master map lexer to also allow "." in macro name. +- allow for "#" and "@" in hostname validation for sshfs mounts. +- set working directory to base of mount before invoking program map. +- simplify alarm_handler function (Anders Blomdell). + +20/2/2007 autofs-5.0.1 +---------------------- +- fix typo in Fix typo in var when removing temp directory. +- remove redundant rpath link option. +- ignore "winbind" if it appears in "automount" nsswitch.conf. +- fix another expire regression introduced in the "mitigate manual umount" patch. +- correct check for busy offset mounts before offset umount. +- make double quote handing consistent. +- fix handling of trailing white space in wildcard lookup. +- check fqdn of each interface when matching export access list. +- fix race when setting task done. +- correct return status from do_mkdir. +- fix localhost replicated mounts not working. +- add "condrestart" to RedHat init script. +- add "@network" and .domain.name export check. +- fix display map name in mount entry for "-hosts" map. +- update kernel patches. + +4/1/2007 autofs-5.0.1 rc3 +------------------------- +- fix handling of autofs specific mount options. +- fix include check full patch for file map of same name. +- fix cache entrys not being cleaned up on submount expire. +- fix LDAP lookup delete cache entry only if entry doesn't exist. +- add missing socket close in replicated host check (Jeff Moyer). +- remove unused option UNDERSCORETODOT from default config files. +- make default installed master map for /net use "-hosts" instead + of auto.net. +- fix included map recursive map key lookup. + - and fix the recursive map key lookup for browsable map case. +- review and fix master map options update for map reload. +- fix "-fstype=nfs4" handling. +- fix get_query_dn not looking in subtree for LDAP search. +- allow syntax "--timeout " for backward compatibility. +- make masked_match independent of hostname for exports comparison. +- fix file handle leak in nsswitch parser. +- fix memory leak in mount and expire request processing. +- add additional check to prevent running of cancelled tasks. +- fix potential file handle leakage in rpc_subs.c for some failure cases. +- fix file handle leak in included map lookup. +- fix "-fstype=nfs4" server probing. +- set close-on-exec flag on open files where possible. +- fix parsing of numeric host names in LDAP map specs. +- fix get_query_dn not looking in subtree for LDAP search (missed second + occurance). +- allow additional common LDAP attributes in map dn. +- deal with changed semantics of mkdir in 2.6.19. +- fix macro table locking. +- fix nsswitch parser locking. +- allow only one master map read task at a time. +- fix misc memory leaks. +- mitigate manual umount of automounts where possible. +- fix multiply recursive bind mounts. +- check kernel module version and require 5.00 or above. +- fix expire regression introduced in the "mitigate manual umount" patch. +- still more on multiply recursive bind mounts. +- fix tokenizer to distinguish between global option and dn string. +- fix incorrect return from spawn (Gordon Lack). +- fix parsing of bad mount mount point in master map. +- fix use after free memory access in cache.c and lookup_yp.c. +- eliminate use of pthread_kill to detect task completion. +- alter nfs4 host probing to not use portmap lookup and add options + check for "port=" parameter. +- correct semantics of "-null" map handling. +- remove ability to use multiple indirect mount entries in master map. +- expand export access checks to include missing syntax options. +- make "-hosts" module try to be sensitive to exports list changes. +- change mount "device" from "automount" to the map name. +- check for buffer overflow in mount_afs.c. +- update master map tokenizer to admit "slasify-colons" option (Capelle Bonoit). +- update location validation to accept "_" (Fabio Olive Leite). +- set close-on-exec flag on open sockets. +- fix nonstrict multi-mount handling. +- reduce thread stack to less excessive size. +- update kernel patches. + +1/9/2006 autofs-5.0.1 rc2 +------------------------- +- code cleanup. +- fix race for current map source. +- cthon map parser corrections. +- cthon multi-map locking fix and current race corrections. +- cthon shutdown expire fix. +- cthon more map parser corrections. +- cthon cleanup and corrections. +- cthon more cleanup and corrections. +- cthon correction to host validation. +- cthon fix submount operation broken by above. +- cthon more parser corrections and attempt to fix multi-mounts + with various combinations of submounts (still broken). +- cthon fix expire of various forms of nested mounts. +- cthon fix some shutdown races. +- cthon corrections for above patch and fix shutdown expire. +- cthon fix expire of wildcard and program mounts broken by above + patches. +- tidy up directory cleanup and add validation check to rmdir_path. +- remove SIGCHLD handler. +- alter expire locking of multi-mounts to lock sub-tree instead of + entire tree. +- review verbose message feedback and update. +- correction for expire of multi-mounts. +- spelling corrections to release notes (Jeff Moyer). +- expire individual submounts. +- add ino_index locking. +- fix nested submount expiring away when pwd is base of submount. +- more expire re-work to cope better with shutdown following cthon tests. +- allow hostname to start with numeric when validating. +- fix included map lookup. +- fix directory cleanup on expire. +- fix task cancelation at shutdown. +- fix included map wild card key lookup. +- fix task cancelation at shutdown (more). +- fix concurrent mount and expire race with nested submounts. +- fix colon escape handling. +- fix recusively referenced bind automounts. +- update kernel patches. + +13/7/2006 autofs-5.0.1 rc1 +-------------------------- +- merge LDAP authentication update for GSSAPI (Jeff Moyer). +- update default auth config to add options documenetation (Jeff Moyer). +- workaround segfaults at exit after using GSSAPI library. +- fix not checking return in init_ldap_connection (Jeff Moyer). +- correct test for existence of auth config file. +- correct shutdown log message print. +- correct auth init test when no credentials required. +- correct auto.net installed as auto.smb. +- update LDAP auth - add autodectect option. +- correct test for libhesiod. +- correct directory cleanup in mount modules. +- merge key and wildcard LDAP query for lookups. +- add cacheing of negative lookups to reduce unneeded map lookups. +- version number change to allow update from beta for rpm packages. + +29/6/2006 autofs-5.0.0_beta6 +---------------------------- +- lookup_init cleanup and fix missed memory leak. +- use nis map order to check if update is needed. +- fix couple of memory leaks in lookup_yp.c. +- fix pasre error in replicated server module. +- correct spelling error in default config. +- fix default auth config not being installed. +- change LDAP query method as my test db was incorrect. +- change ldap defaults code to handle missing auth config. +- fix mistake in parsing old style LDAP specs. +- update LDAP so that new query methos also works for old + syntax. +- allow global macro defines to override system macros. +- cleanup defaults_read_config. +- add rfc2307-bis example map configurations. +- change mode of of config file install to 644. +- update kernel patches. +- fix don't unbind on error return from get_query_dn. +- update RedHat autofs default config. + +20/6/2006 autofs-5.0.0_beta5 +--------------------------- +- re-instate v4 directory cleanup. +- backout master map lookup changes made to beta3. +- change default master map from /etc/auto.master to auto.master + so that we always use nsswitch to locate master map. +- change default installed master map to include "+auto.master" + to pickup NIS master map. +- correct config names in default.c (jpro@bas.ac.uk). +- check base of offset mount tree is not a mount before umounting + its offsets. +- fix replicated mount parse for case where last name in list + fails lookup. +- correct indirect mount expire broken by the wildcard lookup fix. +- fix up multi-mount handling when wildcard map entry present. +- fix mutex not being unlocked on map read. +- fix rpc routines not logging errors. +- fix handling of invalid directory for nobrowse hosts map + lookup. +- add free for working var in get_default_logging. +- add inialisation for kver in autofs_point struct. +- fix sources list corruption in check_update_map_sources. +- fix memory leak in walk_tree. +- fix memory leak in rpc_portmap_getport and rpc_ping_proto. +- fix memory leak in initialisation of lookup modules. +- fix handling of master map entry update. +- fix program map handling of invalid multi-mount offsets. +- move autofs4 module loading back to init script. +- add export access list matching to "hosts" lookup module. +- add check for key length to long (Jeff Moyer). +- add patch to restrict /proc lookup to pid directories (Jeff Moyer). +- fix directory cleanup at exit. + +2/6/2006 autofs-5.0.0_beta4 +--------------------------- +- fix memory alloc error in nis lookup module. +- add "_" to "." mapname translation to nis lookup module. +- fix white space handling in replicated server selection code. +- merge don't strip debug info macro patch (Jeff Moyer). +- merge patch to add sanity checks on rmdir_path and unlink (Jeff Moyer). +- merge patch fix e2fsck error code check (Jeff Moyer). +- update hesiod module (Jeff Moyer). + - add mutex to protect against overlapping mount requests. + - update return from mount request to give more sensible NSS_* + values. +- fix handling of autofs filesystem mount fail on init. +- add back test test for nested mount in program map lookup. + - I must have commented this out for a reason. I guess we'll + find out soon enough. +- fix "-hosts" map not finding entry for the "nobrowse" case. +- update man page with description of "-hosts" map type. +- complete logging, including per mount logging. + - still needs some more work but that will need to wait. +- change init script to check for read on config file instead + of execute. +- fix lookup of wildcard map entry for several map types. +- fix fail to realease cache read lock on indirect map expire. +- fix incorrect cache update of wildcard map entry. +- alter master map lookup semantics to be a little more like + version 4. The way we should locate the master map needs + more investigation and may change again. + +23/5/2006 autofs-5.0.0_beta3 +---------------------------- +- add config and compile time info to output of -V. +- add "-Dvar=value" comand line option to define global + macro definitions. +- remove "nofg" and "bg" options as they aren't relevant for + autofs. +- fix handling of numeric ip address strings in name translation. +- fix error handling for gethostbyname_r. +- change option processing to be inline with standard automount + option processing. ie. mount entry options override global + mount options. +- replicated server selection re-write. + +9/5/2006 autofs-5.0.0_beta2 +--------------------------- +- re-organize functions for direct mount and expire to eliminate + pthread compile error. +- reap return code from child at startup. +- add check for self inclusion when including maps. +- add plus map inclusion depth limit. +- fix logic to check if map update is needed. +- fix shutdown wait for submounts again (and again ...). +- add check for filesystem autofs in /proc/filesystems in case + it's complied in the kernel. +- update Gentoo ebuild. +- updated man pages based on feedback from Jeff Moyer. + +1/5/2006 autofs-5.0.0_beta1 +--------------------------- +- Initial update. + - direct mounts. + - lazy multi-mounts. + - added kernel module to source tree. +- merged depricated LDAP patch. +- merged configurable locking patch. +- implemented nsswitch lookup for client maps. +- sync kernel module source. +- more kernel module fixes. +- redo mapenty cache locking and start work on + background map refresh. +- fix hosts map (broken by mapentry cache locking update). +- fix expire of multi-mount with no root mount. +- final v5 comms packet definition (famous last words). +- remove goto from loop. +- add missing definition of _GNU_SOURCE for strerror_r. +- fix error handling fix for SIGCHLD handler. +- read map in sub-thread progresses. +- add cancelation cleanup routine to expire. +- correct error handling in lookup_ghost. +- remove redundant pthread mutex definition from mapent_cache. +- merged signal_children cleanup patch. +- first cut '+' included maps for client maps. +- move directory management of cache prune out of cache code. +- fix cache enumeration. +- fix cache update of multi-mount entries on umount. +- fix fail to close ioctl on direct mount fail. +- add hesiod to nss source list. +- allow for maps that don't support enumeration. +- add '+' included maps in key lookup (missed this). +- fix update detection following addition of '+' key lookup. +- convert mapent cache to live within autofs_point struct. +- convert submounts to use a thread instead of a process. +- fix rootless multi-mount as target of direct mount. +- fix sub-mount brakage following above. +- fix sub-mount as target of direct mount. +- update mounts.c to use re-entrant getmntent. +- make substution table re-entrant. + - divide into global and local symbol tables. +- implement per thread storage for extended macro vars + (ie. UID, GID, USER, GROUP etc.). +- fix expandsunent not returning fail on failed macro translation. +- fix expandsunent return no checked. +- add EXPERIMENTAL forced shutdown. +- fixed race in expire. +- fixed race in alarm module. +- updated readmap to be autofs_point specific. +- integrated master map parsing into daemon. + - fixed several brakages from above. + - implemented state task manager to improve concurrency. + - fixed thread creation problem with expire and read map. + - worked on master map update (not quite right yet). +- fix (dev, ino) cache lookup. +- convert spawn locking to use mutex instead of file. + - seems to fix hang during mounting ~1000 direct mounts. +- fix expire and readmap thread create. +- fix read multiple sources for mount point. +- fix hanging state machine (again). +- add delay at startup till mount complete. +- initial merge of Jeffs SASL auth code. + - entirely untest as yet. +- re-work LDAP to use configurable defaults. +- updated configure to check for libxml2 - required by SASL code. +- updated configure to provide optional inclusion of SASL auth. +- add check for automount already running. +- attempt to fix excessive CPU usage (not quite there yet). +- make samples install try harder to make backup of existing files. +- SASL auth code functional and added START_TLS code. + - START_TLS doesn't seem to work yet, no idea why. +- fix mount tree find mounts subroutine. +- get START_TLS code working. +- cleanup map parsing and nsswitch lookup in lookup.c. +- first pass documentation update. +- strugle with FSM problems again. +- add recovery of existing automounts at startup. +- add configure option to enable exit leaving busy mounts mounted. +- sort out code for multiple maps per mount point. +- fix side effects on multi-mounts and submounts from above. +- test and fix basic map reread functions. +- remove need to read entire map when browsing is disabled. +- fix lookup context not being released. +- test and fix map re-read for master map entries with multiple entries. +- fix "[no]browse" not recognised in master map. +- fix auto.net not failing on sort command. +- fix expire of rootless mounts such as auto.net multi-mounts (again). + +11/4/2005 autofs-4.1.4 +---------------------- +- add /proc/modules check to Debian init script. +- fix typo in Gentoo init script reload function. +- fix default map type selection for submounts. + - a side affect of this patch is that when the first mount + point of a multi-mount entry is '/' it is no longer + mandatory. This matches the Solaris automount parsing. +- implement a timeout for LDAP communication (Dan Cox). +- change setpgrp to setsid to disascociate from tty properly. +- fix auto.net and auto.smb to not use non-bourne shell regex + when searching for their export list programs. +- fix nsswitch.conf sources detection. +- fix grep failing causing assignments to terminate init script. +- fix handling of localoptions options init script variable. +- sanitize records from auto.master file (Debian bug#298649). +- alter logic of UNDERSCORETODOT to work when it's not set (Debian bug#301358). +- attempt to fix Debian upgrade fail bug#300703. + - changes from Herbert Xus' patch. + - he`s not sure he got all cases and I can't see more either, so we'll see. +- revert some broken changes in Gentoo init script. +- fix end of string handling at end of parsing options string. +- more work on replicated server code - fix occasional mount fail. +- revert some Debian init script changes in favour of Debian maintainer patches. + - and fix the fix. +- update kernel patches. +- remove isprint calls as it breaks 8-bit characters. + +14/2/2005 autofs-4.1.4_beta2 +---------------------------- +- add update to -D variable propogation patch (Michael Blanddford). +- fix rentrancy problem when releasing the cache. +- fix 'automount: ' error in patern match in init script. +- applied autofs4 module check changes in Debian init script. +- fix handling of missing newline on last file map entry. +- fix map entry lookup order (second try). +- fix couple of comiler warnings. +- fix reload init script option for Gentoo and Debian (nearly). + +26/1/2005 autofs-4.1.4_beta1 +--------------------------- +- fix error in define of mtab lock file. +- fix mischief caused by change in default strict -> nonstrict. +- fix replicated server detection (TCP/UDP/NFSv2/NFSv3) logic (Jeff Moyer and Ian Kent). +- fix error when setting pwd. +- fix socket leak (Jeff Moyer and Ian Kent). +- fix potential race in signal handling code (Jeff Moyer). +- miscelaneous corrections to doco and typos (Peter Breitenlohner). +- fix i18n init script underline output (Jeff Moyer). +- fix logic error in replicated server selection (Jeremy Rosengren and Ian Kent). +- the map change detection patch + - note this requires a kernel patch to work, see README.patches. +- correct whitespace handling in maps. +- mount table handling cleanup. +- fix multi map lookup broken after the map update patch. +- fix auto master map concatenation bug (Jeff Moyer). +- merge some Debian patches (Arthur Korn) + - 032 remove trailing slash in mount_afs.c. + - 033 support hesiod priorities in lookup_hesiod.c. + - 034 handle empty options in changer ,ext2 and generic modules. + - 035 program mount repeated last character of map output. + - 036 make make fail on failure. +- fix memory leak in cache_clean function (Jeff Moyer). +- remove restriction of mounting ncpfs filesystems (Mike Fleetwood). + - see README.ncpfs. +- second signal race fix (almost what Chris Feist recommended). +- fix the ordering of +ypmapname entries in master map (Chris Feist). +- fix backslash parse for smb mount option username\password (George Hansper). +- keep udp rpc ping routine from using up reserved ports (Jeff Moyer). +- maintain backwards compatibility w/ local file maps (Chris Feist). +- init script fix (Arthur Korn). +- fix duplicate map handling in init script (Chris Feist). +- force local map paths to begin with / (Chris Feist). +- allow for LDAP maps that have greater than the LDAP result count limit (Chris Feist). +- allow for program maps returning larger than 4kb (Jeff Moyer and Chris Feist?). +- fix some potential buffer overflows, applied with amendments (Steve Grubb). +- carry -D variable definitions to submounts (Michael Blandford). +- merge Debian patch 044 update reference to mailing list (Arthur Korn). +- provide ability to convert read-only NFS mounts to read-only bind mounts (Thorild Selen). +- fix incorrect direct map entry lookup in yp module. +- merge most of RedHat init patch and the browse and umount loopback patches (Jeff Moyer). +- fix file handle being left open in lookup_file.c. +- update 2.4 kernel module patches. +- add 2.6 kernel module patches. +- merge fix for program maps returning larger than 4kb (Jeff Moyer). +- fix trailing white space not removed from map entries not containing a colon. +- merge remaining bits of Debian and Redhat init script patches. +- fix duplicate map entry order - return first read instead of last. +- bump version to 4.1.4 beta1 and start testing. +- fix spec file to build the beta. +- fix compile warning in lookup_yp.c. +- more work on Debian part of init script (wip). +- add Gentoo portage ebuild files. +- review remaining Debian patches + - all 000 and 001 patches are included in 4.1.4. + - 002 log cause of ldap errors applied + - 030 document +map in auto master applied. + - 031 document -D in automount 8 applied in another diff. + - 040 init script policy conformance and backwards compatibility + merged but slightly broken (see above). + - 041 needs review against new version. + - 045 module loading set e applied with minor change. + - 046 needs review against new version. + - 047 277320 correct automount nsswitch regex applied. + - 049 parse_sun to be merged in another diff. + - 050 disable_direct_maps.diff applied. +- update spec file to use autofs sysconfig. +- update auto.net to search for showmount and use lang variable. +- allow for ":" escape in multi mount parse (Elmar Pruesse). +- add auto.smb example program map (Elmar Pruesse). +- apply slashify-colons patch to enable it work (Timo Felbinger). +- fix for ldap_search when multiple cn's are in one LDAP entry (Chris Feist). +- fix alarm not being reset during prune signal event. +- reimplement locking. +- implement signaling of submount processes from within daemon. + - Fix errors in mount entry handling module. + - Update lock module to suit. +- merge multi map over mount patch. +- add init script variable to allow adjustment of time to wait for shutdown. +- fix no directory list when map entry deleted and wild card matches. +- prevent pre-existing automount point directory from being removed at termination (Chirs Feist). +- update replicated server doco + - autofs will always choose localhost regardless of weights (Chirs Feist). + +19/05/2004 autofs-4.1.3 +----------------------- +- fixed bug processing --verbose option in init scripts. +- added missing parameter in call to run fsck on ext2 module (Jeff Moyer). +- added check for executable existance in getldapmounts in init script. +- updated comment about option handling in getmounts in init script. +- updated kernel module patches. +- fix to init script for reload option (Michael Blandford). +- autofs now requires autoconf later than 2.5. +- replicated server fixup. +- fix segv in NIS lookup module (Jeff Moyer). +- init script fix for Debian (Thorild Selen). +- fix pie option not checking for runable executable. +- add NFS V3 and TCP to rpc discovery. +- make nonstrict the default for multi-mount map entries. + +07/04/2004 autofs-4.1.2 +----------------------- +- merge patches from Jeff Moyer. + - Change (back) compile option -fpic -> -fPIC. + - add code to recognise old LDAP master map format. + - document limitation of direct mounts obscuring mount + points in README.direct. + - fix error in detecting duplicate master map entries + in init script. + - add check for automount base already mounted. + - check for pie support and enable compile option if + available. + - corrections to init script including send HUP signal + on reload. +- fix unchecked return from get_best_mount. +- add example file master map using LDAP. +- 1st attempt to deal with smb mounts that go away while + mounted. +- updated kernel patches. + +14/03/2004 autofs-4.1.1 +----------------------- +- added CHANGELOG. +- fixed error in some ident tags. +- merge debian patch 032 document nonstrict special option. +- updated spec file to standardise paths etc. +- reintroduced some 4.0.0 init script code to help serialise + shutdown signaling and umount of submounts. This should + alieviate some of the contention for umount at shutdown. +- fix invalid path reference on error exit. +- merged debian patches. + - 030 man page corrections in autofs(5) and automount(8). + - 033 correct url in man page automount(8). + - 034 correct mount call in mount_changer.c. + - 040 document gid option in man page autofs(5). +- change fsck return code check. + - still try to mount if fsck fails to run. + - error only if fsck finds uncorrectable errors. +- fixed lookup problem with wildcard order in map. +- fixed lookup problem relating to directory tidyup. +- merge Jim Carters' buffer management and expire limit patch. +- remove make requirement for hesoid and ldap presence. +- add check for submount point itself busy. + - requires autofs4 4.04 kernel module. +- added --verbode and --debug options to quieten daemon and + provide ability to get debug output when needed. Default + is produce no output except error messages. +- merge Mike Blandfords' replicated server patches. +- added ability to access external environment var. + - set by default, use --disable-ext-env to turn of. + +4/12/2003 autofs-4.1.0 +---------------------- + +- Fixed problem with regex in init script not recognising -g option. +- removed patch to ignore failed mounts in tree mounts in favour + of using nonstrict option. +- updated autofs4 kernel module patches. + + +10/11/2003 autofs-4.1.0-beta3 +----------------------------- + +- I'm aware of one outstanding problem with multi-mount maps. The + senario is that the daemon cannot remount a manualy umounted multi-mount + entry until after the following expire event. This is due kernel module + and daemon not knowing the umount has occured and consequently not + cleaning up afterward. This causes the kernel module to return a longer + path than it should which cannot be matched in the map. I hope to be able + to fix this a some time in the future. +- removed debug print to catch reported mount problem. +- added patch to ignore failed mounts in tree mounts. + + +14/10/2003 autofs-4.1.0-beta2 +----------------------------- + +- added debug print to catch reported mount problem. +- updated autofs4 patches and their documention. +- added autofs4 patch for 2.4.22. + + +29/9/2003 autofs-4.1.0-beta1 +---------------------------- + +This is a restructuring and improvement of my original v4 patch which added +direct mount support for file and NIS maps. + +As well the considerable restructure and tidy up of my original patch it +includes: + +- Merge of all the RedHat autofs v3 patches. This includes the RedHat + init script and LDAP improvements. See the README files in the package + for more info. +- Add LDAP direct mount support to complement the file and NIS map direct + mount support already present. +- Merged some patches from the Debian and SuSE autofs v4 packages. +- Found a bunch of bugs and fixed them. This was largely due to the huge + efforts made by Aaron Ogden in testing a never ending stream of attempted + corrections. Thanks Aaron. + + +29/09/2003 autofs-4.0.0-1 +------------------------- + +Largely the autofs-4.0.0pre10 with: +- Some patches that I did ages ago to improve submounts. This includes + correcting the double slash in mount points. Changes to the init script + to improve the shutdown when submounts are involved. +- Merged some small patches from the Debian and SuSE autofs v4 packages. +- A simple work around to allow tree mounts to work with RedHat 2.4.20, + and above, kernels in the autofs4 module patch. + + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..37bd5d8 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,18 @@ +For all software in this distribution unless otherwise indicated: + + Copyright 1997-2000 Transmeta Corporation -- All Rights Reserved + + This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + USA; either version 2 of the License, or (at your option) any later + version. + + This program 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. + +Portions Copyright (C) 1999-2000 Jeremy Fitzhardinge +Portions Copyright (C) 2001-2008 Ian Kent + diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..a931de1 --- /dev/null +++ b/CREDITS @@ -0,0 +1,15 @@ + +CREDITS +======= + +Aaron Ogden +----------- + +While other people have contributed to autofs in various ways +Aaron stands out for his patience and unceasing willingness +to test a never ending stream of patches and updates. Aaron +has made it possible for me to remedy a large number of +errors in the package. + +Thanks Aaron + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..68a4376 --- /dev/null +++ b/INSTALL @@ -0,0 +1,181 @@ + +Requirements +============ + +- autofs 5.0.0 requires protocol version 5.00 of the autofs4 kernel + module (see "Applying the kernel patch" below). + +- The LDAP, SASL, and the libxml2 libraries may also be required if + the LDAP configure options are selected (see "Configure options for + autofs" below). + +How to build and install +======================== + +To configure, compile, and install the package directly from the +tar distribution, follow these steps. + + 1. Make sure you have the latest version of the package available + from http://www.kernel.org/pub/linux/daemons/autofs/v4. + + 2. Type `./configure' to configure the package. You may want to add + selected configure options. See "Configure options for autofs" + below. + + 3. Build the package by typing `make' + + 4. Install the package by typing `make install'. + +To build an `rpm' from the tar distribution use: + +rpmbuild -tb autofs-5.0.0_beta6.tar.gz + +and install the resulting rpm package. + +Configure options for autofs +============================ + +There are several configure options available to add functionality. + +--prefix= + autofs usually installs in /usr, with the daemon in /usr/sbin, + the man pages in /usr/man, and the modules in /usr/lib/autofs. + If you wish to install to a different location use this option. + +--with-path=PATH + Allows the specification of the path to search for utility + binaries used by autofs during the `configure' process. If + any of your binaries such as `mount', `mount.cifs' etc. live + in locations other than /usr/bin:/bin:/usr/sbin:/sbin you + will need to use this option. + +--disable-ext-env + This option disables the ability to the external environment + to set an external environment variable for substitution. + +--disable-mount-locking + This option disables mount locking which is to prevent corruption + in /etc/mtab due to multiple concurrent autofs mount commands. + +--enable-force-shutdown + This option enables the use of the USR1 signal to force an + unconditional unlink umount of all mounts at shutdown. + +--enable-ignore-busy + This option enables the automount to exit ignoring any busy + mounts upon receiving a TERM signal. automount attempts to + recover upon startup. + +--with-confdir=DIR + This option allows the specification of the directory that contains + the autofs configuration file `autofs'. If not given the directories + `/etc/sysconfig', `/etc/default' and `/etc/conf.d' and checked for + existence and the first one found is used. + +--with-mapdir=PATH + This option allows the specification of the directory that contains + autofs mount maps. The directories `/etc/autofs' and `/etc' are + checked for existence and first one found is used. + +--with-hesiod=DIR + Use this option to enable Hesiod support if your Hesiod library + isn't installed in /usr/lib with the include files in /usr/include. + +--with-openldap=DIR + Use this To enable LDAP support if your OpenLDAP library isn't + installed in /usr/lib with the include files in /usr/include. + +--with-sasl=DIR + Use this to enable TLS encrypted connections and LDAP + authentication using the SASL library. DIR only needs to be + given if your LDAP and SASL library and include files are + installed in other than /usr/lib and /usr/include. See + auto.master(5) for more information about LDAP configuration. + +Applying The Kernel Patch +========================= + +Patches that can be applied to the kernel are located in the +`patches' directory. If you have installed autofs from an rpm +then they can be found in the packages' doc directory after +install. They consist of a kernel patch for each kernel from +2.6.9 thru 2.6.16 (the patches are in the 2.6.17-rc series so +patching a 2.6.17 or above kernel shouldn't be needed). If +you need a patch for an older 2.6 kernel then post to the +autofs mailing list and ask for one to be done. + +To apply one of these patches: + +1) change to the root of your kernel source tree. +2) patch the kernel +3) make the kernel +4) Install the kernel as normal. + +If you use a packaged kernel then you need to check to see if +the v5 update patch will apply and if it does apply add the +update patch and build your packaged kernel. + +For example, Fedora Core 4 kernel package 2.6.11-1.369_FC4 +would use the 2.6.12 patch because it applies the 2.6.12 release +candidate revision 6 patch. But the autofs patch doesn't apply +because the rpm also applies a patch somewhere that changes one +area the the patch also changes but this change is not present +in the 2.6.12 release kernel. + +On the other hand, Fedora Core 3 kernel 2.6.12-1.1381_FC3 +uses the 2.6.12 patch and it applies cleanly because the kernel +doesn't apply a 2.6.13 release candidate patch and there are no +additional patches that clash with the autofs update patch. To +build this kernel with an update patch modify the kernel-2.6.spec +to apply the update patch after all other patches (see example +change below), copy the autofs4-2.6.12-v5-update to the SOURCES +directory of your RPM build tree and us use +`rpmbuild -bb --target=i686 kernel-2.6.spec' in the SPEC directory +of the build tree and install the resulting RPMs. + +Example of how to add the update patch: + +--- kernel-2.6.spec.orig 2006-05-01 10:14:27.000000000 +0800 ++++ kernel-2.6.spec 2006-05-01 10:16:21.000000000 +0800 +@@ -358,6 +358,8 @@ + Patch10000: linux-2.6.0-compile.patch + Patch10001: linux-2.6-compile-fixes.patch + ++Patch20000: autofs4-2.6.12-v5-update.patch ++ + # END OF PATCH DEFINITIONS + + BuildRoot: %{_tmppath}/kernel-%{KVERREL}-root +@@ -739,6 +741,7 @@ + %patch10000 -p1 + %patch10001 -p1 + ++%patch20000 -p1 + + # END OF PATCH APPLICATIONS + +Some Fedora Core kernels have autofs4 bug fix patches, such as +2.6.16-1.2096_FC5. The update patch supersedes all previous patches +so any additional autofs4 patches should be removed and the update +patch added. For example 2.6.16-1.2096_FC5 needs this change: + +--- kernel-2.6.spec.orig 2006-06-18 14:56:27.000000000 +0800 ++++ kernel-2.6.spec 2006-06-18 14:56:52.000000000 +0800 +@@ -359,7 +359,7 @@ + Patch1630: linux-2.6-radeon-backlight.patch + Patch1640: linux-2.6-ide-tune-locking.patch + Patch1641: linux-2.6-ide-cd-shutup.patch +-Patch1650: linux-2.6-autofs-pathlookup.patch ++#Patch1650: linux-2.6-autofs-pathlookup.patch + Patch1660: linux-2.6-valid-ether-addr.patch + Patch1670: linux-2.6-softcursor-persistent-alloc.patch + Patch1680: linux-2.6-pwc-powerup-by-default.patch +@@ -983,7 +983,7 @@ + # Silence noisy CD drive spew + %patch1641 -p1 + # autofs4 looks up wrong path element when ghosting is enabled +-%patch1650 -p1 ++#%patch1650 -p1 + # + %patch1660 -p1 + # Use persistent allocation in softcursor diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e560a7c --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# +# Main Makefile for the autofs user-space tools +# + +-include Makefile.conf +include Makefile.rules + +.PHONY: daemon all clean samples install install_samples +.PHONY: mrproper distclean backup + +all: daemon samples + +daemon: + set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i all; done + +kernel: + set -e; if [ -d kernel ]; then $(MAKE) -C kernel all; fi + +samples: + set -e; if [ -d samples ]; then $(MAKE) -C samples all; fi + +clean: + for i in $(SUBDIRS) samples; do \ + if [ -d $$i ]; then $(MAKE) -C $$i clean; fi; done + +install: + set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i install; done + +install_kernel: + set -e; if [ -d kernel ]; then $(MAKE) -C kernel install; fi + +install_samples: + set -e; if [ -d samples ]; then $(MAKE) -C samples install; fi + +mrproper distclean: clean + find . -noleaf \( -name '*~' -o -name '#*' -o -name '*.orig' -o -name '*.rej' -o -name '*.old' \) -print0 | xargs -0 rm -f + -rm -f include/config.h Makefile.conf config.* .autofs-* + echo x > .autofs-`cat .version` + sed -e "s/(\.autofs-[0-9.]\+)/(.autofs-`cat .version`)/" < configure.in > configure.in.tmp + mv -f configure.in.tmp configure.in + rm -f configure + $(MAKE) configure + +TODAY := $(shell date +'%Y%m%d') +PKGDIR := $(shell basename `pwd`) +VERSION := $(shell cat .version) + +backup: mrproper + cd .. ; tar zcf - $(PKGDIR) | gzip -9 > autofs-$(VERSION)-bu-$(TODAY).tar.gz + +configure: configure.in aclocal.m4 + autoconf + autoheader + rm -rf config.* *.cache + +configure.in: .version + -rm -f .autofs-* + echo x > .autofs-`cat .version` + sed -e "s/(\.autofs-[0-9.]\+)/(.autofs-`cat .version`)/" < configure.in > configure.in.tmp + mv -f configure.in.tmp configure.in + +-include Makefile.private + + diff --git a/Makefile.conf.in b/Makefile.conf.in new file mode 100644 index 0000000..2bc3202 --- /dev/null +++ b/Makefile.conf.in @@ -0,0 +1,117 @@ +# +# $id$ +# +# Makefile.conf.in +# +# Pattern file to be filled in by configure; contains specific options to +# build autofs. +# + +# Do we build with -fpie? +DAEMON_CFLAGS = @DAEMON_CFLAGS@ +DAEMON_LDFLAGS = @DAEMON_LDFLAGS@ + +# Glibc < 2.17 requires librt for clock_gettime() +LIBCLOCK_GETTIME = @LIBCLOCK_GETTIME@ + +# Special parameters for glibc (libc 6) +LIBNSL = @LIBNSL@ +LIBRESOLV = @LIBRESOLV@ + +# Hesiod support: yes (1) no (0) +HESIOD = @HAVE_HESIOD@ +LIBHESIOD = @LIBHESIOD@ +HESIOD_FLAGS = @HESIOD_FLAGS@ + +# LDAP support: yes (1) no (0) +LDAP = @HAVE_LDAP@ +LIBLDAP= @LIBLDAP@ +LDAP_FLAGS = @LDAP_FLAGS@ + +# sssd support +SSSD = @HAVE_SSS_AUTOFS@ + +# SASL support: yes (1) no (0) +XML_LIBS = @XML_LIBS@ +XML_FLAGS = @XML_FLAGS@ +SASL = @HAVE_SASL@ +LIBSASL= @LIBSASL@ +SASL_FLAGS = @SASL_FLAGS@ +KRB5_LIBS=@KRB5_LIBS@ +KRB5_FLAGS=@KRB5_FLAGS@ + +# NIS+ support: yes (1) no (0) +NISPLUS = @HAVE_NISPLUS@ + +# SMBFS support: yes (1) no (0) +SMBFS = @HAVE_SMBMOUNT@ + +# YellowPages support: yes (1) no (0) +YPCLNT = @HAVE_YPCLNT@ + +# Support for calling e2fsck when mounting ext2 filesystems +EXT2FS = @HAVE_E2FSCK@ + +# Support for calling e3fsck when mounting ext3 filesystems +EXT3FS = @HAVE_E3FSCK@ + +# Support for calling e4fsck when mounting ext4 filesystems +EXT4FS = @HAVE_E4FSCK@ + +LEX = @PATH_LEX@ +YACC = @PATH_YACC@ +RPCGEN = @PATH_RPCGEN@ +RANLIB = @PATH_RANLIB@ + +# Use libtirpc if requested and available +TIRPCLIB = @TIRPCLIB@ + +# Use dmalloc for memory debuging +DMALLOCLIB = @DMALLOCLIB@ + +# +# Note: the DESTDIR define is so you can build autofs into a temporary +# directory and still have all the compiled-in paths point to the right +# place. +# + +# Common install prefix +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# SSS library module directory +ssslibdir=@sssldir@ + +# Directory for autofs modules +autofslibdir = @libdir@/autofs + +# Location for configuration init script +autofsconfdir = @confdir@ + +# Location for autofs maps +autofsmapdir = @mapdir@ + +# Localtion of pid files +autofspiddir = @piddir@ + +# Location for autofs fifos +autofsfifodir = @fifodir@ + +# Location for autofs flag file +autofsflagdir = @flagdir@ + +# Where to install the automount program +sbindir = @sbindir@ + +# Where to install man pages +datarootdir = @datarootdir@ +mandir = @mandir@ + +# Location for init.d files +initdir = @initdir@ + +# Location of systemd unit files +systemddir = @systemddir@ + +# Use the compiler determined by configure instead of hard-coded ones +CC := @CC@ diff --git a/Makefile.rules b/Makefile.rules new file mode 100644 index 0000000..7d1af2e --- /dev/null +++ b/Makefile.rules @@ -0,0 +1,73 @@ +# +# Makefile rules for autofs project +# + +# Root directory contents +SUBDIRS = lib daemon modules man samples +INCDIRS = include +INCFILES = COPYING COPYRIGHT NEWS README* TODO Makefile Makefile.rules \ + Makefile.conf.in .version .autofs-* configure.in aclocal.m4 \ + configure *.patch autofs.spec + +# Attempt to be friends with autotools +INSTALLROOT = $(DESTDIR) + +# autofs utility library +AUTOFS_LIB = ../lib/autofs.a + +# Compilers, linkers and flags +# The STRIP defined here *must not* remove any dynamic-loading symbols + +ifdef DMALLOCLIB +DEBUG=1 +endif + +ifdef DEBUG +CFLAGS ?= -g -Wall -DDEBUG +LDFLAGS ?= -g +else +CFLAGS ?= -O2 -Wall +LDFLAGS ?= -s +endif + +ifdef DONTSTRIP +STRIP ?= : +else +STRIP ?= strip --strip-debug +endif + +CC ?= gcc +CXX ?= g++ +CXXFLAGS ?= $(CFLAGS) +LD ?= ld +SOLDFLAGS = -shared + +CFLAGS += -D_REENTRANT -D_FILE_OFFSET_BITS=64 +LIBS += -lpthread + +ifdef TIRPCLIB +CFLAGS += -I/usr/include/tirpc +LIBS += $(TIRPCLIB) +endif + +ifdef DMALLOCLIB +LIBS += $(DMALLOCLIB) +endif + +LIBS += $(LIBNSL) + +LIBS += $(LIBCLOCK_GETTIME) + +# Standard rules + +.SUFFIXES: .c .o .s .so + +.c.o: + $(CC) $(CFLAGS) -c $< + +.c.s: + $(CC) $(CFLAGS) -S $< + +.c.so: + $(CC) $(SOLDFLAGS) $(CFLAGS) -o $*.so $< $(LDFLAGS) $(AUTOFS_LIB) $(LIBS) + $(STRIP) $*.so diff --git a/README b/README new file mode 100644 index 0000000..9024e64 --- /dev/null +++ b/README @@ -0,0 +1,62 @@ +-*- text -*- + +autofs is a kernel-based automounter for Linux. It performs a job +similar to amd(8) but relies on a small stub of kernel code instead of +pretending to be an NFS server. The result is simpler code, better +reliability, and much faster operation in the common case (everything +already mounted.) + +An AMD/AutoFS HOWTO is available at: + +http://www.Linux-Consulting.com/Amd_AutoFS/autofs.html + +... as well as from your favourite HOWTO archive. + +To build autofs, please run: + + ./configure + +to configure the system. See README.options for options that you can +give configure. + +After configuring, you can: + + make ... make the daemon and modules + make install ... install the daemon and modules + +Development kernels 2.3.41 and onwards contain the autofs4 as +standard. If you're using 2.2, you can apply the patch in +the patches directory; it was made from 2.2.14, but it should +work on 2.2.10 onwards. Patches related to extensions written +by Ian Kent are also in the patches directory. See README.ghosting +for a description of the kernel patches. + +If you use autofs as a module, you need to add "alias autofs4 autofs" +to your modules config file (/etc/modules.conf or /etc/conf.modules). + +Limited direct mount functionality has been implemented and a +describtion of what can be done can be found in README.direct. + +autofs was written by H. Peter Anvin of Transmeta Corporation, please +read the COPYRIGHT file. autofs 4 is the result of Jeremy +Fitzhardinge's work on autofs 3. Further enhancements +have been made by Ian Kent . + +If you use or want to help develop autofs, please join the autofs +mailing list by sending an email to: + + majordomo@vger.kernel.org + +With the body text: + + subscribe autofs + +Once subscribed you can send patches to: + + autofs@vger.kernel.org + +The autofs mailing list archive can be viewed on gmane: + + http://news.gmane.org/gmane.linux.kernel.autofs + http://blog.gmane.org/gmane.linux.kernel.autofs + diff --git a/README.active-restart b/README.active-restart new file mode 100644 index 0000000..95e9862 --- /dev/null +++ b/README.active-restart @@ -0,0 +1,114 @@ + +The problem +----------- + +The initial release of autofs version 5 used the "umount -l" (lazy +umount) to clear stale mounts at startup so that new mounts could +be made with a clean slate. This proved to be a bad choice because +the lazy umount removes the mount from the kernel mount tree. While +the mount itself persists (invisibe to further VFS lookups) until +the mount isn't busy anymore the kernel function d_path(), used to +calculate the path from the mount point to the root, isn't able to +perform its function any more. This leads to things that use d_path(), +such as /bin/pwd, the contents of the link /proc//cwd, and so on. + +The actual problem with autofs is that it can't re-connect to existing +mounts. Immediately one thinks of just adding the ability to remount +the autofs file system would solve it, but that can't work. This is +because autofs direct mounts and the implementation of "on demand mount +and expire" of nested mount trees i(multi-mounts) have the file system +mounted directly on top of the mount trigger directory. + +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. 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". + +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 autofs mount points can be covered by another mount. + +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. To do this the +autofs ioctl control implementation has been re-implemented, +providing the same functions as the existing ioctl interface and +new operations to the ability to open a file handle on a covered +autofs mount point. + +In order to utilize the new interface both kernel and user space +changes (included in this release) are needed. The default install +of autofs won't use the new interface unless it is enabled in the +configuration. This can be done by setting "USE_MISC_DEVICE" to "yes" +in the autofs configuration. In addition, if selinux is being used, +it will likely need to allow operations on the autofs device file +or be set to permissive mode. + +Patches for several recent kernel that don't have the implementation +can be found in the patches directory. They have "dev-ioctl" in their +name. Note that, to use these patches, you should be using a kernel +patched with all the current autofs bug fixes since, apart from porobably +not working, the patch probably won't apply either. The bug fix patches +can also be found in the patches directory and they have "v5-update" in +their names. + +Ian diff --git a/README.amd-maps b/README.amd-maps new file mode 100644 index 0000000..386b068 --- /dev/null +++ b/README.amd-maps @@ -0,0 +1,162 @@ + +amd map parser +============== + +The ability to parse amd format maps has been added to autofs. + +How to use amd maps in autofs +----------------------------- + +To add amd map parsing to autofs a new "format" module has been added. + +There are two ways to use this new map format module. First, the existing +master map syntax can be used as described below, and second, sections that +use the top level mount path may be added to the autofs configuration below +the "amd" section in much the same way as is done with amd. + +The master map entry syntax is: + +mount-point [map-type[,format]:]map [options] + +For amd format maps this becomes: + +/amd/mp   file,amd:amd.mp + +which will use file as the map source and the amd format parser for +the map. + +In order to use nsswitch to specify the map source an amd per-mount +section needs to be added to the autofs configuration so autofs +knows the master map entry is an amd format mount. + +If an amd-per-mount section is added to the autofs configuration a +corresponding master map entry is optional. If both are present the +map name given in the master map entry will override a "map_name" +option in the amd per-mount section. + +If an amd per-mount section is used alone then not giving the "map_type" +option will alow the use of nsswicth for map selection. + +See below for an example of an amd per-mount configuration entry. + + +Configuration sub-system changes +-------------------------------- + +The configuration sub-system has changed to accommodate the amd parser. +See autofs.conf(5) for more information on format changes. + +The configuration is now split into system initialization only +configuration and the daemon configuration. Previously everything was +located in the system initialization configuration file, but now the +configuration is located in autofs.conf in the directory the distribution +uses for the autofs configuration. + +There is information about what amd configuration entries can be used +in comments of the installed configuration so that's worth a look. + +All that's needed to add an existing amd configuration to autofs is to +add it below the autofs configuration. Apart from changing the amd +"[ global ]" section name to "[ amd ]" nothing else should need to be +changed. However, quite a few amd configuration options don't have +meaning within autofs. When these options are seen they are logged. + +Be aware that, if the an old configuration exists and the configuration +hasn't been updated after the installation, changes to the the old +configuration will override changes to the new configuration because +backward compatibility takes priority over the new implementation. + +The amd per-mount sections have two functions, to allow per-mount +configuration, as it does in amd, and to allow master map entries to +avoid the need to specify the "type,format" part of the master map +entry. This allows them to use the nsswitch map source functionality +in the same way autofs master map entries do. + +If amd per-mount sections are present in the autofs configuration +their corresponding master map entries are optional. This allows +amd maps to be used without adding incompatible entries to the autofs +master map in shared infrastructure environments. + +If a section for an amd mount is added below the global amd section +using the mount point path (as is done in amd.conf) then autofs will +know the map format is amd (it doesn't matter if there are no other +configuration options in the mount point section). + +If there is a corresponding master map entry the map given in the +master map entry will be used over the map_name option if it is +present in an amd per-mount section. + +If a mount point is present in the master map and the source of the +map is nis then it is sufficient to use (for example): + +/amd/mp           amd.mp + +in the master map and + +automount: nis + +in /etc/nsswitch.conf or + +[ amd ] +map_type = nis + +in the configuration along with + +[ /amd/mp ] + +or + +[ /amd/mp ] +map_type = nis + +An example of an amd per-mount configuration entry is: + +[ amd ] +... + +[ /test ] +map_name = /etc/amd.test +#map_type = file +#search_path = /etc +#browsable_dirs = yes | no +browsable_dirs = yes + + +amd map options that can be used +-------------------------------- + +In an attempt to describe the usable amd map options, many of the amd +map options have been added to autofs(5). + +Not all the amd functionality has been implemented. The autofs(5) man +page usually mentions if something hasn't been implemented so that's +worth checking. + +What hasn't been implemented +---------------------------- + +The configuration options fully_qualified_hosts, unmount_on_exit and +browsable_dirs = full (and a couple of others) aren't implemented. + +Map types (sources) ndbm, passwd are not implemented. +The map source "sss" can't be used for amd format maps. + +Map caching options aren't used, the existing autofs map caching is +always used for available map sources. + +The regex map key matching feature is not implemented. + +Mount types lustre, nfsx, jfs, program and direct haven't been +implemented and other mount types that aren't implemented in amd are +also not available. + +How to find out more +-------------------- + +Have a look at the man pages autofs.conf(5), autofs(5) and to a +lesser extent auto.master(5). These may help. + +But the best way to get more information is to ask on the +autofs mailing list as described in the README file. + +Ian diff --git a/README.autofs-schema b/README.autofs-schema new file mode 100644 index 0000000..c121e1c --- /dev/null +++ b/README.autofs-schema @@ -0,0 +1,18 @@ +autofs schema +============= + +The distribution file samples/autofs.schema is very old and is +incorrect. + +This schema was added to the discribution long ago when it was +not clear what schema to use for Linux autofs information. + +The schema was corrected somewhere along the line but the autofs +distribution copy was never updated. The schema has now been +updated but it is not recommended for use as the schema for autofs +map information. + +The rfc2307 or, preferably the, rfc2307bis schema is the recommened +schema to use. + +Ian diff --git a/README.changer b/README.changer new file mode 100644 index 0000000..9325299 --- /dev/null +++ b/README.changer @@ -0,0 +1,32 @@ +Fri Jan 21 17:31:43 GMT 2000 +Toby Jaffey +Added modules/mount_changer.c + +I have an NEC CD-ROM DRIVE:251, 4X CD-ROM changer w/4 slots, 128kB +Cache. The drive can only mount one CD at a time. To change the CD in +use you must unmount, swap slots (lots of ioctl() calls) and +remount. Using autofs, this module allows the illusion that all CDs +are mounted at any given time. Only when data is requested does the +drive need to swap. Clearly, this is awful for simultaneous reads +across many disks, but I use it to create mp3 playlists spanning +multiple CDs. + +The code is mostly clean, but rather than adding a new "mediatype" to +the config file, I set my drive up as fstype=changer. The assumption +is made that all of the disks are of type iso9660. This is a bad +thing, but it works for me. + +[hpa: I believe these problems are due to a design error. The changer +should be a lookup type, rather than a filesystem (mount) type.] + +My /etc/auto.master says: +/mnt/changer /etc/auto.misc + +My /etc/auto.misc says: +1 -fstype=changer :/dev/hdb +2 -fstype=changer :/dev/hdb +3 -fstype=changer :/dev/hdb +4 -fstype=changer :/dev/hdb + + + diff --git a/README.ncpfs b/README.ncpfs new file mode 100644 index 0000000..dede8ed --- /dev/null +++ b/README.ncpfs @@ -0,0 +1,85 @@ + +Hi all, + +Below is the contents of a mail from Mike Fleetwood which describes how +he managed to get ncpfs to work with autofs. It's included verbatium. + +I've applied the patch below and hope that this will help those who +need to use ncpfs. + +Ian + +================== + +Below is a very small fix to autofs to mount ncpfs. It just removes +the exclusion preventing mount_generic from being used to mount ncpfs. + +This fix worked for me on Suse 9.1 with kernel 2.6.5 and ncpfs 2.2.4 for +both autofs 3.1.7 and 4.0.0. I suspect it will work almost anywhere +provided that all the required ncpfs mount options can be passed as -o +options to /bin/mount. ncpmount(8) is very informative in this respect. + +Patch: +----8<--------8<---- +diff -urN autofs-4.0.0.orig/daemon/mount.c autofs-4.0.0/daemon/mount.c +--- autofs-4.0.0.orig/daemon/mount.c 2003-09-10 15:27:41.000000000 +0100 ++++ autofs-4.0.0/daemon/mount.c 2004-08-05 10:36:51.813852608 +0100 +@@ -26,7 +26,7 @@ + + /* These filesystems are known not to work with the "generic" module */ + /* Note: starting with Samba 2.0.6, smbfs is handled generically. */ +-static char *not_generic[] = { "nfs", "ncpfs", "userfs", "afs", ++static char *not_generic[] = { "nfs", "userfs", "afs", + "autofs", "changer", "bind", NULL }; + + int do_mount(const char *root, const char *name, int name_len, +----8<--------8<---- + +Searching this list's archive and googling found nothing useful so I am +including extra hints of how I worked out how to configure automounting of +ncpfs. (The ncpfs options you require will very likely be different to +those shown here. See ncpmount(8) for the possible options). + +1) Get cmd line mounting working using ncpmount working first: + ncpmount -S novellservername -U username -A dnsname -V volname /mnt + (Enter Novell password for username when prompted) + umount /mnt + +2) Switch to using /bin/mount with -o options: + mount -t ncpfs -o ipserver=dnsname,volume=volname,passwd=XXXXXX \ + novellservername/username /mnt + umount /mnt + (Note that there are other ways of supplying a password to ncpmount + besides specifying it on the cmd line). + +3) Use /etc/fstab entry to provide all the options to mount. + Add /etc/fstab entry like: + # Device Mount Dir FS Type Options FSCK Dump + novellservername/username /mnt ncpfs ipserver=dnsname,volume=volname,passwd=XXXXXX 0 0 + + mount /mnt + umount /mnt + +4) Switch to using autofs. Add /etc/auto.master entry: + # Mount Dir Map File + /novell /etc/auto.ncpfs + + Create /etc/auto.ncpfs as: + # Key -Options Location + dir -fstype=ncpfs,ipserver=dnsname,volume=volname,passwd=XXXXXX :novellservername/username + + ls /novell/dir + + +All the best, +Mike +-- + __ __ _ _ ___ ____ _ ___ ___ _ ___ ___ _ +| \/ (_| | _ / _ \ | ___| | / _ \/ _ \| |_ _ _ _/ \/ \ _| | +| |\/| | | |/ | ___| | _| | |_| __| ___| __| \/ \/| O | O / _ | +|_| |_|_|_|\_\\___| |_| |____\___|\___||____\_/^\_/\___/\___/\___| + +_______________________________________________ +autofs mailing list +autofs@linux.kernel.org +http://linux.kernel.org/mailman/listinfo/autofs diff --git a/README.replicated-server b/README.replicated-server new file mode 100644 index 0000000..1d771d3 --- /dev/null +++ b/README.replicated-server @@ -0,0 +1,47 @@ +Supported forms for mount paths are: + +Normal single-host (these are unchanged) + host:/path/path + +Single host entries are not probed for a server response. + +Multiple replicated hosts, same path: + host1,host2,hostn:/path/path + +Multiple hosts, some with same path, some with another + host1,host2:/blah host3:/some/other/path + +Multiple replicated hosts, different (potentially) paths: + host1:/path/pathA host2:/path/pathB + +Mutliple weighted, replicated hosts same path: + host1(5),host2(6),host3(1):/path/path + +Multiple weighted, replicated hosts different (potentially) +paths: + host1(3):/path/pathA host2(5):/path/pathB + +For these formats a priority ordered list of hosts is created by using +the following selection rules. + +1) Highest priority in selection is proximity. + Proximity, in order of precedence is: + - PROXIMITY_LOCAL, host corresponds to a local interface. + - PROXIMITY_SUBNET, host is located in a subnet reachable + through a local interface. + - PROXIMITY_NETWORK, host is located in a network reachable + through a local interface. + - PROXIMITY_OTHER, host is on a network not directlty + reachable through a local interface. + +2) NFS version and protocol is selected by caclculating the largest + number of hosts supporting an NFS version and protocol that + have the closest proximity. These hosts are added to the list + in response time order. Hosts may have a corresponding weight + which essentially increases response time and so influences the + host order. + +3) Hosts at further proximity that support the selected NFS version + and protocol are also added to the list in response time order as + in 2 above. + diff --git a/README.smbfs b/README.smbfs new file mode 100644 index 0000000..f2b3288 --- /dev/null +++ b/README.smbfs @@ -0,0 +1,11 @@ +The functionality of converting map options to smbmnt arguments is now +handled by /bin/mount.smbfs, which is shipped with Samba 2.0.6 or +later. You therefore need to make sure you have /bin/mount.smbfs +installed on your system if you plan on using smbfs with autofs. + +WARNING: Some versions of Samba incorrectly install this as +/bin/mount.smb rather than /bin/mount.smbfs. If that is the case on +your system, please link or move /bin/mount.smb to /bin/mount.smbfs. +This is a Samba bug/misfeature and has been reported to the Samba +maintainers. + diff --git a/README.v5.release b/README.v5.release new file mode 100644 index 0000000..7b6dbc2 --- /dev/null +++ b/README.v5.release @@ -0,0 +1,78 @@ + +autofs-5.0.0 +============ + +Differences between version 4 and version 5. + +- Master map is now read and parsed by the `automount' daemon + - the parameters for the `automount' daemon are now very different. + See automount(8). + +- The master map default is "auto.master" and nsswitch is used to + locate it. The line "+auto.master" has been added to the default + installed "/etc/auto.master" to ensure that those using NIS will + still find their master map. This is in line with other industry + automount implementations. The name of the default master map can + be overridden by setting the MASTER_MAP_NAME configuration variable. + +- The `automount' daemon is now a multi-threaded application and so + appears as a single process (unless a thread process display option + is used). + +- `autofs' filesystem mounts only appear in /proc/mounts and not + /etc/mtab. If you have a large number of entries in a direct mount + map and link /etc/mtab to /proc/mounts you may see performance + degradation. + +- `autofs' version 5.0.0 will refuse to run if it cannot find an + autofs4 kernel module that supports protocol version 5.00 or above. + +- mount options present in the master map are accumulated by default + which is different to the behavior of some other autofs + implementations. A configuration option is available to change this + behavior if required. + +New in 5.0.0 is: + +- improved direct mount map support + - single level map entries now work as expected. + - top level directory of direct mount hierarchy no longer obscures + directory tree below. + +- `+' map inclusion + - maps preceded by a `+' in file maps may now be included inline. + - implemented for the master map and mount maps. + +- added nsswitch map source support + - is now used to locate maps if no map source is given in their + specification. + - is used for both master map and mount maps. + +- rewrote multi-mount map code + - multi-mount map entry offsets now mount and expire independently + instead of having to mount and umount all offset entries at once. + +- added LDAP encryption and authentication support + - needs to be enabled using a configure option. + - a configuration file specifies whether encryption and authentication + is to be used (see INSTALL and auto.master(5) for more information). + +- improved shutdown and restart + - needs to be enabled using a configure option. + - see INSTALL for more information. + +- a "hosts" map module has been added + - an entry like "/net -hosts" is now understood and uses the new + multi-mount semantics for lazy mount/umount of exports from the host. + - the implementation is quite simple minded at this stage and will need + refinement. It just iterates through the host table and adds any host + names it finds to the internal map. + +Things not yet done +=================== + +- I have no way to test the Gentoo setup here anymore so it is unlikely + to work. Patches from anyone who needs this are welcome. + +Ian + diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..00811e0 --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,449 @@ +dnl +dnl -------------------------------------------------------------------------- +dnl AF_PATH_INCLUDE: +dnl +dnl Like AC_PATH_PROGS, but add to the .h file as well +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_PATH_INCLUDE, +[AC_PATH_PROGS($1,$2,$3,$4) +if test -n "$$1"; then + AC_DEFINE(HAVE_$1,1,[define if you have $1]) + AC_DEFINE_UNQUOTED(PATH_$1, "$$1", [define if you have $1]) + HAVE_$1=1 +else + HAVE_$1=0 +fi +AC_SUBST(HAVE_$1)]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_PROG: +dnl +dnl Like AC_CHECK_PROG, but fail configure if not found +dnl and only define PATH_ variable +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_CHECK_PROG, +[AC_PATH_PROGS($1,$2,$3,$4) +if test -n "$$1"; then + AC_DEFINE_UNQUOTED(PATH_$1, "$$1", [define if you have $1]) + PATH_$1="$$1" +else + AC_MSG_ERROR([required program $1 not found]) +fi +AC_SUBST(PATH_$1)]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_SSS_LIB: +dnl +dnl Check if a sss autofs library exists. +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_CHECK_SSS_LIB, +[if test -z "$sssldir"; then + AC_MSG_CHECKING(for sssd autofs library) + for libd in /usr/lib64 /usr/lib; do + if test -z "$sssldir"; then + if test -e "$libd/sssd/modules/$2"; then + sssldir=$libd/sssd/modules + fi + fi + done + if test -n "$sssldir"; then + HAVE_$1=1 + AC_MSG_RESULT(yes) + else + HAVE_$1=0 + AC_MSG_RESULT(no) + fi +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_SLOPPY_MOUNT +dnl +dnl Check to see if mount(8) supports the sloppy (-s) option, and define +dnl the cpp variable HAVE_SLOPPY_MOUNT if so. This requires that MOUNT is +dnl already defined by a call to AF_PATH_INCLUDE or AC_PATH_PROGS. +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_SLOPPY_MOUNT, +[if test -n "$MOUNT" ; then + AC_MSG_CHECKING([if mount accepts the -s option]) + if "$MOUNT" -s > /dev/null 2>&1 ; then + enable_sloppy_mount=yes + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi +fi]) + + +dnl -------------------------------------------------------------------------- +dnl AF_LINUX_PROCFS +dnl +dnl Check for the Linux /proc filesystem +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_LINUX_PROCFS, +[AC_CACHE_CHECK([for Linux proc filesystem], [ac_cv_linux_procfs], + [ac_cv_linux_procfs=no + test "x`cat /proc/sys/kernel/ostype 2>&-`" = "xLinux" && ac_cv_linux_procfs=yes]) + if test $ac_cv_linux_procfs = yes + then + AC_DEFINE(HAVE_LINUX_PROCFS, 1, + [Define if you have the Linux /proc filesystem.]) +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_INIT_D +dnl +dnl Check the location of the init.d directory +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_INIT_D, +[if test -z "$initdir"; then + AC_MSG_CHECKING([location of the init.d directory]) + for init_d in /etc/init.d /etc/rc.d/init.d; do + if test -z "$initdir"; then + if test -d "$init_d"; then + initdir="$init_d" + AC_MSG_RESULT($initdir) + fi + fi + done +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_CONF_D +dnl +dnl Check the location of the configuration defaults directory +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_CONF_D, +[if test -z "$confdir"; then + for conf_d in /etc/sysconfig /etc/defaults /etc/conf.d /etc/default; do + if test -z "$confdir"; then + if test -d "$conf_d"; then + confdir="$conf_d" + fi + fi + done +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_MAP_D +dnl +dnl Check the location of the autofs maps directory +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_MAP_D, +[if test -z "$mapdir"; then + for map_d in /etc/autofs /etc; do + if test -z "$mapdir"; then + if test -d "$map_d"; then + mapdir="$map_d" + fi + fi + done +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_PID_D +dnl +dnl Check the location of the pid file directory. +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_PID_D, +[if test -z "$piddir"; then + for pid_d in /run /var/run /tmp; do + if test -z "$piddir"; then + if test -d "$pid_d"; then + piddir="$pid_d" + fi + fi + done +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_FIFO_D +dnl +dnl Check the location of the autofs fifos directory +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_FIFO_D, +[if test -z "$fifodir"; then + for fifo_d in /run /var/run /tmp; do + if test -z "$fifodir"; then + if test -d "$fifo_d"; then + fifodir="$fifo_d" + fi + fi + done +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_FLAG_D +dnl +dnl Check the location of the autofs flag file directory +dnl -------------------------------------------------------------------------- +AC_DEFUN(AF_FLAG_D, +[if test -z "$flagdir"; then + for flag_d in /run /var/run /tmp; do + if test -z "$flagdir"; then + if test -d "$flag_d"; then + flagdir="$flag_d" + fi + fi + done +fi]) + +dnl ----------------------------------- ## -*- Autoconf -*- +dnl Check if --with-dmalloc was given. ## +dnl From Franc,ois Pinard ## +dnl ----------------------------------- ## +dnl +dnl Copyright (C) 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2005 +dnl Free Software Foundation, Inc. +dnl +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl serial 3 + +AC_DEFUN([AM_WITH_DMALLOC], +[AC_MSG_CHECKING([if malloc debugging is wanted]) +AC_ARG_WITH(dmalloc, +[ --with-dmalloc use dmalloc, as in + http://www.dmalloc.com/dmalloc.tar.gz], +[if test "$withval" = yes; then + AC_MSG_RESULT(yes) + AC_DEFINE(WITH_DMALLOC,1, + [Define if using the dmalloc debugging malloc package]) + DMALLOCLIB="-ldmallocth" + LDFLAGS="$LDFLAGS -g" +else + AC_MSG_RESULT(no) +fi], [AC_MSG_RESULT(no)]) +]) + +dnl -------------------------------------------------------------------------- +dnl AF_WITH_SYSTEMD +dnl +dnl Check the location of the systemd unit files directory +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_WITH_SYSTEMD], +[AC_ARG_WITH(systemd, +[ --with-systemd@<:@=systemddir@:>@ install systemd unit file. If 'yes' + probe the system for unit directory. + If a path is specified, assume that + is a valid install path.], +[if test "$withval" = yes; then + if test -z "$systemddir"; then + AC_MSG_CHECKING([location of the systemd unit files directory]) + for systemd_d in /usr/lib/systemd/system /usr/lib64/systemd/system /lib/systemd/system /lib64/systemd/system; do + if test -z "$systemddir"; then + if test -d "$systemd_d"; then + systemddir="$systemd_d" + fi + fi + done + fi + if test -n "$systemddir"; then + AC_MSG_RESULT($systemddir) + else + AC_MSG_RESULT(not found) + fi +else + if test "$withval" != no; then + systemddir=$withval + fi +fi]) +]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_LIBXML +dnl +dnl Check for lib xml +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_LIBXML], +[AC_PATH_PROGS(XML_CONFIG, xml2-config, no) +AC_MSG_CHECKING(for libxml2) +if test "$XML_CONFIG" = "no" +then + AC_MSG_RESULT(no) + HAVE_LIBXML=0 +else + AC_MSG_RESULT(yes) + HAVE_LIBXML=1 + XML_LIBS=`$XML_CONFIG --libs` + XML_FLAGS=`$XML_CONFIG --cflags` + XML_VER=`$XML_CONFIG --version` + XML_MAJOR=`echo $XML_VER|cut -d\. -f1` + if test $XML_MAJOR -le 99 + then + XML_MINOR=`echo $XML_VER|cut -d\. -f2` + if test $XML_MINOR -le 99 + then + XML_REV=`echo $XML_VER|cut -d\. -f3` + if test $XML_REV -le 99; then + AC_DEFINE(LIBXML2_WORKAROUND,1, [Use libxml2 tsd usage workaround]) + fi + fi + fi +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_KRB5 +dnl +dnl Check for Kerberos 5 +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_KRB5], +[AC_PATH_PROGS(KRB5_CONFIG, krb5-config, no) +AC_MSG_CHECKING(for Kerberos library) +if test "$KRB5_CONFIG" = "no" +then + AC_MSG_RESULT(no) + HAVE_KRB5=0 +else + AC_MSG_RESULT(yes) + HAVE_KRB5=1 + KRB5_LIBS=`$KRB5_CONFIG --libs` + KRB5_FLAGS=`$KRB5_CONFIG --cflags` + + SAVE_CFLAGS=$CFLAGS + SAVE_LIBS=$LIBS + CFLAGS="$CFLAGS $KRB5_FLAGS" + LIBS="$LIBS $KRB5_LIBS" + + AC_CHECK_FUNCS([krb5_principal_get_realm]) +fi]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_LIBHESIOD +dnl +dnl Check for lib hesiod +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_LIBHESIOD], +[AC_MSG_CHECKING(for libhesiod) + +# save current libs +af_check_hesiod_save_libs="$LIBS" +LIBS="$LIBS -lhesiod -lresolv" + +AC_TRY_LINK( + [ #include ], + [ void *c; hesiod_init(&c); ], + [ HAVE_HESIOD=1 + LIBHESIOD="$LIBHESIOD -lhesiod -lresolv" + AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) ]) + +# restore libs +LIBS="$af_check_hesiod_save_libs" +]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_FUNC_LDAP_CREATE_PAGE_CONTROL +dnl +dnl Check for function ldap_create_page_control +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_FUNC_LDAP_CREATE_PAGE_CONTROL], +[AC_MSG_CHECKING(for ldap_create_page_control in -lldap) + +# save current libs +af_check_ldap_create_page_control_save_libs="$LIBS" +LIBS="$LIBS -lldap" + +AC_TRY_LINK( + [ #include ], + [ LDAP *ld; + ber_int_t ps; + struct berval *c; + int ic, ret; + LDAPControl **clp; + ret = ldap_create_page_control(ld,ps,c,ic,clp); ], + [ af_have_ldap_create_page_control=yes + AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) ]) + +if test "$af_have_ldap_create_page_control" = "yes"; then + AC_DEFINE(HAVE_LDAP_CREATE_PAGE_CONTROL, 1, + [Define to 1 if you have the `ldap_create_page_control' function.]) +fi + +# restore libs +LIBS="$af_check_ldap_create_page_control_save_libs" +]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_FUNC_LDAP_PARSE_PAGE_CONTROL +dnl +dnl Check for function ldap_parse_page_control +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_FUNC_LDAP_PARSE_PAGE_CONTROL], +[AC_MSG_CHECKING(for ldap_parse_page_control in -lldap) + +# save current libs +af_check_ldap_parse_page_control_save_libs="$LIBS" +LIBS="$LIBS -lldap" + +AC_TRY_LINK( + [ #include ], + [ LDAP *ld; + ber_int_t ct; + struct berval *c; + int ret; + LDAPControl **clp; + ret = ldap_parse_page_control(ld,clp,ct,c); ], + [ af_have_ldap_parse_page_control=yes + AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) ]) + +if test "$af_have_ldap_create_page_control" = "yes"; then + AC_DEFINE(HAVE_LDAP_PARSE_PAGE_CONTROL, 1, + [Define to 1 if you have the `ldap_parse_page_control' function.]) +fi + +# restore libs +LIBS="$af_check_ldap_parse_page_control_save_libs" +]) + +dnl -------------------------------------------------------------------------- +dnl AF_CHECK_LIBTIRPC +dnl +dnl Use libtirpc for rpc transport +dnl -------------------------------------------------------------------------- +AC_DEFUN([AF_CHECK_LIBTIRPC], +[ +# save current flags +af_check_libtirpc_save_cflags="$CFLAGS" +af_check_libtirpc_save_libs="$LIBS" +CFLAGS="$CFLAGS -I/usr/include/tirpc" +LIBS="$LIBS -ltirpc" + +AC_TRY_LINK( + [ #include ], + [ CLIENT *cl; + struct sockaddr_in addr; + int fd; + unsigned long ul; struct timeval t; unsigned int ui; + cl = clntudp_bufcreate(&addr,ul,ul,t,&fd,ui,ui); ], + [ af_have_libtirpc=yes + AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) ]) + +if test "$af_have_libtirpc" = "yes"; then + AC_DEFINE(WITH_LIBTIRPC,1, [Define to 1 if you have the libtirpc library installed]) + AC_DEFINE(TIRPC_WORKAROUND,1, [Define to 1 to use the libtirpc tsd usage workaround]) + TIRPCLIB="-ltirpc" +fi + +AC_CHECK_FUNCS([getrpcbyname getservbyname]) + +# restore flags +CFLAGS="$af_check_libtirpc_save_cflags" +LIBS="$af_check_libtirpc_save_libs" +]) + +AC_DEFUN([AF_WITH_LIBTIRPC], +[AC_MSG_CHECKING([if libtirpc is requested and available]) +AC_ARG_WITH(libtirpc, +[ --with-libtirpc use libtirpc if available], +[if test "$withval" = yes; then + AF_CHECK_LIBTIRPC() +else + AC_MSG_RESULT(no) +fi], [AC_MSG_RESULT(no)]) +]) + diff --git a/autofs.spec b/autofs.spec new file mode 100644 index 0000000..c40fc9f --- /dev/null +++ b/autofs.spec @@ -0,0 +1,199 @@ +# +# +%ifarch sparc i386 i586 i686 +%define _lib lib +%endif + +%ifarch x86_64 sparc64 +%define _lib lib64 +%endif + +# Use --without systemd in your rpmbuild command or force values to 0 to +# disable them. +%define with_systemd %{?_without_systemd: 0} %{?!_without_systemd: 1} + +# Use --without libtirpc in your rpmbuild command or force values to 0 to +# disable them. +%define with_libtirpc %{?_without_libtirpc: 0} %{?!_without_libtirpc: 1} + +Summary: A tool from automatically mounting and umounting filesystems. +Name: autofs +%define version 5.1.3 +%define release 1 +Version: %{version} +Release: %{release} +License: GPL +Group: System Environment/Daemons +Source: ftp://ftp.kernel.org/pub/linux/daemons/autofs/v5/autofs-%{version}.tar.gz +Buildroot: %{_tmppath}/%{name}-tmp +%if %{with_systemd} +BuildRequires: systemd-units +%endif +%if %{with_libtirpc} +BuildRequires: libtirpc-devel +%endif +BuildRequires: autoconf, hesiod-devel, openldap-devel, bison, flex, cyrus-sasl-devel +Requires: chkconfig +Requires: /bin/bash mktemp sed textutils sh-utils grep /bin/ps +%if %{with_systemd} +Requires(post): systemd-sysv +Requires(post): systemd-units +Requires(preun): systemd-units +Requires(postun): systemd-units +%endif +Obsoletes: autofs-ldap +Summary(de): autofs daemon +Summary(fr): démon autofs +Summary(tr): autofs sunucu süreci +Summary(sv): autofs-daemon + +%description +autofs is a daemon which automatically mounts filesystems when you use +them, and unmounts them later when you are not using them. This can +include network filesystems, CD-ROMs, floppies, and so forth. + +%description -l de +autofs ist ein Dämon, der Dateisysteme automatisch montiert, wenn sie +benutzt werden, und sie später bei Nichtbenutzung wieder demontiert. +Dies kann Netz-Dateisysteme, CD-ROMs, Disketten und ähnliches einschließen. + +%description -l fr +autofs est un démon qui monte automatiquement les systèmes de fichiers +lorsqu'on les utilise et les démonte lorsqu'on ne les utilise plus. Cela +inclus les systèmes de fichiers réseau, les CD-ROMs, les disquettes, etc. + +%description -l tr +autofs, kullanýlan dosya sistemlerini gerek olunca kendiliðinden baðlar +ve kullanýmlarý sona erince yine kendiliðinden çözer. Bu iþlem, að dosya +sistemleri, CD-ROM'lar ve disketler üzerinde yapýlabilir. + +%description -l sv +autofs är en daemon som mountar filsystem när de använda, och senare +unmountar dem när de har varit oanvända en bestämd tid. Detta kan +inkludera nätfilsystem, CD-ROM, floppydiskar, och så vidare. + +%prep +%setup -q -n %{name}-%{version} +echo %{version}-%{release} > .version +%if %{with_systemd} + %define unitdir %{?_unitdir:/lib/systemd/system} + %define systemd_configure_arg --with-systemd +%endif +%if %{with_libtirpc} + %define libtirpc_configure_arg --with-libtirpc +%endif + +%build +CFLAGS="$RPM_OPT_FLAGS -Wall" \ +LDFLAGS="-Wl,-z,now" \ +./configure --libdir=%{_libdir} \ + --disable-mount-locking \ + --enable-ignore-busy \ + %{?systemd_configure_arg:} \ + %{?libtirpc_configure_arg:} +CFLAGS="$RPM_OPT_FLAGS -Wall" LDFLAGS="-Wl,-z,now" make initdir=/etc/rc.d/init.d DONTSTRIP=1 + +%install +rm -rf $RPM_BUILD_ROOT +%if %{with_systemd} +install -d -m 755 $RPM_BUILD_ROOT%{unitdir} +%else +mkdir -p -m755 $RPM_BUILD_ROOT/etc/rc.d/init.d +%endif +mkdir -p -m755 $RPM_BUILD_ROOT%{_sbindir} +mkdir -p -m755 $RPM_BUILD_ROOT%{_libdir}/autofs +mkdir -p -m755 $RPM_BUILD_ROOT%{_mandir}/{man5,man8} +mkdir -p -m755 $RPM_BUILD_ROOT/etc/sysconfig +mkdir -p -m755 $RPM_BUILD_ROOT/etc/auto.master.d + +make install mandir=%{_mandir} initdir=/etc/rc.d/init.d INSTALLROOT=$RPM_BUILD_ROOT +echo make -C redhat +make -C redhat +%if %{with_systemd} +# Configure can get this wrong when the unit files appear under /lib and /usr/lib +find $RPM_BUILD_ROOT -type f -name autofs.service -exec rm -f {} \; +install -m 644 redhat/autofs.service $RPM_BUILD_ROOT%{unitdir}/autofs.service +%define init_file_name %{unitdir}/autofs.service +%else +install -m 755 redhat/autofs.init $RPM_BUILD_ROOT/etc/rc.d/init.d/autofs +%define init_file_name /etc/rc.d/init.d/autofs +%endif +install -m 644 redhat/autofs.conf $RPM_BUILD_ROOT/etc/autofs.conf +install -m 644 redhat/autofs.sysconfig $RPM_BUILD_ROOT/etc/sysconfig/autofs + +%clean +[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT + +%post +%if %{with_systemd} +if [ $1 -eq 1 ]; then + %{_bindir}/systemctl daemon-reload >/dev/null 2>&1 || : + # autofs has been approved to be enabled by default + %{_bindir}/systemctl enable %{name}.service >/dev/null 2>&1 || : +fi +%else +if [ $1 -eq 1 ]; then + %{_sbindir}/chkconfig --add autofs +fi +%endif + +%preun +%if %{with_systemd} +if [ $1 -eq 0 ] ; then + %{_bindir}/systemctl --no-reload disable %{name}.service > /dev/null 2>&1 || : + %{_bindir}/systemctl stop %{name}.service > /dev/null 2>&1 || : +fi +%else +if [ $1 -eq 0 ] ; then + %{_sbindir}/service autofs stop > /dev/null 2>&1 || : + %{_sbindir}/chkconfig --del autofs +fi +%endif + +%postun +%if %{with_systemd} +%{_bindir}/systemctl daemon-reload >/dev/null 2>&1 || : +if [ $1 -ge 1 ] ; then + # Package upgrade, not removal + %{_bindir}/systemctl try-restart %{name}.service >/dev/null 2>&1 || : +fi +%else +if [ $1 -ge 1 ] ; then + %{_sbindir}/service autofs condrestart > /dev/null 2>&1 || : +fi +%endif + +#%triggerun -- %{name} < $bla release +## Save the current service runlevel info +## User must manually run systemd-sysv-convert --apply %{name} +## to migrate them to systemd targets +#%{_bindir}/systemd-sysv-convert --save %{name} >/dev/null 2>&1 ||: +# +## Run these because the SysV package being removed won't do them +#%{_sbindir}/chkconfig --del %{name} >/dev/null 2>&1 || : +#%{_bindir}/systemctl try-restart %{name}.service >/dev/null 2>&1 || : + +%files +%defattr(-,root,root) +%doc CREDITS CHANGELOG INSTALL COPY* README* samples/ldap* samples/autofs.schema samples/autofs_ldap_auth.conf +%config %{init_file_name} +%config(noreplace) /etc/auto.master +%config(noreplace) /etc/autofs.conf +%config(noreplace,missingok) /etc/auto.misc +%config(noreplace,missingok) /etc/auto.net +%config(noreplace,missingok) /etc/auto.smb +%config(noreplace) /etc/sysconfig/autofs +%config(noreplace) /etc/autofs_ldap_auth.conf +%{_sbindir}/automount +%dir %{_libdir}/autofs +%{_libdir}/autofs/* +%{_mandir}/*/* +%dir /etc/auto.master.d + +%changelog +* Wed May 24 2017 Ian Kent +- Update package to version 5.1.3. + +* Wed Jun 15 2016 Ian Kent +- Update package to version 5.1.2. + diff --git a/configure b/configure new file mode 100755 index 0000000..10f907e --- /dev/null +++ b/configure @@ -0,0 +1,6950 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file=".autofs-5.1.3" +ac_default_prefix=/usr +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +DAEMON_LDFLAGS +DAEMON_CFLAGS +KRB5_FLAGS +KRB5_LIBS +LIBSASL +HAVE_SASL +SASL_FLAGS +XML_LIBS +XML_FLAGS +LIBLDAP +HAVE_LDAP +LDAP_FLAGS +HAVE_YPCLNT +HAVE_NISPLUS +EGREP +GREP +CPP +HESIOD_FLAGS +LIBHESIOD +HAVE_HESIOD +LIBRESOLV +LIBNSL +LIBCLOCK_GETTIME +KRB5_CONFIG +XML_CONFIG +sssldir +HAVE_SSS_AUTOFS +PATH_RPCGEN +RPCGEN +PATH_RANLIB +RANLIB +PATH_YACC +YACC +PATH_LEX +LEX +HAVE_MODPROBE +MODPROBE +HAVE_E4FSCK +E4FSCK +HAVE_E3FSCK +E3FSCK +HAVE_E2FSCK +E2FSCK +HAVE_UMOUNT +UMOUNT +HAVE_MOUNT_NFS +MOUNT_NFS +HAVE_MOUNT +MOUNT +DMALLOCLIB +TIRPCLIB +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +flagdir +fifodir +mapdir +confdir +systemddir +piddir +initdir +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_path +with_systemd +with_confdir +with_mapdir +with_fifodir +with_flagdir +with_libtirpc +with_dmalloc +enable_sloppy_mount +with_hesiod +with_openldap +with_sasl +enable_ext_env +enable_mount_locking +enable_force_shutdown +enable_ignore_busy +enable_limit_getgrgid_size +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-sloppy-mount enable the use of the -s option to mount + --disable-ext-env disable search in environment for substitution variable + --disable-mount-locking disable use of locking when spawning mount command + --enable-force-shutdown enable USR1 signal to force unlink umount of any + busy mounts during shutdown + --enable-ignore-busy enable exit without umounting busy mounts during + shutdown + --enable-limit-getgrgid-size enable limit stack use of getgrgid_r() + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-path=PATH look in PATH for binaries needed by the automounter + --with-systemd[=systemddir] install systemd unit file. If 'yes' + probe the system for unit directory. + If a path is specified, assume that + is a valid install path. + --with-confdir=DIR use DIR for autofs configuration files + --with-mapdir=PATH look in PATH for mount maps used by the automounter + --with-fifodir=PATH use PATH as the directory for fifos used by the automounter + --with-flagdir=PATH use PATH as the directory for the flag file used by the automounter + --with-libtirpc use libtirpc if available + --with-dmalloc use dmalloc, as in + http://www.dmalloc.com/dmalloc.tar.gz + --with-hesiod=DIR enable Hesiod support (libs and includes in DIR) + --with-openldap=DIR enable OpenLDAP map support (libs and includes in DIR) + --with-sasl=DIR enable SASL support for LDAP maps (libs and includes in DIR) + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +# +# autofs installs by default in /usr +# + + +# +# The user can specify --with-path=PATH rather than relying on the default +# +searchpath="/usr/bin:/bin:/usr/sbin:/sbin" + +# Check whether --with-path was given. +if test "${with_path+set}" = set; then : + withval=$with_path; if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + searchpath="${withval}" + fi + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for binaries in" >&5 +$as_echo_n "checking for binaries in... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $searchpath" >&5 +$as_echo "$searchpath" >&6; } + +# +# Make sure we have "/proc" +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Linux proc filesystem" >&5 +$as_echo_n "checking for Linux proc filesystem... " >&6; } +if ${ac_cv_linux_procfs+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_linux_procfs=no + test "x`cat /proc/sys/kernel/ostype 2>&-`" = "xLinux" && ac_cv_linux_procfs=yes +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_linux_procfs" >&5 +$as_echo "$ac_cv_linux_procfs" >&6; } + if test $ac_cv_linux_procfs = yes + then + +$as_echo "#define HAVE_LINUX_PROCFS 1" >>confdefs.h + +fi + +# +# Location of init.d directory? +# +if test -z "$initdir"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking location of the init.d directory" >&5 +$as_echo_n "checking location of the init.d directory... " >&6; } + for init_d in /etc/init.d /etc/rc.d/init.d; do + if test -z "$initdir"; then + if test -d "$init_d"; then + initdir="$init_d" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $initdir" >&5 +$as_echo "$initdir" >&6; } + fi + fi + done +fi + +if test -z "$piddir"; then + for pid_d in /run /var/run /tmp; do + if test -z "$piddir"; then + if test -d "$pid_d"; then + piddir="$pid_d" + fi + fi + done +fi + + +# +# Check for systemd unit files direectory exists if unit file installation +# is requested +# + +# Check whether --with-systemd was given. +if test "${with_systemd+set}" = set; then : + withval=$with_systemd; if test "$withval" = yes; then + if test -z "$systemddir"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking location of the systemd unit files directory" >&5 +$as_echo_n "checking location of the systemd unit files directory... " >&6; } + for systemd_d in /usr/lib/systemd/system /usr/lib64/systemd/system /lib/systemd/system /lib64/systemd/system; do + if test -z "$systemddir"; then + if test -d "$systemd_d"; then + systemddir="$systemd_d" + fi + fi + done + fi + if test -n "$systemddir"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $systemddir" >&5 +$as_echo "$systemddir" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5 +$as_echo "not found" >&6; } + fi +else + if test "$withval" != no; then + systemddir=$withval + fi +fi +fi + + + + +# +# Location of system config script directory? +# +if test -z "$confdir"; then + for conf_d in /etc/sysconfig /etc/defaults /etc/conf.d /etc/default; do + if test -z "$confdir"; then + if test -d "$conf_d"; then + confdir="$conf_d" + fi + fi + done +fi + +# Check whether --with-confdir was given. +if test "${with_confdir+set}" = set; then : + withval=$with_confdir; if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + confdir="${withval}" + fi + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for autofs configuration file directory" >&5 +$as_echo_n "checking for autofs configuration file directory... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $confdir" >&5 +$as_echo "$confdir" >&6; } + + +# +# The user can specify --with-mapsdir=PATH to specify autofs maps go +# +if test -z "$mapdir"; then + for map_d in /etc/autofs /etc; do + if test -z "$mapdir"; then + if test -d "$map_d"; then + mapdir="$map_d" + fi + fi + done +fi + +# Check whether --with-mapdir was given. +if test "${with_mapdir+set}" = set; then : + withval=$with_mapdir; if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + mapdir="${withval}" + fi + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for autofs maps directory" >&5 +$as_echo_n "checking for autofs maps directory... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $mapdir" >&5 +$as_echo "$mapdir" >&6; } + + +# +# The user can specify --with-fifodir=PATH to specify where autofs fifos go +# +if test -z "$fifodir"; then + for fifo_d in /run /var/run /tmp; do + if test -z "$fifodir"; then + if test -d "$fifo_d"; then + fifodir="$fifo_d" + fi + fi + done +fi + +# Check whether --with-fifodir was given. +if test "${with_fifodir+set}" = set; then : + withval=$with_fifodir; if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + fifodir="${withval}" + fi + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for autofs fifos directory" >&5 +$as_echo_n "checking for autofs fifos directory... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $fifodir" >&5 +$as_echo "$fifodir" >&6; } + + +# +# The user can specify --with-flagdir=PATH to specify where autofs flag file goes +# +if test -z "$flagdir"; then + for flag_d in /run /var/run /tmp; do + if test -z "$flagdir"; then + if test -d "$flag_d"; then + flagdir="$flag_d" + fi + fi + done +fi + +# Check whether --with-flagdir was given. +if test "${with_flagdir+set}" = set; then : + withval=$with_flagdir; if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + flagdir="${withval}" + fi + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for autofs flag file directory" >&5 +$as_echo_n "checking for autofs flag file directory... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $flagdir" >&5 +$as_echo "$flagdir" >&6; } + + +# +# Use libtirpc +# +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtirpc is requested and available" >&5 +$as_echo_n "checking if libtirpc is requested and available... " >&6; } + +# Check whether --with-libtirpc was given. +if test "${with_libtirpc+set}" = set; then : + withval=$with_libtirpc; if test "$withval" = yes; then + +# save current flags +af_check_libtirpc_save_cflags="$CFLAGS" +af_check_libtirpc_save_libs="$LIBS" +CFLAGS="$CFLAGS -I/usr/include/tirpc" +LIBS="$LIBS -ltirpc" + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include +int +main () +{ + CLIENT *cl; + struct sockaddr_in addr; + int fd; + unsigned long ul; struct timeval t; unsigned int ui; + cl = clntudp_bufcreate(&addr,ul,ul,t,&fd,ui,ui); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + af_have_libtirpc=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +if test "$af_have_libtirpc" = "yes"; then + +$as_echo "#define WITH_LIBTIRPC 1" >>confdefs.h + + +$as_echo "#define TIRPC_WORKAROUND 1" >>confdefs.h + + TIRPCLIB="-ltirpc" +fi + +for ac_func in getrpcbyname getservbyname +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +# restore flags +CFLAGS="$af_check_libtirpc_save_cflags" +LIBS="$af_check_libtirpc_save_libs" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + +# +# Optional include dmalloc +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if malloc debugging is wanted" >&5 +$as_echo_n "checking if malloc debugging is wanted... " >&6; } + +# Check whether --with-dmalloc was given. +if test "${with_dmalloc+set}" = set; then : + withval=$with_dmalloc; if test "$withval" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define WITH_DMALLOC 1" >>confdefs.h + + DMALLOCLIB="-ldmallocth" + LDFLAGS="$LDFLAGS -g" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + +# +# Programs needed for various system functions or modules +# +for ac_prog in mount +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_MOUNT+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MOUNT in + [\\/]* | ?:[\\/]*) + ac_cv_path_MOUNT="$MOUNT" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_MOUNT="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +MOUNT=$ac_cv_path_MOUNT +if test -n "$MOUNT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MOUNT" >&5 +$as_echo "$MOUNT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$MOUNT" && break +done +test -n "$MOUNT" || MOUNT="/bin/mount" + +if test -n "$MOUNT"; then + +$as_echo "#define HAVE_MOUNT 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_MOUNT "$MOUNT" +_ACEOF + + HAVE_MOUNT=1 +else + HAVE_MOUNT=0 +fi + +for ac_prog in mount.nfs +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_MOUNT_NFS+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MOUNT_NFS in + [\\/]* | ?:[\\/]*) + ac_cv_path_MOUNT_NFS="$MOUNT_NFS" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_MOUNT_NFS="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +MOUNT_NFS=$ac_cv_path_MOUNT_NFS +if test -n "$MOUNT_NFS"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MOUNT_NFS" >&5 +$as_echo "$MOUNT_NFS" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$MOUNT_NFS" && break +done +test -n "$MOUNT_NFS" || MOUNT_NFS="/sbin/mount.nfs " + +if test -n "$MOUNT_NFS"; then + +$as_echo "#define HAVE_MOUNT_NFS 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_MOUNT_NFS "$MOUNT_NFS" +_ACEOF + + HAVE_MOUNT_NFS=1 +else + HAVE_MOUNT_NFS=0 +fi + +for ac_prog in umount +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_UMOUNT+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $UMOUNT in + [\\/]* | ?:[\\/]*) + ac_cv_path_UMOUNT="$UMOUNT" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_UMOUNT="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +UMOUNT=$ac_cv_path_UMOUNT +if test -n "$UMOUNT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $UMOUNT" >&5 +$as_echo "$UMOUNT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$UMOUNT" && break +done +test -n "$UMOUNT" || UMOUNT="/bin/umount" + +if test -n "$UMOUNT"; then + +$as_echo "#define HAVE_UMOUNT 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_UMOUNT "$UMOUNT" +_ACEOF + + HAVE_UMOUNT=1 +else + HAVE_UMOUNT=0 +fi + +for ac_prog in fsck.ext2 e2fsck +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_E2FSCK+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $E2FSCK in + [\\/]* | ?:[\\/]*) + ac_cv_path_E2FSCK="$E2FSCK" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_E2FSCK="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +E2FSCK=$ac_cv_path_E2FSCK +if test -n "$E2FSCK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $E2FSCK" >&5 +$as_echo "$E2FSCK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$E2FSCK" && break +done + +if test -n "$E2FSCK"; then + +$as_echo "#define HAVE_E2FSCK 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_E2FSCK "$E2FSCK" +_ACEOF + + HAVE_E2FSCK=1 +else + HAVE_E2FSCK=0 +fi + +for ac_prog in fsck.ext3 e3fsck +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_E3FSCK+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $E3FSCK in + [\\/]* | ?:[\\/]*) + ac_cv_path_E3FSCK="$E3FSCK" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_E3FSCK="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +E3FSCK=$ac_cv_path_E3FSCK +if test -n "$E3FSCK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $E3FSCK" >&5 +$as_echo "$E3FSCK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$E3FSCK" && break +done + +if test -n "$E3FSCK"; then + +$as_echo "#define HAVE_E3FSCK 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_E3FSCK "$E3FSCK" +_ACEOF + + HAVE_E3FSCK=1 +else + HAVE_E3FSCK=0 +fi + +for ac_prog in fsck.ext4 e4fsck +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_E4FSCK+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $E4FSCK in + [\\/]* | ?:[\\/]*) + ac_cv_path_E4FSCK="$E4FSCK" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_E4FSCK="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +E4FSCK=$ac_cv_path_E4FSCK +if test -n "$E4FSCK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $E4FSCK" >&5 +$as_echo "$E4FSCK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$E4FSCK" && break +done + +if test -n "$E4FSCK"; then + +$as_echo "#define HAVE_E4FSCK 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_E4FSCK "$E4FSCK" +_ACEOF + + HAVE_E4FSCK=1 +else + HAVE_E4FSCK=0 +fi + +for ac_prog in modprobe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_MODPROBE+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MODPROBE in + [\\/]* | ?:[\\/]*) + ac_cv_path_MODPROBE="$MODPROBE" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_MODPROBE="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +MODPROBE=$ac_cv_path_MODPROBE +if test -n "$MODPROBE"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MODPROBE" >&5 +$as_echo "$MODPROBE" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$MODPROBE" && break +done + +if test -n "$MODPROBE"; then + +$as_echo "#define HAVE_MODPROBE 1" >>confdefs.h + + +cat >>confdefs.h <<_ACEOF +#define PATH_MODPROBE "$MODPROBE" +_ACEOF + + HAVE_MODPROBE=1 +else + HAVE_MODPROBE=0 +fi + + +for ac_prog in flex lex +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_LEX+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $LEX in + [\\/]* | ?:[\\/]*) + ac_cv_path_LEX="$LEX" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_LEX="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +LEX=$ac_cv_path_LEX +if test -n "$LEX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LEX" >&5 +$as_echo "$LEX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$LEX" && break +done + +if test -n "$LEX"; then + +cat >>confdefs.h <<_ACEOF +#define PATH_LEX "$LEX" +_ACEOF + + PATH_LEX="$LEX" +else + as_fn_error $? "required program LEX not found" "$LINENO" 5 +fi + +for ac_prog in bison +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_YACC+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $YACC in + [\\/]* | ?:[\\/]*) + ac_cv_path_YACC="$YACC" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_YACC="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +YACC=$ac_cv_path_YACC +if test -n "$YACC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $YACC" >&5 +$as_echo "$YACC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$YACC" && break +done + +if test -n "$YACC"; then + +cat >>confdefs.h <<_ACEOF +#define PATH_YACC "$YACC" +_ACEOF + + PATH_YACC="$YACC" +else + as_fn_error $? "required program YACC not found" "$LINENO" 5 +fi + +for ac_prog in ranlib +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RANLIB in + [\\/]* | ?:[\\/]*) + ac_cv_path_RANLIB="$RANLIB" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RANLIB="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +RANLIB=$ac_cv_path_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$RANLIB" && break +done + +if test -n "$RANLIB"; then + +cat >>confdefs.h <<_ACEOF +#define PATH_RANLIB "$RANLIB" +_ACEOF + + PATH_RANLIB="$RANLIB" +else + as_fn_error $? "required program RANLIB not found" "$LINENO" 5 +fi + +for ac_prog in rpcgen +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_RPCGEN+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $RPCGEN in + [\\/]* | ?:[\\/]*) + ac_cv_path_RPCGEN="$RPCGEN" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $searchpath +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_RPCGEN="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +RPCGEN=$ac_cv_path_RPCGEN +if test -n "$RPCGEN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RPCGEN" >&5 +$as_echo "$RPCGEN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$RPCGEN" && break +done + +if test -n "$RPCGEN"; then + +cat >>confdefs.h <<_ACEOF +#define PATH_RPCGEN "$RPCGEN" +_ACEOF + + PATH_RPCGEN="$RPCGEN" +else + as_fn_error $? "required program RPCGEN not found" "$LINENO" 5 +fi + + +if test -z "$sssldir"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sssd autofs library" >&5 +$as_echo_n "checking for sssd autofs library... " >&6; } + for libd in /usr/lib64 /usr/lib; do + if test -z "$sssldir"; then + if test -e "$libd/sssd/modules/libsss_autofs.so"; then + sssldir=$libd/sssd/modules + fi + fi + done + if test -n "$sssldir"; then + HAVE_SSS_AUTOFS=1 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + HAVE_SSS_AUTOFS=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi +fi + + + +# +# Newer mounts have the -s (sloppy) option to ignore unknown options, +# good for portability +# +# Check whether --enable-sloppy-mount was given. +if test "${enable_sloppy_mount+set}" = set; then : + enableval=$enable_sloppy_mount; +else + enable_sloppy_mount=auto +fi + +if test x$enable_sloppy_mount = xauto; then + if test -n "$MOUNT" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if mount accepts the -s option" >&5 +$as_echo_n "checking if mount accepts the -s option... " >&6; } + if "$MOUNT" -s > /dev/null 2>&1 ; then + enable_sloppy_mount=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi +fi +fi +if test x$enable_sloppy_mount = xyes; then + +$as_echo "#define HAVE_SLOPPY_MOUNT 1" >>confdefs.h + +fi + +# LDAP SASL auth needs libxml and Kerberos +for ac_prog in xml2-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_XML_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $XML_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_XML_CONFIG="$XML_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_XML_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +XML_CONFIG=$ac_cv_path_XML_CONFIG +if test -n "$XML_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $XML_CONFIG" >&5 +$as_echo "$XML_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$XML_CONFIG" && break +done +test -n "$XML_CONFIG" || XML_CONFIG="no" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libxml2" >&5 +$as_echo_n "checking for libxml2... " >&6; } +if test "$XML_CONFIG" = "no" +then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + HAVE_LIBXML=0 +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + HAVE_LIBXML=1 + XML_LIBS=`$XML_CONFIG --libs` + XML_FLAGS=`$XML_CONFIG --cflags` + XML_VER=`$XML_CONFIG --version` + XML_MAJOR=`echo $XML_VER|cut -d\. -f1` + if test $XML_MAJOR -le 99 + then + XML_MINOR=`echo $XML_VER|cut -d\. -f2` + if test $XML_MINOR -le 99 + then + XML_REV=`echo $XML_VER|cut -d\. -f3` + if test $XML_REV -le 99; then + +$as_echo "#define LIBXML2_WORKAROUND 1" >>confdefs.h + + fi + fi + fi +fi +for ac_prog in krb5-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_KRB5_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $KRB5_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_KRB5_CONFIG="$KRB5_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_KRB5_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +KRB5_CONFIG=$ac_cv_path_KRB5_CONFIG +if test -n "$KRB5_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $KRB5_CONFIG" >&5 +$as_echo "$KRB5_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$KRB5_CONFIG" && break +done +test -n "$KRB5_CONFIG" || KRB5_CONFIG="no" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Kerberos library" >&5 +$as_echo_n "checking for Kerberos library... " >&6; } +if test "$KRB5_CONFIG" = "no" +then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + HAVE_KRB5=0 +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + HAVE_KRB5=1 + KRB5_LIBS=`$KRB5_CONFIG --libs` + KRB5_FLAGS=`$KRB5_CONFIG --cflags` + + SAVE_CFLAGS=$CFLAGS + SAVE_LIBS=$LIBS + CFLAGS="$CFLAGS $KRB5_FLAGS" + LIBS="$LIBS $KRB5_LIBS" + + for ac_func in krb5_principal_get_realm +do : + ac_fn_c_check_func "$LINENO" "krb5_principal_get_realm" "ac_cv_func_krb5_principal_get_realm" +if test "x$ac_cv_func_krb5_principal_get_realm" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_KRB5_PRINCIPAL_GET_REALM 1 +_ACEOF + +fi +done + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing versionsort" >&5 +$as_echo_n "checking for library containing versionsort... " >&6; } +if ${ac_cv_search_versionsort+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char versionsort (); +int +main () +{ +return versionsort (); + ; + return 0; +} +_ACEOF +for ac_lib in '' ; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_versionsort=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_versionsort+:} false; then : + break +fi +done +if ${ac_cv_search_versionsort+:} false; then : + +else + ac_cv_search_versionsort=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_versionsort" >&5 +$as_echo "$ac_cv_search_versionsort" >&6; } +ac_res=$ac_cv_search_versionsort +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +if test "$ac_cv_search_versionsort" = "no"; then + +$as_echo "#define WITHOUT_VERSIONSORT 1" >>confdefs.h + +fi + +# glibc < 2.17 needs librt for clock_gettime() +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if ${ac_cv_lib_rt_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rt_clock_gettime=yes +else + ac_cv_lib_rt_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : + LIBCLOCK_GETTIME="-lrt" +fi + + + +# +# glibc/libc 6 new libraries +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for yp_match in -lnsl" >&5 +$as_echo_n "checking for yp_match in -lnsl... " >&6; } +if ${ac_cv_lib_nsl_yp_match+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnsl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char yp_match (); +int +main () +{ +return yp_match (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nsl_yp_match=yes +else + ac_cv_lib_nsl_yp_match=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_yp_match" >&5 +$as_echo "$ac_cv_lib_nsl_yp_match" >&6; } +if test "x$ac_cv_lib_nsl_yp_match" = xyes; then : + LIBNSL="-lnsl" +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for res_query in -lresolv" >&5 +$as_echo_n "checking for res_query in -lresolv... " >&6; } +if ${ac_cv_lib_resolv_res_query+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lresolv $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char res_query (); +int +main () +{ +return res_query (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_resolv_res_query=yes +else + ac_cv_lib_resolv_res_query=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_res_query" >&5 +$as_echo "$ac_cv_lib_resolv_res_query" >&6; } +if test "x$ac_cv_lib_resolv_res_query" = xyes; then : + LIBRESOLV="-lresolv" +fi + + + +# +# Hesiod support? Expect that this may have a special directory... +# +AF_tmp_ldflags="$LDFLAGS" +LIBHESIOD='' +HAVE_HESIOD='' + +# Check whether --with-hesiod was given. +if test "${with_hesiod+set}" = set; then : + withval=$with_hesiod; if test "$withval" = no + then + HAVE_HESIOD=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for Hesiod in normal directory path + else + : Search for Hesiod in specific directory + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBHESIOD="-L${withval}/lib" + HESIOD_FLAGS="-I${withval}/include" + fi + +fi + + +if test -z "$HAVE_HESIOD" -o "$HAVE_HESIOD" != "0" +then + HAVE_HESIOD=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libhesiod" >&5 +$as_echo_n "checking for libhesiod... " >&6; } + +# save current libs +af_check_hesiod_save_libs="$LIBS" +LIBS="$LIBS -lhesiod -lresolv" + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include +int +main () +{ + void *c; hesiod_init(&c); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + HAVE_HESIOD=1 + LIBHESIOD="$LIBHESIOD -lhesiod -lresolv" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +# restore libs +LIBS="$af_check_hesiod_save_libs" + + if test "$HAVE_HESIOD" == "1"; then + +$as_echo "#define WITH_HESIOD 1" >>confdefs.h + + fi +fi + + + +LDFLAGS="${AF_tmp_ldflags}" + +# NIS+ support? +HAVE_NISPLUS=0 +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +ac_fn_c_check_header_mongrel "$LINENO" "rpcsvc/nis.h" "ac_cv_header_rpcsvc_nis_h" "$ac_includes_default" +if test "x$ac_cv_header_rpcsvc_nis_h" = xyes; then : + HAVE_NISPLUS=1 +fi + + + + +# YellowPages support? +HAVE_YPCLNT=0 +ac_fn_c_check_header_mongrel "$LINENO" "rpcsvc/ypclnt.h" "ac_cv_header_rpcsvc_ypclnt_h" "$ac_includes_default" +if test "x$ac_cv_header_rpcsvc_ypclnt_h" = xyes; then : + HAVE_YPCLNT=1 +fi + + + +if test "$HAVE_YPCLNT" = "1"; then + +$as_echo "#define HAVE_YPCLNT 1" >>confdefs.h + +fi + +# +# OpenLDAP support? Expect that this may have a special directory... +# +AF_tmp_ldflags="$LDFLAGS" +LIBLDAP='' +HAVE_LDAP='' + +# Check whether --with-openldap was given. +if test "${with_openldap+set}" = set; then : + withval=$with_openldap; if test "$withval" = 'no'; then + HAVE_LDAP=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for LDAP in normal directory path + else + : Search for LDAP in specific directory + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBLDAP="-L${withval}/lib" + LDAP_FLAGS="-I${withval}/include" + fi + +fi + +if test -z "$HAVE_LDAP" -o "$HAVE_LDAP" != "0"; then + HAVE_LDAP=0 + LDAP_FLAGS="$LDAP_FLAGS -DLDAP_DEPRECATED=1" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_initialize in -lldap" >&5 +$as_echo_n "checking for ldap_initialize in -lldap... " >&6; } +if ${ac_cv_lib_ldap_ldap_initialize+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lldap -llber -lresolv $LIBS $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ldap_initialize (); +int +main () +{ +return ldap_initialize (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ldap_ldap_initialize=yes +else + ac_cv_lib_ldap_ldap_initialize=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ldap_ldap_initialize" >&5 +$as_echo "$ac_cv_lib_ldap_ldap_initialize" >&6; } +if test "x$ac_cv_lib_ldap_ldap_initialize" = xyes; then : + HAVE_LDAP=1 LIBLDAP="$LIBLDAP -lldap -llber -lresolv" +fi + + if test "$HAVE_LDAP" = "1"; then + +$as_echo "#define WITH_LDAP 1" >>confdefs.h + + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_create_page_control in -lldap" >&5 +$as_echo_n "checking for ldap_create_page_control in -lldap... " >&6; } + +# save current libs +af_check_ldap_create_page_control_save_libs="$LIBS" +LIBS="$LIBS -lldap" + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include +int +main () +{ + LDAP *ld; + ber_int_t ps; + struct berval *c; + int ic, ret; + LDAPControl **clp; + ret = ldap_create_page_control(ld,ps,c,ic,clp); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + af_have_ldap_create_page_control=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +if test "$af_have_ldap_create_page_control" = "yes"; then + +$as_echo "#define HAVE_LDAP_CREATE_PAGE_CONTROL 1" >>confdefs.h + +fi + +# restore libs +LIBS="$af_check_ldap_create_page_control_save_libs" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_parse_page_control in -lldap" >&5 +$as_echo_n "checking for ldap_parse_page_control in -lldap... " >&6; } + +# save current libs +af_check_ldap_parse_page_control_save_libs="$LIBS" +LIBS="$LIBS -lldap" + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include +int +main () +{ + LDAP *ld; + ber_int_t ct; + struct berval *c; + int ret; + LDAPControl **clp; + ret = ldap_parse_page_control(ld,clp,ct,c); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + af_have_ldap_parse_page_control=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +if test "$af_have_ldap_create_page_control" = "yes"; then + +$as_echo "#define HAVE_LDAP_PARSE_PAGE_CONTROL 1" >>confdefs.h + +fi + +# restore libs +LIBS="$af_check_ldap_parse_page_control_save_libs" + +fi + + + + +LDFLAGS="${AF_tmp_ldflags}" + +# +# SASL support +# configure magic taken from: +# http://www.timof.qipc.org/autofs/autofs-4.1.4-ldap-20050930.patch +# + +AF_tmp_ldflags="$LDFLAGS" +LIBSASL='' +HAVE_SASL='' + +# Check whether --with-sasl was given. +if test "${with_sasl+set}" = set; then : + withval=$with_sasl; if test "$withval" = 'no'; then + HAVE_SASL=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for SASL in normal directory path + else + : Search for SASL in specific directory + HAVE_SASL=1 + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBSASL="-L${withval}/lib" + SASL_FLAGS="-I${withval}/include" + fi + +fi + +if test -z "$HAVE_SASL" -o "$HAVE_SASL" != "0" -a "$HAVE_LIBXML" == "1" +then + HAVE_SASL=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sasl_client_start in -lsasl2" >&5 +$as_echo_n "checking for sasl_client_start in -lsasl2... " >&6; } +if ${ac_cv_lib_sasl2_sasl_client_start+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsasl2 -lsasl2 $LIBS $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sasl_client_start (); +int +main () +{ +return sasl_client_start (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_sasl2_sasl_client_start=yes +else + ac_cv_lib_sasl2_sasl_client_start=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sasl2_sasl_client_start" >&5 +$as_echo "$ac_cv_lib_sasl2_sasl_client_start" >&6; } +if test "x$ac_cv_lib_sasl2_sasl_client_start" = xyes; then : + HAVE_SASL=1 LIBSASL="$LIBSASL -lsasl2" +fi + + if test "$HAVE_SASL" == "1"; then + +$as_echo "#define WITH_SASL 1" >>confdefs.h + + fi +fi + + + + + + + + +LDFLAGS="${AF_tmp_ldflags}" + +# +# Does gcc support building position independent executables? +# +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +cat > pietest.c <&5 +$as_echo_n "checking whether gcc -fPIE works... " >&6; } +if test "$cross_compiling" = yes; then : + gcc_supports_pie=no +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +int main(void) {return 0;} + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + gcc_supports_pie=yes +else + gcc_supports_pie=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_supports_pie" >&5 +$as_echo "$gcc_supports_pie" >&6; } +if test $gcc_supports_pie = yes ; then + DAEMON_CFLAGS="-fPIE" + DAEMON_LDFLAGS="-pie" +fi +rm -f pietest.c + + + +# +# Enable ability to access value in external env variable +# +# Check whether --enable-ext-env was given. +if test "${enable_ext_env+set}" = set; then : + enableval=$enable_ext_env; +else + enableval=yes +fi + +if test x$enable_ext_env = xyes -o x$enableval = xyes; then + +$as_echo "#define ENABLE_EXT_ENV 1" >>confdefs.h + +fi + +# +# Disable use of locking when spawning mount command +# +# Check whether --enable-mount-locking was given. +if test "${enable_mount_locking+set}" = set; then : + enableval=$enable_mount_locking; +else + enableval=yes +fi + +if test x$enable_mount_locking = xyes -o x$enableval = xyes; then + +$as_echo "#define ENABLE_MOUNT_LOCKING 1" >>confdefs.h + +fi + +# +# Enable forced shutdown on USR1 signal (unlink umounts all mounts). +# +# Check whether --enable-force-shutdown was given. +if test "${enable_force_shutdown+set}" = set; then : + enableval=$enable_force_shutdown; +else + enableval=no +fi + +if test x$enable_forced_shutdown = xyes -o x$enableval = xyes; then + +$as_echo "#define ENABLE_FORCED_SHUTDOWN 1" >>confdefs.h + +fi + +# +# Enable exit, ignoring busy mounts. +# +# Check whether --enable-ignore-busy was given. +if test "${enable_ignore_busy+set}" = set; then : + enableval=$enable_ignore_busy; +else + enableval=no +fi + +if test x$enable_ignore_busy_mounts = xyes -o x$enableval = xyes; then + +$as_echo "#define ENABLE_IGNORE_BUSY_MOUNTS 1" >>confdefs.h + +fi + +# +# Enable exit, ignoring busy mounts. +# +# Check whether --enable-limit-getgrgid-size was given. +if test "${enable_limit_getgrgid_size+set}" = set; then : + enableval=$enable_limit_getgrgid_size; +else + enableval=no +fi + +if test x$enable_limit_getgrgid_size = xyes -o x$enableval = xyes; then + +$as_echo "#define ENABLE_LIMIT_GETGRGID_SIZE 1" >>confdefs.h + +fi + +# +# Write Makefile.conf and include/config.h +# +ac_config_headers="$ac_config_headers include/config.h" + +ac_config_files="$ac_config_files Makefile.conf" + + + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "include/config.h") CONFIG_HEADERS="$CONFIG_HEADERS include/config.h" ;; + "Makefile.conf") CONFIG_FILES="$CONFIG_FILES Makefile.conf" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi + ;; + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + +# +# Run make clean since we don't explicitly code the header file dependencies +# +ac_config_commands="$ac_config_commands default-1" + diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..0521252 --- /dev/null +++ b/configure.in @@ -0,0 +1,408 @@ +# +# configure.in for the autofs daemon + +AC_PREREQ(2.5) + +# +# Disable caching (the script is tiny, and it doesn't work with --with-path) +# then start autofs +# +define([AC_CACHE_LOAD], )dnl +define([AC_CACHE_SAVE], )dnl +AC_INIT(.autofs-5.1.3) + +# +# autofs installs by default in /usr +# +AC_PREFIX_DEFAULT(/usr) + +# +# The user can specify --with-path=PATH rather than relying on the default +# +searchpath="/usr/bin:/bin:/usr/sbin:/sbin" +AC_ARG_WITH(path, +[ --with-path=PATH look in PATH for binaries needed by the automounter], + if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + searchpath="${withval}" + fi +) + +AC_MSG_CHECKING([for binaries in]) +AC_MSG_RESULT([$searchpath]) + +# +# Make sure we have "/proc" +# +AF_LINUX_PROCFS() + +# +# Location of init.d directory? +# +AF_INIT_D() +AC_SUBST(initdir) +AF_PID_D() +AC_SUBST(piddir) + +# +# Check for systemd unit files direectory exists if unit file installation +# is requested +# +AF_WITH_SYSTEMD() +AC_SUBST(systemddir) + +# +# Location of system config script directory? +# +AF_CONF_D() +AC_ARG_WITH(confdir, +[ --with-confdir=DIR use DIR for autofs configuration files], + if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + confdir="${withval}" + fi +) +AC_MSG_CHECKING([for autofs configuration file directory]) +AC_MSG_RESULT([$confdir]) +AC_SUBST(confdir) + +# +# The user can specify --with-mapsdir=PATH to specify autofs maps go +# +AF_MAP_D() +AC_ARG_WITH(mapdir, +[ --with-mapdir=PATH look in PATH for mount maps used by the automounter], + if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + mapdir="${withval}" + fi +) +AC_MSG_CHECKING([for autofs maps directory]) +AC_MSG_RESULT([$mapdir]) +AC_SUBST(mapdir) + +# +# The user can specify --with-fifodir=PATH to specify where autofs fifos go +# +AF_FIFO_D() +AC_ARG_WITH(fifodir, +[ --with-fifodir=PATH use PATH as the directory for fifos used by the automounter], + if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + fifodir="${withval}" + fi +) +AC_MSG_CHECKING([for autofs fifos directory]) +AC_MSG_RESULT([$fifodir]) +AC_SUBST(fifodir) + +# +# The user can specify --with-flagdir=PATH to specify where autofs flag file goes +# +AF_FLAG_D() +AC_ARG_WITH(flagdir, +[ --with-flagdir=PATH use PATH as the directory for the flag file used by the automounter], + if test -z "$withval" -o "$withval" = "yes" -o "$withval" = "no" + then + : + else + flagdir="${withval}" + fi +) +AC_MSG_CHECKING([for autofs flag file directory]) +AC_MSG_RESULT([$flagdir]) +AC_SUBST(flagdir) + +# +# Use libtirpc +# +AF_WITH_LIBTIRPC() +AC_SUBST(TIRPCLIB) + +# +# Optional include dmalloc +# +AM_WITH_DMALLOC() +AC_SUBST(DMALLOCLIB) + +# +# Programs needed for various system functions or modules +# +AF_PATH_INCLUDE(MOUNT, mount, /bin/mount, $searchpath) +AF_PATH_INCLUDE(MOUNT_NFS, mount.nfs, /sbin/mount.nfs , $searchpath) +AF_PATH_INCLUDE(UMOUNT, umount, /bin/umount, $searchpath) +AF_PATH_INCLUDE(E2FSCK, fsck.ext2 e2fsck, , $searchpath) +AF_PATH_INCLUDE(E3FSCK, fsck.ext3 e3fsck, , $searchpath) +AF_PATH_INCLUDE(E4FSCK, fsck.ext4 e4fsck, , $searchpath) +AF_PATH_INCLUDE(MODPROBE, modprobe, , $searchpath) + +AF_CHECK_PROG(LEX, flex lex, , $searchpath) +AF_CHECK_PROG(YACC, bison, , $searchpath) +AF_CHECK_PROG(RANLIB, ranlib, , $searchpath) +AF_CHECK_PROG(RPCGEN, rpcgen, , $searchpath) + +AF_CHECK_SSS_LIB(SSS_AUTOFS, libsss_autofs.so) +AC_SUBST(HAVE_SSS_AUTOFS) +AC_SUBST(sssldir) + +# +# Newer mounts have the -s (sloppy) option to ignore unknown options, +# good for portability +# +AC_ARG_ENABLE(sloppy-mount, +[ --enable-sloppy-mount enable the use of the -s option to mount],, + enable_sloppy_mount=auto) +if test x$enable_sloppy_mount = xauto; then + AF_SLOPPY_MOUNT() +fi +if test x$enable_sloppy_mount = xyes; then + AC_DEFINE(HAVE_SLOPPY_MOUNT, 1, [define if the mount command supports the -s option]) +fi + +# LDAP SASL auth needs libxml and Kerberos +AF_CHECK_LIBXML() +AF_CHECK_KRB5() + +AC_SEARCH_LIBS([versionsort],[]) +if test "$ac_cv_search_versionsort" = "no"; then + AC_DEFINE(WITHOUT_VERSIONSORT, 1, + [Define if your C library does not provide versionsort]) +fi + +# glibc < 2.17 needs librt for clock_gettime() +AC_CHECK_LIB(rt, clock_gettime, LIBCLOCK_GETTIME="-lrt") +AC_SUBST(LIBCLOCK_GETTIME) + +# +# glibc/libc 6 new libraries +# +AC_CHECK_LIB(nsl, yp_match, LIBNSL="-lnsl") +AC_SUBST(LIBNSL) + +AC_CHECK_LIB(resolv, res_query, LIBRESOLV="-lresolv") +AC_SUBST(LIBRESOLV) + +# +# Hesiod support? Expect that this may have a special directory... +# +AF_tmp_ldflags="$LDFLAGS" +LIBHESIOD='' +HAVE_HESIOD='' +AC_ARG_WITH(hesiod, +[ --with-hesiod=DIR enable Hesiod support (libs and includes in DIR)], + if test "$withval" = no + then + HAVE_HESIOD=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for Hesiod in normal directory path + else + : Search for Hesiod in specific directory + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBHESIOD="-L${withval}/lib" + HESIOD_FLAGS="-I${withval}/include" + fi +) + +if test -z "$HAVE_HESIOD" -o "$HAVE_HESIOD" != "0" +then + HAVE_HESIOD=0 + AF_CHECK_LIBHESIOD() + if test "$HAVE_HESIOD" == "1"; then + AC_DEFINE(WITH_HESIOD,1, + [Define if using Hesiod as a source of automount maps]) + fi +fi +AC_SUBST(HAVE_HESIOD) +AC_SUBST(LIBHESIOD) +AC_SUBST(HESIOD_FLAGS) +LDFLAGS="${AF_tmp_ldflags}" + +# NIS+ support? +HAVE_NISPLUS=0 +AC_CHECK_HEADER(rpcsvc/nis.h, HAVE_NISPLUS=1) +AC_SUBST(HAVE_NISPLUS) + +# YellowPages support? +HAVE_YPCLNT=0 +AC_CHECK_HEADER([rpcsvc/ypclnt.h], HAVE_YPCLNT=1) +AC_SUBST(HAVE_YPCLNT) +if test "$HAVE_YPCLNT" = "1"; then + AC_DEFINE(HAVE_YPCLNT, 1, + [Define if using YellowPages]) +fi + +# +# OpenLDAP support? Expect that this may have a special directory... +# +AF_tmp_ldflags="$LDFLAGS" +LIBLDAP='' +HAVE_LDAP='' +AC_ARG_WITH(openldap, +[ --with-openldap=DIR enable OpenLDAP map support (libs and includes in DIR)], + if test "$withval" = 'no'; then + HAVE_LDAP=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for LDAP in normal directory path + else + : Search for LDAP in specific directory + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBLDAP="-L${withval}/lib" + LDAP_FLAGS="-I${withval}/include" + fi +) +if test -z "$HAVE_LDAP" -o "$HAVE_LDAP" != "0"; then + HAVE_LDAP=0 + LDAP_FLAGS="$LDAP_FLAGS -DLDAP_DEPRECATED=1" + AC_CHECK_LIB(ldap, ldap_initialize, HAVE_LDAP=1 LIBLDAP="$LIBLDAP -lldap -llber -lresolv", , + -llber -lresolv $LIBS) + if test "$HAVE_LDAP" = "1"; then + AC_DEFINE(WITH_LDAP,1, + [Define if using LDAP as a source of automount maps]) + fi + AF_CHECK_FUNC_LDAP_CREATE_PAGE_CONTROL() + AF_CHECK_FUNC_LDAP_PARSE_PAGE_CONTROL() +fi + +AC_SUBST(LDAP_FLAGS) +AC_SUBST(HAVE_LDAP) +AC_SUBST(LIBLDAP) +LDFLAGS="${AF_tmp_ldflags}" + +# +# SASL support +# configure magic taken from: +# http://www.timof.qipc.org/autofs/autofs-4.1.4-ldap-20050930.patch +# + +AF_tmp_ldflags="$LDFLAGS" +LIBSASL='' +HAVE_SASL='' +AC_ARG_WITH(sasl, +[ --with-sasl=DIR enable SASL support for LDAP maps (libs and includes in DIR)], + if test "$withval" = 'no'; then + HAVE_SASL=0 # Disable + elif test -z "$withval" -o "$withval" = 'yes' + then + : Search for SASL in normal directory path + else + : Search for SASL in specific directory + HAVE_SASL=1 + LDFLAGS="$LDFLAGS -L${withval}/lib" + LIBSASL="-L${withval}/lib" + SASL_FLAGS="-I${withval}/include" + fi +) +if test -z "$HAVE_SASL" -o "$HAVE_SASL" != "0" -a "$HAVE_LIBXML" == "1" +then + HAVE_SASL=0 + AC_CHECK_LIB(sasl2, sasl_client_start, HAVE_SASL=1 LIBSASL="$LIBSASL -lsasl2", , -lsasl2 $LIBS) + if test "$HAVE_SASL" == "1"; then + AC_DEFINE(WITH_SASL,1, + [Define if using SASL authentication with the LDAP module]) + fi +fi + +AC_SUBST(XML_FLAGS) +AC_SUBST(XML_LIBS) +AC_SUBST(SASL_FLAGS) +AC_SUBST(HAVE_SASL) +AC_SUBST(LIBSASL) +AC_SUBST(KRB5_LIBS) +AC_SUBST(KRB5_FLAGS) +LDFLAGS="${AF_tmp_ldflags}" + +# +# Does gcc support building position independent executables? +# +AC_PROG_CC +cat > pietest.c < + * Copyright 2001-2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" +#if defined(LIBXML2_WORKAROUND) || defined(TIRPC_WORKAROUND) +#include +#ifdef WITH_LDAP +#include +#endif +#endif + +const char *program; /* Initialized with argv[0] */ +const char *version = VERSION_STRING; /* Program version */ +const char *libdir = AUTOFS_LIB_DIR; /* Location of library modules */ +const char *mapdir = AUTOFS_MAP_DIR; /* Location of mount maps */ +const char *confdir = AUTOFS_CONF_DIR; /* Location of autofs config file */ + +unsigned int nfs_mount_uses_string_options = 0; +static struct nfs_mount_vers vers, check = {1, 1, 1}; + +/* autofs fifo name prefix */ +const char *fifodir = AUTOFS_FIFO_DIR "/autofs.fifo"; + +const char *global_options; /* Global option, from command line */ + +static char *pid_file = NULL; /* File in which to keep pid */ +unsigned int global_selection_options; + +long global_negative_timeout = -1; +int do_force_unlink = 0; /* Forceably unlink mount tree at startup */ + +static int start_pipefd[2]; +static int st_stat = 1; +static int *pst_stat = &st_stat; +static pthread_t state_mach_thid; + +static sigset_t block_sigs; + +/* Pre-calculated kernel packet length */ +static size_t kpkt_len; + +/* Does kernel know about SOCK_CLOEXEC and friends */ +static int cloexec_works = 0; + +/* Attributes for creating detached and joinable threads */ +pthread_attr_t th_attr; +pthread_attr_t th_attr_detached; +size_t detached_thread_stack_size = PTHREAD_STACK_MIN * 144; + +struct master_readmap_cond mrc = { + PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, NULL, 0, 0, 0, 0}; + +struct startup_cond suc = { + PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, 0}; + +pthread_key_t key_thread_stdenv_vars; +pthread_key_t key_thread_attempt_id = (pthread_key_t) 0L; + +#define MAX_OPEN_FILES 10240 + +int aquire_flag_file(void); +void release_flag_file(void); +static int umount_all(struct autofs_point *ap, int force); + +extern struct master *master_list; + +/* simple string hash based on public domain sdbm library */ +static unsigned long sdbm_hash(const char *str, unsigned long seed) +{ + unsigned long hash = seed; + char c; + + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + return hash; +} + +void set_thread_mount_request_log_id(struct pending_args *mt) +{ + char attempt_id_comp[20]; + unsigned long *attempt_id; + int status; + + if (!defaults_get_use_mount_request_log_id()) + return; + + attempt_id = pthread_getspecific(key_thread_attempt_id); + if (attempt_id == NULL) { + attempt_id = (unsigned long *) calloc(1, sizeof(unsigned long)); + if (attempt_id == NULL) + fatal(ENOMEM); + snprintf(attempt_id_comp, 20, "%ld", mt->wait_queue_token); + *attempt_id = sdbm_hash(attempt_id_comp, 0); + snprintf(attempt_id_comp, 20, "%u", mt->pid); + *attempt_id = sdbm_hash(attempt_id_comp, *attempt_id); + *attempt_id = sdbm_hash(mt->name, *attempt_id); + status = pthread_setspecific(key_thread_attempt_id, attempt_id); + if (status != 0) + fatal(status); + } +} + +static int is_remote_fstype(unsigned int fs_type) +{ + int ret = 0; + switch (fs_type) { + case SMB_SUPER_MAGIC: + case CIFS_MAGIC_NUMBER: + case NCP_SUPER_MAGIC: + case NFS_SUPER_MAGIC: + ret = 1; + break; + }; + return ret; +} + +static int do_mkdir(const char *parent, const char *path, mode_t mode) +{ + int status; + mode_t mask; + struct stat st, root; + struct statfs fs; + + /* If path exists we're done */ + status = stat(path, &st); + if (status == 0) { + errno = EEXIST; + if (!S_ISDIR(st.st_mode)) + errno = ENOTDIR; + return 0; + } + + /* + * We don't want to create the path on a remote file system + * unless it's the root file system. + * An empty parent means it's the root directory and always ok. + */ + if (*parent) { + status = statfs(parent, &fs); + if (status == -1) + goto fail; + + if (is_remote_fstype(fs.f_type)) { + status = stat(parent, &st); + if (status == -1) + goto fail; + + status = stat("/", &root); + if (status == -1) + goto fail; + + if (st.st_dev != root.st_dev) + goto fail; + } + } + + mask = umask(0022); + status = mkdir(path, mode); + (void) umask(mask); + if (status == -1) + goto fail; + + return 1; +fail: + errno = EACCES; + return 0; +} + +int mkdir_path(const char *path, mode_t mode) +{ + char buf[PATH_MAX]; + char parent[PATH_MAX]; + const char *cp = path, *lcp = path; + char *bp = buf, *pp = parent; + + *parent = '\0'; + + do { + if (cp != path && (*cp == '/' || *cp == '\0')) { + memcpy(bp, lcp, cp - lcp); + bp += cp - lcp; + *bp = '\0'; + if (!do_mkdir(parent, buf, mode)) { + if (*cp != '\0') { + memcpy(pp, lcp, cp - lcp); + pp += cp - lcp; + *pp = '\0'; + lcp = cp; + continue; + } + return -1; + } + memcpy(pp, lcp, cp - lcp); + pp += cp - lcp; + *pp = '\0'; + lcp = cp; + } + } while (*cp++ != '\0'); + + return 0; +} + +/* Remove as much as possible of a path */ +int rmdir_path(struct autofs_point *ap, const char *path, dev_t dev) +{ + int len = strlen(path); + char buf[PATH_MAX]; + char *cp; + int first = 1; + struct stat st; + struct statfs fs; + + strcpy(buf, path); + cp = buf + len; + + do { + *cp = '\0'; + + /* + * Before removing anything, perform some sanity checks to + * ensure that we are looking at files in the automount + * file system. + */ + memset(&st, 0, sizeof(st)); + if (lstat(buf, &st) != 0) { + crit(ap->logopt, "lstat of %s failed", buf); + return -1; + } + + /* Termination condition removing full path within autofs fs */ + if (st.st_dev != dev) + return 0; + + if (statfs(buf, &fs) != 0) { + error(ap->logopt, "could not stat fs of %s", buf); + return -1; + } + + if (fs.f_type != (__SWORD_TYPE) AUTOFS_SUPER_MAGIC) { + crit(ap->logopt, "attempt to remove directory from a " + "non-autofs filesystem!"); + crit(ap->logopt, + "requester dev == %llu, \"%s\" owner dev == %llu", + dev, buf, st.st_dev); + return -1; + } + + /* + * Last element of path may be a symbolic link; all others + * are directories (and the last directory element is + * processed first, hence the variable name) + */ + if (rmdir(buf) == -1) { + if (first && errno == ENOTDIR) { + /* + * Ensure that we will only remove + * symbolic links. + */ + if (S_ISLNK(st.st_mode)) { + if (unlink(buf) == -1) + return -1; + } else { + crit(ap->logopt, + "file \"%s\" is neither a directory" + " nor a symbolic link. mode %d", + buf, st.st_mode); + return -1; + } + } + + /* + * If we fail to remove a directory for any reason, + * we need to return an error. + */ + return -1; + } + + first = 0; + } while ((cp = strrchr(buf, '/')) != NULL && cp != buf); + + return 0; +} + +/* Like ftw, except fn gets called twice: before a directory is + entered, and after. If the before call returns 0, the directory + isn't entered. */ +static int walk_tree(const char *base, int (*fn) (struct autofs_point *ap, + const char *file, + const struct stat * st, + int, void *), int incl, + struct autofs_point *ap, + void *arg) +{ + char buf[PATH_MAX + 1]; + struct stat st, *pst = &st; + int ret; + + if (!is_mounted(_PATH_MOUNTED, base, MNTS_REAL)) + ret = lstat(base, pst); + else { + pst = NULL; + ret = 0; + } + + if (ret != -1 && (fn) (ap, base, pst, 0, arg)) { + if (S_ISDIR(st.st_mode)) { + struct dirent **de; + int n; + + n = scandir(base, &de, 0, alphasort); + if (n < 0) + return -1; + + while (n--) { + if (strcmp(de[n]->d_name, ".") == 0 || + strcmp(de[n]->d_name, "..") == 0) { + free(de[n]); + continue; + } + + if (!cat_path(buf, sizeof(buf), base, de[n]->d_name)) { + do { + free(de[n]); + } while (n--); + free(de); + return -1; + } + + walk_tree(buf, fn, 1, ap, arg); + free(de[n]); + } + free(de); + } + if (incl) + (fn) (ap, base, pst, 1, arg); + } + return 0; +} + +static int rm_unwanted_fn(struct autofs_point *ap, + const char *file, const struct stat *st, + int when, void *arg) +{ + dev_t dev = *(dev_t *) arg; + char buf[MAX_ERR_BUF]; + struct stat newst; + + if (!st) + return 0; + + if (when == 0) { + if (st->st_dev != dev) + return 0; + return 1; + } + + if (lstat(file, &newst)) { + crit(ap->logopt, + "unable to stat file, possible race condition"); + return 0; + } + + if (newst.st_dev != dev) { + crit(ap->logopt, + "file %s has the wrong device, possible race condition", + file); + return 0; + } + + if (S_ISDIR(newst.st_mode)) { + debug(ap->logopt, "removing directory %s", file); + if (rmdir(file)) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, + "unable to remove directory %s: %s", file, estr); + return 0; + } + } else if (S_ISREG(newst.st_mode)) { + crit(ap->logopt, "attempting to remove files from a mounted " + "directory. file %s", file); + return 0; + } else if (S_ISLNK(newst.st_mode)) { + debug(ap->logopt, "removing symlink %s", file); + unlink(file); + } + return 1; +} + +void rm_unwanted(struct autofs_point *ap, const char *path, int incl) +{ + walk_tree(path, rm_unwanted_fn, incl, ap, &ap->dev); +} + +struct counter_args { + unsigned int count; + dev_t dev; +}; + +static int counter_fn(struct autofs_point *ap, const char *file, + const struct stat *st, int when, void *arg) +{ + struct counter_args *counter = (struct counter_args *) arg; + + if (!st || (S_ISLNK(st->st_mode) || (S_ISDIR(st->st_mode) + && st->st_dev != counter->dev))) { + counter->count++; + return 0; + } + + return 1; +} + +/* Count mounted filesystems and symlinks */ +int count_mounts(struct autofs_point *ap, const char *path, dev_t dev) +{ + struct counter_args counter; + + counter.count = 0; + counter.dev = dev; + + if (walk_tree(path, counter_fn, 1, ap, &counter) == -1) + return -1; + + return counter.count; +} + +static void check_rm_dirs(struct autofs_point *ap, const char *path, int incl) +{ + /* + * If we're a submount the kernel can't know we're trying to + * shutdown and so cannot block processes walking into the + * mount point directory. If this is the call to umount_multi() + * made during shutdown (incl == 0) we have to leave any mount + * point directories in place so we can recover if needed. The + * umount itself will clean these directories up for us + * automagically. + */ + if (!incl && ap->submount) + return; + + if ((!(ap->flags & MOUNT_FLAG_GHOST)) || + (ap->state == ST_SHUTDOWN_PENDING || + ap->state == ST_SHUTDOWN_FORCE || + ap->state == ST_SHUTDOWN)) + rm_unwanted(ap, path, incl); + else if ((ap->flags & MOUNT_FLAG_GHOST) && (ap->type == LKP_INDIRECT)) + rm_unwanted(ap, path, 0); +} + +/* Try to purge cache entries kept around due to existing mounts */ +static void update_map_cache(struct autofs_point *ap, const char *path) +{ + struct map_source *map; + struct mapent_cache *mc; + const char *key; + + if (ap->type == LKP_INDIRECT) + key = strrchr(path, '/') + 1; + else + key = path; + + map = ap->entry->maps; + while (map) { + struct mapent *me = NULL; + + /* Skip current, in-use cache */ + if (ap->entry->age <= map->age) { + map = map->next; + continue; + } + + mc = map->mc; + /* If the lock is busy try later */ + if (cache_try_writelock(mc)) { + me = cache_lookup_distinct(mc, key); + if (me && me->ioctlfd == -1) + cache_delete(mc, key); + cache_unlock(mc); + } + + map = map->next; + } + + return; +} + +static int umount_subtree_mounts(struct autofs_point *ap, const char *path, unsigned int is_autofs_fs) +{ + struct mapent_cache *mc; + struct mapent *me; + unsigned int is_mm_root = 0; + int left; + + me = lookup_source_mapent(ap, path, LKP_DISTINCT); + if (!me) { + char *ind_key; + + ind_key = strrchr(path, '/'); + if (ind_key) + ind_key++; + + me = lookup_source_mapent(ap, ind_key, LKP_NORMAL); + } + + if (me) { + mc = me->mc; + is_mm_root = (me->multi == me); + } + + left = 0; + + if (me && me->multi) { + char root[PATH_MAX]; + char *base; + int cur_state; + + pthread_cleanup_push(cache_lock_cleanup, mc); + + if (!strchr(me->multi->key, '/')) + /* Indirect multi-mount root */ + /* sprintf okay - if it's mounted, it's + * PATH_MAX or less bytes */ + sprintf(root, "%s/%s", ap->path, me->multi->key); + else + strcpy(root, me->multi->key); + + if (is_mm_root) + base = NULL; + else + base = me->key + strlen(root); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + /* Lock the closest parent nesting point for umount */ + cache_multi_writelock(me->parent); + if (umount_multi_triggers(ap, me, root, base)) { + warn(ap->logopt, + "some offset mounts still present under %s", path); + left++; + } + cache_multi_unlock(me->parent); + if (ap->entry->maps && + (ap->entry->maps->flags & MAP_FLAG_FORMAT_AMD)) + cache_pop_mapent(me); + pthread_setcancelstate(cur_state, NULL); + pthread_cleanup_pop(0); + } + + if (me) + cache_unlock(mc); + + if (left || is_autofs_fs) + return left; + + /* + * If this is the root of a multi-mount we've had to umount + * it already to ensure it's ok to remove any offset triggers. + */ + if (!is_mm_root && is_mounted(_PATH_MOUNTED, path, MNTS_REAL)) { + struct amd_entry *entry; + debug(ap->logopt, "unmounting dir = %s", path); + if (umount_ent(ap, path) && + is_mounted(_PATH_MOUNTED, path, MNTS_REAL)) { + warn(ap->logopt, "could not umount dir %s", path); + left++; + goto done; + } + + /* Check for an external mount and umount if possible */ + mounts_mutex_lock(ap); + entry = __master_find_amdmount(ap, path); + if (!entry) { + mounts_mutex_unlock(ap); + goto done; + } + list_del(&entry->entries); + mounts_mutex_unlock(ap); + if (ext_mount_remove(&entry->ext_mount, entry->fs)) { + if (umount_ent(ap, entry->fs)) + debug(ap->logopt, + "failed to umount external mount %s", + entry->fs); + else + debug(ap->logopt, + "umounted external mount %s", + entry->fs); + } + free_amd_entry(entry); + } +done: + return left; +} + +/* umount all filesystems mounted under path. If incl is true, then + it also tries to umount path itself */ +int umount_multi(struct autofs_point *ap, const char *path, int incl) +{ + int is_autofs_fs; + struct stat st; + int left; + + debug(ap->logopt, "path %s incl %d", path, incl); + + /* If path is a mount it can't be a symlink */ + if (is_mounted(_PATH_MOUNTED, path, MNTS_ALL)) + goto real_mount; + + if (lstat(path, &st)) { + warn(ap->logopt, + "failed to stat directory or symlink %s", path); + return 1; + } + + /* if this is a symlink we can handle it now */ + if (S_ISLNK(st.st_mode)) { + struct amd_entry *entry; + if (st.st_dev != ap->dev) { + crit(ap->logopt, + "symlink %s has the wrong device, " + "possible race condition", path); + return 1; + } + debug(ap->logopt, "removing symlink %s", path); + if (unlink(path)) { + error(ap->logopt, + "failed to remove symlink %s", path); + return 1; + } + /* Check for an external mount and attempt umount if needed */ + mounts_mutex_lock(ap); + entry = __master_find_amdmount(ap, path); + if (!entry) { + mounts_mutex_unlock(ap); + return 0; + } + list_del(&entry->entries); + mounts_mutex_unlock(ap); + if (ext_mount_remove(&entry->ext_mount, entry->fs)) { + if (umount_ent(ap, entry->fs)) + debug(ap->logopt, + "failed to umount external mount %s", + entry->fs); + else + debug(ap->logopt, + "umounted external mount %s", + entry->fs); + } + free_amd_entry(entry); + return 0; + } + +real_mount: + is_autofs_fs = 0; + if (master_find_submount(ap, path)) + is_autofs_fs = 1; + + left = 0; + + /* + * If we are a submount we need to umount any offsets our + * parent may have mounted over top of us. + */ + if (ap->submount) + left += umount_subtree_mounts(ap->parent, path, 1); + + left += umount_subtree_mounts(ap, path, is_autofs_fs); + + /* Delete detritus like unwanted mountpoints and symlinks */ + if (left == 0 && + ap->state != ST_READMAP && + !count_mounts(ap, path, ap->dev)) { + update_map_cache(ap, path); + check_rm_dirs(ap, path, incl); + } + + return left; +} + +static int umount_all(struct autofs_point *ap, int force) +{ + int left; + + left = umount_multi(ap, ap->path, 0); + if (force && left) + warn(ap->logopt, "could not unmount %d dirs under %s", + left, ap->path); + + return left; +} + +int umount_autofs(struct autofs_point *ap, const char *root, int force) +{ + int ret = 0; + + if (ap->state == ST_INIT) + return -1; + + /* + * Since lookup.c is lazy about closing lookup modules + * to prevent unneeded opens, we need to clean them up + * before umount. + */ + lookup_close_lookup(ap); + + if (ap->type == LKP_INDIRECT) { + if (umount_all(ap, force) && !force) + return -1; + ret = umount_autofs_indirect(ap, root); + } else + ret = umount_autofs_direct(ap); + + return ret; +} + +static size_t get_kpkt_len(void) +{ + size_t pkt_len = sizeof(struct autofs_v5_packet); + struct utsname un; + int kern_vers; + + kern_vers = linux_version_code(); + if (kern_vers >= KERNEL_VERSION(3, 3, 0)) + return pkt_len; + + uname(&un); + + if (pkt_len % 8) { + if (strcmp(un.machine, "alpha") == 0 || + strcmp(un.machine, "ia64") == 0 || + strcmp(un.machine, "x86_64") == 0 || + strcmp(un.machine, "parisc64") == 0 || + strcmp(un.machine, "ppc64") == 0) + pkt_len += 4; + + } + + return pkt_len; +} + +static int fullread(int fd, void *ptr, size_t len) +{ + char *buf = (char *) ptr; + + while (len > 0) { + ssize_t r = read(fd, buf, len); + + if (r == -1) { + if (errno == EINTR) + continue; + break; + } + + buf += r; + len -= r; + } + + return len; +} + +static char *automount_path_to_fifo(unsigned logopt, const char *path) +{ + char *fifo_name, *p; + int name_len = strlen(path) + strlen(fifodir) + 1; + int ret; + + fifo_name = malloc(name_len); + if (!fifo_name) + return NULL; + ret = snprintf(fifo_name, name_len, "%s%s", fifodir, path); + if (ret >= name_len) { + info(logopt, + "fifo path for \"%s\" truncated to \"%s\". This may " + "lead to --set-log-priority commands being sent to the " + "wrong automount daemon.", path, fifo_name); + } + + /* + * An automount path can be made up of subdirectories. So, to + * create the fifo name, we will just replace instances of '/' with + * '-'. + */ + p = fifo_name + strlen(fifodir); + while (*p != '\0') { + if (*p == '/') + *p = '-'; + p++; + } + + debug(logopt, "fifo name %s",fifo_name); + + return fifo_name; +} + +static int create_logpri_fifo(struct autofs_point *ap) +{ + int ret = -1; + int fd; + char *fifo_name; + char buf[MAX_ERR_BUF]; + + fifo_name = automount_path_to_fifo(ap->logopt, ap->path); + if (!fifo_name) { + crit(ap->logopt, "Failed to allocate memory!"); + goto out_free; /* free(NULL) is okay */ + } + + ret = unlink(fifo_name); + if (ret != 0 && errno != ENOENT) { + crit(ap->logopt, + "Failed to unlink FIFO. Is the automount daemon " + "already running?"); + goto out_free; + } + + ret = mkfifo(fifo_name, S_IRUSR|S_IWUSR); + if (ret != 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + crit(ap->logopt, + "mkfifo for %s failed: %s", fifo_name, estr); + goto out_free; + } + + fd = open_fd(fifo_name, O_RDWR|O_NONBLOCK); + if (fd < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + crit(ap->logopt, + "Failed to open %s: %s", fifo_name, estr); + unlink(fifo_name); + ret = -1; + goto out_free; + } + + ap->logpri_fifo = fd; + +out_free: + free(fifo_name); + return ret; +} + +int destroy_logpri_fifo(struct autofs_point *ap) +{ + int ret = -1; + int fd = ap->logpri_fifo; + char *fifo_name; + char buf[MAX_ERR_BUF]; + + if (fd == -1) + return 0; + + fifo_name = automount_path_to_fifo(ap->logopt, ap->path); + if (!fifo_name) { + crit(ap->logopt, "Failed to allocate memory!"); + goto out_free; /* free(NULL) is okay */ + } + + ap->logpri_fifo = -1; + + ret = close(fd); + if (ret != 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, + "close for fifo %s: %s", fifo_name, estr); + } + + ret = unlink(fifo_name); + if (ret != 0) { + warn(ap->logopt, + "Failed to unlink FIFO. Was the fifo created OK?"); + } + +out_free: + free(fifo_name); + return ret; +} + +static void handle_fifo_message(struct autofs_point *ap, int fd) +{ + int ret; + char buffer[PIPE_BUF]; + char *end; + long pri; + char buf[MAX_ERR_BUF]; + + memset(buffer, 0, sizeof(buffer)); + ret = read(fd, &buffer, sizeof(buffer)); + if (ret < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, "read on fifo returned error: %s", estr); + return; + } + + if (ret != 2) { + debug(ap->logopt, "expected 2 bytes, received %d.", ret); + return; + } + + errno = 0; + pri = strtol(buffer, &end, 10); + if ((pri == LONG_MIN || pri == LONG_MAX) && errno == ERANGE) { + debug(ap->logopt, "strtol reported an %s. Failed to set " + "log priority.", pri == LONG_MIN ? "underflow" : "overflow"); + return; + } + if ((pri == 0 && errno == EINVAL) || end == buffer) { + debug(ap->logopt, "priority is expected to be an integer " + "in the range 0-7 inclusive."); + return; + } + + if (pri > LOG_DEBUG || pri < LOG_EMERG) { + debug(ap->logopt, "invalid log priority (%ld) received " + "on fifo", pri); + return; + } + + /* + * OK, the message passed all of the sanity checks. The + * automounter actually only supports three log priorities. + * Everything is logged at log level debug, deamon messages + * and everything except debug messages are logged with the + * verbose setting and only error and critical messages are + * logged when debugging isn't enabled. + */ + if (pri >= LOG_WARNING) { + if (pri == LOG_DEBUG) { + set_log_debug_ap(ap); + info(ap->logopt, "Debug logging set for %s", ap->path); + } else { + set_log_verbose_ap(ap); + info(ap->logopt, "Verbose logging set for %s", ap->path); + } + } else { + if (ap->logopt & LOGOPT_ANY) + info(ap->logopt, "Basic logging set for %s", ap->path); + set_log_norm_ap(ap); + } +} + +static int set_log_priority(const char *path, int priority) +{ + int fd; + char *fifo_name; + char buf[2]; + + if (priority > LOG_DEBUG || priority < LOG_EMERG) { + fprintf(stderr, "Log priority %d is invalid.\n", priority); + fprintf(stderr, "Please specify a number in the range 0-7.\n"); + return -1; + } + + /* + * This is an ascii based protocol, so we want the string + * representation of the integer log priority. + */ + snprintf(buf, sizeof(buf), "%d", priority); + + fifo_name = automount_path_to_fifo(LOGOPT_NONE, path); + if (!fifo_name) { + fprintf(stderr, "%s: Failed to allocate memory!\n", + __FUNCTION__); + return -1; + } + + /* + * Specify O_NONBLOCK so that the open will fail if there is no + * daemon reading from the other side of the FIFO. + */ + fd = open(fifo_name, O_WRONLY|O_NONBLOCK); + if (fd < 0) { + fprintf(stderr, "%s: open of %s failed with %s\n", + __FUNCTION__, fifo_name, strerror(errno)); + fprintf(stderr, "%s: perhaps the fifo wasn't setup," + " please check your log for more information\n", __FUNCTION__); + free(fifo_name); + return -1; + } + + if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { + fprintf(stderr, "Failed to change logging priority. "); + fprintf(stderr, "write to fifo failed: %s.\n", + strerror(errno)); + close(fd); + free(fifo_name); + return -1; + } + close(fd); + free(fifo_name); + fprintf(stdout, "Successfully set log priority for %s.\n", path); + + return 0; +} + +static int get_pkt(struct autofs_point *ap, union autofs_v5_packet_union *pkt) +{ + struct pollfd fds[3]; + int pollfds = 3; + char buf[MAX_ERR_BUF]; + size_t read; + char *estr; + + fds[0].fd = ap->pipefd; + fds[0].events = POLLIN; + fds[1].fd = ap->state_pipe[0]; + fds[1].events = POLLIN; + fds[2].fd = ap->logpri_fifo; + fds[2].events = POLLIN; + if (fds[2].fd == -1) + pollfds--; + + for (;;) { + if (poll(fds, pollfds, -1) == -1) { + if (errno == EINTR) + continue; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("poll failed: %s", estr); + return -1; + } + + if (fds[1].revents & POLLIN) { + enum states next_state; + size_t read_size = sizeof(next_state); + int state_pipe; + + next_state = ST_INVAL; + + st_mutex_lock(); + + state_pipe = ap->state_pipe[0]; + + read = fullread(state_pipe, &next_state, read_size); + if (read) { + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + "read error on state pipe, " + "read %u, error %s", + read, estr); + st_mutex_unlock(); + continue; + } + + st_mutex_unlock(); + + if (next_state == ST_SHUTDOWN) + return -1; + } + + if (fds[0].revents & POLLIN) { + read = fullread(ap->pipefd, pkt, kpkt_len); + if (read) { + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + "read error on request pipe, " + "read %u, expected %u error %s", + read, kpkt_len, estr); + } + return read; + } + + if (fds[2].fd != -1 && fds[2].revents & POLLIN) { + debug(ap->logopt, "message pending on control fifo."); + handle_fifo_message(ap, fds[2].fd); + } + } +} + +int do_expire(struct autofs_point *ap, const char *name, int namelen) +{ + char buf[PATH_MAX]; + int len, ret; + + if (*name != '/') { + len = ncat_path(buf, sizeof(buf), ap->path, name, namelen); + } else { + len = snprintf(buf, PATH_MAX, "%s", name); + if (len >= PATH_MAX) + len = 0; + } + + if (!len) { + crit(ap->logopt, "path too long for buffer"); + return 1; + } + + info(ap->logopt, "expiring path %s", buf); + + pthread_cleanup_push(master_source_lock_cleanup, ap->entry); + master_source_readlock(ap->entry); + ret = umount_multi(ap, buf, 1); + if (ret == 0) + info(ap->logopt, "expired %s", buf); + else + warn(ap->logopt, "couldn't complete expire of %s", buf); + pthread_cleanup_pop(1); + + return ret; +} + +static int autofs_init_ap(struct autofs_point *ap) +{ + int pipefd[2]; + + if ((ap->state != ST_INIT)) { + /* This can happen if an autofs process is already running*/ + error(ap->logopt, "bad state %d", ap->state); + return -1; + } + + ap->pipefd = ap->kpipefd = ap->ioctlfd = -1; + + /* Pipe for kernel communications */ + if (open_pipe(pipefd) < 0) { + crit(ap->logopt, + "failed to create commumication pipe for autofs path %s", + ap->path); + return -1; + } + + ap->pipefd = pipefd[0]; + ap->kpipefd = pipefd[1]; + + /* Pipe state changes from signal handler to main loop */ + if (open_pipe(ap->state_pipe) < 0) { + crit(ap->logopt, + "failed create state pipe for autofs path %s", ap->path); + close(ap->pipefd); + close(ap->kpipefd); /* Close kernel pipe end */ + return -1; + } + + if (create_logpri_fifo(ap) < 0) { + logmsg("could not create FIFO for path %s\n", ap->path); + logmsg("dynamic log level changes not available for %s", ap->path); + } + + return 0; +} + +static int mount_autofs(struct autofs_point *ap, const char *root) +{ + int status = 0; + + if (autofs_init_ap(ap) != 0) + return -1; + + if (ap->type == LKP_DIRECT) + status = mount_autofs_direct(ap); + else + status = mount_autofs_indirect(ap, root); + + if (status < 0) + return -1; + + st_add_task(ap, ST_READY); + + return 0; +} + +static int handle_packet(struct autofs_point *ap) +{ + union autofs_v5_packet_union pkt; + + if (get_pkt(ap, &pkt)) + return -1; + + debug(ap->logopt, "type = %d", pkt.hdr.type); + + switch (pkt.hdr.type) { + case autofs_ptype_missing_indirect: + return handle_packet_missing_indirect(ap, &pkt.v5_packet); + + case autofs_ptype_missing_direct: + return handle_packet_missing_direct(ap, &pkt.v5_packet); + + case autofs_ptype_expire_indirect: + return handle_packet_expire_indirect(ap, &pkt.v5_packet); + + case autofs_ptype_expire_direct: + return handle_packet_expire_direct(ap, &pkt.v5_packet); + } + error(ap->logopt, "unknown packet type %d", pkt.hdr.type); + return -1; +} + +static void become_daemon(unsigned foreground, unsigned daemon_check) +{ + FILE *pidfp; + char buf[MAX_ERR_BUF]; + int res; + pid_t pid; + + /* Don't BUSY any directories unnecessarily */ + if (chdir("/")) { + fprintf(stderr, "%s: failed change working directory.\n", + program); + exit(0); + } + + if (open_pipe(start_pipefd) < 0) { + fprintf(stderr, "%s: failed to create start_pipefd.\n", + program); + exit(0); + } + + /* Detach from foreground process */ + if (foreground) { + if (daemon_check && !aquire_flag_file()) { + fprintf(stderr, "%s: program is already running.\n", + program); + exit(1); + } + log_to_stderr(); + } else { + pid = fork(); + if (pid > 0) { + close(start_pipefd[1]); + res = read(start_pipefd[0], pst_stat, sizeof(*pst_stat)); + if (res < 0) + exit(1); + exit(*pst_stat); + } else if (pid < 0) { + fprintf(stderr, "%s: Could not detach process\n", + program); + exit(1); + } + close(start_pipefd[0]); + + if (daemon_check && !aquire_flag_file()) { + fprintf(stderr, "%s: program is already running.\n", + program); + /* Return success if already running */ + st_stat = 0; + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + if (res < 0) + exit(1); + close(start_pipefd[1]); + exit(*pst_stat); + } + + /* + * Make our own process group for "magic" reason: processes that share + * our pgrp see the raw filesystem behind the magic. + */ + if (setsid() == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + fprintf(stderr, "setsid: %s", estr); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + exit(*pst_stat); + } + log_to_syslog(); + } + + /* Write pid file if requested */ + if (pid_file) { + if ((pidfp = fopen(pid_file, "wt"))) { + fprintf(pidfp, "%lu\n", (unsigned long) getpid()); + fclose(pidfp); + } else { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("failed to write pid file %s: %s", + pid_file, estr); + pid_file = NULL; + } + } +} + +static unsigned long getnumopt(char *str, char option) +{ + unsigned long val; + char *end; + + val = strtoul(str, &end, 0); + if (!*str || *end) { + fprintf(stderr, + "%s: option -%c requires a numeric argument, got %s\n", + program, option, str); + exit(1); + } + return val; +} + +static void do_master_cleanup_unlock(void *arg) +{ + int status; + + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + + return; +} + +static void *do_notify_state(void *arg) +{ + struct master *master; + int sig; + int status; + + sig = *(int *) arg; + + status = pthread_mutex_lock(&mrc.mutex); + if (status) + fatal(status); + + master = mrc.master; + + debug(master->logopt, "signal %d", sig); + + mrc.signaled = 1; + status = pthread_cond_signal(&mrc.cond); + if (status) { + error(master->logopt, + "failed to signal state notify condition"); + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + pthread_exit(NULL); + } + + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + + master_notify_state_change(master, sig); + + return NULL; +} + +static pthread_t do_signals(struct master *master, int sig) +{ + pthread_t thid; + int r_sig = sig; + int status; + + status = pthread_mutex_lock(&mrc.mutex); + if (status) + fatal(status); + + status = pthread_create(&thid, &th_attr_detached, do_notify_state, &r_sig); + if (status) { + error(master->logopt, + "mount state notify thread create failed"); + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + return 0; + } + + mrc.thid = thid; + mrc.master = master; + + pthread_cleanup_push(do_master_cleanup_unlock, NULL); + + mrc.signaled = 0; + while (!mrc.signaled) { + status = pthread_cond_wait(&mrc.cond, &mrc.mutex); + if (status) + fatal(status); + } + + pthread_cleanup_pop(1); + + return thid; +} + +static void *do_read_master(void *arg) +{ + struct master *master; + unsigned int logopt; + time_t age; + int readall = 1; + int status; + + status = pthread_mutex_lock(&mrc.mutex); + if (status) + fatal(status); + + master = mrc.master; + age = mrc.age; + logopt = master->logopt; + + mrc.signaled = 1; + status = pthread_cond_signal(&mrc.cond); + if (status) { + error(logopt, + "failed to signal master read map condition"); + master->reading = 0; + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + pthread_exit(NULL); + } + + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + + defaults_read_config(1); + + info(logopt, "re-reading master map %s", master->name); + + status = master_read_master(master, age, readall); + + master->reading = 0; + + return NULL; +} + +static int do_hup_signal(struct master *master) +{ + unsigned int logopt = master->logopt; + time_t age = monotonic_time(NULL); + pthread_t thid; + int status; + + status = pthread_mutex_lock(&mrc.mutex); + if (status) + fatal(status); + + nfs_mount_uses_string_options = check_nfs_mount_version(&vers, &check); + + master_mutex_lock(); + /* Already doing a map read or shutdown or no mounts */ + if (master->reading) { + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + master_mutex_unlock(); + return 1; + } + master->reading = 1; + master_mutex_unlock(); + + status = pthread_create(&thid, &th_attr_detached, do_read_master, NULL); + if (status) { + error(logopt, + "master read map thread create failed"); + master->reading = 0; + status = pthread_mutex_unlock(&mrc.mutex); + if (status) + fatal(status); + return 0; + } + + mrc.thid = thid; + mrc.master = master; + mrc.age = age; + + pthread_cleanup_push(do_master_cleanup_unlock, NULL); + + mrc.signaled = 0; + while (!mrc.signaled) { + status = pthread_cond_wait(&mrc.cond, &mrc.mutex); + if (status) + fatal(status); + } + + pthread_cleanup_pop(1); + + return 1; +} + +/* Deal with all the signal-driven events in the state machine */ +static void *statemachine(void *arg) +{ + sigset_t signalset; + int sig; + + memcpy(&signalset, &block_sigs, sizeof(signalset)); + sigdelset(&signalset, SIGCHLD); + sigdelset(&signalset, SIGCONT); + + while (1) { + sigwait(&signalset, &sig); + + switch (sig) { + case SIGTERM: + case SIGINT: + case SIGUSR2: + master_mutex_lock(); + if (list_empty(&master_list->completed)) { + if (list_empty(&master_list->mounts)) { + master_mutex_unlock(); + return NULL; + } + } else { + if (master_done(master_list)) { + master_mutex_unlock(); + return NULL; + } + master_mutex_unlock(); + break; + } + master_mutex_unlock(); + + case SIGUSR1: + do_signals(master_list, sig); + break; + + case SIGHUP: + do_hup_signal(master_list); + break; + + default: + logerr("got unexpected signal %d!", sig); + continue; + } + } +} + +static void return_start_status(void *arg) +{ + struct startup_cond *sc; + int status; + + sc = (struct startup_cond *) arg; + + sc->done = 1; + + /* + * Startup condition mutex must be locked during + * the startup process. + */ + status = pthread_cond_signal(&sc->cond); + if (status) + fatal(status); + + status = pthread_mutex_unlock(&sc->mutex); + if (status) + fatal(status); +} + +int handle_mounts_startup_cond_init(struct startup_cond *suc) +{ + int status; + + status = pthread_mutex_init(&suc->mutex, NULL); + if (status) + return status; + + status = pthread_cond_init(&suc->cond, NULL); + if (status) { + status = pthread_mutex_destroy(&suc->mutex); + if (status) + fatal(status); + return status; + } + + status = pthread_mutex_lock(&suc->mutex); + if (status) { + status = pthread_mutex_destroy(&suc->mutex); + if (status) + fatal(status); + status = pthread_cond_destroy(&suc->cond); + if (status) + fatal(status); + } + + return 0; +} + +void handle_mounts_startup_cond_destroy(void *arg) +{ + struct startup_cond *suc = (struct startup_cond *) arg; + int status; + + status = pthread_mutex_unlock(&suc->mutex); + if (status) + fatal(status); + + status = pthread_mutex_destroy(&suc->mutex); + if (status) + fatal(status); + + status = pthread_cond_destroy(&suc->cond); + if (status) + fatal(status); + + return; +} + +static void handle_mounts_cleanup(void *arg) +{ + struct autofs_point *ap; + char path[PATH_MAX + 1]; + char buf[MAX_ERR_BUF]; + unsigned int clean = 0, submount, logopt; + unsigned int pending = 0; + + ap = (struct autofs_point *) arg; + + logopt = ap->logopt; + submount = ap->submount; + + strcpy(path, ap->path); + if (!submount && strcmp(ap->path, "/-") && + ap->flags & MOUNT_FLAG_DIR_CREATED) + clean = 1; + + if (submount) { + struct amd_entry *am; + /* We are finishing up */ + ap->parent->submnt_count--; + list_del_init(&ap->mounts); + am = __master_find_amdmount(ap->parent, ap->path); + if (am) { + list_del_init(&am->entries); + free_amd_entry(am); + } + } + + /* Don't signal the handler if we have already done so */ + if (!list_empty(&master_list->completed)) + pending = 1; + master_remove_mapent(ap->entry); + master_source_unlock(ap->entry); + + destroy_logpri_fifo(ap); + + /* + * Submounts are detached threads and don't belong to the + * master map entry list so we need to free their resources + * here. + */ + if (submount) { + mounts_mutex_unlock(ap->parent); + master_source_unlock(ap->parent->entry); + master_free_mapent_sources(ap->entry, 1); + master_free_mapent(ap->entry); + } + + if (clean) { + if (rmdir(path) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(logopt, "failed to remove dir %s: %s", + path, estr); + } + } + + info(logopt, "shut down path %s", path); + + /* + * If we are not a submount send a signal to the signal handler + * so it can join with any completed handle_mounts() threads and + * perform final cleanup. + */ + if (!submount && !pending) + pthread_kill(state_mach_thid, SIGTERM); + + master_mutex_unlock(); + + return; +} + +static int submount_source_writelock_nested(struct autofs_point *ap) +{ + struct autofs_point *parent = ap->parent; + int status; + + status = pthread_rwlock_trywrlock(&parent->entry->source_lock); + if (status) + goto done; + + mounts_mutex_lock(parent); + + status = pthread_rwlock_trywrlock(&ap->entry->source_lock); + if (status) { + mounts_mutex_unlock(parent); + master_source_unlock(parent->entry); + } + +done: + if (status && status != EBUSY) { + logmsg("submount nested master_mapent source write lock failed"); + fatal(status); + } + + return status; +} + +static void submount_source_unlock_nested(struct autofs_point *ap) +{ + struct autofs_point *parent = ap->parent; + + master_source_unlock(ap->entry); + mounts_mutex_unlock(parent); + master_source_unlock(parent->entry); +} + +int handle_mounts_exit(struct autofs_point *ap) +{ + int ret, cur_state; + + /* + * If we're a submount we need to ensure our parent + * doesn't try to mount us again until our shutdown + * is complete and that any outstanding mounts are + * completed before we try to shutdown. + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + + master_mutex_lock(); + + if (!ap->submount) + master_source_writelock(ap->entry); + else { + /* + * If a mount request arrives before the locks are + * aquired just return to ready state. + */ + ret = submount_source_writelock_nested(ap); + if (ret) { + warn(ap->logopt, + "can't shutdown submount: mount in progress"); + /* Return to ST_READY is done immediately */ + st_add_task(ap, ST_READY); + master_mutex_unlock(); + pthread_setcancelstate(cur_state, NULL); + return 0; + } + } + + if (ap->state != ST_SHUTDOWN) { + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + /* Return to ST_READY is done immediately */ + st_add_task(ap, ST_READY); + if (ap->submount) + submount_source_unlock_nested(ap); + else + master_source_unlock(ap->entry); + master_mutex_unlock(); + + pthread_setcancelstate(cur_state, NULL); + return 0; + } + + alarm_delete(ap); + st_remove_tasks(ap); + st_wait_task(ap, ST_ANY, 0); + + /* + * For a direct mount map all mounts have already gone + * by the time we get here and since we only ever + * umount direct mounts at shutdown there is no need + * to check for possible recovery. + */ + if (ap->type == LKP_DIRECT) { + umount_autofs(ap, NULL, 1); + handle_mounts_cleanup(ap); + return 1; + } + + /* + * If umount_autofs returns non-zero it wasn't able + * to complete the umount and has left the mount intact + * so we can continue. This can happen if a lookup + * occurs while we're trying to umount. + */ + ret = umount_autofs(ap, NULL, 1); + if (!ret) { + set_indirect_mount_tree_catatonic(ap); + handle_mounts_cleanup(ap); + return 1; + } + + /* Failed shutdown returns to ready */ + warn(ap->logopt, "can't shutdown: filesystem %s still busy", ap->path); + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + /* Return to ST_READY is done immediately */ + st_add_task(ap, ST_READY); + if (ap->submount) + submount_source_unlock_nested(ap); + else + master_source_unlock(ap->entry); + master_mutex_unlock(); + + pthread_setcancelstate(cur_state, NULL); + + return 0; +} + +void *handle_mounts(void *arg) +{ + struct startup_cond *suc; + struct autofs_point *ap; + int cancel_state, status = 0; + char *root; + + suc = (struct startup_cond *) arg; + + ap = suc->ap; + root = strdup(suc->root); + + pthread_cleanup_push(return_start_status, suc); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); + + status = pthread_mutex_lock(&suc->mutex); + if (status) { + logerr("failed to lock startup condition mutex!"); + fatal(status); + } + + if (!root) { + crit(ap->logopt, "failed to alloc string root"); + suc->status = 1; + pthread_setcancelstate(cancel_state, NULL); + pthread_exit(NULL); + } + + if (mount_autofs(ap, root) < 0) { + crit(ap->logopt, "mount of %s failed!", ap->path); + suc->status = 1; + umount_autofs(ap, root, 1); + free(root); + pthread_setcancelstate(cancel_state, NULL); + pthread_exit(NULL); + } + + free(root); + + if (ap->flags & MOUNT_FLAG_NOBIND) + info(ap->logopt, "bind mounts disabled"); + + if (ap->flags & MOUNT_FLAG_GHOST && ap->type != LKP_DIRECT) + info(ap->logopt, "ghosting enabled"); + + suc->status = 0; + pthread_cleanup_pop(1); + + /* We often start several automounters at the same time. Add some + randomness so we don't all expire at the same time. */ + if (!ap->submount && ap->exp_runfreq) + alarm_add(ap, ap->exp_runfreq + rand() % ap->exp_runfreq); + + pthread_setcancelstate(cancel_state, NULL); + + while (1) { + if (handle_packet(ap)) { + if (handle_mounts_exit(ap)) + break; + } + + /* If we get here a packet has been received and handled + * and the autofs mount point has not been shutdown. But + * if the autofs mount point has been set to ST_SHUTDOWN + * we should attempt to perform the shutdown cleanup and + * exit if successful. + */ + if (ap->state == ST_SHUTDOWN) { + if (handle_mounts_exit(ap)) + break; + } + } + + return NULL; +} + +static void key_thread_stdenv_vars_destroy(void *arg) +{ + struct thread_stdenv_vars *tsv; + + tsv = (struct thread_stdenv_vars *) arg; + if (tsv->user) + free(tsv->user); + if (tsv->group) + free(tsv->group); + if (tsv->home) + free(tsv->home); + free(tsv); + return; +} + +static void usage(void) +{ + fprintf(stderr, + "Usage: %s [options] [master_map_name]\n" + " -h --help this text\n" + " -p --pid-file f write process id to file f\n" + " -t --timeout n auto-unmount in n seconds (0-disable)\n" + " -v --verbose be verbose\n" + " -d --debug log debuging info\n" + " -D --define define global macro variable\n" + " -f --foreground do not fork into background\n" + " -r --random-multimount-selection\n" + " use ramdom replicated server selection\n" + " -m --dumpmaps [ ]\n" + " dump automounter maps and exit\n" + " -n --negative-timeout n\n" + " set the timeout for failed key lookups.\n" + " -O --global-options\n" + " specify global mount options\n" + " -l --set-log-priority priority path [path,...]\n" + " set daemon log verbosity\n" + " -C --dont-check-daemon\n" + " don't check if daemon is already running\n" + " -F --force forceably clean up known automounts at start\n" + " -V --version print version, build config and exit\n" + , program); +} + +static void show_build_info(void) +{ + int count = 0; + + printf("\nLinux automount version %s\n", version); + + printf("\nDirectories:\n"); + printf("\tconfig dir:\t%s\n", confdir); + printf("\tmaps dir:\t%s\n", mapdir); + printf("\tmodules dir:\t%s\n", libdir); + + printf("\nCompile options:\n "); + +#ifndef ENABLE_MOUNT_LOCKING + printf("DISABLE_MOUNT_LOCKING "); + count = 22; +#endif + +#ifdef ENABLE_FORCED_SHUTDOWN + printf("ENABLE_FORCED_SHUTDOWN "); + count = count + 23; +#endif + +#ifdef ENABLE_IGNORE_BUSY_MOUNTS + printf("ENABLE_IGNORE_BUSY_MOUNTS "); + count = count + 26; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + + +#ifdef WITH_HESIOD + printf("WITH_HESIOD "); + count = count + 12; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + +#ifdef WITH_LDAP + printf("WITH_LDAP "); + count = count + 10; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + +#ifdef WITH_SASL + printf("WITH_SASL "); + count = count + 10; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + +#ifdef WITH_DMALLOC + printf("WITH_DMALLOC "); + count = count + 13; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + +#ifdef LIBXML2_WORKAROUND + printf("LIBXML2_WORKAROUND "); + count = count + 19; + + if (count > 60) { + printf("\n "); + count = 0; + } +#endif + +#ifdef WITH_LIBTIRPC + printf("WITH_LIBTIRPC "); + count = count + 14; +#endif + + printf("\n\n"); + + return; +} + +typedef struct _code { + char *c_name; + int c_val; +} CODE; + +CODE prioritynames[] = { + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "err", LOG_ERR }, + { "error", LOG_ERR }, /* DEPRECATED */ + { "info", LOG_INFO }, + { "notice", LOG_NOTICE }, + { "panic", LOG_EMERG }, /* DEPRECATED */ + { "warn", LOG_WARNING }, /* DEPRECATED */ + { "warning", LOG_WARNING }, + { NULL, -1 }, +}; + +static int convert_log_priority(char *priority_name) +{ + CODE *priority_mapping; + + for (priority_mapping = prioritynames; + priority_mapping->c_name != NULL; + priority_mapping++) { + + if (!strcasecmp(priority_name, priority_mapping->c_name)) + return priority_mapping->c_val; + } + + return -1; +} + +static void remove_empty_args(char **argv, int *argc) +{ + int next_to_last = *argc - 1; + int i, j; + + for (i = j = 1; i < *argc; i++) { + if (*argv[i]) { + j++; + continue; + } + + while (i < *argc && argv[i] && !*argv[i]) i++; + + if (i == *argc) + break; + + if (i == next_to_last) { + if (*argv[i]) + argv[j++] = argv[i]; + break; + } else { + argv[j++] = argv[i]; + argv[i--] = ""; + } + } + *argc = j; +} + +static int do_master_read_master(struct master *master, int wait) +{ + sigset_t signalset; + /* Wait must be at least 1 second */ + unsigned int retry_wait = 2; + unsigned int elapsed = 0; + int max_wait = wait; + int ret = 0; + time_t age; + + sigemptyset(&signalset); + sigaddset(&signalset, SIGTERM); + sigaddset(&signalset, SIGINT); + sigaddset(&signalset, SIGHUP); + sigprocmask(SIG_UNBLOCK, &signalset, NULL); + + while (1) { + struct timespec t = { retry_wait, 0 }; + + age = monotonic_time(NULL); + if (master_read_master(master, age, 0)) { + ret = 1; + break; + } + + if (nanosleep(&t, NULL) == -1) + break; + + if (max_wait > 0) { + elapsed += retry_wait; + if (elapsed >= max_wait) { + logmsg("problem reading master map, " + "maximum wait exceeded"); + break; + } + } + } + + sigprocmask(SIG_BLOCK, &signalset, NULL); + + return ret; +} + +int main(int argc, char *argv[]) +{ + int res, opt, status; + int logpri = -1; + unsigned ghost, logging, daemon_check; + unsigned dumpmaps, foreground, have_global_options; + unsigned master_read; + int master_wait; + time_t timeout; + time_t age = monotonic_time(NULL); + struct rlimit rlim; + const char *options = "+hp:t:vmdD:fVrO:l:n:CFM"; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"pid-file", 1, 0, 'p'}, + {"timeout", 1, 0, 't'}, + {"verbose", 0, 0, 'v'}, + {"debug", 0, 0, 'd'}, + {"define", 1, 0, 'D'}, + {"foreground", 0, 0, 'f'}, + {"random-multimount-selection", 0, 0, 'r'}, + {"negative-timeout", 1, 0, 'n'}, + {"dumpmaps", 0, 0, 'm'}, + {"global-options", 1, 0, 'O'}, + {"version", 0, 0, 'V'}, + {"set-log-priority", 1, 0, 'l'}, + {"dont-check-daemon", 0, 0, 'C'}, + {"force", 0, 0, 'F'}, + {"master-wait", 1, 0, 'M'}, + {0, 0, 0, 0} + }; + + sigfillset(&block_sigs); + /* allow for the dropping of core files */ + sigdelset(&block_sigs, SIGABRT); + sigdelset(&block_sigs, SIGBUS); + sigdelset(&block_sigs, SIGSEGV); + sigdelset(&block_sigs, SIGILL); + sigdelset(&block_sigs, SIGFPE); + sigdelset(&block_sigs, SIGTRAP); + sigprocmask(SIG_BLOCK, &block_sigs, NULL); + + program = argv[0]; + + defaults_read_config(0); + + nfs_mount_uses_string_options = check_nfs_mount_version(&vers, &check); + + kpkt_len = get_kpkt_len(); + master_wait = defaults_get_master_wait(); + timeout = defaults_get_timeout(); + ghost = defaults_get_browse_mode(); + logging = defaults_get_logging(); + global_selection_options = 0; + global_options = NULL; + have_global_options = 0; + foreground = 0; + dumpmaps = 0; + daemon_check = 1; + + remove_empty_args(argv, &argc); + + opterr = 0; + while ((opt = getopt_long(argc, argv, options, long_options, NULL)) != EOF) { + switch (opt) { + case 'h': + usage(); + exit(0); + + case 'p': + pid_file = optarg; + break; + + case 't': + timeout = getnumopt(optarg, opt); + break; + + case 'v': + logging |= LOGOPT_VERBOSE; + break; + + case 'd': + logging |= LOGOPT_DEBUG; + break; + + case 'D': + macro_parse_globalvar(optarg); + break; + + case 'f': + foreground = 1; + break; + + case 'V': + show_build_info(); + exit(0); + + case 'r': + global_selection_options |= MOUNT_FLAG_RANDOM_SELECT; + break; + + case 'n': + global_negative_timeout = getnumopt(optarg, opt); + break; + + case 'm': + dumpmaps = 1; + break; + + case 'M': + master_wait = getnumopt(optarg, opt); + break; + + case 'O': + if (!have_global_options) { + global_options = strdup(optarg); + have_global_options = 1; + break; + } + printf("%s: global options already specified.\n", + program); + break; + + case 'l': + if (isalpha(*optarg)) { + logpri = convert_log_priority(optarg); + if (logpri < 0) { + fprintf(stderr, "Invalid log priority:" + " %s\n", optarg); + exit(1); + } + } else if (isdigit(*optarg)) { + logpri = getnumopt(optarg, opt); + } else { + fprintf(stderr, "non-alphanumeric character " + "found in log priority. Aborting.\n"); + exit(1); + } + break; + + case 'C': + daemon_check = 0; + break; + + case 'F': + do_force_unlink = 1; + break; + + case '?': + case ':': + printf("%s: Ambiguous or unknown options\n", program); + exit(1); + } + } + + if (logging & LOGOPT_VERBOSE) + set_log_verbose(); + + if (logging & LOGOPT_DEBUG) + set_log_debug(); + + if (geteuid() != 0) { + fprintf(stderr, "%s: this program must be run by root.\n", + program); + exit(1); + } + + /* Remove the options */ + argv += optind; + argc -= optind; + + if (logpri >= 0) { + int exit_code = 0; + int i; + + /* + * The remaining argv elements are the paths for which + * log priorities must be changed. + */ + for (i = 0; i < argc; i++) { + if (set_log_priority(argv[i], logpri) < 0) + exit_code = 1; + } + if (argc < 1) { + fprintf(stderr, + "--set-log-priority requires a path.\n"); + exit_code = 1; + } + exit(exit_code); + } + +#if 0 + if (!load_autofs4_module()) { + fprintf(stderr, "%s: can't load %s filesystem module.\n", + program, FS_MODULE_NAME); + exit(1); + } +#endif + + /* Don't need the kernel module just to look at the configured maps */ + if (!dumpmaps && (!query_kproto_ver() || get_kver_major() < 5)) { + fprintf(stderr, + "%s: test mount forbidden or " + "incorrect kernel protocol version, " + "kernel protocol version 5.00 or above required.\n", + program); + exit(1); + } + + res = getrlimit(RLIMIT_NOFILE, &rlim); + if (res == -1 || rlim.rlim_max <= MAX_OPEN_FILES) { + rlim.rlim_cur = MAX_OPEN_FILES; + rlim.rlim_max = MAX_OPEN_FILES; + } + res = setrlimit(RLIMIT_NOFILE, &rlim); + if (res) + printf("%s: can't increase open file limit - continuing", + program); + +#if ENABLE_CORES + rlim.rlim_cur = RLIM_INFINITY; + rlim.rlim_max = RLIM_INFINITY; + res = setrlimit(RLIMIT_CORE, &rlim); + if (res) + printf("%s: can't increase core file limit - continuing", + program); +#endif + + /* Get processor information for predefined escapes */ + macro_init(); + + if (dumpmaps) { + struct master_mapent *entry; + struct list_head *head, *p; + struct mapent_cache *nc; + const char *type = NULL; + const char *name = NULL; + const char *master = NULL; + + if (argc > 0) { + if (argc >= 2) { + type = argv[0]; + name = argv[1]; + } + if (argc == 3) + master = argv[2]; + } + + if (master) + master_list = master_new(NULL, timeout, ghost); + else + master_list = master_new(master, timeout, ghost); + if (!master_list) { + printf("%s: can't create master map", program); + macro_free_global_table(); + exit(1); + } + + log_to_stderr(); + + master_init_scan(); + + nc = cache_init_null_cache(master_list); + if (!nc) { + printf("%s: failed to init null map cache for %s", + program, master_list->name); + macro_free_global_table(); + exit(1); + } + master_list->nc = nc; + + lookup_nss_read_master(master_list, 0); + if (type) { + const char *map = basename(name); + if (!map) + printf("%s: invalid map name %s\n", + program, name); + else + dump_map(master_list, type, map); + } else + master_show_mounts(master_list); + + head = &master_list->mounts; + p = head->next; + while (p != head) { + entry = list_entry(p, struct master_mapent, list); + p = p->next; + master_free_mapent_sources(entry, 1); + master_free_mapent(entry); + } + master_kill(master_list); + macro_free_global_table(); + + exit(0); + } + + if (argc == 0) + master_list = master_new(NULL, timeout, ghost); + else + master_list = master_new(argv[0], timeout, ghost); + + if (!master_list) { + printf("%s: can't create master map %s", program, argv[0]); + macro_free_global_table(); + exit(1); + } + + become_daemon(foreground, daemon_check); + + if (pthread_attr_init(&th_attr)) { + logerr("%s: failed to init thread attribute struct!", + program); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + if (pthread_attr_init(&th_attr_detached)) { + logerr("%s: failed to init thread attribute struct!", + program); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + if (pthread_attr_setdetachstate( + &th_attr_detached, PTHREAD_CREATE_DETACHED)) { + logerr("%s: failed to set detached thread attribute!", + program); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + +#ifdef _POSIX_THREAD_ATTR_STACKSIZE + if (pthread_attr_setstacksize( + &th_attr_detached, detached_thread_stack_size)) { + logerr("%s: failed to set stack size thread attribute!", + program); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } +#endif + + if (pthread_attr_getstacksize( + &th_attr_detached, &detached_thread_stack_size)) { + logerr("%s: failed to get detached thread stack size!", + program); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + info(logging, "Starting automounter version %s, master map %s", + version, master_list->name); + info(logging, "using kernel protocol version %d.%02d", + get_kver_major(), get_kver_minor()); + + status = pthread_key_create(&key_thread_stdenv_vars, + key_thread_stdenv_vars_destroy); + if (status) { + logerr("%s: failed to create thread data key for std env vars!", + program); + master_kill(master_list); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + status = pthread_key_create(&key_thread_attempt_id, free); + if (status) { + logerr("%s: failed to create thread data key for attempt ID!", + program); + master_kill(master_list); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + init_ioctl_ctl(); + + if (!alarm_start_handler()) { + logerr("%s: failed to create alarm handler thread!", program); + master_kill(master_list); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + + if (!st_start_handler()) { + logerr("%s: failed to create FSM handler thread!", program); + master_kill(master_list); + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + release_flag_file(); + macro_free_global_table(); + exit(1); + } + +#if defined(WITH_LDAP) && defined(LIBXML2_WORKAROUND) + void *dh_xml2 = dlopen("libxml2.so", RTLD_NOW); + if (!dh_xml2) + dh_xml2 = dlopen("libxml2.so.2", RTLD_NOW); + if (dh_xml2) + xmlInitParser(); +#endif +#ifdef TIRPC_WORKAROUND + void *dh_tirpc = dlopen("libtirpc.so", RTLD_NOW); + if (!dh_tirpc) + dh_tirpc = dlopen("libtirpc.so.1", RTLD_NOW); + if (!dh_tirpc) + dh_tirpc = dlopen("libtirpc.so.3", RTLD_NOW); +#endif + + master_read = master_read_master(master_list, age, 0); + if (!master_read) { + /* + * Read master map, waiting until it is available, unless + * a signal is received, in which case exit returning an + * error. + */ + if (!do_master_read_master(master_list, master_wait)) { + logmsg("%s: warning: could not read at least one " + "map source after waiting, continuing ...", + program); + /* + * Failed to read master map, continue with what + * we have anyway. + */ + master_read_master(master_list, age, 1); + } + } + + /* + * Mmm ... reset force unlink umount so we don't also do this + * in future when we receive a HUP signal. + */ + do_force_unlink = 0; + + st_stat = 0; + res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); + close(start_pipefd[1]); + + state_mach_thid = pthread_self(); + statemachine(NULL); + + master_kill(master_list); + + if (pid_file) { + unlink(pid_file); + pid_file = NULL; + } + defaults_conf_release(); + closelog(); + release_flag_file(); + macro_free_global_table(); + +#ifdef TIRPC_WORKAROUND + if (dh_tirpc) + dlclose(dh_tirpc); +#endif +#if defined(WITH_LDAP) && defined( LIBXML2_WORKAROUND) + if (dh_xml2) { + xmlCleanupParser(); + dlclose(dh_xml2); + } +#endif + close_ioctl_ctl(); + + info(logging, "autofs stopped"); + + exit(0); +} diff --git a/daemon/direct.c b/daemon/direct.c new file mode 100644 index 0000000..9a13435 --- /dev/null +++ b/daemon/direct.c @@ -0,0 +1,1514 @@ +/* ----------------------------------------------------------------------- * + * + * direct.c - Linux automounter direct mount handling + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge + * Copyright 2001-2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INCLUDE_PENDING_FUNCTIONS +#include "automount.h" + +/* Attribute to create detached thread */ +extern pthread_attr_t th_attr_detached; + +struct mnt_params { + char *options; +}; + +pthread_key_t key_mnt_direct_params; +pthread_key_t key_mnt_offset_params; +pthread_once_t key_mnt_params_once = PTHREAD_ONCE_INIT; + +static void key_mnt_params_destroy(void *arg) +{ + struct mnt_params *mp; + + mp = (struct mnt_params *) arg; + if (mp->options) + free(mp->options); + free(mp); + return; +} + +static void key_mnt_params_init(void) +{ + int status; + + status = pthread_key_create(&key_mnt_direct_params, key_mnt_params_destroy); + if (status) + fatal(status); + + status = pthread_key_create(&key_mnt_offset_params, key_mnt_params_destroy); + if (status) + fatal(status); + + return; +} + +static void mnts_cleanup(void *arg) +{ + struct mnt_list *mnts = (struct mnt_list *) arg; + tree_free_mnt_tree(mnts); + return; +} + +int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, struct mapent *me) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + char buf[MAX_ERR_BUF]; + int ioctlfd = -1, rv, left, retries; + int opened = 0; + + left = umount_multi(ap, me->key, 0); + if (left) { + warn(ap->logopt, "could not unmount %d dirs under %s", + left, me->key); + return 1; + } + + if (me->ioctlfd != -1) { + if (ap->state == ST_READMAP && + tree_is_mounted(mnts, me->key, MNTS_REAL)) { + error(ap->logopt, + "attempt to umount busy direct mount %s", + me->key); + return 1; + } + ioctlfd = me->ioctlfd; + } else { + ops->open(ap->logopt, &ioctlfd, me->dev, me->key); + opened = 1; + } + + if (ioctlfd >= 0) { + unsigned int status = 1; + + rv = ops->askumount(ap->logopt, ioctlfd, &status); + if (rv) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "ioctl failed: %s", estr); + /* The ioctl failed so this probably won't + * work either but since we opened it here + * try anyway. We should set these catatonic + * too but .... + */ + if (opened) + ops->close(ap->logopt, ioctlfd); + return 1; + } else if (!status) { + if (ap->state != ST_SHUTDOWN_FORCE) { + error(ap->logopt, + "ask umount returned busy for %s", + me->key); + if (opened) + ops->close(ap->logopt, ioctlfd); + return 1; + } else { + me->ioctlfd = -1; + ops->close(ap->logopt, ioctlfd); + goto force_umount; + } + } + me->ioctlfd = -1; + ops->close(ap->logopt, ioctlfd); + } else { + error(ap->logopt, + "couldn't get ioctl fd for direct mount %s", me->key); + return 1; + } + + sched_yield(); + + retries = UMOUNT_RETRIES; + while ((rv = umount(me->key)) == -1 && retries--) { + struct timespec tm = {0, 200000000}; + if (errno != EBUSY) + break; + nanosleep(&tm, NULL); + } + + if (rv == -1) { + switch (errno) { + case ENOENT: + case EINVAL: + warn(ap->logopt, "mount point %s does not exist", + me->key); + return 0; + break; + case EBUSY: + warn(ap->logopt, "mount point %s is in use", me->key); + if (ap->state == ST_SHUTDOWN_FORCE) + goto force_umount; + else { + if (ap->state != ST_READMAP) + set_direct_mount_tree_catatonic(ap, me); + return 0; + } + break; + case ENOTDIR: + error(ap->logopt, "mount point is not a directory"); + return 0; + break; + } + return 1; + } + +force_umount: + if (rv != 0) { + info(ap->logopt, "forcing umount of direct mount %s", me->key); + rv = umount2(me->key, MNT_DETACH); + } else + info(ap->logopt, "umounted direct mount %s", me->key); + + if (!rv && me->flags & MOUNT_FLAG_DIR_CREATED) { + if (rmdir(me->key) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, "failed to remove dir %s: %s", + me->key, estr); + } + } + return rv; +} + +int umount_autofs_direct(struct autofs_point *ap) +{ + struct map_source *map; + struct mapent_cache *nc, *mc; + struct mnt_list *mnts; + struct mapent *me, *ne; + + mnts = tree_make_mnt_tree(_PROC_MOUNTS, "/"); + pthread_cleanup_push(mnts_cleanup, mnts); + nc = ap->entry->master->nc; + cache_readlock(nc); + pthread_cleanup_push(cache_lock_cleanup, nc); + map = ap->entry->maps; + while (map) { + mc = map->mc; + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_readlock(mc); + me = cache_enumerate(mc, NULL); + while (me) { + int error; + + ne = cache_lookup_distinct(nc, me->key); + if (ne && map->master_line > ne->age) { + me = cache_enumerate(mc, me); + continue; + } + + /* The daemon is exiting so ... + * If we get a fail here we must make our + * best effort to set the direct mount trigger + * catatonic regardless of the reason for the + * failed umount. + */ + error = do_umount_autofs_direct(ap, mnts, me); + if (!error) + goto done; + + if (ap->state != ST_READMAP) + set_direct_mount_tree_catatonic(ap, me); +done: + me = cache_enumerate(mc, me); + } + pthread_cleanup_pop(1); + map = map->next; + } + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + close(ap->state_pipe[0]); + close(ap->state_pipe[1]); + if (ap->pipefd >= 0) + close(ap->pipefd); + if (ap->kpipefd >= 0) { + close(ap->kpipefd); + ap->kpipefd = -1; + } + + return 0; +} + +static int unlink_mount_tree(struct autofs_point *ap, struct list_head *list) +{ + struct list_head *p; + int rv, ret; + pid_t pgrp = getpgrp(); + char spgrp[20]; + + sprintf(spgrp, "pgrp=%d", pgrp); + + ret = 1; + list_for_each(p, list) { + struct mnt_list *mnt; + + mnt = list_entry(p, struct mnt_list, list); + + if (strstr(mnt->opts, spgrp)) + continue; + + if (strcmp(mnt->fs_type, "autofs")) + rv = spawn_umount(ap->logopt, "-l", mnt->path, NULL); + else + rv = umount2(mnt->path, MNT_DETACH); + if (rv == -1) { + debug(ap->logopt, + "can't unlink %s from mount tree", mnt->path); + + switch (errno) { + case EINVAL: + warn(ap->logopt, + "bad superblock or not mounted"); + break; + + case ENOENT: + case EFAULT: + ret = 0; + warn(ap->logopt, "bad path for mount"); + break; + } + } + } + return ret; +} + +static int unlink_active_mounts(struct autofs_point *ap, struct mnt_list *mnts, struct mapent *me) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct list_head list; + + INIT_LIST_HEAD(&list); + + if (tree_get_mnt_list(mnts, &list, me->key, 1)) { + if (ap->state == ST_READMAP) { + time_t tout = get_exp_timeout(ap, me->source); + int save_ioctlfd, ioctlfd; + + save_ioctlfd = ioctlfd = me->ioctlfd; + + if (ioctlfd == -1) + ops->open(ap->logopt, + &ioctlfd, me->dev, me->key); + + if (ioctlfd < 0) { + error(ap->logopt, + "failed to create ioctl fd for %s", + me->key); + return 0; + } + + ops->timeout(ap->logopt, ioctlfd, tout); + + if (save_ioctlfd == -1) + ops->close(ap->logopt, ioctlfd); + + return 0; + } + } + + if (!unlink_mount_tree(ap, &list)) { + debug(ap->logopt, + "already mounted as other than autofs " + "or failed to unlink entry in tree"); + return 0; + } + + return 1; +} + +int do_mount_autofs_direct(struct autofs_point *ap, + struct mnt_list *mnts, struct mapent *me, + time_t timeout) +{ + const char *str_direct = mount_type_str(t_direct); + struct ioctl_ops *ops = get_ioctl_ops(); + struct mnt_params *mp; + struct stat st; + int status, ret, ioctlfd; + const char *map_name; + time_t runfreq; + int err; + + if (timeout) { + /* Calculate the expire run frequency */ + runfreq = (timeout + CHECK_RATIO - 1) / CHECK_RATIO; + if (ap->exp_runfreq) + ap->exp_runfreq = min(ap->exp_runfreq, runfreq); + else + ap->exp_runfreq = runfreq; + } + + if (ops->version && !do_force_unlink) { + ap->flags |= MOUNT_FLAG_REMOUNT; + ret = try_remount(ap, me, t_direct); + ap->flags &= ~MOUNT_FLAG_REMOUNT; + if (ret == 1) + return 0; + if (ret == 0) + return -1; + } else { + /* + * A return of 0 indicates we're re-reading the map. + * A return of 1 indicates we successfully unlinked + * the mount tree if there was one. A return of -1 + * inducates we failed to unlink the mount tree so + * we have to return a failure. + */ + ret = unlink_active_mounts(ap, mnts, me); + if (ret == -1 || ret == 0) + return ret; + + if (me->ioctlfd != -1) { + error(ap->logopt, "active direct mount %s", me->key); + return -1; + } + } + + status = pthread_once(&key_mnt_params_once, key_mnt_params_init); + if (status) + fatal(status); + + mp = pthread_getspecific(key_mnt_direct_params); + if (!mp) { + mp = (struct mnt_params *) malloc(sizeof(struct mnt_params)); + if (!mp) { + crit(ap->logopt, + "mnt_params value create failed for direct mount %s", + ap->path); + return 0; + } + mp->options = NULL; + + status = pthread_setspecific(key_mnt_direct_params, mp); + if (status) { + free(mp); + fatal(status); + } + } + + if (!mp->options) { + mp->options = make_options_string(ap->path, ap->kpipefd, str_direct); + if (!mp->options) + return 0; + } + + /* In case the directory doesn't exist, try to mkdir it */ + if (mkdir_path(me->key, 0555) < 0) { + if (errno != EEXIST && errno != EROFS) { + crit(ap->logopt, + "failed to create mount directory %s", me->key); + return -1; + } + /* If we recieve an error, and it's EEXIST or EROFS we know + the directory was not created. */ + me->flags &= ~MOUNT_FLAG_DIR_CREATED; + } else { + /* No errors so the directory was successfully created */ + me->flags |= MOUNT_FLAG_DIR_CREATED; + } + + map_name = me->mc->map->argv[0]; + + ret = mount(map_name, me->key, "autofs", MS_MGC_VAL, mp->options); + if (ret) { + crit(ap->logopt, "failed to mount autofs path %s", me->key); + goto out_err; + } + + ret = stat(me->key, &st); + if (ret == -1) { + error(ap->logopt, + "failed to stat direct mount trigger %s", me->key); + goto out_umount; + } + + if (ap->mode && (err = chmod(me->key, ap->mode))) + warn(ap->logopt, "failed to change mode of %s", me->key); + + ops->open(ap->logopt, &ioctlfd, st.st_dev, me->key); + if (ioctlfd < 0) { + crit(ap->logopt, "failed to create ioctl fd for %s", me->key); + goto out_umount; + } + + ops->timeout(ap->logopt, ioctlfd, timeout); + notify_mount_result(ap, me->key, timeout, str_direct); + cache_set_ino_index(me->mc, me->key, st.st_dev, st.st_ino); + ops->close(ap->logopt, ioctlfd); + + debug(ap->logopt, "mounted trigger %s", me->key); + + return 0; + +out_umount: + /* TODO: maybe force umount (-l) */ + umount(me->key); +out_err: + if (me->flags & MOUNT_FLAG_DIR_CREATED) + rmdir(me->key); + + return -1; +} + +int mount_autofs_direct(struct autofs_point *ap) +{ + struct map_source *map; + struct mapent_cache *nc, *mc; + struct mapent *me, *ne, *nested; + struct mnt_list *mnts; + time_t now = monotonic_time(NULL); + + if (strcmp(ap->path, "/-")) { + error(ap->logopt, "expected direct map, exiting"); + return -1; + } + + /* TODO: check map type */ + if (lookup_nss_read_map(ap, NULL, now)) + lookup_prune_cache(ap, now); + else { + error(ap->logopt, "failed to read direct map"); + return -1; + } + + mnts = tree_make_mnt_tree(_PROC_MOUNTS, "/"); + pthread_cleanup_push(mnts_cleanup, mnts); + pthread_cleanup_push(master_source_lock_cleanup, ap->entry); + master_source_readlock(ap->entry); + nc = ap->entry->master->nc; + cache_readlock(nc); + pthread_cleanup_push(cache_lock_cleanup, nc); + map = ap->entry->maps; + while (map) { + time_t timeout; + /* + * Only consider map sources that have been read since + * the map entry was last updated. + */ + if (ap->entry->age > map->age) { + map = map->next; + continue; + } + + mc = map->mc; + timeout = get_exp_timeout(ap, map); + cache_readlock(mc); + pthread_cleanup_push(cache_lock_cleanup, mc); + me = cache_enumerate(mc, NULL); + while (me) { + ne = cache_lookup_distinct(nc, me->key); + if (ne) { + if (map->master_line < ne->age) { + /* TODO: check return, locking me */ + do_mount_autofs_direct(ap, mnts, me, timeout); + } + me = cache_enumerate(mc, me); + continue; + } + + nested = cache_partial_match(nc, me->key); + if (nested) { + error(ap->logopt, + "removing invalid nested null entry %s", + nested->key); + nested = cache_partial_match(nc, me->key); + if (nested) + cache_delete(nc, nested->key); + } + + /* TODO: check return, locking me */ + do_mount_autofs_direct(ap, mnts, me, timeout); + + me = cache_enumerate(mc, me); + } + pthread_cleanup_pop(1); + map = map->next; + } + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + return 0; +} + +int umount_autofs_offset(struct autofs_point *ap, struct mapent *me) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + char buf[MAX_ERR_BUF]; + int ioctlfd = -1, rv = 1, retries; + int opened = 0; + + if (me->ioctlfd != -1) { + if (is_mounted(_PATH_MOUNTED, me->key, MNTS_REAL)) { + error(ap->logopt, + "attempt to umount busy offset %s", me->key); + return 1; + } + ioctlfd = me->ioctlfd; + } else { + /* offset isn't mounted, return success and try to recover */ + if (!is_mounted(_PROC_MOUNTS, me->key, MNTS_AUTOFS)) { + debug(ap->logopt, + "offset %s not mounted", + me->key); + return 0; + } + ops->open(ap->logopt, &ioctlfd, me->dev, me->key); + opened = 1; + } + + if (ioctlfd >= 0) { + unsigned int status = 1; + + rv = ops->askumount(ap->logopt, ioctlfd, &status); + if (rv) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("ioctl failed: %s", estr); + if (opened && ioctlfd != -1) + ops->close(ap->logopt, ioctlfd); + return 1; + } else if (!status) { + if (ap->state != ST_SHUTDOWN_FORCE) { + if (ap->shutdown) + error(ap->logopt, + "ask umount returned busy for %s", + me->key); + if (opened && ioctlfd != -1) + ops->close(ap->logopt, ioctlfd); + return 1; + } else { + me->ioctlfd = -1; + ops->catatonic(ap->logopt, ioctlfd); + ops->close(ap->logopt, ioctlfd); + goto force_umount; + } + } + me->ioctlfd = -1; + ops->catatonic(ap->logopt, ioctlfd); + ops->close(ap->logopt, ioctlfd); + } else { + struct stat st; + char *estr; + int save_errno = errno; + + /* Non existent directory on remote fs - no mount */ + if (stat(me->key, &st) == -1 && errno == ENOENT) + return 0; + + estr = strerror_r(save_errno, buf, MAX_ERR_BUF); + error(ap->logopt, + "couldn't get ioctl fd for offset %s: %s", + me->key, estr); + goto force_umount; + } + + sched_yield(); + + retries = UMOUNT_RETRIES; + while ((rv = umount(me->key)) == -1 && retries--) { + struct timespec tm = {0, 200000000}; + if (errno != EBUSY) + break; + nanosleep(&tm, NULL); + } + + if (rv == -1) { + switch (errno) { + case ENOENT: + warn(ap->logopt, "mount point does not exist"); + return 0; + break; + case EBUSY: + error(ap->logopt, "mount point %s is in use", me->key); + if (ap->state != ST_SHUTDOWN_FORCE) + return 1; + break; + case ENOTDIR: + error(ap->logopt, "mount point is not a directory"); + return 0; + break; + } + goto force_umount; + } + +force_umount: + if (rv != 0) { + info(ap->logopt, "forcing umount of offset mount %s", me->key); + rv = umount2(me->key, MNT_DETACH); + } else + info(ap->logopt, "umounted offset mount %s", me->key); + + return rv; +} + +int mount_autofs_offset(struct autofs_point *ap, struct mapent *me, const char *root, const char *offset) +{ + const char *str_offset = mount_type_str(t_offset); + struct ioctl_ops *ops = get_ioctl_ops(); + char buf[MAX_ERR_BUF]; + struct mnt_params *mp; + time_t timeout = get_exp_timeout(ap, me->source); + struct stat st; + int ioctlfd, status, ret; + const char *hosts_map_name = "-hosts"; + const char *map_name = hosts_map_name; + const char *type; + char mountpoint[PATH_MAX]; + + if (ops->version && ap->flags & MOUNT_FLAG_REMOUNT) { + ret = try_remount(ap, me, t_offset); + if (ret == 1) + return MOUNT_OFFSET_OK; + /* Offset mount not found, fall thru and try to mount it */ + if (!(ret == -1 && errno == ENOENT)) + return MOUNT_OFFSET_FAIL; + } else { + if (is_mounted(_PROC_MOUNTS, me->key, MNTS_AUTOFS)) { + if (ap->state != ST_READMAP) + warn(ap->logopt, + "trigger %s already mounted", me->key); + return MOUNT_OFFSET_OK; + } + + if (me->ioctlfd != -1) { + error(ap->logopt, "active offset mount %s", me->key); + return MOUNT_OFFSET_FAIL; + } + } + + status = pthread_once(&key_mnt_params_once, key_mnt_params_init); + if (status) + fatal(status); + + mp = pthread_getspecific(key_mnt_offset_params); + if (!mp) { + mp = (struct mnt_params *) malloc(sizeof(struct mnt_params)); + if (!mp) { + crit(ap->logopt, + "mnt_params value create failed for offset mount %s", + me->key); + return MOUNT_OFFSET_OK; + } + mp->options = NULL; + + status = pthread_setspecific(key_mnt_offset_params, mp); + if (status) { + free(mp); + fatal(status); + } + } + + if (!mp->options) { + mp->options = make_options_string(ap->path, ap->kpipefd, str_offset); + if (!mp->options) + return MOUNT_OFFSET_OK; + } + + strcpy(mountpoint, root); + strcat(mountpoint, offset); + + /* In case the directory doesn't exist, try to mkdir it */ + if (mkdir_path(mountpoint, 0555) < 0) { + if (errno == EEXIST) { + /* + * If the mount point directory is a real mount + * and it isn't the root offset then it must be + * a mount that has been automatically mounted by + * the kernel NFS client. + */ + if (me->multi != me && + is_mounted(_PROC_MOUNTS, mountpoint, MNTS_REAL)) + return MOUNT_OFFSET_IGNORE; + + /* + * If we recieve an error, and it's EEXIST + * we know the directory was not created. + */ + me->flags &= ~MOUNT_FLAG_DIR_CREATED; + } else if (errno == EACCES) { + /* + * We require the mount point directory to exist when + * installing multi-mount triggers into a host + * filesystem. + * + * If it doesn't exist it is not a valid part of the + * mount heirachy. + */ + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + debug(ap->logopt, + "can't create mount directory: %s, %s", + mountpoint, estr); + return MOUNT_OFFSET_FAIL; + } else { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + crit(ap->logopt, + "failed to create mount directory: %s, %s", + mountpoint, estr); + return MOUNT_OFFSET_FAIL; + } + } else { + /* No errors so the directory was successfully created */ + me->flags |= MOUNT_FLAG_DIR_CREATED; + } + + debug(ap->logopt, + "calling mount -t autofs " SLOPPY " -o %s automount %s", + mp->options, mountpoint); + + type = ap->entry->maps->type; + if (!type || strcmp(ap->entry->maps->type, "hosts")) + map_name = me->mc->map->argv[0]; + + ret = mount(map_name, mountpoint, "autofs", MS_MGC_VAL, mp->options); + if (ret) { + crit(ap->logopt, + "failed to mount offset trigger %s at %s", + me->key, mountpoint); + goto out_err; + } + + ret = stat(mountpoint, &st); + if (ret == -1) { + error(ap->logopt, + "failed to stat direct mount trigger %s", mountpoint); + goto out_umount; + } + + ops->open(ap->logopt, &ioctlfd, st.st_dev, mountpoint); + if (ioctlfd < 0) { + crit(ap->logopt, "failed to create ioctl fd for %s", mountpoint); + goto out_umount; + } + + ops->timeout(ap->logopt, ioctlfd, timeout); + cache_set_ino_index(me->mc, me->key, st.st_dev, st.st_ino); + if (ap->logopt & LOGOPT_DEBUG) + notify_mount_result(ap, mountpoint, timeout, str_offset); + else + notify_mount_result(ap, me->key, timeout, str_offset); + ops->close(ap->logopt, ioctlfd); + + debug(ap->logopt, "mounted trigger %s at %s", me->key, mountpoint); + + return MOUNT_OFFSET_OK; + +out_umount: + umount(mountpoint); +out_err: + if (stat(mountpoint, &st) == 0 && me->flags & MOUNT_FLAG_DIR_CREATED) + rmdir_path(ap, mountpoint, st.st_dev); + + return MOUNT_OFFSET_FAIL; +} + +void *expire_proc_direct(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct mnt_list *mnts = NULL, *next; + struct list_head list, *p; + struct expire_args *ea; + struct expire_args ec; + struct autofs_point *ap; + struct mapent *me = NULL; + unsigned int now; + int ioctlfd, cur_state; + int status, ret, left; + + ea = (struct expire_args *) arg; + + status = pthread_mutex_lock(&ea->mutex); + if (status) + fatal(status); + + ap = ec.ap = ea->ap; + now = ea->when; + ec.status = -1; + + ea->signaled = 1; + status = pthread_cond_signal(&ea->cond); + if (status) + fatal(status); + + status = pthread_mutex_unlock(&ea->mutex); + if (status) + fatal(status); + + pthread_cleanup_push(expire_cleanup, &ec); + + left = 0; + + mnts = tree_make_mnt_tree(_PROC_MOUNTS, "/"); + pthread_cleanup_push(mnts_cleanup, mnts); + + /* Get a list of mounts select real ones and expire them if possible */ + INIT_LIST_HEAD(&list); + if (!tree_get_mnt_list(mnts, &list, "/", 0)) { + ec.status = 0; + return NULL; + } + + list_for_each(p, &list) { + next = list_entry(p, struct mnt_list, list); + + /* + * All direct mounts must be present in the map + * entry cache. + */ + pthread_cleanup_push(master_source_lock_cleanup, ap->entry); + master_source_readlock(ap->entry); + me = lookup_source_mapent(ap, next->path, LKP_DISTINCT); + pthread_cleanup_pop(1); + if (!me) + continue; + + if (!strcmp(next->fs_type, "autofs")) { + struct stat st; + int ioctlfd; + + cache_unlock(me->mc); + + /* + * If we have submounts check if this path lives below + * one of them and pass on state change. + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + if (strstr(next->opts, "indirect")) { + master_notify_submount(ap, next->path, ap->state); + pthread_setcancelstate(cur_state, NULL); + continue; + } + + if (me->ioctlfd == -1) { + pthread_setcancelstate(cur_state, NULL); + continue; + } + + /* It's got a mount, deal with in the outer loop */ + if (tree_is_mounted(mnts, me->key, MNTS_REAL)) { + pthread_setcancelstate(cur_state, NULL); + continue; + } + + /* + * Maybe a manual umount, repair. + * It will take ap->exp_timeout/4 for us to relaize + * this so user must still use USR1 signal to close + * the open file handle for mounts atop multi-mount + * triggers. There is no way that I'm aware of to + * avoid maintaining a file handle for control + * functions as once it's mounted all opens are + * directed to the mount not the trigger. + */ + + /* Check for manual umount */ + cache_writelock(me->mc); + if (me->ioctlfd != -1 && + fstat(me->ioctlfd, &st) != -1 && + !count_mounts(ap, next->path, st.st_dev)) { + ops->close(ap->logopt, me->ioctlfd); + me->ioctlfd = -1; + cache_unlock(me->mc); + pthread_setcancelstate(cur_state, NULL); + continue; + } + cache_unlock(me->mc); + + ioctlfd = me->ioctlfd; + + ret = ops->expire(ap->logopt, ioctlfd, next->path, now); + if (ret) { + left++; + pthread_setcancelstate(cur_state, NULL); + continue; + } + + pthread_setcancelstate(cur_state, NULL); + continue; + } + + if (me->ioctlfd >= 0) { + /* Real mounts have an open ioctl fd */ + ioctlfd = me->ioctlfd; + cache_unlock(me->mc); + } else { + cache_unlock(me->mc); + continue; + } + + if (ap->state == ST_EXPIRE || ap->state == ST_PRUNE) + pthread_testcancel(); + + debug(ap->logopt, "send expire to trigger %s", next->path); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + ret = ops->expire(ap->logopt, ioctlfd, next->path, now); + if (ret) + left++; + pthread_setcancelstate(cur_state, NULL); + } + pthread_cleanup_pop(1); + + if (left) + info(ap->logopt, "%d remaining in %s", left, ap->path); + + ec.status = left; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + pthread_cleanup_pop(1); + pthread_setcancelstate(cur_state, NULL); + + return NULL; +} + +static void expire_send_fail(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *mt = arg; + struct autofs_point *ap = mt->ap; + ops->send_fail(ap->logopt, + mt->ioctlfd, mt->wait_queue_token, -ENOENT); +} + +static void *do_expire_direct(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *args, mt; + struct autofs_point *ap; + size_t len; + int status, state; + + args = (struct pending_args *) arg; + + pending_mutex_lock(args); + + memcpy(&mt, args, sizeof(struct pending_args)); + + ap = mt.ap; + + args->signaled = 1; + status = pthread_cond_signal(&args->cond); + if (status) + fatal(status); + + pending_mutex_unlock(args); + + pthread_cleanup_push(expire_send_fail, &mt); + + len = _strlen(mt.name, KEY_MAX_LEN); + if (!len) { + warn(ap->logopt, "direct key path too long %s", mt.name); + /* TODO: force umount ?? */ + pthread_exit(NULL); + } + + status = do_expire(ap, mt.name, len); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + if (status) + ops->send_fail(ap->logopt, + mt.ioctlfd, mt.wait_queue_token, -ENOENT); + else { + struct mapent *me; + cache_writelock(mt.mc); + me = cache_lookup_distinct(mt.mc, mt.name); + if (me) + me->ioctlfd = -1; + cache_unlock(mt.mc); + ops->send_ready(ap->logopt, mt.ioctlfd, mt.wait_queue_token); + ops->close(ap->logopt, mt.ioctlfd); + } + pthread_setcancelstate(state, NULL); + + pthread_cleanup_pop(0); + + return NULL; +} + +int handle_packet_expire_direct(struct autofs_point *ap, autofs_packet_expire_direct_t *pkt) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct map_source *map; + struct mapent_cache *mc = NULL; + struct mapent *me = NULL; + struct pending_args *mt; + char buf[MAX_ERR_BUF]; + pthread_t thid; + struct timespec wait; + int status, state; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + /* + * This is a bit of a big deal. + * If we can't find the path and the map entry then + * we can't send a notification back to the kernel. + * Hang results. + * + * OTOH there is a mount so there should be a path + * and since it got mounted we have to trust that + * there is an entry in the cache. + */ + master_source_writelock(ap->entry); + map = ap->entry->maps; + while (map) { + mc = map->mc; + cache_writelock(mc); + me = cache_lookup_ino(mc, pkt->dev, pkt->ino); + if (me) + break; + cache_unlock(mc); + map = map->next; + } + + if (!me) { + /* + * Shouldn't happen as we have been sent this following + * successful thread creation and lookup. + */ + crit(ap->logopt, "can't find map entry for (%lu,%lu)", + (unsigned long) pkt->dev, (unsigned long) pkt->ino); + master_source_unlock(ap->entry); + pthread_setcancelstate(state, NULL); + return 1; + } + + /* Can't expire it if it isn't mounted */ + if (me->ioctlfd == -1) { + int ioctlfd; + ops->open(ap->logopt, &ioctlfd, me->dev, me->key); + if (ioctlfd == -1) { + crit(ap->logopt, "can't open ioctlfd for %s", me->key); + cache_unlock(mc); + master_source_unlock(ap->entry); + pthread_setcancelstate(state, NULL); + return 1; + } + ops->send_ready(ap->logopt, ioctlfd, pkt->wait_queue_token); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + pthread_setcancelstate(state, NULL); + return 0; + } + + mt = malloc(sizeof(struct pending_args)); + if (!mt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + ops->send_fail(ap->logopt, + me->ioctlfd, pkt->wait_queue_token, -ENOMEM); + cache_unlock(mc); + master_source_unlock(ap->entry); + pthread_setcancelstate(state, NULL); + return 1; + } + + pending_cond_init(mt); + + status = pthread_mutex_init(&mt->mutex, NULL); + if (status) + fatal(status); + + mt->ap = ap; + mt->ioctlfd = me->ioctlfd; + mt->mc = mc; + /* TODO: check length here */ + strcpy(mt->name, me->key); + mt->dev = me->dev; + mt->type = NFY_EXPIRE; + mt->wait_queue_token = pkt->wait_queue_token; + + debug(ap->logopt, "token %ld, name %s", + (unsigned long) pkt->wait_queue_token, mt->name); + + pending_mutex_lock(mt); + + status = pthread_create(&thid, &th_attr_detached, do_expire_direct, mt); + if (status) { + error(ap->logopt, "expire thread create failed"); + ops->send_fail(ap->logopt, + mt->ioctlfd, pkt->wait_queue_token, -status); + cache_unlock(mc); + master_source_unlock(ap->entry); + pending_mutex_unlock(mt); + pending_cond_destroy(mt); + pending_mutex_destroy(mt); + free_pending_args(mt); + pthread_setcancelstate(state, NULL); + return 1; + } + + cache_unlock(mc); + master_source_unlock(ap->entry); + + pthread_cleanup_push(free_pending_args, mt); + pthread_cleanup_push(pending_mutex_destroy, mt); + pthread_cleanup_push(pending_cond_destroy, mt); + pthread_cleanup_push(pending_mutex_unlock, mt); + pthread_setcancelstate(state, NULL); + + mt->signaled = 0; + while (!mt->signaled) { + clock_gettime(CLOCK_MONOTONIC, &wait); + wait.tv_sec += 2; + status = pthread_cond_timedwait(&mt->cond, &mt->mutex, &wait); + if (status && status != ETIMEDOUT) + fatal(status); + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + return 0; +} + +static void mount_send_fail(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *mt = arg; + struct autofs_point *ap = mt->ap; + ops->send_fail(ap->logopt, mt->ioctlfd, mt->wait_queue_token, -ENOENT); + ops->close(ap->logopt, mt->ioctlfd); +} + +static void *do_mount_direct(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *args, mt; + struct autofs_point *ap; + struct stat st; + int status, state; + + args = (struct pending_args *) arg; + + pending_mutex_lock(args); + + memcpy(&mt, args, sizeof(struct pending_args)); + + ap = mt.ap; + + set_thread_mount_request_log_id(&mt); + + args->signaled = 1; + status = pthread_cond_signal(&args->cond); + if (status) + fatal(status); + + pending_mutex_unlock(args); + + pthread_cleanup_push(mount_send_fail, &mt); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + status = fstat(mt.ioctlfd, &st); + if (status == -1) { + error(ap->logopt, + "can't stat direct mount trigger %s", mt.name); + ops->send_fail(ap->logopt, + mt.ioctlfd, mt.wait_queue_token, -ENOENT); + ops->close(ap->logopt, mt.ioctlfd); + pthread_setcancelstate(state, NULL); + pthread_exit(NULL); + } + + status = stat(mt.name, &st); + if (status != 0 || !S_ISDIR(st.st_mode) || st.st_dev != mt.dev) { + error(ap->logopt, + "direct trigger not valid or already mounted %s", + mt.name); + ops->send_ready(ap->logopt, mt.ioctlfd, mt.wait_queue_token); + ops->close(ap->logopt, mt.ioctlfd); + pthread_setcancelstate(state, NULL); + pthread_exit(NULL); + } + + pthread_setcancelstate(state, NULL); + + info(ap->logopt, "attempting to mount entry %s", mt.name); + + set_tsd_user_vars(ap->logopt, mt.uid, mt.gid); + + status = lookup_nss_mount(ap, NULL, mt.name, mt.len); + /* + * Direct mounts are always a single mount. If it fails there's + * nothing to undo so just complain + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + if (status) { + struct mapent *me; + struct statfs fs; + unsigned int close_fd = 0; + + if (statfs(mt.name, &fs) == -1 || + (fs.f_type == AUTOFS_SUPER_MAGIC && + !master_find_submount(ap, mt.name))) + close_fd = 1; + cache_writelock(mt.mc); + if ((me = cache_lookup_distinct(mt.mc, mt.name))) { + /* + * Careful here, we need to leave the file handle open + * for direct mount multi-mounts with no real mount at + * their base so they will be expired. + */ + if (close_fd && me == me->multi) + close_fd = 0; + if (!close_fd) + me->ioctlfd = mt.ioctlfd; + } + ops->send_ready(ap->logopt, mt.ioctlfd, mt.wait_queue_token); + cache_unlock(mt.mc); + if (close_fd) + ops->close(ap->logopt, mt.ioctlfd); + info(ap->logopt, "mounted %s", mt.name); + } else { + /* TODO: get mount return status from lookup_nss_mount */ + ops->send_fail(ap->logopt, + mt.ioctlfd, mt.wait_queue_token, -ENOENT); + ops->close(ap->logopt, mt.ioctlfd); + info(ap->logopt, "failed to mount %s", mt.name); + } + pthread_setcancelstate(state, NULL); + + pthread_cleanup_pop(0); + + return NULL; +} + +int handle_packet_missing_direct(struct autofs_point *ap, autofs_packet_missing_direct_t *pkt) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct map_source *map; + struct mapent_cache *mc = NULL; + struct mapent *me = NULL; + pthread_t thid; + struct pending_args *mt; + char buf[MAX_ERR_BUF]; + int status = 0; + struct timespec wait; + int ioctlfd, len, state; + unsigned int kver_major = get_kver_major(); + unsigned int kver_minor = get_kver_minor(); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + master_mutex_lock(); + + /* + * If our parent is a direct or offset mount that has been + * covered by a mount and another lookup occurs after the + * mount but before the device and inode are set in the + * cache entry we will not be able to find the mapent. So + * we must take the source writelock to ensure the parent + * has mount is complete before we look for the entry. + * + * Since the vfs-automount kernel changes we can now block + * on covered mounts during mount tree construction so a + * write lock is no longer needed. So we now can handle a + * wider class of recursively define mount lookups. + */ + if (kver_major > 5 || (kver_major == 5 && kver_minor > 1)) + master_source_readlock(ap->entry); + else + master_source_writelock(ap->entry); + map = ap->entry->maps; + while (map) { + /* + * Only consider map sources that have been read since + * the map entry was last updated. + */ + if (ap->entry->age > map->age) { + map = map->next; + continue; + } + + mc = map->mc; + cache_readlock(mc); + me = cache_lookup_ino(mc, pkt->dev, pkt->ino); + if (me) + break; + cache_unlock(mc); + map = map->next; + } + + if (!me) { + /* + * Shouldn't happen as the kernel is telling us + * someone has walked on our mount point. + */ + logerr("can't find map entry for (%lu,%lu)", + (unsigned long) pkt->dev, (unsigned long) pkt->ino); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 1; + } + + if (me->ioctlfd != -1) { + /* Maybe someone did a manual umount, clean up ! */ + close(me->ioctlfd); + me->ioctlfd = -1; + } + ops->open(ap->logopt, &ioctlfd, me->dev, me->key); + + if (ioctlfd == -1) { + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + crit(ap->logopt, "failed to create ioctl fd for %s", me->key); + /* TODO: how do we clear wait q in kernel ?? */ + return 1; + } + + debug(ap->logopt, "token %ld, name %s, request pid %u", + (unsigned long) pkt->wait_queue_token, me->key, pkt->pid); + + /* Ignore packet if we're trying to shut down */ + if (ap->shutdown || ap->state == ST_SHUTDOWN_FORCE) { + ops->send_fail(ap->logopt, + ioctlfd, pkt->wait_queue_token, -ENOENT); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + + /* Check if we recorded a mount fail for this key */ + if (me->status >= monotonic_time(NULL)) { + ops->send_fail(ap->logopt, + ioctlfd, pkt->wait_queue_token, -ENOENT); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + + len = strlen(me->key); + if (len >= PATH_MAX) { + error(ap->logopt, "direct mount path too long %s", me->key); + ops->send_fail(ap->logopt, + ioctlfd, pkt->wait_queue_token, -ENAMETOOLONG); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + + mt = malloc(sizeof(struct pending_args)); + if (!mt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + ops->send_fail(ap->logopt, + ioctlfd, pkt->wait_queue_token, -ENOMEM); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + memset(mt, 0, sizeof(struct pending_args)); + + pending_cond_init(mt); + + status = pthread_mutex_init(&mt->mutex, NULL); + if (status) + fatal(status); + + pending_mutex_lock(mt); + + mt->ap = ap; + mt->ioctlfd = ioctlfd; + mt->mc = mc; + strcpy(mt->name, me->key); + mt->len = len; + mt->dev = me->dev; + mt->type = NFY_MOUNT; + mt->uid = pkt->uid; + mt->gid = pkt->gid; + mt->pid = pkt->pid; + mt->wait_queue_token = pkt->wait_queue_token; + + status = pthread_create(&thid, &th_attr_detached, do_mount_direct, mt); + if (status) { + error(ap->logopt, "missing mount thread create failed"); + ops->send_fail(ap->logopt, + ioctlfd, pkt->wait_queue_token, -status); + ops->close(ap->logopt, ioctlfd); + cache_unlock(mc); + master_source_unlock(ap->entry); + master_mutex_unlock(); + pending_mutex_unlock(mt); + pending_cond_destroy(mt); + pending_mutex_destroy(mt); + free_pending_args(mt); + pthread_setcancelstate(state, NULL); + return 1; + } + + cache_unlock(mc); + master_source_unlock(ap->entry); + + master_mutex_unlock(); + + pthread_cleanup_push(free_pending_args, mt); + pthread_cleanup_push(pending_mutex_destroy, mt); + pthread_cleanup_push(pending_cond_destroy, mt); + pthread_cleanup_push(pending_mutex_unlock, mt); + pthread_setcancelstate(state, NULL); + + mt->signaled = 0; + while (!mt->signaled) { + clock_gettime(CLOCK_MONOTONIC, &wait); + wait.tv_sec += 2; + status = pthread_cond_timedwait(&mt->cond, &mt->mutex, &wait); + if (status && status != ETIMEDOUT) + fatal(status); + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + return 0; +} + diff --git a/daemon/flag.c b/daemon/flag.c new file mode 100644 index 0000000..db9a4bd --- /dev/null +++ b/daemon/flag.c @@ -0,0 +1,192 @@ +/* ----------------------------------------------------------------------- * + * + * flag.c - autofs flag file management + * + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +#define MAX_PIDSIZE 20 +#define FLAG_FILE AUTOFS_FLAG_DIR "/autofs-running" + +/* Flag for already existing flag file. */ +static int we_created_flagfile = 0; + +/* file descriptor of flag file */ +static int fd = -1; + +static int flag_is_owned(int fd) +{ + int pid = 0, tries = 3; + + while (tries--) { + char pidbuf[MAX_PIDSIZE + 1]; + int got; + + lseek(fd, 0, SEEK_SET); + got = read(fd, pidbuf, MAX_PIDSIZE); + /* + * We add a terminator to the pid to verify write complete. + * If the write isn't finished in 300 milliseconds then it's + * probably a stale lock file. + */ + if (got > 0 && pidbuf[got - 1] == '\n') { + sscanf(pidbuf, "%d", &pid); + break; + } else { + struct timespec t = { 0, 100000000 }; + struct timespec r; + + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + + continue; + } + } + + /* Stale flagfile */ + if (!tries) + return 0; + + if (pid) { + int ret; + + ret = kill(pid, 0); + /* + * If lock file exists but is not owned by a process + * we return unowned status so we can get rid of it + * and continue. + */ + if (ret == -1 && errno == ESRCH) + return 0; + } else { + /* + * Odd, no pid in file - so what should we do? + * Assume something bad happened to owner and + * return unowned status. + */ + return 0; + } + + return 1; +} + +/* Remove flag file. */ +void release_flag_file(void) +{ + if (fd > 0) { + close(fd); + fd = -1; + } + + if (we_created_flagfile) { + unlink(FLAG_FILE); + we_created_flagfile = 0; + } +} + +/* * Try to create flag file */ +int aquire_flag_file(void) +{ + char linkf[PATH_MAX]; + size_t len; + + len = snprintf(linkf, sizeof(linkf), "%s.%d", FLAG_FILE, getpid()); + if (len >= sizeof(linkf)) + /* Didn't acquire it */ + return 0; + + /* + * Repeat until it was us who made the link or we find the + * flag file already exists. If an unexpected error occurs + * we return 0 claiming the flag file exists which may not + * really be the case. + */ + while (!we_created_flagfile) { + int errsv, i, j; + + i = open_fd_mode(linkf, O_WRONLY|O_CREAT, 0); + if (i < 0) { + release_flag_file(); + return 0; + } + close(i); + + j = link(linkf, FLAG_FILE); + errsv = errno; + + (void) unlink(linkf); + + if (j < 0 && errsv != EEXIST) { + release_flag_file(); + return 0; + } + + fd = open_fd(FLAG_FILE, O_RDWR); + if (fd < 0) { + /* Maybe the file was just deleted? */ + if (errno == ENOENT) + continue; + release_flag_file(); + return 0; + } + + if (j == 0) { + char pidbuf[MAX_PIDSIZE + 1]; + int pidlen; + + pidlen = sprintf(pidbuf, "%d\n", getpid()); + if (write(fd, pidbuf, pidlen) != pidlen) { + release_flag_file(); + return 0; + } + + we_created_flagfile = 1; + } else { + /* + * Someone else made the link. + * If the flag file is not owned by anyone clean + * it up and try again, otherwise return fail. + */ + if (!flag_is_owned(fd)) { + close(fd); + fd = -1; + unlink(FLAG_FILE); + continue; + } + + release_flag_file(); + return 0; + } + + close(fd); + fd = -1; + } + + return 1; +} + diff --git a/daemon/indirect.c b/daemon/indirect.c new file mode 100644 index 0000000..ffb11b8 --- /dev/null +++ b/daemon/indirect.c @@ -0,0 +1,903 @@ +/* ----------------------------------------------------------------------- * + * + * indirect.c - Linux automounter indirect mount handling + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge + * Copyright 2001-2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INCLUDE_PENDING_FUNCTIONS +#include "automount.h" + +/* Attribute to create detached thread */ +extern pthread_attr_t th_attr_detached; + +static int unlink_mount_tree(struct autofs_point *ap, struct mnt_list *mnts) +{ + struct mnt_list *this; + int rv, ret; + pid_t pgrp = getpgrp(); + char spgrp[20]; + + sprintf(spgrp, "pgrp=%d", pgrp); + + ret = 1; + this = mnts; + while (this) { + if (strstr(this->opts, spgrp)) { + this = this->next; + continue; + } + + if (strcmp(this->fs_type, "autofs")) + rv = spawn_umount(ap->logopt, "-l", this->path, NULL); + else + rv = umount2(this->path, MNT_DETACH); + if (rv == -1) { + debug(ap->logopt, + "can't unlink %s from mount tree", this->path); + + switch (errno) { + case EINVAL: + warn(ap->logopt, + "bad superblock or not mounted"); + break; + + case ENOENT: + case EFAULT: + ret = 0; + warn(ap->logopt, "bad path for mount"); + break; + } + } + this = this->next; + } + return ret; +} + +static int do_mount_autofs_indirect(struct autofs_point *ap, const char *root) +{ + const char *str_indirect = mount_type_str(t_indirect); + struct ioctl_ops *ops = get_ioctl_ops(); + time_t timeout = get_exp_timeout(ap, ap->entry->maps); + char *options = NULL; + const char *hosts_map_name = "-hosts"; + const char *map_name = hosts_map_name; + const char *type; + struct stat st; + struct mnt_list *mnts; + int ret; + int err; + + /* If the map is being shared the exp_timeout can't be inherited + * from the map source since it may be different so the autofs + * point exp_runfreq must have already been set. + */ + if (ap->entry->maps->ref <= 1) + ap->exp_runfreq = (timeout + CHECK_RATIO - 1) / CHECK_RATIO; + + if (ops->version && !do_force_unlink) { + ap->flags |= MOUNT_FLAG_REMOUNT; + ret = try_remount(ap, NULL, t_indirect); + ap->flags &= ~MOUNT_FLAG_REMOUNT; + if (ret == 1) + return 0; + if (ret == 0) + return -1; + } else { + mnts = get_mnt_list(_PROC_MOUNTS, ap->path, 1); + if (mnts) { + ret = unlink_mount_tree(ap, mnts); + free_mnt_list(mnts); + if (!ret) { + error(ap->logopt, + "already mounted as other than autofs " + "or failed to unlink entry in tree"); + goto out_err; + } + } + } + + options = make_options_string(ap->path, ap->kpipefd, str_indirect); + if (!options) { + error(ap->logopt, "options string error"); + goto out_err; + } + + /* In case the directory doesn't exist, try to mkdir it */ + if (mkdir_path(root, 0555) < 0) { + if (errno != EEXIST && errno != EROFS) { + crit(ap->logopt, + "failed to create autofs directory %s", + root); + goto out_err; + } + /* If we recieve an error, and it's EEXIST or EROFS we know + the directory was not created. */ + ap->flags &= ~MOUNT_FLAG_DIR_CREATED; + } else { + /* No errors so the directory was successfully created */ + ap->flags |= MOUNT_FLAG_DIR_CREATED; + } + + type = ap->entry->maps->type; + if (!type || strcmp(ap->entry->maps->type, "hosts")) + map_name = ap->entry->maps->argv[0]; + + ret = mount(map_name, root, "autofs", MS_MGC_VAL, options); + if (ret) { + crit(ap->logopt, + "failed to mount autofs path %s at %s", ap->path, root); + goto out_rmdir; + } + + free(options); + options = NULL; + + ret = stat(root, &st); + if (ret == -1) { + crit(ap->logopt, + "failed to stat mount for autofs path %s", ap->path); + goto out_umount; + } + + if (ap->mode && (err = chmod(root, ap->mode))) + warn(ap->logopt, "failed to change mode of %s", ap->path); + + if (ops->open(ap->logopt, &ap->ioctlfd, st.st_dev, root)) { + crit(ap->logopt, + "failed to create ioctl fd for autofs path %s", ap->path); + goto out_umount; + } + + ap->dev = st.st_dev; /* Device number for mount point checks */ + + ops->timeout(ap->logopt, ap->ioctlfd, timeout); + if (ap->logopt & LOGOPT_DEBUG) + notify_mount_result(ap, root, timeout, str_indirect); + else + notify_mount_result(ap, ap->path, timeout, str_indirect); + + return 0; + +out_umount: + umount(root); +out_rmdir: + if (ap->flags & MOUNT_FLAG_DIR_CREATED) + rmdir(root); +out_err: + if (options) + free(options); + close(ap->state_pipe[0]); + close(ap->state_pipe[1]); + close(ap->pipefd); + close(ap->kpipefd); + + return -1; +} + +int mount_autofs_indirect(struct autofs_point *ap, const char *root) +{ + time_t now = monotonic_time(NULL); + int status; + int map; + + /* TODO: read map, determine map type is OK */ + if (lookup_nss_read_map(ap, NULL, now)) + lookup_prune_cache(ap, now); + else { + error(ap->logopt, "failed to read map for %s", ap->path); + return -1; + } + + status = do_mount_autofs_indirect(ap, root); + if (status < 0) + return -1; + + map = lookup_ghost(ap, root); + if (map & LKP_FAIL) { + if (map & LKP_DIRECT) { + error(ap->logopt, + "bad map format,found direct, " + "expected indirect exiting"); + } else { + error(ap->logopt, "failed to load map, exiting"); + } + /* TODO: Process cleanup ?? */ + return -1; + } + + if (map & LKP_NOTSUP) + ap->flags &= ~MOUNT_FLAG_GHOST; + + return 0; +} + +void close_mount_fds(struct autofs_point *ap) +{ + /* + * Since submounts look after themselves the parent never knows + * it needs to close the ioctlfd for offset mounts so we have + * to do it here. If the cache entry isn't found then there aren't + * any offset mounts. + */ + if (ap->submount) + lookup_source_close_ioctlfd(ap->parent, ap->path); + + close(ap->state_pipe[0]); + close(ap->state_pipe[1]); + ap->state_pipe[0] = -1; + ap->state_pipe[1] = -1; + + if (ap->pipefd >= 0) + close(ap->pipefd); + + if (ap->kpipefd >= 0) + close(ap->kpipefd); + + return; +} + +int umount_autofs_indirect(struct autofs_point *ap, const char *root) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + char buf[MAX_ERR_BUF]; + char mountpoint[PATH_MAX + 1]; + int rv, retries; + unsigned int unused; + + if (root) + strcpy(mountpoint, root); + else + strcpy(mountpoint, ap->path); + + /* If we are trying to shutdown make sure we can umount */ + rv = ops->askumount(ap->logopt, ap->ioctlfd, &unused); + if (rv == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("ioctl failed: %s", estr); + return 1; + } else if (!unused) { +#if defined(ENABLE_IGNORE_BUSY_MOUNTS) || defined(ENABLE_FORCED_SHUTDOWN) + if (!ap->shutdown) + return 1; + error(ap->logopt, "ask umount returned busy %s", ap->path); +#else + return 1; +#endif + } + + ops->close(ap->logopt, ap->ioctlfd); + ap->ioctlfd = -1; + sched_yield(); + + retries = UMOUNT_RETRIES; + while ((rv = umount(mountpoint)) == -1 && retries--) { + struct timespec tm = {0, 200000000}; + if (errno != EBUSY) + break; + nanosleep(&tm, NULL); + } + + if (rv == -1) { + switch (errno) { + case ENOENT: + case EINVAL: + error(ap->logopt, + "mount point %s does not exist", mountpoint); + close_mount_fds(ap); + return 0; + break; + case EBUSY: + debug(ap->logopt, + "mount point %s is in use", mountpoint); + if (ap->state == ST_SHUTDOWN_FORCE) { + close_mount_fds(ap); + goto force_umount; + } else { + /* + * If the umount returns EBUSY there may be + * a mount request in progress so we need to + * recover unless we have been explicitly + * asked to shutdown and configure option + * ENABLE_IGNORE_BUSY_MOUNTS is enabled. + */ +#ifdef ENABLE_IGNORE_BUSY_MOUNTS + if (ap->shutdown) { + close_mount_fds(ap); + return 0; + } +#endif + ops->open(ap->logopt, + &ap->ioctlfd, ap->dev, mountpoint); + if (ap->ioctlfd < 0) { + warn(ap->logopt, + "could not recover autofs path %s", + mountpoint); + close_mount_fds(ap); + return 0; + } + } + break; + case ENOTDIR: + error(ap->logopt, "mount point is not a directory"); + close_mount_fds(ap); + return 0; + break; + } + return 1; + } + + /* + * We have successfully umounted the mount so we now close + * the descriptors. The kernel end of the kernel pipe will + * have been put during the umount super block cleanup. + */ + close_mount_fds(ap); + +force_umount: + if (rv != 0) { + warn(ap->logopt, + "forcing umount of indirect mount %s", mountpoint); + rv = umount2(mountpoint, MNT_DETACH); + } else { + info(ap->logopt, "umounted indirect mount %s", mountpoint); + if (ap->submount) + rm_unwanted(ap, mountpoint, 1); + } + + return rv; +} + +static void mnts_cleanup(void *arg) +{ + struct mnt_list *mnts = (struct mnt_list *) arg; + free_mnt_list(mnts); + return; +} + +void *expire_proc_indirect(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct autofs_point *ap; + struct mapent *me = NULL; + struct mnt_list *mnts = NULL, *next; + struct expire_args *ea; + struct expire_args ec; + unsigned int now; + int offsets, submnts, count; + int retries; + int ioctlfd, cur_state; + int status, ret, left; + + ea = (struct expire_args *) arg; + + status = pthread_mutex_lock(&ea->mutex); + if (status) + fatal(status); + + ap = ec.ap = ea->ap; + now = ea->when; + ec.status = -1; + + ea->signaled = 1; + status = pthread_cond_signal(&ea->cond); + if (status) + fatal(status); + + status = pthread_mutex_unlock(&ea->mutex); + if (status) + fatal(status); + + pthread_cleanup_push(expire_cleanup, &ec); + + left = 0; + + /* Get a list of real mounts and expire them if possible */ + mnts = get_mnt_list(_PROC_MOUNTS, ap->path, 0); + pthread_cleanup_push(mnts_cleanup, mnts); + for (next = mnts; next; next = next->next) { + char *ind_key; + int ret; + + if (!strcmp(next->fs_type, "autofs")) { + /* + * If we have submounts check if this path lives below + * one of them and pass on the state change. + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + if (strstr(next->opts, "indirect")) + master_notify_submount(ap, next->path, ap->state); + else if (strstr(next->opts, "offset")) { + struct map_source *map; + struct mapent_cache *mc = NULL; + struct mapent *me = NULL; + struct stat st; + + /* It's got a mount, deal with in the outer loop */ + if (is_mounted(_PATH_MOUNTED, next->path, MNTS_REAL)) { + pthread_setcancelstate(cur_state, NULL); + continue; + } + + /* Don't touch submounts */ + if (master_find_submount(ap, next->path)) { + pthread_setcancelstate(cur_state, NULL); + continue; + } + + master_source_writelock(ap->entry); + + map = ap->entry->maps; + while (map) { + mc = map->mc; + cache_writelock(mc); + me = cache_lookup_distinct(mc, next->path); + if (me) + break; + cache_unlock(mc); + map = map->next; + } + + if (!mc || !me) { + master_source_unlock(ap->entry); + pthread_setcancelstate(cur_state, NULL); + continue; + } + + if (me->ioctlfd == -1) { + cache_unlock(mc); + master_source_unlock(ap->entry); + pthread_setcancelstate(cur_state, NULL); + continue; + } + + /* Check for manual umount */ + if (fstat(me->ioctlfd, &st) == -1 || + !count_mounts(ap, me->key, st.st_dev)) { + ops->close(ap->logopt, me->ioctlfd); + me->ioctlfd = -1; + } + + cache_unlock(mc); + master_source_unlock(ap->entry); + } + + pthread_setcancelstate(cur_state, NULL); + continue; + } + + if (ap->state == ST_EXPIRE || ap->state == ST_PRUNE) + pthread_testcancel(); + + /* + * If the mount corresponds to an offset trigger then + * the key is the path, otherwise it's the last component. + */ + ind_key = strrchr(next->path, '/'); + if (ind_key) + ind_key++; + + /* + * If me->key starts with a '/' and it's not an autofs + * filesystem it's a nested mount and we need to use + * the ioctlfd of the mount to send the expire. + * Otherwise it's a top level indirect mount (possibly + * with offsets in it) and we use the usual ioctlfd. + */ + pthread_cleanup_push(master_source_lock_cleanup, ap->entry); + master_source_readlock(ap->entry); + me = lookup_source_mapent(ap, next->path, LKP_DISTINCT); + if (!me && ind_key) + me = lookup_source_mapent(ap, ind_key, LKP_NORMAL); + pthread_cleanup_pop(1); + + ioctlfd = ap->ioctlfd; + if (me) { + if (*me->key == '/') + ioctlfd = me->ioctlfd; + cache_unlock(me->mc); + } + + debug(ap->logopt, "expire %s", next->path); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + ret = ops->expire(ap->logopt, ioctlfd, next->path, now); + if (ret) + left++; + pthread_setcancelstate(cur_state, NULL); + } + + /* + * If there are no more real mounts left we could still + * have some offset mounts with no '/' offset or symlinks + * so we need to umount or unlink them here. + */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + retries = (count_mounts(ap, ap->path, ap->dev) + 1); + while (retries--) { + ret = ops->expire(ap->logopt, ap->ioctlfd, ap->path, now); + if (ret) + left++; + } + pthread_setcancelstate(cur_state, NULL); + pthread_cleanup_pop(1); + + count = offsets = submnts = 0; + mnts = get_mnt_list(_PROC_MOUNTS, ap->path, 0); + pthread_cleanup_push(mnts_cleanup, mnts); + /* Are there any real mounts left */ + for (next = mnts; next; next = next->next) { + if (strcmp(next->fs_type, "autofs")) + count++; + else { + if (strstr(next->opts, "indirect")) + submnts++; + else + offsets++; + } + } + pthread_cleanup_pop(1); + + if (submnts) + info(ap->logopt, + "%d submounts remaining in %s", submnts, ap->path); + + /* + * EXPIRE_MULTI is synchronous, so we can be sure (famous last + * words) the umounts are done by the time we reach here + */ + if (count) + info(ap->logopt, "%d remaining in %s", count, ap->path); + + ec.status = left; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + pthread_cleanup_pop(1); + pthread_setcancelstate(cur_state, NULL); + + return NULL; +} + +static void expire_send_fail(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *mt = arg; + struct autofs_point *ap = mt->ap; + ops->send_fail(ap->logopt, + ap->ioctlfd, mt->wait_queue_token, -ENOENT); +} + +static void *do_expire_indirect(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *args, mt; + struct autofs_point *ap; + int status, state; + + args = (struct pending_args *) arg; + + pending_mutex_lock(args); + + memcpy(&mt, args, sizeof(struct pending_args)); + + ap = mt.ap; + + args->signaled = 1; + status = pthread_cond_signal(&args->cond); + if (status) + fatal(status); + + pending_mutex_unlock(args); + + pthread_cleanup_push(expire_send_fail, &mt); + + status = do_expire(mt.ap, mt.name, mt.len); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + if (status) + ops->send_fail(ap->logopt, + ap->ioctlfd, mt.wait_queue_token, -status); + else + ops->send_ready(ap->logopt, + ap->ioctlfd, mt.wait_queue_token); + pthread_setcancelstate(state, NULL); + + pthread_cleanup_pop(0); + + return NULL; +} + +int handle_packet_expire_indirect(struct autofs_point *ap, autofs_packet_expire_indirect_t *pkt) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *mt; + char buf[MAX_ERR_BUF]; + pthread_t thid; + struct timespec wait; + int status, state; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + debug(ap->logopt, "token %ld, name %s", + (unsigned long) pkt->wait_queue_token, pkt->name); + + mt = malloc(sizeof(struct pending_args)); + if (!mt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("malloc: %s", estr); + ops->send_fail(ap->logopt, + ap->ioctlfd, pkt->wait_queue_token, -ENOMEM); + pthread_setcancelstate(state, NULL); + return 1; + } + + pending_cond_init(mt); + + status = pthread_mutex_init(&mt->mutex, NULL); + if (status) + fatal(status); + + mt->ap = ap; + strncpy(mt->name, pkt->name, pkt->len); + mt->name[pkt->len] = '\0'; + mt->len = pkt->len; + mt->wait_queue_token = pkt->wait_queue_token; + + pending_mutex_lock(mt); + + status = pthread_create(&thid, &th_attr_detached, do_expire_indirect, mt); + if (status) { + error(ap->logopt, "expire thread create failed"); + ops->send_fail(ap->logopt, + ap->ioctlfd, pkt->wait_queue_token, -status); + pending_mutex_unlock(mt); + pending_cond_destroy(mt); + pending_mutex_destroy(mt); + free_pending_args(mt); + pthread_setcancelstate(state, NULL); + return 1; + } + + pthread_cleanup_push(free_pending_args, mt); + pthread_cleanup_push(pending_mutex_destroy, mt); + pthread_cleanup_push(pending_cond_destroy, mt); + pthread_cleanup_push(pending_mutex_unlock, mt); + pthread_setcancelstate(state, NULL); + + mt->signaled = 0; + while (!mt->signaled) { + clock_gettime(CLOCK_MONOTONIC, &wait); + wait.tv_sec += 2; + status = pthread_cond_timedwait(&mt->cond, &mt->mutex, &wait); + if (status && status != ETIMEDOUT) + fatal(status); + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + return 0; +} + +static void mount_send_fail(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *mt = arg; + struct autofs_point *ap = mt->ap; + ops->send_fail(ap->logopt, + ap->ioctlfd, mt->wait_queue_token, -ENOENT); +} + +static void *do_mount_indirect(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct pending_args *args, mt; + struct autofs_point *ap; + char buf[PATH_MAX + 1]; + struct stat st; + int len, status, state; + + args = (struct pending_args *) arg; + + pending_mutex_lock(args); + + memcpy(&mt, args, sizeof(struct pending_args)); + + ap = mt.ap; + + set_thread_mount_request_log_id(&mt); + + args->signaled = 1; + status = pthread_cond_signal(&args->cond); + if (status) + fatal(status); + + pending_mutex_unlock(args); + + pthread_cleanup_push(mount_send_fail, &mt); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + len = ncat_path(buf, sizeof(buf), ap->path, mt.name, mt.len); + if (!len) { + crit(ap->logopt, "path to be mounted is to long"); + ops->send_fail(ap->logopt, + ap->ioctlfd, mt.wait_queue_token, + -ENAMETOOLONG); + pthread_setcancelstate(state, NULL); + pthread_exit(NULL); + } + + status = lstat(buf, &st); + if (status != -1 && !(S_ISDIR(st.st_mode) && st.st_dev == mt.dev)) { + error(ap->logopt, + "indirect trigger not valid or already mounted %s", buf); + ops->send_ready(ap->logopt, ap->ioctlfd, mt.wait_queue_token); + pthread_setcancelstate(state, NULL); + pthread_exit(NULL); + } + + pthread_setcancelstate(state, NULL); + + info(ap->logopt, "attempting to mount entry %s", buf); + + set_tsd_user_vars(ap->logopt, mt.uid, mt.gid); + + status = lookup_nss_mount(ap, NULL, mt.name, mt.len); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + if (status) { + ops->send_ready(ap->logopt, + ap->ioctlfd, mt.wait_queue_token); + info(ap->logopt, "mounted %s", buf); + } else { + /* TODO: get mount return status from lookup_nss_mount */ + ops->send_fail(ap->logopt, + ap->ioctlfd, mt.wait_queue_token, -ENOENT); + info(ap->logopt, "failed to mount %s", buf); + } + pthread_setcancelstate(state, NULL); + + pthread_cleanup_pop(0); + + return NULL; +} + +int handle_packet_missing_indirect(struct autofs_point *ap, autofs_packet_missing_indirect_t *pkt) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + pthread_t thid; + char buf[MAX_ERR_BUF]; + struct pending_args *mt; + struct timespec wait; + struct mapent *me; + int status, state; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + + master_mutex_lock(); + + debug(ap->logopt, "token %ld, name %s, request pid %u", + (unsigned long) pkt->wait_queue_token, pkt->name, pkt->pid); + + /* Ignore packet if we're trying to shut down */ + if (ap->shutdown || ap->state == ST_SHUTDOWN_FORCE) { + ops->send_fail(ap->logopt, + ap->ioctlfd, pkt->wait_queue_token, -ENOENT); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, pkt->name, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + ops->send_fail(ap->logopt, ap->ioctlfd, + pkt->wait_queue_token, -ENOENT); + cache_unlock(me->mc); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 0; + } + cache_unlock(me->mc); + } + + mt = malloc(sizeof(struct pending_args)); + if (!mt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("malloc: %s", estr); + ops->send_fail(ap->logopt, + ap->ioctlfd, pkt->wait_queue_token, -ENOMEM); + master_mutex_unlock(); + pthread_setcancelstate(state, NULL); + return 1; + } + memset(mt, 0, sizeof(struct pending_args)); + + pending_cond_init(mt); + + status = pthread_mutex_init(&mt->mutex, NULL); + if (status) + fatal(status); + + pending_mutex_lock(mt); + + mt->ap = ap; + strncpy(mt->name, pkt->name, pkt->len); + mt->name[pkt->len] = '\0'; + mt->len = pkt->len; + mt->dev = pkt->dev; + mt->uid = pkt->uid; + mt->gid = pkt->gid; + mt->pid = pkt->pid; + mt->wait_queue_token = pkt->wait_queue_token; + + status = pthread_create(&thid, &th_attr_detached, do_mount_indirect, mt); + if (status) { + error(ap->logopt, "expire thread create failed"); + ops->send_fail(ap->logopt, + ap->ioctlfd, pkt->wait_queue_token, -status); + master_mutex_unlock(); + pending_mutex_unlock(mt); + pending_cond_destroy(mt); + pending_mutex_destroy(mt); + free_pending_args(mt); + pthread_setcancelstate(state, NULL); + return 1; + } + + master_mutex_unlock(); + + pthread_cleanup_push(free_pending_args, mt); + pthread_cleanup_push(pending_mutex_destroy, mt); + pthread_cleanup_push(pending_cond_destroy, mt); + pthread_cleanup_push(pending_mutex_unlock, mt); + pthread_setcancelstate(state, NULL); + + mt->signaled = 0; + while (!mt->signaled) { + clock_gettime(CLOCK_MONOTONIC, &wait); + wait.tv_sec += 2; + status = pthread_cond_timedwait(&mt->cond, &mt->mutex, &wait); + if (status && status != ETIMEDOUT) + fatal(status); + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + return 0; +} + diff --git a/daemon/lookup.c b/daemon/lookup.c new file mode 100644 index 0000000..583d3d3 --- /dev/null +++ b/daemon/lookup.c @@ -0,0 +1,1563 @@ +/* ----------------------------------------------------------------------- * + * + * lookup.c - API layer to implement nsswitch semantics for map reading + * and mount lookups. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include "automount.h" +#include "nsswitch.h" + +static void nsslist_cleanup(void *arg) +{ + struct list_head *nsslist = (struct list_head *) arg; + if (!list_empty(nsslist)) + free_sources(nsslist); + return; +} + +static int do_read_master(struct master *master, char *type, time_t age) +{ + struct lookup_mod *lookup; + const char *argv[2]; + int argc; + int status; + + argc = 1; + argv[0] = master->name; + argv[1] = NULL; + + status = open_lookup(type, "", NULL, argc, argv, &lookup); + if (status != NSS_STATUS_SUCCESS) + return status; + + status = lookup->lookup_read_master(master, age, lookup->context); + + close_lookup(lookup); + + return status; +} + +static char *find_map_path(struct autofs_point *ap, struct map_source *map) +{ + const char *mname = map->argv[0]; + unsigned int mlen = strlen(mname); + char *tok, *ptr = NULL; + char *path = NULL; + char *search_path; + struct stat st; + + /* + * This is different to the way it is in amd. + * autofs will always try to locate maps in AUTOFS_MAP_DIR + * but amd has no default and will not find a file map that + * isn't a full path when no search_path is configured, either + * in the mount point or global configuration. + */ + search_path = strdup(AUTOFS_MAP_DIR); + if (map->flags & MAP_FLAG_FORMAT_AMD) { + struct autofs_point *pap = ap; + char *tmp; + /* + * Make sure we get search_path from the root of the + * mount tree, if one is present in the configuration. + * Again different from amd, which ignores the submount + * case. + */ + while (pap->parent) + pap = pap->parent; + tmp = conf_amd_get_search_path(pap->path); + if (tmp) { + if (search_path) + free(search_path); + search_path = tmp; + } + } + if (!search_path) + return NULL; + + tok = strtok_r(search_path, ":", &ptr); + while (tok) { + char *this = malloc(strlen(tok) + mlen + 2); + if (!this) { + free(search_path); + return NULL; + } + strcpy(this, tok); + strcat(this, "/"); + strcat(this, mname); + if (!stat(this, &st)) { + path = this; + break; + } + free(this); + tok = strtok_r(NULL, ":", &ptr); + } + + free(search_path); + return path; +} + +static int read_master_map(struct master *master, char *type, time_t age) +{ + unsigned int logopt = master->logopt; + char *path, *save_name; + int result; + + if (strcasecmp(type, "files")) { + return do_read_master(master, type, age); + } + + /* + * This is a special case as we need to append the + * normal location to the map name. + * note: It's invalid to specify a relative path. + */ + + if (strchr(master->name, '/')) { + error(logopt, "relative path invalid in files map name"); + return NSS_STATUS_NOTFOUND; + } + + path = malloc(strlen(AUTOFS_MAP_DIR) + strlen(master->name) + 2); + if (!path) + return NSS_STATUS_UNKNOWN; + + strcpy(path, AUTOFS_MAP_DIR); + strcat(path, "/"); + strcat(path, master->name); + + save_name = master->name; + master->name = path; + + result = do_read_master(master, type, age); + + master->name = save_name; + free(path); + + return result; +} + +int lookup_nss_read_master(struct master *master, time_t age) +{ + unsigned int logopt = master->logopt; + struct list_head nsslist; + struct list_head *head, *p; + int result = NSS_STATUS_UNKNOWN; + + /* If it starts with a '/' it has to be a file or LDAP map */ + if (*master->name == '/') { + if (*(master->name + 1) == '/') { + debug(logopt, "reading master ldap %s", master->name); + result = do_read_master(master, "ldap", age); + } else { + debug(logopt, "reading master file %s", master->name); + result = do_read_master(master, "file", age); + } + + if (result == NSS_STATUS_UNAVAIL) + master->read_fail = 1; + + return result; + } else { + char *name = master->name; + char *tmp; + + /* Old style name specification will remain I think. */ + tmp = strchr(name, ':'); + if (tmp) { + char source[10]; + + memset(source, 0, 10); + if ((!strncmp(name, "file", 4) && + (name[4] == ',' || name[4] == ':')) || + (!strncmp(name, "yp", 2) && + (name[2] == ',' || name[2] == ':')) || + (!strncmp(name, "nis", 3) && + (name[3] == ',' || name[3] == ':')) || + (!strncmp(name, "nisplus", 7) && + (name[7] == ',' || name[7] == ':')) || + (!strncmp(name, "ldap", 4) && + (name[4] == ',' || name[4] == ':')) || + (!strncmp(name, "ldaps", 5) && + (name[5] == ',' || name[5] == ':')) || + (!strncmp(name, "sss", 3) || + (name[3] == ',' || name[3] == ':')) || + (!strncmp(name, "dir", 3) && + (name[3] == ',' || name[3] == ':'))) { + strncpy(source, name, tmp - name); + + /* + * If it's an ldap map leave the source in the + * name so the lookup module can work out if + * ldaps has been requested. + */ + if (strncmp(name, "ldap", 4)) { + master->name = tmp + 1; + debug(logopt, "reading master %s %s", + source, master->name); + } else { + master->name = name; + debug(logopt, "reading master %s %s", + source, tmp + 1); + } + + result = do_read_master(master, source, age); + master->name = name; + + if (result == NSS_STATUS_UNAVAIL) + master->read_fail = 1; + + return result; + } + } + } + + INIT_LIST_HEAD(&nsslist); + + result = nsswitch_parse(&nsslist); + if (result) { + if (!list_empty(&nsslist)) + free_sources(&nsslist); + error(logopt, "can't to read name service switch config."); + return NSS_STATUS_UNAVAIL; + } + + /* First one gets it */ + result = NSS_STATUS_SUCCESS; + head = &nsslist; + list_for_each(p, head) { + struct nss_source *this; + int status; + + this = list_entry(p, struct nss_source, list); + + if (strncmp(this->source, "files", 5) && + strncmp(this->source, "nis", 3) && + strncmp(this->source, "nisplus", 7) && + strncmp(this->source, "ldap", 4) && + strncmp(this->source, "sss", 3)) + continue; + + debug(logopt, + "reading master %s %s", this->source, master->name); + + result = read_master_map(master, this->source, age); + + /* + * If the name of the master map hasn't been explicitly + * configured and we're not reading an included master map + * then we're using auto.master as the default. Many setups + * also use auto_master as the default master map so we + * check for this map when auto.master isn't found. + */ + if (result != NSS_STATUS_SUCCESS && + !master->depth && !defaults_master_set()) { + char *tmp = strchr(master->name, '.'); + if (tmp) { + debug(logopt, + "%s not found, replacing '.' with '_'", + master->name); + *tmp = '_'; + result = read_master_map(master, this->source, age); + if (result != NSS_STATUS_SUCCESS) + *tmp = '.'; + } + } + + /* We've been instructed to move onto the next source */ + if (result == NSS_STATUS_TRYAGAIN) { + result = NSS_STATUS_SUCCESS; + continue; + } + + if (result == NSS_STATUS_UNKNOWN || + result == NSS_STATUS_NOTFOUND) { + debug(logopt, "no map - continuing to next source"); + result = NSS_STATUS_SUCCESS; + continue; + } + + if (result == NSS_STATUS_UNAVAIL) + master->read_fail = 1; + + status = check_nss_result(this, result); + if (status >= 0) { + free_sources(&nsslist); + return status; + } + } + + if (!list_empty(&nsslist)) + free_sources(&nsslist); + + return result; +} + +static int do_read_map(struct autofs_point *ap, struct map_source *map, time_t age) +{ + struct lookup_mod *lookup; + int status; + + lookup = NULL; + master_source_writelock(ap->entry); + if (!map->lookup) { + status = open_lookup(map->type, "", map->format, + map->argc, map->argv, &lookup); + if (status != NSS_STATUS_SUCCESS) { + master_source_unlock(ap->entry); + debug(ap->logopt, + "lookup module %s open failed", map->type); + return status; + } + map->lookup = lookup; + } else { + lookup = map->lookup; + status = lookup->lookup_reinit(map->format, + map->argc, map->argv, + &lookup->context); + if (status) + warn(ap->logopt, + "lookup module %s reinit failed", map->type); + } + master_source_unlock(ap->entry); + + if (!map->stale) + return NSS_STATUS_SUCCESS; + + master_source_current_wait(ap->entry); + ap->entry->current = map; + + status = lookup->lookup_read_map(ap, age, lookup->context); + + if (status != NSS_STATUS_SUCCESS) + map->stale = 0; + + /* + * For maps that don't support enumeration return success + * and do whatever we must to have autofs function with an + * empty map entry cache. + * + * For indirect maps that use the browse option, when the + * server is unavailable continue as best we can with + * whatever we have in the cache, if anything. + */ + if (status == NSS_STATUS_UNKNOWN || + (ap->type == LKP_INDIRECT && status == NSS_STATUS_UNAVAIL)) + return NSS_STATUS_SUCCESS; + + return status; +} + +static int read_file_source_instance(struct autofs_point *ap, struct map_source *map, time_t age) +{ + struct map_source *instance; + char src_file[] = "file"; + char src_prog[] = "program"; + struct stat st; + char *type, *format; + + if (stat(map->argv[0], &st) == -1) { + warn(ap->logopt, "file map %s not found", map->argv[0]); + return NSS_STATUS_NOTFOUND; + } + + if (!S_ISREG(st.st_mode)) + return NSS_STATUS_NOTFOUND; + + if (st.st_mode & __S_IEXEC) + type = src_prog; + else + type = src_file; + + format = map->format; + + instance = master_find_source_instance(map, type, format, 0, NULL); + if (!instance) { + int argc = map->argc; + const char **argv = map->argv; + instance = master_add_source_instance(map, type, format, age, argc, argv); + if (!instance) + return NSS_STATUS_UNAVAIL; + instance->recurse = map->recurse; + instance->depth = map->depth; + } + instance->stale = map->stale; + + return do_read_map(ap, instance, age); +} + +static int read_source_instance(struct autofs_point *ap, struct map_source *map, const char *type, time_t age) +{ + struct map_source *instance; + const char *format; + + format = map->format; + + instance = master_find_source_instance(map, type, format, 0, NULL); + if (!instance) { + int argc = map->argc; + const char **argv = map->argv; + instance = master_add_source_instance(map, type, format, age, argc, argv); + if (!instance) + return NSS_STATUS_UNAVAIL; + instance->recurse = map->recurse; + instance->depth = map->depth; + } + instance->stale = map->stale; + + return do_read_map(ap, instance, age); +} + +static void argv_cleanup(void *arg) +{ + struct map_source *tmap = (struct map_source *) arg; + /* path is freed in free_argv */ + free_argv(tmap->argc, tmap->argv); + return; +} + +static int lookup_map_read_map(struct autofs_point *ap, + struct map_source *map, time_t age) +{ + char *path; + + if (!map->argv[0]) { + if (!strcmp(map->type, "hosts")) + return do_read_map(ap, map, age); + return NSS_STATUS_UNKNOWN; + } + + /* + * This is only called when map->type != NULL. + * We only need to look for a map if source type is + * file and the map name doesn't begin with a "/". + */ + if (strncmp(map->type, "file", 4)) + return do_read_map(ap, map, age); + + if (map->argv[0][0] == '/') + return do_read_map(ap, map, age); + + path = find_map_path(ap, map); + if (!path) + return NSS_STATUS_UNKNOWN; + + if (map->argc >= 1) { + if (map->argv[0]) + free((char *) map->argv[0]); + map->argv[0] = path; + } else { + error(ap->logopt, "invalid arguments for autofs_point"); + free(path); + return NSS_STATUS_UNKNOWN; + } + + return do_read_map(ap, map, age); +} + +static enum nsswitch_status read_map_source(struct nss_source *this, + struct autofs_point *ap, struct map_source *map, time_t age) +{ + enum nsswitch_status result; + struct map_source tmap; + char *path; + + if (strcasecmp(this->source, "files")) { + return read_source_instance(ap, map, this->source, age); + } + + /* + * autofs built-in map for nsswitch "files" is "file". + * This is a special case as we need to append the + * normal location to the map name. + * note: It's invalid to specify a relative path. + */ + + if (strchr(map->argv[0], '/')) { + error(ap->logopt, "relative path invalid in files map name"); + return NSS_STATUS_NOTFOUND; + } + + this->source[4] = '\0'; + tmap.flags = map->flags; + tmap.type = this->source; + tmap.format = map->format; + tmap.name = map->name; + tmap.lookup = map->lookup; + tmap.mc = map->mc; + tmap.instance = map->instance; + tmap.exp_timeout = map->exp_timeout; + tmap.recurse = map->recurse; + tmap.depth = map->depth; + tmap.stale = map->stale; + tmap.argc = 0; + tmap.argv = NULL; + + path = find_map_path(ap, map); + if (!path) + return NSS_STATUS_UNKNOWN; + + if (map->argc >= 1) { + tmap.argc = map->argc; + tmap.argv = copy_argv(map->argc, map->argv); + if (!tmap.argv) { + error(ap->logopt, "failed to copy args"); + free(path); + return NSS_STATUS_UNKNOWN; + } + if (tmap.argv[0]) + free((char *) tmap.argv[0]); + tmap.argv[0] = path; + } else { + error(ap->logopt, "invalid arguments for autofs_point"); + free(path); + return NSS_STATUS_UNKNOWN; + } + + pthread_cleanup_push(argv_cleanup, &tmap); + result = read_file_source_instance(ap, &tmap, age); + pthread_cleanup_pop(1); + + map->instance = tmap.instance; + + return result; +} + +int lookup_nss_read_map(struct autofs_point *ap, struct map_source *source, time_t age) +{ + struct master_mapent *entry = ap->entry; + struct list_head nsslist; + struct list_head *head, *p; + struct nss_source *this; + struct map_source *map; + enum nsswitch_status status; + unsigned int at_least_one = 0; + int result = 0; + + /* + * For each map source (ie. each entry for the mount + * point in the master map) do the nss lookup to + * locate the map and read it. + */ + if (source) + map = source; + else + map = entry->maps; + while (map) { + /* Is map source up to date or no longer valid */ + if (!map->stale || entry->age > map->age) { + map = map->next; + continue; + } + + if (map->type) { + if (!strncmp(map->type, "multi", 5)) + debug(ap->logopt, "reading multi map"); + else + debug(ap->logopt, + "reading map %s %s", + map->type, map->argv[0]); + result = lookup_map_read_map(ap, map, age); + map = map->next; + continue; + } + + /* If it starts with a '/' it has to be a file or LDAP map */ + if (map->argv && *map->argv[0] == '/') { + if (*(map->argv[0] + 1) == '/') { + char *tmp = strdup("ldap"); + if (!tmp) { + map = map->next; + continue; + } + map->type = tmp; + debug(ap->logopt, + "reading map %s %s", tmp, map->argv[0]); + result = do_read_map(ap, map, age); + } else { + debug(ap->logopt, + "reading map file %s", map->argv[0]); + result = read_file_source_instance(ap, map, age); + } + map = map->next; + continue; + } + + INIT_LIST_HEAD(&nsslist); + + pthread_cleanup_push(nsslist_cleanup, &nsslist); + status = nsswitch_parse(&nsslist); + pthread_cleanup_pop(0); + if (status) { + error(ap->logopt, + "can't to read name service switch config."); + result = 1; + break; + } + + pthread_cleanup_push(nsslist_cleanup, &nsslist); + head = &nsslist; + list_for_each(p, head) { + this = list_entry(p, struct nss_source, list); + + if (map->flags & MAP_FLAG_FORMAT_AMD && + !strcmp(this->source, "sss")) { + warn(ap->logopt, + "source sss is not available for amd maps."); + continue; + } + + debug(ap->logopt, + "reading map %s %s", this->source, map->argv[0]); + + result = read_map_source(this, ap, map, age); + if (result == NSS_STATUS_UNKNOWN) + continue; + + /* Don't try to update the map cache if it's unavailable */ + if (result == NSS_STATUS_UNAVAIL) + map->stale = 0; + + if (result == NSS_STATUS_SUCCESS) { + at_least_one = 1; + result = NSS_STATUS_TRYAGAIN; + } + + status = check_nss_result(this, result); + if (status >= 0) { + map = NULL; + break; + } + + result = NSS_STATUS_SUCCESS; + } + pthread_cleanup_pop(1); + + if (!map) + break; + + map = map->next; + } + + if (!result || at_least_one) + return 1; + + return 0; +} + +static char *make_browse_path(unsigned int logopt, + const char *root, const char *key, + const char *prefix) +{ + unsigned int l_prefix; + unsigned int k_len, r_len; + char *k_start; + char *path; + + k_start = (char *) key; + k_len = strlen(key); + l_prefix = 0; + + if (prefix) { + l_prefix = strlen(prefix); + + if (l_prefix > k_len) + return NULL; + + /* If the prefix doesn't match the beginning + * of the key this entry isn't a sub directory + * at this level. + */ + if (strncmp(key, prefix, l_prefix)) + return NULL; + + /* Directory entry starts following the prefix */ + k_start += l_prefix; + } + + /* No remaining "/" allowed here */ + if (strchr(k_start, '/')) + return NULL; + + r_len = strlen(root); + + if ((r_len + strlen(k_start)) > KEY_MAX_LEN) + return NULL; + + path = malloc(r_len + k_len + 2); + if (!path) { + warn(logopt, "failed to allocate full path"); + return NULL; + } + + sprintf(path, "%s/%s", root, k_start); + + return path; +} + +int lookup_ghost(struct autofs_point *ap, const char *root) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent_cache *mc; + struct mapent *me; + char buf[MAX_ERR_BUF]; + struct stat st; + char *fullpath; + int ret; + + if (!strcmp(ap->path, "/-")) + return LKP_FAIL | LKP_DIRECT; + + if (!(ap->flags & MOUNT_FLAG_GHOST)) + return LKP_INDIRECT; + + pthread_cleanup_push(master_source_lock_cleanup, entry); + master_source_readlock(entry); + map = entry->maps; + while (map) { + /* + * Only consider map sources that have been read since + * the map entry was last updated. + */ + if (entry->age > map->age) { + map = map->next; + continue; + } + + mc = map->mc; + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_readlock(mc); + me = cache_enumerate(mc, NULL); + while (me) { + /* + * Map entries that have been created in the cache + * due to a negative lookup shouldn't have directories + * created if they haven't already been created. + */ + if (!me->mapent) + goto next; + + /* Wildcard cannot be a browse directory and amd map + * keys may end with the wildcard. + */ + if (strchr(me->key, '*')) + goto next; + + /* This will also take care of amd "/defaults" entry as + * amd map keys are not allowd to start with "/" + */ + if (*me->key == '/') { + if (map->flags & MAP_FLAG_FORMAT_AMD) + goto next; + + /* It's a busy multi-mount - leave till next time */ + if (list_empty(&me->multi_list)) + error(ap->logopt, + "invalid key %s", me->key); + goto next; + } + + fullpath = make_browse_path(ap->logopt, + root, me->key, ap->pref); + if (!fullpath) + goto next; + + ret = stat(fullpath, &st); + if (ret == -1 && errno != ENOENT) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, "stat error %s", estr); + free(fullpath); + goto next; + } + + /* Directory already exists? */ + if (!ret) { + /* Shouldn't need this + me->dev = st.st_dev; + me->ino = st.st_ino; + */ + debug(ap->logopt, "me->dev %d me->ino %d", me->dev, me->ino); + free(fullpath); + goto next; + } + + ret = mkdir_path(fullpath, 0555); + if (ret < 0 && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + warn(ap->logopt, + "mkdir_path %s failed: %s", fullpath, estr); + free(fullpath); + goto next; + } + + if (stat(fullpath, &st) != -1) { + me->dev = st.st_dev; + me->ino = st.st_ino; + } + + free(fullpath); +next: + me = cache_enumerate(mc, me); + } + pthread_cleanup_pop(1); + map = map->next; + } + pthread_cleanup_pop(1); + + return LKP_INDIRECT; +} + +int do_lookup_mount(struct autofs_point *ap, struct map_source *map, const char *name, int name_len) +{ + struct lookup_mod *lookup; + int status; + + if (!map->lookup) { + status = open_lookup(map->type, "", + map->format, map->argc, map->argv, &lookup); + if (status != NSS_STATUS_SUCCESS) { + debug(ap->logopt, + "lookup module %s open failed", map->type); + return status; + } + map->lookup = lookup; + } + + lookup = map->lookup; + + master_source_current_wait(ap->entry); + ap->entry->current = map; + + status = lookup->lookup_mount(ap, name, name_len, lookup->context); + + return status; +} + +static int lookup_amd_instance(struct autofs_point *ap, + struct map_source *map, + const char *name, int name_len) +{ + struct map_source *instance; + struct amd_entry *entry; + const char *argv[2]; + const char **pargv = NULL; + int argc = 0; + struct mapent *me; + char *m_key; + + me = cache_lookup_distinct(map->mc, name); + if (!me || !me->multi) { + error(ap->logopt, "expected multi mount entry not found"); + return NSS_STATUS_UNKNOWN; + } + + m_key = malloc(strlen(ap->path) + strlen(me->multi->key) + 2); + if (!m_key) { + error(ap->logopt, + "failed to allocate storage for search key"); + return NSS_STATUS_UNKNOWN; + } + + strcpy(m_key, ap->path); + strcat(m_key, "/"); + strcat(m_key, me->multi->key); + entry = master_find_amdmount(ap, m_key); + free(m_key); + + if (!entry) { + error(ap->logopt, "expected amd mount entry not found"); + return NSS_STATUS_UNKNOWN; + } + + if (strcmp(entry->type, "host")) { + error(ap->logopt, "unexpected map type %s", entry->type); + return NSS_STATUS_UNKNOWN; + } + + if (entry->opts && *entry->opts) { + argv[0] = entry->opts; + argv[1] = NULL; + pargv = argv; + argc = 1; + } + + instance = master_find_source_instance(map, "hosts", "sun", argc, pargv); + /* If this is an nss map instance it may have an amd host map sub instance */ + if (!instance && map->instance) { + struct map_source *next = map->instance; + while (next) { + instance = master_find_source_instance(next, + "hosts", "sun", argc, pargv); + if (instance) + break; + next = next->next; + } + } + if (!instance) { + error(ap->logopt, "expected hosts map instance not found"); + return NSS_STATUS_UNKNOWN; + } + + return do_lookup_mount(ap, instance, name, name_len); +} + +static int lookup_name_file_source_instance(struct autofs_point *ap, struct map_source *map, const char *name, int name_len) +{ + struct map_source *instance; + char src_file[] = "file"; + char src_prog[] = "program"; + time_t age = monotonic_time(NULL); + struct stat st; + char *type, *format; + + if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD) + return lookup_amd_instance(ap, map, name, name_len); + + if (stat(map->argv[0], &st) == -1) { + debug(ap->logopt, "file map not found"); + return NSS_STATUS_NOTFOUND; + } + + if (!S_ISREG(st.st_mode)) + return NSS_STATUS_NOTFOUND; + + if (st.st_mode & __S_IEXEC) + type = src_prog; + else + type = src_file; + + format = map->format; + + instance = master_find_source_instance(map, type, format, 0, NULL); + if (!instance) { + int argc = map->argc; + const char **argv = map->argv; + instance = master_add_source_instance(map, type, format, age, argc, argv); + if (!instance) + return NSS_STATUS_NOTFOUND; + instance->recurse = map->recurse; + instance->depth = map->depth; + } + + return do_lookup_mount(ap, instance, name, name_len); +} + +static int lookup_name_source_instance(struct autofs_point *ap, struct map_source *map, const char *type, const char *name, int name_len) +{ + struct map_source *instance; + const char *format; + time_t age = monotonic_time(NULL); + + if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD) + return lookup_amd_instance(ap, map, name, name_len); + + format = map->format; + + instance = master_find_source_instance(map, type, format, 0, NULL); + if (!instance) { + int argc = map->argc; + const char **argv = map->argv; + instance = master_add_source_instance(map, type, format, age, argc, argv); + if (!instance) + return NSS_STATUS_NOTFOUND; + instance->recurse = map->recurse; + instance->depth = map->depth; + } + + return do_lookup_mount(ap, instance, name, name_len); +} + +static int do_name_lookup_mount(struct autofs_point *ap, + struct map_source *map, + const char *name, int name_len) +{ + char *path; + + if (!map->argv[0]) { + if (!strcmp(map->type, "hosts")) + return do_lookup_mount(ap, map, name, name_len); + return NSS_STATUS_UNKNOWN; + } + + if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD) + return lookup_amd_instance(ap, map, name, name_len); + + /* + * This is only called when map->type != NULL. + * We only need to look for a map if source type is + * file and the map name doesn't begin with a "/". + */ + if (strncmp(map->type, "file", 4)) + return do_lookup_mount(ap, map, name, name_len); + + if (map->argv[0][0] == '/') + return do_lookup_mount(ap, map, name, name_len); + + path = find_map_path(ap, map); + if (!path) + return NSS_STATUS_UNKNOWN; + + if (map->argc >= 1) { + if (map->argv[0]) + free((char *) map->argv[0]); + map->argv[0] = path; + } else { + error(ap->logopt, "invalid arguments for autofs_point"); + free(path); + return NSS_STATUS_UNKNOWN; + } + + return do_lookup_mount(ap, map, name, name_len); +} + +static enum nsswitch_status lookup_map_name(struct nss_source *this, + struct autofs_point *ap, struct map_source *map, + const char *name, int name_len) +{ + enum nsswitch_status result; + struct map_source tmap; + char *path; + + if (strcasecmp(this->source, "files")) + return lookup_name_source_instance(ap, map, + this->source, name, name_len); + + /* + * autofs build-in map for nsswitch "files" is "file". + * This is a special case as we need to append the + * normal location to the map name. + * note: we consider it invalid to specify a relative + * path. + */ + if (strchr(map->argv[0], '/')) { + error(ap->logopt, "relative path invalid in files map name"); + return NSS_STATUS_NOTFOUND; + } + + this->source[4] = '\0'; + tmap.flags = map->flags; + tmap.type = this->source; + tmap.format = map->format; + tmap.name = map->name; + tmap.mc = map->mc; + tmap.instance = map->instance; + tmap.exp_timeout = map->exp_timeout; + tmap.recurse = map->recurse; + tmap.depth = map->depth; + tmap.argc = 0; + tmap.argv = NULL; + + path = find_map_path(ap, map); + if (!path) + return NSS_STATUS_UNKNOWN; + + if (map->argc >= 1) { + tmap.argc = map->argc; + tmap.argv = copy_argv(map->argc, map->argv); + if (!tmap.argv) { + error(ap->logopt, "failed to copy args"); + free(path); + return NSS_STATUS_UNKNOWN; + } + if (tmap.argv[0]) + free((char *) tmap.argv[0]); + tmap.argv[0] = path; + } else { + error(ap->logopt, "invalid arguments for autofs_point"); + free(path); + return NSS_STATUS_UNKNOWN; + } + + result = lookup_name_file_source_instance(ap, &tmap, name, name_len); + + map->instance = tmap.instance; + + /* path is freed in free_argv */ + free_argv(tmap.argc, tmap.argv); + + return result; +} + +static void update_negative_cache(struct autofs_point *ap, struct map_source *source, const char *name) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent *me; + + /* Don't update negative cache for included maps */ + if (source && source->depth) + return; + + /* Don't update the wildcard */ + if (strlen(name) == 1 && *name == '*') + return; + + /* Have we recorded the lookup fail for negative caching? */ + me = lookup_source_mapent(ap, name, LKP_DISTINCT); + if (me) + /* + * Already exists in the cache, the mount fail updates + * will update negative timeout status. + */ + cache_unlock(me->mc); + else { + if (!defaults_disable_not_found_message()) { + /* This really should be a warning but the original + * request for this needed it to be unconditional. + * That produces, IMHO, unnecessary noise in the log + * so a configuration option has been added to provide + * the ability to turn it off. + */ + logmsg("key \"%s\" not found in map source(s).", name); + } + + /* Doesn't exist in any source, just add it somewhere */ + if (source) + map = source; + else + map = entry->maps; + if (map) { + time_t now = monotonic_time(NULL); + int rv = CHE_FAIL; + + cache_writelock(map->mc); + me = cache_lookup_distinct(map->mc, name); + if (me) + rv = cache_push_mapent(me, NULL); + else + rv = cache_update(map->mc, map, name, NULL, now); + if (rv != CHE_FAIL) { + me = cache_lookup_distinct(map->mc, name); + if (me) + me->status = now + ap->negative_timeout; + } + cache_unlock(map->mc); + } + } + return; +} + +int lookup_nss_mount(struct autofs_point *ap, struct map_source *source, const char *name, int name_len) +{ + struct master_mapent *entry = ap->entry; + struct list_head nsslist; + struct list_head *head, *p; + struct nss_source *this; + struct map_source *map; + enum nsswitch_status status; + int result = NSS_STATUS_UNKNOWN; + + /* + * For each map source (ie. each entry for the mount + * point in the master map) do the nss lookup to + * locate the map and lookup the name. + */ + pthread_cleanup_push(master_source_lock_cleanup, entry); + master_source_readlock(entry); + if (source) + map = source; + else + map = entry->maps; + while (map) { + /* + * Only consider map sources that have been read since + * the map entry was last updated. + */ + if (entry->age > map->age) { + status = NSS_STATUS_UNAVAIL; + map = map->next; + continue; + } + + sched_yield(); + + if (map->type) { + result = do_name_lookup_mount(ap, map, name, name_len); + if (result == NSS_STATUS_SUCCESS) + break; + + map = map->next; + continue; + } + + /* If it starts with a '/' it has to be a file or LDAP map */ + if (*map->argv[0] == '/') { + if (*(map->argv[0] + 1) == '/') { + char *tmp = strdup("ldap"); + if (!tmp) { + map = map->next; + status = NSS_STATUS_TRYAGAIN; + continue; + } + map->type = tmp; + result = do_lookup_mount(ap, map, name, name_len); + } else + result = lookup_name_file_source_instance(ap, map, name, name_len); + + if (result == NSS_STATUS_SUCCESS) + break; + + map = map->next; + continue; + } + + INIT_LIST_HEAD(&nsslist); + + status = nsswitch_parse(&nsslist); + if (status) { + error(ap->logopt, + "can't to read name service switch config."); + result = 1; + break; + } + + head = &nsslist; + list_for_each(p, head) { + this = list_entry(p, struct nss_source, list); + + if (map->flags & MAP_FLAG_FORMAT_AMD && + !strcmp(this->source, "sss")) { + warn(ap->logopt, + "source sss is not available for amd maps."); + result = NSS_STATUS_UNAVAIL; + continue; + } + + result = lookup_map_name(this, ap, map, name, name_len); + + if (result == NSS_STATUS_UNKNOWN) + continue; + + status = check_nss_result(this, result); + if (status >= 0) { + map = NULL; + break; + } + } + + if (!list_empty(&nsslist)) + free_sources(&nsslist); + + if (!map) + break; + + map = map->next; + } + if (ap->state != ST_INIT) + send_map_update_request(ap); + + /* + * The last source lookup will return NSS_STATUS_NOTFOUND if the + * map exits and the key has not been found but the map may also + * not exist in which case the key is also not found. + */ + if (result == NSS_STATUS_NOTFOUND || result == NSS_STATUS_UNAVAIL) + update_negative_cache(ap, source, name); + pthread_cleanup_pop(1); + + return !result; +} + +static void lookup_close_lookup_instances(struct map_source *map) +{ + struct map_source *instance; + + instance = map->instance; + while (instance) { + lookup_close_lookup_instances(instance); + instance = instance->next; + } + + if (map->lookup) { + close_lookup(map->lookup); + map->lookup = NULL; + } +} + +void lookup_close_lookup(struct autofs_point *ap) +{ + struct map_source *map; + + map = ap->entry->maps; + if (!map) + return; + + while (map) { + lookup_close_lookup_instances(map); + map = map->next; + } + + return; +} + +static char *make_fullpath(struct autofs_point *ap, const char *key) +{ + char *path = NULL; + int l; + + if (*key != '/') + path = make_browse_path(ap->logopt, ap->path, key, ap->pref); + else { + l = strlen(key) + 1; + if (l > KEY_MAX_LEN) + goto out; + path = malloc(l); + if (!path) + goto out; + strcpy(path, key); + } +out: + return path; +} + +void lookup_prune_one_cache(struct autofs_point *ap, struct mapent_cache *mc, time_t age) +{ + struct mapent *me, *this; + char *path; + int status = CHE_FAIL; + + me = cache_enumerate(mc, NULL); + while (me) { + struct mapent *valid; + char *key = NULL, *next_key = NULL; + + if (me->age >= age) { + /* + * Reset time of last fail for valid map entries to + * force entry update and subsequent mount retry. + * A map entry that's still invalid after a read + * may have been created by a failed wildcard lookup + * so reset the status on those too. + */ + if (me->mapent || cache_lookup(mc, "*")) + me->status = 0; + me = cache_enumerate(mc, me); + continue; + } + + key = strdup(me->key); + me = cache_enumerate(mc, me); + /* Don't consider any entries with a wildcard */ + if (!key || strchr(key, '*')) { + if (key) + free(key); + continue; + } + + path = make_fullpath(ap, key); + if (!path) { + warn(ap->logopt, "can't malloc storage for path"); + free(key); + continue; + } + + /* + * If this key has another valid entry we want to prune it, + * even if it's a mount, as the valid entry will take the + * mount if it is a direct mount or it's just a stale indirect + * cache entry. + */ + valid = lookup_source_valid_mapent(ap, key, LKP_DISTINCT); + if (valid && valid->mc == mc) { + /* + * We've found a map entry that has been removed from + * the current cache so it isn't really valid. + */ + cache_unlock(valid->mc); + valid = NULL; + } + if (!valid && + is_mounted(_PATH_MOUNTED, path, MNTS_REAL)) { + debug(ap->logopt, + "prune check posponed, %s mounted", path); + free(key); + free(path); + continue; + } + if (valid) + cache_unlock(valid->mc); + + if (me) + next_key = strdup(me->key); + + cache_unlock(mc); + + cache_writelock(mc); + this = cache_lookup_distinct(mc, key); + if (!this) { + cache_unlock(mc); + goto next; + } + + if (valid) + cache_delete(mc, key); + else if (!is_mounted(_PROC_MOUNTS, path, MNTS_AUTOFS)) { + dev_t devid = ap->dev; + status = CHE_FAIL; + if (ap->type == LKP_DIRECT) + devid = this->dev; + if (this->ioctlfd == -1) + status = cache_delete(mc, key); + if (status != CHE_FAIL) { + if (ap->type == LKP_INDIRECT) { + if (ap->flags & MOUNT_FLAG_GHOST) + rmdir_path(ap, path, devid); + } else + rmdir_path(ap, path, devid); + } + } + cache_unlock(mc); + +next: + cache_readlock(mc); + if (next_key) { + me = cache_lookup_distinct(mc, next_key); + free(next_key); + } + free(key); + free(path); + } + + return; +} + +int lookup_prune_cache(struct autofs_point *ap, time_t age) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + + pthread_cleanup_push(master_source_lock_cleanup, entry); + master_source_readlock(entry); + + map = entry->maps; + while (map) { + /* Is the map stale */ + if (!map->stale) { + map = map->next; + continue; + } + pthread_cleanup_push(cache_lock_cleanup, map->mc); + cache_readlock(map->mc); + lookup_prune_one_cache(ap, map->mc, age); + pthread_cleanup_pop(1); + clear_stale_instances(map); + map->stale = 0; + map = map->next; + } + + pthread_cleanup_pop(1); + + return 1; +} + +/* Return with cache readlock held */ +struct mapent *lookup_source_valid_mapent(struct autofs_point *ap, const char *key, unsigned int type) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent_cache *mc; + struct mapent *me = NULL; + + map = entry->maps; + while (map) { + /* + * Only consider map sources that have been read since + * the map entry was last updated. + */ + if (ap->entry->age > map->age) { + map = map->next; + continue; + } + + mc = map->mc; + cache_readlock(mc); + if (type == LKP_DISTINCT) + me = cache_lookup_distinct(mc, key); + else + me = cache_lookup(mc, key); + if (me) + break; + cache_unlock(mc); + map = map->next; + } + + return me; +} + +/* Return with cache readlock held */ +struct mapent *lookup_source_mapent(struct autofs_point *ap, const char *key, unsigned int type) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent_cache *mc; + struct mapent *me = NULL; + + map = entry->maps; + while (map) { + mc = map->mc; + cache_readlock(mc); + if (type == LKP_DISTINCT) + me = cache_lookup_distinct(mc, key); + else + me = cache_lookup(mc, key); + if (me) + break; + cache_unlock(mc); + map = map->next; + } + + if (me && me->mc != mc) + error(LOGOPT_ANY, "mismatching mc in cache", me->key); + + return me; +} + +int lookup_source_close_ioctlfd(struct autofs_point *ap, const char *key) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent_cache *mc; + struct mapent *me; + int ret = 0; + + map = entry->maps; + while (map) { + mc = map->mc; + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me) { + if (me->ioctlfd != -1) { + struct ioctl_ops *ops = get_ioctl_ops(); + ops->close(ap->logopt, me->ioctlfd); + me->ioctlfd = -1; + } + cache_unlock(mc); + ret = 1; + break; + } + cache_unlock(mc); + map = map->next; + } + + return ret; +} + diff --git a/daemon/module.c b/daemon/module.c new file mode 100644 index 0000000..8879b64 --- /dev/null +++ b/daemon/module.c @@ -0,0 +1,347 @@ +/* ----------------------------------------------------------------------- * + * + * module.c - common module-management functions + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include "automount.h" +#include "nsswitch.h" + +int load_autofs4_module(void) +{ + FILE *fp; + char buf[PATH_MAX]; + int ret; + + /* + * Check if module already loaded or compiled in. + * If both autofs v3 and v4 are coplied in and + * the v3 module registers first or the v4 module + * is an older version we will catch it at mount + * time. + */ + fp = open_fopen_r("/proc/filesystems"); + if (!fp) { + logerr("cannot open /proc/filesystems"); + return 0; + } + + while (fgets(buf, sizeof(buf), fp)) { + if (strstr(buf, "autofs")) { + fclose(fp); + return 1; + } + } + fclose(fp); + + ret = spawnl(LOGOPT_NONE, PATH_MODPROBE, PATH_MODPROBE, + "-q", FS_MODULE_NAME, NULL); + if (ret) + return 0; + + return 1; +} + +int open_lookup(const char *name, const char *err_prefix, const char *mapfmt, + int argc, const char *const *argv, struct lookup_mod **lookup) +{ + struct lookup_mod *mod; + char buf[MAX_ERR_BUF]; + char fnbuf[PATH_MAX]; + size_t size; + char *type; + void *dh; + int *ver; + + *lookup = NULL; + + mod = malloc(sizeof(struct lookup_mod)); + if (!mod) { + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NSS_STATUS_UNAVAIL; + } + + type = strdup(name); + if (!type) { + free(mod); + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NSS_STATUS_UNAVAIL; + } + + size = snprintf(fnbuf, sizeof(fnbuf), + "%s/lookup_%s.so", AUTOFS_LIB_DIR, name); + if (size >= sizeof(fnbuf)) { + free(mod); + free(type); + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NSS_STATUS_UNAVAIL; + } + + if (!(dh = dlopen(fnbuf, RTLD_NOW))) { + if (err_prefix) + logerr("%scannot open lookup module %s (%s)", + err_prefix, name, dlerror()); + free(mod); + free(type); + return NSS_STATUS_UNAVAIL; + } + + if (!(ver = (int *) dlsym(dh, "lookup_version")) + || *ver != AUTOFS_LOOKUP_VERSION) { + if (err_prefix) + logerr("%slookup module %s version mismatch", + err_prefix, name); + dlclose(dh); + free(mod); + free(type); + return NSS_STATUS_UNAVAIL; + } + + if (!(mod->lookup_init = (lookup_init_t) dlsym(dh, "lookup_init")) || + !(mod->lookup_reinit = (lookup_reinit_t) dlsym(dh, "lookup_reinit")) || + !(mod->lookup_read_master = (lookup_read_master_t) dlsym(dh, "lookup_read_master")) || + !(mod->lookup_read_map = (lookup_read_map_t) dlsym(dh, "lookup_read_map")) || + !(mod->lookup_mount = (lookup_mount_t) dlsym(dh, "lookup_mount")) || + !(mod->lookup_done = (lookup_done_t) dlsym(dh, "lookup_done"))) { + if (err_prefix) + logerr("%slookup module %s corrupt", err_prefix, name); + dlclose(dh); + free(mod); + free(type); + return NSS_STATUS_UNAVAIL; + } + + if (mod->lookup_init(mapfmt, argc, argv, &mod->context)) { + dlclose(dh); + free(mod); + free(type); + return NSS_STATUS_NOTFOUND; + } + + mod->type = type; + mod->dlhandle = dh; + *lookup = mod; + + return NSS_STATUS_SUCCESS; +} + +int reinit_lookup(struct lookup_mod *mod, const char *name, + const char *err_prefix, const char *mapfmt, + int argc, const char *const *argv) +{ + if (mod->lookup_reinit(mapfmt, argc, argv, &mod->context)) { + if (err_prefix) + logerr("%scould not reinit lookup module %s", + err_prefix, name); + return 1; + } + return 0; +} + +int close_lookup(struct lookup_mod *mod) +{ + int rv = mod->lookup_done(mod->context); + dlclose(mod->dlhandle); + free(mod->type); + free(mod); + return rv; +} + +struct parse_mod *open_parse(const char *name, const char *err_prefix, + int argc, const char *const *argv) +{ + struct parse_mod *mod; + char buf[MAX_ERR_BUF]; + char fnbuf[PATH_MAX]; + size_t size; + void *dh; + int *ver; + + + mod = malloc(sizeof(struct parse_mod)); + if (!mod) { + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NULL; + } + + size = snprintf(fnbuf, sizeof(fnbuf), + "%s/parse_%s.so", AUTOFS_LIB_DIR, name); + if (size >= sizeof(fnbuf)) { + free(mod); + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NULL; + } + + if (!(dh = dlopen(fnbuf, RTLD_NOW))) { + if (err_prefix) + logerr("%scannot open parse module %s (%s)", + err_prefix, name, dlerror()); + free(mod); + return NULL; + } + + if (!(ver = (int *) dlsym(dh, "parse_version")) + || *ver != AUTOFS_PARSE_VERSION) { + if (err_prefix) + logerr("%sparse module %s version mismatch", + err_prefix, name); + dlclose(dh); + free(mod); + return NULL; + } + + if (!(mod->parse_init = (parse_init_t) dlsym(dh, "parse_init")) || + !(mod->parse_reinit = (parse_reinit_t) dlsym(dh, "parse_reinit")) || + !(mod->parse_mount = (parse_mount_t) dlsym(dh, "parse_mount")) || + !(mod->parse_done = (parse_done_t) dlsym(dh, "parse_done"))) { + if (err_prefix) + logerr("%sparse module %s corrupt", + err_prefix, name); + dlclose(dh); + free(mod); + return NULL; + } + + if (mod->parse_init(argc, argv, &mod->context)) { + dlclose(dh); + free(mod); + return NULL; + } + mod->dlhandle = dh; + return mod; +} + +int reinit_parse(struct parse_mod *mod, const char *name, + const char *err_prefix, int argc, const char *const *argv) +{ + if (mod->parse_reinit(argc, argv, &mod->context)) { + if (err_prefix) + logerr("%scould not reinit parse module %s", + err_prefix, name); + return 1; + } + return 0; +} + +int close_parse(struct parse_mod *mod) +{ + int rv = mod->parse_done(mod->context); + dlclose(mod->dlhandle); + free(mod); + return rv; +} + +struct mount_mod *open_mount(const char *name, const char *err_prefix) +{ + struct mount_mod *mod; + char buf[MAX_ERR_BUF]; + char fnbuf[PATH_MAX]; + size_t size; + void *dh; + int *ver; + + + mod = malloc(sizeof(struct mount_mod)); + if (!mod) { + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NULL; + } + + size = snprintf(fnbuf, sizeof(fnbuf), + "%s/mount_%s.so", AUTOFS_LIB_DIR, name); + if (size >= sizeof(fnbuf)) { + free(mod); + if (err_prefix) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("%s%s", err_prefix, estr); + } + return NULL; + } + + if (!(dh = dlopen(fnbuf, RTLD_NOW))) { + if (err_prefix) + logerr("%scannot open mount module %s (%s)", + err_prefix, name, dlerror()); + free(mod); + return NULL; + } + + if (!(ver = (int *) dlsym(dh, "mount_version")) + || *ver != AUTOFS_MOUNT_VERSION) { + if (err_prefix) + logerr("%smount module %s version mismatch", + err_prefix, name); + dlclose(dh); + free(mod); + return NULL; + } + + if (!(mod->mount_init = (mount_init_t) dlsym(dh, "mount_init")) || + !(mod->mount_reinit = (mount_reinit_t) dlsym(dh, "mount_reinit")) || + !(mod->mount_mount = (mount_mount_t) dlsym(dh, "mount_mount")) || + !(mod->mount_done = (mount_done_t) dlsym(dh, "mount_done"))) { + if (err_prefix) + logerr("%smount module %s corrupt", + err_prefix, name); + dlclose(dh); + free(mod); + return NULL; + } + + if (mod->mount_init(&mod->context)) { + dlclose(dh); + free(mod); + return NULL; + } + mod->dlhandle = dh; + return mod; +} + +int reinit_mount(struct mount_mod *mod, const char *name, const char *err_prefix) +{ + if (mod->mount_reinit(&mod->context)) { + if (err_prefix) + logerr("%scould not reinit mount module %s", + err_prefix, name); + return 1; + } + return 0; +} + +int close_mount(struct mount_mod *mod) +{ + int rv = mod->mount_done(mod->context); + dlclose(mod->dlhandle); + free(mod); + return rv; +} diff --git a/daemon/mount.c b/daemon/mount.c new file mode 100644 index 0000000..4031eda --- /dev/null +++ b/daemon/mount.c @@ -0,0 +1,82 @@ +/* ----------------------------------------------------------------------- * + * + * mount.c - Abstract mount code used by modules for an unexpected + * filesystem type + * + * Copyright 1997-2000 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include "automount.h" + +#define ERR_PREFIX "(mount):" + +/* These filesystems are known not to work with the "generic" module */ +/* Note: starting with Samba 2.0.6, smbfs is handled generically. */ +static char *not_generic[] = { "nfs", "userfs", "afs", "autofs", + "changer", "bind", NULL +}; + +int do_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options) +{ + struct mount_mod *mod; + const char *modstr; + size_t root_len = root ? strlen(root) : 0; + char **ngp; + int rv; + + /* Initially look for a mount module but don't issue an error on fail */ + mod = open_mount(modstr = fstype, NULL); + if (!mod) { + for (ngp = not_generic; *ngp; ngp++) { + if (!strcmp(fstype, *ngp)) + break; + } + /* + * If there's not a known mount module use the generic module, + * otherwise redo the fs mount module with error reporting + */ + if (!*ngp) + mod = open_mount(modstr = "generic", ERR_PREFIX); + else + mod = open_mount(modstr = fstype, ERR_PREFIX); + if (!mod) { + error(ap->logopt, + "cannot find mount method for filesystem %s", + fstype); + return -1; + } + } + + if (*name == '/') + debug(ap->logopt, + "%s %s type %s options %s using module %s", + what, name, fstype, options, modstr); + else if (root_len > 1 && root[root_len - 1] == '/') + debug(ap->logopt, + "%s %s type %s options %s using module %s", + what, root, fstype, options, modstr); + else + debug(ap->logopt, + "%s %s/%s type %s options %s using module %s", + what, root, name, fstype, options, modstr); + + rv = mod->mount_mount(ap, root, name, name_len, what, fstype, options, mod->context); + close_mount(mod); + + return rv; +} diff --git a/daemon/spawn.c b/daemon/spawn.c new file mode 100644 index 0000000..c640d97 --- /dev/null +++ b/daemon/spawn.c @@ -0,0 +1,676 @@ +/* ----------------------------------------------------------------------- * + * + * spawn.c - run programs synchronously with output redirected to syslog + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +static pthread_mutex_t spawn_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define SPAWN_OPT_NONE 0x0000 +#define SPAWN_OPT_LOCK 0x0001 +#define SPAWN_OPT_OPEN 0x0002 + +#define MTAB_LOCK_RETRIES 3 + +void dump_core(void) +{ + sigset_t segv; + + sigemptyset(&segv); + sigaddset(&segv, SIGSEGV); + pthread_sigmask(SIG_UNBLOCK, &segv, NULL); + sigprocmask(SIG_UNBLOCK, &segv, NULL); + + raise(SIGSEGV); +} + +/* + * Used by subprocesses which exec to avoid carrying over the main + * daemon's signalling environment + */ +void reset_signals(void) +{ + struct sigaction sa; + sigset_t allsignals; + int i; + + sigfillset(&allsignals); + sigprocmask(SIG_BLOCK, &allsignals, NULL); + + /* Discard all pending signals */ + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + for (i = 1; i < NSIG; i++) + if (i != SIGKILL && i != SIGSTOP) + sigaction(i, &sa, NULL); + + sa.sa_handler = SIG_DFL; + + for (i = 1; i < NSIG; i++) + if (i != SIGKILL && i != SIGSTOP) + sigaction(i, &sa, NULL); + + /* Ignore the user signals that may be sent so that we + * don't terminate execed program by mistake */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGUSR2, &sa, NULL); + + sigprocmask(SIG_UNBLOCK, &allsignals, NULL); +} + +#define ERRBUFSIZ 2047 /* Max length of error string excl \0 */ + +static int timed_read(int pipe, char *buf, size_t len, int time) +{ + struct pollfd pfd[1]; + int timeout = time; + int ret; + + pfd[0].fd = pipe; + pfd[0].events = POLLIN; + + if (time != -1) { + if (time >= (INT_MAX - 1)/1000) + timeout = INT_MAX - 1; + else + timeout = time * 1000; + } + + ret = poll(pfd, 1, timeout); + if (ret <= 0) { + if (ret == 0) + ret = -ETIMEDOUT; + return ret; + } + + if (pfd[0].fd == -1) + return 0; + + if ((pfd[0].revents & (POLLIN|POLLHUP)) == POLLHUP) + return 0; + + while ((ret = read(pipe, buf, len)) == -1 && errno == EINTR); + + return ret; +} + +static int do_spawn(unsigned logopt, unsigned int wait, + unsigned int options, const char *prog, + const char *const *argv) +{ + pid_t f; + int ret, status, pipefd[2]; + char errbuf[ERRBUFSIZ + 1], *p, *sp; + int errp, errn; + int cancel_state; + unsigned int use_lock = options & SPAWN_OPT_LOCK; + unsigned int use_open = options & SPAWN_OPT_OPEN; + sigset_t allsigs, tmpsig, oldsig; + struct thread_stdenv_vars *tsv; + pid_t euid = 0; + gid_t egid = 0; + + if (open_pipe(pipefd)) + return -1; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); + + sigfillset(&allsigs); + pthread_sigmask(SIG_BLOCK, &allsigs, &oldsig); + + if (use_lock) { + status = pthread_mutex_lock(&spawn_mutex); + if (status) + fatal(status); + } + + tsv = pthread_getspecific(key_thread_stdenv_vars); + if (tsv) { + euid = tsv->uid; + egid = tsv->gid; + } + + f = fork(); + if (f == 0) { + char **pargv = (char **) argv; + int loc = 0; + + reset_signals(); + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + + /* what to mount must always be second last */ + while (*pargv++) + loc++; + if (loc <= 3) + goto done; + loc -= 2; + + /* + * If the mount location starts with a "/" then it is + * a local path. In this case it is a bind mount, a + * loopback mount or a file system that uses a local + * path so we need to check for dependent mounts. + * + * I hope host names are never allowed "/" as first char + */ + if (use_open && *(argv[loc]) == '/') { + char **p; + int is_bind, fd; + + pid_t pgrp = getpgrp(); + + /* + * Pretend to be requesting user and set non-autofs + * program group to trigger mount + */ + if (euid) { + seteuid(euid); + setegid(egid); + } + setpgrp(); + + /* + * Trigger the recursive mount. + * + * Ignore the open(2) return code as there may be + * multiple waiters for this mount and we need to + * let the VFS handle returns to each individual + * waiter. + */ + fd = open(argv[loc], O_DIRECTORY); + if (fd != -1) + close(fd); + + seteuid(0); + setegid(0); + if (pgrp >= 0) + setpgid(0, pgrp); + + /* + * The kernel leaves mount type autofs alone because + * they are supposed to be autofs sub-mounts and they + * look after their own expiration. So mounts bound + * to an autofs submount won't ever be expired. + */ + is_bind = 0; + p = (char **) argv; + while (*p) { + if (strcmp(*p, "--bind")) { + p++; + continue; + } + is_bind = 1; + break; + } + if (!is_bind) + goto done; + + if (is_mounted(_PROC_MOUNTS, argv[loc], MNTS_AUTOFS)) { + fprintf(stderr, + "error: can't bind to an autofs mount\n"); + close(STDOUT_FILENO); + close(STDERR_FILENO); + _exit(EINVAL); + } + } +done: + execv(prog, (char *const *) argv); + _exit(255); /* execv() failed */ + } else { + tmpsig = oldsig; + + sigaddset(&tmpsig, SIGCHLD); + pthread_sigmask(SIG_SETMASK, &tmpsig, NULL); + + close(pipefd[1]); + + if (f < 0) { + close(pipefd[0]); + if (use_lock) { + status = pthread_mutex_unlock(&spawn_mutex); + if (status) + fatal(status); + } + pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + pthread_setcancelstate(cancel_state, NULL); + return -1; + } + + errp = 0; + do { + errn = timed_read(pipefd[0], + errbuf + errp, ERRBUFSIZ - errp, wait); + if (errn > 0) { + errp += errn; + + sp = errbuf; + while (errp && (p = memchr(sp, '\n', errp))) { + *p++ = '\0'; + if (sp[0]) /* Don't output empty lines */ + warn(logopt, ">> %s", sp); + errp -= (p - sp); + sp = p; + } + + if (errp && sp != errbuf) + memmove(errbuf, sp, errp); + + if (errp >= ERRBUFSIZ) { + /* Line too long, split */ + errbuf[errp] = '\0'; + warn(logopt, ">> %s", errbuf); + errp = 0; + } + } + } while (errn > 0); + + if (errn == -ETIMEDOUT) + kill(f, SIGTERM); + + close(pipefd[0]); + + if (errp > 0) { + /* End of file without \n */ + errbuf[errp] = '\0'; + warn(logopt, ">> %s", errbuf); + } + + if (waitpid(f, &ret, 0) != f) + ret = -1; /* waitpid() failed */ + + if (use_lock) { + status = pthread_mutex_unlock(&spawn_mutex); + if (status) + fatal(status); + } + pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + pthread_setcancelstate(cancel_state, NULL); + + return ret; + } +} + +int spawnv(unsigned logopt, const char *prog, const char *const *argv) +{ + return do_spawn(logopt, -1, SPAWN_OPT_NONE, prog, argv); +} + +int spawnl(unsigned logopt, const char *prog, ...) +{ + va_list arg; + int argc; + char **argv, **p; + + va_start(arg, prog); + for (argc = 1; va_arg(arg, char *); argc++); + va_end(arg); + + if (!(argv = alloca(sizeof(char *) * argc))) + return -1; + + va_start(arg, prog); + p = argv; + while ((*p++ = va_arg(arg, char *))); + va_end(arg); + + return do_spawn(logopt, -1, SPAWN_OPT_NONE, prog, (const char **) argv); +} + +int spawn_mount(unsigned logopt, ...) +{ + va_list arg; + int argc; + char **argv, **p; + char prog[] = PATH_MOUNT; + char arg0[] = PATH_MOUNT; + char argn[] = "-n"; + /* In case we need to use the fake option to mount */ + char arg_fake[] = "-f"; + unsigned int options; + unsigned int retries = MTAB_LOCK_RETRIES; + int update_mtab = 1, ret, printed = 0; + unsigned int wait = defaults_get_mount_wait(); + char buf[PATH_MAX + 1]; + + /* If we use mount locking we can't validate the location */ +#ifdef ENABLE_MOUNT_LOCKING + options = SPAWN_OPT_LOCK; +#else + options = SPAWN_OPT_OPEN; +#endif + + va_start(arg, logopt); + for (argc = 1; va_arg(arg, char *); argc++); + va_end(arg); + + ret = readlink(_PATH_MOUNTED, buf, PATH_MAX); + if (ret != -1) { + buf[ret] = '\0'; + if (!strcmp(buf, _PROC_MOUNTS) || + !strcmp(buf, _PROC_SELF_MOUNTS)) { + debug(logopt, + "mtab link detected, passing -n to mount"); + argc++; + update_mtab = 0; + } + } + + /* Alloc 1 extra slot in case we need to use the "-f" option */ + if (!(argv = alloca(sizeof(char *) * (argc + 2)))) + return -1; + + argv[0] = arg0; + + va_start(arg, logopt); + if (update_mtab) + p = argv + 1; + else { + argv[1] = argn; + p = argv + 2; + } + while ((*p = va_arg(arg, char *))) { + if (options == SPAWN_OPT_OPEN && !strcmp(*p, "-t")) { + *(++p) = va_arg(arg, char *); + if (!*p) + break; + /* + * A cifs mount location begins with a "/" but + * is not a local path, so don't try to resolve + * it. Mmmm ... does anyone use smbfs these days? + */ + if (strstr(*p, "cifs")) + options = SPAWN_OPT_NONE; + } + p++; + } + va_end(arg); + + while (retries--) { + ret = do_spawn(logopt, wait, options, prog, (const char **) argv); + if (ret == MTAB_NOTUPDATED) { + struct timespec tm = {3, 0}; + + /* + * If the mount succeeded but the mtab was not + * updated, then retry the mount with the -f (fake) + * option to just update the mtab. + */ + if (!printed) { + debug(logopt, "mount failed with error code 16" + ", retrying with the -f option"); + printed = 1; + } + + /* + * Move the last two args so do_spawn() can find the + * mount target. + */ + if (!argv[argc]) { + argv[argc + 1] = NULL; + argv[argc] = argv[argc - 1]; + argv[argc - 1] = argv[argc - 2]; + argv[argc - 2] = arg_fake; + } + + nanosleep(&tm, NULL); + + continue; + } + break; + } + + /* This is not a fatal error */ + if (ret == MTAB_NOTUPDATED) { + /* + * Version 5 requires that /etc/mtab be in sync with + * /proc/mounts. If we're unable to update matb after + * retrying then we have no choice but umount the mount + * and return a fail. + */ + warn(logopt, + "Unable to update the mtab file, forcing mount fail!"); + umount(argv[argc]); + ret = MNT_FORCE_FAIL; + } + + return ret; +} + +/* + * For bind mounts that depend on the target being mounted (possibly + * itself an automount) we attempt to mount the target using an open(2) + * call. For this to work the location must be the second last arg. + * + * NOTE: If mount locking is enabled this type of recursive mount cannot + * work. + */ +int spawn_bind_mount(unsigned logopt, ...) +{ + va_list arg; + int argc; + char **argv, **p; + char prog[] = PATH_MOUNT; + char arg0[] = PATH_MOUNT; + char bind[] = "--bind"; + char argn[] = "-n"; + /* In case we need to use the fake option to mount */ + char arg_fake[] = "-f"; + unsigned int options; + unsigned int retries = MTAB_LOCK_RETRIES; + int update_mtab = 1, ret, printed = 0; + char buf[PATH_MAX + 1]; + + /* If we use mount locking we can't validate the location */ +#ifdef ENABLE_MOUNT_LOCKING + options = SPAWN_OPT_LOCK; +#else + options = SPAWN_OPT_OPEN; +#endif + + /* + * Alloc 2 extra slots, one for the bind option and one in case + * we need to use the "-f" option + */ + va_start(arg, logopt); + for (argc = 2; va_arg(arg, char *); argc++); + va_end(arg); + + ret = readlink(_PATH_MOUNTED, buf, PATH_MAX); + if (ret != -1) { + buf[ret] = '\0'; + if (!strcmp(buf, _PROC_MOUNTS) || + !strcmp(buf, _PROC_SELF_MOUNTS)) { + debug(logopt, + "mtab link detected, passing -n to mount"); + argc++; + update_mtab = 0; + } + } + + if (!(argv = alloca(sizeof(char *) * (argc + 2)))) + return -1; + + argv[0] = arg0; + argv[1] = bind; + + va_start(arg, logopt); + if (update_mtab) + p = argv + 2; + else { + argv[2] = argn; + p = argv + 3; + } + while ((*p++ = va_arg(arg, char *))); + va_end(arg); + + while (retries--) { + ret = do_spawn(logopt, -1, options, prog, (const char **) argv); + if (ret == MTAB_NOTUPDATED) { + struct timespec tm = {3, 0}; + + /* + * If the mount succeeded but the mtab was not + * updated, then retry the mount with the -f (fake) + * option to just update the mtab. + */ + if (!printed) { + debug(logopt, "mount failed with error code 16" + ", retrying with the -f option"); + printed = 1; + } + + /* + * Move the last two args so do_spawn() can find the + * mount target. + */ + if (!argv[argc]) { + argv[argc + 1] = NULL; + argv[argc] = argv[argc - 1]; + argv[argc - 1] = argv[argc - 2]; + argv[argc - 2] = arg_fake; + } + + nanosleep(&tm, NULL); + + continue; + } + break; + } + + /* This is not a fatal error */ + if (ret == MTAB_NOTUPDATED) { + /* + * Version 5 requires that /etc/mtab be in sync with + * /proc/mounts. If we're unable to update matb after + * retrying then we have no choice but umount the mount + * and return a fail. + */ + warn(logopt, + "Unable to update the mtab file, forcing mount fail!"); + umount(argv[argc]); + ret = MNT_FORCE_FAIL; + } + + return ret; +} + +int spawn_umount(unsigned logopt, ...) +{ + va_list arg; + int argc; + char **argv, **p; + char prog[] = PATH_UMOUNT; + char arg0[] = PATH_UMOUNT; + char argn[] = "-n"; + unsigned int options; + unsigned int retries = MTAB_LOCK_RETRIES; + int update_mtab = 1, ret, printed = 0; + unsigned int wait = defaults_get_umount_wait(); + char buf[PATH_MAX + 1]; + +#ifdef ENABLE_MOUNT_LOCKING + options = SPAWN_OPT_LOCK; +#else + options = SPAWN_OPT_NONE; +#endif + + va_start(arg, logopt); + for (argc = 1; va_arg(arg, char *); argc++); + va_end(arg); + + ret = readlink(_PATH_MOUNTED, buf, PATH_MAX); + if (ret != -1) { + buf[ret] = '\0'; + if (!strcmp(buf, _PROC_MOUNTS) || + !strcmp(buf, _PROC_SELF_MOUNTS)) { + debug(logopt, + "mtab link detected, passing -n to mount"); + argc++; + update_mtab = 0; + } + } + + if (!(argv = alloca(sizeof(char *) * argc + 1))) + return -1; + + argv[0] = arg0; + + va_start(arg, logopt); + if (update_mtab) + p = argv + 1; + else { + argv[1] = argn; + p = argv + 2; + } + while ((*p++ = va_arg(arg, char *))); + va_end(arg); + + while (retries--) { + ret = do_spawn(logopt, wait, options, prog, (const char **) argv); + if (ret == MTAB_NOTUPDATED) { + /* + * If the mount succeeded but the mtab was not + * updated, then retry the umount just to update + * the mtab. + */ + if (!printed) { + debug(logopt, "mount failed with error code 16" + ", retrying with the -f option"); + printed = 1; + } + } else { + /* + * umount does not support the "fake" option. Thus, + * if we got a return value of MTAB_NOTUPDATED the + * first time, that means the umount actually + * succeeded. Then, a following umount will fail + * due to the fact that nothing was mounted on the + * mount point. So, report this as success. + */ + if (retries < MTAB_LOCK_RETRIES - 1) + ret = 0; + break; + } + } + + /* This is not a fatal error */ + if (ret == MTAB_NOTUPDATED) { + warn(logopt, "Unable to update the mtab file, /proc/mounts " + "and /etc/mtab will differ"); + ret = 0; + } + + return ret; +} + diff --git a/daemon/state.c b/daemon/state.c new file mode 100644 index 0000000..cd0dd93 --- /dev/null +++ b/daemon/state.c @@ -0,0 +1,1235 @@ +/* ----------------------------------------------------------------------- * + * + * state.c - state machine functions. + * + * Copyright 2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include + +#include "automount.h" + +/* Attribute to create detached thread */ +extern pthread_attr_t th_attr_detached; + +struct state_queue { + pthread_t thid; + struct list_head list; + struct list_head pending; + struct autofs_point *ap; + enum states state; + unsigned int busy; + unsigned int done; + unsigned int cancel; +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static unsigned int signaled = 0; +static LIST_HEAD(state_queue); + +static void st_set_thid(struct autofs_point *, pthread_t); +static void st_set_done(struct autofs_point *ap); + +void st_mutex_lock(void) +{ + int status = pthread_mutex_lock(&mutex); + if (status) + fatal(status); +} + +void st_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&mutex); + if (status) + fatal(status); +} + +void dump_state_queue(void) +{ + struct list_head *head = &state_queue; + struct list_head *p, *q; + + logmsg("dumping queue"); + + list_for_each(p, head) { + struct state_queue *entry; + + entry = list_entry(p, struct state_queue, list); + logmsg("queue list head path %s state %d busy %d", + entry->ap->path, entry->state, entry->busy); + + list_for_each(q, &entry->pending) { + struct state_queue *this; + + this = list_entry(q, struct state_queue, pending); + logmsg("queue list entry path %s state %d busy %d", + this->ap->path, this->state, this->busy); + } + } +} + +void nextstate(int statefd, enum states next) +{ + char buf[MAX_ERR_BUF]; + + if (write(statefd, &next, sizeof(next)) != sizeof(next)) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("write failed %s", estr); + } +} + +/* + * Handle expire thread cleanup and return the next state the system + * should enter as a result. + */ +void expire_cleanup(void *arg) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + pthread_t thid = pthread_self(); + struct expire_args *ec; + struct autofs_point *ap; + int success; + enum states next = ST_INVAL; + + ec = (struct expire_args *) arg; + ap = ec->ap; + success = ec->status; + + st_mutex_lock(); + + debug(ap->logopt, + "got thid %lu path %s stat %d", + (unsigned long) thid, ap->path, success); + + /* Check to see if expire process finished */ + if (thid == ap->exp_thread) { + unsigned int idle; + int rv; + + ap->exp_thread = 0; + + switch (ap->state) { + case ST_EXPIRE: + /* FALLTHROUGH */ + case ST_PRUNE: + /* + * If we're a submount and we've just pruned or + * expired everything away, try to shut down. + * + * Since we use the the fact that a mount will not + * expire for at least ap->exp_timeout to avoid a + * mount <-> expire race we need to wait before + * letting a submount expire away. We also need + * them to go away fairly quickly so the owner + * mount expires in a reasonable time. Just skip + * one expire check after it's no longer busy before + * allowing it to shutdown. + * + * But if this mount point is an amd format map it + * is better to keep the mount around longer. This + * is because of the common heavy reuse of maps in + * amd maps and we want to try and avoid constantly + * re-reading large maps. + */ + if (ap->submount && !success) { + rv = ops->askumount(ap->logopt, ap->ioctlfd, &idle); + if (!rv && idle && ap->submount > 1) { + struct map_source *map = ap->entry->maps; + + if (ap->submount > 4 || + !(map->flags & MAP_FLAG_FORMAT_AMD)) { + next = ST_SHUTDOWN_PENDING; + break; + } + } + ap->submount++; + } else if (ap->submount > 1) + ap->submount = 1; + + if (ap->state == ST_EXPIRE && !ap->submount) + alarm_add(ap, ap->exp_runfreq); + + /* FALLTHROUGH */ + + case ST_READY: + next = ST_READY; + break; + + case ST_SHUTDOWN_PENDING: + /* + * If we reveive a mount request while trying to + * shutdown return to ready state unless we have + * been signaled to shutdown. + */ + rv = ops->askumount(ap->logopt, ap->ioctlfd, &idle); + if (!rv && !idle && !ap->shutdown) { + next = ST_READY; + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + break; + } + + next = ST_SHUTDOWN; +#ifdef ENABLE_IGNORE_BUSY_MOUNTS + break; +#else + if (success == 0) + break; + + /* Failed shutdown returns to ready */ + warn(ap->logopt, "filesystem %s still busy", ap->path); + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + next = ST_READY; + break; +#endif + + case ST_SHUTDOWN_FORCE: + next = ST_SHUTDOWN; + break; + + default: + error(ap->logopt, "bad state %d", ap->state); + } + + if (next != ST_INVAL) { + debug(ap->logopt, + "sigchld: exp %lu finished, switching from %d to %d", + (unsigned long) thid, ap->state, next); + } + } + + st_set_done(ap); + + if (next != ST_INVAL) + __st_add_task(ap, next); + + st_mutex_unlock(); + + return; +} + +static unsigned int st_ready(struct autofs_point *ap) +{ + debug(ap->logopt, + "st_ready(): state = %d path %s", ap->state, ap->path); + + ap->shutdown = 0; + ap->state = ST_READY; + + return 1; +} + +enum expire { + EXP_ERROR, + EXP_STARTED, + EXP_PARTIAL +}; + +/* + * Generate expiry messages. If "now" is true, timeouts are ignored. + * + * Returns: ERROR - error + * STARTED - expiry process started + * DONE - nothing to expire + * PARTIAL - partial expire + */ + +void expire_proc_cleanup(void *arg) +{ + struct expire_args *ea; + int status; + + ea = (struct expire_args *) arg; + + status = pthread_mutex_unlock(&ea->mutex); + if (status) + fatal(status); + + status = pthread_cond_destroy(&ea->cond); + if (status) + fatal(status); + + status = pthread_mutex_destroy(&ea->mutex); + if (status) + fatal(status); + + free(ea); + + return; +} + +static enum expire expire_proc(struct autofs_point *ap, int now) +{ + pthread_t thid; + struct expire_args *ea; + void *(*expire)(void *); + int status; + + assert(ap->exp_thread == 0); + + ea = malloc(sizeof(struct expire_args)); + if (!ea) { + error(ap->logopt, "failed to malloc expire cond struct"); + return EXP_ERROR; + } + + status = pthread_mutex_init(&ea->mutex, NULL); + if (status) + fatal(status); + + status = pthread_cond_init(&ea->cond, NULL); + if (status) + fatal(status); + + status = pthread_mutex_lock(&ea->mutex); + if (status) + fatal(status); + + ea->ap = ap; + ea->when = now; + ea->status = 1; + + if (ap->type == LKP_INDIRECT) + expire = expire_proc_indirect; + else + expire = expire_proc_direct; + + status = pthread_create(&thid, &th_attr_detached, expire, ea); + if (status) { + error(ap->logopt, + "expire thread create for %s failed", ap->path); + expire_proc_cleanup((void *) ea); + return EXP_ERROR; + } + ap->exp_thread = thid; + st_set_thid(ap, thid); + + pthread_cleanup_push(expire_proc_cleanup, ea); + + debug(ap->logopt, "exp_proc = %lu path %s", + (unsigned long) ap->exp_thread, ap->path); + + ea->signaled = 0; + while (!ea->signaled) { + status = pthread_cond_wait(&ea->cond, &ea->mutex); + if (status) + fatal(status); + } + + pthread_cleanup_pop(1); + + return EXP_STARTED; +} + +static void do_readmap_cleanup(void *arg) +{ + struct readmap_args *ra; + struct autofs_point *ap; + + ra = (struct readmap_args *) arg; + + ap = ra->ap; + + st_mutex_lock(); + ap->readmap_thread = 0; + st_set_done(ap); + st_ready(ap); + st_mutex_unlock(); + + free(ra); + + return; +} + +static void tree_mnts_cleanup(void *arg) +{ + struct mnt_list *mnts = (struct mnt_list *) arg; + tree_free_mnt_tree(mnts); + return; +} + +static void do_readmap_mount(struct autofs_point *ap, struct mnt_list *mnts, + struct map_source *map, struct mapent *me, time_t now) +{ + struct mapent_cache *nc; + struct mapent *ne, *nested, *valid; + + nc = ap->entry->master->nc; + + ne = cache_lookup_distinct(nc, me->key); + if (!ne) { + nested = cache_partial_match(nc, me->key); + if (nested) { + error(ap->logopt, + "removing invalid nested null entry %s", + nested->key); + nested = cache_partial_match(nc, me->key); + if (nested) + cache_delete(nc, nested->key); + } + } + + if (me->age < now || (ne && map->master_line > ne->age)) { + /* + * The map instance may have changed, such as the map name or + * the mount options, but the direct map entry may still exist + * in one of the other maps. If so then update the new cache + * entry device and inode so we can find it at lookup. Later, + * the mount for the new cache entry will just update the + * timeout. + * + * TODO: how do we recognise these orphaned map instances. We + * can't just delete these instances when the cache becomes + * empty because that is a valid state for a master map entry. + * This is becuase of the requirement to continue running with + * an empty cache awaiting a map re-load. + */ + valid = lookup_source_valid_mapent(ap, me->key, LKP_DISTINCT); + if (valid && valid->mc == me->mc) { + /* + * We've found a map entry that has been removed from + * the current cache so there is no need to update it. + * The stale entry will be dealt with when we prune the + * cache later. + */ + cache_unlock(valid->mc); + valid = NULL; + } + if (valid) { + struct mapent_cache *vmc = valid->mc; + struct ioctl_ops *ops = get_ioctl_ops(); + time_t timeout; + time_t runfreq; + + cache_unlock(vmc); + debug(ap->logopt, + "updating cache entry for valid direct trigger %s", + me->key); + cache_writelock(vmc); + valid = cache_lookup_distinct(vmc, me->key); + if (!valid) { + cache_unlock(vmc); + error(ap->logopt, + "failed to find expected existing valid map entry"); + return; + } + /* Take over the mount if there is one */ + valid->ioctlfd = me->ioctlfd; + me->ioctlfd = -1; + /* Set device and inode number of the new mapent */ + cache_set_ino_index(vmc, me->key, me->dev, me->ino); + cache_unlock(vmc); + /* Set timeout and calculate the expire run frequency */ + timeout = get_exp_timeout(ap, map); + ops->timeout(ap->logopt, valid->ioctlfd, timeout); + if (timeout) { + runfreq = (timeout + CHECK_RATIO - 1) / CHECK_RATIO; + if (ap->exp_runfreq) + ap->exp_runfreq = min(ap->exp_runfreq, runfreq); + else + ap->exp_runfreq = runfreq; + } + } else if (!tree_is_mounted(mnts, me->key, MNTS_REAL)) + do_umount_autofs_direct(ap, mnts, me); + else + debug(ap->logopt, + "%s is mounted", me->key); + } else + do_mount_autofs_direct(ap, mnts, me, get_exp_timeout(ap, map)); + + return; +} + +static void *do_readmap(void *arg) +{ + struct autofs_point *ap; + struct map_source *map; + struct mapent_cache *nc, *mc; + struct readmap_args *ra; + struct mnt_list *mnts; + int status; + time_t now; + + ra = (struct readmap_args *) arg; + + status = pthread_mutex_lock(&ra->mutex); + if (status) + fatal(status); + + ap = ra->ap; + now = ra->now; + + ra->signaled = 1; + status = pthread_cond_signal(&ra->cond); + if (status) { + error(ap->logopt, "failed to signal expire condition"); + pthread_mutex_unlock(&ra->mutex); + fatal(status); + } + + status = pthread_mutex_unlock(&ra->mutex); + if (status) + fatal(status); + + pthread_cleanup_push(do_readmap_cleanup, ra); + + info(ap->logopt, "re-reading map for %s", ap->path); + + pthread_cleanup_push(master_mutex_lock_cleanup, NULL); + master_mutex_lock(); + status = lookup_nss_read_map(ap, NULL, now); + if (!status) + pthread_exit(NULL); + pthread_cleanup_pop(1); + + if (ap->type == LKP_INDIRECT) { + struct ioctl_ops *ops = get_ioctl_ops(); + time_t timeout = get_exp_timeout(ap, ap->entry->maps); + ap->exp_runfreq = (timeout + CHECK_RATIO - 1) / CHECK_RATIO; + ops->timeout(ap->logopt, ap->ioctlfd, timeout); + lookup_prune_cache(ap, now); + status = lookup_ghost(ap, ap->path); + } else { + struct mapent *me; + unsigned int append_alarm = !ap->exp_runfreq; + + mnts = tree_make_mnt_tree(_PROC_MOUNTS, "/"); + pthread_cleanup_push(tree_mnts_cleanup, mnts); + nc = ap->entry->master->nc; + cache_readlock(nc); + pthread_cleanup_push(cache_lock_cleanup, nc); + master_source_readlock(ap->entry); + pthread_cleanup_push(master_source_lock_cleanup, ap->entry); + map = ap->entry->maps; + while (map) { + /* Is map source up to date or no longer valid */ + if (!map->stale) { + map = map->next; + continue; + } + mc = map->mc; + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_readlock(mc); + me = cache_enumerate(mc, NULL); + while (me) { + do_readmap_mount(ap, mnts, map, me, now); + me = cache_enumerate(mc, me); + } + lookup_prune_one_cache(ap, map->mc, now); + pthread_cleanup_pop(1); + clear_stale_instances(map); + map->stale = 0; + map = map->next; + } + + /* If the direct mount map was empty at startup no expire + * alarm will have been added. So add it here if there are + * now map entries. + */ + if (append_alarm && ap->exp_runfreq) + alarm_add(ap, ap->exp_runfreq + + rand() % ap->exp_runfreq); + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + } + + pthread_cleanup_pop(1); + + return NULL; +} + +static void st_readmap_cleanup(void *arg) +{ + struct readmap_args *ra; + int status; + + ra = (struct readmap_args *) arg; + + status = pthread_mutex_unlock(&ra->mutex); + if (status) + fatal(status); + + status = pthread_cond_destroy(&ra->cond); + if (status) + fatal(status); + + status = pthread_mutex_destroy(&ra->mutex); + if (status) + fatal(status); + + return; +} + +static unsigned int st_readmap(struct autofs_point *ap) +{ + pthread_t thid; + struct readmap_args *ra; + int status; + int now = monotonic_time(NULL); + + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_READY); + assert(ap->readmap_thread == 0); + + ap->state = ST_READMAP; + + ra = malloc(sizeof(struct readmap_args)); + if (!ra) { + error(ap->logopt, "failed to malloc readmap cond struct"); + /* It didn't work: return to ready */ + st_ready(ap); + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + return 0; + } + + status = pthread_mutex_init(&ra->mutex, NULL); + if (status) + fatal(status); + + status = pthread_cond_init(&ra->cond, NULL); + if (status) + fatal(status); + + status = pthread_mutex_lock(&ra->mutex); + if (status) + fatal(status); + + ra->ap = ap; + ra->now = now; + + status = pthread_create(&thid, &th_attr_detached, do_readmap, ra); + if (status) { + error(ap->logopt, "readmap thread create failed"); + st_readmap_cleanup(ra); + free(ra); + /* It didn't work: return to ready */ + st_ready(ap); + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + return 0; + } + ap->readmap_thread = thid; + st_set_thid(ap, thid); + + pthread_cleanup_push(st_readmap_cleanup, ra); + + ra->signaled = 0; + while (!ra->signaled) { + status = pthread_cond_wait(&ra->cond, &ra->mutex); + if (status) + fatal(status); + } + + pthread_cleanup_pop(1); + + return 1; +} + +static unsigned int st_prepare_shutdown(struct autofs_point *ap) +{ + int exp; + + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_READY || ap->state == ST_EXPIRE); + ap->state = ST_SHUTDOWN_PENDING; + + /* Unmount everything */ + exp = expire_proc(ap, 1); + switch (exp) { + case EXP_ERROR: + case EXP_PARTIAL: + /* It didn't work: return to ready */ + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + st_ready(ap); + return 0; + + case EXP_STARTED: + return 1; + } + return 0; +} + +static unsigned int st_force_shutdown(struct autofs_point *ap) +{ + int exp; + + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_READY || ap->state == ST_EXPIRE); + ap->state = ST_SHUTDOWN_FORCE; + + /* Unmount everything */ + exp = expire_proc(ap, 1); + switch (exp) { + case EXP_ERROR: + case EXP_PARTIAL: + /* It didn't work: return to ready */ + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + st_ready(ap); + return 0; + + case EXP_STARTED: + return 1; + } + return 0; +} + +static unsigned int st_shutdown(struct autofs_point *ap) +{ + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_SHUTDOWN_PENDING || ap->state == ST_SHUTDOWN_FORCE); + + ap->state = ST_SHUTDOWN; + nextstate(ap->state_pipe[1], ST_SHUTDOWN); + + return 0; +} + +static unsigned int st_prune(struct autofs_point *ap) +{ + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_READY); + ap->state = ST_PRUNE; + + switch (expire_proc(ap, 1)) { + case EXP_ERROR: + case EXP_PARTIAL: + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + st_ready(ap); + return 0; + + case EXP_STARTED: + return 1; + } + return 0; +} + +static unsigned int st_expire(struct autofs_point *ap) +{ + debug(ap->logopt, "state %d path %s", ap->state, ap->path); + + assert(ap->state == ST_READY); + ap->state = ST_EXPIRE; + + switch (expire_proc(ap, 0)) { + case EXP_ERROR: + case EXP_PARTIAL: + if (!ap->submount) + alarm_add(ap, ap->exp_runfreq); + st_ready(ap); + return 0; + + case EXP_STARTED: + return 1; + } + return 0; +} + +static struct state_queue *st_alloc_task(struct autofs_point *ap, enum states state) +{ + struct state_queue *task; + + task = malloc(sizeof(struct state_queue)); + if (!task) + return NULL; + memset(task, 0, sizeof(struct state_queue)); + + task->ap = ap; + task->state = state; + + INIT_LIST_HEAD(&task->list); + INIT_LIST_HEAD(&task->pending); + + return task; +} + +/* + * Insert alarm entry on ordered list. + * State queue mutex and ap state mutex, in that order, must be held. + */ +int __st_add_task(struct autofs_point *ap, enum states state) +{ + struct list_head *head; + struct list_head *p, *q; + struct state_queue *new; + unsigned int empty = 1; + int status; + + /* Task termination marker, poke state machine */ + if (state == ST_READY) { + st_ready(ap); + + signaled = 1; + status = pthread_cond_signal(&cond); + if (status) + fatal(status); + + return 1; + } + + if (ap->state == ST_SHUTDOWN) + return 1; + + if (state == ST_SHUTDOWN) + return st_shutdown(ap); + + head = &state_queue; + + /* Add to task queue for autofs_point ? */ + list_for_each(p, head) { + struct state_queue *task; + + task = list_entry(p, struct state_queue, list); + + if (task->ap != ap) + continue; + + empty = 0; + + /* Don't add duplicate tasks */ + if ((task->state == state && !task->done) || + (ap->state == ST_SHUTDOWN_PENDING || + ap->state == ST_SHUTDOWN_FORCE)) + break; + + /* No pending tasks */ + if (list_empty(&task->pending)) { + new = st_alloc_task(ap, state); + if (new) + list_add_tail(&new->pending, &task->pending); + goto done; + } + + list_for_each(q, &task->pending) { + struct state_queue *p_task; + + p_task = list_entry(q, struct state_queue, pending); + + if (p_task->state == state || + (ap->state == ST_SHUTDOWN_PENDING || + ap->state == ST_SHUTDOWN_FORCE)) + goto done; + } + + new = st_alloc_task(ap, state); + if (new) + list_add_tail(&new->pending, &task->pending); +done: + break; + } + + if (empty) { + new = st_alloc_task(ap, state); + if (new) + list_add(&new->list, head); + } + + signaled = 1; + status = pthread_cond_signal(&cond); + if (status) + fatal(status); + + return 1; +} + +int st_add_task(struct autofs_point *ap, enum states state) +{ + int ret; + + st_mutex_lock(); + ret = __st_add_task(ap, state); + st_mutex_unlock(); + + return ret; +} + +/* + * Remove state queue tasks for ap. + * State queue mutex and ap state mutex, in that order, must be held. + */ +void st_remove_tasks(struct autofs_point *ap) +{ + struct list_head *head; + struct list_head *p, *q; + struct state_queue *task, *waiting; + int status; + + st_mutex_lock(); + + head = &state_queue; + + if (list_empty(head)) { + st_mutex_unlock(); + return; + } + + p = head->next; + while (p != head) { + task = list_entry(p, struct state_queue, list); + p = p->next; + + if (task->ap != ap) + continue; + + if (task->busy) { + /* We only cancel readmap, prune and expire */ + if (task->state == ST_EXPIRE || + task->state == ST_PRUNE || + task->state == ST_READMAP) + task->cancel = 1; + } + + q = (&task->pending)->next; + while(q != &task->pending) { + waiting = list_entry(q, struct state_queue, pending); + q = q->next; + + /* Don't remove existing shutdown task */ + if (waiting->state != ST_SHUTDOWN_PENDING && + waiting->state != ST_SHUTDOWN_FORCE) { + list_del(&waiting->pending); + free(waiting); + } + } + } + + signaled = 1; + status = pthread_cond_signal(&cond); + if (status) + fatal(status); + + st_mutex_unlock(); + + return; +} + +static int st_task_active(struct autofs_point *ap, enum states state) +{ + struct list_head *head; + struct list_head *p, *q; + struct state_queue *task, *waiting; + unsigned int active = 0; + + st_mutex_lock(); + + head = &state_queue; + + list_for_each(p, head) { + task = list_entry(p, struct state_queue, list); + + if (task->ap != ap) + continue; + + if (task->state == state) { + active = 1; + break; + } + + if (state == ST_ANY) { + active = 1; + break; + } + + list_for_each(q, &task->pending) { + waiting = list_entry(q, struct state_queue, pending); + + if (waiting->state == state) { + active = 1; + break; + } + + if (state == ST_ANY) { + active = 1; + break; + } + } + } + + st_mutex_unlock(); + + return active; +} + +int st_wait_task(struct autofs_point *ap, enum states state, unsigned int seconds) +{ + unsigned int wait = 0; + unsigned int duration = 0; + int ret = 0; + + while (1) { + struct timespec t = { 0, 200000000 }; + struct timespec r; + + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + + if (wait++ == 4) { + wait = 0; + duration++; + } + + if (!st_task_active(ap, state)) { + ret = 1; + break; + } + + if (seconds && duration >= seconds) + break; + } + + return ret; +} + +int st_wait_state(struct autofs_point *ap, enum states state) +{ + while (1) { + struct timespec t = { 0, 200000000 }; + struct timespec r; + + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + + st_mutex_lock(); + if (ap->state == state) { + st_mutex_unlock(); + return 1; + } + st_mutex_unlock(); + } + + return 0; +} + + +static int run_state_task(struct state_queue *task) +{ + struct autofs_point *ap; + enum states next_state, state; + unsigned long ret = 0; + + ap = task->ap; + next_state = task->state; + + state = ap->state; + + if (next_state != state) { + switch (next_state) { + case ST_PRUNE: + ret = st_prune(ap); + break; + + case ST_EXPIRE: + ret = st_expire(ap); + break; + + case ST_READMAP: + ret = st_readmap(ap); + break; + + case ST_SHUTDOWN_PENDING: + ret = st_prepare_shutdown(ap); + break; + + case ST_SHUTDOWN_FORCE: + ret = st_force_shutdown(ap); + break; + + default: + error(ap->logopt, "bad next state %d", next_state); + } + } + + return ret; +} + +static void st_set_thid(struct autofs_point *ap, pthread_t thid) +{ + struct list_head *p, *head = &state_queue; + struct state_queue *task; + + list_for_each(p, head) { + task = list_entry(p, struct state_queue, list); + if (task->ap == ap) { + task->thid = thid; + break; + } + } + return; +} + +/* Requires state mutex to be held */ +static void st_set_done(struct autofs_point *ap) +{ + struct list_head *p, *head; + struct state_queue *task; + + head = &state_queue; + list_for_each(p, head) { + task = list_entry(p, struct state_queue, list); + if (task->ap == ap) { + task->done = 1; + break; + } + } + + return; +} + +static void *st_queue_handler(void *arg) +{ + struct list_head *head; + struct list_head *p; + int status, ret; + + st_mutex_lock(); + + while (1) { + /* + * If the state queue list is empty, wait until an + * entry is added. + */ + head = &state_queue; + + while (list_empty(head)) { + status = pthread_cond_wait(&cond, &mutex); + if (status) + fatal(status); + } + + p = head->next; + while(p != head) { + struct state_queue *task; + + task = list_entry(p, struct state_queue, list); + p = p->next; + + if (task->cancel) { + list_del(&task->list); + free(task); + continue; + } + + task->busy = 1; + + ret = run_state_task(task); + if (!ret) { + list_del(&task->list); + free(task); + } + } + + while (1) { + signaled = 0; + while (!signaled) { + status = pthread_cond_wait(&cond, &mutex); + if (status) + fatal(status); + } + + head = &state_queue; + p = head->next; + while (p != head) { + struct state_queue *task, *next; + + task = list_entry(p, struct state_queue, list); + p = p->next; + + /* Task may have been canceled before it started */ + if (!task->thid && task->cancel) + goto remove; + + if (!task->busy) { + /* Start a new task */ + task->busy = 1; + + ret = run_state_task(task); + if (!ret) + goto remove; + continue; + } + + /* Still starting up */ + if (!task->thid) + continue; + + if (task->cancel) { + pthread_cancel(task->thid); + task->cancel = 0; + continue; + } + + /* Still busy */ + if (!task->done) + continue; + +remove: + /* No more tasks for this queue */ + if (list_empty(&task->pending)) { + list_del(&task->list); + free(task); + continue; + } + + /* Next task */ + next = list_entry((&task->pending)->next, + struct state_queue, pending); + + list_del(&task->list); + list_del_init(&next->pending); + free(task); + + list_add_tail(&next->list, head); + if (p == head) + p = head->next; + } + + if (list_empty(head)) + break; + } + } +} + +int st_start_handler(void) +{ + pthread_t thid; + pthread_attr_t attrs; + pthread_attr_t *pattrs = &attrs; + int status; + + status = pthread_attr_init(pattrs); + if (status) + pattrs = NULL; + else { + pthread_attr_setdetachstate(pattrs, PTHREAD_CREATE_DETACHED); +#ifdef _POSIX_THREAD_ATTR_STACKSIZE + pthread_attr_setstacksize(pattrs, PTHREAD_STACK_MIN*4); +#endif + } + + status = pthread_create(&thid, pattrs, st_queue_handler, NULL); + + if (pattrs) + pthread_attr_destroy(pattrs); + + return !status; +} + diff --git a/include/automount.h b/include/automount.h new file mode 100644 index 0000000..39e685d --- /dev/null +++ b/include/automount.h @@ -0,0 +1,761 @@ +/* + * automount.h + * + * Header file for automounter modules + * + */ + +#ifndef AUTOMOUNT_H +#define AUTOMOUNT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "list.h" + +#include + +#include "defaults.h" +#include "state.h" +#include "master.h" +#include "macros.h" +#include "log.h" +#include "rpc_subs.h" +#include "mounts.h" +#include "parse_subs.h" +#include "mounts.h" +#include "dev-ioctl-lib.h" +#include "parse_amd.h" + +#ifdef WITH_DMALLOC +#include +#endif + +#define ENABLE_CORES 1 + +/* We MUST have the paths to mount(8) and umount(8) */ +#ifndef HAVE_MOUNT +#error Failed to locate mount(8)! +#endif + +#ifndef HAVE_UMOUNT +#error Failed to locate umount(8)! +#endif + +#ifndef HAVE_MODPROBE +#error Failed to locate modprobe(8)! +#endif + +#ifndef HAVE_LINUX_PROCFS +#error Failed to verify existence of procfs filesystem! +#endif + +#define FS_MODULE_NAME "autofs4" +int load_autofs4_module(void); + +/* The -s (sloppy) option to mount is good, if we have it... */ + +#ifdef HAVE_SLOPPY_MOUNT +#define SLOPPYOPT "-s", /* For use in spawnl() lists */ +#define SLOPPY "-s " /* For use in strings */ +#else +#define SLOPPYOPT +#define SLOPPY +#endif + +#define AUTOFS_SUPER_MAGIC 0x00000187L +#define SMB_SUPER_MAGIC 0x0000517BL +#define CIFS_MAGIC_NUMBER 0xFF534D42L +#define NCP_SUPER_MAGIC 0x0000564CL +#define NFS_SUPER_MAGIC 0x00006969L + +#define ATTEMPT_ID_SIZE 24 + +/* This sould be enough for at least 20 host aliases */ +#define HOST_ENT_BUF_SIZE 2048 + +#define CHECK_RATIO 4 /* exp_runfreq = exp_timeout/CHECK_RATIO */ +#define AUTOFS_LOCK "/var/lock/autofs" /* To serialize access to mount */ +#define MOUNTED_LOCK _PATH_MOUNTED "~" /* mounts' lock file */ +#define MTAB_NOTUPDATED 0x1000 /* mtab succeded but not updated */ +#define NOT_MOUNTED 0x0100 /* path notmounted */ +#define MNT_FORCE_FAIL -1 +#define _PROC_MOUNTS "/proc/mounts" +#define _PROC_SELF_MOUNTS "/proc/self/mounts" + +/* Constants for lookup modules */ + +#define LKP_FAIL 0x0001 + +#define LKP_INDIRECT 0x0002 +#define LKP_DIRECT 0x0004 +#define LKP_MULTI 0x0008 +#define LKP_NOMATCH 0x0010 +#define LKP_MATCH 0x0020 +#define LKP_NEXT 0x0040 +#define LKP_MOUNT 0x0080 +#define LKP_WILD 0x0100 +#define LKP_LOOKUP 0x0200 +#define LKP_GHOST 0x0400 +#define LKP_REREAD 0x0800 +#define LKP_NORMAL 0x1000 +#define LKP_DISTINCT 0x2000 +#define LKP_ERR_MOUNT 0x4000 +#define LKP_NOTSUP 0x8000 + +#define MAX_ERR_BUF 128 + +#ifdef DEBUG +#define DB(x) do { x; } while(0) +#else +#define DB(x) do { } while(0) +#endif + +#define min(a, b) (a <= b ? a : b) + +/* Forward declaraion */ +struct autofs_point; + +/* mapent cache definition */ + +#define CHE_FAIL 0x0000 +#define CHE_OK 0x0001 +#define CHE_UPDATED 0x0002 +#define CHE_RMPATH 0x0004 +#define CHE_MISSING 0x0008 +#define CHE_COMPLETED 0x0010 +#define CHE_DUPLICATE 0x0020 +#define CHE_UNAVAIL 0x0040 + +#define NULL_MAP_HASHSIZE 64 +#define NEGATIVE_TIMEOUT 10 +#define UMOUNT_RETRIES 8 +#define EXPIRE_RETRIES 3 + +static u_int32_t inline hash(const char *key, unsigned int size) +{ + u_int32_t hashval; + char *s = (char *) key; + + for (hashval = 0; *s != '\0';) { + hashval += (unsigned char) *s++; + hashval += (hashval << 10); + hashval ^= (hashval >> 6); + } + + hashval += (hashval << 3); + hashval ^= (hashval >> 11); + hashval += (hashval << 15); + + return hashval % size; +} + +struct mapent_cache { + pthread_rwlock_t rwlock; + unsigned int size; + pthread_mutex_t ino_index_mutex; + struct list_head *ino_index; + struct autofs_point *ap; + struct map_source *map; + struct mapent **hash; +}; + +struct stack { + char *mapent; + time_t age; + struct stack *next; +}; + +struct mapent { + struct mapent *next; + struct list_head ino_index; + pthread_rwlock_t multi_rwlock; + struct list_head multi_list; + struct mapent_cache *mc; + struct map_source *source; + /* Need to know owner if we're a multi-mount */ + struct mapent *multi; + /* Parent nesting point within multi-mount */ + struct mapent *parent; + char *key; + char *mapent; + struct stack *stack; + time_t age; + /* Time of last mount fail */ + time_t status; + /* For direct mounts per entry context is kept here */ + int flags; + /* File descriptor for ioctls */ + int ioctlfd; + dev_t dev; + ino_t ino; +}; + +void cache_lock_cleanup(void *arg); +void cache_readlock(struct mapent_cache *mc); +void cache_writelock(struct mapent_cache *mc); +int cache_try_writelock(struct mapent_cache *mc); +void cache_unlock(struct mapent_cache *mc); +int cache_push_mapent(struct mapent *me, char *mapent); +int cache_pop_mapent(struct mapent *me); +struct mapent_cache *cache_init(struct autofs_point *ap, struct map_source *map); +struct mapent_cache *cache_init_null_cache(struct master *master); +int cache_set_ino_index(struct mapent_cache *mc, const char *key, dev_t dev, ino_t ino); +/* void cache_set_ino(struct mapent *me, dev_t dev, ino_t ino); */ +struct mapent *cache_lookup_ino(struct mapent_cache *mc, dev_t dev, ino_t ino); +struct mapent *cache_lookup_first(struct mapent_cache *mc); +struct mapent *cache_lookup_next(struct mapent_cache *mc, struct mapent *me); +struct mapent *cache_lookup_key_next(struct mapent *me); +struct mapent *cache_lookup(struct mapent_cache *mc, const char *key); +struct mapent *cache_lookup_distinct(struct mapent_cache *mc, const char *key); +struct mapent *cache_lookup_offset(const char *prefix, const char *offset, int start, struct list_head *head); +struct mapent *cache_partial_match(struct mapent_cache *mc, const char *prefix); +struct mapent *cache_partial_match_wild(struct mapent_cache *mc, const char *prefix); +int cache_add(struct mapent_cache *mc, struct map_source *ms, const char *key, const char *mapent, time_t age); +int cache_update_offset(struct mapent_cache *mc, const char *mkey, const char *key, const char *mapent, time_t age); +void cache_update_negative(struct mapent_cache *mc, struct map_source *ms, const char *key, time_t timeout); +int cache_set_parents(struct mapent *mm); +int cache_update(struct mapent_cache *mc, struct map_source *ms, const char *key, const char *mapent, time_t age); +int cache_delete(struct mapent_cache *mc, const char *key); +int cache_delete_offset(struct mapent_cache *mc, const char *key); +void cache_multi_readlock(struct mapent *me); +void cache_multi_writelock(struct mapent *me); +void cache_multi_unlock(struct mapent *me); +int cache_delete_offset_list(struct mapent_cache *mc, const char *key); +void cache_release(struct map_source *map); +void cache_clean_null_cache(struct mapent_cache *mc); +void cache_release_null_cache(struct master *master); +struct mapent *cache_enumerate(struct mapent_cache *mc, struct mapent *me); +char *cache_get_offset(const char *prefix, char *offset, int start, struct list_head *head, struct list_head **pos); + +/* Utility functions */ + +char **add_argv(int argc, char **argv, char *str); +char **append_argv(int argc1, char **argv1, int argc2, char **argv2); +const char **copy_argv(int argc, const char **argv); +int compare_argv(int argc1, const char **argv1, int argc2, const char **argv2); +int free_argv(int argc, const char **argv); + +struct pending_args; +void set_thread_mount_request_log_id(struct pending_args *mt); + +void dump_core(void); +int aquire_lock(void); +void release_lock(void); +int spawnl(unsigned logopt, const char *prog, ...); +int spawnv(unsigned logopt, const char *prog, const char *const *argv); +int spawn_mount(unsigned logopt, ...); +int spawn_bind_mount(unsigned logopt, ...); +int spawn_umount(unsigned logopt, ...); +void reset_signals(void); +int do_mount(struct autofs_point *ap, const char *root, const char *name, + int name_len, const char *what, const char *fstype, + const char *options); +int mkdir_path(const char *path, mode_t mode); +int rmdir_path(struct autofs_point *ap, const char *path, dev_t dev); + +/* Prototype for module functions */ + +/* lookup module */ + +#define AUTOFS_LOOKUP_VERSION 5 + +#define KEY_MAX_LEN NAME_MAX +#define MAPENT_MAX_LEN 16384 +#define PARSE_MAX_BUF KEY_MAX_LEN + MAPENT_MAX_LEN + 2 + +int lookup_nss_read_master(struct master *master, time_t age); +int lookup_nss_read_map(struct autofs_point *ap, struct map_source *source, time_t age); +int lookup_enumerate(struct autofs_point *ap, + int (*fn)(struct autofs_point *,struct mapent *, int), time_t now); +int lookup_ghost(struct autofs_point *ap, const char *root); +int lookup_nss_mount(struct autofs_point *ap, struct map_source *source, const char *name, int name_len); +void lookup_close_lookup(struct autofs_point *ap); +void lookup_prune_one_cache(struct autofs_point *ap, struct mapent_cache *mc, time_t age); +int lookup_prune_cache(struct autofs_point *ap, time_t age); +struct mapent *lookup_source_valid_mapent(struct autofs_point *ap, const char *key, unsigned int type); +struct mapent *lookup_source_mapent(struct autofs_point *ap, const char *key, unsigned int type); +int lookup_source_close_ioctlfd(struct autofs_point *ap, const char *key); + +#ifdef MODULE_LOOKUP +int lookup_init(const char *mapfmt, int argc, const char *const *argv, void **context); +int lookup_reinit(const char *mapfmt, int argc, const char *const *argv, void **context); +int lookup_read_master(struct master *master, time_t age, void *context); +int lookup_read_map(struct autofs_point *, time_t, void *context); +int lookup_mount(struct autofs_point *, const char *, int, void *); +int lookup_done(void *); +#endif +typedef int (*lookup_init_t) (const char *, int, const char *const *, void **); +typedef int (*lookup_reinit_t) (const char *, int, const char *const *, void **); +typedef int (*lookup_read_master_t) (struct master *master, time_t, void *); +typedef int (*lookup_read_map_t) (struct autofs_point *, time_t, void *); +typedef int (*lookup_mount_t) (struct autofs_point *, const char *, int, void *); +typedef int (*lookup_done_t) (void *); + +struct lookup_mod { + lookup_init_t lookup_init; + lookup_reinit_t lookup_reinit; + lookup_read_master_t lookup_read_master; + lookup_read_map_t lookup_read_map; + lookup_mount_t lookup_mount; + lookup_done_t lookup_done; + char *type; + void *dlhandle; + void *context; +}; + +int open_lookup(const char *name, const char *err_prefix, const char *mapfmt, + int argc, const char *const *argv, struct lookup_mod **lookup); +int reinit_lookup(struct lookup_mod *mod, const char *name, + const char *err_prefix, const char *mapfmt, + int argc, const char *const *argv); +int close_lookup(struct lookup_mod *); + +/* parse module */ + +#define AUTOFS_PARSE_VERSION 5 + +#ifdef MODULE_PARSE +int parse_init(int argc, const char *const *argv, void **context); +int parse_reinit(int argc, const char *const *argv, void **context); +int parse_mount(struct autofs_point *ap, const char *name, + int name_len, const char *mapent, void *context); +int parse_done(void *); +#endif +typedef int (*parse_init_t) (int, const char *const *, void **); +typedef int (*parse_reinit_t) (int, const char *const *, void **); +typedef int (*parse_mount_t) (struct autofs_point *, const char *, int, const char *, void *); +typedef int (*parse_done_t) (void *); + +struct parse_mod { + parse_init_t parse_init; + parse_reinit_t parse_reinit; + parse_mount_t parse_mount; + parse_done_t parse_done; + void *dlhandle; + void *context; +}; + +struct parse_mod *open_parse(const char *name, const char *err_prefix, + int argc, const char *const *argv); +int reinit_parse(struct parse_mod *, const char *name, + const char *err_prefix, int argc, const char *const *argv); +int close_parse(struct parse_mod *); + +/* mount module */ + +#define AUTOFS_MOUNT_VERSION 4 + +#ifdef MODULE_MOUNT +int mount_init(void **context); +int mount_reinit(void **context); +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, void *context); +int mount_done(void *context); +#endif +typedef int (*mount_init_t) (void **); +typedef int (*mount_reinit_t) (void **); +typedef int (*mount_mount_t) (struct autofs_point *, const char *, const char *, int, + const char *, const char *, const char *, void *); +typedef int (*mount_done_t) (void *); + +struct mount_mod { + mount_init_t mount_init; + mount_reinit_t mount_reinit; + mount_mount_t mount_mount; + mount_done_t mount_done; + void *dlhandle; + void *context; +}; + +struct mount_mod *open_mount(const char *name, const char *err_prefix); +int reinit_mount(struct mount_mod *mod, const char *name, const char *err_prefix); +int close_mount(struct mount_mod *); + +/* buffer management */ + +size_t _strlen(const char *str, size_t max); +int cat_path(char *buf, size_t len, const char *dir, const char *base); +int ncat_path(char *buf, size_t len, + const char *dir, const char *base, size_t blen); +int _strncmp(const char *s1, const char *s2, size_t n); + +/* Core automount definitions */ + +#ifndef MNT_DETACH +#define MNT_DETACH 0x00000002 /* Just detach from the tree */ +#endif + +struct startup_cond { + pthread_mutex_t mutex; + pthread_cond_t cond; + struct autofs_point *ap; + char *root; + unsigned int done; + unsigned int status; +}; + +int handle_mounts_startup_cond_init(struct startup_cond *suc); +void handle_mounts_startup_cond_destroy(void *arg); + +struct master_readmap_cond { + pthread_mutex_t mutex; + pthread_cond_t cond; + pthread_t thid; /* map reader thread id */ + struct master *master; + time_t age; /* Last time read */ + enum states state; /* Next state */ + unsigned int signaled; /* Condition has been signaled */ + unsigned int busy; /* Map read in progress. */ +}; + +struct pending_args { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned int signaled; /* Condition has been signaled */ + struct autofs_point *ap; /* autofs mount we are working on */ + int status; /* Return status */ + int type; /* Type of packet */ + int ioctlfd; /* Mount ioctl fd */ + struct mapent_cache *mc; /* Cache Containing entry */ + char name[PATH_MAX]; /* Name field of the request */ + dev_t dev; /* device number of mount */ + unsigned int len; /* Name field len */ + uid_t uid; /* uid of requester */ + gid_t gid; /* gid of requester */ + pid_t pid; /* pid of requestor */ + unsigned long wait_queue_token; /* Associated kernel wait token */ +}; + +#ifdef INCLUDE_PENDING_FUNCTIONS +static void pending_cond_init(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + pthread_condattr_t condattrs; + int status; + + status = pthread_condattr_init(&condattrs); + if (status) + fatal(status); + + status = pthread_condattr_setclock(&condattrs, CLOCK_MONOTONIC); + if (status) + fatal(status); + + status = pthread_cond_init(&mt->cond, &condattrs); + if (status) + fatal(status); + + pthread_condattr_destroy(&condattrs); +} + +static void pending_cond_destroy(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + int status; + status = pthread_cond_destroy(&mt->cond); + if (status) + fatal(status); +} + +static void pending_mutex_destroy(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + int status = pthread_mutex_destroy(&mt->mutex); + if (status) + fatal(status); +} + +static void free_pending_args(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + free(mt); +} + +static void pending_mutex_lock(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + int status = pthread_mutex_lock(&mt->mutex); + if (status) + fatal(status); +} + +static void pending_mutex_unlock(void *arg) +{ + struct pending_args *mt = (struct pending_args *) arg; + int status = pthread_mutex_unlock(&mt->mutex); + if (status) + fatal(status); +} +#endif + +struct thread_stdenv_vars { + uid_t uid; + gid_t gid; + char *user; + char *group; + char *home; +}; + +extern pthread_key_t key_thread_stdenv_vars; + +extern pthread_key_t key_thread_attempt_id; + +struct kernel_mod_version { + unsigned int major; + unsigned int minor; +}; + +/* Enable/disable ghosted directories */ +#define MOUNT_FLAG_GHOST 0x0001 + +/* Directory created for this mount? */ +#define MOUNT_FLAG_DIR_CREATED 0x0002 + +/* Use random policy when selecting a host from which to mount */ +#define MOUNT_FLAG_RANDOM_SELECT 0x0004 + +/* Mount being re-mounted */ +#define MOUNT_FLAG_REMOUNT 0x0008 + +/* Use server weight only for selection */ +#define MOUNT_FLAG_USE_WEIGHT_ONLY 0x0010 + +/* Don't use bind mounts even when system supports them */ +#define MOUNT_FLAG_NOBIND 0x0020 + +/* Use symlinks instead of bind mounting local mounts */ +#define MOUNT_FLAG_SYMLINK 0x0040 + +/* Read amd map even if it's not to be ghosted (browsable) */ +#define MOUNT_FLAG_AMD_CACHE_ALL 0x0080 + +struct autofs_point { + pthread_t thid; + char *path; /* Mount point name */ + mode_t mode; /* Mount point mode */ + char *pref; /* amd prefix */ + int pipefd; /* File descriptor for pipe */ + int kpipefd; /* Kernel end descriptor for pipe */ + int ioctlfd; /* File descriptor for ioctls */ + int logpri_fifo; /* FIFO used for changing log levels */ + dev_t dev; /* "Device" number assigned by kernel */ + struct master_mapent *entry; /* Master map entry for this mount */ + unsigned int type; /* Type of map direct or indirect */ + time_t exp_timeout; /* Indirect mount expire timeout */ + time_t exp_runfreq; /* Frequency for polling for timeouts */ + time_t negative_timeout; /* timeout in secs for failed mounts */ + unsigned int flags; /* autofs mount flags */ + unsigned int logopt; /* Per map logging */ + pthread_t exp_thread; /* Thread that is expiring */ + pthread_t readmap_thread; /* Thread that is reading maps */ + enum states state; /* Current state */ + int state_pipe[2]; /* State change router pipe */ + struct autofs_point *parent; /* Owner of mounts list for submount */ + pthread_mutex_t mounts_mutex; /* Protect mount lists */ + struct list_head mounts; /* List of autofs mounts at current level */ + struct list_head amdmounts; /* List of non submount amd mounts */ + unsigned int submount; /* Is this a submount */ + unsigned int shutdown; /* Shutdown notification */ + unsigned int submnt_count; /* Number of submounts */ + struct list_head submounts; /* List of child submounts */ +}; + +/* Foreably unlink existing mounts at startup. */ +extern int do_force_unlink; + +/* Standard functions used by daemon or modules */ + +#define MOUNT_OFFSET_OK 0 +#define MOUNT_OFFSET_FAIL -1 +#define MOUNT_OFFSET_IGNORE -2 + +void *handle_mounts(void *arg); +int umount_multi(struct autofs_point *ap, const char *path, int incl); +int do_expire(struct autofs_point *ap, const char *name, int namelen); +void *expire_proc_indirect(void *); +void *expire_proc_direct(void *); +int expire_offsets_direct(struct autofs_point *ap, struct mapent *me, int now); +int mount_autofs_indirect(struct autofs_point *ap, const char *root); +int do_mount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, struct mapent *me, time_t timeout); +int mount_autofs_direct(struct autofs_point *ap); +int mount_autofs_offset(struct autofs_point *ap, struct mapent *me, const char *root, const char *offset); +void submount_signal_parent(struct autofs_point *ap, unsigned int success); +void close_mount_fds(struct autofs_point *ap); +int umount_autofs(struct autofs_point *ap, const char *root, int force); +int umount_autofs_indirect(struct autofs_point *ap, const char *root); +int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, struct mapent *me); +int umount_autofs_direct(struct autofs_point *ap); +int umount_autofs_offset(struct autofs_point *ap, struct mapent *me); +int handle_packet_expire_indirect(struct autofs_point *ap, autofs_packet_expire_indirect_t *pkt); +int handle_packet_expire_direct(struct autofs_point *ap, autofs_packet_expire_direct_t *pkt); +int handle_packet_missing_indirect(struct autofs_point *ap, autofs_packet_missing_indirect_t *pkt); +int handle_packet_missing_direct(struct autofs_point *ap, autofs_packet_missing_direct_t *pkt); +void rm_unwanted(struct autofs_point *ap, const char *path, int incl); +int count_mounts(struct autofs_point *ap, const char *path, dev_t dev); + +#define mounts_mutex_lock(ap) \ +do { \ + int _m_lock = pthread_mutex_lock(&ap->mounts_mutex); \ + if (_m_lock) \ + fatal(_m_lock); \ +} while (0) + +#define mounts_mutex_unlock(ap) \ +do { \ + int _m_unlock = pthread_mutex_unlock(&ap->mounts_mutex); \ + if (_m_unlock) \ + fatal(_m_unlock); \ +} while(0) + +static inline time_t monotonic_time(time_t *t) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + if (t) + *t = (time_t) ts.tv_sec; + return (time_t) ts.tv_sec; +} + +/* Expire alarm handling routines */ +int alarm_start_handler(void); +int alarm_add(struct autofs_point *ap, time_t seconds); +void alarm_delete(struct autofs_point *ap); + +/* + * Use CLOEXEC flag for open(), pipe(), fopen() (read-only case) and + * socket() if possible. + */ +static int cloexec_works; + +static inline void check_cloexec(int fd) +{ + if (cloexec_works == 0) { + int fl = fcntl(fd, F_GETFD); + if (fl != -1) + cloexec_works = (fl & FD_CLOEXEC) ? 1 : -1; + } + if (cloexec_works > 0) + return; + fcntl(fd, F_SETFD, FD_CLOEXEC); + return; +} + +static inline int open_fd(const char *path, int flags) +{ + int fd; + +#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) + if (cloexec_works != -1) + flags |= O_CLOEXEC; +#endif + fd = open(path, flags); + if (fd == -1) + return -1; + check_cloexec(fd); + return fd; +} + +static inline int open_fd_mode(const char *path, int flags, int mode) +{ + int fd; + +#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) + if (cloexec_works != -1) + flags |= O_CLOEXEC; +#endif + fd = open(path, flags, mode); + if (fd == -1) + return -1; + check_cloexec(fd); + return fd; +} + +static inline int open_pipe(int pipefd[2]) +{ + int ret; + +#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) && defined(__have_pipe2) + if (cloexec_works != -1) { + ret = pipe2(pipefd, O_CLOEXEC); + if (ret != -1) + return 0; + if (errno != EINVAL) + return -1; + } +#endif + ret = pipe(pipefd); + if (ret == -1) + return -1; + check_cloexec(pipefd[0]); + check_cloexec(pipefd[1]); + return 0; +} + +static inline int open_sock(int domain, int type, int protocol) +{ + int fd; + +#ifdef SOCK_CLOEXEC + if (cloexec_works != -1) + type |= SOCK_CLOEXEC; +#endif + fd = socket(domain, type, protocol); + if (fd == -1) + return -1; + check_cloexec(fd); + return fd; +} + +static inline FILE *open_fopen_r(const char *path) +{ + FILE *f; + +#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) + if (cloexec_works != -1) { + f = fopen(path, "re"); + if (f != NULL) { + check_cloexec(fileno(f)); + return f; + } + } +#endif + f = fopen(path, "r"); + if (f == NULL) + return NULL; + check_cloexec(fileno(f)); + return f; +} + +static inline FILE *open_setmntent_r(const char *table) +{ + FILE *tab; + +#if defined(O_CLOEXEC) && defined(SOCK_CLOEXEC) + if (cloexec_works != -1) { + tab = setmntent(table, "re"); + if (tab != NULL) { + check_cloexec(fileno(tab)); + return tab; + } + } +#endif + tab = fopen(table, "r"); + if (tab == NULL) + return NULL; + check_cloexec(fileno(tab)); + return tab; +} + +#endif + diff --git a/include/base64.h b/include/base64.h new file mode 100644 index 0000000..ce46fcc --- /dev/null +++ b/include/base64.h @@ -0,0 +1,11 @@ + +#ifndef BASE64_H +#define BASE64_H + +#include +#include + +int base64_encode(char *, size_t, char *, size_t); +size_t base64_decode(char *, char *, size_t); + +#endif diff --git a/include/config.h.in b/include/config.h.in new file mode 100644 index 0000000..e888509 --- /dev/null +++ b/include/config.h.in @@ -0,0 +1,166 @@ +/* include/config.h.in. Generated from configure.in by autoheader. */ + +/* leave this alone */ +#undef ENABLE_EXT_ENV + +/* Enable forced shutdown on USR1 signal */ +#undef ENABLE_FORCED_SHUTDOWN + +/* Enable exit, ignoring busy mounts */ +#undef ENABLE_IGNORE_BUSY_MOUNTS + +/* Enable limit stack use of getgrgid_r() */ +#undef ENABLE_LIMIT_GETGRGID_SIZE + +/* Disable use of locking when spawning mount command */ +#undef ENABLE_MOUNT_LOCKING + +/* define if you have E2FSCK */ +#undef HAVE_E2FSCK + +/* define if you have E3FSCK */ +#undef HAVE_E3FSCK + +/* define if you have E4FSCK */ +#undef HAVE_E4FSCK + +/* Define to 1 if you have the `getrpcbyname' function. */ +#undef HAVE_GETRPCBYNAME + +/* Define to 1 if you have the `getservbyname' function. */ +#undef HAVE_GETSERVBYNAME + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `krb5_principal_get_realm' function. */ +#undef HAVE_KRB5_PRINCIPAL_GET_REALM + +/* Define to 1 if you have the `ldap_create_page_control' function. */ +#undef HAVE_LDAP_CREATE_PAGE_CONTROL + +/* Define to 1 if you have the `ldap_parse_page_control' function. */ +#undef HAVE_LDAP_PARSE_PAGE_CONTROL + +/* Define if you have the Linux /proc filesystem. */ +#undef HAVE_LINUX_PROCFS + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* define if you have MODPROBE */ +#undef HAVE_MODPROBE + +/* define if you have MOUNT */ +#undef HAVE_MOUNT + +/* define if you have MOUNT_NFS */ +#undef HAVE_MOUNT_NFS + +/* define if the mount command supports the -s option */ +#undef HAVE_SLOPPY_MOUNT + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* define if you have UMOUNT */ +#undef HAVE_UMOUNT + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if using YellowPages */ +#undef HAVE_YPCLNT + +/* Use libxml2 tsd usage workaround */ +#undef LIBXML2_WORKAROUND + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* define if you have E2FSCK */ +#undef PATH_E2FSCK + +/* define if you have E3FSCK */ +#undef PATH_E3FSCK + +/* define if you have E4FSCK */ +#undef PATH_E4FSCK + +/* define if you have LEX */ +#undef PATH_LEX + +/* define if you have MODPROBE */ +#undef PATH_MODPROBE + +/* define if you have MOUNT */ +#undef PATH_MOUNT + +/* define if you have MOUNT_NFS */ +#undef PATH_MOUNT_NFS + +/* define if you have RANLIB */ +#undef PATH_RANLIB + +/* define if you have RPCGEN */ +#undef PATH_RPCGEN + +/* define if you have UMOUNT */ +#undef PATH_UMOUNT + +/* define if you have YACC */ +#undef PATH_YACC + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 to use the libtirpc tsd usage workaround */ +#undef TIRPC_WORKAROUND + +/* Define if your C library does not provide versionsort */ +#undef WITHOUT_VERSIONSORT + +/* Define if using the dmalloc debugging malloc package */ +#undef WITH_DMALLOC + +/* Define if using Hesiod as a source of automount maps */ +#undef WITH_HESIOD + +/* Define if using LDAP as a source of automount maps */ +#undef WITH_LDAP + +/* Define to 1 if you have the libtirpc library installed */ +#undef WITH_LIBTIRPC + +/* Define if using SASL authentication with the LDAP module */ +#undef WITH_SASL diff --git a/include/dclist.h b/include/dclist.h new file mode 100644 index 0000000..ed89f97 --- /dev/null +++ b/include/dclist.h @@ -0,0 +1,14 @@ +#ifndef __DCLIST_H +#define __DCLIST_H + +#include + +struct dclist { + time_t expire; + const char *uri; +}; + +struct dclist *get_dc_list(unsigned int logopt, const char *uri); +void free_dclist(struct dclist *dclist); + +#endif diff --git a/include/defaults.h b/include/defaults.h new file mode 100644 index 0000000..b28fde3 --- /dev/null +++ b/include/defaults.h @@ -0,0 +1,209 @@ +/* ----------------------------------------------------------------------- * + * + * defaults.h - system initialization defaults. + * + * Copyright 2013 Red Hat, Inc. + * Copyright 2006, 2013 Ian Kent + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifndef DEFAULTS_H +#define DEFAULTS_H + +#define DEFAULT_MASTER_MAP_NAME "auto.master" + +#define DEFAULT_TIMEOUT "600" +#define DEFAULT_MASTER_WAIT "10" +#define DEFAULT_NEGATIVE_TIMEOUT "60" +#define DEFAULT_MOUNT_WAIT "-1" +#define DEFAULT_UMOUNT_WAIT "12" +#define DEFAULT_BROWSE_MODE "1" +#define DEFAULT_LOGGING "none" +#define DEFAULT_FORCE_STD_PROG_MAP_ENV "0" + +#define DEFAULT_LDAP_TIMEOUT "-1" +#define DEFAULT_LDAP_NETWORK_TIMEOUT "8" + +#define DEFAULT_MAP_OBJ_CLASS "nisMap" +#define DEFAULT_ENTRY_OBJ_CLASS "nisObject" +#define DEFAULT_MAP_ATTR "nisMapName" +#define DEFAULT_ENTRY_ATTR "cn" +#define DEFAULT_VALUE_ATTR "nisMapEntry" + +#define DEFAULT_MOUNT_NFS_DEFAULT_PROTOCOL "3" +#define DEFAULT_APPEND_OPTIONS "1" +#define DEFAULT_AUTH_CONF_FILE AUTOFS_MAP_DIR "/autofs_ldap_auth.conf" + +#define DEFAULT_MAP_HASH_TABLE_SIZE "1024" + +#define DEFAULT_USE_HOSTNAME_FOR_MOUNTS "0" +#define DEFAULT_DISABLE_NOT_FOUND_MESSAGE "0" + +#define DEFAULT_SSS_MASTER_MAP_WAIT "0" +#define DEFAULT_USE_MOUNT_REQUEST_LOG_ID "0" + +/* Config entry flags */ +#define CONF_NONE 0x00000000 +#define CONF_ENV 0x00000001 +#define CONF_NOTUSED 0x00000002 +#define CONF_NOTSUP 0x00000004 +#define CONF_BROWSABLE_DIRS 0x00000008 +#define CONF_MOUNT_TYPE_AUTOFS 0x00000010 +#define CONF_SELECTORS_IN_DEFAULTS 0x00000020 +#define CONF_NORMALIZE_HOSTNAMES 0x00000040 +#define CONF_PROCESS_LOCK 0x00000080 +#define CONF_RESTART_EXISTING_MOUNTS 0x00000100 +#define CONF_SHOW_STATFS_ENTRIES 0x00000200 +#define CONF_FULLY_QUALIFIED_HOSTS 0x00000400 +#define CONF_UNMOUNT_ON_EXIT 0x00000800 +#define CONF_AUTOFS_USE_LOFS 0x00001000 +#define CONF_DOMAIN_STRIP 0x00002000 +#define CONF_NORMALIZE_SLASHES 0x00004000 +#define CONF_FORCED_UNMOUNTS 0x00008000 + +#define DEFAULT_AMD_NULL_VALUE NULL + +#define DEFAULT_AMD_AUTO_ATTRCACHE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_AUTO_DIR "/a" +#define DEFAULT_AMD_AUTOFS_USE_LOFS "yes" +#define DEFAULT_AMD_BROWSABLE_DIRS "no" +#define DEFAULT_AMD_CACHE_DURATION "300" +#define DEFAULT_AMD_CLUSTER DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_DEBUG_MTAB_FILE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_DEBUG_OPTIONS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_DISMOUNT_INTERVAL DEFAULT_TIMEOUT +#define DEFAULT_AMD_DOMAIN_STRIP "yes" +#define DEFAULT_AMD_EXEC_MAP_TIMEOUT "10" +#define DEFAULT_AMD_FORCED_UMOUNTS "no" +#define DEFAULT_AMD_FULLY_QUALIFIED_HOSTS "no" +#define DEFAULT_AMD_FULL_OS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_HESIOD_BASE "automount" +#define DEFAULT_AMD_KARCH DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_LDAP_BASE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_LDAP_CACHE_MAXMEM "131072" +#define DEFAULT_AMD_LDAP_CACHE_SECONDS "0" +#define DEFAULT_AMD_LDAP_HOSTPORTS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_LDAP_PROTO_VERSION "2" +#define DEFAULT_AMD_SUB_DOMAIN DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_LOCALHOST_ADDRESS "localhost" +#define DEFAULT_AMD_LOG_FILE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_LOG_OPTIONS "defaults" +#define DEFAULT_AMD_MAP_DEFAULTS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_MAP_OPTIONS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_MAP_RELOAD_INTERVAL "3600" +#define DEFAULT_AMD_MAP_TYPE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_MOUNT_TYPE "autofs" +#define DEFAULT_AMD_PID_FILE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_PORTMAP_PROGRAM DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_PREFERRED_AMQ_PORT DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_ALLOW_ANY_INTERFACE DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_ALLOW_INSECURE_PORT DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_PROTO DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRANSMIT_COUNTER DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRANSMIT_COUNTER_UDP DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRANSMIT_COUNTER_TCP DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRANSMIT_COUNTER_TOPLVL DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRY_INTERVAL DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRY_INTERVAL_UDP DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRY_INTERVAL_TCP DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_RETRY_INTERVAL_TOPLVL DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_VERS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NFS_VERS_PING DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NIS_DOMAIN DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_NORMALIZE_HOSTNAMES "no" +#define DEFAULT_AMD_NORMALIZE_SLASHES "yes" +#define DEFAULT_AMD_OS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_OSVER DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_PLOCK "yes" +#define DEFAULT_AMD_PRINT_PID DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_PRINT_VERSION DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_RESTART_MOUNTS "no" +#define DEFAULT_AMD_SEARCH_PATH DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_SELECTORS_IN_DEFAULTS "no" +#define DEFAULT_AMD_SHOW_STATFS_ENTRIES DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_SUN_MAP_SYNTAX DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_TRUNCATE_LOG DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_UMOUNT_ON_EXIT "yes" +#define DEFAULT_AMD_USE_TCPWRAPPERS DEFAULT_AMD_NULL_VALUE +#define DEFAULT_AMD_VENDOR "unknown" +#define DEFAULT_AMD_LINUX_UFS_MOUNT_TYPE "ext3" + +#ifdef WITH_LDAP +struct ldap_schema; +struct ldap_searchdn; +void defaults_free_uris(struct list_head *); +struct list_head *defaults_get_uris(void); +struct ldap_schema *defaults_get_default_schema(void); +void defaults_free_searchdns(struct ldap_searchdn *); +struct ldap_searchdn *defaults_get_searchdns(void); +struct ldap_schema *defaults_get_schema(void); +#endif + +unsigned int defaults_read_config(unsigned int); +void defaults_conf_release(void); +const char *defaults_get_master_map(void); +int defaults_master_set(void); +unsigned int defaults_get_timeout(void); +int defaults_get_master_wait(void); +unsigned int defaults_get_negative_timeout(void); +unsigned int defaults_get_browse_mode(void); +unsigned int defaults_get_logging(void); +unsigned int defaults_force_std_prog_map_env(void); +const char *defaults_get_ldap_server(void); +unsigned int defaults_get_ldap_timeout(void); +unsigned int defaults_get_ldap_network_timeout(void); +unsigned int defaults_get_mount_nfs_default_proto(void); +unsigned int defaults_get_append_options(void); +unsigned int defaults_get_mount_wait(void); +unsigned int defaults_get_umount_wait(void); +const char *defaults_get_auth_conf_file(void); +unsigned int defaults_get_map_hash_table_size(void); +unsigned int defaults_use_hostname_for_mounts(void); +unsigned int defaults_disable_not_found_message(void); +unsigned int defaults_get_sss_master_map_wait(void); +unsigned int defaults_get_use_mount_request_log_id(void); + +unsigned int conf_amd_mount_section_exists(const char *); +char **conf_amd_get_mount_paths(void); +char *conf_amd_get_arch(void); +char *conf_amd_get_karch(void); +char *conf_amd_get_os(void); +char *conf_amd_get_os_ver(void); +char *conf_amd_get_vendor(void); +char *conf_amd_get_full_os(void); +char *conf_amd_get_auto_dir(void); +char *conf_amd_get_cluster(void); +unsigned int conf_amd_get_exec_map_timeout(void); +char *conf_amd_get_hesiod_base(void); +char *conf_amd_get_karch(void); +char *conf_amd_get_ldap_base(void); +char *conf_amd_get_ldap_hostports(void); +char *conf_amd_get_sub_domain(void); +char *conf_amd_get_localhost_address(void); +unsigned int conf_amd_get_log_options(void); +char *conf_amd_get_nfs_proto(void); +char *conf_amd_get_nis_domain(void); +unsigned int conf_amd_set_nis_domain(const char *); +char *conf_amd_get_map_defaults(const char *); +char *conf_amd_get_map_name(const char *); +char *conf_amd_get_map_options(const char *); +char *conf_amd_get_map_type(const char *); +char *conf_amd_get_search_path(const char *); +unsigned int conf_amd_get_dismount_interval(const char *); +char *conf_amd_get_linux_ufs_mount_type(void); +unsigned long conf_amd_get_flags(const char *); + +#endif + diff --git a/include/dev-ioctl-lib.h b/include/dev-ioctl-lib.h new file mode 100644 index 0000000..eb9075c --- /dev/null +++ b/include/dev-ioctl-lib.h @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------- * + * + * dev-ioctl-lib.h - autofs device control. + * + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifndef AUTOFS_DEV_IOCTL_LIB_H +#define AUTOFS_DEV_IOCTL_LIB_H + +#include + +#define CONTROL_DEVICE "/dev/autofs" + +#define DEV_IOCTL_IS_MOUNTED 0x0001 +#define DEV_IOCTL_IS_AUTOFS 0x0002 +#define DEV_IOCTL_IS_OTHER 0x0004 + +struct ioctl_ctl { + int devfd; + struct ioctl_ops *ops; +}; + +struct ioctl_ops { + int (*version)(unsigned int, int, struct autofs_dev_ioctl *); + int (*protover)(unsigned int, int, unsigned int *); + int (*protosubver)(unsigned int, int, unsigned int *); + int (*mount_device)(unsigned int, const char *, unsigned int, dev_t *); + int (*open)(unsigned int, int *, dev_t, const char *); + int (*close)(unsigned int, int); + int (*send_ready)(unsigned int, int, unsigned int); + int (*send_fail)(unsigned int, int, unsigned int, int); + int (*setpipefd)(unsigned int, int, int); + int (*catatonic)(unsigned int, int); + int (*timeout)(unsigned int, int, time_t); + int (*requester)(unsigned int, int, const char *, uid_t *, gid_t *); + int (*expire)(unsigned int, int, const char *, unsigned int); + int (*askumount)(unsigned int, int, unsigned int *); + int (*ismountpoint)(unsigned int, int, const char *, unsigned int *); +}; + +void init_ioctl_ctl(void); +void close_ioctl_ctl(void); +struct ioctl_ops *get_ioctl_ops(void); +struct autofs_dev_ioctl *alloc_ioctl_ctl_open(const char *, unsigned int); +void free_ioctl_ctl_open(struct autofs_dev_ioctl *); + +#endif + diff --git a/include/linux/auto_dev-ioctl.h b/include/linux/auto_dev-ioctl.h new file mode 100644 index 0000000..850f39b --- /dev/null +++ b/include/linux/auto_dev-ioctl.h @@ -0,0 +1,229 @@ +/* + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_DEV_IOCTL_H +#define _LINUX_AUTO_DEV_IOCTL_H + +#include + +#ifdef __KERNEL__ +#include +#else +#include +#endif /* __KERNEL__ */ + +#define AUTOFS_DEVICE_NAME "autofs" + +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 + +#define AUTOFS_DEVID_LEN 16 + +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) + +/* + * An ioctl interface for autofs mount point control. + */ + +struct args_protover { + __u32 version; +}; + +struct args_protosubver { + __u32 sub_version; +}; + +struct args_openmount { + __u32 devid; +}; + +struct args_ready { + __u32 token; +}; + +struct args_fail { + __u32 token; + __s32 status; +}; + +struct args_setpipefd { + __s32 pipefd; +}; + +struct args_timeout { + __u64 timeout; +}; + +struct args_requester { + __u32 uid; + __u32 gid; +}; + +struct args_expire { + __u32 how; +}; + +struct args_askumount { + __u32 may_umount; +}; + +struct args_ismountpoint { + union { + struct args_in { + __u32 type; + } in; + struct args_out { + __u32 devid; + __u32 magic; + } out; + }; +}; + +/* + * All the ioctls use this structure. + * When sending a path size must account for the total length + * of the chunk of memory otherwise is is the size of the + * structure. + */ + +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[0]; +}; + +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) +{ + memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; + return; +} + +/* + * If you change this make sure you make the corresponding change + * to autofs-dev-ioctl.c:lookup_ioctl() + */ +enum { + /* Get various version info */ + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, + AUTOFS_DEV_IOCTL_PROTOVER_CMD, + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, + + /* Open mount ioctl fd */ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, + + /* Close mount ioctl fd */ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, + + /* Mount/expire status returns */ + AUTOFS_DEV_IOCTL_READY_CMD, + AUTOFS_DEV_IOCTL_FAIL_CMD, + + /* Activate/deactivate autofs mount */ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, + AUTOFS_DEV_IOCTL_CATATONIC_CMD, + + /* Expiry timeout */ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, + + /* Get mount last requesting uid and gid */ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, + + /* Check for eligible expire candidates */ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, + + /* Request busy status */ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, + + /* Check if path is a mountpoint */ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, +}; + +#define AUTOFS_IOCTL 0x93 + +#define AUTOFS_DEV_IOCTL_VERSION \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_READY \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_FAIL \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CATATONIC \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_TIMEOUT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_REQUESTER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_EXPIRE \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) + +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/include/linux/auto_fs.h b/include/linux/auto_fs.h new file mode 100644 index 0000000..64df1a6 --- /dev/null +++ b/include/linux/auto_fs.h @@ -0,0 +1,74 @@ +/* -*- linux-c -*- ------------------------------------------------------- * + * + * linux/include/linux/auto_fs.h + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + + +#ifndef _LINUX_AUTO_FS_H +#define _LINUX_AUTO_FS_H + +#include +#ifndef __KERNEL__ +#include +#endif /* __KERNEL__ */ + +/* This file describes autofs v3 */ +#define AUTOFS_PROTO_VERSION 3 + +/* Range of protocol versions defined */ +#define AUTOFS_MAX_PROTO_VERSION AUTOFS_PROTO_VERSION +#define AUTOFS_MIN_PROTO_VERSION AUTOFS_PROTO_VERSION + +/* + * The wait_queue_token (autofs_wqt_t) is part of a structure which is passed + * back to the kernel via ioctl from userspace. On architectures where 32- and + * 64-bit userspace binaries can be executed it's important that the size of + * autofs_wqt_t stays constant between 32- and 64-bit Linux kernels so that we + * do not break the binary ABI interface by changing the structure size. + */ +#if defined(__ia64__) || defined(__alpha__) /* pure 64bit architectures */ +typedef unsigned long autofs_wqt_t; +#else +typedef unsigned int autofs_wqt_t; +#endif + +/* Packet types */ +#define autofs_ptype_missing 0 /* Missing entry (mount request) */ +#define autofs_ptype_expire 1 /* Expire entry (umount request) */ + +struct autofs_packet_hdr { + int proto_version; /* Protocol version */ + int type; /* Type of packet */ +}; + +/* v3 missing */ +struct autofs_packet_missing { + struct autofs_packet_hdr hdr; + autofs_wqt_t wait_queue_token; + int len; + char name[NAME_MAX+1]; +}; + +/* v3 expire (via ioctl) */ +struct autofs_packet_expire { + struct autofs_packet_hdr hdr; + int len; + char name[NAME_MAX+1]; +}; + +#define AUTOFS_IOC_READY _IO(0x93,0x60) +#define AUTOFS_IOC_FAIL _IO(0x93,0x61) +#define AUTOFS_IOC_CATATONIC _IO(0x93,0x62) +#define AUTOFS_IOC_PROTOVER _IOR(0x93,0x63,int) +#define AUTOFS_IOC_SETTIMEOUT32 _IOWR(0x93,0x64,compat_ulong_t) +#define AUTOFS_IOC_SETTIMEOUT _IOWR(0x93,0x64,unsigned long) +#define AUTOFS_IOC_EXPIRE _IOR(0x93,0x65,struct autofs_packet_expire) + +#endif /* _LINUX_AUTO_FS_H */ diff --git a/include/linux/auto_fs4.h b/include/linux/auto_fs4.h new file mode 100644 index 0000000..e02982f --- /dev/null +++ b/include/linux/auto_fs4.h @@ -0,0 +1,164 @@ +/* -*- c -*- + * linux/include/linux/auto_fs4.h + * + * Copyright 1999-2000 Jeremy Fitzhardinge + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_FS4_H +#define _LINUX_AUTO_FS4_H + +/* Include common v3 definitions */ +#include +#include + +/* autofs v4 definitions */ +#undef AUTOFS_PROTO_VERSION +#undef AUTOFS_MIN_PROTO_VERSION +#undef AUTOFS_MAX_PROTO_VERSION + +#define AUTOFS_PROTO_VERSION 5 +#define AUTOFS_MIN_PROTO_VERSION 3 +#define AUTOFS_MAX_PROTO_VERSION 5 + +#define AUTOFS_PROTO_SUBVERSION 2 + +/* Mask for expire behaviour */ +#define AUTOFS_EXP_IMMEDIATE 1 +#define AUTOFS_EXP_LEAVES 2 + +#define AUTOFS_TYPE_ANY 0U +#define AUTOFS_TYPE_INDIRECT 1U +#define AUTOFS_TYPE_DIRECT 2U +#define AUTOFS_TYPE_OFFSET 4U + +static inline void set_autofs_type_indirect(unsigned int *type) +{ + *type = AUTOFS_TYPE_INDIRECT; + return; +} + +static inline unsigned int autofs_type_indirect(unsigned int type) +{ + return (type == AUTOFS_TYPE_INDIRECT); +} + +static inline void set_autofs_type_direct(unsigned int *type) +{ + *type = AUTOFS_TYPE_DIRECT; + return; +} + +static inline unsigned int autofs_type_direct(unsigned int type) +{ + return (type == AUTOFS_TYPE_DIRECT); +} + +static inline void set_autofs_type_offset(unsigned int *type) +{ + *type = AUTOFS_TYPE_OFFSET; + return; +} + +static inline unsigned int autofs_type_offset(unsigned int type) +{ + return (type == AUTOFS_TYPE_OFFSET); +} + +static inline unsigned int autofs_type_trigger(unsigned int type) +{ + return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); +} + +/* + * This isn't really a type as we use it to say "no type set" to + * indicate we want to search for "any" mount in the + * autofs_dev_ioctl_ismountpoint() device ioctl function. + */ +static inline void set_autofs_type_any(unsigned int *type) +{ + *type = AUTOFS_TYPE_ANY; + return; +} + +static inline unsigned int autofs_type_any(unsigned int type) +{ + return (type == AUTOFS_TYPE_ANY); +} + +/* Daemon notification packet types */ +enum autofs_notify { + NFY_NONE, + NFY_MOUNT, + NFY_EXPIRE +}; + +/* Kernel protocol version 4 packet types */ + +/* Expire entry (umount request) */ +#define autofs_ptype_expire_multi 2 + +/* Kernel protocol version 5 packet types */ + +/* Indirect mount missing and expire requests. */ +#define autofs_ptype_missing_indirect 3 +#define autofs_ptype_expire_indirect 4 + +/* Direct mount missing and expire requests */ +#define autofs_ptype_missing_direct 5 +#define autofs_ptype_expire_direct 6 + +/* v4 multi expire (via pipe) */ +struct autofs_packet_expire_multi { + struct autofs_packet_hdr hdr; + autofs_wqt_t wait_queue_token; + int len; + char name[NAME_MAX+1]; +}; + +union autofs_packet_union { + struct autofs_packet_hdr hdr; + struct autofs_packet_missing missing; + struct autofs_packet_expire expire; + struct autofs_packet_expire_multi expire_multi; +}; + +/* autofs v5 common packet struct */ +struct autofs_v5_packet { + struct autofs_packet_hdr hdr; + autofs_wqt_t wait_queue_token; + __u32 dev; + __u64 ino; + __u32 uid; + __u32 gid; + __u32 pid; + __u32 tgid; + __u32 len; + char name[NAME_MAX+1]; +}; + +typedef struct autofs_v5_packet autofs_packet_missing_indirect_t; +typedef struct autofs_v5_packet autofs_packet_expire_indirect_t; +typedef struct autofs_v5_packet autofs_packet_missing_direct_t; +typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; + struct autofs_v5_packet v5_packet; + autofs_packet_missing_indirect_t missing_indirect; + autofs_packet_expire_indirect_t expire_indirect; + autofs_packet_missing_direct_t missing_direct; + autofs_packet_expire_direct_t expire_direct; +}; + +#define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) +#define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI +#define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI +#define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +#define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +#endif /* _LINUX_AUTO_FS4_H */ diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..516c4d0 --- /dev/null +++ b/include/list.h @@ -0,0 +1,158 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list in reverse + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +#endif diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..7a394cb --- /dev/null +++ b/include/log.h @@ -0,0 +1,97 @@ +/* ----------------------------------------------------------------------- * + * + * log.c - applcation logging declarations. + * + * Copyright 2004 Denis Vlasenko + * - All Rights Reserved + * Copyright 2005 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef LOG_H +#define LOG_H + +/* Define logging functions */ + +#define LOGOPT_NONE 0x0000 +#define LOGOPT_ERROR 0x0000 +#define LOGOPT_DEBUG 0x0001 +#define LOGOPT_VERBOSE 0x0002 +#define LOGOPT_ANY (LOGOPT_DEBUG | LOGOPT_VERBOSE) + +struct autofs_point; + +extern void set_log_norm(void); +extern void set_log_verbose(void); +extern void set_log_debug(void); +extern void set_log_norm_ap(struct autofs_point *ap); +extern void set_log_verbose_ap(struct autofs_point *ap); +extern void set_log_debug_ap(struct autofs_point *ap); +extern void set_mnt_logging(unsigned global_logopt); + +extern void open_log(void); +extern void log_to_syslog(void); +extern void log_to_stderr(void); + +extern void log_info(unsigned int, const char* msg, ...); +extern void log_notice(unsigned int, const char* msg, ...); +extern void log_warn(unsigned int, const char* msg, ...); +extern void log_error(unsigned, const char* msg, ...); +extern void log_crit(unsigned, const char* msg, ...); +extern void log_debug(unsigned int, const char* msg, ...); +extern void logmsg(const char* msg, ...); + +#define debug(opt, msg, args...) \ + do { log_debug(opt, "%s: " msg, __FUNCTION__, ##args); } while (0) + +#define info(opt, msg, args...) \ + do { log_info(opt, msg, ##args); } while (0) + +#define notice(opt, msg, args...) \ + do { log_notice(opt, msg, ##args); } while (0) + +#define warn(opt, msg, args...) \ + do { log_warn(opt, msg, ##args); } while (0) + +#define error(opt, msg, args...) \ + do { log_error(opt, "%s: " msg, __FUNCTION__, ##args); } while (0) + +#define crit(opt, msg, args...) \ + do { log_crit(opt, "%s: " msg, __FUNCTION__, ##args); } while (0) + +#define logerr(msg, args...) \ + do { logmsg("%s:%d: " msg, __FUNCTION__, __LINE__, ##args); } while (0) + +#define fatal(status) \ + do { \ + if (status == EDEADLK) { \ + logmsg("deadlock detected " \ + "at line %d in %s, dumping core.", \ + __LINE__, __FILE__); \ + dump_core(); \ + } \ + logmsg("unexpected pthreads error: %d at %d " \ + "in %s", status, __LINE__, __FILE__); \ + abort(); \ + } while(0) + +#ifndef NDEBUG +#define assert(x) \ +do { \ + if (!(x)) { \ + logmsg(__FILE__ \ + ":%d: assertion failed: " #x, __LINE__); \ + } \ +} while(0) +#else +#define assert(x) do { } while(0) +#endif + +#endif + diff --git a/include/lookup_ldap.h b/include/lookup_ldap.h new file mode 100644 index 0000000..3a7aba7 --- /dev/null +++ b/include/lookup_ldap.h @@ -0,0 +1,137 @@ +#ifndef LOOKUP_LDAP_H +#define LOOKUP_LDAP_H + +#include + +#ifdef WITH_SASL +#include +#include +#include +#include +#include +#endif + +#include + +#include "list.h" +#include "dclist.h" + +struct ldap_schema { + char *map_class; + char *map_attr; + char *entry_class; + char *entry_attr; + char *value_attr; +}; + +struct ldap_uri { + char *uri; + struct list_head list; +}; + +struct ldap_searchdn { + char *basedn; + struct ldap_searchdn *next; +}; + +struct ldap_conn { + LDAP *ldap; +#ifdef WITH_SASL + sasl_conn_t *sasl_conn; +#endif +}; + +struct lookup_context { + char *mapname; + unsigned int format; + + char *server; + int port; + char *base; + char *qdn; + unsigned int timeout; + unsigned int network_timeout; + unsigned long timestamp; + unsigned int check_defaults; + + /* LDAP version 2 or 3 */ + int version; + + /* LDAP lookup configuration */ + struct ldap_schema *schema; + + /* + * List of servers and base dns for searching. + * uri is the list of servers to attempt connection to and is + * used only if server, above, is NULL. The head of the list + * is the server which we are currently connected to. + * cur_host tracks chnages to connected server, triggering + * a scan of basedns when it changes. + * sdns is the list of basdns to check, done in the order + * given in configuration. + */ + pthread_mutex_t uris_mutex; + struct list_head *uris; + struct ldap_uri *uri; + struct dclist *dclist; + char *cur_host; + struct ldap_searchdn *sdns; + + /* TLS and SASL authentication information */ + char *auth_conf; + unsigned use_tls; + unsigned tls_required; + unsigned auth_required; + char *sasl_mech; + char *user; + char *secret; + char *client_princ; + char *client_cc; + int kinit_done; + int kinit_successful; +#ifdef WITH_SASL + /* Kerberos */ + krb5_context krb5ctxt; + krb5_ccache krb5_ccache; + /* SASL external */ + char *extern_cert; + char *extern_key; +#endif + /* keytab file name needs to be added */ + + struct parse_mod *parse; +}; + + +#define LDAP_AUTH_CONF_FILE "test" + +#define LDAP_TLS_DONT_USE 0 +#define LDAP_TLS_REQUIRED 1 +#define LDAP_TLS_INIT 1 +#define LDAP_TLS_RELEASE 2 + +#define LDAP_AUTH_NOTREQUIRED 0x0001 +#define LDAP_AUTH_REQUIRED 0x0002 +#define LDAP_AUTH_AUTODETECT 0x0004 +#define LDAP_NEED_AUTH (LDAP_AUTH_REQUIRED|LDAP_AUTH_AUTODETECT) + +#define LDAP_AUTH_USESIMPLE 0x0008 + +/* lookup_ldap.c */ +LDAP *init_ldap_connection(unsigned logopt, const char *uri, struct lookup_context *ctxt); +int unbind_ldap_connection(unsigned logopt, struct ldap_conn *conn, struct lookup_context *ctxt); +int authtype_requires_creds(const char *authtype); + +#ifdef WITH_SASL +/* cyrus-sasl.c */ +int autofs_sasl_client_init(unsigned logopt); +int autofs_sasl_init(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt); +int autofs_sasl_bind(unsigned logopt, struct ldap_conn *conn, struct lookup_context *ctxt); +void autofs_sasl_unbind(struct ldap_conn *conn, struct lookup_context *ctxt); +void autofs_sasl_dispose(struct ldap_conn *conn, struct lookup_context *ctxt); +void autofs_sasl_done(void); +/* cyrus-sasl-extern */ +int do_sasl_extern(LDAP *ldap, struct lookup_context *ctxt); +#endif + +#endif diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..5a5486e --- /dev/null +++ b/include/macros.h @@ -0,0 +1,45 @@ +/* ----------------------------------------------------------------------- * + * + * macros.h - header file for module to handle macro substitution + * variables for map entries. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef MACROS_H +#define MACROS_H + +#define MAX_MACRO_STRING 128 + +struct substvar { + char *def; /* Define variable */ + char *val; /* Value to replace with */ + unsigned int readonly; /* System vars are readonly */ + struct substvar *next; +}; + +void macro_init(void); +int macro_is_systemvar(const char *str, int len); +int macro_global_addvar(const char *str, int len, const char *value); +int macro_parse_globalvar(const char *define); +void macro_lock(void); +void macro_unlock(void); +struct substvar * +macro_addvar(struct substvar *table, const char *str, int len, const char *value); +void macro_global_removevar(const char *str, int len); +struct substvar * +macro_removevar(struct substvar *table, const char *str, int len); +void macro_free_global_table(void); +void macro_free_table(struct substvar *table); +const struct substvar * +macro_findvar(const struct substvar *table, const char *str, int len); +void macro_setenv(struct substvar *table); + +#endif diff --git a/include/master.h b/include/master.h new file mode 100644 index 0000000..087ddbe --- /dev/null +++ b/include/master.h @@ -0,0 +1,132 @@ +/* ----------------------------------------------------------------------- * + * + * master.h - header file for master map parser utility routines. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifndef MASTER_H +#define MASTER_H + +#define MAP_FLAG_FORMAT_AMD 0x0001 + +struct map_source { + unsigned int ref; + unsigned int flags; + char *type; + char *format; + char *name; + time_t exp_timeout; /* Timeout for expiring mounts */ + time_t age; + unsigned int master_line; + struct mapent_cache *mc; + unsigned int stale; + unsigned int recurse; + unsigned int depth; + struct lookup_mod *lookup; + int argc; + const char **argv; + struct map_source *instance; + struct map_source *next; +}; + +struct master_mapent { + char *path; + pthread_t thid; + time_t age; + struct master *master; + pthread_rwlock_t source_lock; + pthread_mutex_t current_mutex; + pthread_cond_t current_cond; + struct map_source *current; + struct map_source *maps; + struct autofs_point *ap; + struct list_head list; + struct list_head join; +}; + +struct master { + char *name; + unsigned int recurse; + unsigned int depth; + unsigned int reading; + unsigned int read_fail; + unsigned int default_ghost; + unsigned int default_logging; + unsigned int default_timeout; + unsigned int logopt; + struct mapent_cache *nc; + struct list_head mounts; + struct list_head completed; +}; + +/* From the yacc master map parser */ + +void master_init_scan(void); +int master_parse_entry(const char *, unsigned int, unsigned int, time_t); + +/* From master.c master parser utility routines */ + +void master_mutex_lock(void); +void master_mutex_unlock(void); +void master_mutex_lock_cleanup(void *); +void master_set_default_timeout(void); +void master_set_default_ghost_mode(void); +int master_add_autofs_point(struct master_mapent *, unsigned, unsigned, unsigned, int); +void master_free_autofs_point(struct autofs_point *); +struct map_source * +master_add_map_source(struct master_mapent *, char *, char *, time_t, int, const char **); +struct map_source * +master_find_map_source(struct master_mapent *, const char *, const char *, int, const char **); +struct map_source * +master_get_map_source(struct master_mapent *, const char *, const char *, int, const char **); +void master_free_map_source(struct map_source *, unsigned int); +struct map_source * +master_find_source_instance(struct map_source *, const char *, const char *, int, const char **); +struct map_source * +master_add_source_instance(struct map_source *, const char *, const char *, time_t, int, const char **); +void clear_stale_instances(struct map_source *); +void send_map_update_request(struct autofs_point *); +void master_source_writelock(struct master_mapent *); +void master_source_readlock(struct master_mapent *); +void master_source_unlock(struct master_mapent *); +void master_source_lock_cleanup(void *); +void master_source_current_wait(struct master_mapent *); +void master_source_current_signal(struct master_mapent *); +struct master_mapent *master_find_mapent(struct master *, const char *); +unsigned int master_partial_match_mapent(struct master *, const char *); +struct autofs_point *__master_find_submount(struct autofs_point *, const char *); +struct autofs_point *master_find_submount(struct autofs_point *, const char *); +struct amd_entry *__master_find_amdmount(struct autofs_point *, const char *); +struct amd_entry *master_find_amdmount(struct autofs_point *, const char *); +struct master_mapent *master_new_mapent(struct master *, const char *, time_t); +void master_add_mapent(struct master *, struct master_mapent *); +void master_remove_mapent(struct master_mapent *); +void master_free_mapent_sources(struct master_mapent *, unsigned int); +void master_free_mapent(struct master_mapent *); +struct master *master_new(const char *, unsigned int, unsigned int); +int master_read_master(struct master *, time_t, int); +int master_submount_list_empty(struct autofs_point *ap); +int master_notify_submount(struct autofs_point *, const char *path, enum states); +void master_notify_state_change(struct master *, int); +int master_mount_mounts(struct master *, time_t, int); +int dump_map(struct master *, const char *, const char *); +int master_show_mounts(struct master *); +unsigned int master_get_logopt(void); +int master_list_empty(struct master *); +int master_done(struct master *); +int master_kill(struct master *); + +#endif diff --git a/include/mounts.h b/include/mounts.h new file mode 100644 index 0000000..031e2a3 --- /dev/null +++ b/include/mounts.h @@ -0,0 +1,125 @@ +/* ----------------------------------------------------------------------- * + * + * mounts.h - header file for mount utilities module. + * + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2004-2006 Ian Kent - All Rights Reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef MOUNTS_H +#define MOUNTS_H + +#include +#include + +#ifndef AUTOFS_TYPE_ANY +#define AUTOFS_TYPE_ANY 0x0000 +#endif +#ifndef AUTOFS_TYPE_INDIRECT +#define AUTOFS_TYPE_INDIRECT 0x0001 +#endif +#ifndef AUTOFS_TYPE_DIRECT +#define AUTOFS_TYPE_DIRECT 0x0002 +#endif +#ifndef AUTOFS_TYPE_OFFSET +#define AUTOFS_TYPE_OFFSET 0x0004 +#endif + +#define MNTS_ALL 0x0001 +#define MNTS_REAL 0x0002 +#define MNTS_AUTOFS 0x0004 + +#define REMOUNT_SUCCESS 0x0000 +#define REMOUNT_FAIL 0x0001 +#define REMOUNT_OPEN_FAIL 0x0002 +#define REMOUNT_STAT_FAIL 0x0004 +#define REMOUNT_READ_MAP 0x0008 + +extern const unsigned int t_indirect; +extern const unsigned int t_direct; +extern const unsigned int t_offset; + +struct mapent; + +struct mnt_list { + char *path; + char *fs_name; + char *fs_type; + char *opts; + pid_t owner; + /* + * List operations ie. get_mnt_list. + */ + struct mnt_list *next; + /* + * Tree operations ie. tree_make_tree, + * tree_get_mnt_list etc. + */ + struct mnt_list *left; + struct mnt_list *right; + struct list_head self; + struct list_head list; + struct list_head entries; + struct list_head sublist; +}; + + +struct nfs_mount_vers { + unsigned int major; + unsigned int minor; + unsigned int fix; +}; +unsigned int linux_version_code(void); +int check_nfs_mount_version(struct nfs_mount_vers *, struct nfs_mount_vers *); +extern unsigned int nfs_mount_uses_string_options; + +struct amd_entry; + +struct substvar *addstdenv(struct substvar *sv, const char *prefix); +struct substvar *removestdenv(struct substvar *sv, const char *prefix); +void add_std_amd_vars(struct substvar *sv); +void remove_std_amd_vars(void); +struct amd_entry *new_amd_entry(const struct substvar *sv); +void clear_amd_entry(struct amd_entry *entry); +void free_amd_entry(struct amd_entry *entry); +void free_amd_entry_list(struct list_head *entries); + +unsigned int query_kproto_ver(void); +unsigned int get_kver_major(void); +unsigned int get_kver_minor(void); +char *make_options_string(char *path, int kernel_pipefd, const char *extra); +char *make_mnt_name_string(char *path); +int ext_mount_add(struct list_head *, const char *, unsigned int); +int ext_mount_remove(struct list_head *, const char *); +struct mnt_list *get_mnt_list(const char *table, const char *path, int include); +struct mnt_list *reverse_mnt_list(struct mnt_list *list); +void free_mnt_list(struct mnt_list *list); +int is_mounted(const char *table, const char *path, unsigned int type); +int has_fstab_option(const char *opt); +void tree_free_mnt_tree(struct mnt_list *tree); +struct mnt_list *tree_make_mnt_tree(const char *table, const char *path); +int tree_get_mnt_list(struct mnt_list *mnts, struct list_head *list, const char *path, int include); +int tree_get_mnt_sublist(struct mnt_list *mnts, struct list_head *list, const char *path, int include); +int tree_find_mnt_ents(struct mnt_list *mnts, struct list_head *list, const char *path); +int tree_is_mounted(struct mnt_list *mnts, const char *path, unsigned int type); +void set_tsd_user_vars(unsigned int, uid_t, gid_t); +const char *mount_type_str(unsigned int); +void set_exp_timeout(struct autofs_point *ap, struct map_source *source, time_t timeout); +time_t get_exp_timeout(struct autofs_point *ap, struct map_source *source); +void notify_mount_result(struct autofs_point *, const char *, time_t, const char *); +int try_remount(struct autofs_point *, struct mapent *, unsigned int); +void set_indirect_mount_tree_catatonic(struct autofs_point *); +void set_direct_mount_tree_catatonic(struct autofs_point *, struct mapent *); +int umount_ent(struct autofs_point *, const char *); +int mount_multi_triggers(struct autofs_point *, struct mapent *, const char *, unsigned int, const char *); +int umount_multi_triggers(struct autofs_point *, struct mapent *, char *, const char *); +int clean_stale_multi_triggers(struct autofs_point *, struct mapent *, char *, const char *); + +#endif diff --git a/include/nsswitch.h b/include/nsswitch.h new file mode 100644 index 0000000..d3e4027 --- /dev/null +++ b/include/nsswitch.h @@ -0,0 +1,65 @@ +/* ----------------------------------------------------------------------- * + * + * nsswitch.h - header file for module to call parser for nsswitch + * config and store result into a struct. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifndef __NSSWITCH_H +#define __NSSWITCH_H + +#include +#include "list.h" + +#define NSSWITCH_FILE _PATH_NSSWITCH_CONF + +enum nsswitch_status { + NSS_STATUS_UNKNOWN = -1, + NSS_STATUS_SUCCESS, + NSS_STATUS_NOTFOUND, + NSS_STATUS_UNAVAIL, + NSS_STATUS_TRYAGAIN, + NSS_STATUS_MAX +}; + +/* Internal NSS STATUS for map inclusion lookups */ +#define NSS_STATUS_COMPLETED NSS_STATUS_MAX + +enum nsswitch_action { + NSS_ACTION_UNKNOWN = 0, + NSS_ACTION_CONTINUE, + NSS_ACTION_RETURN +}; + +struct nss_action { + enum nsswitch_action action; + int negated; +}; + +struct nss_source { + char *source; + struct nss_action action[NSS_STATUS_MAX]; + struct list_head list; +}; + +int set_action(struct nss_action *a, char *status, char *action, int negated); +int check_nss_result(struct nss_source *this, enum nsswitch_status result); +struct nss_source *add_source(struct list_head *head, char *source); +int free_sources(struct list_head *list); + +int nsswitch_parse(struct list_head *list); + +#endif diff --git a/include/parse_amd.h b/include/parse_amd.h new file mode 100644 index 0000000..1664947 --- /dev/null +++ b/include/parse_amd.h @@ -0,0 +1,72 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2004-2006 Ian Kent + * Copyright 2013 Red Hat, Inc. + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef PARSE_AMD_H +#define PARSE_AMD_H + +#define AMD_MOUNT_TYPE_NONE 0x00000000 +#define AMD_MOUNT_TYPE_AUTO 0x00000001 +#define AMD_MOUNT_TYPE_NFS 0x00000002 +#define AMD_MOUNT_TYPE_LINK 0x00000004 +#define AMD_MOUNT_TYPE_HOST 0x00000008 +#define AMD_MOUNT_TYPE_NFSL 0x00000010 +#define AMD_MOUNT_TYPE_NFSX 0x00000020 +#define AMD_MOUNT_TYPE_LINKX 0x00000040 +#define AMD_MOUNT_TYPE_LOFS 0x00000080 +#define AMD_MOUNT_TYPE_EXT 0x00000100 +#define AMD_MOUNT_TYPE_UFS 0x00000200 +#define AMD_MOUNT_TYPE_XFS 0x00000400 +#define AMD_MOUNT_TYPE_JFS 0x00000800 +#define AMD_MOUNT_TYPE_CACHEFS 0x00001000 +#define AMD_MOUNT_TYPE_CDFS 0x00002000 +#define AMD_MOUNT_TYPE_MASK 0x0000ffff + +#define AMD_ENTRY_CUT 0x00010000 +#define AMD_ENTRY_MASK 0x00ff0000 + +#define AMD_DEFAULTS_MERGE 0x01000000 +#define AMD_DEFAULTS_RESET 0x02000000 +#define AMD_DEFAULTS_MASK 0xff000000 + +#define AMD_CACHE_OPTION_NONE 0x0000 +#define AMD_CACHE_OPTION_INC 0x0001 +#define AMD_CACHE_OPTION_ALL 0x0002 +#define AMD_CACHE_OPTION_REGEXP 0x0004 +#define AMD_CACHE_OPTION_SYNC 0x8000 + +struct amd_entry { + char *path; + unsigned long flags; + unsigned int cache_opts; + char *type; + char *map_type; + char *pref; + char *fs; + char *rhost; + char *rfs; + char *dev; + char *opts; + char *addopts; + char *remopts; + char *sublink; + struct selector *selector; + struct list_head list; + struct list_head entries; + struct list_head ext_mount; +}; + +int amd_parse_list(struct autofs_point *, + const char *, struct list_head *, struct substvar **); + +#endif diff --git a/include/parse_subs.h b/include/parse_subs.h new file mode 100644 index 0000000..1e87716 --- /dev/null +++ b/include/parse_subs.h @@ -0,0 +1,131 @@ +/* ----------------------------------------------------------------------- * + * + * parse_subs.c - misc parser subroutines + * automounter map + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2000 Jeremy Fitzhardinge + * Copyright 2004-2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef PARSE_SUBS_H +#define PARSE_SUBS_H + +#define PROXIMITY_ERROR 0x0000 +#define PROXIMITY_LOCAL 0x0001 +#define PROXIMITY_SUBNET 0x0002 +#define PROXIMITY_NET 0x0004 +#define PROXIMITY_OTHER 0x0008 +#define PROXIMITY_UNSUPPORTED 0x0010 + +#define SEL_ARCH 0x00000001 +#define SEL_KARCH 0x00000002 +#define SEL_OS 0x00000004 +#define SEL_OSVER 0x00000008 +#define SEL_FULL_OS 0x00000010 +#define SEL_VENDOR 0x00000020 +#define SEL_HOST 0x00000040 +#define SEL_HOSTD 0x00000080 +#define SEL_XHOST 0x00000100 +#define SEL_DOMAIN 0x00000200 +#define SEL_BYTE 0x00000400 +#define SEL_CLUSTER 0x00000800 +#define SEL_NETGRP 0x00001000 +#define SEL_NETGRPD 0x00002000 +#define SEL_IN_NETWORK 0x00004000 +#define SEL_UID 0x00008000 +#define SEL_GID 0x00010000 +#define SEL_KEY 0x00020000 +#define SEL_MAP 0x00040000 +#define SEL_PATH 0x00080000 +#define SEL_EXISTS 0x00100000 +#define SEL_AUTODIR 0x00200000 +#define SEL_DOLLAR 0x00400000 +#define SEL_TRUE 0x00800000 +#define SEL_FALSE 0x01000000 + +#define SEL_COMP_NONE 0x0000 +#define SEL_COMP_EQUAL 0x0001 +#define SEL_COMP_NOTEQUAL 0x0002 +#define SEL_COMP_NOT 0x0004 + +#define SEL_FLAG_MACRO 0x0001 +#define SEL_FLAG_FUNC1 0x0002 +#define SEL_FLAG_FUNC2 0x0004 +#define SEL_FLAG_STR 0x0100 +#define SEL_FLAG_NUM 0x0200 +#define SEL_FLAG_BOOL 0x0400 + +#define SEL_FLAGS_TYPE_MASK 0x00FF +#define SEL_FLAGS_VALUE_MASK 0xFF00 +#define SEL_FREE_VALUE_MASK (SEL_FLAG_MACRO|SEL_FLAG_STR|SEL_FLAG_NUM) +#define SEL_FREE_ARG1_MASK (SEL_FLAG_FUNC1) +#define SEL_FREE_ARG2_MASK (SEL_FLAG_FUNC2) + +struct type_compare { + char *value; +}; + +struct type_function { + char *arg1; + char *arg2; +}; + +struct sel { + unsigned long selector; + const char *name; + unsigned int flags; + struct sel *next; +}; + +struct selector { + struct sel *sel; + unsigned int compare; + + union { + struct type_compare comp; + struct type_function func; + }; + + struct selector *next; +}; + +void sel_hash_init(void); +struct sel *sel_lookup(const char *); +struct selector *get_selector(char *); +void free_selector(struct selector *); + +struct mapent; + +struct map_type_info { + char *type; + char *format; + char *map; +}; + +unsigned int get_proximity(struct sockaddr *); +unsigned int get_network_proximity(const char *); +unsigned int in_network(char *); +struct mapent *match_cached_key(struct autofs_point *, const char *, + struct map_source *, const char *); +const char *skipspace(const char *); +int check_colon(const char *); +int chunklen(const char *, int); +int strmcmp(const char *, const char *, int); +char *dequote(const char *, int, unsigned int); +int span_space(const char *, unsigned int); +char *sanitize_path(const char *, int, unsigned int, unsigned int); +char *merge_options(const char *, const char *); +int expandamdent(const char *, char *, const struct substvar *); +int expand_selectors(struct autofs_point *, const char *, char **, struct substvar *); +void free_map_type_info(struct map_type_info *); +struct map_type_info *parse_map_type_info(const char *); + +#endif diff --git a/include/replicated.h b/include/replicated.h new file mode 100644 index 0000000..728f131 --- /dev/null +++ b/include/replicated.h @@ -0,0 +1,79 @@ +/* ----------------------------------------------------------------------- * + * + * repl_list.h - header file for replicated mount server selection + * + * Copyright 2004 Jeff Moyer - All Rights Reserved + * Copyright 2004-2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef _REPLICATED_H +#define _REPLICATED_H + +#define PROXIMITY_ERROR 0x0000 +#define PROXIMITY_LOCAL 0x0001 +#define PROXIMITY_SUBNET 0x0002 +#define PROXIMITY_NET 0x0004 +#define PROXIMITY_OTHER 0x0008 +#define PROXIMITY_UNSUPPORTED 0x0010 + +#define NFS2_SUPPORTED 0x0010 +#define NFS3_SUPPORTED 0x0020 +#define NFS4_SUPPORTED 0x0040 +#define NFS_VERS_MASK (NFS2_SUPPORTED|NFS3_SUPPORTED) +#define NFS4_VERS_MASK (NFS4_SUPPORTED) + +#define NFS2_REQUESTED NFS2_SUPPORTED +#define NFS3_REQUESTED NFS3_SUPPORTED +#define NFS4_REQUESTED NFS4_SUPPORTED + +#define TCP_SUPPORTED 0x0001 +#define UDP_SUPPORTED 0x0002 +#define TCP_REQUESTED TCP_SUPPORTED +#define UDP_REQUESTED UDP_SUPPORTED +#define NFS_PROTO_MASK (TCP_SUPPORTED|UDP_SUPPORTED) + +#define NFS2_TCP_SUPPORTED NFS2_SUPPORTED +#define NFS3_TCP_SUPPORTED NFS3_SUPPORTED +#define NFS4_TCP_SUPPORTED NFS4_SUPPORTED +#define NFS2_UDP_SUPPORTED (NFS2_SUPPORTED << 8) +#define NFS3_UDP_SUPPORTED (NFS3_SUPPORTED << 8) +#define NFS4_UDP_SUPPORTED (NFS4_SUPPORTED << 8) +#define TCP_SELECTED_MASK 0x00FF +#define UDP_SELECTED_MASK 0xFF00 + +#define IS_ERR(supported) (0x8000 & supported) +#define ERR(supported) (IS_ERR(supported) ? (~supported + 1) : supported) + +#define RPC_TIMEOUT 5 + +struct host { + char *name; + struct sockaddr *addr; + size_t addr_len; + unsigned int rr; + char *path; + unsigned int version; + unsigned int options; + unsigned int proximity; + unsigned int weight; + unsigned long cost; + struct host *next; +}; + +void seed_random(void); +struct host *new_host(const char *, struct sockaddr *, size_t, + unsigned int, unsigned int, unsigned int); +void free_host_list(struct host **); +int parse_location(unsigned, struct host **, const char *, unsigned int); +int prune_host_list(unsigned, struct host **, unsigned int, int); +void dump_host_list(struct host *); + +#endif + diff --git a/include/rpc_subs.h b/include/rpc_subs.h new file mode 100644 index 0000000..e744e89 --- /dev/null +++ b/include/rpc_subs.h @@ -0,0 +1,78 @@ +/* ----------------------------------------------------------------------- * + * + * rpc_subs.h - header file for rpc discovery + * + * Copyright 2004 Jeff Moyer - All Rights Reserved + * Copyright 2004-2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef _RPC_SUBS_H +#define _RPC_SUBS_H + +#include +#include +#include +#include +#include + +#define NFS4_VERSION 4 + +/* rpc helper subs */ +#define RPC_PING_FAIL 0x0000 +#define RPC_PING_V2 NFS2_VERSION +#define RPC_PING_V3 NFS3_VERSION +#define RPC_PING_V4 NFS4_VERSION +#define RPC_PING_UDP 0x0100 +#define RPC_PING_TCP 0x0200 +/* + * Close options to allow some choice in how and where the TIMED_WAIT + * happens. + */ +#define RPC_CLOSE_DEFAULT 0x0000 +#define RPC_CLOSE_ACTIVE RPC_CLOSE_DEFAULT +#define RPC_CLOSE_NOLINGER 0x0001 + +#define PMAP_TOUT_UDP 3 +#define PMAP_TOUT_TCP 5 + +#define RPC_TOUT_UDP PMAP_TOUT_UDP +#define RPC_TOUT_TCP PMAP_TOUT_TCP + +#define HOST_ENT_BUF_SIZE 2048 + +struct conn_info { + const char *host; + struct sockaddr *addr; + size_t addr_len; + unsigned short port; + unsigned long program; + unsigned long version; + int proto; + unsigned int send_sz; + unsigned int recv_sz; + struct timeval timeout; + unsigned int close_option; + CLIENT *client; +}; + +int rpc_udp_getclient(struct conn_info *, unsigned int, unsigned int); +void rpc_destroy_udp_client(struct conn_info *); +int rpc_tcp_getclient(struct conn_info *, unsigned int, unsigned int); +void rpc_destroy_tcp_client(struct conn_info *); +int rpc_portmap_getclient(struct conn_info *, const char *, struct sockaddr *, size_t, int, unsigned int); +int rpc_portmap_getport(struct conn_info *, struct pmap *, unsigned short *); +int rpc_ping_proto(struct conn_info *); +int rpc_ping(const char *, long, long, unsigned int); +double monotonic_elapsed(struct timespec, struct timespec); +int rpc_time(const char *, unsigned int, unsigned int, long, long, unsigned int, double *); +const char *get_addr_string(struct sockaddr *, char *, socklen_t); + +#endif + diff --git a/include/state.h b/include/state.h new file mode 100644 index 0000000..b44a353 --- /dev/null +++ b/include/state.h @@ -0,0 +1,98 @@ +/* ----------------------------------------------------------------------- * + * + * state.h - state queue functions. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifndef STATE_H +#define STATE_H + +#include +#include +#include +#include "automount.h" + +/* + * State machine for daemon + * + * READY - reads from pipe; performs mount/umount operations + * PRUNE - generates prune events in subprocess; reads from pipe + * READMAP - read read map for maps taht use cache + * EXPIRE - generates expire events in subprocess; reads from pipe + * SHUTDOWN_PENDING - as prune, but goes to SHUTDOWN when done + * SHUTDOWN - unmount autofs, exit + * + */ +enum states { + ST_ANY = -2, + ST_INVAL, + ST_INIT, + ST_READY, + ST_EXPIRE, + ST_PRUNE, + ST_READMAP, + ST_SHUTDOWN_PENDING, + ST_SHUTDOWN_FORCE, + ST_SHUTDOWN +}; + +struct expire_args { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned int signaled; + struct autofs_point *ap; /* autofs mount we are working on */ + enum states state; /* State prune or expire */ + unsigned int when; /* Immediate expire ? */ + int status; /* Return status */ +}; + +#define expire_args_mutex_lock(ea) \ +do { \ + int _ea_lock = pthread_mutex_lock(&ea->mutex); \ + if (_ea_lock) \ + fatal(_ea_lock); \ +} while (0) + +#define expire_args_mutex_unlock(ea) \ +do { \ + int _ea_unlock = pthread_mutex_unlock(&ea->mutex); \ + if (_ea_unlock) \ + fatal(_ea_unlock); \ +} while (0) + +struct readmap_args { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned int signaled; + struct autofs_point *ap; /* autofs mount we are working on */ + time_t now; /* Time when map is read */ +}; + +void st_mutex_lock(void); +void st_mutex_unlock(void); + +void expire_cleanup(void *); +void expire_proc_cleanup(void *); +void nextstate(int, enum states); + +int st_add_task(struct autofs_point *, enum states); +int __st_add_task(struct autofs_point *, enum states); +void st_remove_tasks(struct autofs_point *); +int st_wait_task(struct autofs_point *, enum states, unsigned int); +int st_wait_state(struct autofs_point *ap, enum states state); +int st_start_handler(void); + +#endif diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..518b483 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,83 @@ +# +# Makefile for autofs utility library +# + +-include ../Makefile.conf +include ../Makefile.rules + +SRCS = cache.c cat_path.c rpc_subs.c mounts.c log.c nsswitch.c \ + master_tok.l master_parse.y nss_tok.c nss_parse.tab.c \ + args.c alarm.c macros.c master.c defaults.c parse_subs.c \ + dev-ioctl-lib.c +RPCS = mount.h mount_clnt.c mount_xdr.c +OBJS = cache.o mount_clnt.o mount_xdr.o cat_path.o rpc_subs.o \ + mounts.o log.o nsswitch.o master_tok.o master_parse.tab.o \ + nss_tok.o nss_parse.tab.o args.o alarm.o macros.o master.o \ + defaults.o parse_subs.o dev-ioctl-lib.o + +YACCSRC = nss_tok.c nss_parse.tab.c nss_parse.tab.h \ + master_tok.c master_parse.tab.c master_parse.tab.h + +LIB = autofs.a + +CFLAGS += -I../include -fPIC -D_GNU_SOURCE +CFLAGS += -DAUTOFS_MAP_DIR=\"$(autofsmapdir)\" +CFLAGS += -DAUTOFS_CONF_DIR=\"$(autofsconfdir)\" + +ifeq ($(LDAP), 1) + CFLAGS += $(XML_FLAGS) $(XML_LIBS) +endif + +.PHONY: all install clean + +all: autofs.a + +autofs.a: $(OBJS) + rm -f $(LIB) + $(AR) $(ARFLAGS) $(LIB) $(OBJS) + -$(RANLIB) $(LIB) + +mount.h: mount.x + $(RPCGEN) -h -o mount.h mount.x + +mount_clnt.c: mount.h + $(RPCGEN) -l -o mount_clnt.c mount.x + +mount_clnt.o: mount_clnt.c + $(CC) $(CFLAGS) -o mount_clnt.o -c mount_clnt.c + $(STRIP) mount_clnt.o + +mount_xdr.c: mount.h + $(RPCGEN) -c -o mount_xdr.c mount.x + +mount_xdr.o: mount_xdr.c + $(CC) $(CFLAGS) -Wno-unused-variable -o mount_xdr.o -c mount_xdr.c + $(STRIP) mount_xdr.o + +master_tok.c: master_tok.l + $(LEX) -o$@ -Pmaster_ $? + +master_parse.tab.c master_parse.tab.h: master_parse.y + $(YACC) -v -d -p master_ -b master_parse $? + +master_tok.o: master_tok.c master_parse.tab.h + +master_parse.tab.o: master_parse.tab.c master_parse.tab.h + +nss_tok.c: nss_tok.l + $(LEX) -o$@ -Pnss_ $? + +nss_parse.tab.c nss_parse.tab.h: nss_parse.y + $(YACC) -v -d -p nss_ -b nss_parse $? + +nss_tok.o: nss_tok.c nss_parse.tab.h + +nss_parse.tab.o: nss_parse.tab.c nss_parse.tab.h + +rpc_subs.o: mount.h + +install: all + +clean: + rm -f $(LIB) $(RPCS) $(OBJS) $(YACCSRC) *.output *~ + diff --git a/lib/alarm.c b/lib/alarm.c new file mode 100755 index 0000000..e6a880b --- /dev/null +++ b/lib/alarm.c @@ -0,0 +1,250 @@ +/* ----------------------------------------------------------------------- * + * + * alarm.c - alarm queue handling module. + * + * Copyright 2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include "automount.h" + +struct alarm { + time_t time; + unsigned int cancel; + struct autofs_point *ap; + struct list_head list; +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond; +static LIST_HEAD(alarms); + +#define alarm_lock() \ +do { \ + int _alm_lock = pthread_mutex_lock(&mutex); \ + if (_alm_lock) \ + fatal(_alm_lock); \ +} while (0) + +#define alarm_unlock() \ +do { \ + int _alm_unlock = pthread_mutex_unlock(&mutex); \ + if (_alm_unlock) \ + fatal(_alm_unlock); \ +} while (0) + +/* Insert alarm entry on ordered list. */ +int alarm_add(struct autofs_point *ap, time_t seconds) +{ + struct list_head *head; + struct list_head *p; + struct alarm *new; + time_t now = monotonic_time(NULL); + time_t next_alarm = 0; + unsigned int empty = 1; + int status; + + if (!seconds) + return 1; + + new = malloc(sizeof(struct alarm)); + if (!new) + return 0; + + new->ap = ap; + new->cancel = 0; + new->time = now + seconds; + + alarm_lock(); + + head = &alarms; + + /* Check if we have a pending alarm */ + if (!list_empty(head)) { + struct alarm *current; + current = list_entry(head->next, struct alarm, list); + next_alarm = current->time; + empty = 0; + } + + list_for_each(p, head) { + struct alarm *this; + + this = list_entry(p, struct alarm, list); + if (this->time >= new->time) { + list_add_tail(&new->list, p); + break; + } + } + if (p == head) + list_add_tail(&new->list, p); + + /* + * Wake the alarm thread if it is not busy (ie. if the + * alarms list was empty) or if the new alarm comes before + * the alarm we are currently waiting on. + */ + if (empty || new->time < next_alarm) { + status = pthread_cond_signal(&cond); + if (status) + fatal(status); + } + + alarm_unlock(); + + return 1; +} + +void alarm_delete(struct autofs_point *ap) +{ + struct list_head *head; + struct list_head *p; + struct alarm *current; + unsigned int signal_cancel = 0; + int status; + + alarm_lock(); + + head = &alarms; + + if (list_empty(head)) { + alarm_unlock(); + return; + } + + current = list_entry(head->next, struct alarm, list); + + p = head->next; + while (p != head) { + struct alarm *this; + + this = list_entry(p, struct alarm, list); + p = p->next; + + if (ap == this->ap) { + if (current != this) { + list_del_init(&this->list); + free(this); + continue; + } + /* Mark as canceled */ + this->cancel = 1; + this->time = 0; + signal_cancel = 1; + } + } + + if (signal_cancel) { + status = pthread_cond_signal(&cond); + if (status) + fatal(status); + } + + alarm_unlock(); + + return; +} + +static void *alarm_handler(void *arg) +{ + struct list_head *head; + struct timespec expire; + struct alarm *first; + time_t now; + int status; + + alarm_lock(); + + head = &alarms; + + while (1) { + if (list_empty(head)) { + /* No alarms, wait for one to be added */ + status = pthread_cond_wait(&cond, &mutex); + if (status) + fatal(status); + continue; + } + + first = list_entry(head->next, struct alarm, list); + + now = monotonic_time(NULL); + + if (first->time > now) { + struct timespec nsecs; + + /* + * Wait for alarm to trigger or a new alarm + * to be added. + */ + clock_gettime(CLOCK_MONOTONIC, &nsecs); + expire.tv_sec = first->time; + expire.tv_nsec = nsecs.tv_nsec; + + status = pthread_cond_timedwait(&cond, &mutex, &expire); + if (status && status != ETIMEDOUT) + fatal(status); + } else { + /* First alarm has triggered, run it */ + + list_del(&first->list); + + if (!first->cancel) { + struct autofs_point *ap = first->ap; + alarm_unlock(); + st_add_task(ap, ST_EXPIRE); + alarm_lock(); + } + free(first); + } + } + /* Will never come here, so alarm_unlock is not necessary */ +} + +int alarm_start_handler(void) +{ + pthread_t thid; + pthread_attr_t attrs; + pthread_attr_t *pattrs = &attrs; + pthread_condattr_t condattrs; + int status; + + status = pthread_attr_init(pattrs); + if (status) + pattrs = NULL; + else { + pthread_attr_setdetachstate(pattrs, PTHREAD_CREATE_DETACHED); +#ifdef _POSIX_THREAD_ATTR_STACKSIZE + pthread_attr_setstacksize(pattrs, PTHREAD_STACK_MIN*4); +#endif + } + + status = pthread_condattr_init(&condattrs); + if (status) + fatal(status); + + status = pthread_condattr_setclock(&condattrs, CLOCK_MONOTONIC); + if (status) + fatal(status); + + status = pthread_cond_init(&cond, &condattrs); + if (status) + fatal(status); + + status = pthread_create(&thid, pattrs, alarm_handler, NULL); + + pthread_condattr_destroy(&condattrs); + + if (pattrs) + pthread_attr_destroy(pattrs); + + return !status; +} + diff --git a/lib/args.c b/lib/args.c new file mode 100644 index 0000000..9616598 --- /dev/null +++ b/lib/args.c @@ -0,0 +1,194 @@ +/* ----------------------------------------------------------------------- * + * + * args.c - argument vector handling. + * + * Copyright 2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include + +#include "automount.h" + +/* + * Add entry to arg vector - argc is new arg vector size + * NOTE: this outine will free the passed in argv vector + * upon success. + */ +char **add_argv(int argc, char **argv, char *str) +{ + char **vector; + size_t vector_size; + int i; + + vector_size = (argc + 1) * sizeof(char *); + vector = (char **) malloc(vector_size); + if (!vector) + return NULL; + + for (i = 0; i < argc - 1; i++) { + if (argv[i]) { + vector[i] = strdup(argv[i]); + if (!vector[i]) { + logerr("failed to strdup arg"); + break; + } + } else + vector[i] = NULL; + } + + if (i < argc - 1) { + free_argv(argc - 1, (const char **) vector); + return NULL; + } + + vector[argc - 1] = strdup(str); + if (!vector[argc - 1]) { + free_argv(argc - 1, (const char **) vector); + return NULL; + } + + vector[argc] = NULL; + + free_argv(argc - 1, (const char **) argv); + + return vector; +} + +char **append_argv(int argc1, char **argv1, int argc2, char **argv2) +{ + char **vector; + size_t vector_size; + int len, i, j; + + len = argc1 + argc2; + vector_size = (len + 1) * sizeof(char *); + vector = (char **) realloc(argv1, vector_size); + if (!vector) { + free_argv(argc1, (const char **) argv1); + free_argv(argc2, (const char **) argv2); + return NULL; + } + + for (i = argc1, j = 0; i <= len; i++, j++) { + if (argv2[j]) { + vector[i] = strdup(argv2[j]); + if (!vector[i]) { + logerr("failed to strdup arg"); + break; + } + } else + vector[i] = NULL; + } + + if (i < len) { + free_argv(len, (const char **) vector); + free_argv(argc2, (const char **) argv2); + return NULL; + } + + vector[len] = NULL; + + free_argv(argc2, (const char **) argv2); + + return vector; +} + +const char **copy_argv(int argc, const char **argv) +{ + char **vector; + size_t vector_size; + int i; + + vector_size = (argc + 1) * sizeof(char *); + vector = (char **) malloc(vector_size); + if (!vector) + return NULL; + + for (i = 0; i < argc; i++) { + if (argv[i]) { + vector[i] = strdup(argv[i]); + if (!vector[i]) { + logerr("failed to strdup arg"); + break; + } + } else + vector[i] = NULL; + } + + if (i < argc) { + free_argv(argc, (const char **) vector); + return NULL; + } + + vector[argc] = NULL; + + return (const char **) vector; + +} + +static int compare(const char *s1, const char *s2) +{ + int res = 0; + + if (s1) { + if (!s2) + goto done; + + if (strcmp(s1, s2)) + goto done; + } else if (s2) + goto done; + + res = 1; +done: + return res; +} + +int compare_argv(int argc1, const char **argv1, int argc2, const char **argv2) +{ + int res = 1; + int i, val; + + if (argc1 != argc2) + return 0; + + i = 0; + while (i < argc1) { + val = compare(argv1[i], argv2[i]); + if (!val) { + res = 0; + break; + } + i++; + } + return res; +} + +int free_argv(int argc, const char **argv) +{ + char **vector = (char **) argv; + int i; + + if (!argc) { + if (vector) + free(vector); + return 1; + } + + for (i = 0; i < argc; i++) { + if (vector[i]) + free(vector[i]); + } + free(vector); + + return 1; +} + diff --git a/lib/cache.c b/lib/cache.c new file mode 100644 index 0000000..44e323d --- /dev/null +++ b/lib/cache.c @@ -0,0 +1,1290 @@ +/* ----------------------------------------------------------------------- * + * + * cache.c - mount entry cache management routines + * + * Copyright 2002-2005 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +void cache_dump_multi(struct list_head *list) +{ + struct list_head *p; + struct mapent *me; + + list_for_each(p, list) { + me = list_entry(p, struct mapent, multi_list); + logmsg("key=%s", me->key); + } +} + +void cache_dump_cache(struct mapent_cache *mc) +{ + struct mapent *me; + unsigned int i; + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (me == NULL) + continue; + while (me) { + logmsg("me->key=%s me->multi=%p dev=%ld ino=%ld", + me->key, me->multi, me->dev, me->ino); + me = me->next; + } + } +} + +void cache_readlock(struct mapent_cache *mc) +{ + int status; + + status = pthread_rwlock_rdlock(&mc->rwlock); + if (status) { + logmsg("mapent cache rwlock lock failed"); + fatal(status); + } + return; +} + +void cache_writelock(struct mapent_cache *mc) +{ + int status; + + status = pthread_rwlock_wrlock(&mc->rwlock); + if (status) { + logmsg("mapent cache rwlock lock failed"); + fatal(status); + } + return; +} + +int cache_try_writelock(struct mapent_cache *mc) +{ + int status; + + status = pthread_rwlock_trywrlock(&mc->rwlock); + if (status) { + logmsg("mapent cache rwlock busy"); + return 0; + } + return 1; +} + +void cache_unlock(struct mapent_cache *mc) +{ + int status; + + status = pthread_rwlock_unlock(&mc->rwlock); + if (status) { + logmsg("mapent cache rwlock unlock failed"); + fatal(status); + } + return; +} + +void cache_lock_cleanup(void *arg) +{ + struct mapent_cache *mc = (struct mapent_cache *) arg; + + cache_unlock(mc); + return; +} + +void cache_multi_readlock(struct mapent *me) +{ + int status; + + if (!me) + return; + + status = pthread_rwlock_rdlock(&me->multi_rwlock); + if (status) { + logmsg("mapent cache multi mutex lock failed"); + fatal(status); + } + return; +} + +void cache_multi_writelock(struct mapent *me) +{ + int status; + + if (!me) + return; + + status = pthread_rwlock_wrlock(&me->multi_rwlock); + if (status) { + logmsg("mapent cache multi mutex lock failed"); + fatal(status); + } + return; +} + +void cache_multi_unlock(struct mapent *me) +{ + int status; + + if (!me) + return; + + status = pthread_rwlock_unlock(&me->multi_rwlock); + if (status) { + logmsg("mapent cache multi mutex unlock failed"); + fatal(status); + } + return; +} + +void cache_multi_lock_cleanup(void *arg) +{ + struct mapent *me = (struct mapent *) arg; + cache_multi_unlock(me); + return; +} + +static inline void ino_index_lock(struct mapent_cache *mc) +{ + int status = pthread_mutex_lock(&mc->ino_index_mutex); + if (status) + fatal(status); + return; +} + +static inline void ino_index_unlock(struct mapent_cache *mc) +{ + int status = pthread_mutex_unlock(&mc->ino_index_mutex); + if (status) + fatal(status); + return; +} + +/* Save the cache entry mapent field onto a stack and set a new mapent */ +int cache_push_mapent(struct mapent *me, char *mapent) +{ + struct stack *s; + char *new; + + if (!me->mapent) + return CHE_FAIL; + + if (!mapent) + new = NULL; + else { + new = strdup(mapent); + if (!new) + return CHE_FAIL; + } + + s = malloc(sizeof(struct stack)); + if (!s) { + if (new) + free(new); + return CHE_FAIL; + } + memset(s, 0, sizeof(*s)); + + s->mapent = me->mapent; + s->age = me->age; + me->mapent = new; + + if (me->stack) + s->next = me->stack; + me->stack = s; + + return CHE_OK; +} + +/* Restore cache entry mapent to a previously saved mapent, discard current */ +int cache_pop_mapent(struct mapent *me) +{ + struct stack *s = me->stack; + char *mapent; + time_t age; + + if (!s || !s->mapent) + return CHE_FAIL; + + mapent = s->mapent; + age = s->age; + me->stack = s->next; + free(s); + + if (age < me->age) { + free(mapent); + return CHE_OK; + } + + if (me->mapent) + free(me->mapent); + me->mapent = mapent; + + return CHE_OK; +} + +struct mapent_cache *cache_init(struct autofs_point *ap, struct map_source *map) +{ + struct mapent_cache *mc; + unsigned int i; + int status; + + if (map->mc) + cache_release(map); + + mc = malloc(sizeof(struct mapent_cache)); + if (!mc) + return NULL; + + mc->size = defaults_get_map_hash_table_size(); + + mc->hash = malloc(mc->size * sizeof(struct mapent *)); + if (!mc->hash) { + free(mc); + return NULL; + } + + mc->ino_index = malloc(mc->size * sizeof(struct list_head)); + if (!mc->ino_index) { + free(mc->hash); + free(mc); + return NULL; + } + + status = pthread_mutex_init(&mc->ino_index_mutex, NULL); + if (status) + fatal(status); + + status = pthread_rwlock_init(&mc->rwlock, NULL); + if (status) + fatal(status); + + cache_writelock(mc); + + for (i = 0; i < mc->size; i++) { + mc->hash[i] = NULL; + INIT_LIST_HEAD(&mc->ino_index[i]); + } + + mc->ap = ap; + mc->map = map; + + cache_unlock(mc); + + return mc; +} + +void cache_clean_null_cache(struct mapent_cache *mc) +{ + struct mapent *me, *next; + int i; + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (me == NULL) + continue; + next = me->next; + free(me->key); + if (me->mapent) + free(me->mapent); + free(me); + + while (next != NULL) { + me = next; + next = me->next; + free(me->key); + free(me); + } + mc->hash[i] = NULL; + } + + return; +} + +struct mapent_cache *cache_init_null_cache(struct master *master) +{ + struct mapent_cache *mc; + unsigned int i; + int status; + + mc = malloc(sizeof(struct mapent_cache)); + if (!mc) + return NULL; + + mc->size = NULL_MAP_HASHSIZE; + + mc->hash = malloc(mc->size * sizeof(struct mapent *)); + if (!mc->hash) { + free(mc); + return NULL; + } + + mc->ino_index = malloc(mc->size * sizeof(struct list_head)); + if (!mc->ino_index) { + free(mc->hash); + free(mc); + return NULL; + } + + status = pthread_mutex_init(&mc->ino_index_mutex, NULL); + if (status) + fatal(status); + + status = pthread_rwlock_init(&mc->rwlock, NULL); + if (status) + fatal(status); + + for (i = 0; i < mc->size; i++) { + mc->hash[i] = NULL; + INIT_LIST_HEAD(&mc->ino_index[i]); + } + + mc->ap = NULL; + mc->map = NULL; + + return mc; +} + +static u_int32_t ino_hash(dev_t dev, ino_t ino, unsigned int size) +{ + u_int32_t hashval; + + hashval = dev + ino; + + return hashval % size; +} + +int cache_set_ino_index(struct mapent_cache *mc, const char *key, dev_t dev, ino_t ino) +{ + u_int32_t ino_index = ino_hash(dev, ino, mc->size); + struct mapent *me; + + me = cache_lookup_distinct(mc, key); + if (!me) + return 0; + + ino_index_lock(mc); + list_del_init(&me->ino_index); + list_add(&me->ino_index, &mc->ino_index[ino_index]); + me->dev = dev; + me->ino = ino; + ino_index_unlock(mc); + + return 1; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup_ino(struct mapent_cache *mc, dev_t dev, ino_t ino) +{ + struct mapent *me = NULL; + struct list_head *head, *p; + u_int32_t ino_index; + + ino_index_lock(mc); + ino_index = ino_hash(dev, ino, mc->size); + head = &mc->ino_index[ino_index]; + + list_for_each(p, head) { + me = list_entry(p, struct mapent, ino_index); + + if (me->dev != dev || me->ino != ino) + continue; + + ino_index_unlock(mc); + return me; + } + ino_index_unlock(mc); + return NULL; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup_first(struct mapent_cache *mc) +{ + struct mapent *me = NULL; + unsigned int i; + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (!me) + continue; + + while (me) { + /* Multi mount entries are not primary */ + if (me->multi && me->multi != me) { + me = me->next; + continue; + } + return me; + } + } + return NULL; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup_next(struct mapent_cache *mc, struct mapent *me) +{ + struct mapent *this; + u_int32_t hashval; + unsigned int i; + + if (!me) + return NULL; + + this = me->next; + while (this) { + /* Multi mount entries are not primary */ + if (this->multi && this->multi != this) { + this = this->next; + continue; + } + return this; + } + + hashval = hash(me->key, mc->size) + 1; + if (hashval < mc->size) { + for (i = (unsigned int) hashval; i < mc->size; i++) { + this = mc->hash[i]; + if (!this) + continue; + + while (this) { + /* Multi mount entries are not primary */ + if (this->multi && this->multi != this) { + this = this->next; + continue; + } + return this; + } + } + } + return NULL; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup_key_next(struct mapent *me) +{ + struct mapent *next; + + if (!me) + return NULL; + + next = me->next; + while (next) { + /* Multi mount entries are not primary */ + if (me->multi && me->multi != me) + continue; + if (!strcmp(me->key, next->key)) + return next; + next = next->next; + } + return NULL; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup(struct mapent_cache *mc, const char *key) +{ + struct mapent *me = NULL; + + if (!key) + return NULL; + + for (me = mc->hash[hash(key, mc->size)]; me != NULL; me = me->next) { + if (strcmp(key, me->key) == 0) + goto done; + } + + me = cache_lookup_first(mc); + if (me != NULL) { + /* Can't have wildcard in direct map */ + if (*me->key == '/') { + me = NULL; + goto done; + } + + for (me = mc->hash[hash("*", mc->size)]; me != NULL; me = me->next) + if (strcmp("*", me->key) == 0) + goto done; + } +done: + return me; +} + +/* cache must be read locked by caller */ +struct mapent *cache_lookup_distinct(struct mapent_cache *mc, const char *key) +{ + struct mapent *me; + + if (!key) + return NULL; + + for (me = mc->hash[hash(key, mc->size)]; me != NULL; me = me->next) { + if (strcmp(key, me->key) == 0) + return me; + } + + return NULL; +} + +/* Lookup an offset within a multi-mount entry */ +struct mapent *cache_lookup_offset(const char *prefix, const char *offset, int start, struct list_head *head) +{ + struct list_head *p; + struct mapent *this; + /* Keys for direct maps may be as long as a path name */ + char o_key[PATH_MAX]; + /* Avoid "//" at the beginning of paths */ + const char *path_prefix = strlen(prefix) > 1 ? prefix : ""; + size_t size; + + /* root offset duplicates "/" */ + size = snprintf(o_key, sizeof(o_key), "%s%s", path_prefix, offset); + if (size >= sizeof(o_key)) + return NULL; + + list_for_each(p, head) { + this = list_entry(p, struct mapent, multi_list); + if (!strcmp(&this->key[start], o_key)) + return this; + } + return NULL; +} + +/* cache must be read locked by caller */ +static struct mapent *__cache_partial_match(struct mapent_cache *mc, + const char *prefix, + unsigned int type) +{ + struct mapent *me = NULL; + size_t len = strlen(prefix); + unsigned int i; + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (me == NULL) + continue; + + if (len < strlen(me->key) && + (strncmp(prefix, me->key, len) == 0) && + me->key[len] == '/') { + if (type == LKP_NORMAL) + return me; + if (type == LKP_WILD && + me->key[len] != '\0' && + me->key[len + 1] == '*') + return me; + } + + me = me->next; + while (me != NULL) { + if (len < strlen(me->key) && + (strncmp(prefix, me->key, len) == 0 && + me->key[len] == '/')) { + if (type == LKP_NORMAL) + return me; + if (type == LKP_WILD && + me->key[len] != '\0' && + me->key[len + 1] == '*') + return me; + } + me = me->next; + } + } + return NULL; +} + +/* cache must be read locked by caller */ +struct mapent *cache_partial_match(struct mapent_cache *mc, const char *prefix) +{ + return __cache_partial_match(mc, prefix, LKP_NORMAL); +} + +/* cache must be read locked by caller */ +struct mapent *cache_partial_match_wild(struct mapent_cache *mc, const char *prefix) +{ + return __cache_partial_match(mc, prefix, LKP_WILD); +} + +/* cache must be write locked by caller */ +int cache_add(struct mapent_cache *mc, struct map_source *ms, const char *key, const char *mapent, time_t age) +{ + struct mapent *me, *existing = NULL; + char *pkey, *pent; + u_int32_t hashval = hash(key, mc->size); + int status; + + me = (struct mapent *) malloc(sizeof(struct mapent)); + if (!me) + return CHE_FAIL; + + pkey = malloc(strlen(key) + 1); + if (!pkey) { + free(me); + return CHE_FAIL; + } + me->key = strcpy(pkey, key); + + if (mapent) { + pent = malloc(strlen(mapent) + 1); + if (!pent) { + free(me); + free(pkey); + return CHE_FAIL; + } + me->mapent = strcpy(pent, mapent); + } else + me->mapent = NULL; + + me->stack = NULL; + + me->age = age; + me->status = 0; + me->mc = mc; + me->source = ms; + INIT_LIST_HEAD(&me->ino_index); + INIT_LIST_HEAD(&me->multi_list); + me->multi = NULL; + me->parent = NULL; + me->ioctlfd = -1; + me->dev = (dev_t) -1; + me->ino = (ino_t) -1; + me->flags = 0; + + status = pthread_rwlock_init(&me->multi_rwlock, NULL); + if (status) + fatal(status); + + /* + * We need to add to the end if values exist in order to + * preserve the order in which the map was read on lookup. + */ + existing = cache_lookup_distinct(mc, key); + if (!existing) { + me->next = mc->hash[hashval]; + mc->hash[hashval] = me; + } else { + while (1) { + struct mapent *next; + + next = cache_lookup_key_next(existing); + if (!next) + break; + + existing = next; + } + me->next = existing->next; + existing->next = me; + } + return CHE_OK; +} + +/* cache must be write locked by caller */ +static void cache_add_ordered_offset(struct mapent *me, struct list_head *head) +{ + struct list_head *p; + struct mapent *this; + + list_for_each(p, head) { + size_t tlen; + int eq; + + this = list_entry(p, struct mapent, multi_list); + tlen = strlen(this->key); + + eq = strncmp(this->key, me->key, tlen); + if (!eq && tlen == strlen(me->key)) + return; + + if (eq > 0) { + list_add_tail(&me->multi_list, p); + return; + } + } + list_add_tail(&me->multi_list, p); + + return; +} + +/* cache must be write locked by caller */ +int cache_update_offset(struct mapent_cache *mc, const char *mkey, const char *key, const char *mapent, time_t age) +{ + unsigned logopt = mc->ap ? mc->ap->logopt : master_get_logopt(); + struct mapent *me, *owner; + int ret = CHE_OK; + + owner = cache_lookup_distinct(mc, mkey); + if (!owner) + return CHE_FAIL; + + me = cache_lookup_distinct(mc, key); + if (me && me->age == age) { + if (me == owner || strcmp(me->key, key) == 0) { + char *pent; + + warn(logopt, + "duplcate offset detected for key %s", me->key); + + pent = malloc(strlen(mapent) + 1); + if (!pent) + warn(logopt, + "map entry not updated: %s", me->mapent); + else { + if (me->mapent) + free(me->mapent); + me->mapent = strcpy(pent, mapent); + warn(logopt, + "map entry updated with: %s", mapent); + } + return CHE_DUPLICATE; + } + } + + ret = cache_update(mc, owner->source, key, mapent, age); + if (ret == CHE_FAIL) { + warn(logopt, "failed to add key %s to cache", key); + return CHE_FAIL; + } + + me = cache_lookup_distinct(mc, key); + if (me) { + cache_add_ordered_offset(me, &owner->multi_list); + me->multi = owner; + goto done; + } + ret = CHE_FAIL; +done: + return ret; +} + +void cache_update_negative(struct mapent_cache *mc, + struct map_source *ms, const char *key, + time_t timeout) +{ + time_t now = monotonic_time(NULL); + struct mapent *me; + int rv = CHE_OK; + + /* Don't update the wildcard */ + if (strlen(key) == 1 && *key == '*') + return; + + me = cache_lookup_distinct(mc, key); + if (me) + rv = cache_push_mapent(me, NULL); + else + rv = cache_update(mc, ms, key, NULL, now); + if (rv != CHE_FAIL) { + me = cache_lookup_distinct(mc, key); + if (me) + me->status = now + timeout; + } + return; +} + + +static struct mapent *get_parent(const char *key, struct list_head *head, struct list_head **pos) +{ + struct list_head *next; + struct mapent *this, *last; + int eq; + + last = NULL; + next = *pos ? (*pos)->next : head->next; + + list_for_each(next, head) { + this = list_entry(next, struct mapent, multi_list); + + if (!strcmp(this->key, key)) + break; + + eq = strncmp(this->key, key, strlen(this->key)); + if (eq == 0) { + *pos = next; + last = this; + continue; + } + } + + return last; +} + +int cache_set_parents(struct mapent *mm) +{ + struct list_head *multi_head, *p, *pos; + struct mapent *this; + + if (!mm->multi) + return 0; + + pos = NULL; + multi_head = &mm->multi->multi_list; + + list_for_each(p, multi_head) { + struct mapent *parent; + this = list_entry(p, struct mapent, multi_list); + parent = get_parent(this->key, multi_head, &pos); + if (parent) + this->parent = parent; + else + this->parent = mm->multi; + } + + return 1; +} + +/* cache must be write locked by caller */ +int cache_update(struct mapent_cache *mc, struct map_source *ms, const char *key, const char *mapent, time_t age) +{ + unsigned logopt = mc->ap ? mc->ap->logopt : master_get_logopt(); + struct mapent *me = NULL; + char *pent; + int ret = CHE_OK; + + me = cache_lookup(mc, key); + while (me && me->source != ms) + me = cache_lookup_key_next(me); + if (!me || (!strcmp(me->key, "*") && strcmp(key, "*"))) { + ret = cache_add(mc, ms, key, mapent, age); + if (!ret) { + debug(logopt, "failed for %s", key); + return CHE_FAIL; + } + ret = CHE_UPDATED; + } else { + /* Already seen one of these */ + if (me->age == age) + return CHE_OK; + + if (!mapent) { + if (me->mapent) + free(me->mapent); + me->mapent = NULL; + } else if (!me->mapent || strcmp(me->mapent, mapent) != 0) { + pent = malloc(strlen(mapent) + 1); + if (pent == NULL) + return CHE_FAIL; + if (me->mapent) + free(me->mapent); + me->mapent = strcpy(pent, mapent); + ret = CHE_UPDATED; + } + me->age = age; + } + return ret; +} + +/* cache_multi_lock of the multi mount owner must be held by caller */ +int cache_delete_offset(struct mapent_cache *mc, const char *key) +{ + u_int32_t hashval = hash(key, mc->size); + struct mapent *me = NULL, *pred; + int status; + + me = mc->hash[hashval]; + if (!me) + return CHE_FAIL; + + if (strcmp(key, me->key) == 0) { + if (me->multi && me->multi == me) + return CHE_FAIL; + mc->hash[hashval] = me->next; + goto delete; + } + + while (me->next != NULL) { + pred = me; + me = me->next; + if (strcmp(key, me->key) == 0) { + if (me->multi && me->multi == me) + return CHE_FAIL; + pred->next = me->next; + goto delete; + } + } + + return CHE_FAIL; + +delete: + status = pthread_rwlock_destroy(&me->multi_rwlock); + if (status) + fatal(status); + list_del(&me->multi_list); + ino_index_lock(mc); + list_del(&me->ino_index); + ino_index_unlock(mc); + free(me->key); + if (me->mapent) + free(me->mapent); + free(me); + + return CHE_OK; +} + +/* cache must be write locked by caller */ +int cache_delete(struct mapent_cache *mc, const char *key) +{ + struct mapent *me = NULL, *pred; + u_int32_t hashval = hash(key, mc->size); + int status, ret = CHE_OK; + char this[PATH_MAX]; + + strcpy(this, key); + + me = mc->hash[hashval]; + if (!me) { + ret = CHE_FAIL; + goto done; + } + + while (me->next != NULL) { + pred = me; + me = me->next; + if (strcmp(this, me->key) == 0) { + struct stack *s = me->stack; + if (me->multi && !list_empty(&me->multi_list)) { + ret = CHE_FAIL; + goto done; + } + pred->next = me->next; + status = pthread_rwlock_destroy(&me->multi_rwlock); + if (status) + fatal(status); + ino_index_lock(mc); + list_del(&me->ino_index); + ino_index_unlock(mc); + free(me->key); + if (me->mapent) + free(me->mapent); + while (s) { + struct stack *next = s->next; + if (s->mapent) + free(s->mapent); + free(s); + s = next; + } + free(me); + me = pred; + } + } + + me = mc->hash[hashval]; + if (!me) + goto done; + + if (strcmp(this, me->key) == 0) { + struct stack *s = me->stack; + if (me->multi && !list_empty(&me->multi_list)) { + ret = CHE_FAIL; + goto done; + } + mc->hash[hashval] = me->next; + status = pthread_rwlock_destroy(&me->multi_rwlock); + if (status) + fatal(status); + ino_index_lock(mc); + list_del(&me->ino_index); + ino_index_unlock(mc); + free(me->key); + if (me->mapent) + free(me->mapent); + while (s) { + struct stack *next = s->next; + if (s->mapent) + free(s->mapent); + free(s); + s = next; + } + free(me); + } +done: + return ret; +} + +/* cache must be write locked by caller */ +int cache_delete_offset_list(struct mapent_cache *mc, const char *key) +{ + unsigned logopt = mc->ap ? mc->ap->logopt : master_get_logopt(); + struct mapent *me; + struct mapent *this; + struct list_head *head, *next; + int remain = 0; + int status; + + me = cache_lookup_distinct(mc, key); + if (!me) + return CHE_FAIL; + + /* Not offset list owner */ + if (me->multi != me) + return CHE_FAIL; + + head = &me->multi_list; + next = head->next; + while (next != head) { + this = list_entry(next, struct mapent, multi_list); + next = next->next; + if (this->ioctlfd != -1) { + error(logopt, + "active offset mount key %s", this->key); + return CHE_FAIL; + } + } + + head = &me->multi_list; + next = head->next; + while (next != head) { + this = list_entry(next, struct mapent, multi_list); + next = next->next; + list_del_init(&this->multi_list); + this->multi = NULL; + debug(logopt, "deleting offset key %s", this->key); + status = cache_delete(mc, this->key); + if (status == CHE_FAIL) { + warn(logopt, + "failed to delete offset %s", this->key); + this->multi = me; + /* TODO: add list back in */ + remain++; + } + } + + if (!remain) { + list_del_init(&me->multi_list); + me->multi = NULL; + } + + if (remain) + return CHE_FAIL; + + return CHE_OK; +} + +void cache_release(struct map_source *map) +{ + struct mapent_cache *mc; + struct mapent *me, *next; + int status; + unsigned int i; + + mc = map->mc; + + cache_writelock(mc); + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (me == NULL) + continue; + next = me->next; + free(me->key); + if (me->mapent) + free(me->mapent); + free(me); + + while (next != NULL) { + me = next; + next = me->next; + free(me->key); + if (me->mapent) + free(me->mapent); + free(me); + } + } + + map->mc = NULL; + + cache_unlock(mc); + + status = pthread_mutex_destroy(&mc->ino_index_mutex); + if (status) + fatal(status); + + status = pthread_rwlock_destroy(&mc->rwlock); + if (status) + fatal(status); + + free(mc->hash); + free(mc->ino_index); + free(mc); +} + +void cache_release_null_cache(struct master *master) +{ + struct mapent_cache *mc; + struct mapent *me, *next; + int status; + unsigned int i; + + mc = master->nc; + + cache_writelock(mc); + + for (i = 0; i < mc->size; i++) { + me = mc->hash[i]; + if (me == NULL) + continue; + next = me->next; + free(me->key); + if (me->mapent) + free(me->mapent); + free(me); + + while (next != NULL) { + me = next; + next = me->next; + free(me->key); + free(me); + } + } + + master->nc = NULL; + + cache_unlock(mc); + + status = pthread_mutex_destroy(&mc->ino_index_mutex); + if (status) + fatal(status); + + status = pthread_rwlock_destroy(&mc->rwlock); + if (status) + fatal(status); + + free(mc->hash); + free(mc->ino_index); + free(mc); +} + + + +/* cache must be read locked by caller */ +struct mapent *cache_enumerate(struct mapent_cache *mc, struct mapent *me) +{ + if (!me) + return cache_lookup_first(mc); + + return cache_lookup_next(mc, me); +} + +/* + * Get each offset from list head under prefix. + * Maintain traversal current position in pos for subsequent calls. + * Return each offset into offset. + */ +/* cache must be read locked by caller */ +char *cache_get_offset(const char *prefix, char *offset, int start, + struct list_head *head, struct list_head **pos) +{ + struct list_head *next; + struct mapent *this; + size_t plen = strlen(prefix); + size_t len = 0; + + if (*pos == head) + return NULL; + + /* Find an offset */ + *offset = '\0'; + next = *pos ? (*pos)->next : head->next; + while (next != head) { + char *offset_start, *pstart, *pend; + + this = list_entry(next, struct mapent, multi_list); + *pos = next; + next = next->next; + + offset_start = &this->key[start]; + if (strlen(offset_start) <= plen) + continue; + + if (!strncmp(prefix, offset_start, plen)) { + struct mapent *np = NULL; + char pe[PATH_MAX + 1]; + + /* "/" doesn't count for root offset */ + if (plen == 1) + pstart = &offset_start[plen - 1]; + else + pstart = &offset_start[plen]; + + /* not part of this sub-tree */ + if (*pstart != '/') + continue; + + /* get next offset */ + pend = pstart; + while (*pend++) { + size_t nest_pt_offset; + + if (*pend != '/') + continue; + + nest_pt_offset = start + pend - pstart; + if (plen > 1) + nest_pt_offset += plen; + strcpy(pe, this->key); + pe[nest_pt_offset] = '\0'; + + np = cache_lookup_distinct(this->mc, pe); + if (np) + break; + } + if (np) + continue; + len = pend - pstart - 1; + strncpy(offset, pstart, len); + offset[len] ='\0'; + break; + } + } + + /* Seek to next offset */ + while (next != head) { + char *offset_start, *pstart; + + this = list_entry(next, struct mapent, multi_list); + + offset_start = &this->key[start]; + if (strlen(offset_start) <= plen + len) + break; + + /* "/" doesn't count for root offset */ + if (plen == 1) + pstart = &offset_start[plen - 1]; + else + pstart = &offset_start[plen]; + + /* not part of this sub-tree */ + if (*pstart != '/') + break; + + /* new offset */ + if (!*(pstart + len + 1)) + break; + + /* compare offset */ + if (pstart[len] != '/' || + strlen(pstart) != len || + strncmp(offset, pstart, len)) + break; + + *pos = next; + next = next->next; + } + + return *offset ? offset : NULL; +} + diff --git a/lib/cat_path.c b/lib/cat_path.c new file mode 100644 index 0000000..527fb9c --- /dev/null +++ b/lib/cat_path.c @@ -0,0 +1,98 @@ +/* ----------------------------------------------------------------------- * + * + * cat_path.c - boundary aware buffer management routines + * + * Copyright 2002-2003 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include "automount.h" +/* + * sum = "dir/base" with attention to buffer overflows, and multiple + * slashes at the joint are avoided. + */ +int cat_path(char *buf, size_t len, const char *dir, const char *base) +{ + char *d = (char *) dir; + char *b = (char *) base; + char *s = buf; + size_t left = len; + + if ((*s = *d)) + while ((*++s = *++d) && --left) ; + + if (!left) { + *s = '\0'; + return 0; + } + + /* Now we have at least 1 left in output buffer */ + + while (*--s == '/' && (left++ < len)) + *s = '\0'; + + *++s = '/'; + left--; + + if (*b == '/') + while (*++b == '/'); + + while (--left && (*++s = *b++)) ; + + if (!left) { + *s = '\0'; + return 0; + } + + return 1; +} + +size_t _strlen(const char *str, size_t max) +{ + const char *s = str; + size_t len = 0; + + while (*s++ && len < max) + len++; + + return len; +} + +/* + * sum = "dir/base" with attention to buffer overflows, and multiple + * slashes at the joint are avoided. The length of base is specified + * explicitly. + */ +int ncat_path(char *buf, size_t len, + const char *dir, const char *base, size_t blen) +{ + char name[PATH_MAX+1]; + size_t alen = _strlen(base, blen); + + if (blen > PATH_MAX || !alen) + return 0; + + strncpy(name, base, alen); + name[alen] = '\0'; + + return cat_path(buf, len, dir, name); +} + +/* Compare first n bytes of s1 and s2 and that n == strlen(s1) */ +int _strncmp(const char *s1, const char *s2, size_t n) +{ + size_t len = strlen(s1); + + if (n && n != len) + return n - len; + return strncmp(s1, s2, n); +} diff --git a/lib/defaults.c b/lib/defaults.c new file mode 100644 index 0000000..d20c190 --- /dev/null +++ b/lib/defaults.c @@ -0,0 +1,2166 @@ +/* ----------------------------------------------------------------------- * + * + * defaults.h - system initialization defaults. + * + * Copyright 2013 Red Hat, Inc. + * Copyright 2006, 2013 Ian Kent + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "list.h" +#include "defaults.h" +#ifdef WITH_LDAP +#include "lookup_ldap.h" +#endif +#include "log.h" +#include "automount.h" + +#define AUTOFS_GLOBAL_SECTION "autofs" +#define AMD_GLOBAL_SECTION "amd" + +/* + * The configuration location has changed. + * The name of the configuration is now autofs.conf and it is + * located in the same directory as the maps. AUTOFS_CONF_DIR + * remains pointed at the init system configuration. + */ +#define DEFAULT_CONFIG_FILE AUTOFS_MAP_DIR "/autofs.conf" +#define OLD_CONFIG_FILE AUTOFS_CONF_DIR "/autofs" +#define MAX_LINE_LEN 256 +#define MAX_SECTION_NAME MAX_LINE_LEN + +#define NAME_MASTER_MAP "master_map_name" + +#define NAME_TIMEOUT "timeout" +#define NAME_MASTER_WAIT "master_wait" +#define NAME_NEGATIVE_TIMEOUT "negative_timeout" +#define NAME_BROWSE_MODE "browse_mode" +#define NAME_LOGGING "logging" +#define NAME_FORCE_STD_PROG_MAP_ENV "force_standard_program_map_env" + +#define NAME_LDAP_URI "ldap_uri" +#define NAME_LDAP_TIMEOUT "ldap_timeout" +#define NAME_LDAP_NETWORK_TIMEOUT "ldap_network_timeout" + +#define NAME_SEARCH_BASE "search_base" + +#define NAME_MAP_OBJ_CLASS "map_object_class" +#define NAME_ENTRY_OBJ_CLASS "entry_object_class" +#define NAME_MAP_ATTR "map_attribute" +#define NAME_ENTRY_ATTR "entry_attribute" +#define NAME_VALUE_ATTR "value_attribute" + +#define NAME_MOUNT_NFS_DEFAULT_PROTOCOL "mount_nfs_default_protocol" +#define NAME_APPEND_OPTIONS "append_options" +#define NAME_MOUNT_WAIT "mount_wait" +#define NAME_UMOUNT_WAIT "umount_wait" +#define NAME_AUTH_CONF_FILE "auth_conf_file" + +#define NAME_MAP_HASH_TABLE_SIZE "map_hash_table_size" + +#define NAME_USE_HOSTNAME_FOR_MOUNTS "use_hostname_for_mounts" +#define NAME_DISABLE_NOT_FOUND_MESSAGE "disable_not_found_message" + +#define NAME_SSS_MASTER_MAP_WAIT "sss_master_map_wait" +#define NAME_USE_MOUNT_REQUEST_LOG_ID "use_mount_request_log_id" + +#define NAME_AMD_ARCH "arch" +#define NAME_AMD_AUTO_ATTRCACHE "auto_attrcache" +#define NAME_AMD_AUTO_DIR "auto_dir" +#define NAME_AMD_AUTOFS_USE_LOFS "autofs_use_lofs" +#define NAME_AMD_BROWSABLE_DIRS "browsable_dirs" +#define NAME_AMD_CACHE_DURATION "cache_duration" +#define NAME_AMD_CLUSTER "cluster" +#define NAME_AMD_DEBUG_MTAB_FILE "debug_mtab_file" +#define NAME_AMD_DEBUG_OPTIONS "debug_options" +#define NAME_AMD_DISMOUNT_INTERVAL "dismount_interval" +#define NAME_AMD_DOMAIN_STRIP "domain_strip" +#define NAME_AMD_EXEC_MAP_TIMEOUT "exec_map_timeout" +#define NAME_AMD_FORCED_UMOUNTS "forced_unmounts" +#define NAME_AMD_FULLY_QUALIFIED_HOSTS "fully_qualified_hosts" +#define NAME_AMD_FULL_OS "full_os" +#define NAME_AMD_HESIOD_BASE "hesiod_base" +#define NAME_AMD_KARCH "karch" +#define NAME_AMD_LDAP_BASE "ldap_base" +#define NAME_AMD_LDAP_CACHE_MAXMEM "ldap_cache_maxmem" +#define NAME_AMD_LDAP_CACHE_SECONDS "ldap_cache_seconds" +#define NAME_AMD_LDAP_HOSTPORTS "ldap_hostports" +#define NAME_AMD_LDAP_PROTO_VERSION "ldap_proto_version" +#define NAME_AMD_SUB_DOMAIN "local_domain" +#define NAME_AMD_LOCALHOST_ADDRESS "localhost_address" +#define NAME_AMD_LOG_FILE "log_file" +#define NAME_AMD_LOG_OPTIONS "log_options" +#define NAME_AMD_MAP_DEFAULTS "map_defaults" +#define NAME_AMD_MAP_OPTIONS "map_options" +#define NAME_AMD_MAP_RELOAD_INTERVAL "map_reload_interval" +#define NAME_AMD_MAP_NAME "map_name" +#define NAME_AMD_MAP_TYPE "map_type" +#define NAME_AMD_MOUNT_TYPE "mount_type" +#define NAME_AMD_PID_FILE "pid_file" +#define NAME_AMD_PORTMAP_PROGRAM "portmap_program" +#define NAME_AMD_PREFERRED_AMQ_PORT "preferred_amq_port" +#define NAME_AMD_NFS_ALLOW_ANY_INTERFACE "nfs_allow_any_interface" +#define NAME_AMD_NFS_ALLOW_INSECURE_PORT "nfs_allow_insecure_port" +#define NAME_AMD_NFS_PROTO "nfs_proto" +#define NAME_AMD_NFS_RETRANSMIT_COUNTER "nfs_retransmit_counter" +#define NAME_AMD_NFS_RETRANSMIT_COUNTER_UDP "nfs_retransmit_counter_udp" +#define NAME_AMD_NFS_RETRANSMIT_COUNTER_TCP "nfs_retransmit_counter_tcp" +#define NAME_AMD_NFS_RETRANSMIT_COUNTER_TOPLVL "nfs_retransmit_counter_toplvl" +#define NAME_AMD_NFS_RETRY_INTERVAL "nfs_retry_interval" +#define NAME_AMD_NFS_RETRY_INTERVAL_UDP "nfs_retry_interval_udp" +#define NAME_AMD_NFS_RETRY_INTERVAL_TCP "nfs_retry_interval_tcp" +#define NAME_AMD_NFS_RETRY_INTERVAL_TOPLVL "nfs_retry_interval_toplvl" +#define NAME_AMD_NFS_VERS "nfs_vers" +#define NAME_AMD_NFS_VERS_PING "nfs_vers_ping" +#define NAME_AMD_NIS_DOMAIN "nis_domain" +#define NAME_AMD_NORMALIZE_HOSTNAMES "normalize_hostnames" +#define NAME_AMD_NORMALIZE_SLASHES "normalize_slashes" +#define NAME_AMD_OS "os" +#define NAME_AMD_OSVER "osver" +#define NAME_AMD_PLOCK "plock" +#define NAME_AMD_PRINT_PID "print_pid" +#define NAME_AMD_PRINT_VERSION "print_version" +#define NAME_AMD_RESTART_MOUNTS "restart_mounts" +#define NAME_AMD_SEARCH_PATH "search_path" +#define NAME_AMD_SELECTORS_ON_DEFAULT "selectors_on_default" +#define NAME_AMD_SELECTORS_IN_DEFAULTS "selectors_in_defaults" +#define NAME_AMD_SHOW_STATFS_ENTRIES "show_statfs_entries" +#define NAME_AMD_SUN_MAP_SYNTAX "sun_map_syntax" +#define NAME_AMD_TRUNCATE_LOG "truncate_log" +#define NAME_AMD_UMOUNT_ON_EXIT "unmount_on_exit" +#define NAME_AMD_USE_TCPWRAPPERS "use_tcpwrappers" +#define NAME_AMD_VENDOR "vendor" +#define NAME_AMD_LINUX_UFS_MOUNT_TYPE "linux_ufs_mount_type" + +/* Status returns */ +#define CFG_OK 0x0000 +#define CFG_FAIL 0x0001 +#define CFG_EXISTS 0x0002 +#define CFG_NOTFOUND 0x0004 + +#define CFG_TABLE_SIZE 128 + +static const char *default_master_map_name = DEFAULT_MASTER_MAP_NAME; +static const char *default_auth_conf_file = DEFAULT_AUTH_CONF_FILE; +static const char *autofs_gbl_sec = AUTOFS_GLOBAL_SECTION; +static const char *amd_gbl_sec = AMD_GLOBAL_SECTION; + +struct conf_option { + char *section; + char *name; + char *value; + unsigned long flags; + struct conf_option *next; +}; + +struct conf_cache { + struct conf_option **hash; + time_t modified; +}; +static pthread_mutex_t conf_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct conf_cache *config = NULL; + +static int conf_load_autofs_defaults(void); +static int conf_update(const char *, const char *, const char *, unsigned long); +static void conf_delete(const char *, const char *); +static struct conf_option *conf_lookup(const char *, const char *); + +static void defaults_mutex_lock(void) +{ + int status = pthread_mutex_lock(&conf_mutex); + if (status) + fatal(status); +} + +static void defaults_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&conf_mutex); + if (status) + fatal(status); +} + +static void message(unsigned int to_syslog, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + if (to_syslog) + vsyslog(LOG_CRIT, msg, ap); + else { + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + return; +} + +static int conf_init(void) +{ + struct conf_cache *cc; + unsigned int size = CFG_TABLE_SIZE; + unsigned int i; + + cc = malloc(sizeof(struct conf_cache)); + if (!cc) + return CFG_FAIL; + cc->modified = 0; + + cc->hash = malloc(size * sizeof(struct conf_option *)); + if (!cc->hash) { + free(cc); + return CFG_FAIL; + } + + for (i = 0; i < size; i++) { + cc->hash[i] = NULL; + } + + config = cc; + + return CFG_OK; +} + +static void __conf_release(void) +{ + struct conf_cache *cc = config; + unsigned int size = CFG_TABLE_SIZE; + struct conf_option *co, *next; + unsigned int i; + + for (i = 0; i < size; i++) { + co = cc->hash[i]; + if (co == NULL) + continue; + next = co->next; + free(co->section); + free(co->name); + if (co->value) + free(co->value); + free(co); + + while (next) { + co = next; + next = co->next; + free(co->section); + free(co->name); + if (co->value) + free(co->value); + free(co); + } + cc->hash[i] = NULL; + } + + free(cc->hash); + free(cc); + config = NULL; + + return; +} + +void defaults_conf_release(void) +{ + defaults_mutex_lock(); + __conf_release(); + defaults_mutex_unlock(); + return; +} + +static int conf_load_autofs_defaults(void) +{ + struct conf_option *co; + const char *sec = autofs_gbl_sec; + int ret; + + ret = conf_update(sec, NAME_TIMEOUT, + DEFAULT_TIMEOUT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_MASTER_WAIT, + DEFAULT_MASTER_WAIT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_NEGATIVE_TIMEOUT, + DEFAULT_NEGATIVE_TIMEOUT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_BROWSE_MODE, + DEFAULT_BROWSE_MODE, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_LOGGING, + DEFAULT_LOGGING, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_LDAP_TIMEOUT, + DEFAULT_LDAP_TIMEOUT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_LDAP_NETWORK_TIMEOUT, + DEFAULT_LDAP_NETWORK_TIMEOUT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_APPEND_OPTIONS, + DEFAULT_APPEND_OPTIONS, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_MOUNT_WAIT, + DEFAULT_MOUNT_WAIT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_UMOUNT_WAIT, + DEFAULT_UMOUNT_WAIT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AUTH_CONF_FILE, + DEFAULT_AUTH_CONF_FILE, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_MOUNT_NFS_DEFAULT_PROTOCOL, + DEFAULT_MOUNT_NFS_DEFAULT_PROTOCOL, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_USE_HOSTNAME_FOR_MOUNTS, + DEFAULT_USE_HOSTNAME_FOR_MOUNTS, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_DISABLE_NOT_FOUND_MESSAGE, + DEFAULT_DISABLE_NOT_FOUND_MESSAGE, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_SSS_MASTER_MAP_WAIT, + DEFAULT_SSS_MASTER_MAP_WAIT, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_USE_MOUNT_REQUEST_LOG_ID, + DEFAULT_USE_MOUNT_REQUEST_LOG_ID, CONF_ENV); + if (ret == CFG_FAIL) + goto error; + + /* LDAP_URI and SEARCH_BASE can occur multiple times */ + while ((co = conf_lookup(sec, NAME_LDAP_URI))) + conf_delete(co->section, co->name); + + while ((co = conf_lookup(sec, NAME_SEARCH_BASE))) + conf_delete(co->section, co->name); + + return 1; + +error: + return 0; +} + +static int conf_load_amd_defaults(void) +{ + struct utsname uts; + const char *sec = amd_gbl_sec; + char *host_os_name, *host_os_version, *host_arch; + int ret; + + if (uname(&uts)) { + host_os_name = uts.sysname; + host_os_version = uts.release; + host_arch = uts.machine; + } else { + host_os_name = NULL; + host_os_version = NULL; + host_arch = NULL; + } + + ret = conf_update(sec, NAME_AMD_ARCH, host_arch, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_KARCH, host_arch, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_OS, host_os_name, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_OSVER, host_os_version, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_AUTO_DIR, + DEFAULT_AMD_AUTO_DIR, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_AUTOFS_USE_LOFS, + DEFAULT_AMD_AUTO_DIR, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_BROWSABLE_DIRS, + DEFAULT_AMD_BROWSABLE_DIRS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_CLUSTER, + DEFAULT_AMD_CLUSTER, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + /* + * DISMOUNT_INTERVAL defers to the autofs default so we + * don't set an amd default in the configuration. + */ + /*ret = conf_update(sec, NAME_AMD_DISMOUNT_INTERVAL, + DEFAULT_AMD_DISMOUNT_INTERVAL, CONF_NONE); + if (ret == CFG_FAIL) + goto error;*/ + + ret = conf_update(sec, NAME_AMD_DOMAIN_STRIP, + DEFAULT_AMD_DOMAIN_STRIP, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_EXEC_MAP_TIMEOUT, + DEFAULT_AMD_EXEC_MAP_TIMEOUT, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_FORCED_UMOUNTS, + DEFAULT_AMD_FORCED_UMOUNTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_FULLY_QUALIFIED_HOSTS, + DEFAULT_AMD_FULLY_QUALIFIED_HOSTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_FULL_OS, + DEFAULT_AMD_FULL_OS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_HESIOD_BASE, + DEFAULT_AMD_HESIOD_BASE, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_KARCH, host_arch, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_LDAP_BASE, + DEFAULT_AMD_LDAP_BASE, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_LDAP_HOSTPORTS, + DEFAULT_AMD_LDAP_HOSTPORTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_SUB_DOMAIN, + DEFAULT_AMD_SUB_DOMAIN, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_LOCALHOST_ADDRESS, + DEFAULT_AMD_LOCALHOST_ADDRESS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_LOG_OPTIONS, + DEFAULT_AMD_LOG_OPTIONS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_MAP_DEFAULTS, + DEFAULT_AMD_MAP_DEFAULTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_MAP_OPTIONS, + DEFAULT_AMD_MAP_OPTIONS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_MAP_TYPE, + DEFAULT_AMD_MAP_TYPE, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_NIS_DOMAIN, + DEFAULT_AMD_NIS_DOMAIN, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_NORMALIZE_HOSTNAMES, + DEFAULT_AMD_NORMALIZE_HOSTNAMES, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_NORMALIZE_SLASHES, + DEFAULT_AMD_NORMALIZE_SLASHES, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_OS, host_os_name, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_RESTART_MOUNTS, + DEFAULT_AMD_RESTART_MOUNTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_SEARCH_PATH, + DEFAULT_AMD_SEARCH_PATH, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + /* selectors_on_default is depricated, use selectors_in_defaults */ + ret = conf_update(sec, NAME_AMD_SELECTORS_ON_DEFAULT, + DEFAULT_AMD_SELECTORS_IN_DEFAULTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_SELECTORS_IN_DEFAULTS, + DEFAULT_AMD_SELECTORS_IN_DEFAULTS, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_UMOUNT_ON_EXIT, + DEFAULT_AMD_UMOUNT_ON_EXIT, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_VENDOR, + DEFAULT_AMD_VENDOR, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + + ret = conf_update(sec, NAME_AMD_LINUX_UFS_MOUNT_TYPE, + DEFAULT_AMD_LINUX_UFS_MOUNT_TYPE, CONF_NONE); + if (ret == CFG_FAIL) + goto error; + return 1; + +error: + return 0; +} + +static u_int32_t get_hash(const char *key, unsigned int size) +{ + const char *pkey = key; + char lkey[PATH_MAX + 1]; + char *plkey = &lkey[0]; + + while (*pkey) + *plkey++ = tolower(*pkey++); + *plkey = '\0'; + return hash(lkey, size); +} + +static int conf_add(const char *section, const char *key, const char *value, unsigned long flags) +{ + struct conf_option *co; + char *sec, *name, *val, *tmp; + unsigned int size = CFG_TABLE_SIZE; + u_int32_t key_hash; + int ret = CFG_FAIL; + + sec = name = val = tmp = NULL; + + /* Environment overrides file value */ + if (((flags & CONF_ENV) && (tmp = getenv(key))) || value) { + if (tmp) + val = strdup(tmp); + else { + if (value) + val = strdup(value); + } + if (!val) + goto error; + } + + name = strdup(key); + if (!key) + goto error; + + sec = strdup(section); + if (!sec) + goto error; + + co = malloc(sizeof(struct conf_option)); + if (!co) + goto error; + + co->section = sec; + co->name = name; + co->value = val; + co->flags = flags; + co->next = NULL; + + /* Don't change user set values in the environment */ + if (flags & CONF_ENV && value) + setenv(name, value, 0); + + key_hash = get_hash(key, size); + if (!config->hash[key_hash]) + config->hash[key_hash] = co; + else { + struct conf_option *last = NULL, *next; + next = config->hash[key_hash]; + while (next) { + last = next; + next = last->next; + } + last->next = co; + } + + return CFG_OK; + +error: + if (name) + free(name); + if (val) + free(val); + if (sec) + free(sec); + + return ret; +} + +static void conf_delete(const char *section, const char *key) +{ + struct conf_option *co, *last; + unsigned int size = CFG_TABLE_SIZE; + u_int32_t key_hash; + + last = NULL; + key_hash = get_hash(key, size); + for (co = config->hash[key_hash]; co != NULL; co = co->next) { + if (strcasecmp(section, co->section)) + continue; + if (!strcasecmp(key, co->name)) + break; + last = co; + } + + if (!co) + return; + + if (last) + last->next = co->next; + else + config->hash[key_hash] = co->next; + + free(co->section); + free(co->name); + if (co->value) + free(co->value); + free(co); +} + +static int conf_update(const char *section, + const char *key, const char *value, + unsigned long flags) +{ + struct conf_option *co = NULL; + int ret; + + ret = CFG_FAIL; + co = conf_lookup(section, key); + if (!co) + return conf_add(section, key, value, flags); + else { + char *val = NULL, *tmp = NULL; + /* Environment overrides file value */ + if (((flags & CONF_ENV) && (tmp = getenv(key))) || value) { + if (tmp) + val = strdup(tmp); + else { + if (value) + val = strdup(value); + } + if (!val) + goto error; + } + if (co->value) + free(co->value); + co->value = val; + if (flags) + co->flags = flags; + /* Don't change user set values in the environment */ + if (flags & CONF_ENV && value) + setenv(key, value, 0); + } + + return CFG_OK; + +error: + return ret; +} + +static struct conf_option *conf_lookup_key(const char *section, const char *key) +{ + struct conf_option *co; + u_int32_t key_hash; + unsigned int size = CFG_TABLE_SIZE; + + key_hash = get_hash(key, size); + for (co = config->hash[key_hash]; co != NULL; co = co->next) { + if (strcasecmp(section, co->section)) + continue; + if (!strcasecmp(key, co->name)) + break; + } + + return co; +} + +static struct conf_option *conf_lookup(const char *section, const char *key) +{ + struct conf_option *co; + + if (!key || !section) + return NULL; + + if (strlen(key) > PATH_MAX) + return NULL; + + co = conf_lookup_key(section, key); + if (!co) { + /* + * Strip "DEFAULT_" and look for config entry for + * backward compatibility with old style config names. + * Perhaps this should be a case sensitive compare? + */ + if (strlen(key) > 8 && !strncasecmp("DEFAULT_", key, 8)) + co = conf_lookup_key(section, key + 8); + else { + /* A new key name has been given but the value + * we seek is stored under an old key name (which + * includes the "DEFAULT_" prefix or doesn't exist. + */ + char old_key[PATH_MAX + 1]; + + strcpy(old_key, "DEFAULT_"); + strcat(old_key, key); + co = conf_lookup_key(section, old_key); + } + } + + return co; +} + +static char **conf_enumerate_amd_mount_sections(void) +{ + struct conf_option *this; + unsigned int count; + char **paths; + char *last; + int i, j; + + last = NULL; + count = 0; + for (i = 0; i < CFG_TABLE_SIZE; i++) { + if (!config->hash[i]) + continue; + + this = config->hash[i]; + while (this) { + /* Only amd mount section names begin with '/' */ + if (*this->section != '/') { + this = this->next; + continue; + } + + if (!last || + strcmp(this->section, last)) + count ++; + last = this->section; + this = this->next; + } + } + + if (!count) + return NULL; + + paths = (char **) malloc(((count + 1) * sizeof(char *))); + if (!paths) + return NULL; + memset(paths, 0, ((count + 1) * sizeof(char *))); + + last = NULL; + j = 0; + + for (i = 0; i < CFG_TABLE_SIZE; i++) { + if (!config->hash[i]) + continue; + + this = config->hash[i]; + while (this) { + /* Only amd mount section names begin with '/' */ + if (*this->section != '/') { + this = this->next; + continue; + } + + if (!last || + strcmp(this->section, last)) { + char *path = strdup(this->section); + if (!path) + goto fail; + paths[j++] = path; + } + last = this->section; + this = this->next; + } + } + + return paths; + +fail: + i = 0; + while (paths[i]) + free(paths[i++]); + free(paths); + return NULL; +} + +static unsigned int conf_section_exists(const char *section) +{ + struct conf_option *co; + int ret; + + if (!section) + return 0; + + ret = 0; + defaults_mutex_lock(); + co = conf_lookup(section, section); + if (co) + ret = 1; + defaults_mutex_unlock(); + + return ret; +} + +/* + * We've changed the key names so we need to check for the + * config key and it's old name for backward conpatibility. +*/ +static int check_set_config_value(const char *section, + const char *res, const char *value) +{ + const char *sec; + int ret; + + if (section) + sec = section; + else + sec = autofs_gbl_sec; + + if (!strcasecmp(res, NAME_LDAP_URI)) + ret = conf_add(sec, res, value, 0); + else if (!strcasecmp(res, NAME_SEARCH_BASE)) + ret = conf_add(sec, res, value, 0); + else + ret = conf_update(sec, res, value, 0); + + return ret; +} + +static int parse_line(char *line, char **sec, char **res, char **value) +{ + char *key, *val, *trailer; + char *tmp; + int len; + + key = line; + + if (*key == '#' || (*key != '[' && !isalpha(*key))) + return 0; + + while (*key && isblank(*key)) + key++; + + if (!*key) + return 0; + + if (*key == '[') { + char *tmp; + while (*key && (*key == '[' || isblank(*key))) + key++; + tmp = strchr(key, ']'); + if (!tmp) + return 0; + *tmp = ' '; + while (*tmp && isblank(*tmp)) { + *tmp = '\0'; + tmp--; + } + *sec = key; + *res = NULL; + *value = NULL; + return 1; + } + + if (!(val = strchr(key, '='))) + return 0; + + tmp = val; + + *val++ = '\0'; + + while (isblank(*(--tmp))) + *tmp = '\0'; + + while (*val && (*val == '"' || isblank(*val))) + val++; + + len = strlen(val); + + if (val[len - 1] == '\n') { + val[len - 1] = '\0'; + len--; + } + + trailer = strchr(val, '#'); + if (!trailer) + trailer = val + len - 1; + else + trailer--; + + while (*trailer && (*trailer == '"' || isblank(*trailer))) + *(trailer--) = '\0';; + + *sec = NULL; + *res = key; + *value = val; + + return 1; +} + +static int read_config(unsigned int to_syslog, FILE *f, const char *name) +{ + char buf[MAX_LINE_LEN + 2]; + char secbuf[MAX_SECTION_NAME]; + char *new_sec; + char *res; + + new_sec = NULL; + while ((res = fgets(buf, MAX_LINE_LEN + 1, f))) { + char *sec, *key, *value; + + if (strlen(res) > MAX_LINE_LEN) { + message(to_syslog, "%s was truncated, ignored", res); + continue; + } + + sec = key = value = NULL; + if (!parse_line(res, &sec, &key, &value)) + continue; + if (sec) { + strcpy(secbuf, sec); + new_sec = &secbuf[0]; + conf_update(sec, sec, NULL, 0); + continue; + } + if (!strcasecmp(res, NAME_AMD_MOUNT_TYPE)) { + message(to_syslog, + "%s is always autofs, ignored", res); + continue; + } + if (!strcasecmp(res, NAME_AMD_PID_FILE)) { + message(to_syslog, + "%s must be specified as a command line" + " option, ignored", res); + continue; + } + if (!strcasecmp(res, NAME_AMD_RESTART_MOUNTS)) { + message(to_syslog, + "%s is always done by autofs, ignored", res); + continue; + } + if (!strcasecmp(res, NAME_AMD_USE_TCPWRAPPERS) || + !strcasecmp(res, NAME_AMD_AUTO_ATTRCACHE) || + !strcasecmp(res, NAME_AMD_PRINT_PID) || + !strcasecmp(res, NAME_AMD_PRINT_VERSION) || + !strcasecmp(res, NAME_AMD_LOG_FILE) || + !strcasecmp(res, NAME_AMD_PREFERRED_AMQ_PORT) || + !strcasecmp(res, NAME_AMD_TRUNCATE_LOG) || + !strcasecmp(res, NAME_AMD_DEBUG_MTAB_FILE) || + !strcasecmp(res, NAME_AMD_DEBUG_OPTIONS) || + !strcasecmp(res, NAME_AMD_SUN_MAP_SYNTAX) || + !strcasecmp(res, NAME_AMD_PORTMAP_PROGRAM) || + !strcasecmp(res, NAME_AMD_NFS_VERS) || + !strcasecmp(res, NAME_AMD_NFS_VERS_PING) || + !strcasecmp(res, NAME_AMD_NFS_PROTO) || + !strcasecmp(res, NAME_AMD_NFS_ALLOW_ANY_INTERFACE) || + !strcasecmp(res, NAME_AMD_NFS_ALLOW_INSECURE_PORT) || + !strcasecmp(res, NAME_AMD_NFS_RETRANSMIT_COUNTER) || + !strcasecmp(res, NAME_AMD_NFS_RETRANSMIT_COUNTER_UDP) || + !strcasecmp(res, NAME_AMD_NFS_RETRANSMIT_COUNTER_TCP) || + !strcasecmp(res, NAME_AMD_NFS_RETRANSMIT_COUNTER_TOPLVL) || + !strcasecmp(res, NAME_AMD_NFS_RETRY_INTERVAL) || + !strcasecmp(res, NAME_AMD_NFS_RETRY_INTERVAL_UDP) || + !strcasecmp(res, NAME_AMD_NFS_RETRY_INTERVAL_TCP) || + !strcasecmp(res, NAME_AMD_NFS_RETRY_INTERVAL_TOPLVL) || + !strcasecmp(res, NAME_AMD_LDAP_CACHE_MAXMEM) || + !strcasecmp(res, NAME_AMD_LDAP_CACHE_SECONDS) || + !strcasecmp(res, NAME_AMD_LDAP_PROTO_VERSION) || + !strcasecmp(res, NAME_AMD_SHOW_STATFS_ENTRIES) || + !strcasecmp(res, NAME_AMD_CACHE_DURATION) || + !strcasecmp(res, NAME_AMD_MAP_RELOAD_INTERVAL) || + !strcasecmp(res, NAME_AMD_MAP_OPTIONS) || + !strcasecmp(res, NAME_AMD_PLOCK)) { + message(to_syslog, + "%s is not used by autofs, ignored", res); + continue; + } + check_set_config_value(new_sec, key, value); + } + + if (!feof(f) || ferror(f)) { + message(to_syslog, + "fgets returned error %d while reading config %s", + ferror(f), name); + return 0; + } + + return 0; +} + +struct conf_option *save_ldap_option_list(const char *key) +{ + struct conf_option *co, *head, *this, *last; + unsigned int size = CFG_TABLE_SIZE; + u_int32_t key_hash; + + key_hash = get_hash(key, size); + co = config->hash[key_hash]; + if (!co) + return NULL; + last = co; + + head = this = NULL; + while (co) { + if (strcasecmp(autofs_gbl_sec, co->section)) { + last = co; + goto next; + } + + if (!strcasecmp(co->name, key)) { + /* Unlink from old */ + if (co == config->hash[key_hash]) + config->hash[key_hash] = co->next; + else + last->next = co->next; + last = co->next; + co->next = NULL; + /* Add to new */ + if (this) + this->next = co; + this = co; + /* If none have been found yet */ + if (!head) + head = co; + co = last; + continue; + } +next: + co = co->next; + } + + return head; +} + +void restore_ldap_option_list(struct conf_option *list) +{ + struct conf_option *co, *this, *last; + unsigned int size = CFG_TABLE_SIZE; + u_int32_t key_hash; + + if (!list) + return; + + this = list; + while (this) { + last = this; + this = this->next; + } + + key_hash = get_hash(list->name, size); + co = config->hash[key_hash]; + config->hash[key_hash] = list; + if (co) + last->next = co; + + return; +} + +void free_ldap_option_list(struct conf_option *list) +{ + struct conf_option *next, *this; + + if (!list) + return; + + this = list; + while (this) { + next = this->next; + free(this->section); + free(this->name); + free(this->value); + free(this); + this = next; + } + + return; +} + +static void clean_ldap_multi_option(const char *key) +{ + const char *sec = autofs_gbl_sec; + struct conf_option *co; + + while ((co = conf_lookup(sec, key))) + conf_delete(co->section, co->name); + + return; +} + +static int reset_defaults(unsigned int to_syslog) +{ + int ret; + + ret = conf_load_autofs_defaults(); + if (!ret) { + message(to_syslog, "failed to reset autofs default config"); + return 0; + } + + ret = conf_load_amd_defaults(); + if (!ret) { + message(to_syslog, "failed to reset amd default config"); + return 0; + } + + return 1; +} + +/* + * Read config env variables and check they have been set. + * + * This simple minded routine assumes the config file + * is valid bourne shell script without spaces around "=" + * and that it has valid values. + */ +unsigned int defaults_read_config(unsigned int to_syslog) +{ + FILE *conf, *oldconf; + struct stat stb, oldstb; + int ret, stat, oldstat; + + ret = 1; + + conf = oldconf = NULL; + + defaults_mutex_lock(); + if (!config) { + if (conf_init()) { + message(to_syslog, "failed to init config"); + ret = 0; + goto out; + } + } + + conf = open_fopen_r(DEFAULT_CONFIG_FILE); + if (!conf) + message(to_syslog, "failed to to open config %s", + DEFAULT_CONFIG_FILE); + + oldconf = open_fopen_r(OLD_CONFIG_FILE); + if (!oldconf) + message(to_syslog, "failed to to open old config %s", + OLD_CONFIG_FILE); + + /* Neither config has been updated */ + stat = oldstat = -1; + if (conf && oldconf && + (stat = fstat(fileno(conf), &stb) != -1) && + stb.st_mtime <= config->modified && + (oldstat = fstat(fileno(oldconf), &oldstb) == -1) && + oldstb.st_mtime <= config->modified) { + goto out; + } + + if (conf || oldconf) { + if (!reset_defaults(to_syslog)) { + ret = 0; + goto out; + } + } + + /* Update last modified */ + if (stat != -1) { + if (oldstat == -1) + config->modified = stb.st_mtime; + else { + if (oldstb.st_mtime < stb.st_mtime) + config->modified = oldstb.st_mtime; + else + config->modified = stb.st_mtime; + } + } + + if (conf) + read_config(to_syslog, conf, DEFAULT_CONFIG_FILE); + + /* + * Read the old config file and override the installed + * defaults in case user has a stale config following + * updating to the new config file location. + */ + if (oldconf) { + struct conf_option *ldap_search_base, *ldap_uris; + const char *sec = amd_gbl_sec; + struct conf_option *co; + + ldap_search_base = save_ldap_option_list(NAME_SEARCH_BASE); + if (ldap_search_base) + clean_ldap_multi_option(NAME_SEARCH_BASE); + + ldap_uris = save_ldap_option_list(NAME_LDAP_URI); + if (ldap_uris) + clean_ldap_multi_option(NAME_LDAP_URI); + + read_config(to_syslog, oldconf, OLD_CONFIG_FILE); + + if (ldap_search_base) { + co = conf_lookup(sec, NAME_SEARCH_BASE); + if (co) + free_ldap_option_list(ldap_search_base); + else + restore_ldap_option_list(ldap_search_base); + } + + if (ldap_uris) { + co = conf_lookup(sec, NAME_LDAP_URI); + if (co) + free_ldap_option_list(ldap_uris); + else + restore_ldap_option_list(ldap_uris); + } + } +out: + if (conf) + fclose(conf); + if (oldconf) + fclose(oldconf); + defaults_mutex_unlock(); + return ret; +} + +static char *conf_get_string(const char *section, const char *name) +{ + struct conf_option *co; + char *val = NULL; + + defaults_mutex_lock(); + co = conf_lookup(section, name); + if (co && co->value) + val = strdup(co->value); + defaults_mutex_unlock(); + return val; +} + +static long conf_get_number(const char *section, const char *name) +{ + struct conf_option *co; + long val = -1; + + defaults_mutex_lock(); + co = conf_lookup(section, name); + if (co && co->value) + val = atol(co->value); + defaults_mutex_unlock(); + return val; +} + +static int conf_get_yesno(const char *section, const char *name) +{ + struct conf_option *co; + int val = -1; + + defaults_mutex_lock(); + co = conf_lookup(section, name); + if (co && co->value) { + if (isdigit(*co->value)) + val = atoi(co->value); + else if (!strcasecmp(co->value, "yes")) + val = 1; + else if (!strcasecmp(co->value, "no")) + val = 0; + } + defaults_mutex_unlock(); + return val; +} + +#ifdef WITH_LDAP +void defaults_free_uris(struct list_head *list) +{ + struct list_head *next; + struct ldap_uri *uri; + + if (list_empty(list)) { + free(list); + return; + } + + next = list->next; + while (next != list) { + uri = list_entry(next, struct ldap_uri, list); + next = next->next; + list_del(&uri->list); + free(uri->uri); + free(uri); + } + free(list); + + return; +} + +static unsigned int add_uris(char *value, struct list_head *list) +{ + char *str, *tok, *ptr = NULL; + size_t len = strlen(value) + 1; + + str = malloc(len); + if (!str) + return 0; + strcpy(str, value); + + tok = strtok_r(str, " ", &ptr); + while (tok) { + struct ldap_uri *new; + char *uri; + + new = malloc(sizeof(struct ldap_uri)); + if (!new) + continue; + + uri = strdup(tok); + if (!uri) + free(new); + else { + new->uri = uri; + list_add_tail(&new->list, list); + } + + tok = strtok_r(NULL, " ", &ptr); + } + free(str); + + return 1; +} + +struct list_head *defaults_get_uris(void) +{ + struct conf_option *co; + struct list_head *list; + + list = malloc(sizeof(struct list_head)); + if (!list) + return NULL; + INIT_LIST_HEAD(list); + + if (!defaults_read_config(0)) { + free(list); + return NULL; + } + + defaults_mutex_lock(); + co = conf_lookup(autofs_gbl_sec, NAME_LDAP_URI); + if (!co) { + defaults_mutex_unlock(); + free(list); + return NULL; + } + + while (co) { + if (!strcasecmp(co->name, NAME_LDAP_URI)) + if (co->value) + add_uris(co->value, list); + co = co->next; + } + defaults_mutex_unlock(); + + if (list_empty(list)) { + free(list); + list = NULL; + } + + return list; +} + +struct ldap_schema *defaults_get_default_schema(void) +{ + struct ldap_schema *schema; + char *mc, *ma, *ec, *ea, *va; + + mc = strdup(DEFAULT_MAP_OBJ_CLASS); + if (!mc) + return NULL; + + ma = strdup(DEFAULT_MAP_ATTR); + if (!ma) { + free(mc); + return NULL; + } + + ec = strdup(DEFAULT_ENTRY_OBJ_CLASS); + if (!ec) { + free(mc); + free(ma); + return NULL; + } + + ea = strdup(DEFAULT_ENTRY_ATTR); + if (!ea) { + free(mc); + free(ma); + free(ec); + return NULL; + } + + va = strdup(DEFAULT_VALUE_ATTR); + if (!va) { + free(mc); + free(ma); + free(ec); + free(ea); + return NULL; + } + + schema = malloc(sizeof(struct ldap_schema)); + if (!schema) { + free(mc); + free(ma); + free(ec); + free(ea); + free(va); + return NULL; + } + + schema->map_class = mc; + schema->map_attr = ma; + schema->entry_class = ec; + schema->entry_attr = ea; + schema->value_attr = va; + + return schema; +} + +static struct ldap_searchdn *alloc_searchdn(const char *value) +{ + struct ldap_searchdn *sdn; + char *val; + + sdn = malloc(sizeof(struct ldap_searchdn)); + if (!sdn) + return NULL; + + val = strdup(value); + if (!val) { + free(sdn); + return NULL; + } + + sdn->basedn = val; + sdn->next = NULL; + + return sdn; +} + +void defaults_free_searchdns(struct ldap_searchdn *sdn) +{ + struct ldap_searchdn *this = sdn; + struct ldap_searchdn *next; + + while (this) { + next = this->next; + free(this->basedn); + free(this); + this = next; + } + + return; +} + +struct ldap_searchdn *defaults_get_searchdns(void) +{ + struct conf_option *co; + struct ldap_searchdn *sdn, *last; + + if (!defaults_read_config(0)) + return NULL; + + defaults_mutex_lock(); + co = conf_lookup(autofs_gbl_sec, NAME_SEARCH_BASE); + if (!co) { + defaults_mutex_unlock(); + return NULL; + } + + sdn = last = NULL; + + while (co) { + struct ldap_searchdn *new; + + if (!co->value || strcasecmp(co->name, NAME_SEARCH_BASE) ) { + co = co->next; + continue; + } + + new = alloc_searchdn(co->value); + if (!new) { + defaults_mutex_unlock(); + defaults_free_searchdns(sdn); + return NULL; + } + + if (!last) + last = new; + else { + last->next = new; + last = new; + } + + if (!sdn) + sdn = new; + + co = co->next; + } + defaults_mutex_unlock(); + + return sdn; +} + +struct ldap_schema *defaults_get_schema(void) +{ + struct ldap_schema *schema; + char *mc, *ma, *ec, *ea, *va; + const char *sec = autofs_gbl_sec; + + mc = conf_get_string(sec, NAME_MAP_OBJ_CLASS); + if (!mc) + return NULL; + + ma = conf_get_string(sec, NAME_MAP_ATTR); + if (!ma) { + free(mc); + return NULL; + } + + ec = conf_get_string(sec, NAME_ENTRY_OBJ_CLASS); + if (!ec) { + free(mc); + free(ma); + return NULL; + } + + ea = conf_get_string(sec, NAME_ENTRY_ATTR); + if (!ea) { + free(mc); + free(ma); + free(ec); + return NULL; + } + + va = conf_get_string(sec, NAME_VALUE_ATTR); + if (!va) { + free(mc); + free(ma); + free(ec); + free(ea); + return NULL; + } + + schema = malloc(sizeof(struct ldap_schema)); + if (!schema) { + free(mc); + free(ma); + free(ec); + free(ea); + free(va); + return NULL; + } + + schema->map_class = mc; + schema->map_attr = ma; + schema->entry_class = ec; + schema->entry_attr = ea; + schema->value_attr = va; + + return schema; +} +#endif + +const char *defaults_get_master_map(void) +{ + char *master = conf_get_string(autofs_gbl_sec, NAME_MASTER_MAP); + if (!master) + return strdup(default_master_map_name); + + return (const char *) master; +} + +int defaults_master_set(void) +{ + struct conf_option *co; + + defaults_mutex_lock(); + co = conf_lookup(autofs_gbl_sec, NAME_MASTER_MAP); + defaults_mutex_unlock(); + if (co) + return 1; + return 0; +} + +unsigned int defaults_get_timeout(void) +{ + long timeout; + + timeout = conf_get_number(autofs_gbl_sec, NAME_TIMEOUT); + if (timeout < 0) + timeout = atol(DEFAULT_TIMEOUT); + + return (unsigned int) timeout; +} + +int defaults_get_master_wait(void) +{ + long wait; + + wait = conf_get_number(autofs_gbl_sec, NAME_MASTER_WAIT); + if (wait < 0) + wait = atol(DEFAULT_MASTER_WAIT); + + return (int) wait; +} + +unsigned int defaults_get_negative_timeout(void) +{ + long n_timeout; + + n_timeout = conf_get_number(autofs_gbl_sec, NAME_NEGATIVE_TIMEOUT); + if (n_timeout <= 0) + n_timeout = atol(DEFAULT_NEGATIVE_TIMEOUT); + + return (unsigned int) n_timeout; +} + +unsigned int defaults_get_browse_mode(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_BROWSE_MODE); + if (res < 0) + res = atoi(DEFAULT_BROWSE_MODE); + + return res; +} + +unsigned int defaults_get_logging(void) +{ + char *res; + unsigned int logging = LOGOPT_NONE; + + res = conf_get_string(autofs_gbl_sec, NAME_LOGGING); + if (!res) + return logging; + + if (!strcasecmp(res, "none")) + logging = LOGOPT_NONE; + else { + if (!strcasecmp(res, "verbose")) + logging |= LOGOPT_VERBOSE; + + if (!strcasecmp(res, "debug")) + logging |= LOGOPT_DEBUG; + } + + free(res); + + return logging; +} + +unsigned int defaults_force_std_prog_map_env(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_FORCE_STD_PROG_MAP_ENV); + if (res < 0) + res = atoi(DEFAULT_FORCE_STD_PROG_MAP_ENV); + + return res; +} + +unsigned int defaults_get_ldap_timeout(void) +{ + int res; + + res = conf_get_number(autofs_gbl_sec, NAME_LDAP_TIMEOUT); + if (res < 0) + res = atoi(DEFAULT_LDAP_TIMEOUT); + + return res; +} + +unsigned int defaults_get_ldap_network_timeout(void) +{ + int res; + + res = conf_get_number(autofs_gbl_sec, NAME_LDAP_NETWORK_TIMEOUT); + if (res < 0) + res = atoi(DEFAULT_LDAP_NETWORK_TIMEOUT); + + return res; +} + +unsigned int defaults_get_mount_nfs_default_proto(void) +{ + int proto; + + proto = conf_get_number(autofs_gbl_sec, NAME_MOUNT_NFS_DEFAULT_PROTOCOL); + if (proto < 2 || proto > 4) + proto = atoi(DEFAULT_MOUNT_NFS_DEFAULT_PROTOCOL); + + return (unsigned int) proto; +} + +unsigned int defaults_get_append_options(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_APPEND_OPTIONS); + if (res < 0) + res = atoi(DEFAULT_APPEND_OPTIONS); + + return res; +} + +unsigned int defaults_get_mount_wait(void) +{ + long wait; + + wait = conf_get_number(autofs_gbl_sec, NAME_MOUNT_WAIT); + if (wait < 0) + wait = atoi(DEFAULT_MOUNT_WAIT); + + return (unsigned int) wait; +} + +unsigned int defaults_get_umount_wait(void) +{ + long wait; + + wait = conf_get_number(autofs_gbl_sec, NAME_UMOUNT_WAIT); + if (wait < 0) + wait = atoi(DEFAULT_UMOUNT_WAIT); + + return (unsigned int) wait; +} + +const char *defaults_get_auth_conf_file(void) +{ + char *cf; + + cf = conf_get_string(autofs_gbl_sec, NAME_AUTH_CONF_FILE); + if (!cf) + return strdup(default_auth_conf_file); + + return (const char *) cf; +} + +unsigned int defaults_get_map_hash_table_size(void) +{ + long size; + + size = conf_get_number(autofs_gbl_sec, NAME_MAP_HASH_TABLE_SIZE); + if (size < 0) + size = atoi(DEFAULT_MAP_HASH_TABLE_SIZE); + + return (unsigned int) size; +} + +unsigned int defaults_use_hostname_for_mounts(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_USE_HOSTNAME_FOR_MOUNTS); + if (res < 0) + res = atoi(DEFAULT_USE_HOSTNAME_FOR_MOUNTS); + + return res; +} + +unsigned int defaults_disable_not_found_message(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_DISABLE_NOT_FOUND_MESSAGE); + if (res < 0) + res = atoi(DEFAULT_DISABLE_NOT_FOUND_MESSAGE); + + return res; +} + +unsigned int defaults_get_sss_master_map_wait(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_SSS_MASTER_MAP_WAIT); + if (res < 0) + res = atoi(DEFAULT_SSS_MASTER_MAP_WAIT); + + return res; +} + +unsigned int defaults_get_use_mount_request_log_id(void) +{ + int res; + + res = conf_get_yesno(autofs_gbl_sec, NAME_USE_MOUNT_REQUEST_LOG_ID); + if (res < 0) + res = atoi(DEFAULT_USE_MOUNT_REQUEST_LOG_ID); + + return res; +} + +unsigned int conf_amd_mount_section_exists(const char *section) +{ + return conf_section_exists(section); +} + +char **conf_amd_get_mount_paths(void) +{ + return conf_enumerate_amd_mount_sections(); +} + +char *conf_amd_get_arch(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_ARCH); +} + +char *conf_amd_get_karch(void) +{ + char *tmp = conf_get_string(amd_gbl_sec, NAME_AMD_KARCH); + if (!tmp) + tmp = conf_amd_get_arch(); + + return tmp; +} + +char *conf_amd_get_os(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_OS); +} + +char *conf_amd_get_os_ver(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_OSVER); +} + +char *conf_amd_get_vendor(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_VENDOR); +} + +char *conf_amd_get_full_os(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_FULL_OS); +} + +char *conf_amd_get_auto_dir(void) +{ + char *tmp = conf_get_string(amd_gbl_sec, NAME_AMD_AUTO_DIR); + if (!tmp) + return strdup(DEFAULT_AMD_AUTO_DIR); + + return tmp; +} + +char *conf_amd_get_cluster(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_CLUSTER); +} + +unsigned int conf_amd_get_exec_map_timeout(void) +{ + long tmp = conf_get_number(amd_gbl_sec, NAME_AMD_EXEC_MAP_TIMEOUT); + if (tmp == -1) + tmp = atoi(DEFAULT_AMD_EXEC_MAP_TIMEOUT); + + return (unsigned int) tmp; +} + +char *conf_amd_get_hesiod_base(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_HESIOD_BASE); +} + +char *conf_amd_get_ldap_base(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_LDAP_BASE); +} + +char *conf_amd_get_ldap_hostports(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_LDAP_HOSTPORTS); +} + +unsigned int conf_amd_get_ldap_proto_version(void) +{ + long tmp = conf_get_number(amd_gbl_sec, NAME_AMD_LDAP_PROTO_VERSION); + if (tmp == -1) + tmp = atoi(DEFAULT_AMD_LDAP_PROTO_VERSION); + + return (unsigned int) tmp; +} + +char *conf_amd_get_sub_domain(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_SUB_DOMAIN); +} + +char *conf_amd_get_localhost_address(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_LOCALHOST_ADDRESS); +} + +unsigned int conf_amd_get_log_options(void) +{ + int log_level = -1; + char *tmp = conf_get_string(amd_gbl_sec, NAME_AMD_LOG_OPTIONS); + if (tmp) { + if (strstr(tmp, "debug") || strstr(tmp, "all")) { + if (log_level < LOG_DEBUG) + log_level = LOG_DEBUG; + } + if (strstr(tmp, "info") || + strstr(tmp, "user") || + strcmp(tmp, "defaults")) { + if (log_level < LOG_INFO) + log_level = LOG_INFO; + } + if (strstr(tmp, "notice")) { + if (log_level < LOG_NOTICE) + log_level = LOG_NOTICE; + } + if (strstr(tmp, "warn") || + strstr(tmp, "map") || + strstr(tmp, "stats") || + strstr(tmp, "warning")) { + if (log_level < LOG_WARNING) + log_level = LOG_WARNING; + } + if (strstr(tmp, "error")) { + if (log_level < LOG_ERR) + log_level = LOG_ERR; + } + if (strstr(tmp, "fatal")) { + if (log_level < LOG_CRIT) + log_level = LOG_CRIT; + } + free(tmp); + } + + if (log_level == -1) + log_level = LOG_ERR; + + return (unsigned int) log_level; +} + +char *conf_amd_get_nis_domain(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_NIS_DOMAIN); +} + +unsigned int conf_amd_set_nis_domain(const char *domain) +{ + int ret; + ret = conf_update(amd_gbl_sec, NAME_AMD_NIS_DOMAIN, domain, CONF_NONE); + + return (unsigned int) ret; +} + +char *conf_amd_get_map_defaults(const char *section) +{ + char *tmp = NULL; + if (section) + tmp = conf_get_string(section, NAME_AMD_MAP_DEFAULTS); + if (!tmp) + tmp = conf_get_string(amd_gbl_sec, NAME_AMD_MAP_DEFAULTS); + + return tmp; +} + +char *conf_amd_get_map_name(const char *section) +{ + char *tmp = NULL; + if (section) + tmp = conf_get_string(section, NAME_AMD_MAP_NAME); + + return tmp; +} + +char *conf_amd_get_map_options(const char *section) +{ + char *tmp = NULL; + if (section) + tmp = conf_get_string(section, NAME_AMD_MAP_OPTIONS); + + return tmp; +} + +char *conf_amd_get_map_type(const char *section) +{ + char *tmp = NULL; + if (section) + tmp = conf_get_string(section, NAME_AMD_MAP_TYPE); + if (!tmp) + tmp = conf_get_string(amd_gbl_sec, NAME_AMD_MAP_TYPE); + + return tmp; +} + +char *conf_amd_get_search_path(const char *section) +{ + char *tmp = NULL; + if (section) + tmp = conf_get_string(section, NAME_AMD_SEARCH_PATH); + if (!tmp) + tmp = conf_get_string(amd_gbl_sec, NAME_AMD_SEARCH_PATH); + + return tmp; +} + +unsigned int conf_amd_get_dismount_interval(const char *section) +{ + long tmp = -1; + if (section) + tmp = conf_get_number(section, NAME_AMD_DISMOUNT_INTERVAL); + if (tmp == -1) + tmp = conf_get_number(amd_gbl_sec, NAME_AMD_DISMOUNT_INTERVAL); + if (tmp == -1) + tmp = defaults_get_timeout(); + /* + * This won't happen as defaults_get_timeout() will return + * the autofs setting which is used if no other setting is + * found. + */ + if (tmp == -1) + tmp = atoi(DEFAULT_TIMEOUT); + + return (unsigned int) tmp; +} + +char *conf_amd_get_linux_ufs_mount_type(void) +{ + return conf_get_string(amd_gbl_sec, NAME_AMD_LINUX_UFS_MOUNT_TYPE); +} + +unsigned long conf_amd_get_flags(const char *section) +{ + const char *amd = amd_gbl_sec; + unsigned long flags, tmp; + + /* Always true for us */ + flags = CONF_MOUNT_TYPE_AUTOFS; + + tmp = -1; + if (section) + tmp = conf_get_yesno(section, NAME_AMD_BROWSABLE_DIRS); + if (tmp == -1) + tmp = conf_get_yesno(amd, NAME_AMD_BROWSABLE_DIRS); + if (tmp) + flags |= CONF_BROWSABLE_DIRS; + + tmp = -1; + if (section) + tmp = conf_get_yesno(section, NAME_AMD_SELECTORS_IN_DEFAULTS); + if (tmp == -1) + tmp = conf_get_yesno(amd, NAME_AMD_SELECTORS_IN_DEFAULTS); + if (tmp) + flags |= CONF_SELECTORS_IN_DEFAULTS; + + tmp = conf_get_yesno(amd, NAME_AMD_NORMALIZE_HOSTNAMES); + if (tmp) + flags |= CONF_NORMALIZE_HOSTNAMES; + + tmp = conf_get_yesno(amd, NAME_AMD_RESTART_MOUNTS); + if (tmp) + flags |= CONF_RESTART_EXISTING_MOUNTS; + + tmp = conf_get_yesno(amd, NAME_AMD_FULLY_QUALIFIED_HOSTS); + if (tmp) + flags |= CONF_FULLY_QUALIFIED_HOSTS; + + tmp = conf_get_yesno(amd, NAME_AMD_UMOUNT_ON_EXIT); + if (tmp) + flags |= CONF_UNMOUNT_ON_EXIT; + + tmp = -1; + if (section) + tmp = conf_get_yesno(section, NAME_AMD_AUTOFS_USE_LOFS); + if (tmp == -1) + tmp = conf_get_yesno(amd, NAME_AMD_AUTOFS_USE_LOFS); + if (tmp) + flags |= CONF_AUTOFS_USE_LOFS; + + tmp = conf_get_yesno(amd, NAME_AMD_DOMAIN_STRIP); + if (tmp) + flags |= CONF_DOMAIN_STRIP; + + tmp = conf_get_yesno(amd, NAME_AMD_NORMALIZE_SLASHES); + if (tmp) + flags |= CONF_NORMALIZE_SLASHES; + + tmp = conf_get_yesno(amd, NAME_AMD_FORCED_UMOUNTS); + if (tmp) + flags |= CONF_FORCED_UNMOUNTS; + + return flags; +} diff --git a/lib/dev-ioctl-lib.c b/lib/dev-ioctl-lib.c new file mode 100644 index 0000000..e851923 --- /dev/null +++ b/lib/dev-ioctl-lib.c @@ -0,0 +1,777 @@ +/* ----------------------------------------------------------------------- * + * + * ctl-dev-lib.c - module for Linux automount mount table lookup functions + * + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +/* ioctld control function interface */ +static struct ioctl_ctl ctl = { -1, NULL }; + +#ifndef AUTOFS_SUPER_MAGIC +#define AUTOFS_SUPER_MAGIC 0x0187 +#endif + +/* + * Define functions for autofs ioctl control. + * + * We provide two interfaces. One which routes ioctls via a + * miscelaneous device node and can be used to obtain an ioctl + * file descriptor for autofs mounts that are covered by an + * active mount (eg. active direct or multi-mount offsets). + * The other provides the traditional autofs ioctl implementation. + * + * The miscielaneous device control functions are prefixed with + * dev_ctl_ and the traditional ones are prefixed with ioctl_. + */ +static int dev_ioctl_version(unsigned int, int, struct autofs_dev_ioctl *); +static int dev_ioctl_protover(unsigned int, int, unsigned int *); +static int dev_ioctl_protosubver(unsigned int, int, unsigned int *); +static int dev_ioctl_mount_device(unsigned int, const char *, unsigned int, dev_t *); +static int dev_ioctl_open(unsigned int, int *, dev_t, const char *); +static int dev_ioctl_close(unsigned int, int); +static int dev_ioctl_send_ready(unsigned int, int, unsigned int); +static int dev_ioctl_send_fail(unsigned int, int, unsigned int, int); +static int dev_ioctl_setpipefd(unsigned int, int, int); +static int dev_ioctl_catatonic(unsigned int, int); +static int dev_ioctl_timeout(unsigned int, int, time_t); +static int dev_ioctl_requester(unsigned int, int, const char *, uid_t *, gid_t *); +static int dev_ioctl_expire(unsigned int, int, const char *, unsigned int); +static int dev_ioctl_askumount(unsigned int, int, unsigned int *); +static int dev_ioctl_ismountpoint(unsigned int, int, const char *, unsigned int *); + +static int ioctl_protover(unsigned int, int, unsigned int *); +static int ioctl_protosubver(unsigned int, int, unsigned int *); +static int ioctl_mount_device(unsigned int, const char *, unsigned int, dev_t *); +static int ioctl_open(unsigned int, int *, dev_t, const char *); +static int ioctl_close(unsigned int, int); +static int ioctl_send_ready(unsigned int, int, unsigned int); +static int ioctl_send_fail(unsigned int, int, unsigned int, int); +static int ioctl_catatonic(unsigned int, int); +static int ioctl_timeout(unsigned int, int, time_t); +static int ioctl_expire(unsigned int, int, const char *, unsigned int); +static int ioctl_askumount(unsigned int, int, unsigned int *); + +static struct ioctl_ops dev_ioctl_ops = { + .version = dev_ioctl_version, + .protover = dev_ioctl_protover, + .protosubver = dev_ioctl_protosubver, + .mount_device = dev_ioctl_mount_device, + .open = dev_ioctl_open, + .close = dev_ioctl_close, + .send_ready = dev_ioctl_send_ready, + .send_fail = dev_ioctl_send_fail, + .setpipefd = dev_ioctl_setpipefd, + .catatonic = dev_ioctl_catatonic, + .timeout = dev_ioctl_timeout, + .requester = dev_ioctl_requester, + .expire = dev_ioctl_expire, + .askumount = dev_ioctl_askumount, + .ismountpoint = dev_ioctl_ismountpoint +}; + +static struct ioctl_ops ioctl_ops = { + .version = NULL, + .protover = ioctl_protover, + .protosubver = ioctl_protosubver, + .mount_device = ioctl_mount_device, + .open = ioctl_open, + .close = ioctl_close, + .send_ready = ioctl_send_ready, + .send_fail = ioctl_send_fail, + .setpipefd = NULL, + .catatonic = ioctl_catatonic, + .timeout = ioctl_timeout, + .requester = NULL, + .expire = ioctl_expire, + .askumount = ioctl_askumount, + .ismountpoint = NULL +}; + +/* + * Allocate the control struct that holds the misc device file + * descriptor and operation despatcher table. + */ +void init_ioctl_ctl(void) +{ + int devfd; + + if (ctl.ops) + return; + + devfd = open_fd(CONTROL_DEVICE, O_RDONLY); + if (devfd == -1) + ctl.ops = &ioctl_ops; + else { + struct autofs_dev_ioctl param; + + /* + * Check compile version against kernel. + * Selinux may allow us to open the device but not + * actually allow us to do anything. + */ + init_autofs_dev_ioctl(¶m); + if (ioctl(devfd, AUTOFS_DEV_IOCTL_VERSION, ¶m) == -1) { + close(devfd); + ctl.ops = &ioctl_ops; + } else { + ctl.devfd = devfd; + ctl.ops = &dev_ioctl_ops; + } + } + return; +} + +void close_ioctl_ctl(void) +{ + if (ctl.devfd != -1) { + close(ctl.devfd); + ctl.devfd = -1; + } + ctl.ops = NULL; + return; +} + +/* Return a pointer to the operations control struct */ +struct ioctl_ops *get_ioctl_ops(void) +{ + if (!ctl.ops) + init_ioctl_ctl(); + return ctl.ops; +} + +/* Get kenrel version of misc device code */ +static int dev_ioctl_version(unsigned int logopt, + int ioctlfd, struct autofs_dev_ioctl *param) +{ + param->ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_VERSION, param) == -1) + return -1; + + return 0; +} + +/* Get major version of autofs kernel module mount protocol */ +static int dev_ioctl_protover(unsigned int logopt, + int ioctlfd, unsigned int *major) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) == -1) + return -1; + + *major = param.protover.version; + + return 0; +} + +static int ioctl_protover(unsigned int logopt, + int ioctlfd, unsigned int *major) +{ + return ioctl(ioctlfd, AUTOFS_IOC_PROTOVER, major); +} + +/* Get minor version of autofs kernel module mount protocol */ +static int dev_ioctl_protosubver(unsigned int logopt, + int ioctlfd, unsigned int *minor) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) == -1) + return -1; + + *minor = param.protosubver.sub_version; + + return 0; +} + +static int ioctl_protosubver(unsigned int logopt, + int ioctlfd, unsigned int *minor) +{ + return ioctl(ioctlfd, AUTOFS_IOC_PROTOSUBVER, minor); +} + +/* + * Allocate a parameter struct for misc device ioctl used when + * opening an autofs mount point. Attach the path to the end + * of the struct. and lookup the device number if not given. + * Locating the device number relies on the mount option + * "dev=" being present in the autofs fs mount + * options. + */ +static struct autofs_dev_ioctl *alloc_dev_ioctl_open(const char *path, dev_t devid) +{ + struct autofs_dev_ioctl *ioctl; + size_t size, p_len; + dev_t devno = devid; + + if (!path) + return NULL; + + p_len = strlen(path); + size = sizeof(struct autofs_dev_ioctl) + p_len + 1; + ioctl = malloc(size); + if (!ioctl) { + errno = ENOMEM; + return NULL; + } + + init_autofs_dev_ioctl(ioctl); + ioctl->size = size; + memcpy(ioctl->path, path, p_len); + ioctl->path[p_len] = '\0'; + ioctl->openmount.devid = devno; + + return ioctl; +} + +static void free_dev_ioctl_open(struct autofs_dev_ioctl *ioctl) +{ + free(ioctl); + return; +} + +/* + * Allocate a parameter struct for misc device ioctl which includes + * a path. This is used when getting the last mount requester uid + * and gid and when checking if a path within the autofs filesystem + * is a mount point. We add the path to the end of the struct. + */ +static struct autofs_dev_ioctl *alloc_dev_ioctl_path(int ioctlfd, const char *path) +{ + struct autofs_dev_ioctl *ioctl; + size_t size, p_len; + + if (!path) { + errno = EINVAL; + return NULL; + } + + p_len = strlen(path); + size = sizeof(struct autofs_dev_ioctl) + p_len + 1; + ioctl = malloc(size); + if (!ioctl) { + errno = ENOMEM; + return NULL; + } + + init_autofs_dev_ioctl(ioctl); + ioctl->ioctlfd = ioctlfd; + ioctl->size = size; + memcpy(ioctl->path, path, p_len); + ioctl->path[p_len] = '\0'; + + return ioctl; +} + +static void free_dev_ioctl_path(struct autofs_dev_ioctl *ioctl) +{ + free(ioctl); + return; +} + +/* + * Find the device number of an autofs mount with given path and + * type (eg..AUTOFS_TYPE_DIRECT). The device number is used by + * the kernel to identify the autofs super block when searching + * for the mount. + */ +static int dev_ioctl_mount_device(unsigned int logopt, const char *path, unsigned int type, dev_t *devid) +{ + struct autofs_dev_ioctl *param; + int err; + + if (!path) { + errno = EINVAL; + return -1; + } + + *devid = -1; + + param = alloc_dev_ioctl_path(-1, path); + if (!param) + return -1; + param->ismountpoint.in.type = type; + + err = ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_ISMOUNTPOINT, param); + if (err == -1) { + int save_errno = errno; + free_dev_ioctl_path(param); + errno = save_errno; + return -1; + } + + if (err) + *devid = param->ismountpoint.out.devid; + + free_dev_ioctl_path(param); + + return err; +} + +static int ioctl_mount_device(unsigned int logopt, + const char *path, unsigned int type, + dev_t *devid) +{ + return -1; +} + +/* Get a file descriptor for control operations */ +static int dev_ioctl_open(unsigned int logopt, + int *ioctlfd, dev_t devid, const char *path) +{ + struct autofs_dev_ioctl *param; + + *ioctlfd = -1; + + param = alloc_dev_ioctl_open(path, devid); + if (!param) + return -1; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) == -1) { + int save_errno = errno; + free_dev_ioctl_open(param); + errno = save_errno; + return -1; + } + + *ioctlfd = param->ioctlfd; + + free_dev_ioctl_open(param); + + return 0; +} + +static int ioctl_open(unsigned int logopt, + int *ioctlfd, dev_t devid, const char *path) +{ + struct statfs sfs; + int save_errno, fd; + + *ioctlfd = -1; + + fd = open_fd(path, O_RDONLY); + if (fd == -1) + return -1; + + if (fstatfs(fd, &sfs) == -1) { + save_errno = errno; + goto err; + } + + if (sfs.f_type != AUTOFS_SUPER_MAGIC) { + save_errno = ENOENT; + goto err; + } + + *ioctlfd = fd; + + return 0; +err: + close(fd); + errno = save_errno; + return -1; +} + +/* Close */ +static int dev_ioctl_close(unsigned int logopt, int ioctlfd) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_CLOSEMOUNT, ¶m) == -1) + return -1; + + return 0; +} + +static int ioctl_close(unsigned int logopt, int ioctlfd) +{ + return close(ioctlfd); +} + +/* Send ready status for given token */ +static int dev_ioctl_send_ready(unsigned int logopt, + int ioctlfd, unsigned int token) +{ + struct autofs_dev_ioctl param; + + if (token == 0) { + errno = EINVAL; + return -1; + } + + debug(logopt, "token = %d", token); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + param.ready.token = token; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_READY, ¶m) == -1) { + char *estr, buf[MAX_ERR_BUF]; + int save_errno = errno; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("AUTOFS_DEV_IOCTL_READY: error %s", estr); + errno = save_errno; + return -1; + } + return 0; +} + +static int ioctl_send_ready(unsigned int logopt, + int ioctlfd, unsigned int token) +{ + if (token == 0) { + errno = EINVAL; + return -1; + } + + debug(logopt, "token = %d", token); + + if (ioctl(ioctlfd, AUTOFS_IOC_READY, token) == -1) { + char *estr, buf[MAX_ERR_BUF]; + int save_errno = errno; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("AUTOFS_IOC_READY: error %s", estr); + errno = save_errno; + return -1; + } + return 0; +} + +/* + * Send ready status for given token. + * + * The device node ioctl implementation allows for sending a status + * of other than ENOENT, unlike the tradional interface. + */ +static int dev_ioctl_send_fail(unsigned int logopt, + int ioctlfd, unsigned int token, int status) +{ + struct autofs_dev_ioctl param; + + if (token == 0) { + errno = EINVAL; + return -1; + } + + debug(logopt, "token = %d", token); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + param.fail.token = token; + param.fail.status = status; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_FAIL, ¶m) == -1) { + char *estr, buf[MAX_ERR_BUF]; + int save_errno = errno; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("AUTOFS_DEV_IOCTL_FAIL: error %s", estr); + errno = save_errno; + return -1; + } + return 0; +} + +static int ioctl_send_fail(unsigned int logopt, + int ioctlfd, unsigned int token, int status) +{ + if (token == 0) { + errno = EINVAL; + return -1; + } + + debug(logopt, "token = %d", token); + + if (ioctl(ioctlfd, AUTOFS_IOC_FAIL, token) == -1) { + char *estr, buf[MAX_ERR_BUF]; + int save_errno = errno; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("AUTOFS_IOC_FAIL: error %s", estr); + errno = save_errno; + return -1; + } + return 0; +} + +/* + * Set the pipe fd for kernel communication. + * + * Normally this is set at mount using an option but if we + * are reconnecting to a busy mount then we need to use this + * to tell the autofs kernel module about the new pipe fd. In + * order to protect mounts against incorrectly setting the + * pipefd we also require that the autofs mount be catatonic. + * + * If successful this also sets the process group id used to + * identify the controlling process to the process group of + * the caller. + */ +static int dev_ioctl_setpipefd(unsigned int logopt, int ioctlfd, int pipefd) +{ + struct autofs_dev_ioctl param; + + if (pipefd == -1) { + errno = EBADF; + return -1; + } + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + param.setpipefd.pipefd = pipefd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_SETPIPEFD, ¶m) == -1) + return -1; + + return 0; +} + +/* + * Make the autofs mount point catatonic, no longer responsive to + * mount requests. Also closes the kernel pipe file descriptor. + */ +static int dev_ioctl_catatonic(unsigned int logopt, int ioctlfd) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_CATATONIC, ¶m) == -1) + return -1; + + return 0; +} + +static int ioctl_catatonic(unsigned int logopt, int ioctlfd) +{ + return ioctl(ioctlfd, AUTOFS_IOC_CATATONIC, 0); +} + +/* Set the autofs mount timeout */ +static int dev_ioctl_timeout(unsigned int logopt, int ioctlfd, time_t timeout) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + param.timeout.timeout = timeout; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) == -1) + return -1; + + return 0; +} + +static int ioctl_timeout(unsigned int logopt, int ioctlfd, time_t timeout) +{ + time_t tout = timeout; + return ioctl(ioctlfd, AUTOFS_IOC_SETTIMEOUT, &tout); +} + +/* + * Get the uid and gid of the last request for the mountpoint, path. + * + * 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. + */ +static int dev_ioctl_requester(unsigned int logopt, + int ioctlfd, const char *path, + uid_t *uid, gid_t *gid) +{ + struct autofs_dev_ioctl *param; + int err; + + if (!path) + errno = EINVAL; + + *uid = -1; + *gid = -1; + + + param = alloc_dev_ioctl_path(ioctlfd, path); + if (!param) + return -1; + + err = ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_REQUESTER, param); + if (err == -1) { + int save_errno = errno; + free_dev_ioctl_open(param); + errno = save_errno; + return -1; + } + + *uid = param->requester.uid; + *gid = param->requester.gid; + + free_dev_ioctl_path(param); + + return 0; +} + +/* + * Call repeatedly until it returns EAGAIN, meaning there's nothing + * more that can be done. + */ +static int expire(unsigned int logopt, + int cmd, int fd, int ioctlfd, const char *path, void *arg) +{ + int ret, retries = EXPIRE_RETRIES; + unsigned int may_umount; + + while (retries--) { + struct timespec tm = {0, 100000000}; + + /* Ggenerate expire message for the mount. */ + ret = ioctl(fd, cmd, arg); + if (ret == -1) { + /* Mount has gone away */ + if (errno == EBADF || errno == EINVAL) + return 0; + + /* + * Other than EAGAIN is an expire error so continue. + * Kernel will try the next mount for indirect maps + * and the same mount again for direct maps, limited + * by retries. + */ + if (errno == EAGAIN) + break; + } + nanosleep(&tm, NULL); + } + + may_umount = 0; + if (ctl.ops->askumount(logopt, ioctlfd, &may_umount)) + return -1; + + if (!may_umount) + return 1; + + return 0; +} + +static int dev_ioctl_expire(unsigned int logopt, + int ioctlfd, const char *path, unsigned int when) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + param.expire.how = when; + + return expire(logopt, AUTOFS_DEV_IOCTL_EXPIRE, + ctl.devfd, ioctlfd, path, (void *) ¶m); +} + +static int ioctl_expire(unsigned int logopt, + int ioctlfd, const char *path, unsigned int when) +{ + return expire(logopt, AUTOFS_IOC_EXPIRE_MULTI, + ioctlfd, ioctlfd, path, (void *) &when); +} + +/* Check if autofs mount point is in use */ +static int dev_ioctl_askumount(unsigned int logopt, + int ioctlfd, unsigned int *busy) +{ + struct autofs_dev_ioctl param; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctlfd; + + if (ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_ASKUMOUNT, ¶m) == -1) + return -1; + + *busy = param.askumount.may_umount; + + return 0; +} + +static int ioctl_askumount(unsigned int logopt, + int ioctlfd, unsigned int *busy) +{ + return ioctl(ioctlfd, AUTOFS_IOC_ASKUMOUNT, busy); +} + +/* + * Check if the given path is a mountpoint. + * + * The path is considered a mountpoint if it is itself a mountpoint + * or contains a mount, such as a multi-mount without a root mount. + * In addition, if the path is itself a mountpoint we return whether + * the mounted file system is an autofs filesystem or other file + * system. + */ +static int dev_ioctl_ismountpoint(unsigned int logopt, + int ioctlfd, const char *path, + unsigned int *mountpoint) +{ + struct autofs_dev_ioctl *param; + int err; + + *mountpoint = 0; + + if (!path) { + errno = EINVAL; + return -1; + } + + param = alloc_dev_ioctl_path(ioctlfd, path); + if (!param) + return -1; + set_autofs_type_any(¶m->ismountpoint.in.type); + + err = ioctl(ctl.devfd, AUTOFS_DEV_IOCTL_ISMOUNTPOINT, param); + if (err == -1) { + int save_errno = errno; + free_dev_ioctl_path(param); + errno = save_errno; + return -1; + } + + if (err) { + *mountpoint = DEV_IOCTL_IS_MOUNTED; + + if (param->ismountpoint.out.magic == AUTOFS_SUPER_MAGIC) + *mountpoint |= DEV_IOCTL_IS_AUTOFS; + else + *mountpoint |= DEV_IOCTL_IS_OTHER; + } + + free_dev_ioctl_path(param); + + return 0; +} diff --git a/lib/log.c b/lib/log.c new file mode 100644 index 0000000..1a0bc3f --- /dev/null +++ b/lib/log.c @@ -0,0 +1,355 @@ +/* ----------------------------------------------------------------------- * + * + * log.c - applcation logging routines. + * + * Copyright 2004 Denis Vlasenko + * - All Rights Reserved + * Copyright 2005 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * This module has been adapted from patches submitted by: + * Denis Vlasenko + * Thanks Denis. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include + +#include "automount.h" + +static unsigned int syslog_open = 0; +static unsigned int logging_to_syslog = 0; + +/* log notification level */ +static unsigned int do_verbose = 0; /* Verbose feedback option */ +static unsigned int do_debug = 0; /* Full debug output */ + +static char *prepare_attempt_prefix(const char *msg) +{ + unsigned long *attempt_id; + char buffer[ATTEMPT_ID_SIZE + 1]; + char *prefixed_msg = NULL; + + attempt_id = pthread_getspecific(key_thread_attempt_id); + if (attempt_id) { + int len = sizeof(buffer) + 1 + strlen(msg) + 1; + + snprintf(buffer, ATTEMPT_ID_SIZE, "%02lx", *attempt_id); + prefixed_msg = (char *) calloc(len, sizeof(char)); + strcpy(prefixed_msg, buffer); + strcat(prefixed_msg, "|"); + strcat(prefixed_msg, msg); + } + + return prefixed_msg; +} + +void set_log_norm(void) +{ + do_verbose = 0; + do_debug = 0; + return; +} + +void set_log_verbose(void) +{ + do_verbose = 1; + return; +} + +void set_log_debug(void) +{ + do_debug = 1; + return; +} + +void set_log_norm_ap(struct autofs_point *ap) +{ + ap->logopt = LOGOPT_ERROR; + return; +} + +void set_log_verbose_ap(struct autofs_point *ap) +{ + ap->logopt = LOGOPT_VERBOSE; + return; +} + +void set_log_debug_ap(struct autofs_point *ap) +{ + ap->logopt = LOGOPT_DEBUG; + return; +} + +void log_info(unsigned int logopt, const char *msg, ...) +{ + unsigned int opt_log = logopt & (LOGOPT_DEBUG | LOGOPT_VERBOSE); + char *prefixed_msg; + va_list ap; + + if (!do_debug && !do_verbose && !opt_log) + return; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_INFO, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void log_notice(unsigned int logopt, const char *msg, ...) +{ + unsigned int opt_log = logopt & (LOGOPT_DEBUG | LOGOPT_VERBOSE); + char *prefixed_msg; + va_list ap; + + if (!do_debug && !do_verbose && !opt_log) + return; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_NOTICE, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void log_warn(unsigned int logopt, const char *msg, ...) +{ + unsigned int opt_log = logopt & (LOGOPT_DEBUG | LOGOPT_VERBOSE); + char *prefixed_msg; + va_list ap; + + if (!do_debug && !do_verbose && !opt_log) + return; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_WARNING, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void log_error(unsigned logopt, const char *msg, ...) +{ + char *prefixed_msg; + va_list ap; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_ERR, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void log_crit(unsigned logopt, const char *msg, ...) +{ + char *prefixed_msg; + va_list ap; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_CRIT, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void log_debug(unsigned int logopt, const char *msg, ...) +{ + unsigned int opt_log = logopt & LOGOPT_DEBUG; + char *prefixed_msg; + va_list ap; + + if (!do_debug && !opt_log) + return; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_WARNING, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void logmsg(const char *msg, ...) +{ + char *prefixed_msg; + va_list ap; + + prefixed_msg = prepare_attempt_prefix(msg); + + va_start(ap, msg); + if (logging_to_syslog) { + if (prefixed_msg) + vsyslog(LOG_CRIT, prefixed_msg, ap); + else + vsyslog(LOG_INFO, msg, ap); + } else { + if (prefixed_msg) + vfprintf(stderr, prefixed_msg, ap); + else + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + } + va_end(ap); + + if (prefixed_msg) + free(prefixed_msg); + + return; +} + +void open_log(void) +{ + if (!syslog_open) { + syslog_open = 1; + openlog("automount", LOG_PID, LOG_DAEMON); + } + + logging_to_syslog = 1; + return; +} + +void log_to_syslog(void) +{ + char buf[MAX_ERR_BUF]; + int nullfd; + + open_log(); + + /* Redirect all our file descriptors to /dev/null */ + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + fprintf(stderr, "cannot open /dev/null: %s", estr); + exit(1); + } + + if (dup2(nullfd, STDIN_FILENO) < 0 || + dup2(nullfd, STDOUT_FILENO) < 0 || + dup2(nullfd, STDERR_FILENO) < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + fprintf(stderr, + "redirecting file descriptors failed: %s", estr); + exit(1); + } + + if (nullfd > 2) + close(nullfd); + + return; +} + +void log_to_stderr(void) +{ + if (syslog_open) { + syslog_open = 0; + closelog(); + } + + logging_to_syslog = 0; + + return; +} diff --git a/lib/macros.c b/lib/macros.c new file mode 100644 index 0000000..ff9ba89 --- /dev/null +++ b/lib/macros.c @@ -0,0 +1,519 @@ +/* ----------------------------------------------------------------------- * + * + * macros.c - module to handle macro substitution variables for map + * entries. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +static struct utsname un; +static char processor[65]; /* Not defined on Linux, so we make our own */ +static char hostname[HOST_NAME_MAX + 1]; +static char host[HOST_NAME_MAX]; +static char domain[HOST_NAME_MAX]; +static char hostd[HOST_NAME_MAX + 1]; +static char endian[] = "unknown"; + +/* Predefined variables: tail of link chain */ +static struct substvar + sv_arch = {"ARCH", un.machine, 1, NULL }, + sv_cpu = {"CPU", processor, 1, &sv_arch}, + sv_host = {"HOST", un.nodename, 1, &sv_cpu}, + sv_osname = {"OSNAME", un.sysname, 1, &sv_host}, + sv_osrel = {"OSREL", un.release, 1, &sv_osname}, + sv_osvers = {"OSVERS", un.version, 1, &sv_osrel}, + sv_dollar = {"dollar", "$", 1, &sv_osvers}, + sv_true = {"true", "1", 1, &sv_dollar}, + sv_false = {"false", "0", 1, &sv_true}, + sv_byte = {"byte", endian, 1, &sv_false}, + sv_host2 = {"host", host, 1, &sv_byte}, + sv_xhost = {"xhost", host, 1, &sv_host2}, + sv_domain = {"domain", domain, 1, &sv_xhost}, + sv_hostd = {"hostd", hostd, 1, &sv_domain +}; + +static struct substvar *system_table = &sv_hostd; +static unsigned int macro_init_done = 0; + +static pthread_mutex_t table_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t macro_mutex = PTHREAD_MUTEX_INITIALIZER; + +void dump_table(struct substvar *table) +{ + struct substvar *lv = table; + int status; + + status = pthread_mutex_lock(&table_mutex); + if (status) + fatal(status); + + while (lv) { + logmsg("lv->def %s lv->val %s lv->next %p", + lv->def, lv->val, lv->next); + lv = lv->next; + } + + status = pthread_mutex_unlock(&table_mutex); + if (status) + fatal(status); +} + +/* Get processor information for predefined macro definitions */ +void macro_init(void) +{ + char *local_domain; + + memset(hostname, 0, HOST_NAME_MAX + 1); + memset(host, 0, HOST_NAME_MAX); + memset(domain, 0, HOST_NAME_MAX); + memset(hostd, 0, HOST_NAME_MAX + 1); + + macro_lock(); + if (macro_init_done) { + macro_unlock(); + return; + } + + uname(&un); + /* + * uname -p is not defined on Linux. Make it the same as + * uname -m, except make it return i386 on all x86 (x >= 3) + */ + strcpy(processor, un.machine); + if (processor[0] == 'i' && processor[1] >= '3' && + !strcmp(processor + 2, "86")) + processor[1] = '3'; + + local_domain = conf_amd_get_sub_domain(); + + if (!gethostname(hostname, HOST_NAME_MAX)) { + char *dot; + dot = strchr(hostname, '.'); + if (dot) { + *dot++ = '\0'; + strcpy(domain, dot); + } + strcpy(host, hostname); + strcpy(hostd, host); + if (*domain || local_domain) { + strcat(hostd, "."); + if (!local_domain) + strcat(hostd, domain); + else { + strcat(hostd, local_domain); + strcpy(domain, local_domain); + } + } + } + + if (sizeof(short) == 2) { + union { short s; char c[sizeof(short)]; } order; + order.s = 0x0102; + if (order.c[0] == 1 && order.c[1] == 2) + strcpy(endian, "big"); + else if (order.c[0] == 2 && order.c[1] == 1) + strcpy(endian, "little"); + else + strcpy(endian, "unknown"); + } + + add_std_amd_vars(system_table); + + macro_init_done = 1; + macro_unlock(); + return; +} + +int macro_is_systemvar(const char *str, int len) +{ + struct substvar *sv; + int found = 0; + int status; + + status = pthread_mutex_lock(&table_mutex); + if (status) + fatal(status); + + sv = system_table; + + while (sv) { + if (!strncmp(str, sv->def, len) && sv->def[len] == '\0') { + found = 1; + break; + } + sv = sv->next; + } + + status = pthread_mutex_unlock(&table_mutex); + if (status) + fatal(status); + + return found; +} + +int macro_global_addvar(const char *str, int len, const char *value) +{ + struct substvar *sv; + int status, ret = 0; + + status = pthread_mutex_lock(&table_mutex); + if (status) + fatal(status); + + sv = system_table; + + while (sv) { + if (!strncmp(str, sv->def, len) && sv->def[len] == '\0') + break; + sv = sv->next; + } + + if (sv && !sv->readonly) { + char *this = malloc(strlen(value) + 1); + if (!this) + goto done; + strcpy(this, value); + free(sv->val); + sv->val = this; + ret = 1; + } else { + struct substvar *new; + char *def, *val; + + def = strdup(str); + if (!def) + goto done; + def[len] = '\0'; + + val = strdup(value); + if (!val) { + free(def); + goto done; + } + + new = malloc(sizeof(struct substvar)); + if (!new) { + free(def); + free(val); + goto done; + } + new->def = def; + new->val = val; + new->readonly = 0; + new->next = system_table; + system_table = new; + ret =1; + } +done: + status = pthread_mutex_unlock(&table_mutex); + if (status) + fatal(status); + + return ret; +} + +int macro_parse_globalvar(const char *define) +{ + char buf[MAX_MACRO_STRING]; + char *pbuf, *value; + + if (strlen(define) >= MAX_MACRO_STRING) + return 0; + + strcpy(buf, define); + + pbuf = buf; + while (pbuf) { + if (*pbuf == '=') { + *pbuf = '\0'; + value = pbuf + 1; + break; + } + pbuf++; + } + + /* Macro must have value */ + if (!pbuf) + return 0; + + return macro_global_addvar(buf, strlen(buf), value); +} + +void macro_lock(void) +{ + int status = pthread_mutex_lock(¯o_mutex); + if (status) + fatal(status); +} + +void macro_unlock(void) +{ + int status = pthread_mutex_unlock(¯o_mutex); + if (status) + fatal(status); +} + +struct substvar * +macro_addvar(struct substvar *table, const char *str, int len, const char *value) +{ + struct substvar *lv = table; + + while (lv) { + if (!strncmp(str, lv->def, len) && lv->def[len] == '\0') + break; + lv = lv->next; + } + + if (lv) { + char *this = malloc(strlen(value) + 1); + if (!this) { + lv = table; + goto done; + } + strcpy(this, value); + free(lv->val); + lv->val = this; + if (lv != table) + lv = table; + } else { + struct substvar *new; + char *def, *val; + + def = strdup(str); + if (!def) { + lv = table; + goto done; + } + def[len] = '\0'; + + val = strdup(value); + if (!val) { + lv = table; + free(def); + goto done; + } + + new = malloc(sizeof(struct substvar)); + if (!new) { + lv = table; + free(def); + free(val); + goto done; + } + new->def = def; + new->val = val; + new->readonly = 0; + new->next = table; + lv = new; + } +done: + + return lv; +} + +void macro_global_removevar(const char *str, int len) +{ + struct substvar *sv; + struct substvar *last = NULL; + int status; + + status = pthread_mutex_lock(&table_mutex); + if (status) + fatal(status); + + sv = system_table; + + while (sv) { + if (!strncmp(str, sv->def, len) && sv->def[len] == '\0') + break; + last = sv; + sv = sv->next; + } + + if (sv && !sv->readonly) { + if (last) + last->next = sv->next; + else + system_table = sv->next; + if (sv->def) + free(sv->def); + if (sv->val) + free(sv->val); + free(sv); + } + + status = pthread_mutex_unlock(&table_mutex); + if (status) + fatal(status); + + return; +} + +struct substvar * +macro_removevar(struct substvar *table, const char *str, int len) +{ + struct substvar *list, *lv; + struct substvar *last = NULL; + + lv = list = table; + + while (lv) { + if (!strncmp(str, lv->def, len) && lv->def[len] == '\0') + break; + last = lv; + lv = lv->next; + } + + if (lv) { + if (last) + last->next = lv->next; + else + list = lv->next; + if (lv->def) + free(lv->def); + if (lv->val) + free(lv->val); + free(lv); + } + + return list; +} + +void macro_free_global_table(void) +{ + struct substvar *sv; + struct substvar *next; + int status; + + status = pthread_mutex_lock(&table_mutex); + if (status) + fatal(status); + + sv = system_table; + + while (sv) { + if (sv->readonly) { + sv = sv->next; + continue; + } + next = sv->next; + if (sv->def) + free(sv->def); + if (sv->val) + free(sv->val); + free(sv); + sv = next; + } + + system_table = &sv_osvers; + + status = pthread_mutex_unlock(&table_mutex); + if (status) + fatal(status); + + return; +} + +void macro_free_table(struct substvar *table) +{ + struct substvar *lv = table; + struct substvar *next; + + if (!lv) + return; + + while (lv) { + next = lv->next; + if (lv->def) + free(lv->def); + if (lv->val) + free(lv->val); + free(lv); + lv = next; + } + + return; +} + +/* Find the $-variable matching a certain string fragment */ +const struct substvar * +macro_findvar(const struct substvar *table, const char *str, int len) +{ + const struct substvar *sv = system_table; + const struct substvar *lv = table; +#ifdef ENABLE_EXT_ENV + /* holds one env var */ + static struct substvar *lv_var; + static char *value; + char etmp[512]; +#endif + + /* First try the passed in local table */ + while (lv) { + if (!strncmp(str, lv->def, len) && lv->def[len] == '\0') + return lv; + lv = lv->next; + } + + /* Then look in the system wide table */ + while (sv) { + if (!strncmp(str, sv->def, len) && sv->def[len] == '\0') + return sv; + sv = sv->next; + } + +#ifdef ENABLE_EXT_ENV + /* builtin and local map failed, try the $ENV */ + memcpy(etmp, str, len); + etmp[len]='\0'; + + if ((value=getenv(etmp)) != NULL) { + lv_var = macro_addvar((struct substvar *) table, str, len, value); + return(lv_var); + } +#endif + + return NULL; +} + +/* Set environment from macro variable table */ +void macro_setenv(struct substvar *table) +{ + const struct substvar *sv = system_table; + const struct substvar *lv = table; + + /* + * First set environment from global table, matching local + * variables will overwrite these. + */ + while (sv) { + if (sv->def) + setenv(sv->def, sv->val, 1); + sv = sv->next; + } + + /* Next set environment from the local table */ + while (lv) { + if (lv->def) + setenv(lv->def, lv->val, 1); + lv = lv->next; + } + + return; +} diff --git a/lib/master.c b/lib/master.c new file mode 100644 index 0000000..142f97e --- /dev/null +++ b/lib/master.c @@ -0,0 +1,1949 @@ +/* ----------------------------------------------------------------------- * + * + * master.c - master map utility routines. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "automount.h" + +/* The root of the map entry tree */ +struct master *master_list = NULL; + +extern const char *global_options; +extern long global_negative_timeout; + +/* Attribute to create a joinable thread */ +extern pthread_attr_t th_attr; + +extern struct startup_cond suc; + +static struct map_source * +__master_find_map_source(struct master_mapent *, + const char *, const char *, int, const char **); + +static pthread_mutex_t master_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t instance_mutex = PTHREAD_MUTEX_INITIALIZER; + +void master_mutex_lock(void) +{ + int status = pthread_mutex_lock(&master_mutex); + if (status) + fatal(status); +} + +void master_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&master_mutex); + if (status) + fatal(status); +} + +void master_mutex_lock_cleanup(void *arg) +{ + master_mutex_unlock(); + return; +} + +int master_add_autofs_point(struct master_mapent *entry, unsigned logopt, + unsigned nobind, unsigned ghost, int submount) +{ + struct autofs_point *ap; + int status; + + ap = malloc(sizeof(struct autofs_point)); + if (!ap) + return 0; + + ap->state = ST_INIT; + + ap->state_pipe[0] = -1; + ap->state_pipe[1] = -1; + ap->logpri_fifo = -1; + + ap->path = strdup(entry->path); + if (!ap->path) { + free(ap); + return 0; + } + ap->pref = NULL; + + ap->entry = entry; + ap->exp_thread = 0; + ap->readmap_thread = 0; + /* + * Program command line option overrides config. + * We can't use 0 negative timeout so use default. + */ + if (global_negative_timeout <= 0) + ap->negative_timeout = defaults_get_negative_timeout(); + else + ap->negative_timeout = global_negative_timeout; + ap->exp_timeout = defaults_get_timeout(); + ap->exp_runfreq = 0; + ap->flags = 0; + if (ghost) + ap->flags = MOUNT_FLAG_GHOST; + + if (nobind) + ap->flags |= MOUNT_FLAG_NOBIND; + + if (ap->path[1] == '-') + ap->type = LKP_DIRECT; + else + ap->type = LKP_INDIRECT; + + ap->logopt = logopt; + + ap->parent = NULL; + ap->thid = 0; + ap->submnt_count = 0; + ap->submount = submount; + INIT_LIST_HEAD(&ap->mounts); + INIT_LIST_HEAD(&ap->submounts); + INIT_LIST_HEAD(&ap->amdmounts); + ap->shutdown = 0; + + status = pthread_mutex_init(&ap->mounts_mutex, NULL); + if (status) { + free(ap->path); + free(ap); + return 0; + } + ap->mode = 0; + + entry->ap = ap; + + return 1; +} + +void master_free_autofs_point(struct autofs_point *ap) +{ + struct list_head *p, *head; + int status; + + if (!ap) + return; + + mounts_mutex_lock(ap); + head = &ap->amdmounts; + p = head->next; + while (p != head) { + struct amd_entry *entry = list_entry(p, struct amd_entry, entries); + p = p->next; + if (!list_empty(&entry->ext_mount)) + ext_mount_remove(&entry->ext_mount, entry->fs); + if (!list_empty(&entry->entries)) + list_del(&entry->entries); + free(entry); + } + mounts_mutex_unlock(ap); + + status = pthread_mutex_destroy(&ap->mounts_mutex); + if (status) + fatal(status); + + if (ap->pref) + free(ap->pref); + free(ap->path); + free(ap); +} + +struct map_source * +master_add_map_source(struct master_mapent *entry, + char *type, char *format, time_t age, + int argc, const char **argv) +{ + struct map_source *source; + char *ntype, *nformat; + const char **tmpargv; + + source = malloc(sizeof(struct map_source)); + if (!source) + return NULL; + memset(source, 0, sizeof(struct map_source)); + source->ref = 1; + + if (type) { + ntype = strdup(type); + if (!ntype) { + master_free_map_source(source, 0); + return NULL; + } + source->type = ntype; + } + + if (format) { + nformat = strdup(format); + if (!nformat) { + master_free_map_source(source, 0); + return NULL; + } + source->format = nformat; + if (!strcmp(nformat, "amd")) + source->flags |= MAP_FLAG_FORMAT_AMD; + } + + source->age = age; + source->stale = 1; + + tmpargv = copy_argv(argc, argv); + if (!tmpargv) { + master_free_map_source(source, 0); + return NULL; + } + source->argc = argc; + source->argv = tmpargv; + if (source->argv[0]) + source->name = strdup(source->argv[0]); + + master_source_writelock(entry); + + if (!entry->maps) { + source->mc = cache_init(entry->ap, source); + if (!source->mc) { + master_free_map_source(source, 0); + master_source_unlock(entry); + return NULL; + } + entry->maps = source; + } else { + struct map_source *this, *last, *next; + + /* Typically there only a few map sources */ + + this = __master_find_map_source(entry, type, format, argc, tmpargv); + if (this) { + error(entry->ap->logopt, + "map source used without taking reference"); + this->age = age; + master_free_map_source(source, 0); + master_source_unlock(entry); + return this; + } + + source->mc = cache_init(entry->ap, source); + if (!source->mc) { + master_free_map_source(source, 0); + master_source_unlock(entry); + return NULL; + } + + last = NULL; + next = entry->maps; + while (next) { + last = next; + next = next->next; + } + if (last) + last->next = source; + else + entry->maps = source; + } + + master_source_unlock(entry); + + return source; +} + +static int compare_source_type_and_format(struct map_source *map, const char *type, const char *format) +{ + int res = 0; + + if (type) { + if (!map->type) + goto done; + + if (strcmp(map->type, type)) + goto done; + } else if (map->type) + goto done; + + if (format) { + if (!map->format) + goto done; + + if (strcmp(map->format, format)) + goto done; + } else if (map->format) + goto done; + + res = 1; +done: + return res; +} + +static struct map_source * +__master_find_map_source(struct master_mapent *entry, + const char *type, const char *format, + int argc, const char **argv) +{ + struct map_source *map; + struct map_source *source = NULL; + int res; + + map = entry->maps; + while (map) { + res = compare_source_type_and_format(map, type, format); + if (!res) + goto next; + + res = compare_argv(map->argc, map->argv, argc, argv); + if (!res) + goto next; + + source = map; + break; +next: + map = map->next; + } + + return source; +} + +struct map_source *master_find_map_source(struct master_mapent *entry, + const char *type, const char *format, + int argc, const char **argv) +{ + struct map_source *source = NULL; + + master_source_readlock(entry); + source = __master_find_map_source(entry, type, format, argc, argv); + master_source_unlock(entry); + + return source; +} + +struct map_source * +master_get_map_source(struct master_mapent *entry, + const char *type, const char *format, + int argc, const char **argv) +{ + struct map_source *source = NULL; + + master_source_readlock(entry); + source = __master_find_map_source(entry, type, format, argc, argv); + if (source) + source->ref++; + master_source_unlock(entry); + + return source; +} + +static void __master_free_map_source(struct map_source *source, unsigned int free_cache) +{ + /* instance map sources are not ref counted */ + if (source->ref && --source->ref) + return; + if (source->type) + free(source->type); + if (source->format) + free(source->format); + if (source->name) + free(source->name); + if (free_cache && source->mc) + cache_release(source); + if (source->lookup) { + struct map_source *instance; + + instance = source->instance; + while (instance) { + if (instance->lookup) + close_lookup(instance->lookup); + instance = instance->next; + } + close_lookup(source->lookup); + } + if (source->argv) + free_argv(source->argc, source->argv); + if (source->instance) { + struct map_source *instance, *next; + + instance = source->instance; + while (instance) { + next = instance->next; + __master_free_map_source(instance, 0); + instance = next; + } + } + + free(source); + + return; +} + +void master_free_map_source(struct map_source *source, unsigned int free_cache) +{ + int status; + + status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); + + __master_free_map_source(source, free_cache); + + status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); +} + +struct map_source *master_find_source_instance(struct map_source *source, const char *type, const char *format, int argc, const char **argv) +{ + struct map_source *map; + struct map_source *instance = NULL; + int status, res; + + status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); + + map = source->instance; + while (map) { + res = compare_source_type_and_format(map, type, format); + if (!res) + goto next; + + if (!argv) { + instance = map; + break; + } + + res = compare_argv(map->argc, map->argv, argc, argv); + if (!res) + goto next; + + instance = map; + break; +next: + map = map->next; + } + + status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); + + return instance; +} + +struct map_source * +master_add_source_instance(struct map_source *source, const char *type, const char *format, time_t age, int argc, const char **argv) +{ + struct map_source *instance; + struct map_source *new; + char *ntype, *nformat; + const char **tmpargv; + int status; + + instance = master_find_source_instance(source, type, format, argc, argv); + if (instance) + return instance; + + new = malloc(sizeof(struct map_source)); + if (!new) + return NULL; + memset(new, 0, sizeof(struct map_source)); + + if (type) { + ntype = strdup(type); + if (!ntype) { + master_free_map_source(new, 0); + return NULL; + } + new->type = ntype; + } + + if (format) { + nformat = strdup(format); + if (!nformat) { + master_free_map_source(new, 0); + return NULL; + } + new->format = nformat; + if (!strcmp(nformat, "amd")) + new->flags |= MAP_FLAG_FORMAT_AMD; + } + + new->age = age; + new->master_line = 0; + new->mc = source->mc; + new->exp_timeout = source->exp_timeout; + new->stale = 1; + + tmpargv = copy_argv(argc, argv); + if (!tmpargv) { + master_free_map_source(new, 0); + return NULL; + } + new->argc = argc; + new->argv = tmpargv; + if (source->name) + new->name = strdup(source->name); + + status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); + + if (!source->instance) + source->instance = new; + else { + /* + * We know there's no other instance of this + * type so just add to head of list + */ + new->next = source->instance; + source->instance = new; + } + + status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); + + return new; +} + +static int check_stale_instances(struct map_source *source) +{ + struct map_source *map; + + if (!source) + return 0; + + map = source->instance; + while (map) { + if (map->stale) + return 1; + if (check_stale_instances(map)) + return 1; + map = map->next; + } + + return 0; +} + +void clear_stale_instances(struct map_source *source) +{ + struct map_source *map; + + if (!source) + return; + + map = source->instance; + while (map) { + clear_stale_instances(map); + if (map->stale) + map->stale = 0; + map = map->next; + } + + return; +} + +void send_map_update_request(struct autofs_point *ap) +{ + struct map_source *map; + int status, need_update = 0; + + status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); + + map = ap->entry->maps; + while (map) { + if (check_stale_instances(map)) + map->stale = 1; + if (map->stale) { + need_update = 1; + break; + } + map = map->next; + } + + status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); + + if (!need_update) + return; + + st_add_task(ap, ST_READMAP); + + return; +} + +void master_source_writelock(struct master_mapent *entry) +{ + int status; + + status = pthread_rwlock_wrlock(&entry->source_lock); + if (status) { + logmsg("master_mapent source write lock failed"); + fatal(status); + } + return; +} + +void master_source_readlock(struct master_mapent *entry) +{ + int retries = 25; + int status; + + while (retries--) { + status = pthread_rwlock_rdlock(&entry->source_lock); + if (status != EAGAIN && status != EBUSY) + break; + else { + struct timespec t = { 0, 200000000 }; + struct timespec r; + + if (status == EAGAIN) + logmsg("master_mapent source too many readers"); + else + logmsg("master_mapent source write lock held"); + + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + } + } + + if (status) { + logmsg("master_mapent source read lock failed"); + fatal(status); + } + + return; +} + +void master_source_unlock(struct master_mapent *entry) +{ + int status; + + status = pthread_rwlock_unlock(&entry->source_lock); + if (status) { + logmsg("master_mapent source unlock failed"); + fatal(status); + } + return; +} + +void master_source_lock_cleanup(void *arg) +{ + struct master_mapent *entry = (struct master_mapent *) arg; + + master_source_unlock(entry); + + return; +} + +void master_source_current_wait(struct master_mapent *entry) +{ + int status; + + status = pthread_mutex_lock(&entry->current_mutex); + if (status) { + logmsg("entry current source lock failed"); + fatal(status); + } + + while (entry->current != NULL) { + status = pthread_cond_wait( + &entry->current_cond, &entry->current_mutex); + if (status) { + logmsg("entry current source condition wait failed"); + fatal(status); + } + } + + return; +} + +void master_source_current_signal(struct master_mapent *entry) +{ + int status; + + status = pthread_cond_signal(&entry->current_cond); + if (status) { + logmsg("entry current source condition signal failed"); + fatal(status); + } + + status = pthread_mutex_unlock(&entry->current_mutex); + if (status) { + logmsg("entry current source unlock failed"); + fatal(status); + } + + return; +} + +struct master_mapent *master_find_mapent(struct master *master, const char *path) +{ + struct list_head *head, *p; + + head = &master->mounts; + list_for_each(p, head) { + struct master_mapent *entry; + + entry = list_entry(p, struct master_mapent, list); + + if (!strcmp(entry->path, path)) + return entry; + } + + return NULL; +} + +unsigned int master_partial_match_mapent(struct master *master, const char *path) +{ + struct list_head *head, *p; + size_t path_len = strlen(path); + int ret = 0; + + head = &master->mounts; + list_for_each(p, head) { + struct master_mapent *entry; + size_t entry_len; + size_t cmp_len; + + entry = list_entry(p, struct master_mapent, list); + + entry_len = strlen(entry->path); + cmp_len = min(entry_len, path_len); + + if (!strncmp(entry->path, path, cmp_len)) { + /* paths are equal, matching master map entry ? */ + if (entry_len == path_len) { + if (entry->maps && + entry->maps->flags & MAP_FLAG_FORMAT_AMD) + ret = 1; + else + ret = -1; + break; + } + + /* amd mount conflicts with entry mount */ + if (entry_len > path_len && + *(entry->path + path_len) == '/') { + ret = -1; + break; + } + + /* entry mount conflicts with amd mount */ + if (entry_len < path_len && + *(path + entry_len) == '/') { + ret = -1; + break; + } + } + } + + return ret; +} + +struct autofs_point *__master_find_submount(struct autofs_point *ap, const char *path) +{ + struct list_head *head, *p; + + head = &ap->submounts; + list_for_each(p, head) { + struct autofs_point *submount; + + submount = list_entry(p, struct autofs_point, mounts); + + if (!strcmp(submount->path, path)) + return submount; + } + + return NULL; +} + +struct autofs_point *master_find_submount(struct autofs_point *ap, const char *path) +{ + struct autofs_point *submount; + + mounts_mutex_lock(ap); + submount = __master_find_submount(ap, path); + mounts_mutex_unlock(ap); + + return submount; +} + +struct amd_entry *__master_find_amdmount(struct autofs_point *ap, const char *path) +{ + struct list_head *head, *p; + + head = &ap->amdmounts; + list_for_each(p, head) { + struct amd_entry *entry; + + entry = list_entry(p, struct amd_entry, entries); + + if (!strcmp(entry->path, path)) + return entry; + } + + return NULL; +} + +struct amd_entry *master_find_amdmount(struct autofs_point *ap, const char *path) +{ + struct amd_entry *entry; + + mounts_mutex_lock(ap); + entry = __master_find_amdmount(ap, path); + mounts_mutex_unlock(ap); + + return entry; +} + +struct master_mapent *master_new_mapent(struct master *master, const char *path, time_t age) +{ + struct master_mapent *entry; + int status; + char *tmp; + + entry = malloc(sizeof(struct master_mapent)); + if (!entry) + return NULL; + + memset(entry, 0, sizeof(struct master_mapent)); + + tmp = strdup(path); + if (!tmp) { + free(entry); + return NULL; + } + entry->path = tmp; + + entry->thid = 0; + entry->age = age; + entry->master = master; + entry->current = NULL; + entry->maps = NULL; + entry->ap = NULL; + + status = pthread_rwlock_init(&entry->source_lock, NULL); + if (status) + fatal(status); + + status = pthread_mutex_init(&entry->current_mutex, NULL); + if (status) + fatal(status); + + status = pthread_cond_init(&entry->current_cond, NULL); + if (status) + fatal(status); + + INIT_LIST_HEAD(&entry->list); + + return entry; +} + +void master_add_mapent(struct master *master, struct master_mapent *entry) +{ + list_add_tail(&entry->list, &master->mounts); + return; +} + +void master_remove_mapent(struct master_mapent *entry) +{ + struct master *master = entry->master; + + if (entry->ap->submount) + return; + + if (!list_empty(&entry->list)) { + list_del_init(&entry->list); + list_add(&entry->join, &master->completed); + } + + return; +} + +void master_free_mapent_sources(struct master_mapent *entry, unsigned int free_cache) +{ + if (entry->maps) { + struct map_source *m, *n; + + m = entry->maps; + while (m) { + n = m->next; + master_free_map_source(m, free_cache); + m = n; + } + entry->maps = NULL; + } + + return; +} + +void master_free_mapent(struct master_mapent *entry) +{ + int status; + + if (entry->path) + free(entry->path); + + master_free_autofs_point(entry->ap); + + status = pthread_rwlock_destroy(&entry->source_lock); + if (status) + fatal(status); + + status = pthread_mutex_destroy(&entry->current_mutex); + if (status) + fatal(status); + + status = pthread_cond_destroy(&entry->current_cond); + if (status) + fatal(status); + + free(entry); + + return; +} + +struct master *master_new(const char *name, unsigned int timeout, unsigned int ghost) +{ + struct master *master; + char *tmp; + + master = malloc(sizeof(struct master)); + if (!master) + return NULL; + + if (!name) + tmp = (char *) defaults_get_master_map(); + else + tmp = strdup(name); + + if (!tmp) { + free(master); + return NULL; + } + + master->name = tmp; + master->nc = NULL; + + master->recurse = 0; + master->depth = 0; + master->reading = 0; + master->read_fail = 0; + master->default_ghost = ghost; + master->default_timeout = timeout; + master->default_logging = defaults_get_logging(); + master->logopt = master->default_logging; + + INIT_LIST_HEAD(&master->mounts); + INIT_LIST_HEAD(&master->completed); + + return master; +} + +static void master_add_amd_mount_section_mounts(struct master *master, time_t age) +{ + unsigned int m_logopt = master->logopt; + struct master_mapent *entry; + struct map_source *source; + unsigned int loglevel; + unsigned int logopt; + unsigned int flags; + char *argv[2]; + char **paths; + int ret; + int i; + + loglevel = conf_amd_get_log_options(); + + paths = conf_amd_get_mount_paths(); + if (!paths) + return; + + i = 0; + while (paths[i]) { + const char *path = paths[i]; + unsigned int ghost = 0; + time_t timeout; + char *type = NULL; + char *map = NULL; + char *opts; + + ret = master_partial_match_mapent(master, path); + if (ret) { + /* If this amd entry is already present in the + * master map it's not a duplicate, don't issue + * an error message. + */ + if (ret == 1) + goto next; + info(m_logopt, + "amd section mount path conflict, %s ignored", + path); + goto next; + } + + map = conf_amd_get_map_name(path); + if (!map) { + error(m_logopt, + "failed to get map name for amd section mount %s", + path); + goto next; + } + + entry = master_new_mapent(master, path, age); + if (!entry) { + error(m_logopt, + "failed to allocate new amd section mount %s", + path); + goto next; + } + + logopt = m_logopt; + if (loglevel <= LOG_DEBUG && loglevel > LOG_INFO) + logopt = LOGOPT_DEBUG; + else if (loglevel <= LOG_INFO && loglevel > LOG_ERR) + logopt = LOGOPT_VERBOSE; + + /* It isn't possible to provide the fullybrowsable amd + * browsing functionality within the autofs framework. + * This flag will not be set if browsable_dirs = full + * in the configuration or fullybrowsable is present as + * an option. + */ + flags = conf_amd_get_flags(path); + if (flags & CONF_BROWSABLE_DIRS) + ghost = 1; + + ret = master_add_autofs_point(entry, logopt, 0, ghost, 0); + if (!ret) { + error(m_logopt, "failed to add autofs_point"); + master_free_mapent(entry); + goto next; + } + + opts = conf_amd_get_map_options(path); + if (opts) { + /* autofs uses the equivalent of cache:=inc,sync + * (except for file maps which use cache:=all,sync) + * but if the map is large then it may be necessary + * to read the whole map at startup even if browsing + * is is not enabled, so look for cache:=all in the + * map_options configuration entry. + */ + if (strstr(opts, "cache:=all")) + entry->ap->flags |= MOUNT_FLAG_AMD_CACHE_ALL; + free(opts); + } + + type = conf_amd_get_map_type(path); + argv[0] = map; + argv[1] = NULL; + + source = master_add_map_source(entry, type, "amd", + age, 1, (const char **) argv); + if (!source) { + error(m_logopt, + "failed to add source for amd section mount %s", + path); + master_free_mapent(entry); + goto next; + } + + timeout = conf_amd_get_dismount_interval(path); + set_exp_timeout(entry->ap, source, timeout); + source->master_line = 0; + + entry->age = age; + entry->current = NULL; + + master_add_mapent(master, entry); +next: + if (type) + free(type); + if (map) + free(map); + i++; + } + + i = 0; + while (paths[i]) + free(paths[i++]); + free(paths); +} + +int master_read_master(struct master *master, time_t age, int readall) +{ + unsigned int logopt = master->logopt; + struct mapent_cache *nc; + + /* + * We need to clear and re-populate the null map entry cache + * before alowing anyone else to use it. + */ + master_mutex_lock(); + if (master->nc) { + cache_writelock(master->nc); + nc = master->nc; + cache_clean_null_cache(nc); + } else { + nc = cache_init_null_cache(master); + if (!nc) { + error(logopt, + "failed to init null map cache for %s", + master->name); + return 0; + } + cache_writelock(nc); + master->nc = nc; + } + master_init_scan(); + lookup_nss_read_master(master, age); + cache_unlock(nc); + master_add_amd_mount_section_mounts(master, age); + master_mutex_unlock(); + + if (!master->read_fail) + master_mount_mounts(master, age, readall); + else { + master->read_fail = 0; + /* HUP signal sets readall == 1 only */ + if (!readall) + return 0; + else + master_mount_mounts(master, age, readall); + } + + master_mutex_lock(); + + if (list_empty(&master->mounts)) + warn(logopt, "no mounts in table"); + + master_mutex_unlock(); + + return 1; +} + +int master_submount_list_empty(struct autofs_point *ap) +{ + int res = 0; + + mounts_mutex_lock(ap); + if (list_empty(&ap->submounts)) + res = 1; + mounts_mutex_unlock(ap); + + return res; +} + +int master_notify_submount(struct autofs_point *ap, const char *path, enum states state) +{ + struct list_head *head, *p; + struct autofs_point *this = NULL; + int ret = 1; + + mounts_mutex_lock(ap); + + head = &ap->submounts; + p = head->prev; + while (p != head) { + this = list_entry(p, struct autofs_point, mounts); + p = p->prev; + + /* path not the same */ + if (strcmp(this->path, path)) + continue; + + if (!master_submount_list_empty(this)) { + char *this_path = strdup(this->path); + if (this_path) { + mounts_mutex_unlock(ap); + master_notify_submount(this, path, state); + mounts_mutex_lock(ap); + if (!__master_find_submount(ap, this_path)) { + free(this_path); + continue; + } + free(this_path); + } + } + + /* Now we have found the submount we want to expire */ + + st_mutex_lock(); + + if (this->state == ST_SHUTDOWN) { + this = NULL; + st_mutex_unlock(); + break; + } + + this->shutdown = ap->shutdown; + + __st_add_task(this, state); + + st_mutex_unlock(); + mounts_mutex_unlock(ap); + + st_wait_task(this, state, 0); + + /* + * If our submount gets to state ST_SHUTDOWN, ST_SHUTDOWN_PENDING or + * ST_SHUTDOWN_FORCE we need to wait until it goes away or changes + * to ST_READY. + */ + mounts_mutex_lock(ap); + st_mutex_lock(); + while ((this = __master_find_submount(ap, path))) { + struct timespec t = { 0, 300000000 }; + struct timespec r; + + if (this->state != ST_SHUTDOWN && + this->state != ST_SHUTDOWN_PENDING && + this->state != ST_SHUTDOWN_FORCE) { + ret = 0; + break; + } + + st_mutex_unlock(); + mounts_mutex_unlock(ap); + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + mounts_mutex_lock(ap); + st_mutex_lock(); + } + st_mutex_unlock(); + break; + } + + mounts_mutex_unlock(ap); + + return ret; +} + +void master_notify_state_change(struct master *master, int sig) +{ + struct master_mapent *entry; + struct autofs_point *ap; + struct list_head *p; + int cur_state; + unsigned int logopt; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + master_mutex_lock(); + + list_for_each(p, &master->mounts) { + enum states next = ST_INVAL; + + entry = list_entry(p, struct master_mapent, list); + + ap = entry->ap; + logopt = ap->logopt; + + st_mutex_lock(); + + if (ap->state == ST_SHUTDOWN) + goto next; + + switch (sig) { + case SIGTERM: + case SIGINT: + if (ap->state != ST_SHUTDOWN_PENDING && + ap->state != ST_SHUTDOWN_FORCE) { + next = ST_SHUTDOWN_PENDING; + ap->shutdown = 1; + __st_add_task(ap, next); + } + break; +#ifdef ENABLE_FORCED_SHUTDOWN + case SIGUSR2: + if (ap->state != ST_SHUTDOWN_FORCE && + ap->state != ST_SHUTDOWN_PENDING) { + next = ST_SHUTDOWN_FORCE; + ap->shutdown = 1; + __st_add_task(ap, next); + } + break; +#endif + case SIGUSR1: + assert(ap->state == ST_READY); + next = ST_PRUNE; + __st_add_task(ap, next); + break; + } +next: + if (next != ST_INVAL) + debug(logopt, + "sig %d switching %s from %d to %d", + sig, ap->path, ap->state, next); + + st_mutex_unlock(); + } + + master_mutex_unlock(); + pthread_setcancelstate(cur_state, NULL); + + return; +} + +static int master_do_mount(struct master_mapent *entry) +{ + struct startup_cond suc; + struct autofs_point *ap; + pthread_t thid; + int status; + + ap = entry->ap; + + if (handle_mounts_startup_cond_init(&suc)) { + crit(ap->logopt, + "failed to init startup cond for mount %s", entry->path); + return 0; + } + + suc.ap = ap; + suc.root = ap->path; + suc.done = 0; + suc.status = 0; + + debug(ap->logopt, "mounting %s", entry->path); + + status = pthread_create(&thid, &th_attr, handle_mounts, &suc); + if (status) { + crit(ap->logopt, + "failed to create mount handler thread for %s", + entry->path); + handle_mounts_startup_cond_destroy(&suc); + return 0; + } + + while (!suc.done) { + status = pthread_cond_wait(&suc.cond, &suc.mutex); + if (status) + fatal(status); + } + + if (suc.status) { + error(ap->logopt, "failed to startup mount"); + handle_mounts_startup_cond_destroy(&suc); + return 0; + } + entry->thid = thid; + + handle_mounts_startup_cond_destroy(&suc); + + return 1; +} + +static void check_update_map_sources(struct master_mapent *entry, int readall) +{ + struct map_source *source, *last; + struct autofs_point *ap; + int map_stale = 0; + + if (readall) + map_stale = 1; + + ap = entry->ap; + + master_source_writelock(entry); + + last = NULL; + source = entry->maps; + while (source) { + if (readall) + source->stale = 1; + + /* + * If a map source is no longer valid and all it's + * entries have expired away we can get rid of it. + */ + if (entry->age > source->age) { + struct mapent *me; + cache_readlock(source->mc); + me = cache_lookup_first(source->mc); + if (!me) { + struct map_source *next = source->next; + + cache_unlock(source->mc); + + if (!last) + entry->maps = next; + else + last->next = next; + + if (entry->maps == source) + entry->maps = next; + + master_free_map_source(source, 1); + + source = next; + continue; + } else { + source->stale = 1; + map_stale = 1; + } + cache_unlock(source->mc); + } + last = source; + source = source->next; + } + + master_source_unlock(entry); + + /* The map sources have changed */ + if (map_stale) + st_add_task(ap, ST_READMAP); + + return; +} + +int master_mount_mounts(struct master *master, time_t age, int readall) +{ + struct mapent_cache *nc = master->nc; + struct list_head *p, *head; + int cur_state; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + master_mutex_lock(); + + head = &master->mounts; + p = head->next; + while (p != head) { + struct master_mapent *this; + struct autofs_point *ap; + struct mapent *ne, *nested; + struct stat st; + int state_pipe, save_errno; + int ret; + + this = list_entry(p, struct master_mapent, list); + p = p->next; + + ap = this->ap; + + /* A master map entry has gone away */ + if (this->age < age) { + st_add_task(ap, ST_SHUTDOWN_PENDING); + continue; + } + + cache_readlock(nc); + ne = cache_lookup_distinct(nc, this->path); + /* + * If this path matched a nulled entry the master map entry + * must be an indirect mount so the master map entry line + * number may be obtained from this->maps. + */ + if (ne) { + int lineno = ne->age; + cache_unlock(nc); + + /* null entry appears after map entry */ + if (this->maps->master_line < lineno) { + warn(ap->logopt, + "ignoring null entry that appears after " + "existing entry for %s", this->path); + goto cont; + } + if (ap->state != ST_INIT) { + st_add_task(ap, ST_SHUTDOWN_PENDING); + continue; + } + /* + * The map entry hasn't been started yet and we've + * seen a preceeding null map entry for it so just + * delete it from the master map entry list so it + * doesn't get in the road. + */ + list_del_init(&this->list); + master_free_mapent_sources(ap->entry, 1); + master_free_mapent(ap->entry); + continue; + } + nested = cache_partial_match(nc, this->path); + if (nested) { + error(ap->logopt, + "removing invalid nested null entry %s", + nested->key); + nested = cache_partial_match(nc, this->path); + if (nested) + cache_delete(nc, nested->key); + } + cache_unlock(nc); +cont: + st_mutex_lock(); + + state_pipe = this->ap->state_pipe[1]; + + /* No pipe so mount is needed */ + ret = fstat(state_pipe, &st); + save_errno = errno; + + st_mutex_unlock(); + + if (!ret) + check_update_map_sources(this, readall); + else if (ret == -1 && save_errno == EBADF) { + if (!master_do_mount(this)) { + list_del_init(&this->list); + master_free_mapent_sources(ap->entry, 1); + master_free_mapent(ap->entry); + } + } + } + + master_mutex_unlock(); + pthread_setcancelstate(cur_state, NULL); + + return 1; +} + +/* The nss source instances end up in reverse order. */ +static void list_source_instances(struct map_source *source, struct map_source *instance) +{ + if (!source || !instance) { + printf("none"); + return; + } + + if (instance->next) + list_source_instances(source, instance->next); + + /* + * For convienience we map nss instance type "files" to "file". + * Check for that and report corrected instance type. + */ + if (strcmp(instance->type, "file")) + printf("%s ", instance->type); + else { + if (source->argv && *(source->argv[0]) != '/') + printf("files "); + else + printf("%s ", instance->type); + } + + return; +} + +static void print_map_info(struct map_source *source) +{ + int argc = source->argc; + int i, multi, map_num; + + multi = (source->type && !strcmp(source->type, "multi")); + map_num = 1; + for (i = 0; i < argc; i++) { + if (source->argv[i] && *source->argv[i] != '-') { + if (!multi) + printf(" map: %s\n", source->argv[i]); + else + printf(" map[%i]: %s\n", map_num, source->argv[i]); + i++; + } + + if (i >= argc) + return; + + if (!strcmp(source->argv[i], "--")) + continue; + + if (source->argv[i]) { + int need_newline = 0; + int j; + + if (!multi) + printf(" arguments:"); + else + printf(" arguments[%i]:", map_num); + + for (j = i; j < source->argc; j++) { + if (!strcmp(source->argv[j], "--")) + break; + printf(" %s", source->argv[j]); + i++; + need_newline = 1; + } + if (need_newline) + printf("\n"); + } + if (multi) + map_num++; + } + + return; +} + +static int match_type(const char *source, const char *type) +{ + if (!strcmp(source, type)) + return 1; + /* Sources file and files are synonymous */ + if (!strncmp(source, type, 4) && (strlen(source) <= 5)) + return 1; + return 0; +} + +static char *get_map_name(const char *string) +{ + char *name, *tmp; + char *start, *end, *base; + + tmp = strdup(string); + if (!tmp) { + printf("error: allocation failure: %s\n", strerror(errno)); + return NULL; + } + + base = basename(tmp); + end = strchr(base, ','); + if (end) + *end = '\0'; + start = strchr(tmp, '='); + if (start) + start++; + else { + char *colon = strrchr(base, ':'); + if (colon) + start = ++colon; + else + start = base; + } + + name = strdup(start); + if (!name) + printf("error: allocation failure: %s\n", strerror(errno)); + free(tmp); + + return name; +} + +static int match_name(struct map_source *source, const char *name) +{ + int argc = source->argc; + int ret = 0; + int i; + + /* + * This can't work for old style "multi" type sources since + * there's no way to know from which map the cache entry came + * from and duplicate entries are ignored at map read time. + * All we can really do is list all the entries for the given + * multi map if one of its map names matches. + */ + for (i = 0; i < argc; i++) { + if (i == 0 || !strcmp(source->argv[i], "--")) { + if (i != 0) { + i++; + if (i >= argc) + break; + } + + if (source->argv[i] && *source->argv[i] != '-') { + char *map = get_map_name(source->argv[i]); + if (!map) + break; + if (!strcmp(map, name)) { + ret = 1; + free(map); + break; + } + free(map); + } + } + } + + return ret; +} + +int dump_map(struct master *master, const char *type, const char *name) +{ + struct list_head *p, *head; + + if (list_empty(&master->mounts)) { + printf("no master map entries found\n"); + return 1; + } + + head = &master->mounts; + p = head->next; + while (p != head) { + struct map_source *source; + struct master_mapent *this; + struct autofs_point *ap; + time_t now = monotonic_time(NULL); + + this = list_entry(p, struct master_mapent, list); + p = p->next; + + ap = this->ap; + + /* + * Ensure we actually read indirect map entries so we can + * list them. The map reads won't read any indirect map + * entries (other than those in a file map) unless the + * browse option is set. + */ + if (ap->type == LKP_INDIRECT) + ap->flags |= MOUNT_FLAG_GHOST; + + /* Read the map content into the cache */ + if (lookup_nss_read_map(ap, NULL, now)) + lookup_prune_cache(ap, now); + else { + printf("failed to read map\n"); + lookup_close_lookup(ap); + continue; + } + + if (!this->maps) { + printf("no map sources found for %s\n", ap->path); + lookup_close_lookup(ap); + continue; + } + + source = this->maps; + while (source) { + struct map_source *instance; + struct mapent *me; + + instance = NULL; + if (source->type) { + if (!match_type(source->type, type)) { + source = source->next; + continue; + } + if (!match_name(source, name)) { + source = source->next; + continue; + } + instance = source; + } else { + struct map_source *map; + + map = source->instance; + while (map) { + if (!match_type(map->type, type)) { + map = map->next; + continue; + } + if (!match_name(map, name)) { + map = map->next; + continue; + } + instance = map; + break; + } + } + + if (!instance) { + source = source->next; + lookup_close_lookup(ap); + continue; + } + + me = cache_lookup_first(source->mc); + if (!me) + printf("no keys found in map\n"); + else { + do { + if (me->source == instance) + printf("%s\t%s\n", me->key, me->mapent); + } while ((me = cache_lookup_next(source->mc, me))); + } + + lookup_close_lookup(ap); + return 1; + } + lookup_close_lookup(ap); + } + + return 0; +} + +int master_show_mounts(struct master *master) +{ + struct list_head *p, *head; + + printf("\nautofs dump map information\n" + "===========================\n\n"); + + printf("global options: "); + if (!global_options) + printf("none configured\n"); + else { + printf("%s\n", global_options); + unsigned int append_options = defaults_get_append_options(); + const char *append = append_options ? "will" : "will not"; + printf("global options %s be appended to map entries\n", append); + } + + if (list_empty(&master->mounts)) { + printf("no master map entries found\n\n"); + return 1; + } + + head = &master->mounts; + p = head->next; + while (p != head) { + struct map_source *source; + struct master_mapent *this; + struct autofs_point *ap; + time_t now = monotonic_time(NULL); + unsigned int count = 0; + + this = list_entry(p, struct master_mapent, list); + p = p->next; + + ap = this->ap; + + printf("\nMount point: %s\n", ap->path); + + printf("\nsource(s):\n"); + + /* + * Ensure we actually read indirect map entries so we can + * list them. The map reads won't read any indirect map + * entries (other than those in a file map) unless the + * browse option is set. + */ + if (ap->type == LKP_INDIRECT) + ap->flags |= MOUNT_FLAG_GHOST; + + /* Read the map content into the cache */ + if (lookup_nss_read_map(ap, NULL, now)) + lookup_prune_cache(ap, now); + else { + printf(" failed to read map\n\n"); + continue; + } + + if (!this->maps) { + printf(" no map sources found\n\n"); + continue; + } + + source = this->maps; + while (source) { + struct mapent *me; + + if (source->type) + printf("\n type: %s\n", source->type); + else { + printf("\n instance type(s): "); + list_source_instances(source, source->instance); + printf("\n"); + } + + if (source->argc >= 1) { + print_map_info(source); + if (count && ap->type == LKP_INDIRECT) + printf(" duplicate indirect map entry" + " will be ignored at run time\n"); + } + + printf("\n"); + + me = cache_lookup_first(source->mc); + if (!me) + printf(" no keys found in map\n"); + else { + do { + printf(" %s | %s\n", me->key, me->mapent); + } while ((me = cache_lookup_next(source->mc, me))); + } + + count++; + + source = source->next; + } + + lookup_close_lookup(ap); + + printf("\n"); + } + + return 1; +} + +int master_list_empty(struct master *master) +{ + int res = 0; + + master_mutex_lock(); + if (list_empty(&master->mounts)) + res = 1; + master_mutex_unlock(); + + return res; +} + +int master_done(struct master *master) +{ + struct list_head *head, *p; + struct master_mapent *entry; + int res = 0; + + head = &master->completed; + p = head->next; + while (p != head) { + entry = list_entry(p, struct master_mapent, join); + p = p->next; + list_del(&entry->join); + pthread_join(entry->thid, NULL); + master_free_mapent_sources(entry, 1); + master_free_mapent(entry); + } + if (list_empty(&master->mounts)) + res = 1; + + return res; +} + +unsigned int master_get_logopt(void) +{ + return master_list ? master_list->logopt : LOGOPT_NONE; +} + +int master_kill(struct master *master) +{ + if (!list_empty(&master->mounts)) + return 0; + + if (master->name) + free(master->name); + + cache_release_null_cache(master); + free(master); + + return 1; +} + +void dump_master(struct master *master) +{ + struct list_head *p, *head; + + head = &master->mounts; + list_for_each(p, head) { + struct master_mapent *this = list_entry(p, struct master_mapent, list); + logmsg("path %s", this->path); + } +} diff --git a/lib/master_parse.y b/lib/master_parse.y new file mode 100644 index 0000000..42e03c2 --- /dev/null +++ b/lib/master_parse.y @@ -0,0 +1,912 @@ +%{ +/* ----------------------------------------------------------------------- * + * + * master_parser.y - master map buffer parser. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +#include "automount.h" +#include "master.h" + +#define MAX_ERR_LEN 512 + +extern struct master *master_list; + +char **add_argv(int, char **, char *); +const char **copy_argv(int, const char **); +int free_argv(int, const char **); + +extern FILE *master_in; +extern char *master_text; +extern int master_lex(void); +extern int master_lineno; +extern void master_set_scan_buffer(const char *); + +static char *master_strdup(char *); +static void local_init_vars(void); +static void local_free_vars(void); +static void trim_maptype(char *); +static int add_multi_mapstr(void); + +static int master_error(const char *s); +static int master_notify(const char *s); +static int master_msg(const char *s); + +static char *path; +static char *type; +static char *format; +static long timeout; +static long negative_timeout; +static unsigned symlnk; +static unsigned nobind; +static unsigned ghost; +extern unsigned global_selection_options; +static unsigned random_selection; +static unsigned use_weight; +static unsigned long mode; +static char **tmp_argv; +static int tmp_argc; +static char **local_argv; +static int local_argc; + +static char errstr[MAX_ERR_LEN]; + +static unsigned int verbose; +static unsigned int debug; + +static int lineno; + +#define YYDEBUG 0 + +#ifndef YYENABLE_NLS +#define YYENABLE_NLS 0 +#endif +#ifndef YYLTYPE_IS_TRIVIAL +#define YYLTYPE_IS_TRIVIAL 0 +#endif + +#if YYDEBUG +static int master_fprintf(FILE *, char *, ...); +#undef YYFPRINTF +#define YYFPRINTF master_fprintf +#endif + +%} + +%union { + char strtype[2048]; + int inttype; + long longtype; +} + +%token COMMENT +%token MAP +%token OPT_TIMEOUT OPT_NTIMEOUT OPT_NOBIND OPT_NOGHOST OPT_GHOST OPT_VERBOSE +%token OPT_DEBUG OPT_RANDOM OPT_USE_WEIGHT OPT_SYMLINK OPT_MODE +%token COLON COMMA NL DDASH +%type map +%type options +%type dn +%type dnattrs +%type dnattr +%type option +%type daemon_option +%type mount_option +%token PATH +%token QUOTE +%token NILL +%token SPACE +%token EQUAL +%token MULTITYPE +%token MAPTYPE +%token DNSERVER +%token DNATTR +%token DNNAME +%token MAPHOSTS +%token MAPNULL +%token MAPXFN +%token MAPNAME +%token NUMBER +%token OCTALNUMBER +%token OPTION + +%start file + +%% + +file: { + master_lineno = 0; +#if YYDEBUG != 0 + master_debug = YYDEBUG; +#endif + } line + ; + +line: + | PATH mapspec + { + path = master_strdup($1); + if (!path) { + local_free_vars(); + YYABORT; + } + } + | PATH MULTITYPE maplist + { + char *tmp = NULL; + + trim_maptype($2); + + path = master_strdup($1); + if (!path) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + + if ((tmp = strchr($2, ','))) + *tmp++ = '\0'; + + type = master_strdup($2); + if (!type) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (tmp) { + format = master_strdup(tmp); + if (!format) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + } + | PATH COLON { master_notify($1); YYABORT; } + | PATH OPTION { master_notify($2); YYABORT; } + | PATH NILL { master_notify($2); YYABORT; } + | PATH OPT_RANDOM { master_notify($1); YYABORT; } + | PATH OPT_USE_WEIGHT { master_notify($1); YYABORT; } + | PATH OPT_DEBUG { master_notify($1); YYABORT; } + | PATH OPT_TIMEOUT { master_notify($1); YYABORT; } + | PATH OPT_SYMLINK { master_notify($1); YYABORT; } + | PATH OPT_NOBIND { master_notify($1); YYABORT; } + | PATH OPT_GHOST { master_notify($1); YYABORT; } + | PATH OPT_NOGHOST { master_notify($1); YYABORT; } + | PATH OPT_VERBOSE { master_notify($1); YYABORT; } + | PATH OPT_MODE { master_notify($1); YYABORT; } + | PATH { master_notify($1); YYABORT; } + | QUOTE { master_notify($1); YYABORT; } + | OPTION { master_notify($1); YYABORT; } + | NILL { master_notify($1); YYABORT; } + | COMMENT { YYABORT; } + ; + +mapspec: map + { + local_argc = tmp_argc; + local_argv = tmp_argv; + tmp_argc = 0; + tmp_argv = NULL; + } + | map options + { + local_argc = tmp_argc; + local_argv = tmp_argv; + tmp_argc = 0; + tmp_argv = NULL; + } + ; + +maplist: map + { + if (!add_multi_mapstr()) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | map options + { + if (!add_multi_mapstr()) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | maplist DDASH map + { + local_argc++; + local_argv = add_argv(local_argc, local_argv, "--"); + if (!local_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (!add_multi_mapstr()) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | maplist DDASH map options + { + local_argc++; + local_argv = add_argv(local_argc, local_argv, "--"); + if (!local_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (!add_multi_mapstr()) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + ; + +map: PATH + { + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $1); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | MAPNAME + { + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $1); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | MAPHOSTS + { + type = master_strdup($1 + 1); + if (!type) { + local_free_vars(); + YYABORT; + } + } + | MAPXFN + { + master_notify($1); + master_msg("X/Open Federated Naming service not supported"); + YYABORT; + } + | MAPNULL + { + type = master_strdup($1 + 1); + if (!type) { + local_free_vars(); + YYABORT; + } + } + | dnattrs + { + type = master_strdup("ldap"); + if (!type) { + local_free_vars(); + YYABORT; + } + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $1); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | MAPTYPE PATH + { + char *tmp = NULL; + + trim_maptype($1); + + if ((tmp = strchr($1, ','))) + *tmp++ = '\0'; + + if (strcmp($1, "exec")) + type = master_strdup($1); + else + type = master_strdup("program"); + if (!type) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (tmp) { + format = master_strdup(tmp); + if (!format) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $2); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | MAPTYPE MAPNAME + { + char *tmp = NULL; + + trim_maptype($1); + + if ((tmp = strchr($1, ','))) + *tmp++ = '\0'; + + if (strcmp($1, "exec")) + type = master_strdup($1); + else + type = master_strdup("program"); + if (!type) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (tmp) { + format = master_strdup(tmp); + if (!format) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $2); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + | MAPTYPE dn + { + char *tmp = NULL; + + trim_maptype($1); + + if ((tmp = strchr($1, ','))) + *tmp++ = '\0'; + + if (strcmp($1, "exec")) + type = master_strdup($1); + else + type = master_strdup("program"); + if (!type) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + if (tmp) { + format = master_strdup(tmp); + if (!format) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $2); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + /* Add back the type for lookup_ldap.c to handle ldaps */ + if (*tmp_argv[0]) { + tmp = malloc(strlen(type) + strlen(tmp_argv[0]) + 2); + if (!tmp) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + strcpy(tmp, type); + strcat(tmp, ":"); + strcat(tmp, tmp_argv[0]); + free(tmp_argv[0]); + tmp_argv[0] = tmp; + } + } + ; + +dn: DNSERVER dnattrs + { + strcpy($$, $1); + strcat($$, $2); + } + | dnattrs + { + strcpy($$, $1); + } + | + { + master_notify("syntax error in dn"); + YYABORT; + } + ; + +dnattrs: DNATTR EQUAL DNNAME + { + if (strcasecmp($1, "cn") && + strcasecmp($1, "ou") && + strcasecmp($1, "automountMapName") && + strcasecmp($1, "nisMapName")) { + strcpy(errstr, $1); + strcat(errstr, "="); + strcat(errstr, $3); + master_notify(errstr); + YYABORT; + } + strcpy($$, $1); + strcat($$, "="); + strcat($$, $3); + } + | DNATTR EQUAL DNNAME COMMA dnattr + { + if (strcasecmp($1, "cn") && + strcasecmp($1, "ou") && + strcasecmp($1, "automountMapName") && + strcasecmp($1, "nisMapName")) { + strcpy(errstr, $1); + strcat(errstr, "="); + strcat(errstr, $3); + master_notify(errstr); + YYABORT; + } + strcpy($$, $1); + strcat($$, "="); + strcat($$, $3); + strcat($$, ","); + strcat($$, $5); + } + | DNNAME + { + /* Matches map in old style syntax ldap:server:map */ + strcpy($$, $1); + } + | DNATTR + { + master_notify($1); + YYABORT; + } + ; + +dnattr: DNATTR EQUAL DNNAME + { + if (!strcasecmp($1, "automountMapName") || + !strcasecmp($1, "nisMapName")) { + strcpy(errstr, $1); + strcat(errstr, "="); + strcat(errstr, $3); + master_notify(errstr); + YYABORT; + } + strcpy($$, $1); + strcat($$, "="); + strcat($$, $3); + } + | DNATTR EQUAL DNNAME COMMA dnattr + { + if (!strcasecmp($1, "automountMapName") || + !strcasecmp($1, "nisMapName")) { + strcpy(errstr, $1); + strcat(errstr, "="); + strcat(errstr, $3); + master_notify(errstr); + YYABORT; + } + strcpy($$, $1); + strcat($$, "="); + strcat($$, $3); + strcat($$, ","); + strcat($$, $5); + } + | DNATTR + { + master_notify($1); + YYABORT; + } + | DNNAME + { + master_notify($1); + YYABORT; + } + ; + +options: option {} + | options COMMA option {} + | options option {} + | options COMMA COMMA option + { + master_notify($1); + YYABORT; + } + | options EQUAL + { + master_notify($1); + YYABORT; + } + ; + +option: daemon_option + | mount_option {} + | error + { + master_notify("bogus option"); + YYABORT; + } + ; + +daemon_option: OPT_TIMEOUT NUMBER { timeout = $2; } + | OPT_NTIMEOUT NUMBER { negative_timeout = $2; } + | OPT_SYMLINK { symlnk = 1; } + | OPT_NOBIND { nobind = 1; } + | OPT_NOGHOST { ghost = 0; } + | OPT_GHOST { ghost = 1; } + | OPT_VERBOSE { verbose = 1; } + | OPT_DEBUG { debug = 1; } + | OPT_RANDOM { random_selection = 1; } + | OPT_USE_WEIGHT { use_weight = 1; } + | OPT_MODE OCTALNUMBER { mode = $2; } + ; + +mount_option: OPTION + { + tmp_argc++; + tmp_argv = add_argv(tmp_argc, tmp_argv, $1); + if (!tmp_argv) { + master_error("memory allocation error"); + local_free_vars(); + YYABORT; + } + } + ; +%% + +#if YYDEBUG +static int master_fprintf(FILE *f, char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vsyslog(LOG_DEBUG, msg, ap); + va_end(ap); + return 1; +} +#endif + +static char *master_strdup(char *str) +{ + char *tmp; + + tmp = strdup(str); + if (!tmp) + master_error("memory allocation error"); + return tmp; +} + +static int master_error(const char *s) +{ + logmsg("%s while parsing map.", s); + return 0; +} + +static int master_notify(const char *s) +{ + logmsg("syntax error in map near [ %s ]", s); + return(0); +} + +static int master_msg(const char *s) +{ + logmsg("%s", s); + return 0; +} + +static void local_init_vars(void) +{ + path = NULL; + type = NULL; + format = NULL; + verbose = 0; + debug = 0; + timeout = -1; + negative_timeout = 0; + symlnk = 0; + nobind = 0; + ghost = defaults_get_browse_mode(); + random_selection = global_selection_options & MOUNT_FLAG_RANDOM_SELECT; + use_weight = 0; + mode = 0; + tmp_argv = NULL; + tmp_argc = 0; + local_argv = NULL; + local_argc = 0; +} + +static void local_free_vars(void) +{ + if (path) + free(path); + + if (type) + free(type); + + if (format) + free(format); + + if (local_argv) { + free_argv(local_argc, (const char **) local_argv); + local_argv = NULL; + local_argc = 0; + } + + if (tmp_argv) { + free_argv(tmp_argc, (const char **) tmp_argv); + tmp_argv = NULL; + tmp_argc = 0; + } +} + +static void trim_maptype(char *type) +{ + char *tmp; + + tmp = strchr(type, ':'); + if (tmp) + *tmp = '\0'; + else { + int len = strlen(type); + while (len-- && isblank(type[len])) + type[len] = '\0'; + } + return; +} + +static int add_multi_mapstr(void) +{ + if (type) { + /* If type given and format is non-null add it back */ + if (format) { + int len = strlen(type) + strlen(format) + 2; + char *tmp = realloc(type, len); + if (!tmp) + return 0; + type = tmp; + strcat(type, ","); + strcat(type, format); + free(format); + format = NULL; + } + + local_argc++; + local_argv = add_argv(local_argc, local_argv, type); + if (!local_argv) { + free(type); + type = NULL; + return 0; + } + + free(type); + type = NULL; + } + + local_argv = append_argv(local_argc, local_argv, tmp_argc, tmp_argv); + if (!local_argv) { + free(type); + type = NULL; + return 0; + } + local_argc += tmp_argc; + + tmp_argc = 0; + tmp_argv = NULL; + + return 1; +} + +void master_init_scan(void) +{ + lineno = 0; +} + +int master_parse_entry(const char *buffer, unsigned int default_timeout, unsigned int logging, time_t age) +{ + struct master *master = master_list; + struct mapent_cache *nc; + struct master_mapent *entry, *new; + struct map_source *source; + unsigned int logopt = logging; + unsigned int m_logopt = master->logopt; + int ret; + + local_init_vars(); + + lineno++; + + master_set_scan_buffer(buffer); + + ret = master_parse(); + if (ret != 0) { + local_free_vars(); + return 0; + } + + nc = master->nc; + + /* Add null map entries to the null map cache */ + if (type && !strcmp(type, "null")) { + cache_update(nc, NULL, path, NULL, lineno); + local_free_vars(); + return 1; + } + + /* Ignore all subsequent matching nulled entries */ + if (cache_lookup_distinct(nc, path)) { + local_free_vars(); + return 1; + } + + if (debug || verbose) { + logopt = (debug ? LOGOPT_DEBUG : 0); + logopt |= (verbose ? LOGOPT_VERBOSE : 0); + } + + new = NULL; + entry = master_find_mapent(master, path); + if (!entry) { + new = master_new_mapent(master, path, age); + if (!new) { + local_free_vars(); + return 0; + } + entry = new; + } else { + if (entry->age && entry->age == age) { + if (strcmp(path, "/-")) { + info(m_logopt, + "ignoring duplicate indirect mount %s", + path); + local_free_vars(); + return 0; + } + } + } + + if (!format) { + if (conf_amd_mount_section_exists(path)) + format = strdup("amd"); + } + + if (format && !strcmp(format, "amd")) { + unsigned int loglevel = conf_amd_get_log_options(); + unsigned int flags = conf_amd_get_flags(path); + + if (loglevel <= LOG_DEBUG && loglevel > LOG_INFO) + logopt = LOGOPT_DEBUG; + else if (loglevel <= LOG_INFO && loglevel > LOG_ERR) + logopt = LOGOPT_VERBOSE; + + /* It isn't possible to provide the fullybrowsable amd + * browsing functionality within the autofs framework. + * This flag will not be set if browsable_dirs = full + * in the configuration or fullybrowsable is present as + * an option. + */ + if (flags & CONF_BROWSABLE_DIRS) + ghost = 1; + } + + + if (!entry->ap) { + ret = master_add_autofs_point(entry, logopt, nobind, ghost, 0); + if (!ret) { + error(m_logopt, "failed to add autofs_point"); + if (new) + master_free_mapent(new); + local_free_vars(); + return 0; + } + } + if (random_selection) + entry->ap->flags |= MOUNT_FLAG_RANDOM_SELECT; + if (use_weight) + entry->ap->flags |= MOUNT_FLAG_USE_WEIGHT_ONLY; + if (symlnk) + entry->ap->flags |= MOUNT_FLAG_SYMLINK; + if (negative_timeout) + entry->ap->negative_timeout = negative_timeout; + if (mode && mode < LONG_MAX) + entry->ap->mode = mode; + + if (timeout < 0) { + /* + * If no timeout is given get the timout from the + * autofs point, or the first map, or the config + * for amd maps. + */ + if (format && !strcmp(format, "amd")) + timeout = conf_amd_get_dismount_interval(path); + else + timeout = get_exp_timeout(entry->ap, entry->maps); + } + + if (format && !strcmp(format, "amd")) { + char *opts = conf_amd_get_map_options(path); + if (opts) { + /* autofs uses the equivalent of cache:=inc,sync + * (except for file maps which use cache:=all,sync) + * but if the map is large then it may be necessary + * to read the whole map at startup even if browsing + * is is not enabled, so look for cache:=all in the + * map_options configuration entry. + */ + if (strstr(opts, "cache:=all")) + entry->ap->flags |= MOUNT_FLAG_AMD_CACHE_ALL; + free(opts); + } + } + +/* + source = master_find_map_source(entry, type, format, + local_argc, (const char **) local_argv); + if (!source) + source = master_add_map_source(entry, type, format, age, + local_argc, (const char **) local_argv); + else + source->age = age; +*/ + source = master_add_map_source(entry, type, format, age, + local_argc, (const char **) local_argv); + if (!source) { + error(m_logopt, "failed to add source"); + if (new) + master_free_mapent(new); + local_free_vars(); + return 0; + } + set_exp_timeout(entry->ap, source, timeout); + source->master_line = lineno; + + entry->age = age; + entry->current = NULL; + + if (new) + master_add_mapent(master, entry); + + local_free_vars(); + + return 1; +} + diff --git a/lib/master_tok.l b/lib/master_tok.l new file mode 100644 index 0000000..b32918d --- /dev/null +++ b/lib/master_tok.l @@ -0,0 +1,498 @@ +%{ +/* ----------------------------------------------------------------------- * + * + * master_tok.l - master map tokenizer. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifdef ECHO +# undef ECHO +#endif /* ECHO */ +static void master_echo(void); /* forward definition */ +#define ECHO master_echo() + +#include +#include +#include +#include +#include "master_parse.tab.h" + +/* + * There are some things that need to be defined only if useing GNU flex. + * These must not be defined if using standard lex + */ +#ifdef FLEX_SCANNER +int master_lineno; +#endif + +int master_lex(void); +int master_wrap(void); + +/* no need for yywrap() */ +#define YY_SKIP_YYWRAP + +#ifndef YY_STACK_USED +#define YY_STACK_USED 0 +#endif +#ifndef YY_ALWAYS_INTERACTIVE +#define YY_ALWAYS_INTERACTIVE 0 +#endif +#ifndef YY_NEVER_INTERACTIVE +#define YY_NEVER_INTERACTIVE 0 +#endif +#ifndef YY_MAIN +#define YY_MAIN 0 +#endif + +void master_set_scan_buffer(const char *); +const char *line = NULL; + +#ifdef FLEX_SCANNER +const char *line_pos = NULL; +const char *line_lim = NULL; +int my_yyinput(char *, int); + +#undef YY_INPUT +#define YY_INPUT(b, r, ms) (r = my_yyinput(b, ms)) +#else +#undef input +#undef unput +#define input() (*(char *) line++) +#define unput(c) (*(char *) --line = c) +#endif + +#define BUFF_LEN 1024 +char buff[BUFF_LEN]; +char *bptr; +char *optr = buff; +unsigned int tlen; + +%} + +%option nounput + +%x PATHSTR MAPSTR DNSTR OPTSTR OCTAL + +WS [[:blank:]]+ +OPTWS [[:blank:]]* +NL \r?\n +CONT \\\n{OPTWS} + +OPTIONSTR ([\-]?([[:alpha:]_]([[:alnum:]_\-])*(=(\"?([[:alnum:]_\-\:])+\"?))?)+) +MACROSTR (-D{OPTWS}([[:alpha:]_]([[:alnum:]_\-\.])*)=([[:alnum:]_\-\.])+) +SLASHIFYSTR (--(no-)?slashify-colons) +NUMBER [0-9]+ +OCTALNUMBER [0-7]+ + +DNSERVSTR1 ([[:alpha:]][[:alnum:]\-.]*(:[0-9]+)?:) +DNSERVSTR2 (\[([[:xdigit:]]:.)+\](:[0-9]+)?:) +DNSERVSTR3 (\/\/[[:alpha:]][[:alnum:]\-.]*(:[0-9]+)?\/) +DNSERVSTR4 (\/\/\[([[:xdigit:]]:.)+\](:[0-9]+)?\/) +DNSERVSTR5 (([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}(:[0-9]+)?:) +DNSERVSTR6 (\/\/([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}(:[0-9]+)?\/) +DNSERVERSTR ({DNSERVSTR1}|{DNSERVSTR2}|{DNSERVSTR3}|{DNSERVSTR4}|{DNSERVSTR5}|{DNSERVSTR6}) + +AT_CN ([cC][[nN]) +AT_NMN ([nN][iI][sS][Mm][aA][pP][Nn][aA][mM][eE]) +AT_AMN ([aA][uU][tT][oO][mM][oO][uU][nN][tT][Mm][aA][pP][Nn][aA][mM][eE]) +AT_OU ([oO][[uU]) +AT_DC ([dD][[cC]) +AT_O ([oO]) +AT_C ([cC]) +AT_L ([lL]) +DNATTRSTR ({AT_CN}|{AT_NMN}|{AT_AMN}|{AT_OU}|{AT_DC}|{AT_O}|{AT_C}|{AT_L}) +DNNAMESTR1 ([[:alnum:]_.\- ]+) +DNNAMESTR2 ([[:alnum:]_.\-]+) + +INTMAP (-hosts|-null) +MULTI ((multi)(,(sun|hesiod))?(:{OPTWS}|{WS})) +MULTISEP ([\-]{2}[[:blank:]]+) +MTYPE ((file|program|exec|sss|yp|nis|nisplus|ldap|ldaps|hesiod|userdir)(,(sun|hesiod|amd))?(:{OPTWS}|{WS})) + + +OPTTOUT (-t{OPTWS}|-t{OPTWS}={OPTWS}|--timeout{OPTWS}|--timeout{OPTWS}={OPTWS}) +OPTNTOUT (-n{OPTWS}|-n{OPTWS}={OPTWS}|--negative-timeout{OPTWS}|--negative-timeout{OPTWS}={OPTWS}) + +MODE (--mode{OPTWS}|--mode{OPTWS}={OPTWS}) + +%% + +{ + {NL} | + \x00 { + if (optr != buff) { + *optr = '\0'; + strcpy(master_lval.strtype, buff); + return NILL; + } + } + + #.* { return COMMENT; } + + "/" { + if (optr != buff) { + *optr = '\0'; + strcpy(master_lval.strtype, buff); + return NILL; + } + BEGIN(PATHSTR); + bptr = buff; + yyless(0); + } + + . { *optr++ = *master_text; } +} + +{ + \x00 { + BEGIN(INITIAL); + *bptr++ = *master_text; + strcpy(master_lval.strtype, buff); + return NILL; + } + + \\. { *bptr++ = *(master_text + 1); } + \" { + BEGIN(INITIAL); + *bptr++ = *master_text; + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + return QUOTE; + } + + {WS} { + BEGIN(MAPSTR); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + bptr = buff; + memset(buff, 0, BUFF_LEN); + return(PATH); + } + + <> { + BEGIN(INITIAL); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + return(PATH); + } + + {NL} { + BEGIN(INITIAL); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + return PATH; + } + + . { *bptr++ = *master_text; } +} + +{ + {OPTWS}\\\n{OPTWS} {} + + {MULTI} { + tlen = master_leng - 1; + if (bptr != buff && isblank(master_text[tlen])) { + /* + * We can't handle unescaped white space in map names + * so just eat the white space. We always have the + * "multi" at the beginning of the string so the while + * will not fall off the end. + */ + while (isblank(master_text[tlen - 1])) + tlen--; + strncat(buff, master_text, tlen); + bptr += tlen; + yyless(tlen); + } else { + strcpy(master_lval.strtype, master_text); + return(MULTITYPE); + } + } + + {MTYPE} | + {MTYPE}/{DNSERVERSTR}{DNATTRSTR}= | + {MTYPE}/{DNATTRSTR}= { + tlen = master_leng - 1; + if (bptr != buff && isblank(master_text[tlen])) { + /* + * We can't handle unescaped white space in map names + * so just eat the white space. We always have the + * maptype keyword at the beginning of the string so + * the while will not fall off the end. + */ + while (isblank(master_text[tlen - 1])) + tlen--; + strncat(buff, master_text, tlen); + bptr += tlen; + yyless(tlen); + } else { + strcpy(master_lval.strtype, master_text); + return(MAPTYPE); + } + } + + {MULTISEP} { return(DDASH); } + + ":" { return(COLON); } + + "-hosts" { + BEGIN(OPTSTR); + strcpy(master_lval.strtype, master_text); + return MAPHOSTS; + } + + "-null" { + BEGIN(OPTSTR); + strcpy(master_lval.strtype, master_text); + return MAPNULL; + } + + "-xfn" { + /* + * The X/Open Federated Naming service isn't supported + * and the parser will call YYABORT() when it sees the + * MAPXFN token so we must set the start state to the + * INITIAL state here for the next yylex() call. + */ + BEGIN(INITIAL); + strcpy(master_lval.strtype, master_text); + return MAPXFN; + } + + "//" { + BEGIN(DNSTR); + yyless(0); + } + + {DNSERVERSTR}{DNATTRSTR}= { + BEGIN(DNSTR); + yyless(0); + } + + {DNATTRSTR}= { + BEGIN(DNSTR); + yyless(0); + } + + {OPTWS}/{NL} { + BEGIN(INITIAL); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + bptr = buff; + return(MAPNAME); + } + + \\. { *bptr++ = *(master_text + 1); } + + {WS} { + BEGIN(OPTSTR); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + bptr = buff; + return(MAPNAME); + } + + {NL} | + \x00 { + BEGIN(INITIAL); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + return(MAPNAME); + } + + <> { + BEGIN(INITIAL); + *bptr = '\0'; + strcpy(master_lval.strtype, buff); + return(MAPNAME); + } + + . { *bptr++ = *master_text; } +} + +{ + {OPTWS}\\\n{OPTWS} {} + + {DNSERVERSTR} { + strcpy(master_lval.strtype, master_text); + return DNSERVER; + } + + {DNATTRSTR}/"=" { + strcpy(master_lval.strtype, master_text); + return DNATTR; + } + + "=" { + return EQUAL; + } + + {DNNAMESTR1}/","{DNATTRSTR}"=" { + strcpy(master_lval.strtype, master_text); + return DNNAME; + } + + {DNNAMESTR2} { + strcpy(master_lval.strtype, master_text); + return DNNAME; + } + + {OPTWS}","{OPTWS} { + return COMMA; + } + + {WS}"=" | + "="{WS} { + BEGIN(INITIAL); + strcpy(master_lval.strtype, master_text); + return SPACE; + } + + {WS} { BEGIN(OPTSTR); } + + {NL} | + \x00 { BEGIN(INITIAL); } + + <> { BEGIN(INITIAL); } +} + +{ + {OPTWS}\\\n{OPTWS} {} + + {MULTISEP} { + BEGIN(MAPSTR); + return(DDASH); + } + + {OPTTOUT}/{NUMBER} { return(OPT_TIMEOUT); } + + {OPTNTOUT}/{NUMBER} { return(OPT_NTIMEOUT); } + + {NUMBER} { + master_lval.longtype = atol(master_text); + return(NUMBER); + } + + -?symlink { return(OPT_SYMLINK); } + -?nobind { return(OPT_NOBIND); } + -?nobrowse { return(OPT_NOGHOST); } + -g|--ghost|-?browse { return(OPT_GHOST); } + -v|--verbose { return(OPT_VERBOSE); } + -d|--debug { return(OPT_DEBUG); } + -w|--use-weight-only { return(OPT_USE_WEIGHT); } + -r|--random-multimount-selection { return(OPT_RANDOM); } + + {MODE}/{OCTALNUMBER} { + BEGIN(OCTAL); + return(OPT_MODE); + } + + {OPTWS}","{OPTWS} { return(COMMA); } + + {OPTWS} {} + + {SLASHIFYSTR} { + strcpy(master_lval.strtype, master_text); + return(OPTION); + } + + {MACROSTR} { + strcpy(master_lval.strtype, master_text); + return(OPTION); + } + + {OPTIONSTR} { + strcpy(master_lval.strtype, master_text); + return(OPTION); + } + + "=" { + strcpy(master_lval.strtype, master_text); + return(EQUAL); + } + + {WS} {} + {NL} | + \x00 { BEGIN(INITIAL); } + + <> { BEGIN(INITIAL); } +} + +{ + {OCTALNUMBER} { + master_lval.longtype = strtoul(master_text, NULL, 8); + return(OCTALNUMBER); + } + + . { BEGIN(OPTSTR); yyless(0); } +} + +%% + +#include "automount.h" + +int master_wrap(void) +{ + return 1; +} + +static void master_echo(void) +{ + logmsg("%s", master_text); + return; +} + +#ifdef FLEX_SCANNER + +void master_set_scan_buffer(const char *buffer) +{ + memset(buff, 0, sizeof(buff)); + optr = buff; + + YY_FLUSH_BUFFER; + + line = buffer; + line_pos = &line[0]; + /* + * Ensure buffer is 1 greater than string and is zeroed before + * the parse so we can fit the extra NULL which allows us to + * explicitly match an end of line within the buffer (ie. the + * need for 2 NULLS when parsing in memeory buffers). + */ + line_lim = line + strlen(buffer) + 1; +} + +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +int my_yyinput(char *buffer, int max_size) +{ + int n = min(max_size, line_lim - line_pos); + + if (n > 0) { + memcpy(buffer, line_pos, n); + line_pos += n; + } + return n; +} + +#else + +void master_set_scan_buffer(const char *buffer) +{ + line = buffer; +} + +#endif diff --git a/lib/mount.x b/lib/mount.x new file mode 100644 index 0000000..f504e7c --- /dev/null +++ b/lib/mount.x @@ -0,0 +1,345 @@ +%/* +% * Sun RPC is a product of Sun Microsystems, Inc. and is provided for +% * unrestricted use provided that this legend is included on all tape +% * media and as a part of the software program in whole or part. Users +% * may copy or modify Sun RPC without charge, but are not authorized +% * to license or distribute it to anyone else except as part of a product or +% * program developed by the user or with the express written consent of +% * Sun Microsystems, Inc. +% * +% * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE +% * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR +% * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. +% * +% * Sun RPC is provided with no support and without any obligation on the +% * part of Sun Microsystems, Inc. to assist in its use, correction, +% * modification or enhancement. +% * +% * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE +% * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC +% * OR ANY PART THEREOF. +% * +% * In no event will Sun Microsystems, Inc. be liable for any lost revenue +% * or profits or other special, indirect and consequential damages, even if +% * Sun has been advised of the possibility of such damages. +% * +% * Sun Microsystems, Inc. +% * 2550 Garcia Avenue +% * Mountain View, California 94043 +% */ + +%/* +% * Copyright (c) 1985, 1990 by Sun Microsystems, Inc. +% */ +% +%/* from @(#)mount.x 1.3 91/03/11 TIRPC 1.0 */ + +/* + * Protocol description for the mount program + */ + +#ifdef RPC_HDR +%#ifndef _rpcsvc_mount_h +%#define _rpcsvc_mount_h +%#include +#endif + +const MNTPATHLEN = 1024; /* maximum bytes in a pathname argument */ +const MNTNAMLEN = 255; /* maximum bytes in a name argument */ +const FHSIZE = 32; /* size in bytes of a file handle */ + +/* + * The fhandle is the file handle that the server passes to the client. + * All file operations are done using the file handles to refer to a file + * or a directory. The file handle can contain whatever information the + * server needs to distinguish an individual file. + */ +typedef opaque fhandle[FHSIZE]; + +/* + * If a status of zero is returned, the call completed successfully, and + * a file handle for the directory follows. A non-zero status indicates + * some sort of error. The status corresponds with UNIX error numbers. + */ +union fhstatus switch (unsigned fhs_status) { +case 0: + fhandle fhs_fhandle; +default: + void; +}; + +/* + * The type dirpath is the pathname of a directory + */ +typedef string dirpath; + +/* + * The type name is used for arbitrary names (hostnames, groupnames) + */ +typedef string name; + +/* + * A list of who has what mounted + */ +typedef struct mountbody *mountlist; +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; + +/* + * A list of netgroups + */ +typedef struct groupnode *groups; +struct groupnode { + name gr_name; + groups gr_next; +}; + +/* + * A list of what is exported and to whom + */ +typedef struct exportnode *exports; +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; + +/* + * POSIX pathconf information + */ +struct ppathcnf { + int pc_link_max; /* max links allowed */ + short pc_max_canon; /* max line len for a tty */ + short pc_max_input; /* input a tty can eat all at once */ + short pc_name_max; /* max file name length (dir entry) */ + short pc_path_max; /* max path name length (/x/y/x/.. ) */ + short pc_pipe_buf; /* size of a pipe (bytes) */ + u_char pc_vdisable; /* safe char to turn off c_cc[i] */ + char pc_xxx; /* alignment padding; cc_t == char */ + short pc_mask[2]; /* validity and boolean bits */ +}; + +/* + * NFSv3 file handle + */ +const FHSIZE3 = 64; /* max size of NFSv3 file handle in bytes */ +typedef opaque fhandle3; + +/* + * NFSv3 mount status + */ +enum mountstat3 { + MNT_OK = 0, /* no error */ + MNT3ERR_PERM = 1, /* not owner */ + MNT3ERR_NOENT = 2, /* no such file or directory */ + MNT3ERR_IO = 5, /* I/O error */ + MNT3ERR_ACCES = 13, /* Permission denied */ + MNT3ERR_NOTDIR = 20, /* Not a directory */ + MNT3ERR_INVAL = 22, /* Invalid argument */ + MNT3ERR_NAMETOOLONG = 63, /* File name too long */ + MNT3ERR_NOTSUPP = 10004,/* Operation not supported */ + MNT3ERR_SERVERFAULT = 10006 /* A failure on the server */ +}; + +/* + * NFSv3 mount result + */ +struct mountres3_ok { + fhandle3 fhandle; + int auth_flavors<>; +}; + +union mountres3 switch (mountstat3 fhs_status) { +case MNT_OK: + mountres3_ok mountinfo; /* File handle and supported flavors */ +default: + void; +}; + +program MOUNTPROG { + /* + * Version one of the mount protocol communicates with version two + * of the NFS protocol. The only connecting point is the fhandle + * structure, which is the same for both protocols. + */ + version MOUNTVERS { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + } = 1; + + /* + * Version two of the mount protocol communicates with version two + * of the NFS protocol. + * The only difference from version one is the addition of a POSIX + * pathconf call. + */ + version MOUNTVERS_POSIX { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + + /* + * POSIX pathconf info (Sun hack) + */ + ppathcnf + MOUNTPROC_PATHCONF(dirpath) = 7; + } = 2; + + /* + * Version 3 of the protocol is for NFSv3 + */ + version MOUNTVERS_NFSV3 { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC3_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + mountres3 + MOUNTPROC3_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC3_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC3_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC3_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC3_EXPORT(void) = 5; + } = 3; +} = 100005; + +#ifdef RPC_HDR +%#endif /*!_rpcsvc_mount_h*/ +#endif diff --git a/lib/mounts.c b/lib/mounts.c new file mode 100644 index 0000000..ce6a60a --- /dev/null +++ b/lib/mounts.c @@ -0,0 +1,2436 @@ +/* ----------------------------------------------------------------------- * + * + * mounts.c - module for mount utilities. + * + * Copyright 2002-2005 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +#define MAX_OPTIONS_LEN 80 +#define MAX_MNT_NAME_LEN 30 +#define MAX_ENV_NAME 15 + +#define EBUFSIZ 1024 + +const unsigned int t_indirect = AUTOFS_TYPE_INDIRECT; +const unsigned int t_direct = AUTOFS_TYPE_DIRECT; +const unsigned int t_offset = AUTOFS_TYPE_OFFSET; +const unsigned int type_count = 3; + +static const char options_template[] = "fd=%d,pgrp=%u,minproto=5,maxproto=%d"; +static const char options_template_extra[] = "fd=%d,pgrp=%u,minproto=5,maxproto=%d,%s"; +static const char mnt_name_template[] = "automount(pid%u)"; + +static struct kernel_mod_version kver = {0, 0}; +static const char kver_options_template[] = "fd=%d,pgrp=%u,minproto=3,maxproto=5"; + +extern size_t detached_thread_stack_size; +static size_t maxgrpbuf = 0; + +#define EXT_MOUNTS_HASH_SIZE 50 + +struct ext_mount { + char *mountpoint; + unsigned int umount; + struct list_head mount; + struct list_head mounts; +}; +static struct list_head ext_mounts_hash[EXT_MOUNTS_HASH_SIZE]; +static unsigned int ext_mounts_hash_init_done = 0; +static pthread_mutex_t ext_mount_hash_mutex = PTHREAD_MUTEX_INITIALIZER; + +unsigned int linux_version_code(void) +{ + struct utsname my_utsname; + unsigned int p, q, r; + char *tmp, *save; + + if (uname(&my_utsname)) + return 0; + + p = q = r = 0; + + tmp = strtok_r(my_utsname.release, ".", &save); + if (!tmp) + return 0; + p = (unsigned int ) atoi(tmp); + + tmp = strtok_r(NULL, ".", &save); + if (!tmp) + return KERNEL_VERSION(p, 0, 0); + q = (unsigned int) atoi(tmp); + + tmp = strtok_r(NULL, ".", &save); + if (!tmp) + return KERNEL_VERSION(p, q, 0); + r = (unsigned int) atoi(tmp); + + return KERNEL_VERSION(p, q, r); +} + +unsigned int query_kproto_ver(void) +{ + struct ioctl_ops *ops; + char dir[] = "/tmp/autoXXXXXX", *t_dir; + char options[MAX_OPTIONS_LEN + 1]; + pid_t pgrp = getpgrp(); + int pipefd[2], ioctlfd, len; + struct stat st; + + t_dir = mkdtemp(dir); + if (!t_dir) + return 0; + + if (pipe(pipefd) == -1) { + rmdir(t_dir); + return 0; + } + + len = snprintf(options, MAX_OPTIONS_LEN, + kver_options_template, pipefd[1], (unsigned) pgrp); + if (len < 0) { + close(pipefd[0]); + close(pipefd[1]); + rmdir(t_dir); + return 0; + } + + if (mount("automount", t_dir, "autofs", MS_MGC_VAL, options)) { + close(pipefd[0]); + close(pipefd[1]); + rmdir(t_dir); + return 0; + } + + close(pipefd[1]); + + if (stat(t_dir, &st) == -1) { + umount(t_dir); + close(pipefd[0]); + rmdir(t_dir); + return 0; + } + + ops = get_ioctl_ops(); + if (!ops) { + umount(t_dir); + close(pipefd[0]); + rmdir(t_dir); + return 0; + } + + ops->open(LOGOPT_NONE, &ioctlfd, st.st_dev, t_dir); + if (ioctlfd == -1) { + umount(t_dir); + close(pipefd[0]); + close_ioctl_ctl(); + rmdir(t_dir); + return 0; + } + + ops->catatonic(LOGOPT_NONE, ioctlfd); + + /* If this ioctl() doesn't work, it is kernel version 2 */ + if (ops->protover(LOGOPT_NONE, ioctlfd, &kver.major)) { + ops->close(LOGOPT_NONE, ioctlfd); + umount(t_dir); + close(pipefd[0]); + close_ioctl_ctl(); + rmdir(t_dir); + return 0; + } + + /* If this ioctl() doesn't work, version is 4 or less */ + if (ops->protosubver(LOGOPT_NONE, ioctlfd, &kver.minor)) { + ops->close(LOGOPT_NONE, ioctlfd); + umount(t_dir); + close(pipefd[0]); + close_ioctl_ctl(); + rmdir(t_dir); + return 0; + } + + ops->close(LOGOPT_NONE, ioctlfd); + umount(t_dir); + close(pipefd[0]); + close_ioctl_ctl(); + rmdir(t_dir); + + return 1; +} + +unsigned int get_kver_major(void) +{ + return kver.major; +} + +unsigned int get_kver_minor(void) +{ + return kver.minor; +} + +#ifdef HAVE_MOUNT_NFS +static int extract_version(char *start, struct nfs_mount_vers *vers) +{ + char *s_ver = strchr(start, ' '); + if (!s_ver) + return 0; + while (*s_ver && !isdigit(*s_ver)) { + s_ver++; + if (!*s_ver) + return 0; + break; + } + vers->major = atoi(strtok(s_ver, ".")); + vers->minor = (unsigned int) atoi(strtok(NULL, ".")); + vers->fix = (unsigned int) atoi(strtok(NULL, ".")); + return 1; +} + +int check_nfs_mount_version(struct nfs_mount_vers *vers, + struct nfs_mount_vers *check) +{ + pid_t f; + int ret, status, pipefd[2]; + char errbuf[EBUFSIZ + 1], *p, *sp; + int errp, errn; + sigset_t allsigs, tmpsig, oldsig; + char *s_ver; + int cancel_state; + + if (pipe(pipefd)) + return -1; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); + + sigfillset(&allsigs); + pthread_sigmask(SIG_BLOCK, &allsigs, &oldsig); + + f = fork(); + if (f == 0) { + reset_signals(); + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + + execl(PATH_MOUNT_NFS, PATH_MOUNT_NFS, "-V", (char *) NULL); + _exit(255); /* execv() failed */ + } + + ret = 0; + + tmpsig = oldsig; + + sigaddset(&tmpsig, SIGCHLD); + pthread_sigmask(SIG_SETMASK, &tmpsig, NULL); + + close(pipefd[1]); + + if (f < 0) { + close(pipefd[0]); + pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + pthread_setcancelstate(cancel_state, NULL); + return -1; + } + + errp = 0; + do { + while (1) { + errn = read(pipefd[0], errbuf + errp, EBUFSIZ - errp); + if (errn == -1 && errno == EINTR) + continue; + break; + } + + if (errn > 0) { + errp += errn; + + sp = errbuf; + while (errp && (p = memchr(sp, '\n', errp))) { + *p++ = '\0'; + errp -= (p - sp); + sp = p; + } + + if (errp && sp != errbuf) + memmove(errbuf, sp, errp); + + if (errp >= EBUFSIZ) { + /* Line too long, split */ + errbuf[errp] = '\0'; + if ((s_ver = strstr(errbuf, "nfs-utils"))) { + if (extract_version(s_ver, vers)) + ret = 1; + } + errp = 0; + } + + if ((s_ver = strstr(errbuf, "nfs-utils"))) { + if (extract_version(s_ver, vers)) + ret = 1; + } + } + } while (errn > 0); + + close(pipefd[0]); + + if (errp > 0) { + /* End of file without \n */ + errbuf[errp] = '\0'; + if ((s_ver = strstr(errbuf, "nfs-utils"))) { + if (extract_version(s_ver, vers)) + ret = 1; + } + } + + if (ret) { + if ((vers->major < check->major) || + ((vers->major == check->major) && (vers->minor < check->minor)) || + ((vers->major == check->major) && (vers->minor == check->minor) && + (vers->fix < check->fix))) + ret = 0; + } + + if (waitpid(f, &status, 0) != f) + debug(LOGOPT_NONE, "no process found to wait for"); + + pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + pthread_setcancelstate(cancel_state, NULL); + + return ret; +} +#else +int check_nfs_mount_version(struct nfs_mount_vers *vers, + struct nfs_mount_vers *check) +{ + return 0; +} +#endif + +static char *set_env_name(const char *prefix, const char *name, char *buf) +{ + size_t len; + + len = strlen(name); + if (prefix) + len += strlen(prefix); + len++; + + if (len > MAX_ENV_NAME) + return NULL; + + if (!prefix) + strcpy(buf, name); + else { + strcpy(buf, prefix); + strcat(buf, name); + } + return buf; +} + +static struct substvar *do_macro_addvar(struct substvar *list, + const char *prefix, + const char *name, + const char *val) +{ + char buf[MAX_ENV_NAME + 1]; + char *new; + size_t len; + + new = set_env_name(prefix, name, buf); + if (new) { + len = strlen(new); + list = macro_addvar(list, new, len, val); + } + return list; +} + +static struct substvar *do_macro_removevar(struct substvar *list, + const char *prefix, + const char *name) +{ + char buf[MAX_ENV_NAME + 1]; + char *new; + size_t len; + + new = set_env_name(prefix, name, buf); + if (new) { + len = strlen(new); + list = macro_removevar(list, new, len); + } + return list; +} + +struct substvar *addstdenv(struct substvar *sv, const char *prefix) +{ + struct substvar *list = sv; + struct thread_stdenv_vars *tsv; + char numbuf[16]; + + tsv = pthread_getspecific(key_thread_stdenv_vars); + if (tsv) { + const struct substvar *mv; + int ret; + long num; + + num = (long) tsv->uid; + ret = sprintf(numbuf, "%ld", num); + if (ret > 0) + list = do_macro_addvar(list, prefix, "UID", numbuf); + num = (long) tsv->gid; + ret = sprintf(numbuf, "%ld", num); + if (ret > 0) + list = do_macro_addvar(list, prefix, "GID", numbuf); + list = do_macro_addvar(list, prefix, "USER", tsv->user); + list = do_macro_addvar(list, prefix, "GROUP", tsv->group); + list = do_macro_addvar(list, prefix, "HOME", tsv->home); + mv = macro_findvar(list, "HOST", 4); + if (mv) { + char *shost = strdup(mv->val); + if (shost) { + char *dot = strchr(shost, '.'); + if (dot) + *dot = '\0'; + list = do_macro_addvar(list, + prefix, "SHOST", shost); + free(shost); + } + } + } + return list; +} + +struct substvar *removestdenv(struct substvar *sv, const char *prefix) +{ + struct substvar *list = sv; + + list = do_macro_removevar(list, prefix, "UID"); + list = do_macro_removevar(list, prefix, "USER"); + list = do_macro_removevar(list, prefix, "HOME"); + list = do_macro_removevar(list, prefix, "GID"); + list = do_macro_removevar(list, prefix, "GROUP"); + list = do_macro_removevar(list, prefix, "SHOST"); + return list; +} + +void add_std_amd_vars(struct substvar *sv) +{ + char *tmp; + + tmp = conf_amd_get_arch(); + if (tmp) { + macro_global_addvar("arch", 4, tmp); + free(tmp); + } + + tmp = conf_amd_get_karch(); + if (tmp) { + macro_global_addvar("karch", 5, tmp); + free(tmp); + } + + tmp = conf_amd_get_os(); + if (tmp) { + macro_global_addvar("os", 2, tmp); + free(tmp); + } + + tmp = conf_amd_get_full_os(); + if (tmp) { + macro_global_addvar("full_os", 7, tmp); + free(tmp); + } + + tmp = conf_amd_get_os_ver(); + if (tmp) { + macro_global_addvar("osver", 5, tmp); + free(tmp); + } + + tmp = conf_amd_get_vendor(); + if (tmp) { + macro_global_addvar("vendor", 6, tmp); + free(tmp); + } + + /* Umm ... HP_UX cluster name, probably not used */ + tmp = conf_amd_get_cluster(); + if (tmp) { + macro_global_addvar("cluster", 7, tmp); + free(tmp); + } else { + const struct substvar *v = macro_findvar(sv, "domain", 4); + if (v && *v->val) { + tmp = strdup(v->val); + if (tmp) + macro_global_addvar("cluster", 7, tmp); + } + } + + tmp = conf_amd_get_auto_dir(); + if (tmp) { + macro_global_addvar("autodir", 7, tmp); + free(tmp); + } + + return; +} + +void remove_std_amd_vars(void) +{ + macro_global_removevar("autodir", 7); + macro_global_removevar("cluster", 7); + macro_global_removevar("vendor", 6); + macro_global_removevar("osver", 5); + macro_global_removevar("full_os", 7); + macro_global_removevar("os", 2); + macro_global_removevar("karch", 5); + macro_global_removevar("arch", 4); + return; + } + +struct amd_entry *new_amd_entry(const struct substvar *sv) +{ + struct amd_entry *new; + const struct substvar *v; + char *path; + + v = macro_findvar(sv, "path", 4); + if (!v) + return NULL; + + path = strdup(v->val); + if (!path) + return NULL; + + new = malloc(sizeof(struct amd_entry)); + if (!new) { + free(path); + return NULL; + } + + memset(new, 0, sizeof(*new)); + new->path = path; + INIT_LIST_HEAD(&new->list); + INIT_LIST_HEAD(&new->entries); + INIT_LIST_HEAD(&new->ext_mount); + + return new; +} + +void clear_amd_entry(struct amd_entry *entry) +{ + if (!entry) + return; + if (entry->path) + free(entry->path); + if (entry->map_type) + free(entry->map_type); + if (entry->pref) + free(entry->pref); + if (entry->fs) + free(entry->fs); + if (entry->rhost) + free(entry->rhost); + if (entry->rfs) + free(entry->rfs); + if (entry->opts) + free(entry->opts); + if (entry->addopts) + free(entry->addopts); + if (entry->remopts) + free(entry->remopts); + if (entry->sublink) + free(entry->sublink); + if (entry->selector) + free_selector(entry->selector); + return; +} + +void free_amd_entry(struct amd_entry *entry) +{ + clear_amd_entry(entry); + free(entry); + return; +} + +void free_amd_entry_list(struct list_head *entries) +{ + if (!list_empty(entries)) { + struct list_head *head = entries; + struct amd_entry *this; + struct list_head *p; + + p = head->next; + while (p != head) { + this = list_entry(p, struct amd_entry, list); + p = p->next; + free_amd_entry(this); + } + } +} + +/* + * Make common autofs mount options string + */ +char *make_options_string(char *path, int pipefd, const char *extra) +{ + char *options; + int len; + + options = malloc(MAX_OPTIONS_LEN + 1); + if (!options) { + logerr("can't malloc options string"); + return NULL; + } + + if (extra) + len = snprintf(options, MAX_OPTIONS_LEN, + options_template_extra, + pipefd, (unsigned) getpgrp(), + AUTOFS_MAX_PROTO_VERSION, extra); + else + len = snprintf(options, MAX_OPTIONS_LEN, options_template, + pipefd, (unsigned) getpgrp(), + AUTOFS_MAX_PROTO_VERSION); + + if (len >= MAX_OPTIONS_LEN) { + logerr("buffer to small for options - truncated"); + len = MAX_OPTIONS_LEN - 1; + } + + if (len < 0) { + logerr("failed to malloc autofs mount options for %s", path); + free(options); + return NULL; + } + options[len] = '\0'; + + return options; +} + +char *make_mnt_name_string(char *path) +{ + char *mnt_name; + int len; + + mnt_name = malloc(MAX_MNT_NAME_LEN + 1); + if (!mnt_name) { + logerr("can't malloc mnt_name string"); + return NULL; + } + + len = snprintf(mnt_name, MAX_MNT_NAME_LEN, + mnt_name_template, (unsigned) getpid()); + + if (len >= MAX_MNT_NAME_LEN) { + logerr("buffer to small for mnt_name - truncated"); + len = MAX_MNT_NAME_LEN - 1; + } + + if (len < 0) { + logerr("failed setting up mnt_name for autofs path %s", path); + free(mnt_name); + return NULL; + } + mnt_name[len] = '\0'; + + return mnt_name; +} + +static void ext_mounts_hash_init(void) +{ + int i; + for (i = 0; i < EXT_MOUNTS_HASH_SIZE; i++) + INIT_LIST_HEAD(&ext_mounts_hash[i]); + ext_mounts_hash_init_done = 1; +} + +static struct ext_mount *ext_mount_lookup(const char *mountpoint) +{ + u_int32_t hval = hash(mountpoint, EXT_MOUNTS_HASH_SIZE); + struct list_head *p, *head; + + if (!ext_mounts_hash_init_done) + ext_mounts_hash_init(); + + if (list_empty(&ext_mounts_hash[hval])) + return NULL; + + head = &ext_mounts_hash[hval]; + list_for_each(p, head) { + struct ext_mount *this = list_entry(p, struct ext_mount, mount); + if (!strcmp(this->mountpoint, mountpoint)) + return this; + } + return NULL; +} + +int ext_mount_add(struct list_head *entry, const char *path, unsigned int umount) +{ + struct ext_mount *em; + char *auto_dir; + u_int32_t hval; + int ret = 0; + + /* Not a mount in the external mount directory */ + auto_dir = conf_amd_get_auto_dir(); + if (strncmp(path, auto_dir, strlen(auto_dir))) { + free(auto_dir); + return 0; + } + free(auto_dir); + + pthread_mutex_lock(&ext_mount_hash_mutex); + + em = ext_mount_lookup(path); + if (em) { + struct list_head *p, *head; + head = &em->mounts; + list_for_each(p, head) { + if (p == entry) + goto done; + } + list_add_tail(entry, &em->mounts); + ret = 1; + goto done; + } + + em = malloc(sizeof(struct ext_mount)); + if (!em) { + ret = -1; + goto done; + } + + em->mountpoint = strdup(path); + if (!em->mountpoint) { + free(em); + ret = -1; + goto done; + } + em->umount = umount; + INIT_LIST_HEAD(&em->mount); + INIT_LIST_HEAD(&em->mounts); + + hval = hash(path, EXT_MOUNTS_HASH_SIZE); + list_add_tail(&em->mount, &ext_mounts_hash[hval]); + + list_add_tail(entry, &em->mounts); + + ret = 1; +done: + pthread_mutex_unlock(&ext_mount_hash_mutex); + return ret; +} + +int ext_mount_remove(struct list_head *entry, const char *path) +{ + struct ext_mount *em; + char *auto_dir; + int ret = 0; + + /* Not a mount in the external mount directory */ + auto_dir = conf_amd_get_auto_dir(); + if (strncmp(path, auto_dir, strlen(auto_dir))) { + free(auto_dir); + return 0; + } + free(auto_dir); + + pthread_mutex_lock(&ext_mount_hash_mutex); + + em = ext_mount_lookup(path); + if (!em) + goto done; + + list_del_init(entry); + + if (!list_empty(&em->mounts)) + goto done; + else { + list_del_init(&em->mount); + if (em->umount) + ret = 1; + if (list_empty(&em->mount)) { + free(em->mountpoint); + free(em); + } + } +done: + pthread_mutex_unlock(&ext_mount_hash_mutex); + return ret; +} + +/* + * Get list of mounts under path in longest->shortest order + */ +struct mnt_list *get_mnt_list(const char *table, const char *path, int include) +{ + FILE *tab; + size_t pathlen = strlen(path); + struct mntent mnt_wrk; + char buf[PATH_MAX * 3]; + struct mntent *mnt; + struct mnt_list *ent, *mptr, *last; + struct mnt_list *list = NULL; + char *pgrp; + size_t len; + + if (!path || !pathlen || pathlen > PATH_MAX) + return NULL; + + tab = open_setmntent_r(table); + if (!tab) { + char *estr = strerror_r(errno, buf, PATH_MAX - 1); + logerr("setmntent: %s", estr); + return NULL; + } + + while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) { + len = strlen(mnt->mnt_dir); + + if ((!include && len <= pathlen) || + strncmp(mnt->mnt_dir, path, pathlen) != 0) + continue; + + /* Not a subdirectory of requested path ? */ + /* pathlen == 1 => everything is subdir */ + if (pathlen > 1 && len > pathlen && + mnt->mnt_dir[pathlen] != '/') + continue; + + ent = malloc(sizeof(*ent)); + if (!ent) { + endmntent(tab); + free_mnt_list(list); + return NULL; + } + memset(ent, 0, sizeof(*ent)); + + mptr = list; + last = NULL; + while (mptr) { + if (len >= strlen(mptr->path)) + break; + last = mptr; + mptr = mptr->next; + } + + if (mptr == list) + list = ent; + else + last->next = ent; + + ent->next = mptr; + + ent->path = malloc(len + 1); + if (!ent->path) { + endmntent(tab); + free_mnt_list(list); + return NULL; + } + strcpy(ent->path, mnt->mnt_dir); + + ent->fs_name = malloc(strlen(mnt->mnt_fsname) + 1); + if (!ent->fs_name) { + endmntent(tab); + free_mnt_list(list); + return NULL; + } + strcpy(ent->fs_name, mnt->mnt_fsname); + + ent->fs_type = malloc(strlen(mnt->mnt_type) + 1); + if (!ent->fs_type) { + endmntent(tab); + free_mnt_list(list); + return NULL; + } + strcpy(ent->fs_type, mnt->mnt_type); + + ent->opts = malloc(strlen(mnt->mnt_opts) + 1); + if (!ent->opts) { + endmntent(tab); + free_mnt_list(list); + return NULL; + } + strcpy(ent->opts, mnt->mnt_opts); + + ent->owner = 0; + pgrp = strstr(mnt->mnt_opts, "pgrp="); + if (pgrp) { + char *end = strchr(pgrp, ','); + if (end) + *end = '\0'; + sscanf(pgrp, "pgrp=%d", &ent->owner); + } + } + endmntent(tab); + + return list; +} + +/* + * Reverse a list of mounts + */ +struct mnt_list *reverse_mnt_list(struct mnt_list *list) +{ + struct mnt_list *next, *last; + + if (!list) + return NULL; + + next = list; + last = NULL; + while (next) { + struct mnt_list *this = next; + next = this->next; + this->next = last; + last = this; + } + return last; +} + +void free_mnt_list(struct mnt_list *list) +{ + struct mnt_list *next; + + if (!list) + return; + + next = list; + while (next) { + struct mnt_list *this = next; + + next = this->next; + + if (this->path) + free(this->path); + + if (this->fs_name) + free(this->fs_name); + + if (this->fs_type) + free(this->fs_type); + + if (this->opts) + free(this->opts); + + free(this); + } +} + +static int table_is_mounted(const char *table, const char *path, unsigned int type) +{ + struct mntent *mnt; + struct mntent mnt_wrk; + char buf[PATH_MAX * 3]; + size_t pathlen = strlen(path); + FILE *tab; + int ret = 0; + + if (!path || !pathlen || pathlen >= PATH_MAX) + return 0; + + tab = open_setmntent_r(table); + if (!tab) { + char *estr = strerror_r(errno, buf, PATH_MAX - 1); + logerr("setmntent: %s", estr); + return 0; + } + + while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) { + size_t len = strlen(mnt->mnt_dir); + + if (type) { + unsigned int autofs_fs; + + autofs_fs = !strcmp(mnt->mnt_type, "autofs"); + + if (type & MNTS_REAL) + if (autofs_fs) + continue; + + if (type & MNTS_AUTOFS) + if (!autofs_fs) + continue; + } + + if (pathlen == len && !strncmp(path, mnt->mnt_dir, pathlen)) { + ret = 1; + break; + } + } + endmntent(tab); + + return ret; +} + +static int ioctl_is_mounted(const char *table, const char *path, unsigned int type) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + unsigned int mounted; + int ret; + + /* If the ioctl fails fall back to the potentially resource + * intensive mount table check. + */ + ret = ops->ismountpoint(LOGOPT_NONE, -1, path, &mounted); + if (ret == -1) + return table_is_mounted(table, path, type); + + if (mounted) { + switch (type) { + case MNTS_ALL: + return 1; + case MNTS_AUTOFS: + return (mounted & DEV_IOCTL_IS_AUTOFS); + case MNTS_REAL: + return (mounted & DEV_IOCTL_IS_OTHER); + } + } + return 0; +} + +int is_mounted(const char *table, const char *path, unsigned int type) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + + if (ops->ismountpoint) + return ioctl_is_mounted(table, path, type); + else + return table_is_mounted(table, path, type); +} + +int has_fstab_option(const char *opt) +{ + struct mntent *mnt; + struct mntent mnt_wrk; + char buf[PATH_MAX * 3]; + FILE *tab; + int ret = 0; + + if (!opt) + return 0; + + tab = open_setmntent_r(_PATH_MNTTAB); + if (!tab) { + char *estr = strerror_r(errno, buf, PATH_MAX - 1); + logerr("setmntent: %s", estr); + return 0; + } + + while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) { + if (hasmntopt(mnt, opt)) { + ret = 1; + break; + } + } + endmntent(tab); + + return ret; +} + +/* + * Since we have to look at the entire mount tree for direct + * mounts (all mounts under "/") and we may have a large number + * of entries to traverse again and again we need to + * use a more efficient method than the routines above. + * + * Thre tree_... routines allow us to read the mount tree + * once and pass it to subsequent functions for use. Since + * it's a tree structure searching should be a low overhead + * operation. + */ +void tree_free_mnt_tree(struct mnt_list *tree) +{ + struct list_head *head, *p; + + if (!tree) + return; + + tree_free_mnt_tree(tree->left); + tree_free_mnt_tree(tree->right); + + head = &tree->self; + p = head->next; + while (p != head) { + struct mnt_list *this; + + this = list_entry(p, struct mnt_list, self); + + p = p->next; + + list_del(&this->self); + + free(this->path); + free(this->fs_name); + free(this->fs_type); + + if (this->opts) + free(this->opts); + + free(this); + } + + free(tree->path); + free(tree->fs_name); + free(tree->fs_type); + + if (tree->opts) + free(tree->opts); + + free(tree); +} + +/* + * Make tree of system mounts in /proc/mounts. + */ +struct mnt_list *tree_make_mnt_tree(const char *table, const char *path) +{ + FILE *tab; + struct mntent mnt_wrk; + char buf[PATH_MAX * 3]; + struct mntent *mnt; + struct mnt_list *ent, *mptr; + struct mnt_list *tree = NULL; + char *pgrp; + size_t plen; + int eq; + + tab = open_setmntent_r(table); + if (!tab) { + char *estr = strerror_r(errno, buf, PATH_MAX - 1); + logerr("setmntent: %s", estr); + return NULL; + } + + plen = strlen(path); + + while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) { + size_t len = strlen(mnt->mnt_dir); + + /* Not matching path */ + if (strncmp(mnt->mnt_dir, path, plen)) + continue; + + /* Not a subdirectory of requested path */ + if (plen > 1 && len > plen && mnt->mnt_dir[plen] != '/') + continue; + + ent = malloc(sizeof(*ent)); + if (!ent) { + endmntent(tab); + tree_free_mnt_tree(tree); + return NULL; + } + memset(ent, 0, sizeof(*ent)); + + INIT_LIST_HEAD(&ent->self); + INIT_LIST_HEAD(&ent->list); + INIT_LIST_HEAD(&ent->entries); + INIT_LIST_HEAD(&ent->sublist); + + ent->path = malloc(len + 1); + if (!ent->path) { + endmntent(tab); + free(ent); + tree_free_mnt_tree(tree); + return NULL; + } + strcpy(ent->path, mnt->mnt_dir); + + ent->fs_name = malloc(strlen(mnt->mnt_fsname) + 1); + if (!ent->fs_name) { + free(ent->path); + free(ent); + endmntent(tab); + tree_free_mnt_tree(tree); + return NULL; + } + strcpy(ent->fs_name, mnt->mnt_fsname); + + ent->fs_type = malloc(strlen(mnt->mnt_type) + 1); + if (!ent->fs_type) { + free(ent->fs_name); + free(ent->path); + free(ent); + endmntent(tab); + tree_free_mnt_tree(tree); + return NULL; + } + strcpy(ent->fs_type, mnt->mnt_type); + + ent->opts = malloc(strlen(mnt->mnt_opts) + 1); + if (!ent->opts) { + free(ent->fs_type); + free(ent->fs_name); + free(ent->path); + free(ent); + endmntent(tab); + tree_free_mnt_tree(tree); + return NULL; + } + strcpy(ent->opts, mnt->mnt_opts); + + ent->owner = 0; + pgrp = strstr(mnt->mnt_opts, "pgrp="); + if (pgrp) { + char *end = strchr(pgrp, ','); + if (end) + *end = '\0'; + sscanf(pgrp, "pgrp=%d", &ent->owner); + } + + mptr = tree; + while (mptr) { + int elen = strlen(ent->path); + int mlen = strlen(mptr->path); + + if (elen < mlen) { + if (mptr->left) { + mptr = mptr->left; + continue; + } else { + mptr->left = ent; + break; + } + } else if (elen > mlen) { + if (mptr->right) { + mptr = mptr->right; + continue; + } else { + mptr->right = ent; + break; + } + } + + eq = strcmp(ent->path, mptr->path); + if (eq < 0) { + if (mptr->left) + mptr = mptr->left; + else { + mptr->left = ent; + break; + } + } else if (eq > 0) { + if (mptr->right) + mptr = mptr->right; + else { + mptr->right = ent; + break; + } + } else { + list_add_tail(&ent->self, &mptr->self); + break; + } + } + + if (!tree) + tree = ent; + } + endmntent(tab); + + return tree; +} + +/* + * Get list of mounts under "path" in longest->shortest order + */ +int tree_get_mnt_list(struct mnt_list *mnts, struct list_head *list, const char *path, int include) +{ + size_t mlen, plen; + + if (!mnts) + return 0; + + plen = strlen(path); + mlen = strlen(mnts->path); + if (mlen < plen) + return tree_get_mnt_list(mnts->right, list, path, include); + else { + struct list_head *self, *p; + + tree_get_mnt_list(mnts->left, list, path, include); + + if ((!include && mlen <= plen) || + strncmp(mnts->path, path, plen)) + goto skip; + + if (plen > 1 && mlen > plen && mnts->path[plen] != '/') + goto skip; + + INIT_LIST_HEAD(&mnts->list); + list_add(&mnts->list, list); + + self = &mnts->self; + list_for_each(p, self) { + struct mnt_list *this; + + this = list_entry(p, struct mnt_list, self); + INIT_LIST_HEAD(&this->list); + list_add(&this->list, list); + } +skip: + tree_get_mnt_list(mnts->right, list, path, include); + } + + if (list_empty(list)) + return 0; + + return 1; +} + +/* + * Get list of mounts under "path" in longest->shortest order + */ +int tree_get_mnt_sublist(struct mnt_list *mnts, struct list_head *list, const char *path, int include) +{ + size_t mlen, plen; + + if (!mnts) + return 0; + + plen = strlen(path); + mlen = strlen(mnts->path); + if (mlen < plen) + return tree_get_mnt_sublist(mnts->right, list, path, include); + else { + struct list_head *self, *p; + + tree_get_mnt_sublist(mnts->left, list, path, include); + + if ((!include && mlen <= plen) || + strncmp(mnts->path, path, plen)) + goto skip; + + if (plen > 1 && mlen > plen && mnts->path[plen] != '/') + goto skip; + + INIT_LIST_HEAD(&mnts->sublist); + list_add(&mnts->sublist, list); + + self = &mnts->self; + list_for_each(p, self) { + struct mnt_list *this; + + this = list_entry(p, struct mnt_list, self); + INIT_LIST_HEAD(&this->sublist); + list_add(&this->sublist, list); + } +skip: + tree_get_mnt_sublist(mnts->right, list, path, include); + } + + if (list_empty(list)) + return 0; + + return 1; +} + +int tree_find_mnt_ents(struct mnt_list *mnts, struct list_head *list, const char *path) +{ + int mlen, plen; + + if (!mnts) + return 0; + + plen = strlen(path); + mlen = strlen(mnts->path); + if (mlen < plen) + return tree_find_mnt_ents(mnts->right, list, path); + else if (mlen > plen) + return tree_find_mnt_ents(mnts->left, list, path); + else { + struct list_head *self, *p; + + tree_find_mnt_ents(mnts->left, list, path); + + if (!strcmp(mnts->path, path)) { + INIT_LIST_HEAD(&mnts->entries); + list_add(&mnts->entries, list); + } + + self = &mnts->self; + list_for_each(p, self) { + struct mnt_list *this; + + this = list_entry(p, struct mnt_list, self); + + if (!strcmp(this->path, path)) { + INIT_LIST_HEAD(&this->entries); + list_add(&this->entries, list); + } + } + + tree_find_mnt_ents(mnts->right, list, path); + + if (!list_empty(list)) + return 1; + } + + return 0; +} + +int tree_is_mounted(struct mnt_list *mnts, const char *path, unsigned int type) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + struct list_head *p; + struct list_head list; + int mounted = 0; + + if (ops->ismountpoint) + return ioctl_is_mounted(_PROC_MOUNTS, path, type); + + INIT_LIST_HEAD(&list); + + if (!tree_find_mnt_ents(mnts, &list, path)) + return 0; + + list_for_each(p, &list) { + struct mnt_list *mptr; + + mptr = list_entry(p, struct mnt_list, entries); + + if (type) { + unsigned int autofs_fs; + + autofs_fs = !strcmp(mptr->fs_type, "autofs"); + + if (type & MNTS_REAL) { + if (!autofs_fs) { + mounted = 1; + break; + } + } else if (type & MNTS_AUTOFS) { + if (autofs_fs) { + mounted = 1; + break; + } + } else { + mounted = 1; + break; + } + } + } + return mounted; +} + +void set_tsd_user_vars(unsigned int logopt, uid_t uid, gid_t gid) +{ + struct thread_stdenv_vars *tsv; + struct passwd pw; + struct passwd *ppw = &pw; + struct passwd **pppw = &ppw; + struct group gr; + struct group *pgr; + struct group **ppgr; + char *pw_tmp, *gr_tmp; + int status, tmplen, grplen; + + /* + * Setup thread specific data values for macro + * substution in map entries during the mount. + * Best effort only as it must go ahead. + */ + + tsv = malloc(sizeof(struct thread_stdenv_vars)); + if (!tsv) { + error(logopt, "failed alloc tsv storage"); + return; + } + + tsv->uid = uid; + tsv->gid = gid; + + /* Try to get passwd info */ + + tmplen = sysconf(_SC_GETPW_R_SIZE_MAX); + if (tmplen < 0) { + error(logopt, "failed to get buffer size for getpwuid_r"); + goto free_tsv; + } + + pw_tmp = malloc(tmplen + 1); + if (!pw_tmp) { + error(logopt, "failed to malloc buffer for getpwuid_r"); + goto free_tsv; + } + + status = getpwuid_r(uid, ppw, pw_tmp, tmplen, pppw); + if (status || !ppw) { + error(logopt, "failed to get passwd info from getpwuid_r"); + free(pw_tmp); + goto free_tsv; + } + + tsv->user = strdup(pw.pw_name); + if (!tsv->user) { + error(logopt, "failed to malloc buffer for user"); + free(pw_tmp); + goto free_tsv; + } + + tsv->home = strdup(pw.pw_dir); + if (!tsv->home) { + error(logopt, "failed to malloc buffer for home"); + free(pw_tmp); + goto free_tsv_user; + } + + free(pw_tmp); + + /* Try to get group info */ + + grplen = sysconf(_SC_GETGR_R_SIZE_MAX); + if (grplen < 0) { + error(logopt, "failed to get buffer size for getgrgid_r"); + goto free_tsv_home; + } + + gr_tmp = NULL; + status = ERANGE; +#ifdef ENABLE_LIMIT_GETGRGID_SIZE + if (!maxgrpbuf) + maxgrpbuf = detached_thread_stack_size * 0.9; +#endif + + /* If getting the group name fails go on without it. It's + * used to set an environment variable for program maps + * which may or may not use it so it isn't critical to + * operation. + */ + + tmplen = grplen; + while (1) { + char *tmp = realloc(gr_tmp, tmplen + 1); + if (!tmp) { + error(logopt, "failed to malloc buffer for getgrgid_r"); + goto no_group; + } + gr_tmp = tmp; + pgr = &gr; + ppgr = &pgr; + status = getgrgid_r(gid, pgr, gr_tmp, tmplen, ppgr); + if (status != ERANGE) + break; + tmplen += grplen; + + /* Don't tempt glibc to alloca() larger than is (likely) + * available on the stack if limit-getgrgid-size is enabled. + */ + if (!maxgrpbuf || (tmplen < maxgrpbuf)) + continue; + + /* Add a message so we know this happened */ + debug(logopt, "group buffer allocation would be too large"); + break; + } + +no_group: + if (status || !pgr) + error(logopt, "failed to get group info from getgrgid_r"); + else { + tsv->group = strdup(gr.gr_name); + if (!tsv->group) + error(logopt, "failed to malloc buffer for group"); + } + + if (gr_tmp) + free(gr_tmp); + + status = pthread_setspecific(key_thread_stdenv_vars, tsv); + if (status) { + error(logopt, "failed to set stdenv thread var"); + goto free_tsv_group; + } + + return; + +free_tsv_group: + if (tsv->group) + free(tsv->group); +free_tsv_home: + free(tsv->home); +free_tsv_user: + free(tsv->user); +free_tsv: + free(tsv); + return; +} + +const char *mount_type_str(const unsigned int type) +{ + static const char *str_type[] = { + "indirect", + "direct", + "offset" + }; + unsigned int pos, i; + + for (pos = 0, i = type; pos < type_count; i >>= 1, pos++) + if (i & 0x1) + break; + + return (pos == type_count ? NULL : str_type[pos]); +} + +void set_exp_timeout(struct autofs_point *ap, + struct map_source *source, time_t timeout) +{ + ap->exp_timeout = timeout; + if (source) + source->exp_timeout = timeout; +} + +time_t get_exp_timeout(struct autofs_point *ap, struct map_source *source) +{ + time_t timeout = ap->exp_timeout; + + if (source && ap->type == LKP_DIRECT) + timeout = source->exp_timeout; + + return timeout; +} + +void notify_mount_result(struct autofs_point *ap, + const char *path, time_t timeout, const char *type) +{ + if (timeout) + info(ap->logopt, + "mounted %s on %s with timeout %u, freq %u seconds", + type, path, (unsigned int) timeout, + (unsigned int) ap->exp_runfreq); + else + info(ap->logopt, + "mounted %s on %s with timeouts disabled", + type, path); + + return; +} + +static int do_remount_direct(struct autofs_point *ap, int fd, const char *path) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + int status = REMOUNT_SUCCESS; + uid_t uid; + gid_t gid; + int ret; + + ops->requester(ap->logopt, fd, path, &uid, &gid); + if (uid != -1 && gid != -1) + set_tsd_user_vars(ap->logopt, uid, gid); + + ret = lookup_nss_mount(ap, NULL, path, strlen(path)); + if (ret) + info(ap->logopt, "re-connected to %s", path); + else { + status = REMOUNT_FAIL; + info(ap->logopt, "failed to re-connect %s", path); + } + + return status; +} + +static int do_remount_indirect(struct autofs_point *ap, int fd, const char *path) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + int status = REMOUNT_SUCCESS; + struct dirent **de; + char buf[PATH_MAX + 1]; + uid_t uid; + gid_t gid; + unsigned int mounted; + int n, size; + + n = scandir(path, &de, 0, alphasort); + if (n < 0) + return -1; + + size = sizeof(buf); + + while (n--) { + int ret, len; + + if (strcmp(de[n]->d_name, ".") == 0 || + strcmp(de[n]->d_name, "..") == 0) { + free(de[n]); + continue; + } + + ret = cat_path(buf, size, path, de[n]->d_name); + if (!ret) { + do { + free(de[n]); + } while (n--); + free(de); + return -1; + } + + ops->ismountpoint(ap->logopt, -1, buf, &mounted); + if (!mounted) { + struct dirent **de2; + int i, j; + + i = j = scandir(buf, &de2, 0, alphasort); + if (i < 0) { + free(de[n]); + continue; + } + while (i--) + free(de2[i]); + free(de2); + if (j <= 2) { + free(de[n]); + continue; + } + } + + ops->requester(ap->logopt, fd, buf, &uid, &gid); + if (uid != -1 && gid != -1) + set_tsd_user_vars(ap->logopt, uid, gid); + + len = strlen(de[n]->d_name); + + ret = lookup_nss_mount(ap, NULL, de[n]->d_name, len); + if (ret) + info(ap->logopt, "re-connected to %s", buf); + else { + status = REMOUNT_FAIL; + info(ap->logopt, "failed to re-connect %s", buf); + } + free(de[n]); + } + free(de); + + return status; +} + +static int remount_active_mount(struct autofs_point *ap, + struct mapent *me, const char *path, dev_t devid, + const unsigned int type, int *ioctlfd) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + const char *str_type = mount_type_str(type); + char buf[MAX_ERR_BUF]; + unsigned int mounted; + time_t timeout; + struct stat st; + int fd; + + *ioctlfd = -1; + + /* Open failed, no mount present */ + ops->open(ap->logopt, &fd, devid, path); + if (fd == -1) + return REMOUNT_OPEN_FAIL; + + if (!me) + timeout = get_exp_timeout(ap, NULL); + else + timeout = get_exp_timeout(ap, me->source); + + /* Re-reading the map, set timeout and return */ + if (ap->state == ST_READMAP) { + debug(ap->logopt, "already mounted, update timeout"); + ops->timeout(ap->logopt, fd, timeout); + ops->close(ap->logopt, fd); + return REMOUNT_READ_MAP; + } + + debug(ap->logopt, "trying to re-connect to mount %s", path); + + /* Mounted so set pipefd and timeout etc. */ + if (ops->catatonic(ap->logopt, fd) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "set catatonic failed: %s", estr); + debug(ap->logopt, "couldn't re-connect to mount %s", path); + ops->close(ap->logopt, fd); + return REMOUNT_OPEN_FAIL; + } + if (ops->setpipefd(ap->logopt, fd, ap->kpipefd) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "set pipefd failed: %s", estr); + debug(ap->logopt, "couldn't re-connect to mount %s", path); + ops->close(ap->logopt, fd); + return REMOUNT_OPEN_FAIL; + } + ops->timeout(ap->logopt, fd, timeout); + if (fstat(fd, &st) == -1) { + error(ap->logopt, + "failed to stat %s mount %s", str_type, path); + debug(ap->logopt, "couldn't re-connect to mount %s", path); + ops->close(ap->logopt, fd); + return REMOUNT_STAT_FAIL; + } + if (type != t_indirect) + cache_set_ino_index(me->mc, path, st.st_dev, st.st_ino); + else + ap->dev = st.st_dev; + notify_mount_result(ap, path, timeout, str_type); + + *ioctlfd = fd; + + /* Any mounts on or below? */ + if (ops->ismountpoint(ap->logopt, fd, path, &mounted) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "ismountpoint %s failed: %s", path, estr); + debug(ap->logopt, "couldn't re-connect to mount %s", path); + ops->close(ap->logopt, fd); + return REMOUNT_FAIL; + } + if (!mounted) { + /* + * If we're an indirect mount we pass back the fd. + * But if were a direct or offset mount with no active + * mount we don't retain an open file descriptor. + */ + if (type != t_indirect) { + ops->close(ap->logopt, fd); + *ioctlfd = -1; + } + } else { + /* + * What can I do if we can't remount the existing + * mount(s) (possibly a partial failure), everything + * following will be broken? + */ + if (type == t_indirect) + do_remount_indirect(ap, fd, path); + else + do_remount_direct(ap, fd, path); + } + + debug(ap->logopt, "re-connected to mount %s", path); + + return REMOUNT_SUCCESS; +} + +int try_remount(struct autofs_point *ap, struct mapent *me, unsigned int type) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + const char *path; + int ret, fd; + dev_t devid; + + if (type == t_indirect) + path = ap->path; + else + path = me->key; + + ret = ops->mount_device(ap->logopt, path, type, &devid); + if (ret == -1 || ret == 0) + return -1; + + ret = remount_active_mount(ap, me, path, devid, type, &fd); + + /* + * The directory must exist since we found a device + * number for the mount but we can't know if we created + * it or not. However, if this is an indirect mount with + * the nobrowse option we need to remove the mount point + * directory at umount anyway. + */ + if (type == t_indirect) { + if (ap->flags & MOUNT_FLAG_GHOST) + ap->flags &= ~MOUNT_FLAG_DIR_CREATED; + else + ap->flags |= MOUNT_FLAG_DIR_CREATED; + } else + me->flags &= ~MOUNT_FLAG_DIR_CREATED; + + /* + * Either we opened the mount or we're re-reading the map. + * If we opened the mount and ioctlfd is not -1 we have + * a descriptor for the indirect mount so we need to + * record that in the mount point struct. Otherwise we're + * re-reading the map. + */ + if (ret == REMOUNT_READ_MAP) + return 1; + else if (ret == REMOUNT_SUCCESS) { + if (fd != -1) { + if (type == t_indirect) + ap->ioctlfd = fd; + else + me->ioctlfd = fd; + return 1; + } + + /* Indirect mount requires a valid fd */ + if (type != t_indirect) + return 1; + } + + /* + * Since we got the device number above a mount exists so + * any other failure warrants a failure return here. + */ + return 0; +} + +/* + * When exiting mounts need be set catatonic, regardless of whether they + * are busy on not, to avoid a hang on access once the daemon has gone + * away. + */ +static int set_mount_catatonic(struct autofs_point *ap, struct mapent *me, int ioctlfd) +{ + struct ioctl_ops *ops = get_ioctl_ops(); + unsigned int opened = 0; + char buf[MAX_ERR_BUF]; + char *path; + int fd = -1; + int error; + dev_t dev; + + path = ap->path; + dev = ap->dev; + if (me && (ap->type == LKP_DIRECT || *me->key == '/')) { + path = me->key; + dev = me->dev; + } + + if (ioctlfd >= 0) + fd = ioctlfd; + else if (me && me->ioctlfd >= 0) + fd = me->ioctlfd; + else { + error = ops->open(ap->logopt, &fd, dev, path); + if (error == -1) { + int err = errno; + char *estr; + + if (errno == ENOENT) + return 0; + + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + "failed to open ioctlfd for %s, error: %s", + path, estr); + return err; + } + opened = 1; + } + + if (fd >= 0) { + error = ops->catatonic(ap->logopt, fd); + if (error == -1) { + int err = errno; + char *estr; + + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + "failed to set %s catatonic, error: %s", + path, estr); + if (opened) + ops->close(ap->logopt, fd); + return err; + } + if (opened) + ops->close(ap->logopt, fd); + } + + debug(ap->logopt, "set %s catatonic", path); + + return 0; +} + +static void set_multi_mount_tree_catatonic(struct autofs_point *ap, struct mapent *me) +{ + if (!list_empty(&me->multi_list)) { + struct list_head *head = &me->multi_list; + struct list_head *p; + + list_for_each(p, head) { + struct mapent *this; + + this = list_entry(p, struct mapent, multi_list); + set_mount_catatonic(ap, this, this->ioctlfd); + } + } +} + +void set_indirect_mount_tree_catatonic(struct autofs_point *ap) +{ + struct master_mapent *entry = ap->entry; + struct map_source *map; + struct mapent_cache *mc; + struct mapent *me; + + if (!is_mounted(_PROC_MOUNTS, ap->path, MNTS_AUTOFS)) + return; + + map = entry->maps; + while (map) { + mc = map->mc; + cache_readlock(mc); + me = cache_enumerate(mc, NULL); + while (me) { + /* Skip negative map entries and wildcard entries */ + if (!me->mapent) + goto next; + + if (!strcmp(me->key, "*")) + goto next; + + /* Only need to set offset mounts catatonic */ + if (me->multi && me->multi == me) + set_multi_mount_tree_catatonic(ap, me); +next: + me = cache_enumerate(mc, me); + } + cache_unlock(mc); + map = map->next; + } + + /* By the time this function is called ap->ioctlfd will have + * been closed so don't try and use it. + */ + set_mount_catatonic(ap, NULL, -1); + + return; +} + +void set_direct_mount_tree_catatonic(struct autofs_point *ap, struct mapent *me) +{ + /* Set offset mounts catatonic for this mapent */ + if (me->multi && me->multi == me) + set_multi_mount_tree_catatonic(ap, me); + set_mount_catatonic(ap, me, me->ioctlfd); +} + +int umount_ent(struct autofs_point *ap, const char *path) +{ + int rv; + + rv = spawn_umount(ap->logopt, path, NULL); + /* We are doing a forced shutcwdown down so unlink busy mounts */ + if (rv && (ap->state == ST_SHUTDOWN_FORCE || ap->state == ST_SHUTDOWN)) { + if (ap->state == ST_SHUTDOWN_FORCE) { + info(ap->logopt, "forcing umount of %s", path); + rv = spawn_umount(ap->logopt, "-l", path, NULL); + } + + /* + * Verify that we actually unmounted the thing. This is a + * belt and suspenders approach to not eating user data. + * We have seen cases where umount succeeds, but there is + * still a file system mounted on the mount point. How + * this happens has not yet been determined, but we want to + * make sure to return failure here, if that is the case, + * so that we do not try to call rmdir_path on the + * directory. + */ + if (!rv && is_mounted(_PATH_MOUNTED, path, MNTS_REAL)) { + crit(ap->logopt, + "the umount binary reported that %s was " + "unmounted, but there is still something " + "mounted on this path.", path); + rv = -1; + } + } + + return rv; +} + +static int do_mount_autofs_offset(struct autofs_point *ap, + struct mapent *oe, const char *root, + char *offset) + +{ + int mounted = 0; + int ret; + + debug(ap->logopt, "mount offset %s at %s", oe->key, root); + + ret = mount_autofs_offset(ap, oe, root, offset); + if (ret >= MOUNT_OFFSET_OK) + mounted++; + else { + if (ret != MOUNT_OFFSET_IGNORE) + warn(ap->logopt, "failed to mount offset"); + else { + debug(ap->logopt, "ignoring \"nohide\" trigger %s", + oe->key); + free(oe->mapent); + oe->mapent = NULL; + } + } + + return mounted; +} + +int mount_multi_triggers(struct autofs_point *ap, struct mapent *me, + const char *root, unsigned int start, const char *base) +{ + char path[PATH_MAX + 1]; + char *offset = path; + struct mapent *oe; + struct list_head *pos = NULL; + unsigned int fs_path_len; + int mounted; + + fs_path_len = start + strlen(base); + if (fs_path_len > PATH_MAX) + return -1; + + mounted = 0; + offset = cache_get_offset(base, offset, start, &me->multi_list, &pos); + while (offset) { + int plen = fs_path_len + strlen(offset); + + if (plen > PATH_MAX) { + warn(ap->logopt, "path loo long"); + goto cont; + } + + oe = cache_lookup_offset(base, offset, start, &me->multi_list); + if (!oe || !oe->mapent) + goto cont; + + mounted += do_mount_autofs_offset(ap, oe, root, offset); + + /* + * If re-constructing a multi-mount it's necessary to walk + * into nested mounts, unlike the usual "mount only what's + * needed as you go" behavior. + */ + if (ap->state == ST_READMAP && ap->flags & MOUNT_FLAG_REMOUNT) { + if (oe->ioctlfd != -1 || + is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) { + char oe_root[PATH_MAX + 1]; + strcpy(oe_root, root); + strcat(oe_root, offset); + mount_multi_triggers(ap, oe, oe_root, strlen(oe_root), base); + } + } +cont: + offset = cache_get_offset(base, + offset, start, &me->multi_list, &pos); + } + + return mounted; +} + +static int rmdir_path_offset(struct autofs_point *ap, struct mapent *oe) +{ + char *dir, *path; + unsigned int split; + int ret; + + if (ap->type == LKP_DIRECT) + return rmdir_path(ap, oe->key, oe->multi->dev); + + dir = strdup(oe->key); + + if (ap->flags & MOUNT_FLAG_GHOST) + split = strlen(ap->path) + strlen(oe->multi->key) + 1; + else + split = strlen(ap->path); + + dir[split] = '\0'; + path = &dir[split + 1]; + + if (chdir(dir) == -1) { + error(ap->logopt, "failed to chdir to %s", dir); + free(dir); + return -1; + } + + ret = rmdir_path(ap, path, ap->dev); + + free(dir); + + if (chdir("/") == -1) + error(ap->logopt, "failed to chdir to /"); + + return ret; +} + +int umount_multi_triggers(struct autofs_point *ap, struct mapent *me, char *root, const char *base) +{ + char path[PATH_MAX + 1]; + char *offset; + struct mapent *oe; + struct list_head *mm_root, *pos; + const char o_root[] = "/"; + const char *mm_base; + int left, start; + + left = 0; + start = strlen(root); + + mm_root = &me->multi->multi_list; + + if (!base) + mm_base = o_root; + else + mm_base = base; + + pos = NULL; + offset = path; + + while ((offset = cache_get_offset(mm_base, offset, start, mm_root, &pos))) { + char *oe_base; + + oe = cache_lookup_offset(mm_base, offset, start, &me->multi_list); + /* root offset is a special case */ + if (!oe || (strlen(oe->key) - start) == 1) + continue; + + /* + * Check for and umount subtree offsets resulting from + * nonstrict mount fail. + */ + oe_base = oe->key + strlen(root); + left += umount_multi_triggers(ap, oe, root, oe_base); + + if (oe->ioctlfd != -1 || + is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) { + left++; + continue; + } + + debug(ap->logopt, "umount offset %s", oe->key); + + if (umount_autofs_offset(ap, oe)) { + warn(ap->logopt, "failed to umount offset"); + left++; + } else { + struct stat st; + int ret; + + if (!(oe->flags & MOUNT_FLAG_DIR_CREATED)) + continue; + + /* + * An error due to partial directory removal is + * ok so only try and remount the offset if the + * actual mount point still exists. + */ + ret = rmdir_path_offset(ap, oe); + if (ret == -1 && !stat(oe->key, &st)) { + ret = do_mount_autofs_offset(ap, oe, root, offset); + if (ret) + left++; + /* But we did origianlly create this */ + oe->flags |= MOUNT_FLAG_DIR_CREATED; + } + } + } + + if (!left && me->multi == me) { + struct mapent_cache *mc = me->mc; + int status; + + /* + * Special case. + * If we can't umount the root container then we can't + * delete the offsets from the cache and we need to put + * the offset triggers back. + */ + if (is_mounted(_PATH_MOUNTED, root, MNTS_REAL)) { + info(ap->logopt, "unmounting dir = %s", root); + if (umount_ent(ap, root) && + is_mounted(_PATH_MOUNTED, root, MNTS_REAL)) { + if (mount_multi_triggers(ap, me, root, strlen(root), "/") < 0) + warn(ap->logopt, + "failed to remount offset triggers"); + return ++left; + } + } + + /* We're done - clean out the offsets */ + status = cache_delete_offset_list(mc, me->key); + if (status != CHE_OK) + warn(ap->logopt, "couldn't delete offset list"); + } + + return left; +} + +int clean_stale_multi_triggers(struct autofs_point *ap, + struct mapent *me, char *top, const char *base) +{ + char *root; + char mm_top[PATH_MAX + 1]; + char path[PATH_MAX + 1]; + char buf[MAX_ERR_BUF]; + char *offset; + struct mapent *oe; + struct list_head *mm_root, *pos; + const char o_root[] = "/"; + const char *mm_base; + int left, start; + time_t age; + + if (top) + root = top; + else { + if (!strchr(me->multi->key, '/')) + /* Indirect multi-mount root */ + /* sprintf okay - if it's mounted, it's + * PATH_MAX or less bytes */ + sprintf(mm_top, "%s/%s", ap->path, me->multi->key); + else + strcpy(mm_top, me->multi->key); + root = mm_top; + } + + left = 0; + start = strlen(root); + + mm_root = &me->multi->multi_list; + + if (!base) + mm_base = o_root; + else + mm_base = base; + + pos = NULL; + offset = path; + age = me->multi->age; + + while ((offset = cache_get_offset(mm_base, offset, start, mm_root, &pos))) { + char *oe_base; + char *key; + int ret; + + oe = cache_lookup_offset(mm_base, offset, start, &me->multi_list); + /* root offset is a special case */ + if (!oe || (strlen(oe->key) - start) == 1) + continue; + + /* Check for and umount stale subtree offsets */ + oe_base = oe->key + strlen(root); + ret = clean_stale_multi_triggers(ap, oe, root, oe_base); + left += ret; + if (ret) + continue; + + if (oe->age == age) + continue; + + /* + * If an offset that has an active mount has been removed + * from the multi-mount we don't want to attempt to trigger + * mounts for it. Obviously this is because it has been + * removed, but less obvious is the potential strange + * behaviour that can result if we do try and mount it + * again after it's been expired. For example, if an NFS + * file system is no longer exported and is later umounted + * it can be mounted again without any error message but + * shows as an empty directory. That's going to confuse + * people for sure. + * + * If the mount cannot be umounted (the process is now + * using a stale mount) the offset needs to be invalidated + * so no further mounts will be attempted but the offset + * cache entry must remain so expires can continue to + * attempt to umount it. If the mount can be umounted and + * the offset is removed, at least for NFS we will get + * ESTALE errors when attempting list the directory. + */ + if (oe->ioctlfd != -1 || + is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) { + if (umount_ent(ap, oe->key) && + is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) { + debug(ap->logopt, + "offset %s has active mount, invalidate", + oe->key); + if (oe->mapent) { + free(oe->mapent); + oe->mapent = NULL; + } + left++; + continue; + } + } + + key = strdup(oe->key); + if (!key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + left++; + continue; + } + + debug(ap->logopt, "umount offset %s", oe->key); + + if (umount_autofs_offset(ap, oe)) { + warn(ap->logopt, "failed to umount offset %s", key); + left++; + } else { + struct stat st; + + /* Mount point not ours to delete ? */ + if (!(oe->flags & MOUNT_FLAG_DIR_CREATED)) { + debug(ap->logopt, "delete offset key %s", key); + if (cache_delete_offset(oe->mc, key) == CHE_FAIL) + error(ap->logopt, + "failed to delete offset key %s", key); + free(key); + continue; + } + + /* + * An error due to partial directory removal is + * ok so only try and remount the offset if the + * actual mount point still exists. + */ + ret = rmdir_path_offset(ap, oe); + if (ret == -1 && !stat(oe->key, &st)) { + ret = do_mount_autofs_offset(ap, oe, root, offset); + if (ret) { + left++; + /* But we did origianlly create this */ + oe->flags |= MOUNT_FLAG_DIR_CREATED; + free(key); + continue; + } + /* + * Fall through if the trigger can't be mounted + * again, since there is no offset there can't + * be any mount requests so remove the map + * entry from the cache. There's now a dead + * offset mount, but what else can we do .... + */ + } + + debug(ap->logopt, "delete offset key %s", key); + + if (cache_delete_offset(oe->mc, key) == CHE_FAIL) + error(ap->logopt, + "failed to delete offset key %s", key); + } + free(key); + } + + return left; +} + diff --git a/lib/nss_parse.y b/lib/nss_parse.y new file mode 100644 index 0000000..0721ba1 --- /dev/null +++ b/lib/nss_parse.y @@ -0,0 +1,200 @@ +%{ +/* ----------------------------------------------------------------------- * + * + * nss_parser.y - nsswitch parser. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include + +#include "automount.h" +#include "nsswitch.h" +#include "nss_parse.tab.h" + +static pthread_mutex_t parse_mutex = PTHREAD_MUTEX_INITIALIZER; + +static struct list_head *nss_list; +static struct nss_source *src; +struct nss_action act[NSS_STATUS_MAX]; + +#define YYDEBUG 0 + +#ifndef YYENABLE_NLS +#define YYENABLE_NLS 0 +#endif +#ifndef YYLTYPE_IS_TRIVIAL +#define YYLTYPE_IS_TRIVIAL 0 +#endif + +unsigned int nss_automount_found; + +extern int nss_lineno; +extern int nss_lex(void); +extern FILE *nss_in; + +static int nss_ignore(const char *s); +static int nss_error(const char *s); + +%} + +%union { +char strval[128]; +} + +%token LBRACKET RBRACKET EQUAL BANG NL +%token SOURCE +%token STATUS +%token ACTION + +%start file + +%% + +file: { +#if YYDEBUG != 0 + nss_debug = YYDEBUG; +#endif + } sources NL + | /* empty */ + ; + +sources: nss_source + | nss_source sources + ; + +nss_source: SOURCE +{ + if (!strcmp($1, "files") || !strcmp($1, "yp") || + !strcmp($1, "nis") || !strcmp($1, "ldap") || + !strcmp($1, "nisplus") || !strcmp($1, "hesiod") || + !strcmp($1, "sss")) + src = add_source(nss_list, $1); + else + nss_ignore($1); +} | SOURCE LBRACKET status_exp_list RBRACKET +{ + enum nsswitch_status a; + + if (!strcmp($1, "files") || !strcmp($1, "yp") || + !strcmp($1, "nis") || !strcmp($1, "ldap") || + !strcmp($1, "nisplus") || !strcmp($1, "hesiod") || + !strcmp($1, "sss")) { + src = add_source(nss_list, $1); + for (a = 0; a < NSS_STATUS_MAX; a++) { + if (act[a].action != NSS_ACTION_UNKNOWN) { + src->action[a].action = act[a].action; + src->action[a].negated = act[a].negated; + } + } + } else + nss_ignore($1); +} | SOURCE LBRACKET status_exp_list SOURCE { nss_error("missing close bracket"); YYABORT; } + | SOURCE LBRACKET status_exp_list NL { nss_error("missing close bracket"); YYABORT; } + | SOURCE LBRACKET SOURCE { nss_error($3); YYABORT; } + | error SOURCE { nss_error($2); YYABORT; }; + +status_exp_list: status_exp + | status_exp status_exp_list + +status_exp: STATUS EQUAL ACTION +{ + set_action(act, $1, $3, 0); +} | BANG STATUS EQUAL ACTION +{ + set_action(act, $2, $4, 1); +} | STATUS EQUAL SOURCE {nss_error($3); YYABORT; } + | STATUS SOURCE {nss_error($2); YYABORT; } + | BANG STATUS EQUAL SOURCE {nss_error($4); YYABORT; } + | BANG STATUS SOURCE {nss_error($3); YYABORT; } + | BANG SOURCE {nss_error($2); YYABORT; }; + +%% + +static int nss_ignore(const char *s) +{ + logmsg("ignored unsupported autofs nsswitch source \"%s\"", s); + return(0); +} + +static int nss_error(const char *s) +{ + logmsg("syntax error in nsswitch config near [ %s ]\n", s); + return(0); +} + +static void parse_mutex_lock(void) +{ + int status = pthread_mutex_lock(&parse_mutex); + if (status) + fatal(status); + return; +} + +static void parse_mutex_unlock(void *arg) +{ + int status = pthread_mutex_unlock(&parse_mutex); + if (status) + fatal(status); + return; +} + +static void parse_close_nsswitch(void *arg) +{ + FILE *nsswitch = (FILE *) arg; + fclose(nsswitch); + return; +} + +int nsswitch_parse(struct list_head *list) +{ + FILE *nsswitch; + int status; + + nsswitch = open_fopen_r(NSSWITCH_FILE); + if (!nsswitch) { + logerr("couldn't open %s", NSSWITCH_FILE); + return 1; + } + + pthread_cleanup_push(parse_close_nsswitch, nsswitch); + + parse_mutex_lock(); + pthread_cleanup_push(parse_mutex_unlock, NULL); + + nss_in = nsswitch; + + nss_automount_found = 0; + nss_list = list; + status = nss_parse(); + nss_list = NULL; + + /* No "automount" nsswitch entry, use "files" */ + if (!nss_automount_found) + if (add_source(list, "files")) + status = 0; + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + if (status) + return 1; + + return 0; +} diff --git a/lib/nss_tok.l b/lib/nss_tok.l new file mode 100644 index 0000000..1aede97 --- /dev/null +++ b/lib/nss_tok.l @@ -0,0 +1,142 @@ +%{ +/* ----------------------------------------------------------------------- * + * + * nss_tok.l - nsswitch tokenizer. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#ifdef ECHO +# undef ECHO +#endif /* ECHO */ +static void nss_echo(void); /* forward definition */ +#define ECHO nss_echo() + +#include +#include +#include +#include "nss_parse.tab.h" + +/* + * There are some things that need to be defined only if useing GNU flex. + * These must not be defined if using standard lex + */ +#ifdef FLEX_SCANNER +int nss_lineno; +#endif + +int nss_lex(void); +#ifndef nss_wrap +int nss_wrap(void); +#endif +#define YY_SKIP_YYWRAP + +#ifndef YY_STACK_USED +#define YY_STACK_USED 0 +#endif +#ifndef YY_ALWAYS_INTERACTIVE +#define YY_ALWAYS_INTERACTIVE 0 +#endif +#ifndef YY_NEVER_INTERACTIVE +#define YY_NEVER_INTERACTIVE 0 +#endif +#ifndef YY_MAIN +#define YY_MAIN 0 +#endif + +extern unsigned int nss_automount_found; + +%} + +%option nounput + +%x AUTOMOUNT ACTIONSTR + +WS [[:blank:]]+ + +automount ([Aa][Uu][Tt][Oo][Mm][Oo][Uu][Nn][Tt]) + +source [[:alnum:]@$%^&*()-+_":;?,<>./'{}~`]+ + +success ([Ss][Uu][Cc][Cc][Ee][Ss][Ss]) +notfound ([Nn][Oo][Tt][Ff][Oo][Uu][Nn][Dd]) +unavail ([Uu][Nn][Aa][Vv][Aa][Ii][Ll]) +tryagain ([Tt][Rr][Yy][Aa][Gg][Aa][Ii][Nn]) + +status ({success}|{notfound}|{unavail}|{tryagain}) + +continue ([Cc][Oo][Nn][Tt][Ii][Nn][Uu][Ee]) +return ([Rr][Ee][Tt][Uu][Rr][Nn]) + +action ({continue}|{return}) + +%% + +^{automount}: { + nss_automount_found = 1; + BEGIN(AUTOMOUNT); +} + +\n|. {} + +{ + {WS} { } + + {source} { + strcpy(nss_lval.strval, nss_text); + return SOURCE; + } + + "[" { BEGIN(ACTIONSTR); yyless(0); } + + \n { BEGIN(INITIAL); return NL; } +} + +{ + {WS} { } + + {status} { + strcpy(nss_lval.strval, nss_text); + return STATUS; + } + + {action} { + strcpy(nss_lval.strval, nss_text); + return ACTION; + } + + "[" { return LBRACKET; } + "]" { BEGIN(AUTOMOUNT); return RBRACKET; } + "=" { return EQUAL; } + "!" { return BANG; } + + . { BEGIN(AUTOMOUNT); yyless(0); } + \n { BEGIN(INITIAL); return NL; } +} + +%% + +#include "automount.h" + +int nss_wrap(void) +{ + return 1; +} + +static void nss_echo(void) +{ + logmsg("%s", nss_text); + return; +} diff --git a/lib/nsswitch.c b/lib/nsswitch.c new file mode 100644 index 0000000..74c7525 --- /dev/null +++ b/lib/nsswitch.c @@ -0,0 +1,186 @@ +/* ----------------------------------------------------------------------- * + * + * nsswitch.c - module to call parser for nsswitch config and store + * result into a struct. + * + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include "automount.h" +#include "nsswitch.h" + +int set_action(struct nss_action *act, char *status, char *action, int negated) +{ + enum nsswitch_action a; + + if (!strcasecmp(action, "continue")) + a = NSS_ACTION_CONTINUE; + else if (!strcasecmp(action, "return")) + a = NSS_ACTION_RETURN; + else + return 0; + + if (!strcasecmp(status, "SUCCESS")) { + act[NSS_STATUS_SUCCESS].action = a; + act[NSS_STATUS_SUCCESS].negated = negated; + } else if (!strcasecmp(status, "NOTFOUND")) { + act[NSS_STATUS_NOTFOUND].action = a; + act[NSS_STATUS_NOTFOUND].negated = negated; + } else if (!strcasecmp(status, "UNAVAIL")) { + act[NSS_STATUS_UNAVAIL].action = a; + act[NSS_STATUS_UNAVAIL].negated = negated; + } else if (!strcasecmp(status, "TRYAGAIN")) { + act[NSS_STATUS_TRYAGAIN].action = a; + act[NSS_STATUS_TRYAGAIN].negated = negated; + } else + return 0; + + return 1; +} + +int check_nss_result(struct nss_source *this, enum nsswitch_status result) +{ + enum nsswitch_status status; + struct nss_action a; + + /* Check if we have negated actions */ + for (status = 0; status < NSS_STATUS_MAX; status++) { + a = this->action[status]; + if (a.action == NSS_ACTION_UNKNOWN) + continue; + + if (a.negated && result != status) { + if (a.action == NSS_ACTION_RETURN) { + if (result == NSS_STATUS_SUCCESS) + return 1; + else + return 0; + } + } + } + + a = this->action[result]; + + /* Check if we have other actions for this status */ + switch (result) { + case NSS_STATUS_SUCCESS: + if (a.action == NSS_ACTION_CONTINUE) + break; + return 1; + + case NSS_STATUS_NOTFOUND: + case NSS_STATUS_UNAVAIL: + case NSS_STATUS_TRYAGAIN: + if (a.action == NSS_ACTION_RETURN) { + return 0; + } + break; + + default: + break; + } + + return -1; +} + +struct nss_source *add_source(struct list_head *head, char *source) +{ + struct nss_source *s; + char *tmp; + enum nsswitch_status status; + + s = malloc(sizeof(struct nss_source)); + if (!s) + return NULL; + + memset(s, 0, sizeof(struct nss_source)); + INIT_LIST_HEAD(&s->list); + + tmp = strdup(source); + if (!tmp) { + free(s); + return NULL; + } + s->source = tmp; + + for (status = 0; status < NSS_STATUS_MAX; status++) + s->action[status].action = NSS_ACTION_UNKNOWN; + + list_add_tail(&s->list, head); + + return s; +} + +int free_sources(struct list_head *list) +{ + struct nss_source *this; + struct list_head *head, *next; + + if (list_empty(list)) + return 0; + + head = list; + next = list->next; + while (next != head) { + this = list_entry(next, struct nss_source, list); + next = next->next; + + list_del(&this->list); + if (this->source) + free(this->source); + free(this); + } + return 1; +} + +/* +int main(void) +{ + struct nss_source *this; + struct list_head list; + struct list_head *head, *next; + int status; + + + status = nsswitch_parse(&list); + if (status) { + printf("error exit from nss_parse\n"); + free_sources(&list); + exit(1); + } + + head = &list; + next = head->next; + while (next != head) { + this = list_entry(next, struct nss_source, list); + next = next->next; + + printf("list->source = %s", this->source); + for (status = 0; status < NSS_STATUS_MAX; status++) { + if (this->action[status].action != NSS_ACTION_UNKNOWN) + printf(" ."); + } + printf("\n"); + } + free_sources(&list); + + exit(0); +} +*/ diff --git a/lib/parse_subs.c b/lib/parse_subs.c new file mode 100644 index 0000000..80383c2 --- /dev/null +++ b/lib/parse_subs.c @@ -0,0 +1,1358 @@ +/* ----------------------------------------------------------------------- * + * + * parse_subs.c - misc parser subroutines + * automounter map + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2000 Jeremy Fitzhardinge + * Copyright 2004-2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" + +#define MAX_OPTIONS_LEN 256 +#define MAX_OPTION_LEN 40 + +#define MAX_NETWORK_LEN 255 + +#define MAX_IFC_BUF 2048 +static int volatile ifc_buf_len = MAX_IFC_BUF; +static int volatile ifc_last_len = 0; + +#define MASK_A 0x7F000000 +#define MASK_B 0xBFFF0000 +#define MASK_C 0xDFFFFF00 + +/* Get numeric value of the n bits starting at position p */ +#define getbits(x, p, n) ((x >> (p + 1 - n)) & ~(~0 << n)) + +#define EXPAND_LEADING_SLASH 0x0001 +#define EXPAND_TRAILING_SLASH 0x0002 +#define EXPAND_LEADING_DOT 0x0004 +#define EXPAND_TRAILING_DOT 0x0008 + +#define SELECTOR_HASH_SIZE 20 + +static struct sel sel_table[] = { + { SEL_ARCH, "arch", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_KARCH, "karch", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_OS, "os", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_OSVER, "osver", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_FULL_OS, "full_os", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_VENDOR, "vendor", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_HOST, "host", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_HOSTD, "hostd", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_XHOST, "xhost", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_DOMAIN, "domain", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_BYTE, "byte", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_CLUSTER, "cluster", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_NETGRP, "netgrp", SEL_FLAG_FUNC2|SEL_FLAG_BOOL, NULL }, + { SEL_NETGRPD, "netgrpd", SEL_FLAG_FUNC2|SEL_FLAG_BOOL, NULL }, + { SEL_IN_NETWORK, "in_network", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_IN_NETWORK, "netnumber", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_IN_NETWORK, "network", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_IN_NETWORK, "wire", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_UID, "uid", SEL_FLAG_MACRO|SEL_FLAG_NUM, NULL }, + { SEL_GID, "gid", SEL_FLAG_MACRO|SEL_FLAG_NUM, NULL }, + { SEL_KEY, "key", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_MAP, "map", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_PATH, "path", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_EXISTS, "exists", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_AUTODIR, "autodir", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_DOLLAR, "dollar", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL }, + { SEL_TRUE, "true", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, + { SEL_FALSE, "false", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL }, +}; +static unsigned int sel_count = sizeof(sel_table)/sizeof(struct sel); + +static struct sel *sel_hash[SELECTOR_HASH_SIZE]; +static unsigned int sel_hash_init_done = 0; +static pthread_mutex_t sel_hash_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct types { + char *type; + unsigned int len; +}; + +static struct types map_type[] = { + { "file", 4 }, + { "program", 7 }, + { "yp", 2 }, + { "nis", 3 }, + { "nisplus", 7 }, + { "ldap", 4 }, + { "ldaps", 5 }, + { "hesiod", 6 }, + { "userdir", 7 }, + { "hosts", 5 }, +}; +static unsigned int map_type_count = sizeof(map_type)/sizeof(struct types); + +static struct types format_type[] = { + { "sun", 3 }, + { "hesiod", 6 }, + { "amd", 3}, +}; +static unsigned int format_type_count = sizeof(format_type)/sizeof(struct types); + +static void sel_add(struct sel *sel) +{ + u_int32_t hval = hash(sel->name, SELECTOR_HASH_SIZE); + struct sel *old; + + old = sel_hash[hval]; + sel_hash[hval] = sel; + sel_hash[hval]->next = old; +} + +void sel_hash_init(void) +{ + int i; + + pthread_mutex_lock(&sel_hash_mutex); + if (sel_hash_init_done) { + pthread_mutex_unlock(&sel_hash_mutex); + return; + } + for (i = 0; i < SELECTOR_HASH_SIZE; i++) + sel_hash[i] = NULL; + + for (i = 0; i < sel_count; i++) + sel_add(&sel_table[i]); + + sel_hash_init_done = 1; + pthread_mutex_unlock(&sel_hash_mutex); +} + +struct sel *sel_lookup(const char *name) +{ + u_int32_t hval = hash(name, SELECTOR_HASH_SIZE); + struct sel *sel; + + pthread_mutex_lock(&sel_hash_mutex); + for (sel = sel_hash[hval]; sel != NULL; sel = sel->next) { + if (strcmp(name, sel->name) == 0) { + pthread_mutex_unlock(&sel_hash_mutex); + return sel; + } + } + pthread_mutex_unlock(&sel_hash_mutex); + return NULL; +} + +struct selector *get_selector(char *name) +{ + struct sel *sel; + + sel = sel_lookup(name); + if (sel) { + struct selector *new = malloc(sizeof(struct selector)); + if (!new) + return NULL; + memset(new, 0, sizeof(*new)); + new->sel = sel; + return new; + } + return NULL; +} + +void free_selector(struct selector *selector) +{ + struct selector *s = selector; + struct selector *next = s; + + while (s) { + next = s->next; + if (s->sel->flags & SEL_FREE_VALUE_MASK) + free(s->comp.value); + if (s->sel->flags & SEL_FREE_ARG1_MASK) + free(s->func.arg1); + if (s->sel->flags & SEL_FREE_ARG2_MASK) + free(s->func.arg2); + s = next; + } + return; +} + +static unsigned int ipv6_mask_cmp(uint32_t *host, uint32_t *iface, uint32_t *mask) +{ + unsigned int ret = 1; + unsigned int i; + + for (i = 0; i < 4; i++) { + if ((host[i] & mask[i]) != (iface[i] & mask[i])) { + ret = 0; + break; + } + } + return ret; +} + +unsigned int get_proximity(struct sockaddr *host_addr) +{ + struct ifaddrs *ifa = NULL; + struct ifaddrs *this; + struct sockaddr_in *addr, *msk_addr, *if_addr; + struct sockaddr_in6 *addr6, *msk6_addr, *if6_addr; + struct in_addr *hst_addr; + struct in6_addr *hst6_addr; + int addr_len; + char buf[MAX_ERR_BUF]; + uint32_t mask, ha, ia, *mask6, *ha6, *ia6; + int ret; + + addr = NULL; + addr6 = NULL; + hst_addr = NULL; + hst6_addr = NULL; + mask6 = NULL; + ha6 = NULL; + ia6 = NULL; + ha = 0; + + switch (host_addr->sa_family) { + case AF_INET: + addr = (struct sockaddr_in *) host_addr; + hst_addr = (struct in_addr *) &addr->sin_addr; + ha = ntohl((uint32_t) hst_addr->s_addr); + addr_len = sizeof(*hst_addr); + break; + + case AF_INET6: +#ifndef WITH_LIBTIRPC + return PROXIMITY_UNSUPPORTED; +#else + addr6 = (struct sockaddr_in6 *) host_addr; + hst6_addr = (struct in6_addr *) &addr6->sin6_addr; + ha6 = &hst6_addr->s6_addr32[0]; + addr_len = sizeof(*hst6_addr); + break; +#endif + + default: + return PROXIMITY_ERROR; + } + + ret = getifaddrs(&ifa); + if (ret) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr("getifaddrs: %s", estr); + return PROXIMITY_ERROR; + } + + this = ifa; + while (this) { + if (!(this->ifa_flags & IFF_UP) || + this->ifa_flags & IFF_POINTOPOINT || + this->ifa_addr == NULL) { + this = this->ifa_next; + continue; + } + + switch (this->ifa_addr->sa_family) { + case AF_INET: + if (host_addr->sa_family == AF_INET6) + break; + if_addr = (struct sockaddr_in *) this->ifa_addr; + ret = memcmp(&if_addr->sin_addr, hst_addr, addr_len); + if (!ret) { + freeifaddrs(ifa); + return PROXIMITY_LOCAL; + } + break; + + case AF_INET6: +#ifdef WITH_LIBTIRPC + if (host_addr->sa_family == AF_INET) + break; + if6_addr = (struct sockaddr_in6 *) this->ifa_addr; + ret = memcmp(&if6_addr->sin6_addr, hst6_addr, addr_len); + if (!ret) { + freeifaddrs(ifa); + return PROXIMITY_LOCAL; + } +#endif + default: + break; + } + this = this->ifa_next; + } + + this = ifa; + while (this) { + if (!(this->ifa_flags & IFF_UP) || + this->ifa_flags & IFF_POINTOPOINT || + this->ifa_addr == NULL) { + this = this->ifa_next; + continue; + } + + switch (this->ifa_addr->sa_family) { + case AF_INET: + if (host_addr->sa_family == AF_INET6) + break; + if_addr = (struct sockaddr_in *) this->ifa_addr; + ia = ntohl((uint32_t) if_addr->sin_addr.s_addr); + + /* Is the address within a localy attached subnet */ + + msk_addr = (struct sockaddr_in *) this->ifa_netmask; + mask = ntohl((uint32_t) msk_addr->sin_addr.s_addr); + + if ((ia & mask) == (ha & mask)) { + freeifaddrs(ifa); + return PROXIMITY_SUBNET; + } + + /* + * Is the address within a local ipv4 network. + * + * Bit position 31 == 0 => class A. + * Bit position 30 == 0 => class B. + * Bit position 29 == 0 => class C. + */ + + if (!getbits(ia, 31, 1)) + mask = MASK_A; + else if (!getbits(ia, 30, 1)) + mask = MASK_B; + else if (!getbits(ia, 29, 1)) + mask = MASK_C; + else + break; + + if ((ia & mask) == (ha & mask)) { + freeifaddrs(ifa); + return PROXIMITY_NET; + } + break; + + case AF_INET6: +#ifdef WITH_LIBTIRPC + if (host_addr->sa_family == AF_INET) + break; + if6_addr = (struct sockaddr_in6 *) this->ifa_addr; + ia6 = &if6_addr->sin6_addr.s6_addr32[0]; + + /* Is the address within the network of the interface */ + + msk6_addr = (struct sockaddr_in6 *) this->ifa_netmask; + mask6 = &msk6_addr->sin6_addr.s6_addr32[0]; + + if (ipv6_mask_cmp(ha6, ia6, mask6)) { + freeifaddrs(ifa); + return PROXIMITY_SUBNET; + } + + /* How do we define "local network" in ipv6? */ +#endif + default: + break; + } + this = this->ifa_next; + } + + freeifaddrs(ifa); + + return PROXIMITY_OTHER; +} + +static char *inet_fill_net(const char *net_num, char *net) +{ + char *np; + int dots = 3; + + if (strlen(net_num) > INET_ADDRSTRLEN) + return NULL; + + if (!isdigit(*net_num)) + return NULL; + + *net = '\0'; + strcpy(net, net_num); + + np = net; + while (*np++) { + if (*np == '.') { + np++; + dots--; + if (!*np && dots) + strcat(net, "0"); + continue; + } + + if ((*np && !isdigit(*np)) || dots < 0) { + *net = '\0'; + return NULL; + } + } + + while (dots--) + strcat(net, ".0"); + + return net; +} + +static char *get_network_number(const char *network) +{ + struct netent *netent; + char cnet[MAX_NETWORK_LEN]; + uint32_t h_net; + size_t len; + + len = strlen(network) + 1; + if (len > MAX_NETWORK_LEN) + return NULL; + + netent = getnetbyname(network); + if (!netent) + return NULL; + h_net = ntohl(netent->n_net); + + if (!inet_ntop(AF_INET, &h_net, cnet, INET_ADDRSTRLEN)) + return NULL; + + return strdup(cnet); +} + +unsigned int get_network_proximity(const char *name) +{ + struct addrinfo hints; + struct addrinfo *ni, *this; + char name_or_num[NI_MAXHOST + 1]; + unsigned int proximity; + char *net; + int ret; + + if (!name) + return PROXIMITY_ERROR; + + net = get_network_number(name); + if (net) { + strcpy(name_or_num, net); + free(net); + } else { + char this[NI_MAXHOST + 1]; + char *mask; + + if (strlen(name) > NI_MAXHOST) + return PROXIMITY_ERROR; + strcpy(this, name); + if ((mask = strchr(this, '/'))) + *mask++ = '\0'; + if (!strchr(this, '.')) + strcpy(name_or_num, this); + else { + char buf[NI_MAXHOST + 1], *new; + new = inet_fill_net(this, buf); + if (!new) + return PROXIMITY_ERROR; + strcpy(name_or_num, new); + } + } + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + ret = getaddrinfo(name_or_num, NULL, &hints, &ni); + if (ret) { + logerr("getaddrinfo: %s", gai_strerror(ret)); + return PROXIMITY_ERROR; + } + + proximity = PROXIMITY_OTHER; + + this = ni; + while (this) { + unsigned int prx = get_proximity(this->ai_addr); + if (prx < proximity) + proximity = prx; + this = this->ai_next; + } + freeaddrinfo(ni); + + return proximity; +} + +unsigned int in_network(char *network) +{ + unsigned int proximity = get_network_proximity(network); + if (proximity == PROXIMITY_ERROR || + proximity > PROXIMITY_SUBNET) + return 0; + return 1; +} + +struct mapent *match_cached_key(struct autofs_point *ap, + const char *err_prefix, + struct map_source *source, + const char *key) +{ + char buf[MAX_ERR_BUF]; + struct mapent_cache *mc; + struct mapent *me; + + mc = source->mc; + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + int ret; + + me = cache_lookup(mc, key); + /* + * Stale mapent => check for entry in alternate source or + * wildcard. Note, plus included direct mount map entries + * are included as an instance (same map entry cache), not + * in a distinct source. + */ + if (me && (!me->mapent || + (me->source != source && *me->key != '/'))) { + while ((me = cache_lookup_key_next(me))) + if (me->source == source) + break; + if (!me) + me = cache_lookup_distinct(mc, "*"); + } + + if (!me) + goto done; + + /* + * If this is a lookup add wildcard match for later validation + * checks and negative cache lookups. + */ + if (!(ap->flags & MOUNT_FLAG_REMOUNT) && + ap->type == LKP_INDIRECT && *me->key == '*') { + ret = cache_update(mc, source, key, me->mapent, me->age); + if (!(ret & (CHE_OK | CHE_UPDATED))) + me = NULL; + } + } else { + char *lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "%s strdup: %s", err_prefix, estr); + return NULL; + } + + /* If it's found we're done */ + me = cache_lookup_distinct(mc, lkp_key); + if (me) + goto free; + + /* + * Otherwise strip successive directory components and try + * a match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while (!me) { + char *prefix; + + while ((prefix = strrchr(lkp_key, '/'))) { + *prefix = '\0'; + me = cache_partial_match_wild(mc, lkp_key); + if (me) + goto free; + } + + me = cache_lookup_distinct(mc, "*"); + if (me) + goto free; + + break; + } +free: + free(lkp_key); + } +done: + return me; +} + +/* + * Skip whitespace in a string; if we hit a #, consider the rest of the + * entry a comment. + */ +const char *skipspace(const char *whence) +{ + while (1) { + switch (*whence) { + case ' ': + case '\b': + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + whence++; + break; + case '#': /* comment: skip to end of string */ + while (*whence != '\0') + whence++; + /* FALLTHROUGH */ + + default: + return whence; + } + } +} + +/* + * Check a string to see if a colon appears before the next '/'. + */ +int check_colon(const char *str) +{ + char *ptr = (char *) str; + + /* Colon escape */ + if (!strncmp(ptr, ":/", 2)) + return 1; + + while (*ptr && strncmp(ptr, ":/", 2)) + ptr++; + + if (!*ptr) + return 0; + + return 1; +} + +/* Get the length of a chunk delimitered by whitespace */ +int chunklen(const char *whence, int expect_colon) +{ + char *str = (char *) whence; + int n = 0; + int quote = 0; + + for (; *str; str++, n++) { + switch (*str) { + case '\\': + if( quote ) { + break; + } else { + quote = 1; + continue; + } + case '"': + if (quote) + break; + while (*str) { + str++; + n++; + if (*str == '"') + break; + if (!strncmp(str, ":/", 2)) + expect_colon = 0; + } + break; + case ':': + if (expect_colon && !strncmp(str, ":/", 2)) + expect_colon = 0; + continue; + case ' ': + case '\t': + /* Skip space or tab if we expect a colon */ + if (expect_colon) + continue; + case '\b': + case '\n': + case '\v': + case '\f': + case '\r': + case '\0': + if (!quote) + return n; + /* FALLTHROUGH */ + default: + break; + } + quote = 0; + } + + return n; +} + +/* + * Compare str with pat. Return 0 if compare equal or + * str is an abbreviation of pat of no less than mchr characters. + */ +int strmcmp(const char *str, const char *pat, int mchr) +{ + int nchr = 0; + + while (*str == *pat) { + if (!*str) + return 0; + str++; + pat++; + nchr++; + } + + if (!*str && nchr > mchr) + return 0; + + return *pat - *str; +} + +char *dequote(const char *str, int origlen, unsigned int logopt) +{ + char *ret = malloc(origlen + 1); + char *cp = ret; + const char *scp; + int len = origlen; + int quote = 0, dquote = 0; + int i, j; + + if (ret == NULL) + return NULL; + + /* first thing to do is strip white space from the end */ + i = len - 1; + while (isspace(str[i])) { + /* of course, we have to keep escaped white-space */ + j = i - 1; + if (j > 0 && (str[j] == '\\' || str[j] == '"')) + break; + i--; + len--; + } + + for (scp = str; len > 0 && *scp; scp++, len--) { + if (!quote) { + if (*scp == '"') { + if (dquote) + dquote = 0; + else + dquote = 1; + continue; + } + + if (!dquote) { + if (*scp == '\\') { + quote = 1; + continue; + } + } + } + quote = 0; + *cp++ = *scp; + } + *cp = '\0'; + + if (dquote) { + debug(logopt, "unmatched quote in %.*s", origlen, str); + free(ret); + return NULL; + } + + return ret; +} + +int span_space(const char *str, unsigned int maxlen) +{ + const char *p = str; + unsigned int len = 0; + + while (*p && !isblank(*p) && len < maxlen) { + if (*p == '"') { + while (*p++ && len++ < maxlen) { + if (*p == '"') + break; + } + } else if (*p == '\\') { + p += 2; + len += 2; + continue; + } + p++; + len++; + } + return len; +} + +char *sanitize_path(const char *path, int origlen, unsigned int type, unsigned int logopt) +{ + char *slash, *cp, *s_path; + const char *scp; + int len = origlen; + unsigned int seen_slash = 0, quote = 0, dquote = 0; + + if (type & (LKP_INDIRECT | LKP_DIRECT)) { + char *tmp = path; + + if (*tmp == '"') + tmp++; + slash = strchr(tmp, '/'); + if (slash) { + if (type == LKP_INDIRECT) + return NULL; + if (*tmp != '/') + return NULL; + } else { + if (type == LKP_DIRECT) + return NULL; + } + } + + s_path = malloc(origlen + 1); + if (!s_path) + return NULL; + + for (cp = s_path, scp = path; len > 0; scp++, len--) { + if (!quote) { + if (*scp == '"') { + if (dquote) + dquote = 0; + else + dquote = 1; + continue; + } + + if (!dquote) { + /* Badness in string - go away */ + if (*scp < 32) { + free(s_path); + return NULL; + } + + if (*scp == '\\') { + quote = 1; + continue; + } + } + + /* + * Not really proper but we get problems with + * paths with multiple slashes. The kernel + * compresses them so when we get a query there + * should be only single slashes. + */ + if (*scp == '/') { + if (seen_slash) + continue; + seen_slash = 1; + } else + seen_slash = 0; + } + quote = 0; + *cp++ = *scp; + } + *cp = '\0'; + + if (dquote) { + debug(logopt, "unmatched quote in %.*s", origlen, path); + free(s_path); + return NULL; + } + + /* Remove trailing / but watch out for a quoted / alone */ + if (strlen(cp) > 1 && origlen > 1 && *(cp - 1) == '/') + *(cp - 1) = '\0'; + + return s_path; +} + +static char *hasopt(const char *str, const char *opt) +{ + const size_t optlen = strlen(opt); + char *rest = (char *) str, *p; + + while ((p = strstr(rest, opt)) != NULL) { + if ((p == rest || p[-1] == ',') && + (p[optlen] == '\0' || p[optlen] == '=' || + p[optlen] == ',')) + return p; + + rest = strchr (p, ','); + if (rest == NULL) + break; + ++rest; + } + + return NULL; +} + +char *merge_options(const char *opt1, const char *opt2) +{ + char str[MAX_OPTIONS_LEN + 1]; + char result[MAX_OPTIONS_LEN + 1]; + char neg[MAX_OPTION_LEN + 1]; + char *tok, *ptr = NULL; + size_t resultlen, len; + + if ((!opt1 || !*opt1) && (!opt2 || !*opt2)) + return NULL; + + if (!opt2 || !*opt2) { + if (!*opt1) + return NULL; + return strdup(opt1); + } + + if (!opt1 || !*opt1) { + if (!*opt2) + return NULL; + return strdup(opt2); + } + + if (!strcmp(opt1, opt2)) + return strdup(opt1); + + if (strlen(str) > MAX_OPTIONS_LEN) + return NULL; + memset(result, 0, sizeof(result)); + strcpy(str, opt1); + + resultlen = 0; + tok = strtok_r(str, ",", &ptr); + while (tok) { + const char *this = (const char *) tok; + char *eq = strchr(this, '='); + if (eq) { + *eq = '\0'; + if (!hasopt(opt2, this)) { + if (resultlen + strlen(this) > MAX_OPTIONS_LEN) + return NULL; + *eq = '='; + if (!*result) + strcpy(result, this); + else + strcat(result, this); + strcat(result, ","); + resultlen += strlen(this) + 1; + goto next; + } + } + + if (!strcmp(this, "rw") && hasopt(opt2, "ro")) + goto next; + if (!strcmp(this, "ro") && hasopt(opt2, "rw")) + goto next; + if (!strcmp(this, "bg") && hasopt(opt2, "fg")) + goto next; + if (!strcmp(this, "fg") && hasopt(opt2, "bg")) + goto next; + if (!strcmp(this, "bg") && hasopt(opt2, "fg")) + goto next; + if (!strcmp(this, "soft") && hasopt(opt2, "hard")) + goto next; + if (!strcmp(this, "hard") && hasopt(opt2, "soft")) + goto next; + + if (!strncmp(this, "no", 2)) { + if (strlen(this + 2) > MAX_OPTION_LEN) + return NULL; + strcpy(neg, this + 2); + if (hasopt(opt2, neg)) + goto next; + } else { + if ((strlen(this) + 2) > MAX_OPTION_LEN) + return NULL; + strcpy(neg, "no"); + strcat(neg, this); + if (hasopt(opt2, neg)) + goto next; + } + + if (hasopt(opt2, tok)) + goto next; + + if (resultlen + strlen(this) + 1 > MAX_OPTIONS_LEN) + return NULL; + + if (!*result) + strcpy(result, this); + else + strcat(result, this); + strcat(result, ","); + resultlen =+ strlen(this) + 1; +next: + tok = strtok_r(NULL, ",", &ptr); + } + + if (resultlen + strlen(opt2) > MAX_OPTIONS_LEN) + return NULL; + + if (!*result) + strcpy(result, opt2); + else + strcat(result, opt2); + + len = strlen(result); + if (len && result[len - 1] == ',') + result[len - 1] = '\0'; + + return strdup(result); +} + +static char *expand_slash_or_dot(char *str, unsigned int type) +{ + char *val = NULL; + + if (!str) + return NULL; + + if (!type) + return str; + + if (type & EXPAND_LEADING_SLASH) + val = basename(str); + else if (type & EXPAND_TRAILING_SLASH) + val = dirname(str); + else if (type & (EXPAND_LEADING_DOT | EXPAND_TRAILING_DOT)) { + char *dot = strchr(str, '.'); + if (dot) + *dot++ = '\0'; + if (type & EXPAND_LEADING_DOT) + val = dot; + else + val = str; + } + + return val; +} + +/* + * $-expand an amd-style map entry and return the length of the entry. + * If "dst" is NULL, just count the length. + */ +/* TODO: how should quoting be handled? */ +int expandamdent(const char *src, char *dst, const struct substvar *svc) +{ + unsigned int flags = conf_amd_get_flags(NULL); + const struct substvar *sv; + const char *o_src = src; + int len, l; + const char *p; + char ch; + + len = 0; + + while ((ch = *src++)) { + switch (ch) { + case '$': + if (*src == '{') { + char *start, *end; + unsigned int type = 0; + p = strchr(++src, '}'); + if (!p) { + /* Ignore rest of string */ + if (dst) + *dst = '\0'; + return len; + } + start = (char *) src; + if (*src == '/' || *src == '.') { + start++; + type = EXPAND_LEADING_SLASH; + if (*src == '.') + type = EXPAND_LEADING_DOT; + } + end = (char *) p; + if (*(p - 1) == '/' || *(p - 1) == '.') { + end--; + type = EXPAND_TRAILING_SLASH; + if (*(p - 1) == '.') + type = EXPAND_TRAILING_DOT; + } + sv = macro_findvar(svc, start, end - start); + if (sv) { + char *val; + char *str = strdup(sv->val); + val = expand_slash_or_dot(str, type); + if (!val) + val = sv->val; + l = strlen(val); + if (dst) { + if (*dst) + strcat(dst, val); + else + strcpy(dst, val); + dst += l; + } + len += l; + if (str) + free(str); + } else { + if (dst) { + *dst++ = ch; + *dst++ = '{'; + strncat(dst, src, p - src); + dst += (p - src); + *dst++ = '}'; + } + len += 1 + 1 + (p - src) + 1; + } + src = p + 1; + } else { + if (dst) + *(dst++) = ch; + len++; + } + break; + + case '\\': + if (!(flags & CONF_NORMALIZE_SLASHES)) { + len++; + if (dst) + *dst++ = ch; + break; + } + + if (*src) { + len++; + if (dst) + *dst++ = *src; + src++; + } + break; + + case '/': + len++; + if (dst) + *dst++ = ch; + + if (!(flags & CONF_NORMALIZE_SLASHES)) + break; + + /* Double slash at start is allowed */ + if (src == (o_src + 1) && *src == '/') { + len++; + if (dst) + *dst++ = *src; + src++; + } + while (*src == '/') + src++; + break; + + case '"': + len++; + if (dst) + *dst++ = ch; + + while (*src && *src != '"') { + len++; + if (dst) + *dst++ = *src; + src++; + } + if (*src) { + len++; + if (dst) + *dst++ = *src; + src++; + } + break; + + default: + if (dst) + *(dst++) = ch; + len++; + break; + } + } + if (dst) + *dst = '\0'; + + return len; +} + +int expand_selectors(struct autofs_point *ap, + const char *mapstr, char **pmapstr, + struct substvar *sv) +{ + char buf[MAX_ERR_BUF]; + char *expand; + size_t len; + + if (!mapstr) + return 0; + + len = expandamdent(mapstr, NULL, sv); + if (len == 0) { + error(ap->logopt, "failed to expand map entry"); + return 0; + } + + expand = malloc(len + 1); + if (!expand) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + return 0; + } + memset(expand, 0, len + 1); + + expandamdent(mapstr, expand, sv); + + *pmapstr = expand; + + return len; +} + +void free_map_type_info(struct map_type_info *info) +{ + if (info->type) + free(info->type); + if (info->format) + free(info->format); + if (info->map) + free(info->map); + free(info); + return; +} + +struct map_type_info *parse_map_type_info(const char *str) +{ + struct map_type_info *info; + char *buf, *type, *fmt, *map, *tmp; + char *pos; + + buf = strdup(str); + if (!buf) + return NULL; + + info = malloc(sizeof(struct map_type_info)); + if (!info) { + free(buf); + return NULL; + } + memset(info, 0, sizeof(struct map_type_info)); + + type = fmt = map = NULL; + + tmp = strchr(buf, ':'); + if (!tmp) { + pos = buf; + while (*pos == ' ') + *pos++ = '\0'; + map = pos; + } else { + int i, j; + + for (i = 0; i < map_type_count; i++) { + char *m_type = map_type[i].type; + unsigned int m_len = map_type[i].len; + + pos = buf; + + if (strncmp(m_type, pos, m_len)) + continue; + + type = pos; + pos += m_len; + + if (*pos == ' ' || *pos == ':') { + while (*pos == ' ') + *pos++ = '\0'; + if (*pos != ':') { + free(buf); + free(info); + return NULL; + } else { + *pos++ = '\0'; + while (*pos && *pos == ' ') + *pos++ = '\0'; + map = pos; + break; + } + } + + if (*pos == ',') { + *pos++ = '\0'; + for (j = 0; j < format_type_count; j++) { + char *f_type = format_type[j].type; + unsigned int f_len = format_type[j].len; + + if (strncmp(f_type, pos, f_len)) + continue; + + fmt = pos; + pos += f_len; + + if (*pos == ' ' || *pos == ':') { + while (*pos == ' ') + *pos++ = '\0'; + if (*pos != ':') { + free(buf); + free(info); + return NULL; + } else { + *pos++ = '\0'; + while (*pos && *pos == ' ') + *pos++ = '\0'; + map = pos; + break; + } + } + } + } + } + + if (!type) { + pos = buf; + while (*pos == ' ') + *pos++ = '\0'; + map = pos; + } + } + + /* Look for space terminator - ignore local options */ + for (tmp = buf; *tmp; tmp++) { + if (*tmp == ' ') { + *tmp = '\0'; + break; + } + if (*tmp == '\\') + tmp++; + } + + if (type) { + info->type = strdup(type); + if (!info->type) { + free(buf); + free_map_type_info(info); + return NULL; + } + } + + if (fmt) { + info->format = strdup(fmt); + if (!info->format) { + free(buf); + free_map_type_info(info); + return NULL; + } + } + + if (map) { + info->map = strdup(map); + if (!info->map) { + free(buf); + free_map_type_info(info); + return NULL; + } + } + + free(buf); + + return info; +} + diff --git a/lib/rpc_subs.c b/lib/rpc_subs.c new file mode 100644 index 0000000..386c7ba --- /dev/null +++ b/lib/rpc_subs.c @@ -0,0 +1,1344 @@ +/* ----------------------------------------------------------------------- * + * + * rpc_subs.c - routines for rpc discovery + * + * Copyright 2004 Ian Kent - All Rights Reserved + * Copyright 2004 Jeff Moyer - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_LIBTIRPC +const rpcprog_t rpcb_prog = RPCBPROG; +const rpcvers_t rpcb_version = RPCBVERS; +#else +const rpcprog_t rpcb_prog = PMAPPROG; +const rpcvers_t rpcb_version = PMAPVERS; +#endif + +#include "mount.h" +#include "rpc_subs.h" +#include "automount.h" + +/* #define STANDALONE */ +#ifdef STANDALONE +#define error(logopt, msg, args...) fprintf(stderr, msg "\n", ##args) +#else +#include "log.h" +#endif + +#define MAX_IFC_BUF 1024 +#define MAX_ERR_BUF 128 + +#define MAX_NETWORK_LEN 255 + +/* Get numeric value of the n bits starting at position p */ +#define getbits(x, p, n) ((x >> (p + 1 - n)) & ~(~0 << n)) + +static const rpcvers_t mount_vers[] = { + MOUNTVERS_NFSV3, + MOUNTVERS_POSIX, + MOUNTVERS, +}; + +static int connect_nb(int, struct sockaddr *, socklen_t, struct timeval *); + +/* + * Perform a non-blocking connect on the socket fd. + * + * The input struct timeval always has tv_nsec set to zero, + * we only ever use tv_sec for timeouts. + */ +static int connect_nb(int fd, struct sockaddr *addr, socklen_t len, struct timeval *tout) +{ + struct pollfd pfd[1]; + int timeout = tout->tv_sec; + int flags, ret; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return -errno; + + ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (ret < 0) + return -errno; + + /* + * From here on subsequent sys calls could change errno so + * we set ret = -errno to capture it in case we decide to + * use it later. + */ + ret = connect(fd, addr, len); + if (ret < 0 && errno != EINPROGRESS) { + ret = -errno; + goto done; + } + + if (ret == 0) + goto done; + + if (timeout != -1) { + if (timeout >= (INT_MAX - 1)/1000) + timeout = INT_MAX - 1; + else + timeout = timeout * 1000; + } + + pfd[0].fd = fd; + pfd[0].events = POLLOUT; + + ret = poll(pfd, 1, timeout); + if (ret <= 0) { + if (ret == 0) + ret = -ETIMEDOUT; + else + ret = -errno; + goto done; + } + + if (pfd[0].revents) { + int status; + + len = sizeof(ret); + status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len); + if (status < 0) { + char buf[MAX_ERR_BUF + 1]; + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + + /* + * We assume getsockopt amounts to a read on the + * descriptor and gives us the errno we need for + * the POLLERR revent case. + */ + ret = -errno; + + /* Unexpected case, log it so we know we got caught */ + if (pfd[0].revents & POLLNVAL) + logerr("unexpected poll(2) error on connect:" + " %s", estr); + + goto done; + } + + /* Oops - something wrong with connect */ + if (ret) + ret = -ret; + } + +done: + fcntl(fd, F_SETFL, flags); + return ret; +} + +#ifndef WITH_LIBTIRPC +static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, int *fd, CLIENT **client) +{ + CLIENT *clnt = NULL; + struct sockaddr_in in4_laddr; + struct sockaddr_in *in4_raddr; + int type, proto, ret; + socklen_t slen; + + *client = NULL; + + proto = info->proto; + if (proto == IPPROTO_UDP) + type = SOCK_DGRAM; + else + type = SOCK_STREAM; + + /* + * bind to any unused port. If we left this up to the rpc + * layer, it would bind to a reserved port, which has been shown + * to exhaust the reserved port range in some situations. + */ + in4_laddr.sin_family = AF_INET; + in4_laddr.sin_port = htons(0); + in4_laddr.sin_addr.s_addr = htonl(INADDR_ANY); + slen = sizeof(struct sockaddr_in); + + if (!info->client) { + struct sockaddr *laddr; + + *fd = open_sock(addr->sa_family, type, proto); + if (*fd < 0) + return -errno; + + laddr = (struct sockaddr *) &in4_laddr; + if (bind(*fd, laddr, slen) < 0) + return -errno; + } + + in4_raddr = (struct sockaddr_in *) addr; + in4_raddr->sin_port = htons(info->port); + + switch (info->proto) { + case IPPROTO_UDP: + clnt = clntudp_bufcreate(in4_raddr, + info->program, info->version, + info->timeout, fd, + info->send_sz, info->recv_sz); + break; + + case IPPROTO_TCP: + ret = connect_nb(*fd, addr, slen, &info->timeout); + if (ret < 0) + return ret; + + clnt = clnttcp_create(in4_raddr, + info->program, info->version, fd, + info->send_sz, info->recv_sz); + break; + + default: + break; + } + + *client = clnt; + + return 0; +} +static int rpc_getport(struct conn_info *info, + struct pmap *parms, CLIENT *client, + unsigned short *port) +{ + enum clnt_stat status; + + /* + * Check to see if server is up otherwise a getport will take + * forever to timeout. + */ + status = clnt_call(client, PMAPPROC_NULL, + (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_void, 0, + info->timeout); + + if (status == RPC_SUCCESS) { + status = clnt_call(client, PMAPPROC_GETPORT, + (xdrproc_t) xdr_pmap, (caddr_t) parms, + (xdrproc_t) xdr_u_short, (caddr_t) port, + info->timeout); + } + + return status; +} +#else +static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, int *fd, CLIENT **client) +{ + CLIENT *clnt = NULL; + struct sockaddr_in in4_laddr; + struct sockaddr_in6 in6_laddr; + struct sockaddr *laddr = NULL; + struct netbuf nb_addr; + int type, proto; + size_t slen; + int ret; + + *client = NULL; + + proto = info->proto; + if (proto == IPPROTO_UDP) + type = SOCK_DGRAM; + else + type = SOCK_STREAM; + + /* + * bind to any unused port. If we left this up to the rpc + * layer, it would bind to a reserved port, which has been shown + * to exhaust the reserved port range in some situations. + */ + if (addr->sa_family == AF_INET) { + struct sockaddr_in *in4_raddr = (struct sockaddr_in *) addr; + in4_laddr.sin_family = AF_INET; + in4_laddr.sin_port = htons(0); + in4_laddr.sin_addr.s_addr = htonl(INADDR_ANY); + laddr = (struct sockaddr *) &in4_laddr; + in4_raddr->sin_port = htons(info->port); + slen = sizeof(struct sockaddr_in); + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *in6_raddr = (struct sockaddr_in6 *) addr; + in6_laddr.sin6_family = AF_INET6; + in6_laddr.sin6_port = htons(0); + in6_laddr.sin6_addr = in6addr_any; + laddr = (struct sockaddr *) &in6_laddr; + in6_raddr->sin6_port = htons(info->port); + slen = sizeof(struct sockaddr_in6); + } else + return -EINVAL; + + /* + * bind to any unused port. If we left this up to the rpc layer, + * it would bind to a reserved port, which has been shown to + * exhaust the reserved port range in some situations. + */ + if (!info->client) { + *fd = open_sock(addr->sa_family, type, proto); + if (*fd < 0) { + ret = -errno; + return ret; + } + + if (bind(*fd, laddr, slen) < 0) { + ret = -errno; + return ret; + } + } + + nb_addr.maxlen = nb_addr.len = slen; + nb_addr.buf = addr; + + if (info->proto == IPPROTO_UDP) + clnt = clnt_dg_create(*fd, &nb_addr, + info->program, info->version, + info->send_sz, info->recv_sz); + else if (info->proto == IPPROTO_TCP) { + ret = connect_nb(*fd, addr, slen, &info->timeout); + if (ret < 0) + return ret; + clnt = clnt_vc_create(*fd, &nb_addr, + info->program, info->version, + info->send_sz, info->recv_sz); + } else + return -EINVAL; + + /* Our timeout is in seconds */ + if (clnt && info->timeout.tv_sec) + clnt_control(clnt, CLSET_TIMEOUT, (void *) &info->timeout); + + *client = clnt; + + return 0; +} + +/* + * Thankfully nfs-utils had already dealt with this. + * Thanks to Chuck Lever for his nfs-utils patch series, much of + * which is used here. + */ +static pthread_mutex_t proto_mutex = PTHREAD_MUTEX_INITIALIZER; + +static enum clnt_stat rpc_get_netid(const sa_family_t family, + const int protocol, char **netid) +{ + char *nc_protofmly, *nc_proto, *nc_netid; + struct netconfig *nconf; + struct protoent *proto; + void *handle; + + switch (family) { + case AF_LOCAL: + case AF_INET: + nc_protofmly = NC_INET; + break; + case AF_INET6: + nc_protofmly = NC_INET6; + break; + default: + return RPC_UNKNOWNPROTO; + } + + pthread_mutex_lock(&proto_mutex); + proto = getprotobynumber(protocol); + if (!proto) { + pthread_mutex_unlock(&proto_mutex); + return RPC_UNKNOWNPROTO; + } + nc_proto = strdup(proto->p_name); + pthread_mutex_unlock(&proto_mutex); + if (!nc_proto) + return RPC_SYSTEMERROR; + + handle = setnetconfig(); + while ((nconf = getnetconfig(handle)) != NULL) { + if (nconf->nc_protofmly != NULL && + strcmp(nconf->nc_protofmly, nc_protofmly) != 0) + continue; + if (nconf->nc_proto != NULL && + strcmp(nconf->nc_proto, nc_proto) != 0) + continue; + + nc_netid = strdup(nconf->nc_netid); + if (!nc_netid) { + free(nc_proto); + return RPC_SYSTEMERROR; + } + + *netid = nc_netid; + } + endnetconfig(handle); + free(nc_proto); + + return RPC_SUCCESS; +} + +static char *rpc_sockaddr2universal(const struct sockaddr *addr) +{ + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr; + const struct sockaddr_un *sun = (const struct sockaddr_un *) addr; + const struct sockaddr_in *sin = (const struct sockaddr_in *) addr; + char buf[INET6_ADDRSTRLEN + 8 /* for port information */]; + uint16_t port; + size_t count; + char *result; + int len; + + switch (addr->sa_family) { + case AF_LOCAL: + return strndup(sun->sun_path, sizeof(sun->sun_path)); + case AF_INET: + if (inet_ntop(AF_INET, (const void *)&sin->sin_addr.s_addr, + buf, (socklen_t)sizeof(buf)) == NULL) + goto out_err; + port = ntohs(sin->sin_port); + break; + case AF_INET6: + if (inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr, + buf, (socklen_t)sizeof(buf)) == NULL) + goto out_err; + port = ntohs(sin6->sin6_port); + break; + default: + goto out_err; + } + + count = sizeof(buf) - strlen(buf); + len = snprintf(buf + strlen(buf), count, ".%u.%u", + (unsigned)(port >> 8), (unsigned)(port & 0xff)); + /* before glibc 2.0.6, snprintf(3) could return -1 */ + if (len < 0 || (size_t)len > count) + goto out_err; + + result = strdup(buf); + return result; + +out_err: + return NULL; +} + +static int rpc_universal2port(const char *uaddr) +{ + char *addrstr; + char *p, *endptr; + unsigned long portlo, porthi; + int port = -1; + + addrstr = strdup(uaddr); + if (!addrstr) + return -1; + + p = strrchr(addrstr, '.'); + if (!p) + goto out; + + portlo = strtoul(p + 1, &endptr, 10); + if (*endptr != '\0' || portlo > 255) + goto out; + *p = '\0'; + + p = strrchr(addrstr, '.'); + if (!p) + goto out; + + porthi = strtoul(p + 1, &endptr, 10); + if (*endptr != '\0' || porthi > 255) + goto out; + *p = '\0'; + + port = (porthi << 8) | portlo; + +out: + free(addrstr); + return port; +} + +static enum clnt_stat rpc_rpcb_getport(CLIENT *client, + struct rpcb *parms, + struct timeval timeout, + unsigned short *port) +{ + rpcvers_t rpcb_version; + struct rpc_err rpcerr; + int s_port = 0; + + for (rpcb_version = RPCBVERS_4; + rpcb_version >= RPCBVERS_3; + rpcb_version--) { + enum clnt_stat status; + char *uaddr = NULL; + + CLNT_CONTROL(client, CLSET_VERS, (void *) &rpcb_version); + status = CLNT_CALL(client, (rpcproc_t) RPCBPROC_GETADDR, + (xdrproc_t) xdr_rpcb, (void *) parms, + (xdrproc_t) xdr_wrapstring, (void *) &uaddr, + timeout); + + switch (status) { + case RPC_SUCCESS: + if ((uaddr == NULL) || (uaddr[0] == '\0')) + return RPC_PROGNOTREGISTERED; + + s_port = rpc_universal2port(uaddr); + xdr_free((xdrproc_t) xdr_wrapstring, (char *) &uaddr); + if (s_port == -1) { + return RPC_N2AXLATEFAILURE; + } + *port = s_port; + return RPC_SUCCESS; + + case RPC_PROGVERSMISMATCH: + clnt_geterr(client, &rpcerr); + if (rpcerr.re_vers.low > RPCBVERS4) + return status; + continue; + + case RPC_PROGUNAVAIL: + continue; + + case RPC_PROGNOTREGISTERED: + continue; + + default: + /* Most likely RPC_TIMEDOUT or RPC_CANTRECV */ + return status; + } + } + + return RPC_PROGNOTREGISTERED; +} + +static enum clnt_stat rpc_getport(struct conn_info *info, + struct pmap *parms, CLIENT *client, + unsigned short *port) +{ + enum clnt_stat status; + struct sockaddr *paddr, addr; + struct rpcb rpcb_parms; + char *netid, *raddr; + + if (info->addr) + paddr = info->addr; + else { + if (!clnt_control(client, CLGET_SERVER_ADDR, (char *) &addr)) + return RPC_UNKNOWNADDR; + paddr = &addr; + } + + netid = NULL; + status = rpc_get_netid(paddr->sa_family, info->proto, &netid); + if (status != RPC_SUCCESS) + return status; + + raddr = rpc_sockaddr2universal(paddr); + if (!raddr) { + free(netid); + return RPC_UNKNOWNADDR; + } + + memset(&rpcb_parms, 0, sizeof(rpcb_parms)); + rpcb_parms.r_prog = parms->pm_prog; + rpcb_parms.r_vers = parms->pm_vers; + rpcb_parms.r_netid = netid; + rpcb_parms.r_addr = raddr; + rpcb_parms.r_owner = ""; + + status = rpc_rpcb_getport(client, &rpcb_parms, info->timeout, port); + + free(netid); + free(raddr); + + if (status == RPC_PROGNOTREGISTERED) { + /* Last chance, version 2 uses a different procedure */ + rpcvers_t rpcb_version = PMAPVERS; + CLNT_CONTROL(client, CLSET_VERS, (void *) &rpcb_version); + status = clnt_call(client, PMAPPROC_GETPORT, + (xdrproc_t) xdr_pmap, (caddr_t) parms, + (xdrproc_t) xdr_u_short, (caddr_t) port, + info->timeout); + } + + return status; +} +#endif + +#if defined(HAVE_GETRPCBYNAME) || defined(HAVE_GETSERVBYNAME) +static pthread_mutex_t rpcb_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +static rpcprog_t rpc_getrpcbyname(const rpcprog_t program) +{ +#ifdef HAVE_GETRPCBYNAME + static const char *rpcb_pgmtbl[] = { + "rpcbind", "portmap", "portmapper", "sunrpc", NULL, + }; + struct rpcent *entry; + rpcprog_t prog_number; + unsigned int i; + + pthread_mutex_lock(&rpcb_mutex); + for (i = 0; rpcb_pgmtbl[i] != NULL; i++) { + entry = getrpcbyname(rpcb_pgmtbl[i]); + if (entry) { + prog_number = entry->r_number; + pthread_mutex_unlock(&rpcb_mutex); + return prog_number; + } + } + pthread_mutex_unlock(&rpcb_mutex); +#endif + return program; +} + +static unsigned short rpc_getrpcbport(const int proto) +{ +#ifdef HAVE_GETSERVBYNAME + static const char *rpcb_netnametbl[] = { + "rpcbind", "portmapper", "sunrpc", NULL, + }; + struct servent *entry; + struct protoent *p_ent; + unsigned short port; + unsigned int i; + + pthread_mutex_lock(&rpcb_mutex); + p_ent = getprotobynumber(proto); + if (!p_ent) + goto done; + for (i = 0; rpcb_netnametbl[i] != NULL; i++) { + entry = getservbyname(rpcb_netnametbl[i], p_ent->p_name); + if (entry) { + port = entry->s_port; + pthread_mutex_unlock(&rpcb_mutex); + return port; + } + } +done: + pthread_mutex_unlock(&rpcb_mutex); +#endif + return (unsigned short) htons(PMAPPORT); +} + +/* + * Create an RPC client + */ +static int create_client(struct conn_info *info, CLIENT **client) +{ + struct addrinfo *ai, *haddr; + struct addrinfo hints; + int fd, ret; + + fd = RPC_ANYSOCK; + *client = NULL; + + if (info->client) { + if (clnt_control(info->client, CLGET_FD, (char *) &fd)) + clnt_control(info->client, CLSET_FD_NCLOSE, NULL); + else + fd = RPC_ANYSOCK; + clnt_destroy(info->client); + info->client = NULL; + } + + if (info->addr) { + ret = rpc_do_create_client(info->addr, info, &fd, client); + if (ret == 0) + goto done; + if (ret == -EHOSTUNREACH) + goto out_close; + if (ret == -EINVAL) { + char buf[MAX_ERR_BUF]; + char *estr = strerror_r(-ret, buf, MAX_ERR_BUF); + error(LOGOPT_ANY, "connect() failed: %s", estr); + goto out_close; + } + + if (fd != RPC_ANYSOCK) { + close(fd); + fd = RPC_ANYSOCK; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = AF_UNSPEC; + if (info->proto == IPPROTO_UDP) + hints.ai_socktype = SOCK_DGRAM; + else + hints.ai_socktype = SOCK_STREAM; + + ret = getaddrinfo(info->host, NULL, &hints, &ai); + if (ret) { + error(LOGOPT_ANY, + "hostname lookup failed: %s", gai_strerror(ret)); + goto out_close; + } + + haddr = ai; + while (haddr) { + if (haddr->ai_protocol != info->proto) { + haddr = haddr->ai_next; + continue; + } + + ret = rpc_do_create_client(haddr->ai_addr, info, &fd, client); + if (ret == 0) + break; + if (ret == -EHOSTUNREACH) { + freeaddrinfo(ai); + goto out_close; + } + + if (fd != RPC_ANYSOCK) { + close(fd); + fd = RPC_ANYSOCK; + } + + haddr = haddr->ai_next; + } + + freeaddrinfo(ai); + +done: + if (!*client) { + ret = -ENOTCONN; + goto out_close; + } + + /* Close socket fd on destroy, as is default for rpcowned fds */ + if (!clnt_control(*client, CLSET_FD_CLOSE, NULL)) { + clnt_destroy(*client); + ret = -ENOTCONN; + goto out_close; + } + + return 0; + +out_close: + if (fd != RPC_ANYSOCK) + close(fd); + return ret; +} + +int rpc_udp_getclient(struct conn_info *info, + unsigned int program, unsigned int version) +{ + CLIENT *client; + int ret; + + if (!info->client) { + info->proto = IPPROTO_UDP; + info->timeout.tv_sec = RPC_TOUT_UDP; + info->timeout.tv_usec = 0; + info->send_sz = UDPMSGSIZE; + info->recv_sz = UDPMSGSIZE; + } + + info->program = program; + info->version = version; + + ret = create_client(info, &client); + if (ret < 0) + return ret; + + info->client = client; + + return 0; +} + +void rpc_destroy_udp_client(struct conn_info *info) +{ + if (!info->client) + return; + + clnt_destroy(info->client); + info->client = NULL; + return; +} + +int rpc_tcp_getclient(struct conn_info *info, + unsigned int program, unsigned int version) +{ + CLIENT *client; + int ret; + + if (!info->client) { + info->proto = IPPROTO_TCP; + info->timeout.tv_sec = RPC_TOUT_TCP; + info->timeout.tv_usec = 0; + info->send_sz = 0; + info->recv_sz = 0; + } + + info->program = program; + info->version = version; + + ret = create_client(info, &client); + if (ret < 0) + return ret; + + info->client = client; + + return 0; +} + +void rpc_destroy_tcp_client(struct conn_info *info) +{ + struct linger lin = { 1, 0 }; + socklen_t lin_len = sizeof(struct linger); + int fd; + + if (!info->client) + return; + + if (!clnt_control(info->client, CLGET_FD, (char *) &fd)) + fd = -1; + + switch (info->close_option) { + case RPC_CLOSE_NOLINGER: + if (fd >= 0) + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, lin_len); + break; + } + + clnt_destroy(info->client); + info->client = NULL; + + return; +} + +int rpc_portmap_getclient(struct conn_info *info, + const char *host, struct sockaddr *addr, size_t addr_len, + int proto, unsigned int option) +{ + CLIENT *client; + int ret; + + info->host = host; + info->addr = addr; + info->addr_len = addr_len; + info->program = rpc_getrpcbyname(rpcb_prog); + info->port = ntohs(rpc_getrpcbport(proto)); + /* + * When using libtirpc we might need to change the rpcbind version + * to qurey AF_INET addresses. Since we might not have an address + * yet set AF_INET rpcbind version in rpc_do_create_client() when + * we always have an address. + */ + info->version = rpcb_version; + info->proto = proto; + info->send_sz = RPCSMALLMSGSIZE; + info->recv_sz = RPCSMALLMSGSIZE; + info->timeout.tv_sec = PMAP_TOUT_UDP; + info->timeout.tv_usec = 0; + info->close_option = option; + info->client = NULL; + + if (info->proto == IPPROTO_TCP) + info->timeout.tv_sec = PMAP_TOUT_TCP; + + ret = create_client(info, &client); + if (ret < 0) + return ret; + + info->client = client; + + return 0; +} + +int rpc_portmap_getport(struct conn_info *info, + struct pmap *parms, unsigned short *port) +{ + struct conn_info pmap_info; + CLIENT *client; + enum clnt_stat status; + int proto = info->proto; + int ret; + + memset(&pmap_info, 0, sizeof(struct conn_info)); + + pmap_info.proto = proto; + + if (proto == IPPROTO_TCP) + pmap_info.timeout.tv_sec = PMAP_TOUT_TCP; + else + pmap_info.timeout.tv_sec = PMAP_TOUT_UDP; + + if (info->client) + client = info->client; + else { + pmap_info.host = info->host; + pmap_info.addr = info->addr; + pmap_info.addr_len = info->addr_len; + pmap_info.port = ntohs(rpc_getrpcbport(info->proto)); + pmap_info.program = rpc_getrpcbyname(rpcb_prog); + /* + * When using libtirpc we might need to change the rpcbind + * version to qurey AF_INET addresses. Since we might not + * have an address yet set AF_INET rpcbind version in + * rpc_do_create_client() when we always have an address. + */ + pmap_info.version = rpcb_version; + pmap_info.proto = info->proto; + pmap_info.send_sz = RPCSMALLMSGSIZE; + pmap_info.recv_sz = RPCSMALLMSGSIZE; + + ret = create_client(&pmap_info, &client); + if (ret < 0) + return ret; + } + + status = rpc_getport(&pmap_info, parms, client, port); + + if (!info->client) { + /* + * Only play with the close options if we think it + * completed OK + */ + if (proto == IPPROTO_TCP && status == RPC_SUCCESS) { + struct linger lin = { 1, 0 }; + socklen_t lin_len = sizeof(struct linger); + int fd; + + if (!clnt_control(client, CLGET_FD, (char *) &fd)) + fd = -1; + + switch (info->close_option) { + case RPC_CLOSE_NOLINGER: + if (fd >= 0) + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, lin_len); + break; + } + } + clnt_destroy(client); + } + + if (status == RPC_TIMEDOUT) + return -ETIMEDOUT; + else if (status != RPC_SUCCESS) + return -EIO; + + return 0; +} + +int rpc_ping_proto(struct conn_info *info) +{ + CLIENT *client; + enum clnt_stat status; + int proto = info->proto; + int ret; + + if (info->client) + client = info->client; + else { + if (info->proto == IPPROTO_UDP) { + info->send_sz = UDPMSGSIZE; + info->recv_sz = UDPMSGSIZE; + } + ret = create_client(info, &client); + if (ret < 0) + return ret; + } + + clnt_control(client, CLSET_TIMEOUT, (char *) &info->timeout); + clnt_control(client, CLSET_RETRY_TIMEOUT, (char *) &info->timeout); + + status = clnt_call(client, NFSPROC_NULL, + (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_void, 0, + info->timeout); + + if (!info->client) { + /* + * Only play with the close options if we think it + * completed OK + */ + if (proto == IPPROTO_TCP && status == RPC_SUCCESS) { + struct linger lin = { 1, 0 }; + socklen_t lin_len = sizeof(struct linger); + int fd; + + if (!clnt_control(client, CLGET_FD, (char *) &fd)) + fd = -1; + + switch (info->close_option) { + case RPC_CLOSE_NOLINGER: + if (fd >= 0) + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, lin_len); + break; + } + } + clnt_destroy(client); + } + + if (status == RPC_TIMEDOUT) + return -ETIMEDOUT; + else if (status != RPC_SUCCESS) + return -EIO; + + return 1; +} + +static int __rpc_ping(const char *host, + unsigned long version, int proto, + long seconds, long micros, unsigned int option) +{ + int status; + struct conn_info info; + struct pmap parms; + + info.proto = proto; + info.host = host; + info.addr = NULL; + info.addr_len = 0; + info.program = NFS_PROGRAM; + info.version = version; + info.send_sz = 0; + info.recv_sz = 0; + info.timeout.tv_sec = seconds; + info.timeout.tv_usec = micros; + info.close_option = option; + info.client = NULL; + + status = RPC_PING_FAIL; + + parms.pm_prog = NFS_PROGRAM; + parms.pm_vers = version; + parms.pm_prot = info.proto; + parms.pm_port = 0; + + status = rpc_portmap_getport(&info, &parms, &info.port); + if (status < 0) + return status; + + status = rpc_ping_proto(&info); + + return status; +} + +int rpc_ping(const char *host, long seconds, long micros, unsigned int option) +{ + unsigned long vers4 = NFS4_VERSION; + unsigned long vers3 = NFS3_VERSION; + unsigned long vers2 = NFS2_VERSION; + int status; + + status = __rpc_ping(host, vers2, IPPROTO_UDP, seconds, micros, option); + if (status > 0) + return RPC_PING_V2 | RPC_PING_UDP; + + status = __rpc_ping(host, vers3, IPPROTO_UDP, seconds, micros, option); + if (status > 0) + return RPC_PING_V3 | RPC_PING_UDP; + + /* UDP isn't recommended for NFSv4, don't bother checking it. + status = __rpc_ping(host, vers4, IPPROTO_UDP, seconds, micros, option); + if (status > 0) + return RPC_PING_V4 | RPC_PING_UDP; + */ + + status = __rpc_ping(host, vers2, IPPROTO_TCP, seconds, micros, option); + if (status > 0) + return RPC_PING_V2 | RPC_PING_TCP; + + status = __rpc_ping(host, vers3, IPPROTO_TCP, seconds, micros, option); + if (status > 0) + return RPC_PING_V3 | RPC_PING_TCP; + + status = __rpc_ping(host, vers4, IPPROTO_TCP, seconds, micros, option); + if (status > 0) + return RPC_PING_V4 | RPC_PING_TCP; + + return status; +} + +double monotonic_elapsed(struct timespec start, struct timespec end) +{ + double t1, t2; + + t1 = (double) start.tv_sec + + (double) (start.tv_nsec/(1000*1000*1000)); + t2 = (double) end.tv_sec + + (double) (end.tv_nsec/(1000*1000*1000)); + return t2 - t1; +} + +int rpc_time(const char *host, + unsigned int ping_vers, unsigned int ping_proto, + long seconds, long micros, unsigned int option, double *result) +{ + int status; + double taken; + struct timespec start, end; + int proto = (ping_proto & RPC_PING_UDP) ? IPPROTO_UDP : IPPROTO_TCP; + unsigned long vers = ping_vers; + + clock_gettime(CLOCK_MONOTONIC, &start); + status = __rpc_ping(host, vers, proto, seconds, micros, option); + clock_gettime(CLOCK_MONOTONIC, &end); + + if (status == RPC_PING_FAIL || status < 0) + return status; + + taken = monotonic_elapsed(start, end); + + if (result != NULL) + *result = taken; + + return status; +} + +static int rpc_get_exports_proto(struct conn_info *info, exports *exp) +{ + CLIENT *client; + enum clnt_stat status; + int proto = info->proto; + unsigned int option = info->close_option; + int vers_entry; + int ret; + + if (info->proto == IPPROTO_UDP) { + info->send_sz = UDPMSGSIZE; + info->recv_sz = UDPMSGSIZE; + } + ret = create_client(info, &client); + if (ret < 0) + return 0; + + clnt_control(client, CLSET_TIMEOUT, (char *) &info->timeout); + clnt_control(client, CLSET_RETRY_TIMEOUT, (char *) &info->timeout); + + client->cl_auth = authunix_create_default(); + if (client->cl_auth == NULL) { + error(LOGOPT_ANY, "auth create failed"); + clnt_destroy(client); + return 0; + } + + vers_entry = 0; + while (1) { + status = clnt_call(client, MOUNTPROC_EXPORT, + (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_exports, (caddr_t) exp, + info->timeout); + if (status == RPC_SUCCESS) + break; + if (++vers_entry > 2) + break; + CLNT_CONTROL(client, CLSET_VERS, + (void *) &mount_vers[vers_entry]); + } + + /* Only play with the close options if we think it completed OK */ + if (proto == IPPROTO_TCP && status == RPC_SUCCESS) { + struct linger lin = { 1, 0 }; + socklen_t lin_len = sizeof(struct linger); + int fd; + + if (!clnt_control(client, CLGET_FD, (char *) &fd)) + fd = -1; + + switch (option) { + case RPC_CLOSE_NOLINGER: + if (fd >= 0) + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lin, lin_len); + break; + } + } + auth_destroy(client->cl_auth); + clnt_destroy(client); + + if (status != RPC_SUCCESS) + return 0; + + return 1; +} + +static void rpc_export_free(exports item) +{ + groups grp; + groups tmp; + + if (item->ex_dir) + free(item->ex_dir); + + grp = item->ex_groups; + while (grp) { + if (grp->gr_name) + free(grp->gr_name); + tmp = grp; + grp = grp->gr_next; + free(tmp); + } + free(item); +} + +void rpc_exports_free(exports list) +{ + exports tmp; + + while (list) { + tmp = list; + list = list->ex_next; + rpc_export_free(tmp); + } + return; +} + +exports rpc_get_exports(const char *host, long seconds, long micros, unsigned int option) +{ + struct conn_info info; + exports exportlist; + struct pmap parms; + int status; + + info.host = host; + info.addr = NULL; + info.addr_len = 0; + info.program = MOUNTPROG; + info.version = mount_vers[0]; + info.send_sz = 0; + info.recv_sz = 0; + info.timeout.tv_sec = seconds; + info.timeout.tv_usec = micros; + info.close_option = option; + info.client = NULL; + + parms.pm_prog = info.program; + parms.pm_vers = info.version; + parms.pm_port = 0; + + /* Try UDP first */ + info.proto = IPPROTO_UDP; + + parms.pm_prot = info.proto; + + status = rpc_portmap_getport(&info, &parms, &info.port); + if (status < 0) + goto try_tcp; + + memset(&exportlist, '\0', sizeof(exportlist)); + + status = rpc_get_exports_proto(&info, &exportlist); + if (status) + return exportlist; + +try_tcp: + info.proto = IPPROTO_TCP; + + parms.pm_prot = info.proto; + + status = rpc_portmap_getport(&info, &parms, &info.port); + if (status < 0) + return NULL; + + memset(&exportlist, '\0', sizeof(exportlist)); + + status = rpc_get_exports_proto(&info, &exportlist); + if (!status) + return NULL; + + return exportlist; +} + +const char *get_addr_string(struct sockaddr *sa, char *name, socklen_t len) +{ + void *addr; + + if (len < INET6_ADDRSTRLEN) + return NULL; + + if (sa->sa_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *) sa; + addr = &(ipv4->sin_addr); + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) sa; + addr = &(ipv6->sin6_addr); + } else { + return NULL; + } + + return inet_ntop(sa->sa_family, addr, name, len); +} + +#if 0 +#include + +int main(int argc, char **argv) +{ + int ret; + double res = 0.0; + exports exportlist, tmp; + groups grouplist; + int n, maxlen; + +/* + ret = rpc_ping("budgie", 10, 0, RPC_CLOSE_DEFAULT); + printf("ret = %d\n", ret); + + res = 0.0; + ret = rpc_time("budgie", NFS2_VERSION, RPC_PING_TCP, 10, 0, RPC_CLOSE_DEFAULT, &res); + printf("v2 tcp ret = %d, res = %f\n", ret, res); + + res = 0.0; + ret = rpc_time("budgie", NFS3_VERSION, RPC_PING_TCP, 10, 0, RPC_CLOSE_DEFAULT, &res); + printf("v3 tcp ret = %d, res = %f\n", ret, res); + + res = 0.0; + ret = rpc_time("budgie", NFS2_VERSION, RPC_PING_UDP, 10, 0, RPC_CLOSE_DEFAULT, &res); + printf("v2 udp ret = %d, res = %f\n", ret, res); + + res = 0.0; + ret = rpc_time("budgie", NFS3_VERSION, RPC_PING_UDP, 10, 0, RPC_CLOSE_DEFAULT, &res); + printf("v3 udp ret = %d, res = %f\n", ret, res); +*/ + exportlist = rpc_get_exports("budgie", 10, 0, RPC_CLOSE_NOLINGER); + exportlist = rpc_exports_prune(exportlist); + + maxlen = 0; + for (tmp = exportlist; tmp; tmp = tmp->ex_next) { + if ((n = strlen(tmp->ex_dir)) > maxlen) + maxlen = n; + } + + if (exportlist) { + while (exportlist) { + printf("%-*s ", maxlen, exportlist->ex_dir); + grouplist = exportlist->ex_groups; + if (grouplist) { + while (grouplist) { + printf("%s%s", grouplist->gr_name, + grouplist->gr_next ? "," : ""); + grouplist = grouplist->gr_next; + } + } + printf("\n"); + exportlist = exportlist->ex_next; + } + } + rpc_exports_free(exportlist); + + exit(0); +} +#endif diff --git a/man/Makefile b/man/Makefile new file mode 100644 index 0000000..9aa9cb7 --- /dev/null +++ b/man/Makefile @@ -0,0 +1,24 @@ + +-include ../Makefile.conf +include ../Makefile.rules + +GENFILES = $(patsubst %.in, %, $(wildcard *.[58].in)) + +.SUFFIXES: .in + +.in: + sed -e 's|@@initdir@@|$(initdir)|g' \ + -e 's|@@autofsmapdir@@|$(autofsmapdir)|g' \ + -e "s|@@autofsconfdir@@|$(autofsconfdir)|g" \ + < $< > $@ + +all: $(GENFILES) + +install: all + install -d -m 755 $(INSTALLROOT)$(mandir)/man5 + install -c *.5 -m 644 $(INSTALLROOT)$(mandir)/man5 + install -d -m 755 $(INSTALLROOT)$(mandir)/man8 + install -c *.8 -m 644 $(INSTALLROOT)$(mandir)/man8 + +clean: + rm -f $(GENFILES) diff --git a/man/auto.master.5.in b/man/auto.master.5.in new file mode 100644 index 0000000..ba28494 --- /dev/null +++ b/man/auto.master.5.in @@ -0,0 +1,374 @@ +.\" t +.TH AUTO.MASTER 5 "11 Apr 2006" +.SH NAME +auto.master \- Master Map for automounter +.SH "DESCRIPTION" +The +.B auto.master +map is consulted to set up automount managed mount points when the +.nh +.BR autofs (8) +.hy +script is invoked or the +.nh +.BR automount (8) +.hy +program is run. Each line describes a mount point and refers to an +autofs map describing file systems to be mounted under the mount +point. +.P +The default location of the master map is +.nh +.B @@autofsmapdir@@/auto.master +.hy +but an alternate name may be given on the command line when running +the automounter and the default master map may changed by setting the +.nh +.B "MASTER_MAP_NAME" +.hy +configuration variable in +.nh +.B @@autofsconfdir@@/autofs. +.hy +If the master map name has no path then the system Name Service Switch configuration +will be consulted and each of the sources searched in line with the rules +given in the Name Service Switch configuration. +.P +Access to mounts in maps is governed by a key. +.P +For direct maps the mount point is always specified as: +.P +.BR /- +.P +and the key used within the direct map is the full path to the mount point. The direct map +may have multiple entries in the master map. +.P +For indirect maps access is by using the path scheme: +.P +.BR /mount-point/key +.P +where +.I mount-point +is one of the entries listed in the master map. The +.I key +is a single directory component and is matched against entries in the +map given in the entry (See +.BR autofs (5)). +.P +Additionally, a map may be included from its source as if it were itself +present in the master map by including a line of the form: +.P +.BR + [ maptype [, format ]:] map\ [ options ] +.P +and +.BR automount (8) +will process the map according to the specification described below for +map entries. Indirect map entries must be unique in the master map so +second and subsequent entries for an indirect mount point are ignored by +.BR automount (8). +.SH "FORMAT" +Master map entries have three fields separated by an arbitrary number +of spaces or tabs. Lines beginning with # are comments. The first field +is the mount point described above and the second field is the name of +the map to be consulted for the mount point followed by the third field +which contains options to be applied to all entries in the map. +.P +The format of a master map entry is: +.TP +.BR mount-point\ [ map-type [, format ]:] map\ [ options ] +.TP +.B mount-point +Base location for the \fBautofs\fP filesystem to be mounted. For +indirect maps this directory will be created (as with \fBmkdir \-p\fP) +and is removed when the \fBautofs\fP filesystem is umounted. +.TP +.B map-type +Type of map used for this mount point. The following are +valid map types: +.RS +.TP +.B file +The map is a regular text file. +.TP +.B program +The map is an executable program, which is passed a key on the command +line and returns an entry (everything besides the key) on stdout if successful. +Optinally, the keyword exec may be used as a synonym for program to avoid +confusion with amd formated maps mount type program. +.TP +.B yp +The map is a NIS (YP) database. +.TP +.B nisplus +The map is a NIS+ database. +.TP +.B hesiod +The map is a hesiod database whose +.B filsys +entries are used for maps. +.TP +.B ldap \fPor\fB ldaps +The map is stored in an LDAP directory. If \fBldaps\fP is used the +appropriate certificate must be configured in the LDAP client. +.TP +.B multi +This map type allows the specification of multiple maps separated +by "--". These maps are searched in order to resolve key lookups. +.TP +.B dir +This map type can be used at +.BR + +master map including notation. The contents of files under given directory are included +to the master map. The name of file to be included must be ended with ".autofs". A file +will be ignored if its name is not ended with the suffix. In addition a dot file, a file +which name is started with "." is also ignored. +.RE +.TP +.B format +.br +Format of the map data; currently the formats recognized are \fBsun\fP, +which is a subset of the Sun automounter map format, \fBhesiod\fP, for +hesiod filesys entries and \fBamd\fP for amd formated map entries. +If the format is left unspecified, it defaults to \fBsun\fP for all map +types except \fBhesiod\fP unless it is a top level \fBamd\fP mount that +has a configuration entry for the mount point path, in which case the +format used is \fBamd\fP. +.TP +.B map +.br +Name of the map to use. This is an absolute UNIX pathname +for maps of types \fBfile\fP, \fBdir\fP, or \fBprogram\fP, and the name of a database +in the case for maps of type \fByp\fP, \fBnisplus\fP, or \fBhesiod\fP or +the \fBdn\fP of an LDAP entry for maps of type \fBldap\fP. +.TP +.B options +.br +Any remaining command line arguments without leading dashes (\-) are +taken as options (\fI\-o\fP) to \fBmount\fP. Arguments with leading +dashes are considered options for the maps and are passed to automount (8). +.sp +The \fBsun\fP format supports the following options: +.RS +.TP +.I "\-Dvariable=value" +Replace \fIvariable\fP with \fIvalue\fP in map substitutions. +.TP +.I "\-strict" +Treat errors when mounting file systems as fatal. This is important when +multiple file systems should be mounted (`multimounts'). If this option +is given, no file system is mounted at all if at least one file system +can't be mounted. +.TP +.I "[no]browse" +This is an autofs specific option that is a pseudo mount option and +so is given without a leading dash. Use of the browse option pre-creates +mount point directories for indirect mount maps so the map keys can be +seen in a directory listing without being mounted. Use of this option +can cause performance problem if the indirect map is large so it should +be used with caution. The internal program default is to enable browse +mode for indirect mounts but the default installed configuration overrides +this by setting BROWSE_MODE to "no" because of the potential performance +problem. +.TP +.I "nobind" +This is an autofs specific option that is a pseudo mount option and +so is given without a leading dash. It may be used either in the master +map entry (so it effects all the map entries) or with individual map +entries to prevent bind mounting of local NFS filesystems. For direct +mount maps the option is only effective if specified on the first direct +map entry and is applied to all direct mount maps in the master map. It +is ignored if given on subsequent direct map entries. It may be used +on individual map entries of both types. Bind mounting of NFS file +systems can also be prevented for specific map entrys by adding the +"port=" mount option to the entries. +.TP +.I "symlink" +This option makes bind mounting use a symlink instead of an actual bind +mount. It is an autofs specific option that is a pseudo mount option and +so is given without a leading dash. It may be used with indirect map +entries only, either in the master map (so it effects all map entries) +or with individual map entries. The option is ignored for direct mounts +and non-root offest mount entries. +.TP +.I "\-r, \-\-random-multimount-selection" +Enables the use of ramdom selection when choosing a host from a +list of replicated servers. This option is applied to this mount +only, overriding the global setting that may be specified on the +command line. +.TP +.I "\-w, \-\-use-weight-only" +Use only specified weights for server selection where more than one +server is specified in the map entry. If no server weights are given +then each available server will be tried in the order listed, within +proximity. +.TP +.I "\-t, \-\-timeout " +Set the expire timeout for map entries. This option can be used to +override the global default given either on the command line +or in the configuration. +.TP +.I "\-n, \-\-negative\-timeout " +Set the timeout for caching failed key lookups. This option can be +used to override the global default given either on the command line +or in the configuration. +.TP +.I "\-\-mode " +Set the directory mode for the base location of the \fBautofs\fP mount point. +If this option is given, \fBautofs\fP will chmod that directory with this +mode. +.SH BUILTIN MAP \-hosts +If "\-hosts" is given as the map then accessing a key under the mount point +which corresponds to a hostname will allow access to the exports of that +host. The hosts map cannot be dynamically updated and requires a HUP signal +to be sent to the daemon for it to check hosts for an update. Due to possible +hierarchic dependencies within a mount tree, it might not be completely +updated during the HUP signal processing. +.P +For example, with an entry in the master map of +.nh +.B /net \-hosts +.hy +accessing /net/myserver will mount exports from myserver on directories below +/net/myserver. +.P +NOTE: mounts done from a hosts map will be mounted with the "nosuid,nodev,intr" options +unless overridden by explicily specifying the "suid", "dev" or "nointr" options in the +master map entry. +.SH LDAP MAPS +If the map type \fBldap\fP is specified the mapname is of the form +\fB[//servername/]dn\fP, where the optional \fBservername\fP is +the name of the LDAP server to query, and \fBdn\fP is the Distinguished +Name of a subtree to search for map entries. +The old style +.nh +.B ldap:servername:mapname +.hy +is also understood. Alternatively, the type can be obtained from the Name Service Switch +configuration, in which case the map name alone must be given. +.P +If no schema is set in the autofs configuration then autofs will check +each of the commonly used schema for a valid entry and if one is found +it will used for subsequent lookups. +.P +There are three common schemas in use: +.TP +.I nisMap +.br +Entries in the \fBnisMap\fP schema are \fBnisObject\fP objects in +the specified subtree, where the \fBcn\fP attribute is the key +(the wildcard key is "/"), and the \fBnisMapEntry\fP attribute +contains the information used by the automounter. +.TP +.I automountMap +The \fBautomountMap\fP schema has two variations that differ in the attribute +used for the map key. Entries in the automountMap schema are \fBautomount\fP +objects in the specified subtree, where the \fBcn\fP or \fBautomountKey\fP +attribute (depending on local usage) is the key (the wildcard key is "/"), +and the \fBautomountInformation\fP attribute contains the information used +by the automounter. Note that the \fBcn\fP attribute is case insensitive. +.P +The object classes and attributes used for accessing automount maps in +LDAP can be changed by setting entries in the autofs configuration +located in +.nh +.BR @@autofsconfdir@@/autofs.conf . +.hy +.TP +.B NOTE: +If a schema is given in the configuration then all the schema configuration +values must be set, any partial schema specification will be ignored. +.TP +For \fBamd\fP format maps a different schema is used: +.TP +.I amdMap +.br +The \fBamdmap\fP schema contains attributes \fBamdmapName\fP, \fBamdmapKey\fP +and \fBamdmapValue\fP where \fBamdmapName\fP contains the name of the containing +map, \fBamdmapKey\fP contains the map key and \fBamdmapValue\fP contains the +map entry. +.SH LDAP AUTHENTICATION, ENCRYPTED AND CERTIFIED CONNECTIONS +LDAP authenticated binds, TLS encrypted connections and certification +may be used by setting appropriate values in the autofs authentication +configuration file and configuring the LDAP client with appropriate +settings. The default location of this file is +.nh +.BR @@autofsmapdir@@/autofs_ldap_auth.conf . +.hy +.P +If this file exists it will be used to establish whether TLS or authentication +should be used. +.P +An example of this file is: +.sp +.RS +.2i +.ta 1.0i +.nf + + +.fi +.RE +.sp +If TLS encryption is to be used the location of the Certificate Authority +certificate must be set within the LDAP client configuration in +order to validate the server certificate. If, in addition, a certified +connection is to be used then the client certificate and private key file +locations must also be configured within the LDAP client. +.P +In OpenLDAP these may be configured in the \fBldap.conf\fP file or in the +per-user configuration. For example it may be sensible to use the system +wide configuration for the location of the Certificate Authority certificate +and set the location of the client certificate and private key +in the per-user configuration. The location of these files and the configuration +entry requirements is system dependent so the documentation for your +installation will need to be consulted to get further information. +.P +See +.B autofs_ldap_auth.conf (5) +for more information. +.SH EXAMPLE +.sp +.RS +.2i +.ta 1.0i +.nf +/- auto.data +/home /etc/auto.home +/mnt yp:mnt.map +.fi +.RE +.sp +This will generate two mountpoints for +.IR /home +and +.IR /mnt +and install direct mount triggers for each entry in the direct mount map +.IR auto.data . +All accesses to +.I /home +will lead to the consultation of the map in +.IR /etc/auto.home +and all accesses to +.I /mnt +will consult the NIS map +.IR mnt.map . +All accesses to paths in the map +.IR auto.data +will trigger mounts when they are accessed and the Name Service Switch +configuration will be used to locate the source of the map +.IR auto.data . +.SH "SEE ALSO" +.BR automount (8), +.BR autofs (5), +.BR autofs (8), +.BR autofs.conf (5), +.BR autofs_ldap_auth.conf (5) +.SH AUTHOR +This manual page was written by Christoph Lameter , +for the Debian GNU/Linux system. Edited by and +Ian Kent . diff --git a/man/autofs.5 b/man/autofs.5 new file mode 100644 index 0000000..efde77a --- /dev/null +++ b/man/autofs.5 @@ -0,0 +1,611 @@ +.\" t +.TH AUTOFS 5 "9 Feb 2014" +.SH NAME +autofs \- Format of the automounter maps +.SH "DESCRIPTION" +The automounter maps are FILE, NIS, NISPLUS or LDAP (including LDAP via SSS) +referred to by the master map of the automounter (see +.BR auto.master (5)). +These maps describe how file systems below the mount point of the map +(given in the master map) are to be mounted. This page describes the +.B sun +map format; if another map format, other than +.B amd , +is specified (e.g. \fBhesiod\fP), +this documentation does not apply. + +Indirect maps, except for the internal hosts map, can be changed on the fly +and the automouter will recognize those changes on the next operation it +performs on that map. Direct maps require a HUP signal be sent to the +daemon to refresh their contents as does the master map. +.SH "SUN FORMAT" +This is a description of the text file format. Other methods of specifying +these files may exist. All empty lines or lines beginning with # are +ignored. The basic format of one line in such maps is: +.P +.BR key\ [ \-options ]\ location +.TP +.B key +.br +For indirect mounts this is the part of the path name between the mount point +and the path into the filesystem when it is mounted. Usually you can think about the +key as a sub-directory name below the autofs managed mount point. + +For direct mounts this is the full path of each mount point. This map is always +associated with the /- mount point in the master map. +.TP +.B options +.br +Zero or more options may be given. Options can also be given in the +.B auto.master +file in which case both values are cumulative (this is a difference +from SunOS). The options are a list of comma separated options as +customary for the +.BR mount (8) +command. + +There are several special options +.RS +.TP +.B \-fstype= +.br +is used to specify a filesystem type if the filesystem is not of the default +NFS type. This option is processed by the automounter and not by the mount +command. +.TP +.B \-strict +.br +is used to treat errors when mounting file systems as fatal. This is important when +multiple file systems should be mounted (`multi-mounts'). If this option +is given, no file system is mounted at all if at least one file system +can't be mounted. +.TP +.B \-use-weight-only +.br +is used to make the weight the sole factor in selecting a server when multiple +servers are present in a map entry. +and +.TP +.B \-no-use-weight-only +.br +can be used to negate the option if it is present in the master map entry +for the map but is not wanted for the given mount. +.RE +.TP +.B location +The location specifies from where the file system is to be mounted. In the +most cases this will be an NFS volume and the usual notation +.I host:pathname +is used to indicate the remote filesystem and path to be mounted. If +the filesystem to be mounted begins with a / (such as local +.I /dev +entries or smbfs shares) a : needs to be prefixed (e.g. +.IR :/dev/sda1 ). +.SH EXAMPLE +Indirect map: +.sp +.RS +.2i +.ta 1.0i 3.0i +.nf +kernel \-ro,soft,intr ftp.kernel.org:/pub/linux +boot \-fstype=ext2 :/dev/hda1 +windoze \-fstype=smbfs ://windoze/c +removable \-fstype=ext2 :/dev/hdd +cd \-fstype=iso9660,ro :/dev/hdc +floppy \-fstype=auto :/dev/fd0 +server \-rw,hard,intr / \-ro myserver.me.org:/ \\ + /usr myserver.me.org:/usr \\ + /home myserver.me.org:/home +.fi +.RE +.sp +In the first line we have a NFS remote mount of the kernel directory on +.IR ftp.kernel.org . +This is mounted read-only. The second line mounts an ext2 volume from a +local ide drive. The third makes a share exported from a Windows +machine available for automounting. The rest should be fairly +self-explanatory. The last entry (the last three lines) is an example +of a multi-map (see below). + +If you use the automounter for a filesystem without access permissions +(like \fBvfat\fP), users usually can't write on such a filesystem +because it is mounted as user \fBroot\fP. +You can solve this problem by passing the option \fIgid=\fP, +e.g. \fIgid=floppy\fP. The filesystem is then mounted as group +\fBfloppy\fP instead of \fBroot\fP. Then you can add the users +to this group, and they can write to the filesystem. Here's an +example entry for an autofs map: +.sp +.RS +.2i +.ta 1.0i 3.0i +.nf +floppy-vfat \-fstype=vfat,sync,gid=floppy,umask=002 :/dev/fd0 +.fi +.RE +.sp +Direct map: +.sp +.RS +.2i +.ta 1.0i 3.0i +.nf +/nfs/apps/mozilla bogus:/usr/local/moxill +/nfs/data/budgets tiger:/usr/local/budgets +/tst/sbin bogus:/usr/sbin +.fi +.RE +.sp +.SH FEATURES +.SS Map Key Substitution +An & character in the +.B location +is expanded to the value of the +.B key +field that matched the line (which probably only makes sense together with +a wildcard key). +.SS Wildcard Key +A map key of * denotes a wild-card entry. This entry is consulted +if the specified key does not exist in the map. A typical wild-card +entry looks like this: +.sp +.RS +.2i +.ta 1.0i +.nf +* server:/export/home/& +.fi +.RE +.sp +The special character '&' will be replaced by the provided key. So, +in the example above, a lookup for the key 'foo' would yield a mount +of server:/export/home/foo. +.SS Variable Substitution +The following special variables will be substituted in the location +field of an automounter map entry if prefixed with $ as customary +from shell scripts (curly braces can be used to separate the field +name): +.sp +.RS +.2i +.ta 1.5i +.nf +ARCH Architecture (uname \-m) +CPU Processor Type +HOST Hostname (uname \-n) +OSNAME Operating System (uname \-s) +OSREL Release of OS (uname \-r) +OSVERS Version of OS (uname \-v) +.fi +.RE +.sp +autofs provides additional variables that are set based on the +user requesting the mount: +.sp +.RS +.2i +.ta 1.5i +.nf +USER The user login name +UID The user login ID +GROUP The user group name +GID The user group ID +HOME The user home directory +SHOST Short hostname (domain part removed if present) +.fi +.RE +.sp +If a program map is used these standard environment variables will have +a prefix of "AUTOFS_" to prevent interpreted languages like python from +being able to load and execute arbitray code from a user home directory. +.RE +.sp +Additional entries can be defined with the \-Dvariable=Value map-option to +.BR automount (8). +.SS Executable Maps +A map can be marked as executable. A +.B program +map will be called with the key as an argument. It may +return no lines of output if there's an error, or one or more lines +containing a map entry (with \\ quoting line breaks). The map entry +corresponds to what would normally follow a map key. +.P +An executable map can return an error code to indicate the failure in addition +to no output at all. All output sent to stderr is logged into the system +logs. +.SS Multiple Mounts +A +.B multi-mount map +can be used to name multiple filesystems to mount. It takes the form: +.sp +.RS +.2i +.ta 1.0i +.nf +.BI "key [ \-options ] [[/] location [/relative-mount-point [ \-options ] location...]..." +.fi +.RE +.sp +.P +This may extend over multiple lines, quoting the line-breaks with \`\\\'. +If present, the per-mountpoint mount-options are appended to the +default mount-options. This behaviour may be overridden by the append_options +configuration setting. +.SS Replicated Server +A mount location can specify multiple hosts for a location, portentially +with a different export path for the same file system. Historically these +different locations are read-only and provide the same replicated file system. +.sp +.RS +.2i +.ta 1.5i +.nf +Multiple replicated hosts, same path: + host1,host2,hostn:/path/path + +Multiple hosts, some with same path, some with another + host1,host2:/blah host3:/some/other/path + +Multiple replicated hosts, different (potentially) paths: + host1:/path/pathA host2:/path/pathB + +Mutliple weighted, replicated hosts same path: + host1(5),host2(6),host3(1):/path/path + +Multiple weighted, replicated hosts different (potentially) paths: + host1(3):/path/pathA host2(5):/path/pathB + +Anything else is questionable and unsupported, but these variations will also work: + host1(3),host:/blah +.fi +.RE +.SH UNSUPPORTED +This version of the automounter supports direct maps stored in FILE, NIS, NISPLUS +and LDAP (including LDAP via SSS) only. +.P +.SH "AMD FORMAT" +This is a description of the text file format. Other methods of specifying +mount map entries may be required for different map sources. All empty +lines or lines beginning with # are ignored. The basic format of one +line in such maps is: +.P +.BR key\ location-list +.TP +.B key +.br +A \fBkey\fP is a path (or a single path component alone) that may end +in the wildcard key, "*", or the wildcard key alone and must not begin +with the "/" character. +.TP +.B location-list +Following the \fBkey\fP is a mount \fBlocation-list\fP. +.TP +A \fBlocation-list\fP list has the following syntax: +.TP +.B location[\ location[\ ...\ ]]\ [||\ location[\ location[\ ...\ ]] +.P +A mount \fBlocation-list\fP can use the cut operator, \fB||\fP, to specify +locations that should be tried if none of the locations to the left of it +where selected for a mount attempt. + +A mount \fBlocation\fP consists of an optional colon separated list +of \fBselectors\fP, followed by a colon separated list of \fBoption:=value\fP +pairs. + +The \fBselectors\fP that may be used return a value or boolean result. +Those that return a value may be to used with the comparison +operators \fB==\fP and \fB!=\fP and those that return a boolean result +may be negated with the \fB!\fP. + +For a \fBlocation\fP to be selected for a mount attempt all of its \fBselectors\fP +must evaluate to true. If a \fBlocation\fP is selected for a mount attempt +and succeeds the lookup is completed and returns success. If the mount +attempt fails the proceedure continues with the next \fBlocation\fP until +they have all been tried. + +In addition some \fBselectors\fP take no argumenets, some one argument +and others optionally take two arguments. + +The \fBselectors\fP that take no arguments are: +.RS +.TP +.B arch +.br +The machine architecture which, if not set in the confugration, is +obtained using uname(2). +.TP +.B karch +.br +The machine kernel architecture which, if not set in the confugration, +is obtained using uname(2). +.TP +.B os +.br +The operating system name, if not set in the confugration, is obtained +using uname(2). +.TP +.B osver +.br +The operating system version, if not set in the confugration, is obtained +using uname(2). +.TP +.B full_os +.br +The full operating system name, if not set in the confugration this selector +has no value. +.TP +.B vendor +.br +The operating system vendor name, if not set in the confugration this selector +has the value "unknown". +.TP +.B byte +.br +The endianness of the hardware. +.TP +.B cluster +.br +The name of the local cluster. It has a value only if it is set in the +configuration. +.TP +.B autodir +.br +The base path under which external mounts are done if they are needed. +Most mounts are done in place but some can't be and this is the base +path under which those mounts will be done. +.TP +.B domain +.br +The local domain name. It is set to the value of the configuration +option \fBsub_domain\fP. If sub_domain is not given in the configuration +it is set to the domain part of the local host name, as given by +gethostname(2). +.TP +.B host +.br +The local host name, without the domain part, as given by gethostname(2). +.TP +.B hostd +.br +The full host name. If \fBsub_domain\fP is given in the configuration +this is set to the contatenation of \fBhost\fP and \fBsub_domain\fP deperated +by a \fB.\fP. If \fBsub_domain\fP is not set in the configuration the value +of \fBdomain\fP is used instead of \fBsub_domain\fP. +.TP +.B uid +.br +The numeric value of the uid of the user that first requested the mount. Note +this is usual the same as that used by amd but can be different within autofs. +.TP +.B gid +.br +The numeric value of the gid of the user that first requested the mount. Note +this is usual the same as that used by amd but can be different within autofs. +.TP +.B key +.br +The string value of the key being looked up. +.TP +.B map +.br +The string value of the map name used to lookup \fBkey\fPs. +.TP +.B path +.br +The string value of the full path to the mount being requested. +.TP +.B dollar +.br +Evaluates to the string "$". +.RE +.TP +The \fBselectors\fP that take one argument are: +.RS +.TP +.B in_network(network) ", " network(network) ", " netnumber(network) ", " wire(network) +.br +These \fBselectors\fP are all the same. \fBin_network()\fP is the +preferred usage. The \fBnetwork\fP argument is an address (which may include +a subnet mask) or network name. The function compares \fBnetwork\fP +against each interface and returns true if \fBnetwork\fP belongs to +the network the interface is connected to. +.TP +.B xhost(hostname) +.br +The \fBxhost()\fP selector compares \fBhostname\fP to the \fB${host}\fP +and if it doesn't match it attempts to lookup the cannonical name +of \fBhostname\fP and compares it to \f${host}\fP as well. +.TP +.B exists(filename) +.br +Returns true if \fBfilename\fP exits as determined by lstat(2). +.TP +.B true() +.br +Evaluates to true, the argument is ignored and may be empty. +.TP +.B false() +.br +Evaluates to false, the argument is ignored and may be empty. +.RE +.TP +The \fBselectors\fP that take up to two arguments are: +.RS +.TP +.B netgrp(netgroup[,hostname]) +.br +The \fBnetgrp()\fP selector returns true if \fPhostname\fP is a member of +the netgroup \fBnetgroup\fP. If \fBhostname\fP is not given \fB${host}\fP +is used for the comparison. +.TP +.B netgrpd(netgroup[,hostname]) +.br +The \fBnetgrpd()i\fP selector behaves the same as \fBnetgrp()\fP except +that if \fBhostname\fP is not given \fB${hostd}\fP, the fully qualified +hostname, is used instead of \fB${host}\fP. +.RE +.TP +The \fBoptions\fP that may be used are: +.RS +.TP +.B type +.br +This is the mount filesystem \fBtype\fP. +It can have a value of +.BR auto ", " link ", " linkx ", " host ", " lofs ", " ext2-4 ", " +.BR xfs ", " nfs ", " nfsl " or " cdfs "." +Other types that are not yet implemented or are not available iin autofs are +.BR nfsx ", " lustre ", " jfs ", " program ", " cachefs " and " direct "." +.TP +.B maptype +.br +The \fBmaptype\fP option specifies the type of the map source and can +have a value of \fBfile\fP, \fBnis\fP, \fBnisplus\fP, \fBexec\fP, \fBldap\fP +or \fBhesiod\fP. Map sources either not yet implemented or not available in +autofs are \fBsss\fP, \fBndbm\fP, \fBpasswd\fP and \fBunion\fP. +.TP +.B fs +.br +The option \fBfs\fP is used to specify the local filesystem. The meaning of +this option (and whether or not it is used) is dependent on the mount +filesystem \fBtype\fP. +.TP +.B rhost +.br +The remote host name for network mount requests. +.TP +.B rfs +.br +The remote host filesystem path for network mount requests. +.TP +.B dev +.br +Must resolve to the device file for local device mount +requests. +.TP +.B sublink +.br +The \fBsublink\fP option is used to specify a subdirectory +within the mount location to which this entry will point. +.TP +.B pref +.br +The \fBpref\fP option is used to specify a prefix that is +prepended to the lookup key before looking up the map entry +key. +.TP +.B opts +.br +The \fBopts\fP option is used to specify mount options to be +used for the mount. If a "\fB-\fP" is given it is ignored. +Options that may be used are dependend on the mount filesystem. +.TP +.B addopts +.br +The \fBaddopts\fP option is used to specify additional mount +options used in addition to the default mount options for the +mount location. +.TP +.B remopts +.br +The \fBaddopts\fP option is used to specify mount options used +instead the options given in \fBopts\fP when the mount location +is on a remote retwork. +.RE +.TP +A number of \fBoptions\fP aren't available or aren't yet implemented +within autofs, these are: +.RS +.TP +.B cache +.br +The \fBcache option\fP isn't used by autofs. The map entry cache is +continually updated and stale entries cleaned on re-load when map +changes are detected so these configuration entries are not used. +The \fBregex\fP map key matching is not implemented and may not be +due to the potential overhead of the full map scans needed on every +key lookup. +.TP +.B cachedir +.br +The \fBcachefs\fP filesystem is not available on Linux, a different +implementation is used for caching network mounted file systems. +.TP +.B mount ", " unmount ", " umount +.br +These \fBoptions\fP are used by the amd \fBprogram\fP mount type which +is not yet implemented. +.TP +.B delay +.br +This \fBoption\fP is not used by the autofs implementation and is ignored. +.RE +.BR +.SH FEATURES +.SS Key Matching +The amd parser key matching is unusual. + +The key string to be looked up is constructed by prepending the prefix, if +there is one. + +The resulting relative path string is matched by first trying the sting +itself. If no match is found the last component of the key string is +replaced with the wilcard match cahracter ("*") and a wildcard match is +attemted. This process continues until a match is found or until the +last match, against the wilcard match key alone, fails to match a map +entry and the key lookup fails. +.SS Macro Usage +Macros are used a lot in the autofs amd implementation. + +Many of the option values are set as macro variables corresponding to the +option name during the map entry parse. So they may be used in subsequent +option values. Beware though, the order in which option values is not +necessarily left to right so you may get unexpected results. +.BR +.SH EXAMPLE +Example NFS mount map: +.P +Assuming we have the autofs master map entry: +.sp +.RS +.2i +.ta 1.0i 3.0i +.nf +/test file,amd:/etc/amd.test +.fi +.RE +.sp +And the following map in /etc/amd.test: +.sp +.RS +.2i +.ta 1.0i 3.0i +.nf +/defaults type:=nfs;rhost:=bilbo +apps rfs:=/autofs +util rhost:=zeus;rfs:=/work/util +local rfs:=/shared;sublink:=local +.fi +.RE +.sp +In the first line we have an NFS remote mount of the exported directory +/autofs from host bilbo which would be mounted on /test/apps. Next +another nfs mount for the exported directory /work/util from host zeus. +This would be mounted on /test/util. + +Finally we have an example of the use of the \fBsublink\fP option. In +this case the filesystem bilbo:/shared would be mounted on a path +external the automount directory (under the direcory given by +configuration option auto_dir) and the path /test/local either +symlinked or bind mounted (depending on the setting autofs_use_lofs) +to the "local" subdirectory of the external mount. +.BR +.SH "NOTES" +To be able to use IPv6 within autofs maps the package must be build +to use the libtirpc library for its RPC communications. This is +becuase the glibc RPC implementation doesn't support IPv6 and is +depricated so this is not likely to change. +.BR +.SH "SEE ALSO" +.BR automount (8), +.BR auto.master (5), +.BR autofs (8), +.BR autofs.conf (5), +.BR mount (8). +.BR autofs_ldap_auth.conf (5) +.SH AUTHOR +This manual page was written by Christoph Lameter , +for the Debian GNU/Linux system. Edited by H. Peter Avian +, Jeremy Fitzhardinge and +Ian Kent . diff --git a/man/autofs.8.in b/man/autofs.8.in new file mode 100644 index 0000000..b2b757b --- /dev/null +++ b/man/autofs.8.in @@ -0,0 +1,81 @@ +.TH AUTOFS 8 "9 Sep 1997" +.SH NAME +Service control for the automounter +.SH SYNOPSIS +If a SysV init script system is being used: +.br +.B @@initdir@@/autofs +.I start|stop|restart|reload|status +.P +or if the systemd init system is being used: +.br +.B systemctl +.I start|stop|restart|reload|status +.B autofs.service +.SH "DESCRIPTION" +.B autofs +controls the operation of the +.BR automount (8) +daemon(s) running on the Linux system. Usually +.B autofs +is invoked at system boot time with the +.I start +parameter and at shutdown time with the +.I stop +parameter. Service control actions can also be manually invoked by +the system administrator to shut down, restart, reload or obtain +service status. +.P +.SH "OPERATION" +.B autofs +will consult a configuration file +.I @@autofsmapdir@@/auto.master +(see +.BR auto.master (5)) +by default to find mount points on the system. For each of those mount points +.BR automount (8) +will mount and start a thread, with the appropriate parameters, to +manage the mount point. +.P +.B @@initdir@@/autofs reload +or +.B systemctl autofs.service reload +will check the current auto.master map against the current automount managed +mounts. It will terminate those daemons or threads (depending on +.B autofs +version) whose entries have been removed, re-read the automount maps for +entries that have changed and start new daemons or threads for entries +that have been added. +.P +If an indirect map is modified then the change will become effective immediately. +If an indirect map uses the +.B browse +option, the master map contains direct mount maps or the +.I auto.master +map is modified then the +.B autofs +service control reload action must be rerun to activate the changes. +.P +.B @@initdir@@/autofs status +or +.B systemctl autofs.service status +will display the status of, +.BR automount (8), +running or not. When using the systemd init system the status output includes +somewhat more information related to the service status. +.P +.B systemctl(1) +has more functions than the actions mentioned here, see +.B systemctl(1) +for more information. +.SH "SEE ALSO" +.BR automount (8), +.BR autofs (5), +.BR autofs.conf (5), +.BR auto.master (5). +.BR autofs_ldap_auth.conf (5) +.BR systemctl(1) +.SH AUTHOR +This manual page was written by Christoph Lameter , +for the Debian GNU/Linux system. Edited by H. Peter Anvin + and Ian Kent . diff --git a/man/autofs.conf.5.in b/man/autofs.conf.5.in new file mode 100644 index 0000000..a777c58 --- /dev/null +++ b/man/autofs.conf.5.in @@ -0,0 +1,518 @@ +.\" t +.TH AUTOFS.CONF "23 Jan 2014" +.SH NAME +autofs.conf \- autofs configuration +.SH "DESCRIPTION" +.P +Configuration settings used by +.BR automount (8) +may be changed in the configuration file \fB@@autofsmapdir@@/autofs.conf\fP. +.P +This file contains two primary sections, \fBautofs\fP and \fBamd\fP. +.P +Configuration entries may be present at the beginning of the +configuration file without a section header and are implicitly +included as part of the \fBautofs\fP section. +.P +Each section name is enclosed in square brackets with +spaces between the brackets and the section name. The \fBamd\fP +section may be followed by further sections, named by the +top level mount point path, that contain per mount +configuration settings. +.SH "SECTION autofs CONFIGURATION OPTIONS" +.P +Configuration settings available are: +.TP +.B timeout +.br +Sets the default mount timeout in seconds. The internal program +default is 10 minutes, but the default installed configuration +overrides this and sets the timeout to 5 minutes to be consistent +with earlier autofs releases. +.TP +.B master_wait +sets the default maximum time to wait for the master map to become +available if it cannot be read at program start (program default 10, +wait for 10 seconds then continue). +.TP +.B negative_timeout +.br +Set the default timeout for caching failed key lookups (program default +60). If the equivalent command line option is given it will override this +setting. +.TP +.B mount_wait +.br +Set the default time to wait for a response from a spawned mount(8) +before sending it a SIGTERM. Note that we still need to wait for the +RPC layer to timeout before the sub-process exits so this isn't ideal +but it is the best we can do. The default is to wait until mount(8) +returns without intervention. +.TP +.B umount_wait +.br +Set the default time to wait for a response from a spawned umount(8) +before sending it a SIGTERM. Note that we still need to wait for the +RPC layer to timeout before the sub-process exits so this isn't ideal +but it is the best we can do. +.TP +.B browse_mode +.br +Maps are browsable by default (program default "yes"). +.TP +.B mount_nfs_default_protocol +.br +Specify the default protocol used by +.BR mount.nfs (8) +(program default 3). Since we can't identify this default automatically +we need to set it in the autofs configuration. +.TP +.B append_options +.br +Determine whether global options, given on the command line or per mount +in the master map, are appended to map entry options or if the map entry +options replace the global options (program default "yes", append options). +.TP +.B logging +.br +set default log level "none", "verbose" or "debug" (program default "none"). +.TP +.B force_standard_program_map_env +.br +override the use of a prefix with standard environment variables when a +program map is executed. Since program maps are run as the privileded +user setting these standard environment variables opens automount(8) to +potential user privilege escalation when the program map is written in a +language that can load components from, for example, a user home directory +(program default "no"). +.TP +.B map_hash_table_size +.br +This configuration option may be used to change the number of hash +table slots (default 1024). + +This configuration option affects the overhead of searching the map +entry cache for map entries when there are a large number of entries. +It affects the number of entries that must be looked at to locate a +map entry in the map entry cache. For example, the default of 1024 +and a direct map with 8000 entries would result in each slot +containing an average of 8 entries, which should be acceptable. + +However, if excessive CPU usage is observed during automount lookups +increasing this option can reduce the CPU overhead considerably becuase +it reduces the length of the search chains. + +Note that the number of entries in a map doesn't necessarily relate +to the number of entries used in the map entry cache. + +There are three distinct cases, direct maps and indirect maps that +use the "browse" option must be read in their entirity at program +start so, in these two cases the map size does retate directly to +the map entry cache size. + +For indirect maps that do not use the "browse" option entries are +added to the map entry cache at lookup so the number of active cache +entries, in this case, is usually much less than the number of entries +in the map. In this last case it would be unusual for the map entry +cache to grow large enough to warrant increasing the default before +an event that cleans stale entries, a map re-read for example. +.TP +.B use_hostname_for_mounts +.br +NFS mounts where the host name resolves to more than one IP address +are probed for availability and to establish the order in which mounts +to them should be tried. To ensure that mount attempts are made only +to hosts that are responding and are tried in the order of hosts with +the quickest response the IP address of the host needs to be used for +the mount. + +If it is necessary to use the hostname given in the map entry for the +mount regardless, then set this option to "yes". + +Be aware that if this is done there is no defense against the host +name resolving to one that isn't responding and while the number +of attempts at a successful mount will correspond to the number of +addresses the host name resolves to the order will also not correspond +to fastest responding hosts. +.TP +.B disable_not_found_message +.br +The original request to add this log message needed it to be unconditional. +That produces, IMHO, unnecessary noise in the log so a configuration option +has been added to provide the ability to turn it off. The default is "no" +to maintain the current behaviour. +.TP +.B sss_master_map_wait +.br +Set the time to wait and retry if sssd returns "no such entry" when starting +up. When sssd is starting up it can sometimes return "no such entry" for a +short time until it has read in the LDAP map information. Default is 0 seconds, +don't wait. +.TP +.B use_mount_request_log_id +.br +Set whether to use a mount request log id so that log entries for specific +mount requests can be easily identified in logs that have multiple conncurrent +requests. Default is don't use mount request log ids. +.SS LDAP Configuration +.P +Configuration settings available are: +.TP +.B ldap_timeout +.br +Set the network response timeout (default 8). +Set timeout value for the synchronous API calls. The default is the LDAP +library default of an infinite timeout. +.TP +.B ldap_network_timeout +.br +Set the network response timeout (default 8). +.TP +.B ldap_uri +.br +A space separated list of server uris of the form ://[/] +where can be ldap or ldaps. The option can be given multiple times. +Map entries that include a server name override this option and it is then +not used. Default is an empty list in which case either the server given +in a map entry or the LDAP configured default is used. This uri list is read at +startup and whenever the daemon receives a HUP signal. + +This configuration option can also be used to request autofs lookup SRV RRs +for a domain of the form :///[]. Note that a trailing +"/" is not allowed when using this form. If the domain dn is not specified +the dns domain name (if any) is used to construct the domain dn for the +SRV RR lookup. The server list returned from an SRV RR lookup is refreshed +according to the minimum ttl found in the SRV RR records or after one hour, +whichever is less. +.TP +.B search_base +.br +The base dn to use when searching for amap base dn. This entry may be +given multiple times and each will be checked for a map base dn in +the order they occur in the configuration. The search base list is read +at startup and whenever the daemon recieves a HUP signal. +.TP +.B map_object_class +.br +The map object class. In the \fBnisMap\fP schema this corresponds to the class +\fBnisMap\fP and in the \fBautomountMap\fP schema it corresponds to the class +\fBautomountMap\fP. +.TP +.B entry_object_class +.br +The map entry object class. In the \fBnisMap\fP schema this corresponds +to the class \fBnisObject\fP and in the \fBautomountMap\fP schema it +corresponds to the class \fBautomount\fP. +.TP +.B map_attribute +.br +The attribute used to identify the name of the map to which this +entry belongs. In the \fBnisMap\fP schema this corresponds to the attribute +\fBnisMapName\fP and in the \fBautomountMap\fP schema it corresponds to the +attribute \fBou\fP or \fBautomountMapName\fP. +.TP +.B entry_attribute +.br +The attribute used to identify a map key. In the \fBnisMap\fP schema this +corresponds to the attribute \fBcn\fP and in the \fBautomountMap\fP schema +it corresponds to the attribute \fBautomountKey\fP. +.TP +.B value_attribute +.br +The attribute used to identify the value of the map entry. In the \fBnisMap\fP +schema this corresponds to the attribute \fBnisMapEntry\fP and in the \fBautomountMap\fP +schema it corresponds to the attribute +.BR automountInformation . +.TP +.B NOTE: +It is essential that entries use class and attribute in a consistent +manner for correct operation of autofs. For example mixing \fBcn\fP +and \fBautomountKey\fP attributes in \fBautomount\fP schema will +not work as expected. +.TP +.B auth_conf_file +This configuration option may be used to specify an alternate location +for the ldap authentication configuration file. See +.BR autofs_ldap_auth.conf (5) +for more information. +.SH "SECTION amd CONFIGURATION OPTIONS" +.P +A number of the amd configuration options are not used by autofs, +some because they are not relevant within autofs, some because +they are done differently in autofs and others that are not yet +implemented. + +Since \fBmount_type\fP is always autofs (because there's no user space +NFS server) the configuration entries relating to that aren't used. +Also, server availability is done differently within autofs so the +options that relate to the amd server monitoring sub-system are +also not used. + +These options are \fBmount_type\fP, \fBauto_attrcache\fP, \fBportmap_program\fP, +\fBnfs_vers_ping\fP, \fBnfs_allow_any_interface\fP, \fBnfs_allow_insecure_port\fP, +\fBnfs_proto\fP, \fBnfs_retransmit_counter\fP, \fBnfs_retransmit_counter_udp\fP, +\fBnfs_retransmit_counter_tcp\fP, \fBnfs_retransmit_counter_toplvl\fP, +\fBnfs_retry_interval\fP, \fBnfs_retry_interval_udp\fP, \fBnfs_retry_interval_tcp\fP, +\fBnfs_retry_interval_toplvl\fP and \fBnfs_vers\fP. + +Other options that are not used within the autofs implementation: +.TP +.BR log_file ", " truncate_log +.br autofs used either stderr when running in the foreground or +sends its output to syslog so an alternate log file (or truncating +the log) can't be used. +.TP +.B print_pid +.br +There's no corresponding option for this within autofs. +.TP +.BR use_tcpwrappers ", " show_statfs_entries +.br +There's no user space NFS server to control access to so this +option isn't relevant. The show_statfs_entries can't be +implemented for the same reason. +.TP +.B debug_mtab_file +.br +There's no user space NFS server and autofs avoids using file +based mtab whenever possible. +.TP +.B sun_map_syntax +.br +Sun map format is handled by autofs itself. +.TP +.BR plock ", " show_statfs_entries ", " preferred_amq_port +.br +Are not supported by autofs. +.TP +.BR ldap_cache_maxmem ", " ldap_cache_seconds +.br +External ldap caching is not used by autofs. +.TP +.B ldap_proto_version +.br +autofs always attempts to use the highest available ldap +protocol version. +.TP +.BR cache_duration ", " map_reload_interval ", " map_options +.br +The map entry cache is continually updated and stale entries +cleaned on re-load, which is done when map changes are detected +so these configuration entries are not used by autofs. An +exception to this is the case where the map is large. In this +case it may be necessary to read the whole map at startup even if +browsing is is not enabled. Adding the cache:=all option to +map_options can be used to for this. +.TP +.B localhost_address +This is not used within autofs. This configuration option was +only used in the amd user space server code and is not relevant +within autofs. +.P +Options that are handled differently within autofs: +.TP +.B pid_file +.br +To specify a pid file name a command line option must be used on startup. +.TP +.B print_version +.br +Program version and feature information is obtained by using the +automount command line option "-V". +.TP +.B debug_options ", " log_options +.br +autofs has somewhat more limited logging and debug logging options. +When the log_options options is encountered it is converted to the +nearest matching autofs logging option. Since the configuration +option debug_options would be handled the same way it is ignored. +.TP +.B restart_mounts +.br +This option has no sensible meaning within autofs because autofs +always tries to re-connect to existing mounts. While this has its +own set of problems not re-connecting to existing mounts always +results in a non-functional automount tree if mounts were busy at +the last shutdown (as is also the case with amd when using +mount_type autofs). +.TP +.B forced_unmounts +.br +Detaching mounts often causes serious problems for users of +existing mounts. It is used by autofs in some cases, either at +the explicit request of the user (with a command line or init +option) and in some special cases during program operation but +is avoided whenever possible. +.P +A number of configuration options are not yet implemented: +.TP +.B search_path +.br +Always a little frustrating, the compiled in map location should +be used to locate maps but isn't in some cases. This requires +work within autofs itself and that will (obviously) include +implementing this configuration option for the amd map parser +as well. +.TP +.B fully_qualified_hosts +Not yet implemented. +.TP +.B unmount_on_exit +.br +Since autofs always tries to re-connect to mounts left mounted +from a previous shutdown this is a sensible option to implement +and that will be done. +.TP +.B browsable_dirs +.br +Allow map keys to be shown in directory listings. This option +can have values of "yes" or "no". The default is "no". A variation +of this option, "browsable", can be used as a pseudo mount option +in type "auto" map entries to provide provide browsing funtionality +in sub-mounts. The amd "browsable_dirs = full" option cannot be +implemented within the current autofs framework and is not supported. +.TP +.B exec_map_timeout +.br +A timeout is not currently used for for program maps, might be +implemented. +.TP +.B tag +.br +The tag option is not implemented within autofs. +.P +Supported options: +.TP +.BR arch ", " karch ", " os ", " osver +.br +These options default to what is returned from uname(2) and can +be overridden if required. +.TP +.B full_os +This option has no default and must be set in the configuration +if used in maps. +.TP +.B cluster +.br +If not set defaults to the host domain name. This option corresponds +to the HP_UX cluster name (according to the amd source) and is +probably not used in Linux but is set anyway. +.TP +.B vendor +This option has a default value of "unknown", it must be set in the +configuration if used in maps. +.TP +.B auto_dir +.br +Is the base name of the mount tree used for external mounts that +are sometimes needed by amd maps. Its default value is "/a". +.TP +.B map_type +.br +Specifies the autofs map source, such as file, nis, ldap etc. and +has no default value set. +.TP +.B map_defaults +.br +This option is used to override /defaults entries within maps +and can be used to provide different defaults on specific machines +without having to modify centrally managed maps. It is empty by +default. +.TP +.B search_path +.br +Colon separated paths to search for maps that are not specified +as a full path. +.TP +.B dismount_interval +.br +Is equivalent to the autofs timeout option. It is only possible +to use this with type "auto" mounts due to the way the autofs +kernel module performs expiry. It takes its default value from +the autofs internal defaulti of 600 seconds. +.TP +.B autofs_use_lofs +.br +If set to "yes" autofs will attempt to use bind mounts for type +"auto" when possible. +.TP +.B nis_domain +.br +Allows setting of a domain name other than the system default. +.TP +.B local_domain +.br +Is used to override (or set) the host domain name. +.TP +.B normalize_hostnames +.br +If set to "yes" then the contents of ${rhost} is translated in +its official host name. +.TP +.B domain_strip +.br +If set to "yes" the domain name part of the host is strippped +when normalizing hostnames. This can be useful when using of +the same maps in a multiple domain environment. +.TP +.B normalize_slashes +.br +This option is set to "yes" by default and will collapse +multiple unescaped occurrences of "/" to a single "/". +.TP +.BR selectors_in_defaults ", " selectors_on_default +.br +This option has a default value of "no". If set to "yes" then +any defaults entry will be checked for selectors to determine +the values to be used. selectors_in_defaults is the preferred +option to use. +.TP +.B ldap_base +.br +iThis option has no default value. It must be set to the base dn +that is used for queries if ldap is to be used as a map source. +.TP +.B ldap_hostports +.br +This option has no default value set. It must be set to the URI +of the LDAP server to be used for lookups wheni ldap is used a +map source. It may contain a comma or space separated list of +LDAP URIs. +.TP +.B hesiod_base +.br +Sets the base name used for hesiod map sources. +.TP +.B linux_ufs_mount_type +.br +This is an additional configuration option for the autofs amd format +parser implementation. + +There's no simple way to determine what the system default filesystem +is and am-utils needs to be continually updated to do this and can +easily get it wrong ayway. So allow it to be set in the configuration. +.SH EXAMPLE +.sp +.RS +.2i +.ta 1.0i +.nf +[ autofs ] +timeout = 300 +browse_mode = no + +[ amd ] +dismount_interval = 300 +map_type = nis +autofs_use_lofs = no + +[ /expamle/mount ] +dismount_interval = 60 +map_type = file +.fi +.RE +.SH "SEE ALSO" +.BR automount (8), +.BR auto.master (5), +.BR autofs_ldap_auth.conf (5) +.SH AUTHOR +This manual page was written by Ian Kent . diff --git a/man/autofs_ldap_auth.conf.5.in b/man/autofs_ldap_auth.conf.5.in new file mode 100644 index 0000000..fe5077d --- /dev/null +++ b/man/autofs_ldap_auth.conf.5.in @@ -0,0 +1,118 @@ +.\" t +.TH AUTOFS_LDAP_AUTH.CONF 5 "19 Feb 2010" +.SH NAME +autofs_ldap_auth.conf \- autofs LDAP authentication configuration +.SH "DESCRIPTION" +LDAP authenticated binds, TLS encrypted connections and certification +may be used by setting appropriate values in the autofs authentication +configuration file and configuring the LDAP client with appropriate +settings. The default location of this file is +.nh +.BR @@autofsmapdir@@/autofs_ldap_auth.conf . +.hy +If this file exists it will be used to establish whether TLS or authentication +should be used. +.P +An example of this file is: +.sp +.RS +.2i +.ta 1.0i +.nf + + +.fi +.RE +.sp +If TLS encryption is to be used the location of the Certificate Authority +certificate must be set within the LDAP client configuration in +order to validate the server certificate. If, in addition, a certified +connection is to be used then the client certificate and private key file +locations must also be configured within the LDAP client. +.SH "OPTIONS" +This files contains a single XML element, as shown in the example above, with +several attributes. +.TP +The possible attributes are: +.TP +\fBusetls="yes"|"no"\fP +Determines whether an encrypted connection to the ldap server +should be attempted. +.TP +\fBtlsrequired="yes"|"no"\fP +This flag tells whether the ldap connection must be encrypted. If set to "yes", +the automounter will fail to start if an encrypted connection cannot be +established. +.TP +\fBauthrequired="yes"|"no"|"autodetect"|"simple"\fP +This option tells whether an authenticated connection to the ldap server is +required in order to perform ldap queries. If the flag is set to yes, only +sasl authenticated connections will be allowed. If it is set to no then +authentication is not needed for ldap server connections. If it is set to +autodetect then the ldap server will be queried to establish a suitable sasl +authentication mechanism. If no suitable mechanism can be found, connections +to the ldap server are made without authentication. Finally, if it is set to +simple, then simple authentication will be used instead of SASL. +.TP +\fBauthtype="GSSAPI"|"LOGIN"|"PLAIN"|"ANONYMOUS"|"DIGEST-MD5|EXTERNAL"\fP +This attribute can be used to specify a preferred authentication mechanism. +In normal operations, the automounter will attempt to authenticate to the +ldap server using the list of supportedSASLmechanisms obtained from the +directory server. Explicitly setting the authtype will bypass this selection +and only try the mechanism specified. The EXTERNAL mechanism may be used to +authenticate using a client certificate and requires that authrequired +set to "yes" if using SSL or usetls, tlsrequired and authrequired all set to +"yes" if using TLS, in addition to authtype being set to EXTERNAL. +.sp +If using authtype EXTERNAL two additional configuration entries are +required: +.sp +\fBexternal_cert=""\fP +.sp +This specifies the path of the file containing the client certificate. +.sp +\fBexternal_key=""\fP +.sp +This specifies the path of the file containing the client certificate key. +.sp +These two configuration entries are mandatory when using the EXTERNAL method +as the HOME environment variable cannot be assumed to be set or, if it is, +to be set to the location we expect. +.TP +\fBuser=""\fP +This attribute holds the authentication identity used by authentication +mechanisms that require it. Legal values for this attribute include any +printable characters that can be used by the selected authentication +mechanism. +.TP +\fBsecret=""\fP +This attribute holds the secret used by authentication mechanisms that +require it. Legal values for this attribute include any printable +characters that can be used by the selected authentication mechanism. +.TP +\fBencoded_secret=""\fP +This attribute holds the base64 encoded secret used by authentication +mechanisms that require it. If this entry is present as well as the +secret entry this value will take precedence. +.TP +.TP +\fBclientprinc=""\fP +When using GSSAPI authentication, this attribute is consulted to determine +the principal name to use when authenticating to the directory server. By +default, this will be set to "autofsclient/@. +.TP +\fBcredentialcache=""\fP +When using GSSAPI authentication, this attribute can be used to specify an +externally configured credential cache that is used during authentication. +By default, autofs will setup a memory based credential cache. +.SH "SEE ALSO" +.BR auto.master (5), +.BR autofs.conf (5), +.SH AUTHOR +This manual page was written by Ian Kent . diff --git a/man/automount.8 b/man/automount.8 new file mode 100644 index 0000000..601c18c --- /dev/null +++ b/man/automount.8 @@ -0,0 +1,192 @@ +.\" Linux man page by B. James Phillippe, 1997 +.\" +.\" This page was written to contribute to the Linux kernel autofs +.\" implementation by H. Peter Anvin (1997). It is loosly based on +.\" the documentation for mount(8) and amd(8) Linux manpages. +.\" +.\" This is free documentation. +.\" +.TH AUTOMOUNT 8 "12 Apr 2006" +.SH NAME +automount \- manage autofs mount points +.SH SYNOPSIS +\fBautomount\fP [\fIoptions\fP] [\fImaster_map\fP] +.SH DESCRIPTION +The \fBautomount\fP program is used to manage mount points for +autofs, the inlined Linux automounter. \fBautomount\fP works by +reading the +.nh +.BR auto.master (5) +.hy +map and sets up mount points for each entry in the master map allowing +them to be automatically mounted when accessed. The file systems are +then automatically umounted after a period of inactivity. +.SH OPTIONS +.TP +.I "\-h, \-\-help" +Print brief help on program usage. +.TP +.I "\-p, \-\-pid-file" +Write the pid of the daemon to the specified file. +.TP +.I "\-t , \-\-timeout " +Set the global minimum timeout, in seconds, until directories +are unmounted. The default is 10 minutes. Setting the timeout +to zero disables umounts completely. +The internal program default is 10 minutes, but the default +installed configuration overrides this and sets the timeout +to 5 minutes to be consistent with earlier autofs releases. +.TP +.I "\-M , \-\-master-wait " +Set the maximum time to wait for the master map to become available +if it cannot be read at program start. +.TP +.I "\-n , \-\-negative\-timeout " +Set the default timeout for caching failed key lookups. The default is 60 seconds. +.TP +.I "\-v, \-\-verbose" +Enables logging of general status and progress messages for all +autofs managed mounts. +.TP +.I "\-d, \-\-debug" +Enables logging of general status and progress messages as well as +debugging messages for all autofs managed mounts. +.TP +.I "\-Dvariable=value, --define variable=value" +Define a global macro substitution variable. Global definitions +are over-ridden macro definitions of the same name specified in +mount entries. +.TP +.I "\-f, \-\-foreground" +Run the daemon in the foreground and log to stderr instead of syslog." +.TP +.I "\-r, \-\-random-multimount-selection" +Enables the use of ramdom selection when choosing a host from a +list of replicated servers. +.TP +.I "\-m, \-\-dumpmaps [ ]" +With no parameters, list information about the configured automounter +maps, then exit. + +If the dumpmaps option is given and is followed by two parameters, +" " then simple "" pairs that would +be read in by a map read are printed to stdout if the given map type +and map name are found in the map configuration. + +If the map is an LDAP map and there is more than one map of same name +in different base dns only the first map encountered by autofs will +be listed. Similarly, if the map is a file map and there is more than +one map of the same name in different directories, only the first map +encountered will be listed. + +If the map type is an old style multi-map and any one of the map +names in the multi-map entry matches the given map name the entries +that would be used by autofs for the whole multi-map will be listed. +.TP +.I "\-O, \-\-global-options" +Allows the specification of global mount options used for all master +map entries. These options will either replace or be appened to options +given in a master map entry depending on the APPEND_OPTIONS configuration +setting. +.TP +.I "\-V, \-\-version" +Display the version number, then exit. +.TP +.I "\-l, \-\-set-log-priority priority path [path,...]" +Set the daemon log priority to the specified value. Valid values include +the numbers 0-7, or the strings emerg, alert, crit, err, warning, notice, +info, or debug. Log level debug will log everything, log levels info, warn +(or warning), or notice with enable the daemon verbose logging. Any other +level will set basic logging. Note that enabling debug or verbose +logging in the autofs global configuration will override dynamic log level +changes. For example, if verbose logging is set in the configuration then +attempting to set logging to basic logging, by using alert, crit, err +or emerg won't stop the verbose logging. However, setting logging to debug +will lead to everything (debug logging) being logged witch can then also +be disabled, returning the daemon to verbose logging. This option can be +specified to change the logging priority of an already running automount +process. +.P +The \fIpath\fP argument corresponds to the automounted +path name as specified in the master map. +.TP +.I "\-C, \-\-dont-check-daemon" +Don't check if the daemon is currently running (see NOTES). +.TP +.I "\-F, \-\-force" +Force an unlink umount of existing mounts under autofs managed mount points +during startup. This can cause problems for processes with working directories +within these mounts (see NOTES). +.SH ARGUMENTS +\fBautomount\fP takes one optional argument, the name of the master map to +use. +.TP +\fBmaster_map\fP +Location for autofs master map that defines autofs managed mount points +and the mount maps they will use. The default is +.nh +\fBauto.master\fP. +.hy +.RE +.SH NOTES +If the \fBautomount\fP daemon catches a USR1 signal, it will umount all +currently unused autofs managed mounted file systems and continue running +(forced expire). If it catches the TERM signal it will umount +all unused autofs managed mounted file systems and exit if there are +no remaining busy file systems. If autofs has been compiled with the +option to ignore busy mounts on exit it will exit leaving any busy +mounts in place otherwise busy file systems will not be umounted +and autofs will not exit. +Alternatively, if autofs has been compiled with the option to enable +forced shutdown then a USR2 signal to the daemon will cause all +mounts to be umounted and any busy mounts to be forcibly umounted, +including autofs mount point directories (summary execution). Note +that the forced umount is an unlink operation and the actual umount +will not happen in the kernel until active file handles are released. +The daemon also responds to a HUP signal which triggers an update of +the maps for each mount point. +.P +If any autofs mount point directories are busy when the daemon is sent +an exit signal the daemon will not exit. The exception to this is +if autofs has been built with configure options to either ignore busy +mounts at exit or force umount at exit. If the ignore busy mounts at +exit option is used the filesystems will be left in a catatonic +(non-functional) state and can be manually umounted when they become +unused. If the force umount at exit option is used the filesystems +will be umounted but the mount will not be released by the kernel +until they are no longer in use by the processes that held them busy. +If automount managed filesystems are found mounted when autofs is +started they will be recovered unless they are no longer present in +the map in which case they need to umounted manually. +.P +If the option to disable the check to see if the daemon is already +running is used be aware that autofs currently may not function correctly +for certain types of automount maps. The mounts of the separate daemons +might interfere with one another. The implications of running multiple +daemon instances needs to be checked and tested before we can say this +is supported. +.P +If the option to force an unlink of mounts at startup is used then processes +whose working directory is within unlinked automounted directories will not +get the correct pwd from the system. This is because, after the mount is +unlinked from the mount tree, anything that needs to walk back up the mount +tree to construct a path, such as getcwd(2) and the proc filesystem +/proc//cwd, cannot work because the point from which the path is +constructed has been detached from the mount tree. +.SH "SEE ALSO" +.BR autofs (5), +.BR autofs (8), +.BR autofs.conf (5), +.BR auto.master (5), +.BR mount (8). +.BR autofs_ldap_auth.conf (5) +.SH BUGS +Don't know, I've fixed everything I know about. + +The documentation could be better. + +Please report other bugs along with a detailed description to +. Visit http://vger.kernel.org/vger-lists.html#autofs +for information about the list. +.SH AUTHOR +H. Peter Anvin and Ian Kent . diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..d9ab06c --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,140 @@ +# +# Makefile for autofs +# + +-include ../Makefile.conf +include ../Makefile.rules + +SRCS := lookup_file.c lookup_program.c lookup_userhome.c \ + lookup_multi.c lookup_hosts.c lookup_dir.c \ + parse_sun.c parse_amd.c \ + mount_generic.c mount_nfs.c mount_afs.c mount_autofs.c \ + mount_changer.c mount_bind.c + +MODS := lookup_file.so lookup_program.so lookup_userhome.so \ + lookup_multi.so lookup_hosts.so lookup_dir.so \ + parse_sun.so parse_amd.so \ + mount_generic.so mount_nfs.so mount_afs.so mount_autofs.so \ + mount_changer.so mount_bind.so + +YACCSRC = amd_tok.c amd_parse.tab.c amd_parse.tab.h + +ifeq ($(EXT2FS), 1) + SRCS += mount_ext2.c + MODS += mount_ext2.so +else + ifeq ($(EXT3FS), 1) + SRCS += mount_ext2.c + MODS += mount_ext2.so + endif +endif + +ifeq ($(HESIOD), 1) + SRCS += lookup_hesiod.c parse_hesiod.c + MODS += lookup_hesiod.so parse_hesiod.so +endif + +ifeq ($(NISPLUS), 1) + SRCS += lookup_nisplus.c + MODS += lookup_nisplus.so +endif + +ifeq ($(YPCLNT), 1) + SRCS += lookup_yp.c + MODS += lookup_yp.so +endif + +ifeq ($(LDAP), 1) + SRCS += lookup_ldap.c + MODS += lookup_ldap.so + LDAP_FLAGS += $(XML_FLAGS) -DLDAP_THREAD_SAFE + LIBLDAP += $(XML_LIBS) + ifeq ($(SASL), 1) + SASL_OBJ = cyrus-sasl.o cyrus-sasl-extern.o + LDAP_FLAGS += $(SASL_FLAGS) $(KRB5_FLAGS) + LIBLDAP += $(LIBSASL) $(KRB5_LIBS) + endif +endif + +ifeq ($(SSSD), 1) + CFLAGS += -DSSS_LIB_DIR=\"$(ssslibdir)\" + SRCS += lookup_sss.c + MODS += lookup_sss.so +endif + +CFLAGS += -I../include -I../lib -fPIC -D_GNU_SOURCE +CFLAGS += -DAUTOFS_LIB_DIR=\"$(autofslibdir)\" +CFLAGS += -DAUTOFS_MAP_DIR=\"$(autofsmapdir)\" + +all: $(MODS) + +clean: + rm -f *.o *.s *.so *~ *.output $(YACCSRC) + +# mount_smbfs.so is an obsolete module which must be removed +install: all + install -d -m 755 $(INSTALLROOT)$(autofslibdir) + install -c $(MODS) -m 755 $(INSTALLROOT)$(autofslibdir) + -rm -f $(INSTALLROOT)$(autofslibdir)/mount_smbfs.so + ln -fs lookup_file.so $(INSTALLROOT)$(autofslibdir)/lookup_files.so + ln -fs lookup_yp.so $(INSTALLROOT)$(autofslibdir)/lookup_nis.so +ifeq ($(LDAP), 1) + ln -fs lookup_ldap.so $(INSTALLROOT)$(autofslibdir)/lookup_ldaps.so +endif + ln -fs mount_nfs.so $(INSTALLROOT)$(autofslibdir)/mount_nfs4.so +ifeq ($(EXT2FS), 1) + ifeq ($(EXT3FS), 1) + ln -fs mount_ext2.so $(INSTALLROOT)$(autofslibdir)/mount_ext3.so + endif + ifeq ($(EXT4FS), 1) + ln -fs mount_ext2.so $(INSTALLROOT)$(autofslibdir)/mount_ext4.so + endif +else ifeq ($(EXT3FS), 1) + mv $(INSTALLROOT)$(autofslibdir)/mount_ext2.so $(INSTALLROOT)$(autofslibdir)/mount_ext3.so + ifeq ($(EXT4FS), 1) + ln -fs mount_ext3.so $(INSTALLROOT)$(autofslibdir)/mount_ext4.so + endif +else ifeq ($(EXT4FS), 1) + mv $(INSTALLROOT)$(autofslibdir)/mount_ext2.so $(INSTALLROOT)$(autofslibdir)/mount_ext4.so +endif + +amd_tok.c: amd_tok.l + $(LEX) -o$@ -Pamd_ $? + +amd_tok.o: amd_tok.c amd_parse.tab.h + +amd_parse.tab.c amd_parse.tab.h: amd_parse.y + $(YACC) -v -d -p amd_ -b amd_parse $? + +amd_parse.tab.o: amd_parse.tab.c amd_parse.tab.h + +parse_amd.so: parse_amd.c amd_parse.tab.o amd_tok.o + $(CC) $(SOLDFLAGS) $(CFLAGS) -o parse_amd.so \ + parse_amd.c amd_parse.tab.o amd_tok.o $(LDFLAGS) $(AUTOFS_LIB) $(LIBS) + $(STRIP) parse_amd.so + +# +# Ad hoc compilation rules for modules which need auxilliary libraries +# +lookup_hesiod.so: lookup_hesiod.c + $(CC) $(SOLDFLAGS) $(CFLAGS) $(HESIOD_FLAGS) -o lookup_hesiod.so \ + lookup_hesiod.c $(LDFLAGS) $(AUTOFS_LIB) $(LIBHESIOD) $(LIBRESOLV) $(LIBS) + $(STRIP) lookup_hesiod.so + +cyrus-sasl.o: cyrus-sasl.c + $(CC) $(CFLAGS) $(LDAP_FLAGS) -c $< + +cyrus-sasl-extern.o: cyrus-sasl-extern.c + $(CC) $(CFLAGS) $(LDAP_FLAGS) -c $< + +lookup_ldap.so: lookup_ldap.c dclist.o base64.o $(SASL_OBJ) + $(CC) $(SOLDFLAGS) $(CFLAGS) $(LDAP_FLAGS) -o lookup_ldap.so \ + lookup_ldap.c dclist.o base64.o $(SASL_OBJ) \ + $(LDFLAGS) $(AUTOFS_LIB) $(LIBLDAP) $(LIBRESOLV) $(LIBS) + $(STRIP) lookup_ldap.so + +mount_nfs.so: mount_nfs.c replicated.o + $(CC) $(SOLDFLAGS) $(CFLAGS) -o mount_nfs.so \ + mount_nfs.c replicated.o $(LDFLAGS) $(AUTOFS_LIB) $(LIBS) + $(STRIP) mount_nfs.so + diff --git a/modules/amd_parse.y b/modules/amd_parse.y new file mode 100644 index 0000000..e99820b --- /dev/null +++ b/modules/amd_parse.y @@ -0,0 +1,702 @@ +%{ +/* ----------------------------------------------------------------------- * + * + * Copyright 2013 Ian Kent + * Copyright 2013 Red Hat, Inc. + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" +#include "parse_amd.h" +#include "log.h" + +#define MAX_OPTS_LEN 1024 +#define MAX_ERR_LEN 512 + +static pthread_mutex_t parse_mutex = PTHREAD_MUTEX_INITIALIZER; + +extern FILE *amd_in; +extern char *amd_text; +extern int amd_lex(void); +extern void amd_set_scan_buffer(const char *); + +static char *amd_strdup(char *); +static void local_init_vars(void); +static void local_free_vars(void); + +static int amd_error(const char *s); +static int amd_notify(const char *s); +static int amd_info(const char *s); +static int amd_msg(const char *s); + +static int add_location(void); +static int make_selector(char *name, + char *value1, char *value2, + unsigned int compare); +static void add_selector(struct selector *selector); + +static struct amd_entry entry; +static struct list_head *entries; +static struct autofs_point *pap; +struct substvar *psv; +static char opts[MAX_OPTS_LEN]; +static void prepend_opt(char *, char *); +static char msg_buf[MAX_ERR_LEN]; + +#define YYDEBUG 0 + +#ifndef YYENABLE_NLS +#define YYENABLE_NLS 0 +#endif +#ifndef YYLTYPE_IS_TRIVIAL +#define YYLTYPE_IS_TRIVIAL 0 +#endif + +#if YYDEBUG +static int amd_fprintf(FILE *, char *, ...); +#undef YYFPRINTF +#define YYFPRINTF amd_fprintf +#endif + +%} + +%union { + char strtype[2048]; + int inttype; + long longtype; +} + +%token COMMENT +%token SEPERATOR +%token SPACE +%token HYPHEN +%token IS_EQUAL +%token CUT +%token NOT_EQUAL +%token COMMA +%token OPTION_ASSIGN +%token LBRACKET +%token RBRACKET +%token NOT +%token NILL + +%token MAP_OPTION +%token MAP_TYPE +%token CACHE_OPTION +%token FS_TYPE +%token FS_OPTION +%token FS_OPT_VALUE +%token MNT_OPTION +%token SELECTOR +%token SELECTOR_VALUE +%token SEL_ARG_VALUE +%token OPTION +%token MACRO +%token OTHER + +%type options + +%start file + +%% + +file: { +#if YYDEBUG != 0 + amd_debug = YYDEBUG; +#endif + memset(opts, 0, sizeof(opts)); + } line + ; + +line: + | location_selection_list + ; + +location_selection_list: location + { + if (!add_location()) { + amd_msg("failed to allocate new location"); + YYABORT; + } + } + | location_selection_list SPACE location + { + if (!add_location()) { + amd_msg("failed to allocate new location"); + YYABORT; + } + } + | location_selection_list SPACE CUT SPACE location + { + entry.flags |= AMD_ENTRY_CUT; + if (!add_location()) { + amd_msg("failed to allocate new location"); + YYABORT; + } + } + ; + +location: location_entry + { + } + | HYPHEN location_entry + { + entry.flags |= AMD_DEFAULTS_MERGE; + } + | HYPHEN + { + entry.flags |= AMD_DEFAULTS_RESET; + } + ; + +location_entry: selector_or_option + { + } + | location_entry SEPERATOR selector_or_option + { + } + | location_entry SEPERATOR + { + } + ; + +selector_or_option: selection + { + } + | option_assignment + { + } + | OTHER + { + amd_notify($1); + YYABORT; + } + ; + +selection: SELECTOR IS_EQUAL SELECTOR_VALUE + { + if (!make_selector($1, $3, NULL, SEL_COMP_EQUAL)) { + amd_notify($1); + YYABORT; + } + } + | SELECTOR IS_EQUAL + { + if (!make_selector($1, "", NULL, SEL_COMP_EQUAL)) { + amd_notify($1); + YYABORT; + } + } + | SELECTOR NOT_EQUAL SELECTOR_VALUE + { + if (!make_selector($1, $3, NULL, SEL_COMP_NOTEQUAL)) { + amd_notify($1); + YYABORT; + } + } + | SELECTOR NOT_EQUAL + { + if (!make_selector($1, "", NULL, SEL_COMP_EQUAL)) { + amd_notify($1); + YYABORT; + } + } + | SELECTOR LBRACKET SEL_ARG_VALUE RBRACKET + { + if (!make_selector($1, $3, NULL, SEL_COMP_NONE)) { + amd_notify($1); + YYABORT; + } + } + | SELECTOR LBRACKET SEL_ARG_VALUE COMMA SEL_ARG_VALUE RBRACKET + { + if (!make_selector($1, $3, $5, SEL_COMP_NONE)) { + amd_notify($1); + YYABORT; + } + } + | NOT SELECTOR LBRACKET SEL_ARG_VALUE RBRACKET + { + if (!make_selector($2, $4, NULL, SEL_COMP_NOT)) { + amd_notify($2); + YYABORT; + } + } + | NOT SELECTOR LBRACKET SEL_ARG_VALUE COMMA SEL_ARG_VALUE RBRACKET + { + if (!make_selector($2, $4, $6, SEL_COMP_NOT)) { + amd_notify($2); + YYABORT; + } + } + ; + +option_assignment: MAP_OPTION OPTION_ASSIGN FS_TYPE + { + if (!strcmp($3, "auto")) { + entry.flags |= AMD_MOUNT_TYPE_AUTO; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "nfs") || + !strcmp($3, "nfs4")) { + entry.flags |= AMD_MOUNT_TYPE_NFS; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "nfsl")) { + entry.flags |= AMD_MOUNT_TYPE_NFSL; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "link")) { + entry.flags |= AMD_MOUNT_TYPE_LINK; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "linkx")) { + entry.flags |= AMD_MOUNT_TYPE_LINKX; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "host")) { + entry.flags |= AMD_MOUNT_TYPE_HOST; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "lofs")) { + entry.flags |= AMD_MOUNT_TYPE_LOFS; + entry.type = amd_strdup("bind"); + } else if (!strcmp($3, "xfs")) { + entry.flags |= AMD_MOUNT_TYPE_XFS; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "ext2") || + !strcmp($3, "ext3") || + !strcmp($3, "ext4")) { + entry.flags |= AMD_MOUNT_TYPE_EXT; + entry.type = amd_strdup($3); + } else if (!strcmp($3, "ufs")) { + entry.flags |= AMD_MOUNT_TYPE_UFS; + entry.type = conf_amd_get_linux_ufs_mount_type(); + } else if (!strcmp($3, "cdfs")) { + entry.flags |= AMD_MOUNT_TYPE_CDFS; + entry.type = amd_strdup("iso9660"); + } else if (!strcmp($3, "jfs") || + !strcmp($3, "nfsx") || + !strcmp($3, "program") || + !strcmp($3, "lustre") || + !strcmp($3, "direct")) { + sprintf(msg_buf, "file system type %s is " + "not yet implemented", $3); + amd_msg(msg_buf); + YYABORT; + } else if (!strcmp($3, "cachefs")) { + sprintf(msg_buf, "file syatem %s is not " + "supported by autofs, ignored", $3); + amd_msg(msg_buf); + } else { + amd_notify($1); + YYABORT; + } + } + | MAP_OPTION OPTION_ASSIGN MAP_TYPE + { + if (!strcmp($3, "file") || + !strcmp($3, "nis") || + !strcmp($3, "nisplus") || + !strcmp($3, "ldap") || + !strcmp($3, "hesiod")) + entry.map_type = amd_strdup($3); + else if (!strcmp($3, "exec")) + /* autofs uses "program" for "exec" map type */ + entry.map_type = amd_strdup("program"); + else if (!strcmp($3, "passwd")) { + sprintf(msg_buf, "map type %s is " + "not yet implemented", $3); + amd_msg(msg_buf); + YYABORT; + } else if (!strcmp($3, "ndbm") || + !strcmp($3, "union")) { + sprintf(msg_buf, "map type %s is not " + "supported by autofs", $3); + amd_msg(msg_buf); + YYABORT; + } else { + amd_notify($1); + YYABORT; + } + } + | MAP_OPTION OPTION_ASSIGN FS_OPT_VALUE + { + if (!strcmp($1, "fs")) + entry.fs = amd_strdup($3); + else if (!strcmp($1, "sublink")) + entry.sublink = amd_strdup($3); + else if (!strcmp($1, "pref")) { + if (!strcmp($3, "null")) + entry.pref = amd_strdup(""); + else + entry.pref = amd_strdup($3); + } else { + amd_notify($1); + YYABORT; + } + } + | MAP_OPTION OPTION_ASSIGN + { + if (!strcmp($1, "fs")) + entry.fs = amd_strdup(""); + else { + amd_notify($1); + YYABORT; + } + } + | FS_OPTION OPTION_ASSIGN FS_OPT_VALUE + { + if (!strcmp($1, "rhost")) + entry.rhost = amd_strdup($3); + else if (!strcmp($1, "rfs")) + entry.rfs = amd_strdup($3); + else if (!strcmp($1, "dev")) + entry.dev = amd_strdup($3); + else if (!strcmp($1, "mount") || + !strcmp($1, "unmount") || + !strcmp($1, "umount")) { + amd_info("file system type program is not " + "yet implemented, option ignored"); + YYABORT; + } else if (!strcmp($1, "delay") || + !strcmp($1, "cachedir")) { + sprintf(msg_buf, "option %s is not used by autofs", $1); + amd_info(msg_buf); + } else { + amd_notify($1); + YYABORT; + } + } + | FS_OPTION OPTION_ASSIGN + { + if (!strcmp($1, "rhost")) + entry.rhost = amd_strdup(""); + else if (!strcmp($1, "rfs")) + entry.rfs = amd_strdup(""); + else if (!strcmp($1, "dev")) + entry.dev = amd_strdup(""); + else { + amd_notify($1); + YYABORT; + } + } + | MNT_OPTION OPTION_ASSIGN options + { + if (!strcmp($1, "opts")) + entry.opts = amd_strdup(opts); + else if (!strcmp($1, "addopts")) + entry.addopts = amd_strdup(opts); + else if (!strcmp($1, "remopts")) + entry.remopts = amd_strdup(opts); + else { + amd_notify($1); + YYABORT; + } + memset(opts, 0, sizeof(opts)); + } + | MNT_OPTION OPTION_ASSIGN + { + memset(opts, 0, sizeof(opts)); + if (!strcmp($1, "opts")) + entry.opts = amd_strdup(""); + else if (!strcmp($1, "addopts")) + entry.addopts = amd_strdup(""); + else if (!strcmp($1, "remopts")) + entry.remopts = amd_strdup(""); + else { + amd_notify($1); + YYABORT; + } + } + | MAP_OPTION OPTION_ASSIGN CACHE_OPTION + { + if (strncmp($3, "inc", 3)) + entry.cache_opts = AMD_CACHE_OPTION_INC; + else if (strncmp($3, "all", 3)) + entry.cache_opts = AMD_CACHE_OPTION_ALL; + else if (strncmp($3, "re", 2)) + entry.cache_opts = AMD_CACHE_OPTION_REGEXP; + if (strstr($3, "sync")) + entry.cache_opts |= AMD_CACHE_OPTION_SYNC; + } + ; + +options: OPTION + { + if (!strcmp($1, "fullybrowsable") || + !strcmp($1, "nounmount") || + !strcmp($1, "unmount")) { + sprintf(msg_buf, "option %s is not currently " + "implemented, ignored", $1); + amd_info(msg_buf); + } else if (!strncmp($1, "ping=", 5) || + !strncmp($1, "retry=", 6) || + !strcmp($1, "public") || + !strcmp($1, "softlookup") || + !strcmp($1, "xlatecookie")) { + sprintf(msg_buf, "option %s is not used by " + "autofs, ignored", $1); + amd_info(msg_buf); + } else if (!strncmp($1, "utimeout=", 9)) { + if (entry.flags & AMD_MOUNT_TYPE_AUTO) { + char *opt = $1; + prepend_opt(opts, ++opt); + } else { + sprintf(msg_buf, "umount timeout can't be " + "used for other than type " + "\"auto\" with autofs, " + "ignored"); + amd_info(msg_buf); + } + } else + prepend_opt(opts, $1); + } + | OPTION COMMA options + { + prepend_opt(opts, $1); + } + | OPTION COMMA + { + prepend_opt(opts, $1); + } + ; + +%% + +static void prepend_opt(char *dest, char *opt) +{ + char new[MAX_OPTS_LEN]; + strcpy(new, opt); + if (*dest != '\0') { + strcat(new, ","); + strcat(new, dest); + } + memmove(dest, new, strlen(new)); +} + +#if YYDEBUG +static int amd_fprintf(FILE *f, char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vsyslog(LOG_DEBUG, msg, ap); + va_end(ap); + return 1; +} +#endif + +static char *amd_strdup(char *str) +{ + char *tmp; + + tmp = strdup(str); + if (!tmp) + amd_error("memory allocation error"); + return tmp; +} + +static int amd_error(const char *s) +{ + if (strcmp(s, "syntax")) + logmsg("syntax error in location near [ %s ]\n", amd_text); + else + logmsg("%s while parsing location.\n", s); + return 0; +} + +static int amd_notify(const char *s) +{ + logmsg("syntax error in location near [ %s ]\n", s); + return(0); +} + +static int amd_info(const char *s) +{ + info(pap->logopt, "%s\n", s); + return 0; +} + +static int amd_msg(const char *s) +{ + logmsg("%s\n", s); + return 0; +} + +static void local_init_vars(void) +{ + memset(&entry, 0, sizeof(entry)); + entry.cache_opts = AMD_CACHE_OPTION_NONE; + memset(opts, 0, sizeof(opts)); +} + +static void local_free_vars(void) +{ + clear_amd_entry(&entry); + return; +} + +static void add_selector(struct selector *selector) +{ + struct selector *s = entry.selector; + + if (!s) { + entry.selector = selector; + return; + } + + while (s->next) + s = s->next; + + selector->next = s; + entry.selector = selector; + + return; +} + +static int make_selector(char *name, + char *value1, char *value2, + unsigned int compare) +{ + struct selector *s; + char *tmp; + + if (!sel_lookup(name)) + return 0; + + s = get_selector(name); + if (!s) + return 0; + + if (s->sel->flags & SEL_FLAG_MACRO) { + tmp = amd_strdup(value1); + if (!tmp) + goto error; + s->comp.value = tmp; + } else if (s->sel->flags & SEL_FLAG_FUNC1) { + if (!value1) + tmp = NULL; + else { + char *tmp = amd_strdup(value1); + if (!tmp) + goto error; + } + s->func.arg1 = tmp; + } else if (s->sel->flags & SEL_FLAG_FUNC2) { + char *tmp = amd_strdup(value1); + if (!tmp) + goto error; + s->func.arg1 = tmp; + if (value2) { + tmp = amd_strdup(value2); + if (tmp) + s->func.arg2 = tmp; + } + } + s->compare = compare; + + add_selector(s); + + return 1; +error: + free_selector(s); + return 0; +} + +void amd_init_scan(void) +{ +} + +static void parse_mutex_lock(void) +{ + int status = pthread_mutex_lock(&parse_mutex); + if (status) + fatal(status); + return; +} + +static void parse_mutex_unlock(void *arg) +{ + int status = pthread_mutex_unlock(&parse_mutex); + if (status) + fatal(status); + return; +} + +static int add_location(void) +{ + struct amd_entry *new; + + new = new_amd_entry(psv); + if (!new) + return 0; + + if (entry.path) { + free(new->path); + new->path = entry.path; + } + new->flags = entry.flags; + new->type = entry.type; + new->map_type = entry.map_type; + new->pref = entry.pref; + new->fs = entry.fs; + new->rhost = entry.rhost; + new->rfs = entry.rfs; + new->dev = entry.dev; + new->opts = entry.opts; + new->addopts = entry.addopts; + new->remopts = entry.remopts; + new->sublink = entry.sublink; + new->selector = entry.selector; + list_add_tail(&new->list, entries); + memset(&entry, 0, sizeof(struct amd_entry)); + + return 1; +} + +int amd_parse_list(struct autofs_point *ap, + const char *buffer, struct list_head *list, + struct substvar **sv) +{ + char *buf; + size_t len; + int ret; + + len = strlen(buffer) + 2; + buf = malloc(len); + if (!buf) + return 0; + strcpy(buf, buffer); + + parse_mutex_lock(); + pthread_cleanup_push(parse_mutex_unlock, NULL); + + pap = ap; + psv = *sv; + entries = list; + amd_set_scan_buffer(buf); + + local_init_vars(); + ret = amd_parse(); + local_free_vars(); + *sv = psv; + + pthread_cleanup_pop(1); + free(buf); + + return ret; +} diff --git a/modules/amd_tok.l b/modules/amd_tok.l new file mode 100644 index 0000000..2eac5cb --- /dev/null +++ b/modules/amd_tok.l @@ -0,0 +1,438 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2013 Ian Kent + * Copyright 2013 Red Hat, Inc. + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +%s START MAPOPTVAL FSOPTVAL MNTOPTVAL SELOPTVAL SELARGVAL + +%{ + +static int reset_start_state = 0; + +#ifdef ECHO +# undef ECHO +#endif +static void amd_echo(void); /* forward definition */ +static void amd_copy_buffer(void); +#define ECHO amd_echo() +int amd_wrap(void); + +#include +#include +#include +#include +#include "amd_parse.tab.h" + +int amd_lex(void); +int mad_wrap(void); + +#define YY_SKIP_YYWRAP + +#ifndef YY_STACK_USED +#define YY_STACK_USED 0 +#endif +#ifndef YY_ALWAYS_INTERACTIVE +#define YY_ALWAYS_INTERACTIVE 0 +#endif +#ifndef YY_NEVER_INTERACTIVE +#define YY_NEVER_INTERACTIVE 0 +#endif +#ifndef YY_MAIN +#define YY_MAIN 0 +#endif + +void amd_set_scan_buffer(const char *); +static const char *line = NULL; + +#ifdef FLEX_SCANNER +static const char *line_pos = NULL; +static const char *line_lim = NULL; +int amd_yyinput(char *, int); + +#undef YY_INPUT +#define YY_INPUT(b, r, ms) (r = amd_yyinput(b, ms)) +#else +#undef input +#undef unput +#define input() (*(char *) line++) +#define unput(c) (*(char *) --line = c) +#endif + +%} + +%option nounput + +NL \r?\n +OPTWS [[:blank:]]* +OTHR [^!;:=/|\- \t\r\n#]* + +V4NUM ([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5]) + +MACRO (\$\{([[:alpha:]_/]([[:alnum:]_\-])([[:alnum:]_\-/])*)\}) +QSTR (\"([^"\\]|\\.)*\") +OSTR ([[:alpha:]]([[:alnum:]_\-])+) +FSTR ([[:alnum:]_/\.]([[:alnum:]_\-/\+\.]|(\\.))*) +VSTR (([[:alnum:]_\-\:/\.])+) +SSTR ([[:alpha:]]([[:alnum:]\-\.])+) +IP4ADDR ({V4NUM}\.((({V4NUM}\.){0,2}){V4NUM}?)) +V4MASK ({IP4ADDR}|([1-2][0-9]|3[0-2]|[1-9])) +IP6ADDR ((([A-Fa-f0-9]{1,4}\:\:?){1,7}[A-Fa-f0-9]{1,4})|(\:\:1)) +V6MASK (12[0-8]|1[0-1][0-9]|[1-9][0-9]|[1-9]) + +FOPT (({QSTR}|{FSTR}|{MACRO})+) +OPTS ({OSTR}(=({VSTR}|{QSTR}|{MACRO})+)?) +SOPT (({SSTR}|{QSTR}|{MACRO})+) +NOPT ({SSTR}|(({IP4ADDR}(\/{V4MASK})?)|({IP6ADDR}(\/{V6MASK})?))) + +MAPOPT (fs|type|maptype|pref|sublink|cache) +MNTOPT (opts|addopts|remopts) +FSOPTS (rhost|rfs|dev|cachedir|mount|unmount|umount|delay) +CHEOPT ((mapdefault|none|inc|re|regexp|all)(,sync)?) +MAPTYPE (file|nis|nisplus|ldap|hesiod|exec|ndbm|passwd|union) +FSTYPE_LOCAL (link|linkx|lofs|ufs|ext2|ext3|ext4|xfs|jfs|cdfs|cachefs) +FSTYPE_NET (nfs|nfsx|nfsl|host) +FSTYPE (auto|program|direct|lustre|{FSTYPE_LOCAL}|{FSTYPE_NET}) + +OSSEL (arch|karch|os|osver|full_os|vendor) +HSTSEL (host|hostd|domain|byte|cluster) +NETSEL (netnumber|network|wire|in_network) +USRSEL (uid|gid) +MAPSEL (key|map|path) +OTRSEL (autodir|dollar) +BOLSEL (true|false) + +SELOPT ({OSSEL}|{HSTSEL}|{USRSEL}|{MAPSEL}|{OTRSEL}) +SEL1ARG (xhost|exists|{NETSEL}|{BOLSEL}) +SEL2ARG (netgrp|netgrpd) + +CUTSEP (\|\||\/) + +%% + +%{ + if (reset_start_state) { + BEGIN START; + reset_start_state = 0; + } +%} + +{ + {NL} | + \x00 { } + + {MAPOPT} { + BEGIN(MAPOPTVAL); + amd_copy_buffer(); + return MAP_OPTION; + + } + + {FSOPTS} { + BEGIN(FSOPTVAL); + amd_copy_buffer(); + return FS_OPTION; + } + + {MNTOPT} { + BEGIN(MNTOPTVAL); + amd_copy_buffer(); + return MNT_OPTION; + } + + {SELOPT} { + BEGIN(SELOPTVAL); + amd_copy_buffer(); + return SELECTOR; + } + + "!"/({SEL1ARG}|{SEL2ARG}) { return NOT; } + + {SEL1ARG} { + BEGIN(SELARGVAL); + amd_copy_buffer(); + return SELECTOR; + } + + {SEL2ARG} { + BEGIN(SELARGVAL); + amd_copy_buffer(); + return SELECTOR; + } + + {CUTSEP} { return CUT; } + + "-" { return HYPHEN; } + + {OPTWS} { return SPACE; } + + #.* { return COMMENT; } + + {OTHR} { + amd_copy_buffer(); + return OTHER; + } +} + +{ + {NL} { + BEGIN(START); + yyless(1); + } + + \x00 { + BEGIN(START); + return SEPERATOR; + yyless(1); + } + + ";" { + BEGIN(START); + return SEPERATOR; + } + + {OPTWS} { + BEGIN(START); + return SPACE; + } + + ":=" { return OPTION_ASSIGN; } + + {FSTYPE} { + amd_copy_buffer(); + return FS_TYPE; + } + + {MAPTYPE} { + amd_copy_buffer(); + return MAP_TYPE; + } + + {CHEOPT} { + amd_copy_buffer(); + return CACHE_OPTION; + } + + {FOPT} { + amd_copy_buffer(); + return FS_OPT_VALUE; + } +} + +{ + {NL} { + BEGIN(START); + yyless(1); + } + + \x00 { + BEGIN(START); + return SEPERATOR; + yyless(1); + } + + ";" { + BEGIN(START); + return SEPERATOR; + } + + {OPTWS} { + BEGIN(START); + return SPACE; + } + + ":=" { return OPTION_ASSIGN; } + + {FOPT} { + amd_copy_buffer(); + return FS_OPT_VALUE; + } +} + +{ + {NL} { + BEGIN(START); + yyless(1); + } + + \x00 { + BEGIN(START); + return SEPERATOR; + yyless(1); + } + + ";" { + BEGIN(START); + return SEPERATOR; + } + + {OPTWS} { + BEGIN(START); + return SPACE; + } + + (:=)(,+)? { return OPTION_ASSIGN; } + + ,+ { return COMMA; } + + {OPTS} { + amd_copy_buffer(); + return OPTION; + } +} + +{ + {NL} { + BEGIN(START); + yyless(1); + } + + \x00 { + BEGIN(START); + return SEPERATOR; + yyless(1); + } + + ";" { + BEGIN(START); + return SEPERATOR; + } + + {OPTWS} { + BEGIN(START); + return SPACE; + } + + "==" { return IS_EQUAL; } + + "!=" { return NOT_EQUAL; } + + {SOPT} { + amd_copy_buffer(); + return SELECTOR_VALUE; + } +} + +{ + {NL} { + BEGIN(START); + yyless(1); + } + + \x00 { + BEGIN(START); + return SEPERATOR; + yyless(1); + } + + ";" { + BEGIN(START); + return SEPERATOR; + } + + "(" { return LBRACKET; } + + {NOPT} { + amd_copy_buffer(); + return SEL_ARG_VALUE; + } + + {SOPT}/"," { + amd_copy_buffer(); + return SEL_ARG_VALUE; + } + + "," { return COMMA; } + + {SOPT} { + amd_copy_buffer(); + return SEL_ARG_VALUE; + } + + {FOPT} { + amd_copy_buffer(); + return SEL_ARG_VALUE; + } + + ")" { return RBRACKET; } +} + +%% + +#include "automount.h" + +int amd_wrap(void) +{ + return 1; +} + +static void amd_copy_buffer(void) +{ + if (amd_leng < 2048) + strcpy(amd_lval.strtype, amd_text); + else { + strncpy(amd_lval.strtype, amd_text, 2047); + amd_lval.strtype[2047] = '\0'; + logmsg("warning: truncated option near %s\n", + &amd_lval.strtype[2030]); + } +} + +static void amd_echo(void) +{ + logmsg("%s\n", amd_text); + return; +} + +#ifdef FLEX_SCANNER + +void amd_set_scan_buffer(const char *buffer) +{ + YY_FLUSH_BUFFER; + reset_start_state = 1; + + line = buffer; + line_pos = &line[0]; + /* + * Ensure buffer is 1 greater than string and is zeroed before + * the parse so we can fit the extra NULL which allows us to + * explicitly match an end of line within the buffer (ie. the + * need for 2 NULLS when parsing in memeory buffers). + */ + line_lim = line + strlen(buffer) + 1; +} + +#define amd_min(a,b) (((a) < (b)) ? (a) : (b)) + +int amd_yyinput(char *buffer, int max_size) +{ + int n = amd_min(max_size, line_lim - line_pos); + + if (n > 0) { + memcpy(buffer, line_pos, n); + line_pos += n; + } + return n; +} + +#else + +void amd_set_scan_buffer(const char *buffer) +{ + line = buffer; +} + +#endif diff --git a/modules/base64.c b/modules/base64.c new file mode 100644 index 0000000..0bb356a --- /dev/null +++ b/modules/base64.c @@ -0,0 +1,225 @@ +#include + +/* + * characters used for Base64 encoding + */ +static const char *BASE64_CHARS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * encode three bytes using base64 (RFC 3548) + * + * @param triple three bytes that should be encoded + * @param result buffer of four characters where the result is stored + */ +static void _base64_encode_triple(char triple[3], char result[4]) +{ + int tripleValue, i; + + tripleValue = triple[0]; + tripleValue *= 256; + tripleValue += triple[1]; + tripleValue *= 256; + tripleValue += triple[2]; + + for (i=0; i<4; i++) { + result[3 - i] = BASE64_CHARS[tripleValue % 64]; + tripleValue /= 64; + } +} + +/* + * encode an array of bytes using Base64 (RFC 3548) + * + * @param source the source buffer + * @param sourcelen the length of the source buffer + * @param target the target buffer + * @param targetlen the length of the target buffer + * @return 1 on success, 0 otherwise + */ +int base64_encode(char *source, size_t sourcelen, char *target, size_t targetlen) +{ + /* check if the result will fit in the target buffer */ + if ((sourcelen + 2)/ 3*4 > targetlen - 1) + return 0; + + /* encode all full triples */ + while (sourcelen >= 3) { + _base64_encode_triple(source, target); + sourcelen -= 3; + source += 3; + target += 4; + } + + /* encode the last one or two characters */ + if (sourcelen > 0) { + unsigned char temp[3]; + memset(temp, 0, sizeof(temp)); + memcpy(temp, source, sourcelen); + _base64_encode_triple(temp, target); + target[3] = '='; + if (sourcelen == 1) + target[2] = '='; + target += 4; + } + + /* terminate the string */ + target[0] = 0; + + return 1; +} + +/* + * determine the value of a base64 encoding character + * + * @param base64char the character of which the value is searched + * @return the value in case of success (0-63), -1 on failure + */ +static int _base64_char_value(char base64char) +{ + if (base64char >= 'A' && base64char <= 'Z') + return base64char-'A'; + if (base64char >= 'a' && base64char <= 'z') + return base64char-'a'+26; + if (base64char >= '0' && base64char <= '9') + return base64char-'0'+2*26; + if (base64char == '+') + return 2*26+10; + if (base64char == '/') + return 2*26+11; + return -1; +} + +/* + * decode a 4 char base64 encoded byte triple + * + * @param quadruple the 4 characters that should be decoded + * @param result the decoded data + * @return lenth of the result (1, 2 or 3), 0 on failure + */ +static int _base64_decode_triple(char quadruple[4], char *result) +{ + int i, triple_value, bytes_to_decode = 3, only_equals_yet = 1; + int char_value[4]; + + for (i=0; i<4; i++) + char_value[i] = _base64_char_value(quadruple[i]); + + /* check if the characters are valid */ + for (i=3; i>=0; i--) { + if (char_value[i]<0) { + if (only_equals_yet && quadruple[i]=='=') { + /* we will ignore this character anyway, make it + * something that does not break our calculations */ + char_value[i]=0; + bytes_to_decode--; + continue; + } + return 0; + } + /* after we got a real character, no other '=' are allowed anymore */ + only_equals_yet = 0; + } + + /* if we got "====" as input, bytes_to_decode is -1 */ + if (bytes_to_decode < 0) + bytes_to_decode = 0; + + /* make one big value out of the partial values */ + triple_value = char_value[0]; + triple_value *= 64; + triple_value += char_value[1]; + triple_value *= 64; + triple_value += char_value[2]; + triple_value *= 64; + triple_value += char_value[3]; + + /* break the big value into bytes */ + for (i=bytes_to_decode; i<3; i++) + triple_value /= 256; + for (i=bytes_to_decode-1; i>=0; i--) { + result[i] = triple_value%256; + triple_value /= 256; + } + + return bytes_to_decode; +} + +/* + * decode base64 encoded data + * + * @param source the encoded data (zero terminated) + * @param target pointer to the target buffer + * @param targetlen length of the target buffer + * @return length of converted data on success, -1 otherwise + */ +size_t base64_decode(char *source, char *target, size_t targetlen) +{ + char *src, *tmpptr; + char quadruple[4], tmpresult[3]; + int i, tmplen = 3; + size_t converted = 0; + + /* concatinate '===' to the source to handle unpadded base64 data */ + src = malloc(strlen(source)+5); + if (src == NULL) + return -1; + strcpy(src, source); + strcat(src, "===="); + tmpptr = src; + + memset(target, 0, targetlen); + + /* convert as long as we get a full result */ + while (tmplen == 3) { + /* get 4 characters to convert */ + for (i = 0; i < 4; i++) { + /* skip invalid characters - we won't reach the end */ + while (*tmpptr != '=' && _base64_char_value(*tmpptr) < 0) + tmpptr++; + quadruple[i] = *(tmpptr++); + } + + /* convert the characters */ + tmplen = _base64_decode_triple(quadruple, tmpresult); + + /* check if the fit in the result buffer */ + if (targetlen < tmplen) { + free(src); + return -1; + } + + /* put the partial result in the result buffer */ + memcpy(target, tmpresult, tmplen); + target += tmplen; + targetlen -= tmplen; + converted += tmplen; + } + + free(src); + return converted; +} + +#if 0 +#include + +int main(int argc, char **argv) +{ + char *source = "testt(estt#wot%est'ing"; + int source_len = strlen(source); + char dest[80]; + int dest_len = 79; + char decoded[80]; + int decode_len = 79; + int len; + + printf("string %s\n", source); + + if (base64_encode(source, source_len, dest, dest_len)) + printf("encoded %s len %d\n", dest, strlen(dest)); + + len = base64_decode(dest, decoded, decode_len); + if (len != -1) + printf("decoded %s len %d\n", decoded, len); +} +#endif diff --git a/modules/cyrus-sasl-extern.c b/modules/cyrus-sasl-extern.c new file mode 100644 index 0000000..5bb3028 --- /dev/null +++ b/modules/cyrus-sasl-extern.c @@ -0,0 +1,117 @@ +/* + * cyrus-sasl-extern.c - Module for Cyrus sasl external authentication. + * + * Copyright 2010 Ian Kent + * Copyright 2010 Red Hat, Inc. + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + */ + +#include "config.h" + +#ifdef WITH_SASL +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lookup_ldap.h" + +struct values { + char *mech; + char *realm; + char *authcid; + char *authzid; + char *password; + char **resps; + int nresps; +}; + +static int interaction(unsigned flags, sasl_interact_t *interact, void *values) +{ + const char *val = interact->defresult; + struct values *vals = values; + + switch(interact->id) { + case SASL_CB_GETREALM: + if (values) + val = vals->realm; + break; + + case SASL_CB_AUTHNAME: + if (values) + val = vals->authcid; + break; + + case SASL_CB_PASS: + if (values) + val = vals->password; + break; + + case SASL_CB_USER: + if (values) + val = vals->authzid; + break; + + case SASL_CB_NOECHOPROMPT: + case SASL_CB_ECHOPROMPT: + break; + } + + if (val && !*val) + val = NULL; + + if (val || interact->id == SASL_CB_USER) { + interact->result = (val && *val) ? val : ""; + interact->len = strlen(interact->result); + } + + return LDAP_SUCCESS; +} + +int sasl_extern_interact(LDAP *ldap, unsigned flags, void *values, void *list) +{ + sasl_interact_t *interact = list; + + if (!ldap) + return LDAP_PARAM_ERROR; + + while (interact->id != SASL_CB_LIST_END) { + int rc = interaction(flags, interact, values); + if (rc) + return rc; + interact++; + } + + return LDAP_SUCCESS; +} + +int do_sasl_extern(LDAP *ldap, struct lookup_context *ctxt) +{ + int flags = LDAP_SASL_QUIET; + char *mech = ctxt->sasl_mech; + struct values values; + int rc; + + memset(&values, 0, sizeof(struct values)); + values.mech = mech; + + rc = ldap_sasl_interactive_bind_s(ldap, NULL, mech, NULL, NULL, + flags, sasl_extern_interact, &values); + return rc; +} +#endif diff --git a/modules/cyrus-sasl.c b/modules/cyrus-sasl.c new file mode 100644 index 0000000..cf596b8 --- /dev/null +++ b/modules/cyrus-sasl.c @@ -0,0 +1,1098 @@ +/* + Copyright 2005 Red Hat, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Red Hat, Inc., nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * cyrus-sasl.c + * + * Description: + * + * This file implements SASL authentication to an LDAP server for the + * following mechanisms: + * GSSAPI, EXTERNAL, ANONYMOUS, PLAIN, DIGEST-MD5, KERBEROS_V5, LOGIN + * The mechanism to use is specified in an external file, + * LDAP_AUTH_CONF_FILE. See the samples directory in the autofs + * distribution for an example configuration file. + * + * This file is written with the intent that it will work with both the + * openldap and the netscape ldap client libraries. + * + * Author: Nalin Dahyabhai + * Modified by Jeff Moyer to adapt it to autofs. + */ +#include +#include +#include +#include +#include +#include + +#include "automount.h" +#include "lookup_ldap.h" + +#ifndef LDAP_OPT_RESULT_CODE +#ifdef LDAP_OPT_ERROR_NUMBER +#define LDAP_OPT_RESULT_CODE LDAP_OPT_ERROR_NUMBER +#else +#error "Could not determine the proper value for LDAP_OPT_RESULT_CODE." +#endif +#endif + +#ifdef HAVE_KRB5_PRINCIPAL_GET_REALM +void _krb5_princ_realm(krb5_context context, krb5_const_principal princ, + const char **realm, int *len) +{ + *realm = krb5_principal_get_realm(context, princ); + if (*realm) + *len = strlen(*realm); + else + *len = 0; + return; +} +#else +void _krb5_princ_realm(krb5_context context, krb5_const_principal princ, + const char **realm, int *len) +{ + const krb5_data *data; + + data = krb5_princ_realm(context, princ); + if (data) { + *realm = data->data; + *len = data->length; + } else { + *realm = NULL; + *len = 0; + } + return; +} +#endif + + +/* + * Once a krb5 credentials cache is setup, we need to set the KRB5CCNAME + * environment variable so that the library knows where to find it. + */ +static const char *krb5ccenv = "KRB5CCNAME"; +static const char *krb5ccval = "MEMORY:_autofstkt"; +static const char *default_client = "autofsclient"; +static pthread_mutex_t krb5cc_mutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned int krb5cc_in_use = 0; + +static int sasl_log_func(void *, int, const char *); +static int getpass_func(sasl_conn_t *, void *, int, sasl_secret_t **); +static int getuser_func(void *, int, const char **, unsigned *); + +static sasl_callback_t callbacks[] = { + { SASL_CB_LOG, &sasl_log_func, NULL }, + { SASL_CB_USER, &getuser_func, NULL }, + { SASL_CB_AUTHNAME, &getuser_func, NULL }, + { SASL_CB_PASS, &getpass_func, NULL }, + { SASL_CB_LIST_END, NULL, NULL }, +}; + +static char *sasl_auth_id = NULL; +static char *sasl_auth_secret = NULL; + +static int +sasl_log_func(void *context, int level, const char *message) +{ + switch (level) { + case SASL_LOG_ERR: + case SASL_LOG_FAIL: + logerr("%s", message); + break; + case SASL_LOG_WARN: + logmsg("%s", message); + break; + case SASL_LOG_NOTE: + logmsg("%s", message); + break; + case SASL_LOG_DEBUG: + case SASL_LOG_TRACE: + case SASL_LOG_PASS: + debug(LOGOPT_DEBUG, "%s", message); + break; + default: + break; + } + + return SASL_OK; +} + +static int +getuser_func(void *context, int id, const char **result, unsigned *len) +{ + debug(LOGOPT_NONE, "called with context %p, id %d.", context, id); + + switch (id) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + *result = sasl_auth_id; + if (len) + *len = strlen(sasl_auth_id); + break; + default: + error(LOGOPT_VERBOSE, "unknown id in request: %d", id); + return SASL_FAIL; + } + + return SASL_OK; +} + +/* + * This function creates a sasl_secret_t from the credentials specified in + * the configuration file. sasl_client_auth can return SASL_OK or + * SASL_NOMEM. We simply propagate this return value to the caller. + */ +static int +getpass_func(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) +{ + int len = strlen(sasl_auth_secret); + + debug(LOGOPT_NONE, "context %p, id %d", context, id); + + *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len); + if (!*psecret) + return SASL_NOMEM; + + (*psecret)->len = strlen(sasl_auth_secret); + strncpy((char *)(*psecret)->data, sasl_auth_secret, len); + + return SASL_OK; +} + +/* + * retrieves the supportedSASLmechanisms from the LDAP server. + * + * Return Value: the result of ldap_get_values on success, NULL on failure. + * The caller is responsible for calling ldap_value_free on + * the returned data. + */ +char ** +get_server_SASL_mechanisms(unsigned logopt, LDAP *ld) +{ + int ret; + const char *saslattrlist[] = {"supportedSASLmechanisms", NULL}; + LDAPMessage *results = NULL, *entry; + char **mechanisms; + + ret = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", + (char **)saslattrlist, 0, + NULL, NULL, + NULL, LDAP_NO_LIMIT, &results); + if (ret != LDAP_SUCCESS) { + error(logopt, "%s", ldap_err2string(ret)); + return NULL; + } + + entry = ldap_first_entry(ld, results); + if (entry == NULL) { + /* No root DSE. (!) */ + ldap_msgfree(results); + debug(logopt, + "a lookup of \"supportedSASLmechanisms\" returned " + "no results."); + return NULL; + } + + mechanisms = ldap_get_values(ld, entry, "supportedSASLmechanisms"); + ldap_msgfree(results); + if (mechanisms == NULL) { + /* Well, that was a waste of time. */ + info(logopt, "No SASL authentication mechanisms are supported" + " by the LDAP server."); + return NULL; + } + + return mechanisms; +} + +/* + * Returns 0 upon successful connect, -1 on failure. + */ +int +do_sasl_bind(unsigned logopt, LDAP *ld, sasl_conn_t *conn, const char **clientout, + unsigned int *clientoutlen, const char *auth_mech, int sasl_result) +{ + int ret, msgid, bind_result = LDAP_OTHER; + struct berval client_cred, *server_cred, temp_cred; + LDAPMessage *results; + int have_data, expected_data; + + do { + /* Take whatever client data we have and send it to the + * server. */ + client_cred.bv_val = (char *)*clientout; + client_cred.bv_len = *clientoutlen; + ret = ldap_sasl_bind(ld, NULL, auth_mech, + (client_cred.bv_len > 0) ? + &client_cred : NULL, + NULL, NULL, &msgid); + if (ret != LDAP_SUCCESS) { + crit(logopt, + "Error sending sasl_bind request to " + "the server: %s", ldap_err2string(ret)); + return -1; + } + + /* Wait for a result message for this bind request. */ + results = NULL; + ret = ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &results); + if (ret != LDAP_RES_BIND) { + crit(logopt, + "Error while waiting for response to " + "sasl_bind request: %s", ldap_err2string(ret)); + return -1; + } + + /* Retrieve the result code for the bind request and + * any data which the server sent. */ + server_cred = NULL; + ret = ldap_parse_sasl_bind_result(ld, results, + &server_cred, 0); + ldap_msgfree(results); + + /* Okay, here's where things get tricky. Both + * Mozilla's LDAP SDK and OpenLDAP store the result + * code which was returned by the server in the + * handle's ERROR_NUMBER option. Mozilla returns + * LDAP_SUCCESS if the data was parsed correctly, even + * if the result was an error, while OpenLDAP returns + * the result code. I'm leaning toward Mozilla being + * more correct. + * In either case, we stuff the result into bind_result. + */ + if (ret == LDAP_SUCCESS) { + /* Mozilla? */ + ret = ldap_get_option(ld, LDAP_OPT_RESULT_CODE, + &bind_result); + if (ret != LDAP_SUCCESS) { + crit(logopt, + "Error retrieving response to sasl_bind " + "request: %s", ldap_err2string(ret)); + ret = -1; + break; + } + } else { + /* OpenLDAP? */ + switch (ret) { + case LDAP_SASL_BIND_IN_PROGRESS: + bind_result = ret; + break; + default: + warn(logopt, + "Error parsing response to sasl_bind " + "request: %s.", ldap_err2string(ret)); + break; + } + } + + /* + * The LDAP server is supposed to send a NULL value for + * server_cred if there was no data. However, *some* + * server implementations get this wrong, and instead send + * an empty string. We check for both. + */ + have_data = server_cred != NULL && server_cred->bv_len > 0; + + /* + * If the result of the sasl_client_start is SASL_CONTINUE, + * then the server should have sent us more data. + */ + expected_data = sasl_result == SASL_CONTINUE; + + if (have_data && !expected_data) { + warn(logopt, + "The LDAP server sent data in response to our " + "bind request, but indicated that the bind was " + "complete. LDAP SASL bind with mechansim %s " + "failed.", auth_mech); + ret = -1; + break; + } + if (expected_data && !have_data) { + warn(logopt, + "The LDAP server indicated that the LDAP SASL " + "bind was incomplete, but did not provide the " + "required data to proceed. LDAP SASL bind with " + "mechanism %s failed.", auth_mech); + ret = -1; + break; + } + + /* If we need another round trip, process whatever we + * received and prepare data to be transmitted back. */ + if ((sasl_result == SASL_CONTINUE) && + ((bind_result == LDAP_SUCCESS) || + (bind_result == LDAP_SASL_BIND_IN_PROGRESS))) { + if (server_cred != NULL) { + temp_cred = *server_cred; + } else { + temp_cred.bv_len = 0; + temp_cred.bv_val = NULL; + } + sasl_result = sasl_client_step(conn, + temp_cred.bv_val, + temp_cred.bv_len, + NULL, + clientout, + clientoutlen); + /* If we have data to send, then the server + * had better be expecting it. (It's valid + * to send the server no data with a request.) + */ + if ((*clientoutlen > 0) && + (bind_result != LDAP_SASL_BIND_IN_PROGRESS)) { + warn(logopt, + "We have data for the server, " + "but it thinks we are done!"); + /* XXX should print out debug data here */ + ret = -1; + } + } + + if (server_cred && server_cred->bv_len > 0) { + ber_bvfree(server_cred); + server_cred = NULL; + } + + } while ((bind_result == LDAP_SASL_BIND_IN_PROGRESS) || + (sasl_result == SASL_CONTINUE)); + + if (server_cred && server_cred->bv_len > 0) + ber_bvfree(server_cred); + + return ret; +} + +/* + * Read client credentials from the default keytab, create a credentials + * cache, add the TGT to that cache, and set the environment variable so + * that the sasl/krb5 libraries can find our credentials. + * + * Returns 0 upon success. ctxt->kinit_done and ctxt->kinit_successful + * are set for cleanup purposes. The krb5 context and ccache entries in + * the lookup_context are also filled in. + * + * Upon failure, -1 is returned. + */ +int +sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt) +{ + krb5_error_code ret; + krb5_principal tgs_princ, krb5_client_princ; + krb5_creds my_creds; + char *tgs_name; + const char *realm_name; + int status, realm_length; + + if (ctxt->kinit_done) + return 0; + ctxt->kinit_done = 1; + + debug(logopt, + "initializing kerberos ticket: client principal %s", + ctxt->client_princ ? ctxt->client_princ : default_client); + + ret = krb5_init_context(&ctxt->krb5ctxt); + if (ret) { + error(logopt, "krb5_init_context failed with %d", ret); + return -1; + } + + ret = krb5_cc_resolve(ctxt->krb5ctxt, krb5ccval, &ctxt->krb5_ccache); + if (ret) { + error(logopt, "krb5_cc_resolve failed with error %d", + ret); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + if (ctxt->client_princ) { + debug(logopt, + "calling krb5_parse_name on client principal %s", + ctxt->client_princ); + + ret = krb5_parse_name(ctxt->krb5ctxt, ctxt->client_princ, + &krb5_client_princ); + if (ret) { + error(logopt, + "krb5_parse_name failed for " + "specified client principal %s", + ctxt->client_princ); + goto out_cleanup_cc; + } + } else { + char *tmp_name = NULL; + + debug(logopt, + "calling krb5_sname_to_principal using defaults"); + + ret = krb5_sname_to_principal(ctxt->krb5ctxt, NULL, + default_client, KRB5_NT_SRV_HST, + &krb5_client_princ); + if (ret) { + error(logopt, + "krb5_sname_to_principal failed for " + "%s with error %d", default_client, ret); + goto out_cleanup_cc; + } + + + ret = krb5_unparse_name(ctxt->krb5ctxt, + krb5_client_princ, &tmp_name); + if (ret) { + debug(logopt, + "krb5_unparse_name failed with error %d", + ret); + goto out_cleanup_client_princ; + } + + debug(logopt, + "principal used for authentication: %s", tmp_name); + + krb5_free_unparsed_name(ctxt->krb5ctxt, tmp_name); + } + + /* setup a principal for the ticket granting service */ + _krb5_princ_realm(ctxt->krb5ctxt, krb5_client_princ, &realm_name, &realm_length); + ret = krb5_build_principal_ext(ctxt->krb5ctxt, &tgs_princ, + realm_length, realm_name, + strlen(KRB5_TGS_NAME), KRB5_TGS_NAME, + realm_length, realm_name, + 0); + if (ret) { + error(logopt, + "krb5_build_principal failed with error %d", ret); + goto out_cleanup_client_princ; + } + + ret = krb5_unparse_name(ctxt->krb5ctxt, tgs_princ, &tgs_name); + if (ret) { + error(logopt, "krb5_unparse_name failed with error %d", + ret); + goto out_cleanup_client_princ; + } + + debug(logopt, "Using tgs name %s", tgs_name); + + memset(&my_creds, 0, sizeof(my_creds)); + ret = krb5_get_init_creds_keytab(ctxt->krb5ctxt, &my_creds, + krb5_client_princ, + NULL /*keytab*/, + 0 /* relative start time */, + tgs_name, NULL); + if (ret) { + error(logopt, + "krb5_get_init_creds_keytab failed with error %d", + ret); + goto out_cleanup_unparse; + } + + status = pthread_mutex_lock(&krb5cc_mutex); + if (status) + fatal(status); + + if (krb5cc_in_use++ == 0) + /* tell the cache what the default principal is */ + ret = krb5_cc_initialize(ctxt->krb5ctxt, + ctxt->krb5_ccache, krb5_client_princ); + + status = pthread_mutex_unlock(&krb5cc_mutex); + if (status) + fatal(status); + + if (ret) { + error(logopt, + "krb5_cc_initialize failed with error %d", ret); + goto out_cleanup_creds; + } + + /* and store credentials for that principal */ + ret = krb5_cc_store_cred(ctxt->krb5ctxt, ctxt->krb5_ccache, &my_creds); + if (ret) { + error(logopt, + "krb5_cc_store_cred failed with error %d", ret); + goto out_cleanup_creds; + } + + /* finally, set the environment variable to point to our + * credentials cache */ + if (setenv(krb5ccenv, krb5ccval, 1) != 0) { + error(logopt, "setenv failed with %d", errno); + goto out_cleanup_creds; + } + ctxt->kinit_successful = 1; + + debug(logopt, "Kerberos authentication was successful!"); + + krb5_free_unparsed_name(ctxt->krb5ctxt, tgs_name); + krb5_free_cred_contents(ctxt->krb5ctxt, &my_creds); + krb5_free_principal(ctxt->krb5ctxt, tgs_princ); + krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ); + + return 0; + +out_cleanup_creds: + krb5cc_in_use--; + krb5_free_cred_contents(ctxt->krb5ctxt, &my_creds); +out_cleanup_unparse: + krb5_free_principal(ctxt->krb5ctxt, tgs_princ); + krb5_free_unparsed_name(ctxt->krb5ctxt, tgs_name); +out_cleanup_client_princ: + krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ); +out_cleanup_cc: + status = pthread_mutex_lock(&krb5cc_mutex); + if (status) + fatal(status); + + if (krb5cc_in_use) + ret = krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + else + ret = krb5_cc_destroy(ctxt->krb5ctxt, ctxt->krb5_ccache); + if (ret) + warn(logopt, + "krb5_cc_destroy failed with non-fatal error %d", ret); + + status = pthread_mutex_unlock(&krb5cc_mutex); + if (status) + fatal(status); + + krb5_free_context(ctxt->krb5ctxt); + + return -1; +} + +/* + * Check a client given external credential cache. + * + * Returns 0 upon success. ctxt->kinit_done and ctxt->kinit_successful + * are set for cleanup purposes. The krb5 context and ccache entries in + * the lookup_context are also filled in. + * + * Upon failure, -1 is returned. + */ +int +sasl_do_kinit_ext_cc(unsigned logopt, struct lookup_context *ctxt) +{ + krb5_principal def_princ; + krb5_principal krb5_client_princ; + krb5_error_code ret; + char *cc_princ, *client_princ; + + if (ctxt->kinit_done) + return 0; + ctxt->kinit_done = 1; + + debug(logopt, + "using external credential cache for auth: client principal %s", + ctxt->client_princ ? ctxt->client_princ : default_client); + + ret = krb5_init_context(&ctxt->krb5ctxt); + if (ret) { + error(logopt, "krb5_init_context failed with %d", ret); + return -1; + } + + ret = krb5_cc_resolve(ctxt->krb5ctxt, ctxt->client_cc, &ctxt->krb5_ccache); + if (ret) { + error(logopt, "krb5_cc_resolve failed with error %d", + ret); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + ret = krb5_cc_get_principal(ctxt->krb5ctxt, ctxt->krb5_ccache, &def_princ); + if (ret) { + error(logopt, "krb5_cc_get_principal failed with error %d", ret); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + ret = krb5_unparse_name(ctxt->krb5ctxt, def_princ, &cc_princ); + if (ret) { + error(logopt, "krb5_unparse_name failed with error %d", ret); + krb5_free_principal(ctxt->krb5ctxt, def_princ); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + debug(logopt, "external credential cache default principal %s", cc_princ); + + /* + * If the principal isn't set in the config construct the default + * so we can check against the default principal of the external + * cred cache. + */ + if (ctxt->client_princ) + client_princ = ctxt->client_princ; + else { + debug(logopt, + "calling krb5_sname_to_principal using defaults"); + + ret = krb5_sname_to_principal(ctxt->krb5ctxt, NULL, + default_client, KRB5_NT_SRV_HST, + &krb5_client_princ); + if (ret) { + error(logopt, + "krb5_sname_to_principal failed for " + "%s with error %d", default_client, ret); + krb5_free_principal(ctxt->krb5ctxt, def_princ); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + + ret = krb5_unparse_name(ctxt->krb5ctxt, + krb5_client_princ, &client_princ); + if (ret) { + debug(logopt, + "krb5_unparse_name failed with error %d", + ret); + krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ); + krb5_free_principal(ctxt->krb5ctxt, def_princ); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + debug(logopt, + "principal used for authentication: %s", client_princ); + + krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ); + } + + /* + * Check if the principal to be used matches the default principal in + * the external cred cache. + */ + if (strcmp(cc_princ, client_princ)) { + error(logopt, + "configured client principal %s ", + ctxt->client_princ); + error(logopt, + "external credential cache default principal %s", + cc_princ); + error(logopt, + "cannot use credential cache, external " + "default principal does not match configured " + "principal"); + if (!ctxt->client_princ) + krb5_free_unparsed_name(ctxt->krb5ctxt, client_princ); + krb5_free_unparsed_name(ctxt->krb5ctxt, cc_princ); + krb5_free_principal(ctxt->krb5ctxt, def_princ); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + + if (!ctxt->client_princ) + krb5_free_unparsed_name(ctxt->krb5ctxt, client_princ); + krb5_free_unparsed_name(ctxt->krb5ctxt, cc_princ); + krb5_free_principal(ctxt->krb5ctxt, def_princ); + + /* Set the environment variable to point to the external cred cache */ + if (setenv(krb5ccenv, ctxt->client_cc, 1) != 0) { + error(logopt, "setenv failed with %d", errno); + krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + krb5_free_context(ctxt->krb5ctxt); + return -1; + } + ctxt->kinit_successful = 1; + + debug(logopt, "Kerberos authentication was successful!"); + + return 0; +} + +/* + * Attempt to bind to the ldap server using a given authentication + * mechanism. ldap should be a properly initialzed ldap pointer. + * + * Returns a valid sasl_conn_t pointer upon success, NULL on failure. + */ +sasl_conn_t * +sasl_bind_mech(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt, const char *mech) +{ + sasl_conn_t *conn; + char *tmp, *host = NULL; + const char *clientout; + unsigned int clientoutlen; + const char *chosen_mech; + int result; + + if (!strncmp(mech, "GSSAPI", 6)) { + if (ctxt->client_cc) + result = sasl_do_kinit_ext_cc(logopt, ctxt); + else + result = sasl_do_kinit(logopt, ctxt); + if (result != 0) + return NULL; + } + + debug(logopt, "Attempting sasl bind with mechanism %s", mech); + + result = ldap_get_option(ldap, LDAP_OPT_HOST_NAME, &host); + if (result != LDAP_OPT_SUCCESS || !host) { + debug(logopt, "failed to get hostname for connection"); + return NULL; + } + + /* + * We need a host name to start the client. + * But the ldap library can return a list of host names so + * just use the first one. + */ + if ((tmp = strchr(host, ' '))) + *tmp = '\0'; + if ((tmp = strrchr(host, ':'))) { + if (*(tmp - 1) != ']') { + *tmp = '\0'; + tmp = host; + } else { + *(tmp - 1) = '\0'; + tmp = host; + if (*tmp == '[') + tmp++; + } + } + + /* Create a new authentication context for the service. */ + result = sasl_client_new("ldap", tmp, NULL, NULL, NULL, 0, &conn); + if (result != SASL_OK) { + error(logopt, "sasl_client_new failed with error %d", + result); + ldap_memfree(host); + return NULL; + } + + chosen_mech = NULL; + result = sasl_client_start(conn, mech, NULL, + &clientout, &clientoutlen, &chosen_mech); + + /* OK and CONTINUE are the only non-fatal return codes here. */ + if ((result != SASL_OK) && (result != SASL_CONTINUE)) { + warn(logopt, "sasl_client_start failed for %s", host); + debug(logopt, "sasl_client_start: %s", sasl_errdetail(conn)); + ldap_memfree(host); + sasl_dispose(&conn); + return NULL; + } + + result = do_sasl_bind(logopt, ldap, conn, + &clientout, &clientoutlen, chosen_mech, result); + if (result == 0) { + ldap_memfree(host); + debug(logopt, "sasl bind with mechanism %s succeeded", + chosen_mech); + return conn; + } + + info(logopt, "sasl bind with mechanism %s failed", mech); + + /* sasl bind failed */ + ldap_memfree(host); + sasl_dispose(&conn); + + return NULL; +} + +/* + * Returns 0 if a suitable authentication mechanism is available. Returns + * -1 on error or if no mechanism is supported by both client and server. + */ +sasl_conn_t * +sasl_choose_mech(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt) +{ + sasl_conn_t *conn = NULL; + int authenticated; + int i; + char **mechanisms; + + mechanisms = get_server_SASL_mechanisms(logopt, ldap); + if (!mechanisms) + return NULL; + + /* Try each supported mechanism in turn. */ + authenticated = 0; + for (i = 0; mechanisms[i] != NULL; i++) { + /* + * This routine is called if there is no configured + * mechanism. As such, we can skip over any auth + * mechanisms that require user credentials. These include + * PLAIN, LOGIN, and DIGEST-MD5. + */ + if (authtype_requires_creds(mechanisms[i])) + continue; + + conn = sasl_bind_mech(logopt, ldap, ctxt, mechanisms[i]); + if (conn) { + ctxt->sasl_mech = strdup(mechanisms[i]); + if (!ctxt->sasl_mech) { + crit(logopt, "Successfully authenticated with " + "mechanism %s, but failed to allocate " + "memory to hold the mechanism type.", + mechanisms[i]); + sasl_dispose(&conn); + ldap_value_free(mechanisms); + return NULL; + } + authenticated = 1; + break; + } + debug(logopt, "Failed to authenticate with mech %s", + mechanisms[i]); + } + + debug(logopt, "authenticated: %d, sasl_mech: %s", + authenticated, ctxt->sasl_mech); + + ldap_value_free(mechanisms); + return conn; +} + +/* + * Routine called when unbinding an ldap connection. + */ +void +autofs_sasl_unbind(struct ldap_conn *conn, struct lookup_context *ctxt) +{ + if (ctxt->sasl_mech && !strncmp(ctxt->sasl_mech, "EXTERNAL", 8)) { + if (conn->ldap) { + ldap_unbind_s(conn->ldap); + conn->ldap = NULL; + } + return; + } + + if (conn->sasl_conn) { + sasl_dispose(&conn->sasl_conn); + conn->sasl_conn = NULL; + } +} + +/* + * Given a lookup context that has been initialized with any user-specified + * parameters, figure out which sasl mechanism to use. Then, initialize + * the necessary parameters to authenticate with the chosen mechanism. + * + * Return Values: + * 0 - Success + * -1 - Failure + */ +int +autofs_sasl_bind(unsigned logopt, + struct ldap_conn *conn, struct lookup_context *ctxt) +{ + sasl_conn_t *sasl_conn = NULL; + + if (ctxt->sasl_mech && !strncmp(ctxt->sasl_mech, "EXTERNAL", 8)) { + int result; + + debug(logopt, + "Attempting sasl bind with mechanism %s", + ctxt->sasl_mech); + + result = do_sasl_extern(conn->ldap, ctxt); + if (result) + debug(logopt, + "Failed to authenticate with mech %s", + ctxt->sasl_mech); + else + debug(logopt, + "sasl bind with mechanism %s succeeded", + ctxt->sasl_mech); + + return result; + } + + sasl_auth_id = ctxt->user; + sasl_auth_secret = ctxt->secret; + + if (ctxt->auth_required & LDAP_AUTH_AUTODETECT) { + if (ctxt->sasl_mech) { + free(ctxt->sasl_mech); + ctxt->sasl_mech = NULL; + } + } + + /* + * If LDAP_AUTH_AUTODETECT is set, it means that there was no + * mechanism specified in the configuration file or auto + * selection has been requested, so try to auto-select an + * auth mechanism. + */ + if (ctxt->sasl_mech) + sasl_conn = sasl_bind_mech(logopt, + conn->ldap, ctxt, ctxt->sasl_mech); + else + sasl_conn = sasl_choose_mech(logopt, conn->ldap, ctxt); + + if (!sasl_conn) + return -1; + + conn->sasl_conn = sasl_conn; + + return 0; +} + +/* + * Destructor routine. This should be called when finished with an ldap + * session. + */ +void autofs_sasl_dispose(struct ldap_conn *conn, struct lookup_context *ctxt) +{ + int status, ret; + + if (ctxt->sasl_mech && !strncmp(ctxt->sasl_mech, "EXTERNAL", 8)) { + if (conn && conn->ldap) { + ldap_unbind_s(conn->ldap); + conn->ldap = NULL; + } + return; + } + + if (conn && conn->sasl_conn) { + sasl_dispose(&conn->sasl_conn); + conn->sasl_conn = NULL; + } + + if (ctxt->kinit_successful) { + status = pthread_mutex_lock(&krb5cc_mutex); + if (status) + fatal(status); + + if (--krb5cc_in_use || ctxt->client_cc) + ret = krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache); + else + ret = krb5_cc_destroy(ctxt->krb5ctxt, ctxt->krb5_ccache); + if (ret) + logmsg("krb5_cc_destroy failed with non-fatal error %d", + ret); + + status = pthread_mutex_unlock(&krb5cc_mutex); + if (status) + fatal(status); + + krb5_free_context(ctxt->krb5ctxt); + if (unsetenv(krb5ccenv) != 0) + logerr("unsetenv failed with error %d", errno); + + ctxt->krb5ctxt = NULL; + ctxt->krb5_ccache = NULL; + ctxt->kinit_done = 0; + ctxt->kinit_successful = 0; + } +} + +static void *sasl_mutex_new(void) +{ + pthread_mutex_t* mutex; + + mutex = malloc(sizeof(pthread_mutex_t)); + if (!mutex) + return 0; + + pthread_mutex_init(mutex, NULL); + + return (void *) mutex; +} + +static int sasl_mutex_lock(void *mutex __attribute__((unused))) +{ + int rc; + + if (!mutex) + return SASL_FAIL; + + rc = pthread_mutex_lock((pthread_mutex_t *) mutex); + + return (rc==0 ? SASL_OK : SASL_FAIL); +} + +static int sasl_mutex_unlock(void *mutex __attribute__((unused))) +{ + int rc; + + if (!mutex) + return SASL_FAIL; + + rc = pthread_mutex_unlock((pthread_mutex_t *) mutex); + + return (rc==0 ? SASL_OK : SASL_FAIL); +} + +static void sasl_mutex_dispose(void *mutex __attribute__((unused))) +{ + int rc; + + if (!mutex) + return; + + rc = pthread_mutex_destroy((pthread_mutex_t *) mutex); + if (rc == 0) + free(mutex); + + return; +} + +/* + * Initialize the sasl callbacks, which increments the global + * use counter. + */ +int autofs_sasl_client_init(unsigned logopt) +{ + + sasl_set_mutex(sasl_mutex_new, + sasl_mutex_lock, + sasl_mutex_unlock, + sasl_mutex_dispose); + + /* Start up Cyrus SASL--only needs to be done at library load. */ + if (sasl_client_init(callbacks) != SASL_OK) { + error(logopt, "sasl_client_init failed"); + return 0; + } + return 1; +} + +/* + * Decrement the library reference count and free resources if + * we are the last to close the library. + */ +void autofs_sasl_done(void) +{ + sasl_done(); + return; +} + diff --git a/modules/dclist.c b/modules/dclist.c new file mode 100644 index 0000000..4daa199 --- /dev/null +++ b/modules/dclist.c @@ -0,0 +1,584 @@ +/* + * Copyright 2011 Ian Kent + * Copyright 2011 Red Hat, Inc. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automount.h" +#include "dclist.h" + +#define MAX_TTL (60*60) /* 1 hour */ + +struct rr { + unsigned int type; + unsigned int class; + unsigned long ttl; + unsigned int len; +}; + +struct srv_rr { + const char *name; + unsigned int priority; + unsigned int weight; + unsigned int port; + unsigned long ttl; +}; + +static pthread_mutex_t dclist_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void dclist_mutex_lock(void) +{ + int status = pthread_mutex_lock(&dclist_mutex); + if (status) + fatal(status); + return; +} + +static void dclist_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&dclist_mutex); + if (status) + fatal(status); + return; +} + +static int do_srv_query(unsigned int logopt, char *name, u_char **packet) +{ + int len = PACKETSZ; + unsigned int last_len = len; + char ebuf[MAX_ERR_BUF]; + u_char *buf; + + while (1) { + buf = malloc(last_len); + if (!buf) { + char *estr = strerror_r(errno, ebuf, MAX_ERR_BUF); + error(logopt, "malloc: %s", estr); + return -1; + } + + len = res_query(name, C_IN, T_SRV, buf, last_len); + if (len < 0) { + char *estr = strerror_r(errno, ebuf, MAX_ERR_BUF); + error(logopt, "Failed to resolve %s (%s)", name, estr); + free(buf); + return -1; + } + + if (len == last_len) { + /* These shouldn't too large, bump by PACKETSZ only */ + last_len += PACKETSZ; + free(buf); + continue; + } + + break; + } + + *packet = buf; + + return len; +} + +static int get_name_len(u_char *buffer, u_char *start, u_char *end) +{ + char tmp[MAXDNAME]; + return dn_expand(buffer, end, start, tmp, MAXDNAME); +} + +static int get_data_offset(u_char *buffer, + u_char *start, u_char *end, + struct rr *rr) +{ + u_char *cp = start; + int name_len; + + name_len = get_name_len(buffer, start, end); + if (name_len < 0) + return -1; + cp += name_len; + + GETSHORT(rr->type, cp); + GETSHORT(rr->class, cp); + GETLONG(rr->ttl, cp); + GETSHORT(rr->len, cp); + + return (cp - start); +} + +static struct srv_rr *parse_srv_rr(unsigned int logopt, + u_char *buffer, u_char *start, u_char *end, + struct rr *rr, struct srv_rr *srv) +{ + u_char *cp = start; + char ebuf[MAX_ERR_BUF]; + char tmp[MAXDNAME]; + int len; + + GETSHORT(srv->priority, cp); + GETSHORT(srv->weight, cp); + GETSHORT(srv->port, cp); + srv->ttl = rr->ttl; + + len = dn_expand(buffer, end, cp, tmp, MAXDNAME); + if (len < 0) { + error(logopt, "failed to expand name"); + return NULL; + } + srv->name = strdup(tmp); + if (!srv->name) { + char *estr = strerror_r(errno, ebuf, MAX_ERR_BUF); + error(logopt, "strdup: %s", estr); + return NULL; + } + + return srv; +} + +static int cmp(struct srv_rr *a, struct srv_rr *b) +{ + if (a->priority < b->priority) + return -1; + + if (a->priority > b->priority) + return 1; + + if (!a->weight || a->weight == b->weight) + return 0; + + if (a->weight > b->weight) + return -1; + + return 1; +} + +static void free_srv_rrs(struct srv_rr *dcs, unsigned int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (dcs[i].name) + free((void *) dcs[i].name); + } + free(dcs); +} + +int get_srv_rrs(unsigned int logopt, + char *name, struct srv_rr **dcs, unsigned int *dcs_count) +{ + struct srv_rr *srvs; + unsigned int srv_num; + HEADER *header; + u_char *packet; + u_char *start; + u_char *end; + unsigned int count; + int i, len; + char ebuf[MAX_ERR_BUF]; + + len = do_srv_query(logopt, name, &packet); + if (len < 0) + return 0; + + header = (HEADER *) packet; + start = packet + sizeof(HEADER); + end = packet + len; + + srvs = NULL; + srv_num = 0; + + /* Skip over question */ + len = get_name_len(packet, start, end); + if (len < 0) { + error(logopt, "failed to get name length"); + goto error_out; + } + + start += len + QFIXEDSZ; + + count = ntohs(header->ancount); + + debug(logopt, "%d records returned in the answer section", count); + + if (count <= 0) { + error(logopt, "no records found in answers section"); + goto error_out; + } + + srvs = malloc(sizeof(struct srv_rr) * count); + if (!srvs) { + char *estr = strerror_r(errno, ebuf, MAX_ERR_BUF); + error(logopt, "malloc: %s", estr); + goto error_out; + } + memset(srvs, 0, sizeof(struct srv_rr) * count); + + srv_num = 0; + for (i = 0; i < count && (start < end); i++) { + unsigned int data_offset; + struct srv_rr srv; + struct srv_rr *psrv; + struct rr rr; + + memset(&rr, 0, sizeof(struct rr)); + + data_offset = get_data_offset(packet, start, end, &rr); + if (data_offset <= 0) { + error(logopt, "failed to get start of data"); + goto error_out; + } + start += data_offset; + + if (rr.type != T_SRV) + continue; + + psrv = parse_srv_rr(logopt, packet, start, end, &rr, &srv); + if (psrv) { + memcpy(&srvs[srv_num], psrv, sizeof(struct srv_rr)); + srv_num++; + } + + start += rr.len; + } + free(packet); + + if (!srv_num) { + error(logopt, "no srv resource records found"); + goto error_srvs; + } + + qsort(srvs, srv_num, sizeof(struct srv_rr), + (int (*)(const void *, const void *)) cmp); + + *dcs = srvs; + *dcs_count = srv_num; + + return 1; + +error_out: + free(packet); +error_srvs: + if (srvs) + free_srv_rrs(srvs, srv_num); + return 0; +} + +static char *escape_dn_commas(const char *uri) +{ + size_t len = strlen(uri); + char *new, *tmp, *ptr; + + ptr = (char *) uri; + while (*ptr) { + if (*ptr == '\\') + ptr += 2; + if (*ptr == ',') + len += 2; + ptr++; + } + + new = malloc(len + 1); + if (!new) + return NULL; + memset(new, 0, len + 1); + + ptr = (char *) uri; + tmp = new; + while (*ptr) { + if (*ptr == '\\') { + ptr++; + *tmp++ = *ptr++; + continue; + } + if (*ptr == ',') { + strcpy(tmp, "%2c"); + ptr++; + tmp += 3; + continue; + } + *tmp++ = *ptr++; + } + + return new; +} + +void free_dclist(struct dclist *dclist) +{ + if (dclist->uri) + free((void *) dclist->uri); + free(dclist); +} + +static char *getdnsdomainname(unsigned int logopt) +{ + struct addrinfo hints, *ni; + char name[MAXDNAME + 1]; + char buf[MAX_ERR_BUF]; + char *dnsdomain = NULL; + char *ptr; + int ret; + + memset(name, 0, sizeof(name)); + if (gethostname(name, MAXDNAME) == -1) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "gethostname: %s", estr); + return NULL; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + ret = getaddrinfo(name, NULL, &hints, &ni); + if (ret) { + error(logopt, "hostname lookup failed: %s", gai_strerror(ret)); + return NULL; + } + + ptr = ni->ai_canonname; + while (*ptr && *ptr != '.') + ptr++; + + if (*++ptr) + dnsdomain = strdup(ptr); + + freeaddrinfo(ni); + + return dnsdomain; +} + +struct dclist *get_dc_list(unsigned int logopt, const char *uri) +{ + LDAPURLDesc *ludlist = NULL; + LDAPURLDesc **ludp; + unsigned int min_ttl = MAX_TTL; + struct dclist *dclist = NULL;; + char buf[MAX_ERR_BUF]; + char *dn_uri, *esc_uri; + char *domain; + char *list; + int ret; + + if (strcmp(uri, "ldap:///") && strcmp(uri, "ldaps:///")) { + dn_uri = strdup(uri); + if (!dn_uri) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "strdup: %s", estr); + return NULL; + } + } else { + char *dnsdomain; + char *hdn; + + dnsdomain = getdnsdomainname(logopt); + if (!dnsdomain) { + error(logopt, "failed to get dns domainname"); + return NULL; + } + + if (ldap_domain2dn(dnsdomain, &hdn) || hdn == NULL) { + error(logopt, + "Could not turn domain \"%s\" into a dn\n", + dnsdomain); + free(dnsdomain); + return NULL; + } + free(dnsdomain); + + dn_uri = malloc(strlen(uri) + strlen(hdn) + 1); + if (!dn_uri) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "malloc: %s", estr); + ber_memfree(hdn); + return NULL; + } + + strcpy(dn_uri, uri); + strcat(dn_uri, hdn); + ber_memfree(hdn); + } + + esc_uri = escape_dn_commas(dn_uri); + if (!esc_uri) { + error(logopt, "Could not escape commas in uri %s", dn_uri); + free(dn_uri); + return NULL; + } + + ret = ldap_url_parse(esc_uri, &ludlist); + if (ret != LDAP_URL_SUCCESS) { + error(logopt, "Could not parse uri %s (%d)", dn_uri, ret); + free(esc_uri); + free(dn_uri); + return NULL; + } + + free(esc_uri); + + if (!ludlist) { + error(logopt, "No dn found in uri %s", dn_uri); + free(dn_uri); + return NULL; + } + + free(dn_uri); + + dclist = malloc(sizeof(struct dclist)); + if (!dclist) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "malloc: %s", estr); + ldap_free_urldesc(ludlist); + return NULL; + } + memset(dclist, 0, sizeof(struct dclist)); + + list = NULL; + for (ludp = &ludlist; *ludp != NULL;) { + LDAPURLDesc *lud = *ludp; + struct srv_rr *dcs = NULL; + unsigned int numdcs = 0; + size_t req_len, len; + char *request = NULL; + char *tmp; + int i; + + if (!lud->lud_dn && !lud->lud_dn[0] && + (!lud->lud_host || !lud->lud_host[0])) { + *ludp = lud->lud_next; + continue; + } + + domain = NULL; + if (ldap_dn2domain(lud->lud_dn, &domain) || domain == NULL) { + error(logopt, + "Could not turn dn \"%s\" into a domain", + lud->lud_dn); + *ludp = lud->lud_next; + continue; + } + + debug(logopt, "doing lookup of SRV RRs for domain %s", domain); + + req_len = sizeof("_ldap._tcp.") + strlen(domain); + request = malloc(req_len); + if (!request) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "malloc: %s", estr); + goto out_error; + } + + ret = snprintf(request, req_len, "_ldap._tcp.%s", domain); + if (ret >= req_len) { + free(request); + goto out_error; + } + + dclist_mutex_lock(); + ret = get_srv_rrs(logopt, request, &dcs, &numdcs); + if (!ret | !dcs) { + error(logopt, + "DNS SRV query failed for domain %s", domain); + dclist_mutex_unlock(); + free(request); + goto out_error; + } + dclist_mutex_unlock(); + free(request); + + len = strlen(lud->lud_scheme); + len += sizeof("://"); + len *= numdcs; + + for (i = 0; i < numdcs; i++) { + if (dcs[i].ttl > 0 && dcs[i].ttl < min_ttl) + min_ttl = dcs[i].ttl; + len += strlen(dcs[i].name); + if (dcs[i].port > 0) + len += sizeof(":65535"); + } + + tmp = realloc(list, len); + if (!tmp) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, "realloc: %s", estr); + free_srv_rrs(dcs, numdcs); + goto out_error; + } + + if (!list) + memset(tmp, 0, len); + else + strcat(tmp, " "); + + list = NULL; + for (i = 0; i < numdcs; i++) { + if (i > 0) + strcat(tmp, " "); + strcat(tmp, lud->lud_scheme); + strcat(tmp, "://"); + strcat(tmp, dcs[i].name); + if (dcs[i].port > 0) { + char port[7]; + ret = snprintf(port, 7, ":%d", dcs[i].port); + if (ret > 6) { + error(logopt, + "invalid port: %u", dcs[i].port); + free_srv_rrs(dcs, numdcs); + free(tmp); + goto out_error; + } + strcat(tmp, port); + } + } + list = tmp; + + *ludp = lud->lud_next; + ber_memfree(domain); + free_srv_rrs(dcs, numdcs); + } + + ldap_free_urldesc(ludlist); + + if (!list) + goto out_error; + + dclist->expire = monotonic_time(NULL) + min_ttl; + dclist->uri = list; + + return dclist; + +out_error: + if (list) + free(list); + if (domain) + ber_memfree(domain); + ldap_free_urldesc(ludlist); + free_dclist(dclist); + return NULL; +} diff --git a/modules/lookup_dir.c b/modules/lookup_dir.c new file mode 100644 index 0000000..ab1d82f --- /dev/null +++ b/modules/lookup_dir.c @@ -0,0 +1,248 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_dir.c - module for including master files in a directory. + * + * Copyright 2011 Red Hat, Inc. All rights reserved. + * Copyright 2011 Masatake YAMATO + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MODPREFIX "lookup(dir): " + +#define MAX_INCLUDE_DEPTH 16 + +#define AUTOFS_DIR_EXT ".autofs" +#define AUTOFS_DIR_EXTSIZ (sizeof(AUTOFS_DIR_EXT) - 1) + +/* Work around non-GNU systems that don't provide versionsort */ +#ifdef WITHOUT_VERSIONSORT +#define versionsort alphasort +#endif + +struct lookup_context { + const char *mapname; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt) +{ + struct stat st; + + if (argc < 1) { + logerr(MODPREFIX "No map name"); + return 1; + } + + ctxt->mapname = argv[0]; + + if (ctxt->mapname[0] != '/') { + logmsg(MODPREFIX + "dir map %s is not an absolute pathname", argv[0]); + return 1; + } + + if (access(ctxt->mapname, R_OK)) { + warn(LOGOPT_NONE, MODPREFIX + "dir map %s missing or not readable", argv[0]); + return 1; + } + + if (stat(ctxt->mapname, &st)) { + warn(LOGOPT_NONE, MODPREFIX + "dir map %s, could not stat", argv[0]); + return 1; + } + + if ((!S_ISDIR(st.st_mode)) && (!S_ISLNK(st.st_mode))) { + warn(LOGOPT_NONE, MODPREFIX + "dir map %s, is not a directory", argv[0]); + return 1; + } + + return 0; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (do_init(mapfmt, argc, argv, ctxt)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context new; + int ret; + + ret = do_init(mapfmt, argc, argv, &new); + if (ret) + return 1; + + ctxt->mapname = new.mapname; + + return 0; +} + +static int acceptable_dirent_p(const struct dirent *e) +{ + size_t namesz; + + namesz = strlen(e->d_name); + if (!namesz) + return 0; + + if (e->d_name[0] == '.') + return 0; + + if (namesz < AUTOFS_DIR_EXTSIZ + 1 || + strcmp(e->d_name + (namesz - AUTOFS_DIR_EXTSIZ), + AUTOFS_DIR_EXT)) + return 0; + + return 1; +} + + +static int include_file(struct master *master, time_t age, struct lookup_context* ctxt, struct dirent *e) +{ + unsigned int logopt = master->logopt; + char included_path[PATH_MAX + 1]; + int included_path_len; + char *save_name; + int status; + + included_path_len = snprintf(included_path, + PATH_MAX + 1, + "%s/%s", + ctxt->mapname, + e->d_name); + if (included_path_len > PATH_MAX) + return NSS_STATUS_NOTFOUND; + + save_name = master->name; + master->name = included_path; + + master->depth++; + debug(logopt, MODPREFIX "include: %s", master->name); + status = lookup_nss_read_master(master, age); + if (status != NSS_STATUS_SUCCESS) { + warn(logopt, + MODPREFIX + "failed to read included master map %s", + master->name); + } + master->depth--; + + master->name = save_name; + return NSS_STATUS_SUCCESS; +} + + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + int n, i; + struct dirent **namelist = NULL; + struct lookup_context *ctxt = (struct lookup_context *) context; + unsigned int logopt = master->logopt; + char buf[MAX_ERR_BUF]; + + + if (master->depth > MAX_INCLUDE_DEPTH) { + error(logopt, MODPREFIX + "maximum include depth exceeded %s", master->name); + return NSS_STATUS_UNAVAIL; + } + + debug(logopt, MODPREFIX "scandir: %s", ctxt->mapname); + n = scandir(ctxt->mapname, &namelist, acceptable_dirent_p, versionsort); + if (n < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + + error(logopt, + MODPREFIX "could not scan master map dir %s: %s", + ctxt->mapname, + estr); + return NSS_STATUS_UNAVAIL; + } + + for (i = 0; i < n; i++) { + struct dirent *e = namelist[i]; + + include_file(master, age, ctxt, e); + free(e); + } + free(namelist); + + return NSS_STATUS_SUCCESS; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + return NSS_STATUS_UNKNOWN; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + return NSS_STATUS_UNKNOWN; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + + free(ctxt); + return 0; +} diff --git a/modules/lookup_file.c b/modules/lookup_file.c new file mode 100644 index 0000000..b548ac3 --- /dev/null +++ b/modules/lookup_file.c @@ -0,0 +1,1326 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_file.c - module for Linux automount to query a flat file map + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2001-2003 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" + +#define MODPREFIX "lookup(file): " + +#define MAX_INCLUDE_DEPTH 16 + +typedef enum { + st_begin, st_compare, st_star, st_badent, st_entspc, st_getent +} LOOKUP_STATE; + +typedef enum { got_nothing, got_star, got_real, got_plus } FOUND_STATE; +typedef enum { esc_none, esc_char, esc_val, esc_all } ESCAPES; + +struct lookup_context { + const char *mapname; + int opts_argc; + const char **opts_argv; + time_t last_read; + struct parse_mod *parse; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + int ret = 0; + + if (argc < 1) { + logerr(MODPREFIX "No map name"); + return 1; + } + + ctxt->mapname = argv[0]; + + if (ctxt->mapname[0] != '/') { + logmsg(MODPREFIX + "file map %s is not an absolute pathname", argv[0]); + return 1; + } + + if (access(ctxt->mapname, R_OK)) { + warn(LOGOPT_NONE, MODPREFIX + "file map %s missing or not readable", argv[0]); + return 1; + } + + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + argc--; + argv++; + + ctxt->opts_argv = copy_argv(argc, (const char **) argv); + if (ctxt->opts_argv == NULL) { + warn(LOGOPT_NONE, MODPREFIX "failed to duplicate options"); + return 1; + } + ctxt->opts_argc = argc; + + if (reinit) { + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc, argv); + if (!ctxt->parse) { + logmsg(MODPREFIX "failed to open parse context"); + ret = 1; + } + } + + if (ret) + free_argv(ctxt->opts_argc, ctxt->opts_argv); + + return ret; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, + void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (do_init(mapfmt, argc, argv, ctxt, 0)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + *context = new; + + free_argv(ctxt->opts_argc, ctxt->opts_argv); + free(ctxt); + + return 0; +} + +static int read_one(unsigned logopt, FILE *f, char *key, unsigned int *k_len, char *mapent, unsigned int *m_len) +{ + char *kptr, *p; + int mapent_len, key_len; + int ch, nch; + LOOKUP_STATE state; + FOUND_STATE getting, gotten; + ESCAPES escape; + + kptr = key; + p = NULL; + mapent_len = key_len = 0; + state = st_begin; + memset(key, 0, KEY_MAX_LEN + 1); + memset(mapent, 0, MAPENT_MAX_LEN + 1); + getting = gotten = got_nothing; + escape = esc_none; + + /* This is ugly. We can't just remove \ escape sequences in the value + portion of an entry, because the parsing routine may need it. */ + + while ((ch = getc(f)) != EOF) { + switch (escape) { + case esc_none: + if (ch == '\\') { + /* Handle continuation lines */ + if ((nch = getc(f)) == '\n') + continue; + ungetc(nch, f); + escape = esc_char; + } + if (ch == '"') + escape = esc_all; + break; + + case esc_char: + escape = esc_val; + break; + + case esc_val: + escape = esc_none; + break; + + case esc_all: + if (ch == '"') + escape = esc_none; + break; + } + + switch (state) { + case st_begin: + if (!escape) { + if (isspace(ch)); + else if (ch == '#') + state = st_badent; + else if (ch == '*') { + state = st_star; + *(kptr++) = ch; + key_len++; + } else { + if (ch == '+') + gotten = got_plus; + state = st_compare; + *(kptr++) = ch; + key_len++; + } + } else if (escape == esc_all) { + state = st_compare; + *(kptr++) = ch; + key_len++; + } else if (escape == esc_char); + else + state = st_badent; + break; + + case st_compare: + if (ch == '\n') { + state = st_begin; + if (gotten == got_plus) + goto got_it; + else if (escape == esc_all) { + warn(logopt, MODPREFIX + "unmatched \" in map key %s", key); + goto next; + } else if (escape != esc_val) + goto got_it; + } else if (isspace(ch) && !escape) { + getting = got_real; + state = st_entspc; + if (gotten == got_plus) + goto got_it; + } else if (escape == esc_char); + else { + if (key_len == KEY_MAX_LEN) { + state = st_badent; + gotten = got_nothing; + warn(logopt, + MODPREFIX "map key \"%s...\" " + "is too long. The maximum key " + "length is %d", key, + KEY_MAX_LEN); + } else { + if (escape == esc_val) { + *(kptr++) = '\\'; + key_len++; + } + *(kptr++) = ch; + key_len++; + } + } + break; + + case st_star: + if (ch == '\n') + state = st_begin; + else if (isspace(ch) && gotten < got_star && !escape) { + getting = got_star; + state = st_entspc; + } else if (escape != esc_char) + state = st_badent; + break; + + case st_badent: + if (ch == '\n') { + nch = getc(f); + if (nch != EOF && isblank(nch)) { + ungetc(nch, f); + break; + } + ungetc(nch, f); + state = st_begin; + if (gotten == got_real || gotten == getting) + goto got_it; + warn(logopt, MODPREFIX + "bad map entry \"%s...\" for key " + "\"%s\"", mapent, key); + goto next; + } else if (!isblank(ch)) + gotten = got_nothing; + break; + + case st_entspc: + if (ch == '\n') + state = st_begin; + else if (!isspace(ch) || escape) { + if (escape) { + if (escape == esc_char) + break; + if (ch <= 32) { + getting = got_nothing; + state = st_badent; + break; + } + p = mapent; + if (escape == esc_val) { + *(p++) = '\\'; + mapent_len++; + } + *(p++) = ch; + mapent_len++; + } else { + p = mapent; + *(p++) = ch; + mapent_len = 1; + } + state = st_getent; + gotten = getting; + } + break; + + case st_getent: + if (ch == '\n') { + if (escape == esc_all) { + state = st_begin; + warn(logopt, MODPREFIX + "unmatched \" in %s for key %s", + mapent, key); + goto next; + } + nch = getc(f); + if (nch != EOF && isblank(nch)) { + ungetc(nch, f); + state = st_badent; + break; + } + ungetc(nch, f); + state = st_begin; + if (gotten == got_real || gotten == getting) + goto got_it; + } else if (mapent_len < MAPENT_MAX_LEN) { + if (p) { + mapent_len++; + *(p++) = ch; + } + nch = getc(f); + if (nch == EOF && + (gotten == got_real || gotten == getting)) + goto got_it; + ungetc(nch, f); + } else { + warn(logopt, + MODPREFIX "map entry \"%s...\" for key " + "\"%s\" is too long. The maximum entry" + " size is %d", mapent, key, + MAPENT_MAX_LEN); + state = st_badent; + } + break; + } + continue; + + got_it: + if (gotten == got_nothing) + goto next; + + *k_len = key_len; + *m_len = mapent_len; + + return 1; + + next: + kptr = key; + p = NULL; + mapent_len = key_len = 0; + memset(key, 0, KEY_MAX_LEN + 1); + memset(mapent, 0, MAPENT_MAX_LEN + 1); + getting = gotten = got_nothing; + escape = esc_none; + } + + return 0; +} + +static int check_master_self_include(struct master *master, struct lookup_context *ctxt) +{ + char *m_path, *m_base, *i_path, *i_base; + + /* + * If we are including a file map then check the + * full path of the map. + */ + if (*master->name == '/') { + if (!strcmp(master->name, ctxt->mapname)) + return 1; + else + return 0; + } + + /* Otherwise only check the map name itself. */ + + i_path = strdup(ctxt->mapname); + if (!i_path) + return 0; + i_base = basename(i_path); + + m_path = strdup(master->name); + if (!m_path) { + free(i_path); + return 0; + } + m_base = basename(m_path); + + if (!strcmp(m_base, i_base)) { + free(i_path); + free(m_path); + return 1; + } + free(i_path); + free(m_path); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + unsigned int timeout = master->default_timeout; + unsigned int logging = master->default_logging; + unsigned int logopt = master->logopt; + char *buffer; + int blen; + char path[KEY_MAX_LEN + 1]; + char ent[MAPENT_MAX_LEN + 1]; + FILE *f; + unsigned int path_len, ent_len; + int entry, cur_state; + + /* Don't return fail on self include, skip source */ + if (master->recurse) + return NSS_STATUS_TRYAGAIN; + + if (master->depth > MAX_INCLUDE_DEPTH) { + error(logopt, MODPREFIX + "maximum include depth exceeded %s", master->name); + return NSS_STATUS_UNAVAIL; + } + + f = open_fopen_r(ctxt->mapname); + if (!f) { + if (errno == ENOENT) + return NSS_STATUS_NOTFOUND; + error(logopt, + MODPREFIX "could not open master map file %s", + ctxt->mapname); + return NSS_STATUS_UNAVAIL; + } + + while(1) { + entry = read_one(logopt, f, path, &path_len, ent, &ent_len); + if (!entry) { + if (feof(f)) + break; + if (ferror(f)) { + warn(logopt, MODPREFIX + "error reading map %s", ctxt->mapname); + break; + } + continue; + } + + debug(logopt, MODPREFIX "read entry %s", path); + + /* + * If key starts with '+' it has to be an + * included map. + */ + if (*path == '+') { + char *save_name; + unsigned int inc; + int status; + + save_name = master->name; + master->name = path + 1; + + inc = check_master_self_include(master, ctxt); + if (inc) + master->recurse = 1; + master->depth++; + status = lookup_nss_read_master(master, age); + if (status != NSS_STATUS_SUCCESS) { + warn(logopt, + MODPREFIX + "failed to read included master map %s", + master->name); + if (status != NSS_STATUS_NOTFOUND) { + /* + * If we're starting up we need the whole + * master map initially, so tell the upper + * layer to retry. + */ + master->read_fail = 1; + } + } + master->depth--; + master->recurse = 0; + + master->name = save_name; + } else { + blen = path_len + 1 + ent_len + 2; + buffer = malloc(blen); + if (!buffer) { + error(logopt, + MODPREFIX "could not malloc parse buffer"); + fclose(f); + return NSS_STATUS_UNAVAIL; + } + memset(buffer, 0, blen); + + strcpy(buffer, path); + strcat(buffer, " "); + strcat(buffer, ent); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + master_parse_entry(buffer, timeout, logging, age); + free(buffer); + pthread_setcancelstate(cur_state, NULL); + } + + if (feof(f)) + break; + } + + fclose(f); + + return NSS_STATUS_SUCCESS; +} + +static int check_self_include(const char *key, struct lookup_context *ctxt) +{ + char *m_key, *m_base, *i_key, *i_base; + + /* + * If we are including a file map then check the + * full path of the map. + */ + if (*(key + 1) == '/') { + if (!strcmp(key + 1, ctxt->mapname)) + return 1; + else + return 0; + } + + i_key = strdup(key + 1); + if (!i_key) + return 0; + i_base = basename(i_key); + + m_key = strdup(ctxt->mapname); + if (!m_key) { + free(i_key); + return 0; + } + m_base = basename(m_key); + + if (!strcmp(m_base, i_base)) { + free(i_key); + free(m_key); + return 1; + } + free(i_key); + free(m_key); + + return 0; +} + +static struct map_source * +prepare_plus_include(struct autofs_point *ap, + struct map_source *source, + time_t age, char *key, unsigned int inc, + struct lookup_context *ctxt) +{ + struct map_source *new; + struct map_type_info *info; + const char *argv[2]; + char **tmp_argv, **tmp_opts; + int argc; + char *buf; + + /* + * TODO: + * Initially just consider the passed in key to be a simple map + * name (and possible source) and use the global map options in + * the given autofs_point. ie. global options override. + * + * Later we might want to parse this and fill in the autofs_point + * options fields. + */ + /* skip plus */ + buf = strdup(key + 1); + if (!buf) { + error(ap->logopt, MODPREFIX "failed to strdup key"); + return NULL; + } + + if (!(info = parse_map_type_info(buf))) { + error(ap->logopt, MODPREFIX "failed to parse map info"); + free(buf); + return NULL; + } + + argc = 1; + argv[0] = info->map; + argv[1] = NULL; + + tmp_argv = (char **) copy_argv(argc, argv); + if (!tmp_argv) { + error(ap->logopt, MODPREFIX "failed to allocate args vector"); + free_map_type_info(info); + free(buf); + return NULL; + } + + tmp_opts = (char **) copy_argv(ctxt->opts_argc, ctxt->opts_argv); + if (!tmp_opts) { + error(ap->logopt, MODPREFIX "failed to allocate options args vector"); + free_argv(argc, (const char **) tmp_argv); + free_map_type_info(info); + free(buf); + return NULL; + } + + tmp_argv = append_argv(argc, tmp_argv, ctxt->opts_argc, tmp_opts); + if (!tmp_argv) { + error(ap->logopt, MODPREFIX "failed to append options vector"); + free_map_type_info(info); + free(buf); + return NULL; + } + argc += ctxt->opts_argc; + + new = master_find_source_instance(source, + info->type, info->format, + argc, (const char **) tmp_argv); + if (new) { + /* + * Make sure included map age is in sync with its owner + * or we could incorrectly wipe out its entries. + */ + new->age = age; + new->stale = 1; + } else { + new = master_add_source_instance(source, + info->type, info->format, age, + argc, (const char **) tmp_argv); + if (!new) { + free_argv(argc, (const char **) tmp_argv); + free_map_type_info(info); + free(buf); + error(ap->logopt, "failed to add included map instance"); + return NULL; + } + } + free_argv(argc, (const char **) tmp_argv); + + new->depth = source->depth + 1; + if (inc) + new->recurse = 1; + + free_map_type_info(info); + free(buf); + + return new; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + char key[KEY_MAX_LEN + 1]; + char mapent[MAPENT_MAX_LEN + 1]; + FILE *f; + unsigned int k_len, m_len; + int entry; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + if (source->recurse) + return NSS_STATUS_TRYAGAIN; + + if (source->depth > MAX_INCLUDE_DEPTH) { + error(ap->logopt, + "maximum include depth exceeded %s", ctxt->mapname); + return NSS_STATUS_UNAVAIL; + } + + f = open_fopen_r(ctxt->mapname); + if (!f) { + if (errno == ENOENT) + return NSS_STATUS_NOTFOUND; + error(ap->logopt, + MODPREFIX "could not open map file %s", ctxt->mapname); + return NSS_STATUS_UNAVAIL; + } + + while(1) { + entry = read_one(ap->logopt, f, key, &k_len, mapent, &m_len); + if (!entry) { + if (feof(f)) + break; + if (ferror(f)) { + warn(ap->logopt, MODPREFIX + "error reading map %s", ctxt->mapname); + break; + } + continue; + } + + /* + * If key starts with '+' it has to be an + * included map. + */ + if (*key == '+') { + struct map_source *inc_source; + unsigned int inc; + int status; + + debug(ap->logopt, "read included map %s", key); + + inc = check_self_include(key, ctxt); + + inc_source = prepare_plus_include(ap, source, + age, key, inc, ctxt); + if (!inc_source) { + debug(ap->logopt, + "failed to select included map %s", key); + continue; + } + + /* Gim'ee some o' that 16k stack baby !! */ + status = lookup_nss_read_map(ap, inc_source, age); + if (!status) { + warn(ap->logopt, + "failed to read included map %s", key); + } + } else { + char *s_key; + + if (source->flags & MAP_FLAG_FORMAT_AMD) { + if (!strcmp(key, "/defaults")) { + cache_writelock(mc); + cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + continue; + } + /* Don't fail on "/" in key => type == 0 */ + s_key = sanitize_path(key, k_len, 0, ap->logopt); + if (!s_key) + continue; + } else { + s_key = sanitize_path(key, k_len, ap->type, ap->logopt); + if (!s_key) + continue; + } + + cache_writelock(mc); + cache_update(mc, source, s_key, mapent, age); + cache_unlock(mc); + + free(s_key); + } + + if (feof(f)) + break; + } + + source->age = age; + ctxt->last_read = time(NULL); + + fclose(f); + + return NSS_STATUS_SUCCESS; +} + +static int match_key(struct autofs_point *ap, + struct map_source *source, char *map_key, + const char *key, size_t key_len, const char *mapent) +{ + char buf[MAX_ERR_BUF]; + struct mapent_cache *mc; + time_t age = monotonic_time(NULL); + char *lkp_key; + char *prefix; + size_t map_key_len; + int ret, eq; + + mc = source->mc; + + /* exact match is a match for both autofs and amd */ + eq = strcmp(map_key, key); + if (eq == 0) { + cache_writelock(mc); + ret = cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + return ret; + } + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) + return CHE_FAIL; + + map_key_len = strlen(map_key); + + lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "strdup: %s", estr); + return CHE_FAIL; + } + + ret = CHE_FAIL; + + if (map_key_len > (strlen(lkp_key) + 2)) + goto done; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. If we get a match + * then update the cache with the read key and its mapent. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + size_t len; + *prefix = '\0'; + len = strlen(lkp_key); + eq = strncmp(map_key, lkp_key, len); + if (!eq && map_key[len + 1] == '*') { + cache_writelock(mc); + ret = cache_update(mc, source, map_key, mapent, age); + cache_unlock(mc); + goto done; + } + } +done: + free(lkp_key); + return ret; +} + +static int lookup_one(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc = source->mc; + char mkey[KEY_MAX_LEN + 1]; + char mapent[MAPENT_MAX_LEN + 1]; + time_t age = monotonic_time(NULL); + FILE *f; + unsigned int k_len, m_len; + int entry, ret; + + f = open_fopen_r(ctxt->mapname); + if (!f) { + error(ap->logopt, + MODPREFIX "could not open map file %s", ctxt->mapname); + return CHE_FAIL; + } + + while(1) { + entry = read_one(ap->logopt, f, mkey, &k_len, mapent, &m_len); + if (entry) { + /* + * If key starts with '+' it has to be an + * included map. + */ + if (*mkey == '+') { + struct map_source *inc_source; + unsigned int inc; + int status; + + debug(ap->logopt, + MODPREFIX "lookup included map %s", mkey); + + inc = check_self_include(mkey, ctxt); + + inc_source = prepare_plus_include(ap, source, + age, mkey, inc, ctxt); + if (!inc_source) { + debug(ap->logopt, + MODPREFIX + "failed to select included map %s", + key); + continue; + } + + /* Gim'ee some o' that 16k stack baby !! */ + status = lookup_nss_mount(ap, inc_source, key, key_len); + if (status) { + fclose(f); + return CHE_COMPLETED; + } + } else { + char *s_key; + + if (source->flags & MAP_FLAG_FORMAT_AMD) { + if (!strcmp(mkey, "/defaults")) { + cache_writelock(mc); + cache_update(mc, source, mkey, mapent, age); + cache_unlock(mc); + continue; + } + /* Don't fail on "/" in key => type == 0 */ + s_key = sanitize_path(mkey, k_len, 0, ap->logopt); + if (!s_key) + continue; + } else { + s_key = sanitize_path(mkey, k_len, ap->type, ap->logopt); + if (!s_key) + continue; + + if (key_len != strlen(s_key)) { + free(s_key); + continue; + } + } + + ret = match_key(ap, source, + s_key, key, key_len, mapent); + if (ret == CHE_FAIL) { + free(s_key); + continue; + } + + free(s_key); + + fclose(f); + + return ret; + } + } + + if (feof(f)) + break; + + if (ferror(f)) { + warn(ap->logopt, MODPREFIX + "error reading map %s", ctxt->mapname); + break; + } + } + + fclose(f); + + return CHE_MISSING; +} + +static int lookup_wild(struct autofs_point *ap, + struct map_source *source, struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char mkey[KEY_MAX_LEN + 1]; + char mapent[MAPENT_MAX_LEN + 1]; + time_t age = monotonic_time(NULL); + FILE *f; + unsigned int k_len, m_len; + int entry, ret; + + mc = source->mc; + + f = open_fopen_r(ctxt->mapname); + if (!f) { + error(ap->logopt, + MODPREFIX "could not open map file %s", ctxt->mapname); + return CHE_FAIL; + } + + while(1) { + entry = read_one(ap->logopt, f, mkey, &k_len, mapent, &m_len); + if (entry) { + int eq; + + eq = (*mkey == '*' && k_len == 1); + if (eq == 0) + continue; + + cache_writelock(mc); + ret = cache_update(mc, source, "*", mapent, age); + cache_unlock(mc); + + fclose(f); + + return ret; + } + + if (feof(f)) + break; + + if (ferror(f)) { + warn(ap->logopt, MODPREFIX + "error reading map %s", ctxt->mapname); + break; + } + } + + fclose(f); + + return CHE_MISSING; +} + +static int check_map_indirect(struct autofs_point *ap, + struct map_source *source, + char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + struct mapent *exists; + int ret = CHE_OK; + + mc = source->mc; + + ret = lookup_one(ap, source, key, key_len, ctxt); + if (ret == CHE_COMPLETED) + return NSS_STATUS_COMPLETED; + + if (ret == CHE_FAIL) + return NSS_STATUS_NOTFOUND; + + cache_writelock(mc); + if (source->flags & MAP_FLAG_FORMAT_AMD) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup_distinct(mc, key); + /* Not found in the map but found in the cache */ + if (exists && exists->source == source && ret & CHE_MISSING) { + if (exists->mapent) { + free(exists->mapent); + exists->mapent = NULL; + exists->status = 0; + } + } + cache_unlock(mc); + + if (ret == CHE_MISSING) { + struct mapent *we; + int wild = CHE_MISSING; + + wild = lookup_wild(ap, source, ctxt); + /* + * Check for map change and update as needed for + * following cache lookup. + */ + cache_writelock(mc); + we = cache_lookup_distinct(mc, "*"); + if (we) { + /* Wildcard entry existed and is now gone */ + if (we->source == source && (wild & CHE_MISSING)) + cache_delete(mc, "*"); + } + cache_unlock(mc); + + if (wild & (CHE_OK | CHE_UPDATED)) + return NSS_STATUS_SUCCESS; + } + + if (ret == CHE_MISSING) + return NSS_STATUS_NOTFOUND; + + return NSS_STATUS_SUCCESS; +} + +static int map_update_needed(struct autofs_point *ap, + struct map_source *source, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + struct mapent *me; + struct stat st; + int ret = 1; + + mc = source->mc; + + /* + * We can skip the map lookup and cache update altogether + * if we know the map hasn't been modified since it was + * last read. If it has then we can mark the map stale + * so a re-read is triggered following the lookup. + */ + if (stat(ctxt->mapname, &st)) { + error(ap->logopt, MODPREFIX + "file map %s, could not stat", ctxt->mapname); + return -1; + } + + if (st.st_mtime <= ctxt->last_read) { + /* + * If any map instances are present for this source + * then either we have plus included entries or we + * are looking through the list of nsswitch sources. + * In either case, or if it's a "multi" source, we + * cannot avoid reading through the map because we + * must preserve the key order over multiple sources + * or maps. But also, we can't know, at this point, + * if a source instance has been changed since the + * last time we checked it. + */ + if (!source->instance && + source->type && strcmp(source->type, "multi")) + ret = 0; + } else + source->stale = 1; + + return ret; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + char key[KEY_MAX_LEN + 1]; + int key_len; + char *lkp_key; + char *mapent = NULL; + char mapent_buf[MAPENT_MAX_LEN + 1]; + char buf[MAX_ERR_BUF]; + int status = 0; + int ret = 1; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + if (source->recurse) + return NSS_STATUS_UNAVAIL; + + if (source->depth > MAX_INCLUDE_DEPTH) { + error(ap->logopt, + MODPREFIX + "maximum include depth exceeded %s", ctxt->mapname); + return NSS_STATUS_SUCCESS; + } + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + } else { + key_len = expandamdent(name, NULL, NULL); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + memset(key, 0, KEY_MAX_LEN + 1); + expandamdent(name, key, NULL); + debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, key, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, key); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, key); + } + cache_unlock(smc); + } + } + } + + /* + * We can't check the direct mount map as if it's not in + * the map cache already we never get a mount lookup, so + * we never know about it. + */ + if (ap->type == LKP_INDIRECT && *key != '/') { + int ret; + + ret = map_update_needed(ap, source, ctxt); + if (!ret) + goto do_cache_lookup; + /* Map isn't accessable, just try the cache */ + if (ret < 0) + goto do_cache_lookup; + + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me && me->multi) + lkp_key = strdup(me->multi->key); + else if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + cache_unlock(mc); + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNKNOWN; + } + + status = check_map_indirect(ap, source, + lkp_key, strlen(lkp_key), ctxt); + free(lkp_key); + if (status) { + if (status == NSS_STATUS_COMPLETED) + return NSS_STATUS_SUCCESS; + return NSS_STATUS_NOTFOUND; + } + } + + /* + * We can't take the writelock for direct mounts. If we're + * starting up or trying to re-connect to an existing direct + * mount we could be iterating through the map entries with + * the readlock held. But we don't need to update the cache + * when we're starting up so just take the readlock in that + * case. + */ +do_cache_lookup: + if (ap->flags & MOUNT_FLAG_REMOUNT) + cache_readlock(mc); + else + cache_writelock(mc); + + if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + cache_unlock(mc); + return NSS_STATUS_UNKNOWN; + } + + me = match_cached_key(ap, MODPREFIX, source, lkp_key); + if (me && me->mapent) { + if (me && (me->source == source || *me->key == '/')) { + strcpy(mapent_buf, me->mapent); + mapent = mapent_buf; + } + } + cache_unlock(mc); + free(lkp_key); + + if (!me) + return NSS_STATUS_NOTFOUND; + + if (!mapent) + return NSS_STATUS_TRYAGAIN; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); + ret = ctxt->parse->parse_mount(ap, key, key_len, + mapent, ctxt->parse->context); + if (ret) { + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, key, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + free_argv(ctxt->opts_argc, ctxt->opts_argv); + free(ctxt); + return rv; +} diff --git a/modules/lookup_hesiod.c b/modules/lookup_hesiod.c new file mode 100644 index 0000000..3bd6200 --- /dev/null +++ b/modules/lookup_hesiod.c @@ -0,0 +1,502 @@ +/* + * lookup_hesiod.c + * + * Module for Linux automountd to access automount maps in hesiod filsys + * entries. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "hesiod" +#define AMD_MAP_PREFIX "hesiod." +#define AMD_MAP_PREFIX_LEN 7 + +#define MODPREFIX "lookup(hesiod): " +#define HESIOD_LEN 512 + +struct lookup_context { + const char *mapname; + struct parse_mod *parser; + void *hesiod_context; +}; + +static pthread_mutex_t hesiod_mutex = PTHREAD_MUTEX_INITIALIZER; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + char buf[MAX_ERR_BUF]; + int ret = 0; + + /* Initialize the resolver. */ + res_init(); + + /* Initialize the hesiod context. */ + if (hesiod_init(&(ctxt->hesiod_context)) != 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "hesiod_init(): %s", estr); + return 1; + } + + /* If a map type isn't explicitly given, parse it as hesiod entries. */ + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + if (!strcmp(mapfmt, "amd")) { + /* amd formated hesiod maps have a map name */ + const char *mapname = argv[0]; + if (strncmp(mapname, AMD_MAP_PREFIX, AMD_MAP_PREFIX_LEN)) { + hesiod_end(ctxt->hesiod_context); + logerr(MODPREFIX + "incorrect prefix for hesiod map %s", mapname); + return 1; + } + ctxt->mapname = mapname; + argc--; + argv++; + } + + if (reinit) { + ret = reinit_parse(ctxt->parser, mapfmt, + MODPREFIX, argc - 1, argv - 1); + if (ret) + logerr(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parser = open_parse(mapfmt, + MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parser) { + logerr(MODPREFIX "failed to open parse context"); + ret = 1; + } + } + + if (ret) + hesiod_end(ctxt->hesiod_context); + + return ret; +} + +/* This initializes a context (persistent non-global data) for queries to + this module. */ +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + int ret; + + *context = NULL; + + /* If we can't build a context, bail. */ + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + ret = do_init(mapfmt, argc, argv, ctxt, 0); + if (ret) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + /* If we can't build a context, bail. */ + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parser = ctxt->parser; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + *context = new; + + hesiod_end(ctxt->hesiod_context); + free(ctxt); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + return NSS_STATUS_UNKNOWN; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + return NSS_STATUS_UNKNOWN; +} + +/* + * Lookup and act on a filesystem name. In this case, lookup the "filsys" + * record in hesiod. If it's an AFS or NFS filesystem, parse it out. If + * it's an ERR filesystem, it's an error message we should log. Otherwise, + * assume it's something we know how to deal with already (generic). + */ +static int lookup_one(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char **hes_result; + char **record, *best_record = NULL, *p; + int priority, lowest_priority = INT_MAX; + int ret, status; + + mc = source->mc; + + status = pthread_mutex_lock(&hesiod_mutex); + if (status) + fatal(status); + + hes_result = hesiod_resolve(ctxt->hesiod_context, key, "filsys"); + if (!hes_result || !hes_result[0]) { + int err = errno; + if (!defaults_disable_not_found_message()) { + error(ap->logopt, + MODPREFIX "key \"%s\" not found in map", key); + } + status = pthread_mutex_unlock(&hesiod_mutex); + if (status) + fatal(status); + if (err == HES_ER_NOTFOUND) + return CHE_MISSING; + else + return CHE_FAIL; + } + + /* autofs doesn't support falling back to alternate records, so just + find the record with the lowest priority and hope it works. + -- Aaron Ucko 2002-03-11 */ + for (record = hes_result; *record; ++record) { + p = strrchr(*record, ' '); + if ( p && isdigit(p[1]) ) { + priority = atoi(p+1); + } else { + priority = INT_MAX - 1; + } + if (priority < lowest_priority) { + lowest_priority = priority; + best_record = *record; + } + } + + cache_writelock(mc); + ret = cache_update(mc, source, key, best_record, monotonic_time(NULL)); + cache_unlock(mc); + if (ret == CHE_FAIL) { + hesiod_free_list(ctxt->hesiod_context, hes_result); + status = pthread_mutex_unlock(&hesiod_mutex); + if (status) + fatal(status); + return ret; + } + + debug(ap->logopt, + MODPREFIX "lookup for \"%s\" gave \"%s\"", + key, best_record); + + hesiod_free_list(ctxt->hesiod_context, hes_result); + + status = pthread_mutex_unlock(&hesiod_mutex); + if (status) + fatal(status); + + return ret; +} + +static int lookup_one_amd(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char *hesiod_base; + char **hes_result; + char *lkp_key; + int status, ret; + + mc = source->mc; + + hesiod_base = conf_amd_get_hesiod_base(); + if (!hesiod_base) + return CHE_FAIL; + + lkp_key = malloc(key_len + strlen(ctxt->mapname) - 7 + 2); + if (!lkp_key) { + free(hesiod_base); + return CHE_FAIL; + } + + strcpy(lkp_key, key); + strcat(lkp_key, "."); + strcat(lkp_key, ctxt->mapname + AMD_MAP_PREFIX_LEN); + + status = pthread_mutex_lock(&hesiod_mutex); + if (status) + fatal(status); + + hes_result = hesiod_resolve(ctxt->hesiod_context, lkp_key, hesiod_base); + if (!hes_result || !hes_result[0]) { + int err = errno; + if (err == HES_ER_NOTFOUND) + ret = CHE_MISSING; + else + ret = CHE_FAIL; + goto done; + } + + cache_writelock(mc); + ret = cache_update(mc, source, lkp_key, *hes_result, monotonic_time(NULL)); + cache_unlock(mc); + + if (hes_result) + hesiod_free_list(ctxt->hesiod_context, hes_result); +done: + free(lkp_key); + + status = pthread_mutex_unlock(&hesiod_mutex); + if (status) + fatal(status); + + return ret; +} + +static int match_amd_key(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + char buf[MAX_ERR_BUF]; + char *lkp_key; + char *prefix; + int ret; + + ret = lookup_one_amd(ap, source, key, key_len, ctxt); + if (ret == CHE_OK || ret == CHE_UPDATED) + return ret; + + lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "strdup: %s", estr); + return CHE_FAIL; + } + + ret = CHE_MISSING; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + char *match; + size_t len; + *prefix = '\0'; + len = strlen(lkp_key) + 3; + match = malloc(len); + if (!match) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + ret = CHE_FAIL; + goto done; + } + len--; + strcpy(match, lkp_key); + strcat(match, "/*"); + ret = lookup_one_amd(ap, source, match, len, ctxt); + free(match); + if (ret == CHE_OK || ret == CHE_UPDATED) + goto done; + } + + /* Lastly try the wildcard */ + ret = lookup_one_amd(ap, source, "*", 1, ctxt); +done: + free(lkp_key); + return ret; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct mapent_cache *mc; + char buf[MAX_ERR_BUF]; + struct map_source *source; + struct mapent *me; + char key[KEY_MAX_LEN + 1]; + size_t key_len; + char *lkp_key; + size_t len; + char *mapent; + int rv; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, + MODPREFIX "looking up root=\"%s\", name=\"%s\"", + ap->path, name); + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + } else { + key_len = expandamdent(name, NULL, NULL); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + expandamdent(name, key, NULL); + key[key_len] = '\0'; + debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, name, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, name); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, name); + } + cache_unlock(smc); + } + } + } + + /* If this is not here the filesystem stays busy, for some reason... */ + if (chdir("/")) + warn(ap->logopt, + MODPREFIX "failed to set working directory to \"/\""); + + len = key_len; + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) + lkp_key = strdup(key); + else { + rv = lookup_one_amd(ap, source, "/defaults", 9, ctxt); + if (rv == CHE_FAIL) + warn(ap->logopt, + MODPREFIX "failed to lookup \"/defaults\" entry"); + + if (!ap->pref) + lkp_key = strdup(key); + else { + len += strlen(ap->pref); + lkp_key = malloc(len + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, name); + } + } + } + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + return NSS_STATUS_UNKNOWN; + } + + if (source->flags & MAP_FLAG_FORMAT_AMD) + rv = match_amd_key(ap, source, lkp_key, len, ctxt); + else + rv = lookup_one(ap, source, lkp_key, len, ctxt); + + if (rv == CHE_FAIL) { + free(lkp_key); + return NSS_STATUS_UNAVAIL; + } + + me = match_cached_key(ap, MODPREFIX, source, lkp_key); + free(lkp_key); + if (!me) + return NSS_STATUS_NOTFOUND; + + if (!me->mapent) + return NSS_STATUS_UNAVAIL; + + mapent = strdup(me->mapent); + + rv = ctxt->parser->parse_mount(ap, key, key_len, + mapent, ctxt->parser->context); + free(mapent); + + /* + * Unavailable due to error such as module load fail + * or out of memory, etc. + */ + if (rv == 1 || rv == -1) + return NSS_STATUS_UNAVAIL; + + return NSS_STATUS_SUCCESS; +} + +/* This destroys a context for queries to this module. It releases the parser + structure (unloading the module) and frees the memory used by the context. */ +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parser); + + hesiod_end(ctxt->hesiod_context); + free(ctxt); + return rv; +} diff --git a/modules/lookup_hosts.c b/modules/lookup_hosts.c new file mode 100644 index 0000000..163b02d --- /dev/null +++ b/modules/lookup_hosts.c @@ -0,0 +1,416 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_hosts.c - module for Linux automount to mount the exports + * from a given host + * + * Copyright 2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +/* + * Avoid annoying compiler noise by using an alternate name for + * typedef name in mount.h + */ +#define name __dummy_type_name +#include "mount.h" +#undef name + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" +#define MODPREFIX "lookup(hosts): " + +pthread_mutex_t hostent_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct lookup_context { + struct parse_mod *parse; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +exports rpc_get_exports(const char *host, long seconds, long micros, unsigned int option); +void rpc_exports_free(exports list); + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + + mapfmt = MAPFMT_DEFAULT; + + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc, argv); + if (!ctxt->parse) { + logerr(MODPREFIX "failed to open parse context"); + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + int ret; + + mapfmt = MAPFMT_DEFAULT; + + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv); + if (ret) + return 1; + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + return NSS_STATUS_UNKNOWN; +} + +static char *get_exports(struct autofs_point *ap, const char *host) +{ + char buf[MAX_ERR_BUF]; + char *mapent; + exports exp, this; + + debug(ap->logopt, MODPREFIX "fetchng export list for %s", host); + + exp = rpc_get_exports(host, 10, 0, RPC_CLOSE_NOLINGER); + + mapent = NULL; + this = exp; + while (this) { + if (mapent) { + int len = strlen(mapent) + 1; + + len += strlen(host) + 2*(strlen(this->ex_dir) + 2) + 3; + mapent = realloc(mapent, len); + if (!mapent) { + char *estr; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + rpc_exports_free(exp); + return NULL; + } + strcat(mapent, " \""); + strcat(mapent, this->ex_dir); + strcat(mapent, "\""); + } else { + int len = 2*(strlen(this->ex_dir) + 2) + strlen(host) + 3; + + mapent = malloc(len); + if (!mapent) { + char *estr; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + rpc_exports_free(exp); + return NULL; + } + strcpy(mapent, "\""); + strcat(mapent, this->ex_dir); + strcat(mapent, "\""); + } + strcat(mapent, " \""); + strcat(mapent, host); + strcat(mapent, ":"); + strcat(mapent, this->ex_dir); + strcat(mapent, "\""); + + this = this->ex_next; + } + rpc_exports_free(exp); + + if (!mapent) + error(ap->logopt, MODPREFIX "exports lookup failed for %s", host); + + return mapent; +} + +static int do_parse_mount(struct autofs_point *ap, struct map_source *source, + const char *name, int name_len, char *mapent, + struct lookup_context *ctxt) +{ + int ret; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + ret = ctxt->parse->parse_mount(ap, name, name_len, + mapent, ctxt->parse->context); + if (ret) { + struct mapent_cache *mc = source->mc; + + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, name, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + return NSS_STATUS_SUCCESS; +} + +static void update_hosts_mounts(struct autofs_point *ap, + struct map_source *source, time_t age, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + struct mapent *me; + char *mapent; + int ret; + + mc = source->mc; + + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_writelock(mc); + me = cache_lookup_first(mc); + while (me) { + /* Hosts map entry not yet expanded or already expired */ + if (!me->multi) + goto next; + + debug(ap->logopt, MODPREFIX "get list of exports for %s", me->key); + + mapent = get_exports(ap, me->key); + if (mapent) { + cache_update(mc, source, me->key, mapent, age); + free(mapent); + } +next: + me = cache_lookup_next(mc, me); + } + pthread_cleanup_pop(1); + + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_readlock(mc); + me = cache_lookup_first(mc); + while (me) { + /* + * Hosts map entry not yet expanded, already expired + * or not the base of the tree + */ + if (!me->multi || me->multi != me) + goto cont; + + debug(ap->logopt, MODPREFIX + "attempt to update exports for %s", me->key); + + master_source_current_wait(ap->entry); + ap->entry->current = source; + ap->flags |= MOUNT_FLAG_REMOUNT; + ret = ctxt->parse->parse_mount(ap, me->key, strlen(me->key), + me->mapent, ctxt->parse->context); + if (ret) + warn(ap->logopt, MODPREFIX + "failed to parse mount %s", me->mapent); + ap->flags &= ~MOUNT_FLAG_REMOUNT; +cont: + me = cache_lookup_next(mc, me); + } + pthread_cleanup_pop(1); +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + struct hostent *host; + int status; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "read hosts map"); + + /* + * If we don't need to create directories then there's no use + * reading the map. We always need to read the whole map for + * direct mounts in order to mount the triggers. + */ + if (!(ap->flags & MOUNT_FLAG_GHOST) && ap->type != LKP_DIRECT) { + debug(ap->logopt, MODPREFIX + "map not browsable, update existing host entries only"); + update_hosts_mounts(ap, source, age, ctxt); + source->age = age; + return NSS_STATUS_SUCCESS; + } + + status = pthread_mutex_lock(&hostent_mutex); + if (status) { + error(ap->logopt, MODPREFIX "failed to lock hostent mutex"); + return NSS_STATUS_UNAVAIL; + } + + sethostent(0); + while ((host = gethostent()) != NULL) { + pthread_cleanup_push(cache_lock_cleanup, mc); + cache_writelock(mc); + cache_update(mc, source, host->h_name, NULL, age); + cache_unlock(mc); + pthread_cleanup_pop(0); + } + endhostent(); + + status = pthread_mutex_unlock(&hostent_mutex); + if (status) + error(ap->logopt, MODPREFIX "failed to unlock hostent mutex"); + + update_hosts_mounts(ap, source, age, ctxt); + source->age = age; + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + char *mapent = NULL; + int mapent_len; + time_t now = monotonic_time(NULL); + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, name, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, name); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, name); + } + cache_unlock(smc); + } + } + } + + cache_readlock(mc); + me = cache_lookup_distinct(mc, name); + if (!me) { + cache_unlock(mc); + /* + * We haven't read the list of hosts into the + * cache so go straight to the lookup. + */ + if (!(ap->flags & MOUNT_FLAG_GHOST)) { + /* + * If name contains a '/' we're searching for an + * offset that doesn't exist in the export list + * so it's NOTFOUND otherwise this could be a + * lookup for a new host. + */ + if (*name != '/' && strchr(name, '/')) + return NSS_STATUS_NOTFOUND; + goto done; + } + + if (*name == '/') + info(ap->logopt, MODPREFIX + "can't find path in hosts map %s", name); + else + info(ap->logopt, MODPREFIX + "can't find path in hosts map %s/%s", + ap->path, name); + + debug(ap->logopt, + MODPREFIX "lookup failed - update exports list"); + goto done; + } + /* + * Host map export entries are added to the cache as + * direct mounts. If the name we seek starts with a slash + * it must be a mount request for one of the exports. + */ + if (*name == '/') { + pthread_cleanup_push(cache_lock_cleanup, mc); + mapent_len = strlen(me->mapent); + mapent = malloc(mapent_len + 1); + if (mapent) + strcpy(mapent, me->mapent); + pthread_cleanup_pop(0); + } + cache_unlock(mc); + +done: + debug(ap->logopt, MODPREFIX "%s -> %s", name, mapent); + + if (!mapent) { + /* We need to get the exports list and update the cache. */ + mapent = get_exports(ap, name); + + /* Exports lookup failed so we're outa here */ + if (!mapent) + return NSS_STATUS_UNAVAIL; + + cache_writelock(mc); + cache_update(mc, source, name, mapent, now); + cache_unlock(mc); + } + + ret = do_parse_mount(ap, source, name, name_len, mapent, ctxt); + + free(mapent); + + return ret; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + free(ctxt); + return rv; +} diff --git a/modules/lookup_ldap.c b/modules/lookup_ldap.c new file mode 100644 index 0000000..98701e5 --- /dev/null +++ b/modules/lookup_ldap.c @@ -0,0 +1,3828 @@ +/* + * lookup_ldap.c - Module for Linux automountd to access automount + * maps in LDAP directories. + * + * Copyright 2001-2003 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version. + * + * This program 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" +#include "lookup_ldap.h" +#include "base64.h" + +#define MAPFMT_DEFAULT "sun" + +#define MODPREFIX "lookup(ldap): " + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +#define ENV_LDAPTLS_CERT "LDAPTLS_CERT" +#define ENV_LDAPTLS_KEY "LDAPTLS_KEY" + +static struct ldap_schema common_schema[] = { + {"nisMap", "nisMapName", "nisObject", "cn", "nisMapEntry"}, + {"automountMap", "ou", "automount", "cn", "automountInformation"}, + {"automountMap", "automountMapName", "automount", "automountKey", "automountInformation"}, +}; +static unsigned int common_schema_count = sizeof(common_schema)/sizeof(struct ldap_schema); + +static struct ldap_schema amd_timestamp = { + "madmap", "amdmapName", "amdmapTimestamp", NULL, "amdmapTimestamp" +}; + +static struct ldap_schema amd_schema = { + "amdmap", "amdmapName", "amdmap", "amdmapKey", "amdmapValue" +}; + +/* + * Initialization and de-initialization of LDAP and OpenSSL must be + * always serialized to avoid corruption of context structures inside + * these libraries. + */ +pthread_mutex_t ldapinit_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct ldap_search_params { + struct autofs_point *ap; + LDAP *ldap; + char *base; + char *query, **attrs; + struct berval *cookie; + ber_int_t pageSize; + int morePages; + ber_int_t totalCount; + LDAPMessage *result; + time_t age; +}; + +static int decode_percent_hack(const char *, char **); + +#ifdef WITH_SASL +static int set_env(unsigned logopt, const char *name, const char *val) +{ + int ret = setenv(name, val, 1); + if (ret == -1) { + error(logopt, "failed to set config value for %s", name); + return 0; + } + return 1; +} +#endif + +#ifndef HAVE_LDAP_CREATE_PAGE_CONTROL +int ldap_create_page_control(LDAP *ldap, ber_int_t pagesize, + struct berval *cookie, char isCritical, + LDAPControl **output) +{ + BerElement *ber; + int rc; + + if (!ldap || !output) + return LDAP_PARAM_ERROR; + + ber = ber_alloc_t(LBER_USE_DER); + if (!ber) + return LDAP_NO_MEMORY; + + if (ber_printf(ber, "{io}", pagesize, + (cookie && cookie->bv_val) ? cookie->bv_val : "", + (cookie && cookie->bv_val) ? cookie->bv_len : 0) + == LBER_ERROR) { + ber_free(ber, 1); + return LDAP_ENCODING_ERROR; + } + + rc = ldap_create_control(LDAP_CONTROL_PAGEDRESULTS, ber, isCritical, output); + + return rc; +} +#endif /* HAVE_LDAP_CREATE_PAGE_CONTROL */ + +#ifndef HAVE_LDAP_PARSE_PAGE_CONTROL +int ldap_parse_page_control(LDAP *ldap, LDAPControl **controls, + ber_int_t *totalcount, struct berval **cookie) +{ + int i, rc; + BerElement *theBer; + LDAPControl *listCtrlp; + + for (i = 0; controls[i] != NULL; i++) { + if (strcmp(controls[i]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0) { + listCtrlp = controls[i]; + + theBer = ber_init(&listCtrlp->ldctl_value); + if (!theBer) + return LDAP_NO_MEMORY; + + rc = ber_scanf(theBer, "{iO}", totalcount, cookie); + if (rc == LBER_ERROR) { + ber_free(theBer, 1); + return LDAP_DECODING_ERROR; + } + + ber_free(theBer, 1); + return LDAP_SUCCESS; + } + } + + return LDAP_CONTROL_NOT_FOUND; +} +#endif /* HAVE_LDAP_PARSE_PAGE_CONTROL */ + +static void ldapinit_mutex_lock(void) +{ + int status = pthread_mutex_lock(&ldapinit_mutex); + if (status) + fatal(status); + return; +} + +static void ldapinit_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&ldapinit_mutex); + if (status) + fatal(status); + return; +} + +static void uris_mutex_lock(struct lookup_context *ctxt) +{ + int status = pthread_mutex_lock(&ctxt->uris_mutex); + if (status) + fatal(status); + return; +} + +static void uris_mutex_unlock(struct lookup_context *ctxt) +{ + int status = pthread_mutex_unlock(&ctxt->uris_mutex); + if (status) + fatal(status); + return; +} + +int bind_ldap_simple(unsigned logopt, LDAP *ldap, const char *uri, struct lookup_context *ctxt) +{ + int rv; + + if (ctxt->auth_required == LDAP_AUTH_USESIMPLE) + rv = ldap_simple_bind_s(ldap, ctxt->user, ctxt->secret); + else if (ctxt->version == 2) + rv = ldap_simple_bind_s(ldap, ctxt->base, NULL); + else + rv = ldap_simple_bind_s(ldap, NULL, NULL); + + if (rv != LDAP_SUCCESS) { + if (!ctxt->uris) { + crit(logopt, MODPREFIX + "Unable to bind to the LDAP server: " + "%s, error %s", ctxt->server ? "" : "(default)", + ldap_err2string(rv)); + } else { + info(logopt, MODPREFIX "Unable to bind to the LDAP server: " + "%s, error %s", uri, ldap_err2string(rv)); + } + return -1; + } + + return 0; +} + +int __unbind_ldap_connection(unsigned logopt, + struct ldap_conn *conn, + struct lookup_context *ctxt) +{ + int rv = LDAP_SUCCESS; + + if (ctxt->use_tls == LDAP_TLS_RELEASE) + ctxt->use_tls = LDAP_TLS_INIT; +#ifdef WITH_SASL + if (ctxt->auth_required & LDAP_NEED_AUTH) + autofs_sasl_unbind(conn, ctxt); + /* No, sasl_dispose does not release the ldap connection + * unless it's using sasl EXTERNAL + */ +#endif + if (conn->ldap) { + rv = ldap_unbind_ext(conn->ldap, NULL, NULL); + conn->ldap = NULL; + } + if (rv != LDAP_SUCCESS) + error(logopt, "unbind failed: %s", ldap_err2string(rv)); + + return rv; +} + +int unbind_ldap_connection(unsigned logopt, + struct ldap_conn *conn, + struct lookup_context *ctxt) +{ + int rv; + + ldapinit_mutex_lock(); + rv = __unbind_ldap_connection(logopt, conn, ctxt); + ldapinit_mutex_unlock(); + + return rv; +} + +LDAP *init_ldap_connection(unsigned logopt, const char *uri, struct lookup_context *ctxt) +{ + LDAP *ldap = NULL; + struct timeval timeout = { ctxt->timeout, 0 }; + struct timeval net_timeout = { ctxt->network_timeout, 0 }; + int rv; + + ctxt->version = 3; + + /* Initialize the LDAP context. */ + rv = ldap_initialize(&ldap, uri); + if (rv != LDAP_OPT_SUCCESS) { + info(logopt, MODPREFIX + "couldn't initialize LDAP connection to %s", + uri ? uri : "default"); + return NULL; + } + + /* Use LDAPv3 */ + rv = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ctxt->version); + if (rv != LDAP_OPT_SUCCESS) { + /* fall back to LDAPv2 */ + ldap_unbind_ext(ldap, NULL, NULL); + rv = ldap_initialize(&ldap, uri); + if (rv != LDAP_OPT_SUCCESS) { + crit(logopt, MODPREFIX "couldn't initialize LDAP"); + return NULL; + } + ctxt->version = 2; + } + + + if (ctxt->timeout != -1) { + /* Set synchronous call timeout */ + rv = ldap_set_option(ldap, LDAP_OPT_TIMEOUT, &timeout); + if (rv != LDAP_OPT_SUCCESS) + info(logopt, MODPREFIX + "failed to set synchronous call timeout to %d", + timeout.tv_sec); + } + + /* Sane network timeout */ + rv = ldap_set_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &net_timeout); + if (rv != LDAP_OPT_SUCCESS) + info(logopt, MODPREFIX "failed to set connection timeout to %d", + net_timeout.tv_sec); + + if (ctxt->use_tls) { + if (ctxt->version == 2) { + if (ctxt->tls_required) { + error(logopt, MODPREFIX + "TLS required but connection is version 2"); + ldap_unbind_ext(ldap, NULL, NULL); + return NULL; + } + return ldap; + } + + rv = ldap_start_tls_s(ldap, NULL, NULL); + if (rv != LDAP_SUCCESS) { + ldap_unbind_ext(ldap, NULL, NULL); + if (ctxt->tls_required) { + error(logopt, MODPREFIX + "TLS required but START_TLS failed: %s", + ldap_err2string(rv)); + return NULL; + } + ctxt->use_tls = LDAP_TLS_DONT_USE; + ldap = init_ldap_connection(logopt, uri, ctxt); + if (ldap) + ctxt->use_tls = LDAP_TLS_INIT; + return ldap; + } + ctxt->use_tls = LDAP_TLS_RELEASE; + } + + return ldap; +} + +static int get_query_dn(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt, const char *class, const char *key) +{ + char buf[MAX_ERR_BUF]; + char *query, *dn, *qdn = NULL; + LDAPMessage *result = NULL, *e; + char *attrs[2]; + struct berval **value; + int scope; + int rv, l; + + attrs[0] = (char *) key; + attrs[1] = NULL; + + if (!ctxt->mapname && !ctxt->base) { + error(logopt, MODPREFIX "no master map to lookup"); + return 0; + } + + /* Build a query string. */ + l = strlen("(objectclass=)") + strlen(class) + 1; + if (ctxt->mapname) + l += strlen(key) + strlen(ctxt->mapname) + strlen("(&(=))"); + + query = malloc(l); + if (query == NULL) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + crit(logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + /* + * If we have a master mapname construct a query using it + * otherwise assume the base dn will catch it. + */ + if (ctxt->mapname) { + if (sprintf(query, "(&(objectclass=%s)(%s=%.*s))", class, + key, (int) strlen(ctxt->mapname), ctxt->mapname) >= l) { + debug(logopt, + MODPREFIX "error forming query string"); + free(query); + return 0; + } + scope = LDAP_SCOPE_SUBTREE; + } else { + if (sprintf(query, "(objectclass=%s)", class) >= l) { + debug(logopt, + MODPREFIX "error forming query string"); + free(query); + return 0; + } + scope = LDAP_SCOPE_SUBTREE; + } + + dn = NULL; + if (!ctxt->sdns) { + rv = ldap_search_s(ldap, ctxt->base, + scope, query, attrs, 0, &result); + if ((rv != LDAP_SUCCESS) || !result) { + error(logopt, + MODPREFIX "query failed for %s: %s", + query, ldap_err2string(rv)); + if (result) + ldap_msgfree(result); + free(query); + return 0; + } + + e = ldap_first_entry(ldap, result); + if (e && (value = ldap_get_values_len(ldap, e, key))) { + ldap_value_free_len(value); + dn = ldap_get_dn(ldap, e); + debug(logopt, MODPREFIX "found query dn %s", dn); + } else { + debug(logopt, + MODPREFIX "query succeeded, no matches for %s", + query); + ldap_msgfree(result); + free(query); + return 0; + } + } else { + struct ldap_searchdn *this = ctxt->sdns; + + debug(logopt, MODPREFIX "check search base list"); + + result = NULL; + while (this) { + rv = ldap_search_s(ldap, this->basedn, + scope, query, attrs, 0, &result); + if ((rv == LDAP_SUCCESS) && result) { + debug(logopt, MODPREFIX + "found search base under %s", + this->basedn); + + e = ldap_first_entry(ldap, result); + if (e && (value = ldap_get_values_len(ldap, e, key))) { + ldap_value_free_len(value); + dn = ldap_get_dn(ldap, e); + debug(logopt, MODPREFIX "found query dn %s", dn); + break; + } else { + debug(logopt, + MODPREFIX "query succeeded, no matches for %s", + query); + ldap_msgfree(result); + result = NULL; + } + } else { + error(logopt, + MODPREFIX "query failed for search dn %s: %s", + this->basedn, ldap_err2string(rv)); + if (result) { + ldap_msgfree(result); + result = NULL; + } + } + + this = this->next; + } + + if (!result) { + error(logopt, + MODPREFIX "failed to find query dn under search base dns"); + free(query); + return 0; + } + } + + free(query); + if (dn) { + qdn = strdup(dn); + ldap_memfree(dn); + } + ldap_msgfree(result); + if (!qdn) + return 0; + + uris_mutex_lock(ctxt); + if (ctxt->qdn) + free(ctxt->qdn); + ctxt->qdn = qdn; + uris_mutex_unlock(ctxt); + + return 1; +} + +static struct ldap_schema *alloc_common_schema(struct ldap_schema *s) +{ + struct ldap_schema *schema; + char *mc, *ma, *ec, *ea, *va; + + mc = strdup(s->map_class); + if (!mc) + return NULL; + + ma = strdup(s->map_attr); + if (!ma) { + free(mc); + return NULL; + } + + ec = strdup(s->entry_class); + if (!ec) { + free(mc); + free(ma); + return NULL; + } + + ea = strdup(s->entry_attr); + if (!ea) { + free(mc); + free(ma); + free(ec); + return NULL; + } + + va = strdup(s->value_attr); + if (!va) { + free(mc); + free(ma); + free(ec); + free(ea); + return NULL; + } + + schema = malloc(sizeof(struct ldap_schema)); + if (!schema) { + free(mc); + free(ma); + free(ec); + free(ea); + free(va); + return NULL; + } + + schema->map_class = mc; + schema->map_attr = ma; + schema->entry_class = ec; + schema->entry_attr = ea; + schema->value_attr = va; + + return schema; +} + +static int find_query_dn(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt) +{ + struct ldap_schema *schema; + unsigned int i; + + if (ctxt->schema) + return 0; + + if (ctxt->format & MAP_FLAG_FORMAT_AMD) { + schema = alloc_common_schema(&amd_schema); + if (!schema) { + error(logopt, MODPREFIX "failed to allocate schema"); + return 0; + } + ctxt->schema = schema; + return 1; + } + + for (i = 0; i < common_schema_count; i++) { + const char *class = common_schema[i].map_class; + const char *key = common_schema[i].map_attr; + if (get_query_dn(logopt, ldap, ctxt, class, key)) { + schema = alloc_common_schema(&common_schema[i]); + if (!schema) { + error(logopt, MODPREFIX "failed to allocate schema"); + return 0; + } + ctxt->schema = schema; + return 1; + } + } + + return 0; +} + +static int do_bind(unsigned logopt, struct ldap_conn *conn, + const char *uri, struct lookup_context *ctxt) +{ + char *host = NULL, *nhost; + int rv; + +#ifdef WITH_SASL + debug(logopt, MODPREFIX "auth_required: %d, sasl_mech %s", + ctxt->auth_required, ctxt->sasl_mech); + + if (ctxt->auth_required & LDAP_NEED_AUTH) { + rv = autofs_sasl_bind(logopt, conn, ctxt); + debug(logopt, MODPREFIX "autofs_sasl_bind returned %d", rv); + } else { + rv = bind_ldap_simple(logopt, conn->ldap, uri, ctxt); + debug(logopt, MODPREFIX "ldap simple bind returned %d", rv); + } +#else + rv = bind_ldap_simple(logopt, conn->ldap, uri, ctxt); + debug(logopt, MODPREFIX "ldap simple bind returned %d", rv); +#endif + + if (rv != 0) + return 0; + + rv = ldap_get_option(conn->ldap, LDAP_OPT_HOST_NAME, &host); + if (rv != LDAP_SUCCESS || !host) { + debug(logopt, "failed to get hostname for connection"); + return 0; + } + + nhost = strdup(host); + if (!nhost) { + debug(logopt, "failed to alloc context for hostname"); + return 0; + } + ldap_memfree(host); + + uris_mutex_lock(ctxt); + if (!ctxt->cur_host) { + ctxt->cur_host = nhost; + if (!(ctxt->format & MAP_FLAG_FORMAT_AMD)) { + /* Check if schema defined in conf first time only */ + ctxt->schema = defaults_get_schema(); + } + } else { + /* If connection host has changed update */ + if (!strcmp(ctxt->cur_host, nhost)) + free(nhost); + else { + free(ctxt->cur_host); + ctxt->cur_host = nhost; + } + } + uris_mutex_unlock(ctxt); + + return 1; +} + +static int do_connect(unsigned logopt, struct ldap_conn *conn, + const char *uri, struct lookup_context *ctxt) +{ + char *cur_host = NULL; + int ret = NSS_STATUS_SUCCESS; + +#ifdef WITH_SASL + if (ctxt->extern_cert && ctxt->extern_key) { + set_env(logopt, ENV_LDAPTLS_CERT, ctxt->extern_cert); + set_env(logopt, ENV_LDAPTLS_KEY, ctxt->extern_key); + } +#endif + + conn->ldap = init_ldap_connection(logopt, uri, ctxt); + if (!conn->ldap) { + ret = NSS_STATUS_UNAVAIL; + goto out; + } + + uris_mutex_lock(ctxt); + if (ctxt->cur_host) + cur_host = ctxt->cur_host; + uris_mutex_unlock(ctxt); + + if (!do_bind(logopt, conn, uri, ctxt)) { + __unbind_ldap_connection(logopt, conn, ctxt); + ret = NSS_STATUS_UNAVAIL; + goto out; + } + + /* If the lookup schema and the query dn are set and the + * ldap host hasn't changed return. + */ + uris_mutex_lock(ctxt); + if (ctxt->schema && ctxt->qdn && (cur_host == ctxt->cur_host)) { + uris_mutex_unlock(ctxt); + goto out; + } + uris_mutex_unlock(ctxt); + + /* + * If the schema isn't defined in the configuration then check for + * presence of a map dn with a the common schema. Then calculate the + * base dn for searches. + */ + if (!ctxt->schema) { + if (!find_query_dn(logopt, conn->ldap, ctxt)) { + __unbind_ldap_connection(logopt, conn, ctxt); + ret = NSS_STATUS_NOTFOUND; + warn(logopt, + MODPREFIX "failed to find valid query dn"); + goto out; + } + } else if (!(ctxt->format & MAP_FLAG_FORMAT_AMD)) { + const char *class = ctxt->schema->map_class; + const char *key = ctxt->schema->map_attr; + if (!get_query_dn(logopt, conn->ldap, ctxt, class, key)) { + __unbind_ldap_connection(logopt, conn, ctxt); + ret = NSS_STATUS_NOTFOUND; + error(logopt, MODPREFIX "failed to get query dn"); + goto out; + } + } + +out: + return ret; +} + +static unsigned long get_amd_timestamp(struct lookup_context *ctxt) +{ + struct ldap_conn conn; + LDAP *ldap; + LDAPMessage *result = NULL, *e; + char *query; + int scope = LDAP_SCOPE_SUBTREE; + char *map, *class, *value; + char *attrs[2]; + struct berval **bvValues; + unsigned long timestamp = 0; + int rv, l, ql; + + memset(&conn, 0, sizeof(struct ldap_conn)); + rv = do_connect(LOGOPT_ANY, &conn, ctxt->server, ctxt); + if (rv != NSS_STATUS_SUCCESS) + return 0; + ldap = conn.ldap; + + map = amd_timestamp.map_attr; + class = amd_timestamp.entry_class; + value = amd_timestamp.value_attr; + + attrs[0] = value; + attrs[1] = NULL; + + /* Build a query string. */ + l = strlen(class) + + strlen(map) + strlen(ctxt->mapname) + 21; + + query = malloc(l); + if (query == NULL) { + char buf[MAX_ERR_BUF]; + char *estr = strerror_r(errno, buf, sizeof(buf)); + crit(LOGOPT_ANY, MODPREFIX "malloc: %s", estr); + return 0; + } + + /* + * Look for an entry in class under ctxt-base + * whose entry is equal to qKey. + */ + ql = sprintf(query, "(&(objectclass=%s)(%s=%s))", + class, map, ctxt->mapname); + if (ql >= l) { + error(LOGOPT_ANY, + MODPREFIX "error forming query string"); + free(query); + return 0; + } + + rv = ldap_search_s(ldap, ctxt->base, scope, query, attrs, 0, &result); + if ((rv != LDAP_SUCCESS) || !result) { + crit(LOGOPT_ANY, MODPREFIX "timestamp query failed %s", query); + unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); + if (result) + ldap_msgfree(result); + free(query); + return 0; + } + + e = ldap_first_entry(ldap, result); + if (!e) { + debug(LOGOPT_ANY, + MODPREFIX "got answer, but no entry for timestamp"); + ldap_msgfree(result); + unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); + free(query); + return CHE_MISSING; + } + + while (e) { + char *v_val; + char *endptr; + + bvValues = ldap_get_values_len(ldap, e, value); + if (!bvValues || !*bvValues) { + debug(LOGOPT_ANY, + MODPREFIX "no value found in timestamp"); + goto next; + } + + /* There should be one value for a timestamp */ + v_val = bvValues[0]->bv_val; + + timestamp = strtol(v_val, &endptr, 0); + if ((errno == ERANGE && + (timestamp == LONG_MAX || timestamp == LONG_MIN)) || + (errno != 0 && timestamp == 0)) { + debug(LOGOPT_ANY, + MODPREFIX "invalid value in timestamp"); + free(query); + return 0; + } + + if (endptr == v_val) { + debug(LOGOPT_ANY, + MODPREFIX "no digits found in timestamp"); + free(query); + return 0; + } + + if (*endptr != '\0') { + warn(LOGOPT_ANY, MODPREFIX + "characters found after number: %s", endptr); + warn(LOGOPT_ANY, + MODPREFIX "timestamp may be invalid"); + } + + ldap_value_free_len(bvValues); + break; +next: + ldap_value_free_len(bvValues); + e = ldap_next_entry(ldap, e); + } + + ldap_msgfree(result); + unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); + free(query); + + return timestamp; +} + +static int connect_to_server(unsigned logopt, struct ldap_conn *conn, + const char *uri, struct lookup_context *ctxt) +{ + int ret; + + ret = do_connect(logopt, conn, uri, ctxt); + if (ret != NSS_STATUS_SUCCESS) { + warn(logopt, + MODPREFIX "couldn't connect to server %s", + uri ? uri : "default"); + } + + return ret; +} + +static int find_dc_server(unsigned logopt, struct ldap_conn *conn, + const char *uri, struct lookup_context *ctxt) +{ + char *str, *tok, *ptr = NULL; + int ret = NSS_STATUS_UNAVAIL; + + str = strdup(uri); + if (!str) + return ret; + + tok = strtok_r(str, " ", &ptr); + while (tok) { + const char *this = (const char *) tok; + int rv; + + debug(logopt, "trying server uri %s", this); + rv = connect_to_server(logopt, conn, this, ctxt); + if (rv == NSS_STATUS_SUCCESS) { + info(logopt, "connected to uri %s", this); + free(str); + return rv; + } + if (rv == NSS_STATUS_NOTFOUND) + ret = NSS_STATUS_NOTFOUND; + tok = strtok_r(NULL, " ", &ptr); + } + + free(str); + + return ret; +} + +static int find_server(unsigned logopt, + struct ldap_conn *conn, struct lookup_context *ctxt) +{ + struct ldap_uri *this = NULL; + struct list_head *p, *first; + struct dclist *dclist; + char *uri = NULL; + int ret = NSS_STATUS_UNAVAIL; + + uris_mutex_lock(ctxt); + dclist = ctxt->dclist; + if (!ctxt->uri) + first = ctxt->uris; + else + first = &ctxt->uri->list; + uris_mutex_unlock(ctxt); + + + /* Try each uri, save point in server list upon success */ + p = first->next; + while(p != first) { + int rv; + + /* Skip list head */ + if (p == ctxt->uris) { + p = p->next; + continue; + } + this = list_entry(p, struct ldap_uri, list); + if (!strstr(this->uri, ":///")) { + uri = strdup(this->uri); + debug(logopt, "trying server uri %s", uri); + rv = connect_to_server(logopt, conn, uri, ctxt); + if (rv == NSS_STATUS_SUCCESS) { + ret = NSS_STATUS_SUCCESS; + info(logopt, "connected to uri %s", uri); + free(uri); + break; + } + if (rv == NSS_STATUS_NOTFOUND) + ret = NSS_STATUS_NOTFOUND; + } else { + if (dclist) + uri = strdup(dclist->uri); + else { + struct dclist *tmp; + tmp = get_dc_list(logopt, this->uri); + if (!tmp) { + p = p->next; + continue; + } + dclist = tmp; + uri = strdup(dclist->uri); + } + rv = find_dc_server(logopt, conn, uri, ctxt); + if (rv == NSS_STATUS_SUCCESS) { + ret = NSS_STATUS_SUCCESS; + free(uri); + break; + } + if (rv == NSS_STATUS_NOTFOUND) + ret = NSS_STATUS_NOTFOUND; + } + free(uri); + uri = NULL; + if (dclist) { + free_dclist(dclist); + dclist = NULL; + } + p = p->next; + } + + uris_mutex_lock(ctxt); + if (conn->ldap) + ctxt->uri = this; + if (dclist) { + if (!ctxt->dclist) + ctxt->dclist = dclist; + else { + if (ctxt->dclist != dclist) { + free_dclist(ctxt->dclist); + ctxt->dclist = dclist; + } + } + } + uris_mutex_unlock(ctxt); + + return ret; +} + +static int do_reconnect(unsigned logopt, + struct ldap_conn *conn, struct lookup_context *ctxt) +{ + int ret = NSS_STATUS_UNAVAIL; + int dcrv = NSS_STATUS_SUCCESS; + int rv = NSS_STATUS_SUCCESS; + + ldapinit_mutex_lock(); + if (ctxt->server || !ctxt->uris) { + ret = do_connect(logopt, conn, ctxt->server, ctxt); +#ifdef WITH_SASL + /* Dispose of the sasl authentication connection and try again. */ + if (ctxt->auth_required & LDAP_NEED_AUTH && + ret != NSS_STATUS_SUCCESS && ret != NSS_STATUS_NOTFOUND) { + autofs_sasl_dispose(conn, ctxt); + ret = connect_to_server(logopt, conn, + ctxt->server, ctxt); + } +#endif + ldapinit_mutex_unlock(); + return ret; + } + + if (ctxt->dclist) { + dcrv = find_dc_server(logopt, conn, ctxt->dclist->uri, ctxt); + if (dcrv == NSS_STATUS_SUCCESS) { + ldapinit_mutex_unlock(); + return dcrv; + } + } + + uris_mutex_lock(ctxt); + if (ctxt->dclist) { + if (!conn->ldap || ctxt->dclist->expire < monotonic_time(NULL)) { + free_dclist(ctxt->dclist); + ctxt->dclist = NULL; + } + /* Make sure we don't skip the domain spec */ + ctxt->uri = NULL; + uris_mutex_unlock(ctxt); + goto find_server; + } + uris_mutex_unlock(ctxt); + + if (!ctxt->uri) + goto find_server; + + rv = do_connect(logopt, conn, ctxt->uri->uri, ctxt); +#ifdef WITH_SASL + /* + * Dispose of the sasl authentication connection and try the + * current server again before trying other servers in the list. + */ + if (ctxt->auth_required & LDAP_NEED_AUTH && + rv != NSS_STATUS_SUCCESS && rv != NSS_STATUS_NOTFOUND) { + autofs_sasl_dispose(conn, ctxt); + rv = connect_to_server(logopt, conn, ctxt->uri->uri, ctxt); + } +#endif + if (rv == NSS_STATUS_SUCCESS) { + ldapinit_mutex_unlock(); + return rv; + } + + /* Failed to connect, try to find a new server */ + +find_server: +#ifdef WITH_SASL + autofs_sasl_dispose(conn, ctxt); +#endif + + /* Current server failed, try the rest or dc connection */ + ret = find_server(logopt, conn, ctxt); + if (ret != NSS_STATUS_SUCCESS) { + if (ret == NSS_STATUS_NOTFOUND || + dcrv == NSS_STATUS_NOTFOUND || + rv == NSS_STATUS_NOTFOUND) + ret = NSS_STATUS_NOTFOUND; + error(logopt, MODPREFIX "failed to find available server"); + } + ldapinit_mutex_unlock(); + + return ret; +} + +int get_property(unsigned logopt, xmlNodePtr node, const char *prop, char **value) +{ + xmlChar *ret; + xmlChar *property = (xmlChar *) prop; + + if (!(ret = xmlGetProp(node, property))) { + *value = NULL; + return 0; + } + + if (!(*value = strdup((char *) ret))) { + logerr(MODPREFIX "strdup failed with %d", errno); + xmlFree(ret); + return -1; + } + + xmlFree(ret); + return 0; +} + +/* + * For plain text, login and digest-md5 authentication types, we need + * user and password credentials. + */ +int authtype_requires_creds(const char *authtype) +{ +#ifdef WITH_SASL + if (!strncmp(authtype, "PLAIN", strlen("PLAIN")) || + !strncmp(authtype, "DIGEST-MD5", strlen("DIGEST-MD5")) || + !strncmp(authtype, "LOGIN", strlen("LOGIN"))) + return 1; +#endif + return 0; +} + +/* + * Returns: + * -1 -- The permission on the file are not correct or + * the xml document was mal-formed + * 0 -- The file was non-existent + * the file was empty + * the file contained valid data, which was filled into + * ctxt->sasl_mech, ctxt->user, and ctxt->secret + * + * The idea is that a -1 return value should abort the program. A 0 + * return value requires more checking. If ctxt->authtype is filled in, + * then no further action is necessary. If it is not, the caller is free + * to then use another method to determine how to connect to the server. + */ +int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt) +{ + int ret = 0, fallback = 0; + unsigned int auth_required = LDAP_AUTH_NOTREQUIRED; + unsigned int tls_required = 0, use_tls = 0; + struct stat st; + xmlDocPtr doc = NULL; + xmlNodePtr root = NULL; + char *authrequired, *auth_conf, *authtype; + char *user = NULL, *secret = NULL; + char *extern_cert = NULL, *extern_key = NULL; + char *client_princ = NULL, *client_cc = NULL; + char *usetls, *tlsrequired; + + authtype = user = secret = NULL; + + auth_conf = (char *) defaults_get_auth_conf_file(); + if (!auth_conf) { + error(logopt, + MODPREFIX "failed to get auth config file name."); + return 0; + } + + /* + * Here we check that the config file exists, and that we have + * permission to read it. The XML library does not specify why a + * parse happens to fail, so we have to do all of this checking + * beforehand. + */ + memset(&st, 0, sizeof(st)); + if (stat(auth_conf, &st) == -1 || st.st_size == 0) { + /* Auth config doesn't exist so disable TLS and auth */ + if (errno == ENOENT) { + ctxt->auth_conf = auth_conf; + ctxt->use_tls = LDAP_TLS_DONT_USE; + ctxt->tls_required = LDAP_TLS_DONT_USE; + ctxt->auth_required = LDAP_AUTH_NOTREQUIRED; + ctxt->sasl_mech = NULL; + ctxt->user = NULL; + ctxt->secret = NULL; + ctxt->client_princ = NULL; + return 0; + } + error(logopt, + MODPREFIX "stat(2) failed with error %s.", + strerror(errno)); + return 0; + } + + if (!S_ISREG(st.st_mode) || + st.st_uid != 0 || st.st_gid != 0 || + (st.st_mode & 0x01ff) != 0600) { + error(logopt, MODPREFIX + "Configuration file %s exists, but is not usable. " + "Please make sure that it is owned by root, group " + "is root, and the mode is 0600.", + auth_conf); + return -1; + } + + doc = xmlParseFile(auth_conf); + if (!doc) { + error(logopt, MODPREFIX + "xmlParseFile failed for %s.", auth_conf); + goto out; + } + + root = xmlDocGetRootElement(doc); + if (!root) { + debug(logopt, MODPREFIX + "empty xml document (%s).", auth_conf); + fallback = 1; + goto out; + } + + if (xmlStrcmp(root->name, (const xmlChar *)"autofs_ldap_sasl_conf")) { + error(logopt, MODPREFIX + "The root node of the XML document %s is not " + "autofs_ldap_sasl_conf.", auth_conf); + goto out; + } + + ret = get_property(logopt, root, "usetls", &usetls); + if (ret != 0) { + error(logopt, + MODPREFIX + "Failed read the usetls property from " + "the configuration file %s.", auth_conf); + goto out; + } + + if (!usetls || ctxt->port == LDAPS_PORT) + use_tls = LDAP_TLS_DONT_USE; + else { + if (!strcasecmp(usetls, "yes")) + use_tls = LDAP_TLS_INIT; + else if (!strcasecmp(usetls, "no")) + use_tls = LDAP_TLS_DONT_USE; + else { + error(logopt, + MODPREFIX + "The usetls property must have value " + "\"yes\" or \"no\"."); + ret = -1; + goto out; + } + free(usetls); + } + + ret = get_property(logopt, root, "tlsrequired", &tlsrequired); + if (ret != 0) { + error(logopt, + MODPREFIX + "Failed read the tlsrequired property from " + "the configuration file %s.", auth_conf); + goto out; + } + + if (!tlsrequired) + tls_required = LDAP_TLS_DONT_USE; + else { + if (!strcasecmp(tlsrequired, "yes")) + tls_required = LDAP_TLS_REQUIRED; + else if (!strcasecmp(tlsrequired, "no")) + tls_required = LDAP_TLS_DONT_USE; + else { + error(logopt, + MODPREFIX + "The tlsrequired property must have value " + "\"yes\" or \"no\"."); + ret = -1; + goto out; + } + free(tlsrequired); + } + + ret = get_property(logopt, root, "authrequired", &authrequired); + if (ret != 0) { + error(logopt, + MODPREFIX + "Failed read the authrequired property from " + "the configuration file %s.", auth_conf); + goto out; + } + + if (!authrequired) + auth_required = LDAP_AUTH_NOTREQUIRED; + else { + if (!strcasecmp(authrequired, "yes")) + auth_required = LDAP_AUTH_REQUIRED; + else if (!strcasecmp(authrequired, "no")) + auth_required = LDAP_AUTH_NOTREQUIRED; + else if (!strcasecmp(authrequired, "autodetect")) + auth_required = LDAP_AUTH_AUTODETECT; + else if (!strcasecmp(authrequired, "simple")) + auth_required = LDAP_AUTH_USESIMPLE; + else { + error(logopt, + MODPREFIX + "The authrequired property must have value " + "\"yes\", \"no\", \"autodetect\", or \"simple\"."); + ret = -1; + goto out; + } + free(authrequired); + } + + ret = get_property(logopt, root, "authtype", &authtype); + if (ret != 0) { + error(logopt, + MODPREFIX + "Failed read the authtype property from the " + "configuration file %s.", auth_conf); + goto out; + } + + if (auth_required == LDAP_AUTH_USESIMPLE || + (authtype && authtype_requires_creds(authtype))) { + char *s1 = NULL, *s2 = NULL; + ret = get_property(logopt, root, "user", &user); + ret |= get_property(logopt, root, "secret", &s1); + ret |= get_property(logopt, root, "encoded_secret", &s2); + if (ret != 0 || (!user || (!s1 && !s2))) { +auth_fail: + error(logopt, + MODPREFIX + "%s authentication type requires a username " + "and a secret. Please fix your configuration " + "in %s.", authtype, auth_conf); + free(authtype); + if (user) + free(user); + if (s1) + free(s1); + if (s2) + free(s2); + + ret = -1; + goto out; + } + if (!s2) + secret = s1; + else { + char dec_buf[120]; + int dec_len = base64_decode(s2, dec_buf, 119); + if (dec_len <= 0) + goto auth_fail; + secret = strdup(dec_buf); + if (!secret) + goto auth_fail; + if (s1) + free(s1); + if (s2) + free(s2); + } + } else if (auth_required == LDAP_AUTH_REQUIRED && + (authtype && !strncmp(authtype, "EXTERNAL", 8))) { +#ifdef WITH_SASL + ret = get_property(logopt, root, "external_cert", &extern_cert); + ret |= get_property(logopt, root, "external_key", &extern_key); + /* + * For EXTERNAL auth to function we need a client certificate + * and and certificate key. The ca certificate used to verify + * the server certificate must also be set correctly in the + * global configuration as the connection must be encrypted + * and the server and client certificates must have been + * verified for the EXTERNAL method to be offerred by the + * server. If the cert and key have not been set in the autofs + * configuration they must be set in the ldap rc file. + */ + if (ret != 0 || !extern_cert || !extern_key) { + if (extern_cert) + free(extern_cert); + if (extern_key) + free(extern_key); + } +#endif + } + + /* + * We allow the admin to specify the principal to use for the + * client. The default is "autofsclient/hostname@REALM". + */ + (void)get_property(logopt, root, "clientprinc", &client_princ); + (void)get_property(logopt, root, "credentialcache", &client_cc); + + ctxt->auth_conf = auth_conf; + ctxt->use_tls = use_tls; + ctxt->tls_required = tls_required; + ctxt->auth_required = auth_required; + ctxt->sasl_mech = authtype; + if (!authtype && (auth_required & LDAP_AUTH_REQUIRED)) + ctxt->auth_required |= LDAP_AUTH_AUTODETECT; + ctxt->user = user; + ctxt->secret = secret; + ctxt->client_princ = client_princ; + ctxt->client_cc = client_cc; +#ifdef WITH_SASL + ctxt->extern_cert = extern_cert; + ctxt->extern_key = extern_key; +#endif + + debug(logopt, MODPREFIX + "ldap authentication configured with the following options:"); + debug(logopt, MODPREFIX + "use_tls: %u, " + "tls_required: %u, " + "auth_required: %u, " + "sasl_mech: %s", + use_tls, tls_required, auth_required, authtype); + if (authtype && !strncmp(authtype, "EXTERNAL", 8)) { + debug(logopt, MODPREFIX "external cert: %s", + extern_cert ? extern_cert : "ldap default"); + debug(logopt, MODPREFIX "external key: %s ", + extern_key ? extern_key : "ldap default"); + } else { + debug(logopt, MODPREFIX + "user: %s, " + "secret: %s, " + "client principal: %s " + "credential cache: %s", + user, secret ? "specified" : "unspecified", + client_princ, client_cc); + } +out: + xmlFreeDoc(doc); + + if (fallback) + return 0; + + return ret; +} + +/* + * Take an input string as specified in the master map, and break it + * down into a server name and basedn. + */ +static int parse_server_string(unsigned logopt, const char *url, struct lookup_context *ctxt) +{ + char buf[MAX_ERR_BUF], *tmp = NULL, proto[9]; + const char *ptr, *name; + int l, al_len; + + memset(proto, 0, 9); + ptr = url; + + debug(logopt, MODPREFIX + "Attempting to parse LDAP information from string \"%s\".", ptr); + + ctxt->port = LDAP_PORT; + if (!strncmp(ptr, "ldap:", 5) || !strncmp(ptr, "ldaps:", 6)) { + if (*(ptr + 4) == 's') { + ctxt->port = LDAPS_PORT; + memcpy(proto, ptr, 6); + strcat(proto, "//"); + ptr += 6; + } else { + memcpy(proto, ptr, 5); + strcat(proto, "//"); + ptr += 5; + } + } + + if (!strncmp(ptr, "//", 2)) { + const char *s = ptr + 2; + const char *q = NULL; + + /* Isolate the server(s). */ + if ((q = strchr(s, '/')) || (q = strchr(s, '\0'))) { + l = q - s; + if (*proto) { + al_len = l + strlen(proto) + 2; + tmp = malloc(al_len); + } else { + al_len = l + 1; + tmp = malloc(al_len); + } + if (!tmp) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return 0; + } + ctxt->server = tmp; + memset(ctxt->server, 0, al_len); + if (*proto) { + strcpy(ctxt->server, proto); + memcpy(ctxt->server + strlen(proto), s, l); + strcat(ctxt->server, "/"); + } else + memcpy(ctxt->server, s, l); + ptr = q + 1; + } else { + crit(logopt, + MODPREFIX "invalid LDAP map syntax %s", ptr); + return 0; +/* TODO: why did I put this here, the parser shouldn't let this by + l = strlen(ptr); + tmp = malloc(l + 1); + if (!tmp) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + crit(logopt, MODPREFIX "malloc: %s", estr); + return 0; + } + ctxt->server = tmp; + memset(ctxt->server, 0, l + 1); + memcpy(ctxt->server, s, l); +*/ + } + } else if (strchr(ptr, ':') != NULL || *ptr == '[') { + const char *q = NULL; + + /* Isolate the server. Include the port spec */ + if (*ptr != '[') { + q = strchr(ptr, ':'); + if (!q) { + crit(logopt, MODPREFIX + "LDAP server name not found in %s", ptr); + return 0; + } + } else { + q = ++ptr; + while (*q == ':' || isxdigit(*q)) + q++; + if (*q != ']') { + crit(logopt, MODPREFIX + "invalid LDAP map syntax %s", ptr); + return 0; + } + q++; + if (*q == ':') + q++; + } + + if (isdigit(*q)) + while (isdigit(*q)) + q++; + + if (*q != ':') { + crit(logopt, + MODPREFIX "invalid LDAP map syntax %s", ptr); + return 0; + } + + l = q - ptr; + if (*proto) { + al_len = l + strlen(proto) + 2; + tmp = malloc(al_len); + } else { + al_len = l + 1; + tmp = malloc(al_len); + } + /* Isolate the server's name. */ + if (!tmp) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return 0; + } + ctxt->server = tmp; + memset(ctxt->server, 0, al_len); + if (*proto) { + strcpy(ctxt->server, proto); + memcpy(ctxt->server + strlen(proto), ptr, l); + strcat(ctxt->server, "/"); + } else + memcpy(ctxt->server, ptr, l); + ptr += l + 1; + } + + if (!ptr || ctxt->format & MAP_FLAG_FORMAT_AMD) + goto done; + + /* + * For nss support we can have a map name with no + * type or dn info. If present a base dn must have + * at least an "=" and a "," to be at all functional. + * If a dn is given it must be fully specified or + * the later LDAP calls will fail. + */ + l = strlen(ptr); + if ((name = strchr(ptr, '='))) { + char *base; + + /* + * An '=' with no ',' means a mapname has been given so just + * grab it alone to keep it independent of schema otherwize + * we expect a full dn. + */ + if (!strchr(ptr, ',')) { + char *map = strdup(name + 1); + if (map) + ctxt->mapname = map; + else { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "strdup: %s", estr); + if (ctxt->server) + free(ctxt->server); + return 0; + } + + } else { + base = malloc(l + 1); + if (!base) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + if (ctxt->server) + free(ctxt->server); + return 0; + } + ctxt->base = base; + memset(ctxt->base, 0, l + 1); + memcpy(ctxt->base, ptr, l); + } + } else { + char *map = malloc(l + 1); + if (!map) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + if (ctxt->server) + free(ctxt->server); + return 0; + } + ctxt->mapname = map; + memset(ctxt->mapname, 0, l + 1); + memcpy(map, ptr, l); + } + + if (!ctxt->server && *proto) { + if (!strncmp(proto, "ldaps", 5)) { + info(logopt, MODPREFIX + "server must be given to force ldaps, connection " + "will use LDAP client configured protocol"); + } + } +done: + if (ctxt->mapname) + debug(logopt, MODPREFIX "mapname %s", ctxt->mapname); + else + debug(logopt, MODPREFIX "server \"%s\", base dn \"%s\"", + ctxt->server ? ctxt->server : "(default)", + ctxt->base); + + return 1; +} + +static void free_context(struct lookup_context *ctxt) +{ + int ret; + + if (ctxt->schema) { + free(ctxt->schema->map_class); + free(ctxt->schema->map_attr); + free(ctxt->schema->entry_class); + free(ctxt->schema->entry_attr); + free(ctxt->schema->value_attr); + free(ctxt->schema); + } + if (ctxt->auth_conf) + free(ctxt->auth_conf); + if (ctxt->sasl_mech) + free(ctxt->sasl_mech); + if (ctxt->user) + free(ctxt->user); + if (ctxt->secret) + free(ctxt->secret); + if (ctxt->client_princ) + free(ctxt->client_princ); + if (ctxt->client_cc) + free(ctxt->client_cc); + if (ctxt->mapname) + free(ctxt->mapname); + if (ctxt->qdn) + free(ctxt->qdn); + if (ctxt->server) + free(ctxt->server); + if (ctxt->cur_host) + free(ctxt->cur_host); + if (ctxt->base) + free(ctxt->base); + if (ctxt->uris) + defaults_free_uris(ctxt->uris); + ret = pthread_mutex_destroy(&ctxt->uris_mutex); + if (ret) + fatal(ret); + if (ctxt->sdns) + defaults_free_searchdns(ctxt->sdns); + if (ctxt->dclist) + free_dclist(ctxt->dclist); +#ifdef WITH_SASL + if (ctxt->extern_cert) + free(ctxt->extern_cert); + if (ctxt->extern_key) + free(ctxt->extern_key); +#endif + free(ctxt); + + return; +} + +static void validate_uris(struct list_head *list) +{ + struct list_head *next; + + next = list->next; + while (next != list) { + struct ldap_uri *this; + + this = list_entry(next, struct ldap_uri, list); + next = next->next; + + /* At least we get some basic validation */ + if (!ldap_is_ldap_url(this->uri)) { + list_del(&this->list); + free(this->uri); + free(this); + } + } + + return; +} + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + unsigned int is_amd_format; + int ret; + + ret = pthread_mutex_init(&ctxt->uris_mutex, NULL); + if (ret) { + error(LOGOPT_ANY, MODPREFIX "failed to init uris mutex"); + return 1; + } + + /* If a map type isn't explicitly given, parse it like sun entries. */ + if (mapfmt == NULL) + mapfmt = MAPFMT_DEFAULT; + + is_amd_format = 0; + if (!strcmp(mapfmt, "amd")) { + is_amd_format = 1; + ctxt->format = MAP_FLAG_FORMAT_AMD; + ctxt->check_defaults = 1; + } + + ctxt->timeout = defaults_get_ldap_timeout(); + ctxt->network_timeout = defaults_get_ldap_network_timeout(); + + if (!is_amd_format) { + /* + * Parse out the server name and base dn, and fill them + * into the proper places in the lookup context structure. + */ + if (!parse_server_string(LOGOPT_NONE, argv[0], ctxt)) { + error(LOGOPT_ANY, MODPREFIX "cannot parse server string"); + return 1; + } + + if (!ctxt->base) + ctxt->sdns = defaults_get_searchdns(); + + if (!ctxt->server) { + struct list_head *uris = defaults_get_uris(); + if (uris) { + validate_uris(uris); + if (!list_empty(uris)) + ctxt->uris = uris; + else { + error(LOGOPT_ANY, MODPREFIX + "no valid uris found in config list" + ", using default system config"); + free(uris); + } + } + } + } else { + char *tmp = conf_amd_get_ldap_base(); + if (!tmp) { + error(LOGOPT_ANY, MODPREFIX "failed to get base dn"); + return 1; + } + ctxt->base = tmp; + + tmp = conf_amd_get_ldap_hostports(); + if (!tmp) { + error(LOGOPT_ANY, + MODPREFIX "failed to get ldap_hostports"); + return 1; + } + + /* + * Parse out the server name and port, and save them in + * the proper places in the lookup context structure. + */ + if (!parse_server_string(LOGOPT_NONE, tmp, ctxt)) { + error(LOGOPT_ANY, MODPREFIX "cannot parse server string"); + free(tmp); + return 1; + } + free(tmp); + + if (!ctxt->server) { + error(LOGOPT_ANY, MODPREFIX "ldap_hostports not valid"); + return 1; + } + + tmp = strdup(argv[0]); + if (!tmp) { + error(LOGOPT_ANY, MODPREFIX "failed to set mapname"); + return 1; + } + ctxt->mapname = tmp; + } + + /* + * First, check to see if a preferred authentication method was + * specified by the user. parse_ldap_config will return error + * if the permissions on the file were incorrect, or if the + * specified authentication type is not valid. + */ + ret = parse_ldap_config(LOGOPT_NONE, ctxt); + if (ret) { + error(LOGOPT_ANY, MODPREFIX "failed to parse ldap config"); + return 1; + } + +#ifdef WITH_SASL + /* Init the sasl callbacks */ + ldapinit_mutex_lock(); + if (!autofs_sasl_client_init(LOGOPT_NONE)) { + error(LOGOPT_ANY, "failed to init sasl client"); + ldapinit_mutex_unlock(); + return 1; + } + ldapinit_mutex_unlock(); +#endif + + if (is_amd_format) + ctxt->timestamp = get_amd_timestamp(ctxt); + + if (reinit) { + ret = reinit_parse(ctxt->parse, + mapfmt, MODPREFIX, argc - 1, argv + 1); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + /* Open the parser, if we can. */ + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parse) { + logerr(MODPREFIX "failed to open parse context"); + ret = 1; + } + } + + return ret; +} + +/* + * This initializes a context (persistent non-global data) for queries to + * this module. Return zero if we succeed. + */ +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + int ret; + + *context = NULL; + + /* If we can't build a context, bail. */ + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + ret = do_init(mapfmt, argc, argv, ctxt, 0); + if (ret) { + free_context(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + /* If we can't build a context, bail. */ + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free_context(new); + return 1; + } + + *context = new; + + free_context(ctxt); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + unsigned int timeout = master->default_timeout; + unsigned int logging = master->default_logging; + unsigned int logopt = master->logopt; + struct ldap_conn conn; + LDAP *ldap; + int rv, l, count; + char buf[MAX_ERR_BUF]; + char parse_buf[PARSE_MAX_BUF]; + char *query; + LDAPMessage *result = NULL, *e; + char *class, *info, *entry; + char **keyValue = NULL; + char **values = NULL; + char *attrs[3]; + int scope = LDAP_SCOPE_SUBTREE; + + /* Initialize the LDAP context. */ + memset(&conn, 0, sizeof(struct ldap_conn)); + rv = do_reconnect(logopt, &conn, ctxt); + if (rv) + return rv; + ldap = conn.ldap; + + class = ctxt->schema->entry_class; + entry = ctxt->schema->entry_attr; + info = ctxt->schema->value_attr; + + attrs[0] = entry; + attrs[1] = info; + attrs[2] = NULL; + + l = strlen("(objectclass=)") + strlen(class) + 1; + + query = malloc(l); + if (query == NULL) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + if (sprintf(query, "(objectclass=%s)", class) >= l) { + error(logopt, MODPREFIX "error forming query string"); + free(query); + return NSS_STATUS_UNAVAIL; + } + + /* Look around. */ + debug(logopt, + MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->qdn); + + rv = ldap_search_s(ldap, ctxt->qdn, scope, query, attrs, 0, &result); + + if ((rv != LDAP_SUCCESS) || !result) { + error(logopt, MODPREFIX "query failed for %s: %s", + query, ldap_err2string(rv)); + unbind_ldap_connection(logging, &conn, ctxt); + if (result) + ldap_msgfree(result); + free(query); + return NSS_STATUS_NOTFOUND; + } + + e = ldap_first_entry(ldap, result); + if (!e) { + debug(logopt, + MODPREFIX "query succeeded, no matches for %s", + query); + ldap_msgfree(result); + unbind_ldap_connection(logging, &conn, ctxt); + free(query); + return NSS_STATUS_NOTFOUND; + } else + debug(logopt, MODPREFIX "examining entries"); + + while (e) { + char *key = NULL; + int dec_len, i; + + keyValue = ldap_get_values(ldap, e, entry); + + if (!keyValue || !*keyValue) { + e = ldap_next_entry(ldap, e); + continue; + } + + /* + * By definition keys must be unique within + * each map entry + */ + count = ldap_count_values(keyValue); + if (strcasecmp(class, "nisObject")) { + if (count > 1) { + error(logopt, MODPREFIX + "key %s has duplicates - ignoring", + *keyValue); + goto next; + } + key = strdup(keyValue[0]); + if (!key) { + error(logopt, MODPREFIX + "failed to dup map key %s - ignoring", + *keyValue); + goto next; + } + } else if (count == 1) { + dec_len = decode_percent_hack(keyValue[0], &key); + if (dec_len < 0) { + error(logopt, MODPREFIX + "invalid map key %s - ignoring", + *keyValue); + goto next; + } + } else { + dec_len = decode_percent_hack(keyValue[0], &key); + if (dec_len < 0) { + error(logopt, MODPREFIX + "invalid map key %s - ignoring", + *keyValue); + goto next; + } + + for (i = 1; i < count; i++) { + char *k; + dec_len = decode_percent_hack(keyValue[i], &k); + if (dec_len < 0) { + error(logopt, MODPREFIX + "invalid map key %s - ignoring", + *keyValue); + goto next; + } + if (strcmp(key, k)) { + error(logopt, MODPREFIX + "key entry mismatch %s - ignoring", + *keyValue); + free(k); + goto next; + } + free(k); + } + } + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*key == '+') { + warn(logopt, + MODPREFIX + "ignoreing '+' map entry - not in file map"); + goto next; + } + + values = ldap_get_values(ldap, e, info); + if (!values || !*values) { + debug(logopt, + MODPREFIX "no %s defined for %s", info, query); + goto next; + } + + /* + * We require that there be only one value per key. + */ + count = ldap_count_values(values); + if (count > 1) { + error(logopt, + MODPREFIX + "one value per key allowed in master map"); + ldap_value_free(values); + goto next; + } + + if (snprintf(parse_buf, sizeof(parse_buf), "%s %s", + key, *values) >= sizeof(parse_buf)) { + error(logopt, MODPREFIX "map entry too long"); + ldap_value_free(values); + goto next; + } + ldap_value_free(values); + + master_parse_entry(parse_buf, timeout, logging, age); +next: + ldap_value_free(keyValue); + if (key) + free(key); + e = ldap_next_entry(ldap, e); + } + + /* Clean up. */ + ldap_msgfree(result); + unbind_ldap_connection(logopt, &conn, ctxt); + free(query); + + return NSS_STATUS_SUCCESS; +} + +static int get_percent_decoded_len(const char *name) +{ + int escapes = 0; + int escaped = 0; + const char *tmp = name; + int look_for_close = 0; + + while (*tmp) { + if (*tmp == '%') { + /* assume escapes aren't interpreted inside brackets */ + if (look_for_close) { + tmp++; + continue; + } + /* check for escaped % */ + if (escaped) { + tmp++; + escaped = 0; + continue; + } + escapes++; + tmp++; + if (*tmp == '[') { + escapes++; + tmp++; + look_for_close = 1; + } else + escaped = 1; + } else if (*tmp == ']' && look_for_close) { + escaped = 0; + escapes++; + tmp++; + look_for_close = 0; + } else { + tmp++; + escaped = 0; + } + } + + assert(strlen(name) > escapes); + return strlen(name) - escapes; +} + +/* + * Try to catch heap corruption if our logic happens to be incorrect. + */ +static void validate_string_len(const char *orig, char *start, + char *end, unsigned int len) +{ + debug(LOGOPT_NONE, MODPREFIX "string %s encoded as %s", orig, start); + /* make sure we didn't overflow the allocated space */ + if (end - start > len + 1) { + crit(LOGOPT_ANY, MODPREFIX "orig %s, len %d", orig, len); + crit(LOGOPT_ANY, MODPREFIX "en/decoded %s, len %d", start, + end - start); + } + assert(end-start <= len + 1); +} + +/* + * Deal with encode and decode of % hack. + * Return + * 0 => % hack not present. + * -1 => syntax error or alloc fail. + * 1 transofrmed value returned. + */ +/* + * Assumptions: %'s must be escaped by %'s. %'s are not used to escape + * anything else except capital letters (so you can't escape a closing + * bracket, for example). + */ +static int decode_percent_hack(const char *name, char **key) +{ + const char *tmp; + char *ptr, *new; + unsigned int len; + int escaped = 0, look_for_close = 0; + + if (!key) + return -1; + + *key = NULL; + + len = get_percent_decoded_len(name); + new = malloc(len + 1); + if (!new) + return -1; + + ptr = new; + tmp = name; + while (*tmp) { + if (*tmp == '%') { + if (escaped) { + *ptr++ = *tmp++; + if (!look_for_close) + escaped = 0; + continue; + } + tmp++; + if (*tmp == '[') { + tmp++; + look_for_close = 1; + escaped = 1; + } else + escaped = 1; + } else if (*tmp == ']' && look_for_close) { + tmp++; + look_for_close = 0; + } else { + escaped = 0; + *ptr++ = *tmp++; + } + } + *ptr = '\0'; + *key = new; + + validate_string_len(name, new, ptr, len); + return strlen(new); +} + +/* + * Calculate the length of a string replacing all capital letters with %letter. + * For example: + * Sale -> %Sale + * SALE -> %S%A%L%E + */ +static int get_encoded_len_escaping_every_cap(const char *name) +{ + const char *tmp; + unsigned int escapes = 0; /* number of % escape characters */ + + tmp = name; + while (*tmp) { + /* We'll need to escape percents */ + if (*tmp == '%' || isupper(*tmp)) + escapes++; + tmp++; + } + + return strlen(name) + escapes; +} + +/* + * Calculate the length of a string replacing sequences (1 or more) of capital + * letters with %[letters]. For example: + * FOO -> %[FOO] + * Work -> %[W]ork + * WorksForMe -> %[W]orks%[F]or%[M]e + * aaBBaa -> aa%[BB]aa + */ +static int get_encoded_len_escaping_sequences(const char *name) +{ + const char *tmp; + unsigned int escapes = 0; + + tmp = name; + while (*tmp) { + /* escape percents */ + if (*tmp == '%') + escapes++; + else if (isupper(*tmp)) { + /* start an escape block %[...] */ + escapes += 3; /* %[] */ + while (*tmp && isupper(*tmp)) + tmp++; + continue; + } + tmp++; + } + + return strlen(name) + escapes; +} + +static void encode_individual(const char *name, char *new, unsigned int len) +{ + const char *tmp; + char *ptr; + + ptr = new; + tmp = name; + while (*tmp) { + if (*tmp == '%' || isupper(*tmp)) + *ptr++ = '%'; + *ptr++ = *tmp++; + } + *ptr = '\0'; + validate_string_len(name, new, ptr, len); +} + +static void encode_sequence(const char *name, char *new, unsigned int len) +{ + const char *tmp; + char *ptr; + + ptr = new; + tmp = name; + while (*tmp) { + if (*tmp == '%') { + *ptr++ = '%'; + *ptr++ = *tmp++; + } else if (isupper(*tmp)) { + *ptr++ = '%'; + *ptr++ = '['; + *ptr++ = *tmp++; + + while (*tmp && isupper(*tmp)) { + *ptr++ = *tmp; + tmp++; + } + *ptr++ = ']'; + } else + *ptr++ = *tmp++; + } + *ptr = '\0'; + validate_string_len(name, new, ptr, len); +} + +/* + * use_class: 1 means encode string as %[CAPITALS], 0 means encode as + * %C%A%P%I%T%A%L%S + */ +static int encode_percent_hack(const char *name, char **key, unsigned int use_class) +{ + unsigned int len = 0; + + if (!key) + return -1; + + if (use_class) + len = get_encoded_len_escaping_sequences(name); + else + len = get_encoded_len_escaping_every_cap(name); + + /* If there is no escaping to be done, return 0 */ + if (len == strlen(name)) + return 0; + + *key = malloc(len + 1); + if (!*key) + return -1; + + if (use_class) + encode_sequence(name, *key, len); + else + encode_individual(name, *key, len); + + if (strlen(*key) != len) + crit(LOGOPT_ANY, MODPREFIX "encoded key length mismatch: key " + "%s len %d strlen %d", *key, len, strlen(*key)); + + return strlen(*key); +} + +static int do_paged_query(struct ldap_search_params *sp, struct lookup_context *ctxt) +{ + struct autofs_point *ap = sp->ap; + LDAPControl *pageControl=NULL, *controls[2] = { NULL, NULL }; + LDAPControl **returnedControls = NULL; + static char pagingCriticality = 'T'; + int rv, scope = LDAP_SCOPE_SUBTREE; + + if (sp->morePages == TRUE) + goto do_paged; + + rv = ldap_search_s(sp->ldap, sp->base, scope, sp->query, sp->attrs, 0, &sp->result); + if ((rv != LDAP_SUCCESS) || !sp->result) { + /* + * Check for Size Limit exceeded and force run through loop + * and requery using page control. + */ + if (rv == LDAP_SIZELIMIT_EXCEEDED || + rv == LDAP_ADMINLIMIT_EXCEEDED) + sp->morePages = TRUE; + else { + debug(ap->logopt, + MODPREFIX "query failed for %s: %s", + sp->query, ldap_err2string(rv)); + return rv; + } + } + return rv; + +do_paged: + /* we need to use page controls so requery LDAP */ + debug(ap->logopt, MODPREFIX "geting page of results"); + + rv = ldap_create_page_control(sp->ldap, sp->pageSize, sp->cookie, + pagingCriticality, &pageControl); + if (rv != LDAP_SUCCESS) { + warn(ap->logopt, MODPREFIX "failed to create page control"); + return rv; + } + + /* Insert the control into a list to be passed to the search. */ + controls[0] = pageControl; + + /* Search for entries in the directory using the parmeters. */ + rv = ldap_search_ext_s(sp->ldap, + sp->base, scope, sp->query, sp->attrs, + 0, controls, NULL, NULL, 0, &sp->result); + if ((rv != LDAP_SUCCESS) && (rv != LDAP_PARTIAL_RESULTS)) { + ldap_control_free(pageControl); + if (rv != LDAP_ADMINLIMIT_EXCEEDED) + debug(ap->logopt, + MODPREFIX "query failed for %s: %s", + sp->query, ldap_err2string(rv)); + return rv; + } + + /* Parse the results to retrieve the contols being returned. */ + rv = ldap_parse_result(sp->ldap, sp->result, + NULL, NULL, NULL, NULL, + &returnedControls, FALSE); + if (sp->cookie != NULL) { + ber_bvfree(sp->cookie); + sp->cookie = NULL; + } + + if (rv != LDAP_SUCCESS) { + debug(ap->logopt, + MODPREFIX "ldap_parse_result failed with %d", rv); + goto out_free; + } + + /* + * Parse the page control returned to get the cookie and + * determine whether there are more pages. + */ + rv = ldap_parse_page_control(sp->ldap, + returnedControls, &sp->totalCount, + &sp->cookie); + if (sp->cookie && sp->cookie->bv_val && + (strlen(sp->cookie->bv_val) || sp->cookie->bv_len)) + sp->morePages = TRUE; + else + sp->morePages = FALSE; + + /* Cleanup the controls used. */ + if (returnedControls) + ldap_controls_free(returnedControls); + +out_free: + ldap_control_free(pageControl); + return rv; +} + +static int do_get_entries(struct ldap_search_params *sp, struct map_source *source, struct lookup_context *ctxt) +{ + struct autofs_point *ap = sp->ap; + struct mapent_cache *mc = source->mc; + char buf[MAX_ERR_BUF]; + struct berval **bvKey; + struct berval **bvValues; + LDAPMessage *e; + char *class, *info, *entry; + int rv, ret; + int i, count; + + class = ctxt->schema->entry_class; + entry = ctxt->schema->entry_attr; + info = ctxt->schema->value_attr; + + e = ldap_first_entry(sp->ldap, sp->result); + if (!e) { + debug(ap->logopt, + MODPREFIX "query succeeded, no matches for %s", + sp->query); + ret = ldap_parse_result(sp->ldap, sp->result, + &rv, NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } else + debug(ap->logopt, MODPREFIX "examining entries"); + + while (e) { + char *mapent = NULL; + size_t mapent_len = 0; + char *k_val; + ber_len_t k_len; + char *s_key; + + bvKey = ldap_get_values_len(sp->ldap, e, entry); + if (!bvKey || !*bvKey) { + e = ldap_next_entry(sp->ldap, e); + if (!e) { + debug(ap->logopt, MODPREFIX + "failed to get next entry for query %s", + sp->query); + ret = ldap_parse_result(sp->ldap, + sp->result, &rv, + NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } + continue; + } + + /* + * By definition keys should be unique within each map entry, + * but as always there are exceptions. + */ + k_val = NULL; + k_len = 0; + + /* + * Keys should be unique so, in general, there shouldn't be + * more than one attribute value. We make an exception for + * wildcard entries as people may have values for '*' or + * '/' for compaibility reasons. We use the '/' as the + * wildcard in LDAP but allow '*' as well to allow for + * people using older schemas that allow '*' as a key + * value. Another case where there can be multiple key + * values is when people have used the "%" hack to specify + * case matching ctriteria in a case insensitive attribute. + */ + count = ldap_count_values_len(bvKey); + if (count > 1) { + unsigned int i; + + /* Check for the "/" and "*" and use as "/" if found */ + for (i = 0; i < count; i++) { + bvKey[i]->bv_val[bvKey[i]->bv_len] = '\0'; + + /* + * If multiple entries are present they could + * be the result of people using the "%" hack so + * ignore them. + */ + if (strchr(bvKey[i]->bv_val, '%')) + continue; + + /* check for wildcard */ + if (bvKey[i]->bv_len == 1 && + (*bvKey[i]->bv_val == '/' || + *bvKey[i]->bv_val == '*')) { + /* always use '/' internally */ + *bvKey[i]->bv_val = '/'; + k_val = bvKey[i]->bv_val; + k_len = 1; + break; + } + + /* + * We have a result from LDAP so this is a + * valid entry. Set the result to the LDAP + * key that isn't a wildcard and doesn't have + * any "%" hack values present. This should be + * the case insensitive match string for the + * nis schema, the default value. + */ + k_val = bvKey[i]->bv_val; + k_len = bvKey[i]->bv_len; + + break; + } + + if (!k_val) { + error(ap->logopt, + MODPREFIX "invalid entry %.*s - ignoring", + bvKey[0]->bv_len, bvKey[0]->bv_val); + goto next; + } + } else { + /* Check for the "*" and use as "/" if found */ + if (bvKey[0]->bv_len == 1 && *bvKey[0]->bv_val == '*') + *bvKey[0]->bv_val = '/'; + k_val = bvKey[0]->bv_val; + k_len = bvKey[0]->bv_len; + } + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*k_val == '+') { + warn(ap->logopt, + MODPREFIX + "ignoreing '+' map entry - not in file map"); + goto next; + } + + bvValues = ldap_get_values_len(sp->ldap, e, info); + if (!bvValues || !*bvValues) { + debug(ap->logopt, + MODPREFIX "no %s defined for %s", info, sp->query); + goto next; + } + + /* + * We expect that there will be only one value because + * questions of order of returned value entries but we + * accumulate values to support simple multi-mounts. + * + * If the ordering of a mount spec with another containing + * options or the actual order of entries causes problems + * it won't be supported. Perhaps someone can instruct us + * how to force an ordering. + */ + count = ldap_count_values_len(bvValues); + for (i = 0; i < count; i++) { + char *v_val = bvValues[i]->bv_val; + ber_len_t v_len = bvValues[i]->bv_len; + + if (!mapent) { + mapent = malloc(v_len + 1); + if (!mapent) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + ldap_value_free_len(bvValues); + goto next; + } + strncpy(mapent, v_val, v_len); + mapent[v_len] = '\0'; + mapent_len = v_len; + } else { + int new_size = mapent_len + v_len + 2; + char *new_me; + new_me = realloc(mapent, new_size); + if (new_me) { + mapent = new_me; + strcat(mapent, " "); + strncat(mapent, v_val, v_len); + mapent[new_size - 1] = '\0'; + mapent_len = new_size - 1; + } else { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "realloc: %s", estr); + } + } + } + ldap_value_free_len(bvValues); + + if (*k_val == '/' && k_len == 1) { + if (ap->type == LKP_DIRECT) + goto next; + *k_val = '*'; + } + + if (strcasecmp(class, "nisObject")) { + s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); + if (!s_key) + goto next; + } else { + char *dec_key; + int dec_len = decode_percent_hack(k_val, &dec_key); + + if (dec_len < 0) { + crit(ap->logopt, + "could not use percent hack to decode key %s", + k_val); + goto next; + } + + if (dec_len == 0) + s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); + else { + s_key = sanitize_path(dec_key, dec_len, ap->type, ap->logopt); + free(dec_key); + } + if (!s_key) + goto next; + } + + cache_writelock(mc); + cache_update(mc, source, s_key, mapent, sp->age); + cache_unlock(mc); + + free(s_key); +next: + if (mapent) { + free(mapent); + mapent = NULL; + } + + ldap_value_free_len(bvKey); + e = ldap_next_entry(sp->ldap, e); + if (!e) { + debug(ap->logopt, MODPREFIX + "failed to get next entry for query %s", + sp->query); + ret = ldap_parse_result(sp->ldap, + sp->result, &rv, + NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } + } + + return LDAP_SUCCESS; +} + +static int do_get_amd_entries(struct ldap_search_params *sp, + struct map_source *source, + struct lookup_context *ctxt) +{ + struct autofs_point *ap = sp->ap; + struct mapent_cache *mc = source->mc; + struct berval **bvKey; + struct berval **bvValues; + LDAPMessage *e; + char *entry, *value; + int rv, ret, count; + + entry = ctxt->schema->entry_attr; + value = ctxt->schema->value_attr; + + e = ldap_first_entry(sp->ldap, sp->result); + if (!e) { + debug(ap->logopt, + MODPREFIX "query succeeded, no matches for %s", + sp->query); + ret = ldap_parse_result(sp->ldap, sp->result, + &rv, NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } else + debug(ap->logopt, MODPREFIX "examining entries"); + + while (e) { + char *k_val, *v_val; + ber_len_t k_len; + char *s_key; + + bvKey = ldap_get_values_len(sp->ldap, e, entry); + if (!bvKey || !*bvKey) { + e = ldap_next_entry(sp->ldap, e); + if (!e) { + debug(ap->logopt, MODPREFIX + "failed to get next entry for query %s", + sp->query); + ret = ldap_parse_result(sp->ldap, + sp->result, &rv, + NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } + continue; + } + + /* By definition keys should be unique within each map entry */ + k_val = NULL; + k_len = 0; + + count = ldap_count_values_len(bvKey); + if (count > 1) + warn(ap->logopt, MODPREFIX + "more than one %s, using first", entry); + + k_val = bvKey[0]->bv_val; + k_len = bvKey[0]->bv_len; + + bvValues = ldap_get_values_len(sp->ldap, e, value); + if (!bvValues || !*bvValues) { + debug(ap->logopt, + MODPREFIX "no %s defined for %s", + value, sp->query); + goto next; + } + + count = ldap_count_values_len(bvValues); + if (count > 1) + warn(ap->logopt, MODPREFIX + "more than one %s, using first", value); + + v_val = bvValues[0]->bv_val; + + /* Don't fail on "/" in key => type == 0 */ + s_key = sanitize_path(k_val, k_len, 0, ap->logopt); + if (!s_key) + goto next; + + cache_writelock(mc); + cache_update(mc, source, s_key, v_val, sp->age); + cache_unlock(mc); + + free(s_key); +next: + ldap_value_free_len(bvValues); + ldap_value_free_len(bvKey); + e = ldap_next_entry(sp->ldap, e); + if (!e) { + debug(ap->logopt, MODPREFIX + "failed to get next entry for query %s", + sp->query); + ret = ldap_parse_result(sp->ldap, + sp->result, &rv, + NULL, NULL, NULL, NULL, 0); + if (ret == LDAP_SUCCESS) + return rv; + else + return LDAP_OPERATIONS_ERROR; + } + } + + return LDAP_SUCCESS; +} + +static int read_one_map(struct autofs_point *ap, + struct map_source *source, + struct lookup_context *ctxt, + time_t age, int *result_ldap) +{ + struct ldap_conn conn; + struct ldap_search_params sp; + char buf[MAX_ERR_BUF]; + char *class, *info, *entry; + char *attrs[3]; + int rv, l; + + /* + * If we don't need to create directories (or don't need + * to read an amd cache:=all map) then there's no use + * reading the map. We always need to read the whole map + * for direct mounts in order to mount the triggers. + */ + if (ap->type != LKP_DIRECT && + !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) { + debug(ap->logopt, "map read not needed, so not done"); + return NSS_STATUS_SUCCESS; + } + + sp.ap = ap; + sp.age = age; + + /* Initialize the LDAP context. */ + memset(&conn, 0, sizeof(struct ldap_conn)); + rv = do_reconnect(ap->logopt, &conn, ctxt); + if (rv) + return rv; + sp.ldap = conn.ldap; + + class = ctxt->schema->entry_class; + entry = ctxt->schema->entry_attr; + info = ctxt->schema->value_attr; + + attrs[0] = entry; + attrs[1] = info; + attrs[2] = NULL; + sp.attrs = attrs; + + /* Build a query string. */ + l = strlen("(objectclass=)") + strlen(class) + 1; + + sp.query = malloc(l); + if (sp.query == NULL) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + if (sprintf(sp.query, "(objectclass=%s)", class) >= l) { + error(ap->logopt, MODPREFIX "error forming query string"); + free(sp.query); + return NSS_STATUS_UNAVAIL; + } + + if (ctxt->format & MAP_FLAG_FORMAT_AMD) + sp.base = ctxt->base; + else + sp.base = ctxt->qdn; + + /* Look around. */ + debug(ap->logopt, + MODPREFIX "searching for \"%s\" under \"%s\"", sp.query, sp.base); + + sp.cookie = NULL; + sp.pageSize = 2000; + sp.morePages = FALSE; + sp.totalCount = 0; + sp.result = NULL; + + do { + rv = do_paged_query(&sp, ctxt); + + if (rv == LDAP_ADMINLIMIT_EXCEEDED || + rv == LDAP_SIZELIMIT_EXCEEDED) { + if (sp.result) { + ldap_msgfree(sp.result); + sp.result = NULL; + } + if (sp.cookie) { + ber_bvfree(sp.cookie); + sp.cookie = NULL; + } + sp.pageSize = sp.pageSize / 2; + if (sp.pageSize < 5) { + debug(ap->logopt, MODPREFIX + "result size too small"); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + *result_ldap = rv; + free(sp.query); + return NSS_STATUS_UNAVAIL; + } + continue; + } + + if (rv != LDAP_SUCCESS || !sp.result) { + unbind_ldap_connection(ap->logopt, &conn, ctxt); + *result_ldap = rv; + if (sp.result) + ldap_msgfree(sp.result); + if (sp.cookie) + ber_bvfree(sp.cookie); + free(sp.query); + return NSS_STATUS_UNAVAIL; + } + + if (source->flags & MAP_FLAG_FORMAT_AMD) + rv = do_get_amd_entries(&sp, source, ctxt); + else + rv = do_get_entries(&sp, source, ctxt); + if (rv != LDAP_SUCCESS) { + ldap_msgfree(sp.result); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + *result_ldap = rv; + if (sp.cookie) + ber_bvfree(sp.cookie); + free(sp.query); + return NSS_STATUS_NOTFOUND; + } + ldap_msgfree(sp.result); + sp.result = NULL; + } while (sp.morePages == TRUE); + + debug(ap->logopt, MODPREFIX "done updating map"); + + unbind_ldap_connection(ap->logopt, &conn, ctxt); + + source->age = age; + if (sp.cookie) + ber_bvfree(sp.cookie); + free(sp.query); + + return NSS_STATUS_SUCCESS; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + int rv = LDAP_SUCCESS; + int ret, cur_state; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + ret = read_one_map(ap, source, ctxt, age, &rv); + if (ret != NSS_STATUS_SUCCESS) { + switch (rv) { + case LDAP_SIZELIMIT_EXCEEDED: + crit(ap->logopt, MODPREFIX + "Unable to download entire LDAP map for: %s", + ap->path); + case LDAP_UNWILLING_TO_PERFORM: + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_UNAVAIL; + } + } + pthread_setcancelstate(cur_state, NULL); + + return ret; +} + +static int lookup_one(struct autofs_point *ap, struct map_source *source, + char *qKey, int qKey_len, struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + struct ldap_conn conn; + LDAP *ldap; + int rv, i, l, ql, count; + char buf[MAX_ERR_BUF]; + time_t age = monotonic_time(NULL); + char *query; + LDAPMessage *result = NULL, *e; + char *class, *info, *entry; + char *enc_key1, *enc_key2; + int enc_len1 = 0, enc_len2 = 0; + struct berval **bvKey; + struct berval **bvValues; + char *attrs[3]; + int scope = LDAP_SCOPE_SUBTREE; + struct mapent *we; + unsigned int wild = 0; + int ret = CHE_MISSING; + + mc = source->mc; + + if (ctxt == NULL) { + crit(ap->logopt, MODPREFIX "context was NULL"); + return CHE_FAIL; + } + + /* Initialize the LDAP context. */ + memset(&conn, 0, sizeof(struct ldap_conn)); + rv = do_reconnect(ap->logopt, &conn, ctxt); + if (rv == NSS_STATUS_UNAVAIL) + return CHE_UNAVAIL; + if (rv == NSS_STATUS_NOTFOUND) + return ret; + ldap = conn.ldap; + + class = ctxt->schema->entry_class; + entry = ctxt->schema->entry_attr; + info = ctxt->schema->value_attr; + + attrs[0] = entry; + attrs[1] = info; + attrs[2] = NULL; + + if (*qKey == '*' && qKey_len == 1) + *qKey = '/'; + else if (!strcasecmp(class, "nisObject")) { + enc_len1 = encode_percent_hack(qKey, &enc_key1, 0); + if (enc_len1 < 0) { + crit(ap->logopt, + "could not use percent hack encode key %s", + qKey); + return CHE_FAIL; + } + if (enc_len1 != 0) { + enc_len2 = encode_percent_hack(qKey, &enc_key2, 1); + if (enc_len2 < 0) { + free(enc_key1); + crit(ap->logopt, + "could not use percent hack encode key %s", + qKey); + return CHE_FAIL; + } + } + } + + /* Build a query string. */ + l = strlen(class) + 3*strlen(entry) + strlen(qKey) + 35; + if (enc_len1) + l += 2*strlen(entry) + enc_len1 + enc_len2 + 6; + + query = malloc(l); + if (query == NULL) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + crit(ap->logopt, MODPREFIX "malloc: %s", estr); + if (enc_len1) { + free(enc_key1); + free(enc_key2); + } + free(query); + return CHE_FAIL; + } + + /* + * Look for an entry in class under ctxt-base + * whose entry is equal to qKey. + */ + if (!enc_len1) { + ql = sprintf(query, + "(&(objectclass=%s)(|(%s=%s)(%s=/)(%s=\\2A)))", + class, entry, qKey, entry, entry); + } else { + if (enc_len2) { + ql = sprintf(query, + "(&(objectclass=%s)" + "(|(%s=%s)(%s=%s)(%s=%s)(%s=/)(%s=\\2A)))", + class, entry, qKey, + entry, enc_key1, entry, enc_key2, entry, entry); + free(enc_key1); + free(enc_key2); + } else { + ql = sprintf(query, + "(&(objectclass=%s)" + "(|(%s=%s)(%s=%s)(%s=/)(%s=\\2A)))", + class, entry, qKey, entry, enc_key1, entry, entry); + free(enc_key1); + } + } + if (ql >= l) { + error(ap->logopt, + MODPREFIX "error forming query string"); + free(query); + return CHE_FAIL; + } + + debug(ap->logopt, + MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->qdn); + + rv = ldap_search_s(ldap, ctxt->qdn, scope, query, attrs, 0, &result); + + if ((rv != LDAP_SUCCESS) || !result) { + crit(ap->logopt, MODPREFIX "query failed for %s", query); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + if (result) + ldap_msgfree(result); + free(query); + return CHE_FAIL; + } + + debug(ap->logopt, + MODPREFIX "getting first entry for %s=\"%s\"", entry, qKey); + + e = ldap_first_entry(ldap, result); + if (!e) { + debug(ap->logopt, + MODPREFIX "got answer, but no entry for %s", query); + ldap_msgfree(result); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + free(query); + return CHE_MISSING; + } + + while (e) { + char *mapent = NULL; + size_t mapent_len = 0; + char *k_val; + ber_len_t k_len; + char *s_key; + + bvKey = ldap_get_values_len(ldap, e, entry); + if (!bvKey || !*bvKey) { + e = ldap_next_entry(ldap, e); + continue; + } + + /* + * By definition keys should be unique within each map entry, + * but as always there are exceptions. + */ + k_val = NULL; + k_len = 0; + + /* + * Keys must be unique so, in general, there shouldn't be + * more than one attribute value. We make an exception for + * wildcard entries as people may have values for '*' or + * '/' for compaibility reasons. We use the '/' as the + * wildcard in LDAP but allow '*' as well to allow for + * people using older schemas that allow '*' as a key + * value. Another case where there can be multiple key + * values is when people have used the "%" hack to specify + * case matching ctriteria in a caase insensitive attribute. + */ + count = ldap_count_values_len(bvKey); + if (count > 1) { + unsigned int i; + + /* Check for the "/" and "*" and use as "/" if found */ + for (i = 0; i < count; i++) { + bvKey[i]->bv_val[bvKey[i]->bv_len] = '\0'; + + /* + * If multiple entries are present they could + * be the result of people using the "%" hack so + * ignore them. + */ + if (strchr(bvKey[i]->bv_val, '%')) + continue; + + /* check for wildcard */ + if (bvKey[i]->bv_len == 1 && + (*bvKey[i]->bv_val == '/' || + *bvKey[i]->bv_val == '*')) { + /* always use '/' internally */ + *bvKey[i]->bv_val = '/'; + k_val = bvKey[i]->bv_val; + k_len = 1; + break; + } + + /* + * The key was matched by LDAP so this is a + * valid entry. Set the result key to the + * lookup key to provide the mixed case + * matching provided by the "%" hack. + */ + k_val = qKey; + k_len = strlen(qKey); + + break; + } + + if (!k_val) { + error(ap->logopt, + MODPREFIX "no valid key found for %.*s", + qKey_len, qKey); + ret = CHE_FAIL; + goto next; + } + } else { + /* Check for the "*" and use as "/" if found */ + if (bvKey[0]->bv_len == 1 && *bvKey[0]->bv_val == '*') + *bvKey[0]->bv_val = '/'; + k_val = bvKey[0]->bv_val; + k_len = bvKey[0]->bv_len; + } + + debug(ap->logopt, MODPREFIX "examining first entry"); + + bvValues = ldap_get_values_len(ldap, e, info); + if (!bvValues || !*bvValues) { + debug(ap->logopt, + MODPREFIX "no %s defined for %s", info, query); + goto next; + } + + count = ldap_count_values_len(bvValues); + for (i = 0; i < count; i++) { + char *v_val = bvValues[i]->bv_val; + ber_len_t v_len = bvValues[i]->bv_len; + + if (!mapent) { + mapent = malloc(v_len + 1); + if (!mapent) { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "malloc: %s", estr); + ldap_value_free_len(bvValues); + goto next; + } + strncpy(mapent, v_val, v_len); + mapent[v_len] = '\0'; + mapent_len = v_len; + } else { + int new_size = mapent_len + v_len + 2; + char *new_me; + new_me = realloc(mapent, new_size); + if (new_me) { + mapent = new_me; + strcat(mapent, " "); + strncat(mapent, v_val, v_len); + mapent[new_size - 1] = '\0'; + mapent_len = new_size - 1; + } else { + char *estr; + estr = strerror_r(errno, buf, sizeof(buf)); + logerr(MODPREFIX "realloc: %s", estr); + } + } + } + ldap_value_free_len(bvValues); + + if (*k_val == '/' && k_len == 1) { + if (ap->type == LKP_DIRECT) + goto next; + wild = 1; + cache_writelock(mc); + cache_update(mc, source, "*", mapent, age); + cache_unlock(mc); + goto next; + } + + if (strcasecmp(class, "nisObject")) { + s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); + if (!s_key) + goto next; + } else { + char *dec_key; + int dec_len = decode_percent_hack(k_val, &dec_key); + + if (dec_len < 0) { + crit(ap->logopt, + "could not use percent hack to decode key %s", + k_val); + goto next; + } + + if (dec_len == 0) + s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); + else { + s_key = sanitize_path(dec_key, dec_len, ap->type, ap->logopt); + free(dec_key); + } + if (!s_key) + goto next; + } + + cache_writelock(mc); + ret = cache_update(mc, source, s_key, mapent, age); + cache_unlock(mc); + + free(s_key); +next: + if (mapent) { + free(mapent); + mapent = NULL; + } + + ldap_value_free_len(bvKey); + e = ldap_next_entry(ldap, e); + } + + ldap_msgfree(result); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + + /* Failed to find wild entry, update cache if needed */ + cache_writelock(mc); + we = cache_lookup_distinct(mc, "*"); + if (we) { + /* Wildcard entry existed and is now gone */ + if (we->source == source && !wild) { + cache_delete(mc, "*"); + source->stale = 1; + } + } else { + /* Wildcard not in map but now is */ + if (wild) + source->stale = 1; + } + /* Not found in the map but found in the cache */ + if (ret == CHE_MISSING) { + struct mapent *exists = cache_lookup_distinct(mc, qKey); + if (exists && exists->source == source) { + if (exists->mapent) { + free(exists->mapent); + exists->mapent = NULL; + source->stale = 1; + exists->status = 0; + } + } + } + cache_unlock(mc); + free(query); + + return ret; +} + +static int lookup_one_amd(struct autofs_point *ap, + struct map_source *source, + char *qKey, int qKey_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc = source->mc; + struct ldap_conn conn; + LDAP *ldap; + LDAPMessage *result = NULL, *e; + char *query; + int scope = LDAP_SCOPE_SUBTREE; + char *map, *class, *entry, *value; + char *attrs[3]; + struct berval **bvKey; + struct berval **bvValues; + char buf[MAX_ERR_BUF]; + time_t age = monotonic_time(NULL); + int rv, l, ql, count; + int ret = CHE_MISSING; + + if (ctxt == NULL) { + crit(ap->logopt, MODPREFIX "context was NULL"); + return CHE_FAIL; + } + + /* Initialize the LDAP context. */ + memset(&conn, 0, sizeof(struct ldap_conn)); + rv = do_reconnect(ap->logopt, &conn, ctxt); + if (rv == NSS_STATUS_UNAVAIL) + return CHE_UNAVAIL; + if (rv == NSS_STATUS_NOTFOUND) + return ret; + ldap = conn.ldap; + + map = ctxt->schema->map_attr; + class = ctxt->schema->entry_class; + entry = ctxt->schema->entry_attr; + value = ctxt->schema->value_attr; + + attrs[0] = entry; + attrs[1] = value; + attrs[2] = NULL; + + /* Build a query string. */ + l = strlen(class) + + strlen(map) + strlen(ctxt->mapname) + + strlen(entry) + strlen(qKey) + 24; + + query = malloc(l); + if (query == NULL) { + char *estr = strerror_r(errno, buf, sizeof(buf)); + crit(ap->logopt, MODPREFIX "malloc: %s", estr); + return CHE_FAIL; + } + + /* + * Look for an entry in class under ctxt-base + * whose entry is equal to qKey. + */ + ql = sprintf(query, "(&(objectclass=%s)(%s=%s)(%s=%s))", + class, map, ctxt->mapname, entry, qKey); + if (ql >= l) { + error(ap->logopt, + MODPREFIX "error forming query string"); + free(query); + return CHE_FAIL; + } + + debug(ap->logopt, + MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->base); + + rv = ldap_search_s(ldap, ctxt->base, scope, query, attrs, 0, &result); + if ((rv != LDAP_SUCCESS) || !result) { + crit(ap->logopt, MODPREFIX "query failed for %s", query); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + if (result) + ldap_msgfree(result); + free(query); + return CHE_FAIL; + } + + debug(ap->logopt, + MODPREFIX "getting first entry for %s=\"%s\"", entry, qKey); + + e = ldap_first_entry(ldap, result); + if (!e) { + debug(ap->logopt, + MODPREFIX "got answer, but no entry for %s", query); + ldap_msgfree(result); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + free(query); + return CHE_MISSING; + } + + while (e) { + char *k_val, *v_val; + ber_len_t k_len; + char *s_key; + + bvKey = ldap_get_values_len(ldap, e, entry); + if (!bvKey || !*bvKey) { + e = ldap_next_entry(ldap, e); + continue; + } + + /* By definition keys should be unique within each map entry */ + k_val = NULL; + k_len = 0; + + count = ldap_count_values_len(bvKey); + if (count > 1) + warn(ap->logopt, MODPREFIX + "more than one %s, using first", entry); + + k_val = bvKey[0]->bv_val; + k_len = bvKey[0]->bv_len; + + debug(ap->logopt, MODPREFIX "examining first entry"); + + bvValues = ldap_get_values_len(ldap, e, value); + if (!bvValues || !*bvValues) { + debug(ap->logopt, + MODPREFIX "no %s defined for %s", value, query); + goto next; + } + + count = ldap_count_values_len(bvValues); + if (count > 1) + warn(ap->logopt, MODPREFIX + "more than one %s, using first", value); + + /* There should be one value for a key, use first value */ + v_val = bvValues[0]->bv_val; + + /* Don't fail on "/" in key => type == 0 */ + s_key = sanitize_path(k_val, k_len, 0, ap->logopt); + if (!s_key) + goto next; + + cache_writelock(mc); + ret = cache_update(mc, source, s_key, v_val, age); + cache_unlock(mc); + + free(s_key); +next: + ldap_value_free_len(bvValues); + ldap_value_free_len(bvKey); + e = ldap_next_entry(ldap, e); + } + + ldap_msgfree(result); + unbind_ldap_connection(ap->logopt, &conn, ctxt); + free(query); + + return ret; +} + +static int match_key(struct autofs_point *ap, + struct map_source *source, + char *key, int key_len, + struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + char buf[MAX_ERR_BUF]; + char *lkp_key; + char *prefix; + int ret; + + if (is_amd_format) + ret = lookup_one_amd(ap, source, key, key_len, ctxt); + else + ret = lookup_one(ap, source, key, key_len, ctxt); + + if (ret == CHE_OK || ret == CHE_UPDATED || !is_amd_format) + return ret; + + lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "strdup: %s", estr); + return CHE_FAIL; + } + + ret = CHE_MISSING; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + char *match; + size_t len; + *prefix = '\0'; + len = strlen(lkp_key + 3); + match = malloc(len); + if (!match) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + ret = CHE_FAIL; + goto done; + } + len--; + strcpy(match, lkp_key); + strcat(match, "/*"); + ret = lookup_one_amd(ap, source, match, len, ctxt); + free(match); + if (ret == CHE_OK || ret == CHE_UPDATED) + goto done; + } +done: + free(lkp_key); + return ret; +} + +static int check_map_indirect(struct autofs_point *ap, + struct map_source *source, + char *key, int key_len, + struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + struct mapent_cache *mc; + struct mapent *me; + time_t now = monotonic_time(NULL); + time_t t_last_read; + int ret, cur_state; + int status; + + mc = source->mc; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + + status = pthread_mutex_lock(&ap->entry->current_mutex); + if (status) + fatal(status); + if (is_amd_format) { + unsigned long timestamp = get_amd_timestamp(ctxt); + if (timestamp > ctxt->timestamp) { + ctxt->timestamp = timestamp; + source->stale = 1; + ctxt->check_defaults = 1; + } + + if (ctxt->check_defaults) { + /* Check for a /defaults entry */ + ret = lookup_one_amd(ap, source, "/defaults", 9, ctxt); + if (ret == CHE_FAIL) { + warn(ap->logopt, MODPREFIX + "error getting /defaults from map %s", + ctxt->mapname); + } else + ctxt->check_defaults = 0; + } + } + status = pthread_mutex_unlock(&ap->entry->current_mutex); + if (status) + fatal(status); + + ret = match_key(ap, source, key, key_len, ctxt); + if (ret == CHE_FAIL) { + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_NOTFOUND; + } else if (ret == CHE_UNAVAIL) { + struct mapent *exists; + /* + * If the server is down and the entry exists in the cache + * and belongs to this map return success and use the entry. + */ + if (source->flags & MAP_FLAG_FORMAT_AMD) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup(mc, key); + if (exists && exists->source == source) { + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_SUCCESS; + } + + warn(ap->logopt, + MODPREFIX "lookup for %s failed: connection failed", key); + + return NSS_STATUS_UNAVAIL; + } + pthread_setcancelstate(cur_state, NULL); + + if (!is_amd_format) { + /* + * Check for map change and update as needed for + * following cache lookup. + */ + cache_readlock(mc); + t_last_read = ap->exp_runfreq + 1; + me = cache_lookup_first(mc); + while (me) { + if (me->source == source) { + t_last_read = now - me->age; + break; + } + me = cache_lookup_next(mc, me); + } + cache_unlock(mc); + + status = pthread_mutex_lock(&ap->entry->current_mutex); + if (status) + fatal(status); + if (t_last_read > ap->exp_runfreq && ret & CHE_UPDATED) + source->stale = 1; + status = pthread_mutex_unlock(&ap->entry->current_mutex); + if (status) + fatal(status); + } + + cache_readlock(mc); + me = cache_lookup_distinct(mc, "*"); + if (ret == CHE_MISSING && (!me || me->source != source)) { + cache_unlock(mc); + return NSS_STATUS_NOTFOUND; + } + cache_unlock(mc); + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + char key[KEY_MAX_LEN + 1]; + int key_len; + char *lkp_key; + char *mapent = NULL; + char mapent_buf[MAPENT_MAX_LEN + 1]; + char buf[MAX_ERR_BUF]; + int status = 0; + int ret = 1; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + } else { + key_len = expandamdent(name, NULL, NULL); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + expandamdent(name, key, NULL); + key[key_len] = '\0'; + debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, key, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, key); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, key); + } + cache_unlock(smc); + } + } + } + + /* + * We can't check the direct mount map as if it's not in + * the map cache already we never get a mount lookup, so + * we never know about it. + */ + if (ap->type == LKP_INDIRECT && *key != '/') { + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me && me->multi) + lkp_key = strdup(me->multi->key); + else if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + cache_unlock(mc); + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNKNOWN; + } + + status = check_map_indirect(ap, source, + lkp_key, strlen(lkp_key), ctxt); + free(lkp_key); + if (status) + return status; + } + + /* + * We can't take the writelock for direct mounts. If we're + * starting up or trying to re-connect to an existing direct + * mount we could be iterating through the map entries with + * the readlock held. But we don't need to update the cache + * when we're starting up so just take the readlock in that + * case. + */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + cache_readlock(mc); + else + cache_writelock(mc); + + if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + cache_unlock(mc); + return NSS_STATUS_UNKNOWN; + } + + me = match_cached_key(ap, MODPREFIX, source, lkp_key); + /* Stale mapent => check for entry in alternate source or wildcard */ + if (me && !me->mapent) { + while ((me = cache_lookup_key_next(me))) + if (me->source == source) + break; + if (!me) + me = cache_lookup_distinct(mc, "*"); + } + if (me && me->mapent) { + /* + * If this is a lookup add wildcard match for later validation + * checks and negative cache lookups. + */ + if (!(ap->flags & MOUNT_FLAG_REMOUNT) && + ap->type == LKP_INDIRECT && *me->key == '*') { + ret = cache_update(mc, source, key, me->mapent, me->age); + if (!(ret & (CHE_OK | CHE_UPDATED))) + me = NULL; + } + if (me && (me->source == source || *me->key == '/')) { + strcpy(mapent_buf, me->mapent); + mapent = mapent_buf; + } + } + cache_unlock(mc); + free(lkp_key); + + if (!mapent) + return NSS_STATUS_TRYAGAIN; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); + ret = ctxt->parse->parse_mount(ap, key, key_len, + mapent, ctxt->parse->context); + if (ret) { + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, key, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + + return NSS_STATUS_SUCCESS; +} + +/* + * This destroys a context for queries to this module. It releases the parser + * structure (unloading the module) and frees the memory used by the context. + */ +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); +#ifdef WITH_SASL + ldapinit_mutex_lock(); + autofs_sasl_dispose(NULL, ctxt); + autofs_sasl_done(); + ldapinit_mutex_unlock(); +#endif + free_context(ctxt); + return rv; +} diff --git a/modules/lookup_multi.c b/modules/lookup_multi.c new file mode 100644 index 0000000..fadd2ea --- /dev/null +++ b/modules/lookup_multi.c @@ -0,0 +1,584 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_multi.c - module for Linux automount to seek multiple lookup + * methods in succession + * + * Copyright 1999 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAX_MAP_TYPE_STRING 20 + +#define MODPREFIX "lookup(multi): " + +struct module_info { + int argc; + const char **argv; + struct lookup_mod *mod; +}; + +struct lookup_context { + int n; + const char **argl; + struct module_info *m; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int free_multi_context(struct lookup_context *); + +static struct lookup_context *alloc_context(const char *format, + int argc, const char *const *argv) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + char **args; + int i, an; + char *estr; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) + goto nomem; + + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (argc < 1) { + logerr(MODPREFIX "No map list"); + goto error_out; + } + + ctxt->n = 1; /* Always at least one map */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) /* -- separates maps */ + ctxt->n++; + } + + if (!(ctxt->m = malloc(ctxt->n * sizeof(struct module_info))) || + !(ctxt->argl = malloc((argc + 1) * sizeof(const char *)))) + goto nomem; + + memset(ctxt->m, 0, ctxt->n * sizeof(struct module_info)); + + memcpy(ctxt->argl, argv, (argc + 1) * sizeof(const char *)); + + args = NULL; + for (i = an = 0; ctxt->argl[an]; an++) { + if (ctxt->m[i].argc == 0) + args = (char **) &ctxt->argl[an]; + + if (strcmp(ctxt->argl[an], "--")) + ctxt->m[i].argc++; + else { + ctxt->argl[an] = NULL; + if (!args) { + logerr(MODPREFIX "error assigning map args"); + goto error_out; + } + ctxt->m[i].argv = copy_argv(ctxt->m[i].argc, + (const char **) args); + if (!ctxt->m[i].argv) + goto nomem; + args = NULL; + i++; + } + } + + /* catch the last one */ + if (args) { + ctxt->m[i].argv = copy_argv(ctxt->m[i].argc, (const char **) args); + if (!ctxt->m[i].argv) + goto nomem; + } + + return ctxt; + +nomem: + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "error: %s", estr); + +error_out: + free_multi_context(ctxt); + free(ctxt); + + return NULL; +} + +static int free_multi_context(struct lookup_context *ctxt) +{ + int rv; + + if (!ctxt) + return 0; + + rv = 0; + if (ctxt->m) { + int i; + + for (i = 0; i < ctxt->n; i++) { + if (ctxt->m[i].mod) + rv = rv || close_lookup(ctxt->m[i].mod); + if (ctxt->m[i].argv) + free_argv(ctxt->m[i].argc, ctxt->m[i].argv); + } + free(ctxt->m); + } + + if (ctxt->argl) + free(ctxt->argl); + + return rv; +} + +static struct lookup_context *update_multi_context(struct lookup_context *ctxt, + struct lookup_context *new) +{ + int i; + + for (i = 0; i < new->n && i < ctxt->n; i++) { + if (new->m[i].mod) + continue; + + if (!ctxt->m[i].mod) + continue; + + /* reinit or open failed, use old one, questionable but + * we need to do something. + */ + new->m[i].mod = ctxt->m[i].mod; + ctxt->m[i].mod = NULL; + new->m[i].argc = ctxt->m[i].argc; + new->m[i].argv = ctxt->m[i].argv; + ctxt->m[i].argv = NULL; + } + + return new; +} + +static struct lookup_mod *nss_open_lookup(const char *format, int argc, const char **argv) +{ + struct list_head nsslist; + struct list_head *head, *p; + struct lookup_mod *mod; + char buf[MAX_ERR_BUF], *estr; + + if (!argv || !argv[0]) + return NULL; + + if (*argv[0] == '/') { + open_lookup("file", MODPREFIX, format, argc, argv, &mod); + return mod; + } + + if (!strncmp(argv[0], "file", 4) || + !strncmp(argv[0], "yp", 2) || + !strncmp(argv[0], "nisplus", 7) || + !strncmp(argv[0], "nis", 3) || + !strncmp(argv[0], "ldaps", 5) || + !strncmp(argv[0], "ldap", 4) || + !strncmp(argv[0], "sss", 3)) { + char type[MAX_MAP_TYPE_STRING]; + char *fmt; + + strcpy(type, argv[0]); + fmt = strchr(type, ','); + if (!fmt) + fmt = (char *) format; + else { + *fmt = '\0'; + fmt++; + } + open_lookup(argv[0], MODPREFIX, fmt, argc - 1, argv + 1, &mod); + return mod; + } + + INIT_LIST_HEAD(&nsslist); + + if (nsswitch_parse(&nsslist)) { + if (!list_empty(&nsslist)) + free_sources(&nsslist); + logerr("can't to read name service switch config."); + return NULL; + } + + head = &nsslist; + list_for_each(p, head) { + struct nss_source *this; + int status; + int ret; + + this = list_entry(p, struct nss_source, list); + + if (!strcmp(this->source, "files")) { + char src_file[] = "file"; + char src_prog[] = "program"; + struct stat st; + char *type, *path, *save_argv0; + + path = malloc(strlen(AUTOFS_MAP_DIR) + strlen(argv[0]) + 2); + if (!path) { + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "error: %s", estr); + free_sources(&nsslist); + return NULL; + } + strcpy(path, AUTOFS_MAP_DIR); + strcat(path, "/"); + strcat(path, argv[0]); + + if (stat(path, &st) == -1 || !S_ISREG(st.st_mode)) { + free(path); + continue; + } + + if (st.st_mode & __S_IEXEC) + type = src_prog; + else + type = src_file; + + save_argv0 = (char *) argv[0]; + argv[0] = path; + + status = open_lookup(type, MODPREFIX, + format, argc, argv, &mod); + if (status == NSS_STATUS_SUCCESS) { + free_sources(&nsslist); + free(save_argv0); + return mod; + } + + argv[0] = save_argv0; + free(path); + + ret = check_nss_result(this, status); + if (ret >= 0) + break; + } + + status = open_lookup(this->source, MODPREFIX, + format, argc, argv, &mod); + if (status == NSS_STATUS_SUCCESS) { + free_sources(&nsslist); + return mod; + } + + ret = check_nss_result(this, status); + if (ret >= 0) + break; + } + free_sources(&nsslist); + + return NULL; +} + +int lookup_init(const char *my_mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + int i; + + *context = NULL; + + ctxt = alloc_context(my_mapfmt, argc, argv); + if (!ctxt) + return 1; + + for (i = 0; i < ctxt->n; i++) { + ctxt->m[i].mod = nss_open_lookup(my_mapfmt, + ctxt->m[i].argc, ctxt->m[i].argv); + if (!ctxt->m[i].mod) { + logerr(MODPREFIX "error opening module"); + free_multi_context(ctxt); + free(ctxt); + return 1; + } + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *my_mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct list_head nsslist; + struct list_head *head, *p; + struct lookup_context *new; + char buf[MAX_ERR_BUF], *estr; + int i, ret = 0; + int status; + + new = alloc_context(my_mapfmt, argc, argv); + if (!new) + return 1; + + for (i = 0; i < new->n; i++) { + if (i >= ctxt->n) { + new->m[i].mod = nss_open_lookup(my_mapfmt, + new->m[i].argc, + new->m[i].argv); + if (!new->m[i].mod) { + logerr(MODPREFIX "error opening module"); + /* TODO: check */ + ret = 1; + goto out; + } + continue; + } + + if (*new->m[i].argv[0] == '/') { + if (strcmp(new->m[i].argv[0], ctxt->m[i].argv[0])) + open_lookup("file", MODPREFIX, + my_mapfmt, + new->m[i].argc, + new->m[i].argv, + &new->m[i].mod); + else { + new->m[i].mod = ctxt->m[i].mod; + if (reinit_lookup(new->m[i].mod, "file", + MODPREFIX, my_mapfmt, + new->m[i].argc, new->m[i].argv)) + new->m[i].mod = NULL; + else + ctxt->m[i].mod = NULL; + } + continue; + } + + if (!strncmp(new->m[i].argv[0], "file", 4) || + !strncmp(new->m[i].argv[0], "yp", 2) || + !strncmp(new->m[i].argv[0], "nisplus", 7) || + !strncmp(new->m[i].argv[0], "nis", 3) || + !strncmp(new->m[i].argv[0], "ldaps", 5) || + !strncmp(new->m[i].argv[0], "ldap", 4) || + !strncmp(new->m[i].argv[0], "sss", 3)) { + char type[MAX_MAP_TYPE_STRING]; + char *fmt; + + strcpy(type, new->m[i].argv[0]); + fmt = strchr(type, ','); + if (!fmt) + fmt = (char *) my_mapfmt; + else { + *fmt = '\0'; + fmt++; + } + + if (!strcmp(new->m[i].argv[0], ctxt->m[i].argv[0]) && + !strcmp(new->m[i].argv[1], ctxt->m[i].argv[1])) { + new->m[i].mod = ctxt->m[i].mod; + if (reinit_lookup(new->m[i].mod, new->m[i].argv[0], + MODPREFIX, fmt, + new->m[i].argc - 1, new->m[i].argv + 1)) + new->m[i].mod = NULL; + else + ctxt->m[i].mod = NULL; + } else { + open_lookup(type, MODPREFIX, fmt, + new->m[i].argc - 1, + new->m[i].argv + 1, + &new->m[i].mod); + } + continue; + } + + INIT_LIST_HEAD(&nsslist); + + if (nsswitch_parse(&nsslist)) { + if (!list_empty(&nsslist)) + free_sources(&nsslist); + logerr("can't to read name service switch config."); + /* TODO: check */ + ret = 1; + goto out; + } + + head = &nsslist; + list_for_each(p, head) { + struct nss_source *this; + + this = list_entry(p, struct nss_source, list); + + if (!strcmp(this->source, ctxt->m[i].mod->type)) { + new->m[i].mod = ctxt->m[i].mod; + if (reinit_lookup(new->m[i].mod, this->source, + MODPREFIX, my_mapfmt, + new->m[i].argc, new->m[i].argv)) + new->m[i].mod = NULL; + else + ctxt->m[i].mod = NULL; + continue; + } + + if (!strcmp(this->source, "files")) { + char src_file[] = "file"; + char src_prog[] = "program"; + struct stat st; + char *type, *path, *save_argv0; + + path = malloc(strlen(AUTOFS_MAP_DIR) + + strlen(new->m[i].argv[0]) + 2); + if (!path) { + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "error: %s", estr); + free_sources(&nsslist); + ret = 1; + goto out; + } + strcpy(path, AUTOFS_MAP_DIR); + strcat(path, "/"); + strcat(path, new->m[i].argv[0]); + + if (stat(path, &st) == -1 || !S_ISREG(st.st_mode)) { + free(path); + continue; + } + + if (st.st_mode & __S_IEXEC) + type = src_prog; + else + type = src_file; + + save_argv0 = (char *) new->m[i].argv[0]; + new->m[i].argv[0] = path; + + if (strcmp(type, ctxt->m[i].mod->type)) { + status = open_lookup(type, + MODPREFIX, + my_mapfmt, + new->m[i].argc, + new->m[i].argv, + &new->m[i].mod); + if (status == NSS_STATUS_SUCCESS) { + free(save_argv0); + break; + } + } else { + new->m[i].mod = ctxt->m[i].mod; + if (reinit_lookup(new->m[i].mod, type, + MODPREFIX, my_mapfmt, + new->m[i].argc, new->m[i].argv)) + new->m[i].mod = NULL; + else { + ctxt->m[i].mod = NULL; + free(save_argv0); + break; + } + } + + new->m[i].argv[0] = save_argv0; + free(path); + continue; + } + + if (strcmp(this->source, ctxt->m[i].mod->type)) { + status = open_lookup(this->source, MODPREFIX, + my_mapfmt, + new->m[i].argc, + new->m[i].argv, + &new->m[i].mod); + if (status == NSS_STATUS_SUCCESS) + break; + } else { + new->m[i].mod = ctxt->m[i].mod; + if (reinit_lookup(new->m[i].mod, this->source, + MODPREFIX, my_mapfmt, + new->m[i].argc, new->m[i].argv)) + new->m[i].mod = NULL; + else { + ctxt->m[i].mod = NULL; + break; + } + } + } + free_sources(&nsslist); + } +out: + /* Update new context with any needed old context */ + *context = update_multi_context(ctxt, new); + free_multi_context(ctxt); + free(ctxt); + + return ret; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + return NSS_STATUS_UNKNOWN; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + int i, ret, at_least_1 = 0; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + for (i = 0; i < ctxt->n; i++) { + master_source_current_wait(ap->entry); + ap->entry->current = source; + ret = ctxt->m[i].mod->lookup_read_map(ap, age, + ctxt->m[i].mod->context); + if (ret & LKP_FAIL || ret == LKP_NOTSUP) + continue; + + at_least_1 = 1; + } + + if (!at_least_1) + return NSS_STATUS_NOTFOUND; + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + int i; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + for (i = 0; i < ctxt->n; i++) { + master_source_current_wait(ap->entry); + ap->entry->current = source; + if (ctxt->m[i].mod->lookup_mount(ap, name, name_len, + ctxt->m[i].mod->context) == 0) + return NSS_STATUS_SUCCESS; + } + return NSS_STATUS_NOTFOUND; /* No module succeeded */ +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv; + + rv = free_multi_context(ctxt); + free(ctxt); + + return rv; +} diff --git a/modules/lookup_nisplus.c b/modules/lookup_nisplus.c new file mode 100644 index 0000000..6430b89 --- /dev/null +++ b/modules/lookup_nisplus.c @@ -0,0 +1,861 @@ +/* + * lookup_nisplus.c + * + * Module for Linux automountd to access a NIS+ automount map + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" + +#define MODPREFIX "lookup(nisplus): " + +struct lookup_context { + const char *domainname; + const char *mapname; + struct parse_mod *parse; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + int ret = 0; + + if (argc < 1) { + logmsg(MODPREFIX "No map name"); + ret = 1; + goto out; + } + ctxt->mapname = argv[0]; + + /* + * nis_local_directory () returns a pointer to a static buffer. + * We don't need to copy or free it. + */ + ctxt->domainname = nis_local_directory(); + if (!ctxt->domainname) { + logmsg(MODPREFIX "NIS+ domain not set"); + ret = 1; + goto out; + } + + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + if (reinit) { + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parse) { + logerr(MODPREFIX "failed to open parse context"); + ret = 1; + } + } +out: + return ret; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "%s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (do_init(mapfmt, argc, argv, ctxt, 0)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "%s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + *context = new; + + free(ctxt); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + unsigned int timeout = master->default_timeout; + unsigned int logging = master->default_logging; + unsigned int logopt = master->logopt; + char *tablename; + nis_result *result; + nis_object *this; + unsigned int current, result_count; + char *path, *ent; + char *buffer; + char buf[MAX_ERR_BUF]; + int cur_state, len; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20); + if (!tablename) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_UNKNOWN; + } + sprintf(tablename, "%s.org_dir.%s", ctxt->mapname, ctxt->domainname); + + /* check that the table exists */ + result = nis_lookup(tablename, FOLLOW_PATH | FOLLOW_LINKS); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + int status = result->status; + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + if (status == NIS_UNAVAIL || status == NIS_FAIL) + return NSS_STATUS_UNAVAIL; + else { + crit(logopt, + MODPREFIX "couldn't locate nis+ table %s", + ctxt->mapname); + return NSS_STATUS_NOTFOUND; + } + } + + sprintf(tablename, "[],%s.org_dir.%s", ctxt->mapname, ctxt->domainname); + + result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_freeresult(result); + crit(logopt, + MODPREFIX "couldn't enumrate nis+ map %s", ctxt->mapname); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_UNAVAIL; + } + + current = 0; + result_count = NIS_RES_NUMOBJ(result); + + while (result_count--) { + this = &result->objects.objects_val[current++]; + path = ENTRY_VAL(this, 0); + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*path == '+') + continue; + + ent = ENTRY_VAL(this, 1); + + len = ENTRY_LEN(this, 0) + 1 + ENTRY_LEN(this, 1) + 2; + buffer = malloc(len); + if (!buffer) { + logerr(MODPREFIX "could not malloc parse buffer"); + continue; + } + memset(buffer, 0, len); + + strcat(buffer, path); + strcat(buffer, " "); + strcat(buffer, ent); + + master_parse_entry(buffer, timeout, logging, age); + + free(buffer); + } + + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + + return NSS_STATUS_SUCCESS; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + char *tablename; + nis_result *result; + nis_object *this; + unsigned int current, result_count; + char *key, *mapent; + char buf[MAX_ERR_BUF]; + int cur_state; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + /* + * If we don't need to create directories (or don't need + * to read an amd cache:=all map) then there's no use + * reading the map. We always need to read the whole map + * for direct mounts in order to mount the triggers. + */ + if (ap->type != LKP_DIRECT && + !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) { + debug(ap->logopt, "map read not needed, so not done"); + return NSS_STATUS_SUCCESS; + } + + mc = source->mc; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20); + if (!tablename) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_UNAVAIL; + } + sprintf(tablename, "%s.org_dir.%s", ctxt->mapname, ctxt->domainname); + + /* check that the table exists */ + result = nis_lookup(tablename, FOLLOW_PATH | FOLLOW_LINKS); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_freeresult(result); + crit(ap->logopt, + MODPREFIX "couldn't locate nis+ table %s", ctxt->mapname); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_NOTFOUND; + } + + sprintf(tablename, "[],%s.org_dir.%s", ctxt->mapname, ctxt->domainname); + + result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_freeresult(result); + crit(ap->logopt, + MODPREFIX "couldn't enumrate nis+ map %s", ctxt->mapname); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_UNAVAIL; + } + + current = 0; + result_count = NIS_RES_NUMOBJ(result); + + while (result_count--) { + char *s_key; + size_t len; + + this = &result->objects.objects_val[current++]; + key = ENTRY_VAL(this, 0); + len = ENTRY_LEN(this, 0); + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*key == '+') + continue; + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) + s_key = sanitize_path(key, len, ap->type, ap->logopt); + else { + if (!strcmp(key, "/defaults")) { + mapent = ENTRY_VAL(this, 1); + cache_writelock(mc); + cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + continue; + } + /* Don't fail on "/" in key => type == 0 */ + s_key = sanitize_path(key, len, 0, ap->logopt); + } + if (!s_key) + continue; + + mapent = ENTRY_VAL(this, 1); + + cache_writelock(mc); + cache_update(mc, source, s_key, mapent, age); + cache_unlock(mc); + + free(s_key); + } + + nis_freeresult(result); + + source->age = age; + + free(tablename); + pthread_setcancelstate(cur_state, NULL); + + return NSS_STATUS_SUCCESS; +} + +static int lookup_one(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char *tablename; + nis_result *result; + nis_object *this; + char *mapent; + time_t age = monotonic_time(NULL); + int ret, cur_state; + char buf[MAX_ERR_BUF]; + + mc = source->mc; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + tablename = malloc(strlen(key) + strlen(ctxt->mapname) + + strlen(ctxt->domainname) + 20); + if (!tablename) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + pthread_setcancelstate(cur_state, NULL); + return -1; + } + sprintf(tablename, "[key=%s],%s.org_dir.%s", key, ctxt->mapname, + ctxt->domainname); + + result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_error rs = result->status; + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + if (rs == NIS_NOTFOUND || + rs == NIS_S_NOTFOUND || + rs == NIS_PARTIAL) + return CHE_MISSING; + + return -rs; + } + + + this = NIS_RES_OBJECT(result); + mapent = ENTRY_VAL(this, 1); + cache_writelock(mc); + ret = cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + + return ret; +} + +static int match_key(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + char buf[MAX_ERR_BUF]; + char *lkp_key; + char *prefix; + int ret; + + ret = lookup_one(ap, source, key, key_len, ctxt); + if (ret < 0) + return ret; + if (ret == CHE_OK || ret == CHE_UPDATED || is_amd_format) + return ret; + + lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "strdup: %s", estr); + return CHE_FAIL; + } + + ret = CHE_MISSING; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + char *match; + size_t len; + *prefix = '\0'; + len = strlen(lkp_key) + 3; + match = malloc(len); + if (!match) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + ret = CHE_FAIL; + goto done; + } + len--; + strcpy(match, lkp_key); + strcat(match, "/*"); + ret = lookup_one(ap, source, match, len, ctxt); + free(match); + if (ret < 0) + goto done; + if (ret == CHE_OK || ret == CHE_UPDATED) + goto done; + } +done: + free(lkp_key); + return ret; +} + +static int lookup_wild(struct autofs_point *ap, + struct map_source *source, struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char *tablename; + nis_result *result; + nis_object *this; + char *mapent; + time_t age = monotonic_time(NULL); + int ret, cur_state; + char buf[MAX_ERR_BUF]; + + mc = source->mc; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20); + if (!tablename) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + pthread_setcancelstate(cur_state, NULL); + return -1; + } + sprintf(tablename, "[key=*],%s.org_dir.%s", ctxt->mapname, + ctxt->domainname); + + result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_error rs = result->status; + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + if (rs == NIS_NOTFOUND || + rs == NIS_S_NOTFOUND || + rs == NIS_PARTIAL) + return CHE_MISSING; + + return -rs; + } + + this = NIS_RES_OBJECT(result); + mapent = ENTRY_VAL(this, 1); + cache_writelock(mc); + ret = cache_update(mc, source, "*", mapent, age); + cache_unlock(mc); + + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + + return ret; +} + +static int lookup_amd_defaults(struct autofs_point *ap, + struct map_source *source, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc = source->mc; + char *tablename; + nis_result *result; + nis_object *this; + char *mapent; + char buf[MAX_ERR_BUF]; + int cur_state; + int ret; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + tablename = malloc(9 + strlen(ctxt->mapname) + + strlen(ctxt->domainname) + 20); + if (!tablename) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + pthread_setcancelstate(cur_state, NULL); + return CHE_FAIL; + } + sprintf(tablename, "[key=/defaults],%s.org_dir.%s", + ctxt->mapname, ctxt->domainname); + + result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); + if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) { + nis_error rs = result->status; + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + if (rs == NIS_NOTFOUND || + rs == NIS_S_NOTFOUND || + rs == NIS_PARTIAL) + return CHE_MISSING; + + return -rs; + } + + this = NIS_RES_OBJECT(result); + mapent = ENTRY_VAL(this, 1); + + cache_writelock(mc); + ret = cache_update(mc, source, "/defaults", mapent, monotonic_time(NULL)); + cache_unlock(mc); + + nis_freeresult(result); + free(tablename); + pthread_setcancelstate(cur_state, NULL); + + return ret; +} + +static int check_map_indirect(struct autofs_point *ap, + struct map_source *source, + char *key, int key_len, + struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + struct mapent_cache *mc; + struct mapent *me, *exists; + time_t now = monotonic_time(NULL); + time_t t_last_read; + int ret = 0; + + mc = source->mc; + + if (is_amd_format) { + /* Check for a /defaults entry to update the map source */ + if (lookup_amd_defaults(ap, source, ctxt) == CHE_FAIL) { + warn(ap->logopt, MODPREFIX + "error getting /defaults from map %s", + ctxt->mapname); + } + } + + /* check map and if change is detected re-read map */ + ret = match_key(ap, source, key, key_len, ctxt); + if (ret == CHE_FAIL) + return NSS_STATUS_NOTFOUND; + + if (ret < 0) { + /* + * If the server is down and the entry exists in the cache + * and belongs to this map return success and use the entry. + */ + cache_readlock(mc); + if (source->flags & MAP_FLAG_FORMAT_AMD) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup(mc, key); + if (exists && exists->source == source) { + cache_unlock(mc); + return NSS_STATUS_SUCCESS; + } + cache_unlock(mc); + + warn(ap->logopt, + MODPREFIX "lookup for %s failed: %s", + key, nis_sperrno(-ret)); + + return NSS_STATUS_UNAVAIL; + } + + cache_writelock(mc); + t_last_read = ap->exp_runfreq + 1; + me = cache_lookup_first(mc); + while (me) { + if (me->source == source) { + t_last_read = now - me->age; + break; + } + me = cache_lookup_next(mc, me); + } + if (is_amd_format) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup_distinct(mc, key); + exists = cache_lookup_distinct(mc, key); + /* Not found in the map but found in the cache */ + if (exists && exists->source == source && ret & CHE_MISSING) { + if (exists->mapent) { + free(exists->mapent); + exists->mapent = NULL; + source->stale = 1; + exists->status = 0; + } + } + cache_unlock(mc); + + if (t_last_read > ap->exp_runfreq && ret & CHE_UPDATED) + source->stale = 1; + + if (ret == CHE_MISSING) { + int wild = CHE_MISSING; + struct mapent *we; + + wild = lookup_wild(ap, source, ctxt); + /* + * Check for map change and update as needed for + * following cache lookup. + */ + cache_writelock(mc); + we = cache_lookup_distinct(mc, "*"); + if (we) { + /* Wildcard entry existed and is now gone */ + if (we->source == source && wild & CHE_MISSING) { + cache_delete(mc, "*"); + source->stale = 1; + } + } else { + /* Wildcard not in map but now is */ + if (wild & (CHE_OK | CHE_UPDATED)) + source->stale = 1; + } + cache_unlock(mc); + + if (wild & (CHE_UPDATED | CHE_OK)) + return NSS_STATUS_SUCCESS; + } + + if (ret == CHE_MISSING) + return NSS_STATUS_NOTFOUND; + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + char key[KEY_MAX_LEN + 1]; + int key_len; + char *lkp_key; + char *mapent = NULL; + int mapent_len; + struct mapent *me; + char buf[MAX_ERR_BUF]; + int status; + int ret = 1; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + } else { + key_len = expandamdent(name, NULL, NULL); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + memset(key, 0, KEY_MAX_LEN + 1); + expandamdent(name, key, NULL); + debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, key, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, key); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, key); + } + cache_unlock(smc); + } + } + } + + /* + * We can't check the direct mount map as if it's not in + * the map cache already we never get a mount lookup, so + * we never know about it. + */ + if (ap->type == LKP_INDIRECT && *key != '/') { + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me && me->multi) + lkp_key = strdup(me->multi->key); + else if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + cache_unlock(mc); + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNKNOWN; + } + + status = check_map_indirect(ap, source, + lkp_key, strlen(lkp_key), ctxt); + free(lkp_key); + if (status) + return status; + } + + /* + * We can't take the writelock for direct mounts. If we're + * starting up or trying to re-connect to an existing direct + * mount we could be iterating through the map entries with + * the readlock held. But we don't need to update the cache + * when we're starting up so just take the readlock in that + * case. + */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + cache_readlock(mc); + else + cache_writelock(mc); + + if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + cache_unlock(mc); + return NSS_STATUS_UNKNOWN; + } + + me = match_cached_key(ap, MODPREFIX, source, lkp_key); + /* Stale mapent => check for entry in alternate source or wildcard */ + if (me && !me->mapent) { + while ((me = cache_lookup_key_next(me))) + if (me->source == source) + break; + if (!me) + me = cache_lookup_distinct(mc, "*"); + } + if (me && me->mapent) { + /* + * If this is a lookup add wildcard match for later validation + * checks and negative cache lookups. + */ + if (!(ap->flags & MOUNT_FLAG_REMOUNT) && + ap->type == LKP_INDIRECT && *me->key == '*') { + ret = cache_update(mc, source, key, me->mapent, me->age); + if (!(ret & (CHE_OK | CHE_UPDATED))) + me = NULL; + } + if (me && (me->source == source || *me->key == '/')) { + mapent_len = strlen(me->mapent); + mapent = malloc(mapent_len + 1); + if (mapent) + strcpy(mapent, me->mapent); + } + } + cache_unlock(mc); + free(lkp_key); + + if (!mapent) + return NSS_STATUS_TRYAGAIN; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); + ret = ctxt->parse->parse_mount(ap, key, key_len, + mapent, ctxt->parse->context); + if (ret) { + free(mapent); + + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, key, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + free(mapent); + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + free(ctxt); + return rv; +} diff --git a/modules/lookup_program.c b/modules/lookup_program.c new file mode 100644 index 0000000..b3f1c1f --- /dev/null +++ b/modules/lookup_program.c @@ -0,0 +1,718 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_program.c - module for Linux automount to access an + * automount map via a query program + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" + +#define MODPREFIX "lookup(program): " + +struct lookup_context { + const char *mapname; + char *mapfmt; + struct parse_mod *parse; +}; + +struct parse_context { + char *optstr; /* Mount options */ + char *macros; /* Map wide macro defines */ + struct substvar *subst; /* $-substitutions */ + int slashify_colons; /* Change colons to slashes? */ +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + int ret = 0; + + if (argc < 1) { + logmsg(MODPREFIX "No map name"); + ret = 1; + goto out; + } + ctxt->mapname = argv[0]; + + if (ctxt->mapname[0] != '/') { + logmsg(MODPREFIX "program map %s is not an absolute pathname", + ctxt->mapname); + ret = 1; + goto out; + } + + if (access(ctxt->mapname, X_OK)) { + logmsg(MODPREFIX "program map %s missing or not executable", + ctxt->mapname); + ret = 1; + goto out; + } + + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + ctxt->mapfmt = strdup(mapfmt); + if (!ctxt->mapfmt) { + logmsg(MODPREFIX "failed to allocate storage for map format"); + ret = 1; + goto out; + } + + if (reinit) { + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc - 1, argv + 1); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parse) { + logmsg(MODPREFIX "failed to open parse context"); + ret = 1; + } + } +out: + if (ret && ctxt->mapfmt) + free(ctxt->mapfmt); + + return ret; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (do_init(mapfmt, argc, argv, ctxt, 0)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + *context = new; + + free(ctxt->mapfmt); + free(ctxt); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + return NSS_STATUS_UNKNOWN; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + return NSS_STATUS_UNKNOWN; +} + +static char *lookup_one(struct autofs_point *ap, + const char *name, int name_len, + struct lookup_context *ctxt) +{ + char *mapent = NULL, *mapp, *tmp; + char buf[MAX_ERR_BUF]; + char errbuf[1024], *errp; + char ch; + int pipefd[2], epipefd[2]; + struct pollfd pfd[2]; + pid_t f; + enum state { st_space, st_map, st_done } state; + int quoted = 0; + int distance; + int alloci = 1; + int status; + char *prefix; + + mapent = (char *) malloc(MAPENT_MAX_LEN + 1); + if (!mapent) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return NULL; + } + + /* + * By default use a prefix with standard environment + * variables to prevent system subversion by interpreted + * languages. + */ + if (defaults_force_std_prog_map_env()) + prefix = NULL; + else + prefix = "AUTOFS_"; + + /* + * We don't use popen because we don't want to run /bin/sh plus we + * want to send stderr to the syslog, and we don't use spawnl() + * because we need the pipe hooks + */ + if (open_pipe(pipefd)) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "pipe: %s", estr); + goto out_error; + } + if (open_pipe(epipefd)) { + close(pipefd[0]); + close(pipefd[1]); + goto out_error; + } + + f = fork(); + if (f < 0) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "fork: %s", estr); + close(pipefd[0]); + close(pipefd[1]); + close(epipefd[0]); + close(epipefd[1]); + goto out_error; + } else if (f == 0) { + reset_signals(); + close(pipefd[0]); + close(epipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(epipefd[1], STDERR_FILENO); + close(pipefd[1]); + close(epipefd[1]); + if (chdir(ap->path)) + warn(ap->logopt, + MODPREFIX "failed to set PWD to %s for map %s", + ap->path, ctxt->mapname); + + /* + * MAPFMT_DEFAULT must be "sun" for ->parse_init() to have setup + * the macro table. + */ + if (ctxt->mapfmt && !strcmp(ctxt->mapfmt, MAPFMT_DEFAULT)) { + struct parse_context *pctxt = (struct parse_context *) ctxt->parse->context; + /* Add standard environment as seen by sun map parser */ + pctxt->subst = addstdenv(pctxt->subst, prefix); + macro_setenv(pctxt->subst); + } + execl(ctxt->mapname, ctxt->mapname, name, NULL); + _exit(255); /* execl() failed */ + } + close(pipefd[1]); + close(epipefd[1]); + + mapp = mapent; + errp = errbuf; + state = st_space; + + pfd[0].fd = pipefd[0]; + pfd[0].events = POLLIN; + pfd[1].fd = epipefd[0]; + pfd[1].events = POLLIN; + + while (1) { + int bytes; + + if (poll(pfd, 2, -1) < 0 && errno != EINTR) + break; + + if (pfd[0].fd == -1 && pfd[1].fd == -1) + break; + + if ((pfd[0].revents & (POLLIN|POLLHUP)) == POLLHUP && + (pfd[1].revents & (POLLIN|POLLHUP)) == POLLHUP) + break; + + /* Parse maps from stdout */ + if (pfd[0].revents) { +cont: + bytes = read(pipefd[0], &ch, 1); + if (bytes == 0) + goto next; + else if (bytes < 0) { + pfd[0].fd = -1; + state = st_done; + goto next; + } + + if (!quoted && ch == '\\') { + quoted = 1; + goto cont; + } + + switch (state) { + case st_space: + if (quoted || !isspace(ch)) { + *mapp++ = ch; + state = st_map; + } + break; + case st_map: + if (!quoted && ch == '\n') { + *mapp = '\0'; + state = st_done; + break; + } + + /* We overwrite up to 3 characters, so we + * need to make sure we have enough room + * in the buffer for this. */ + /* else */ + if (mapp - mapent > + ((MAPENT_MAX_LEN+1) * alloci) - 3) { + /* + * Alloc another page for map entries. + */ + distance = mapp - mapent; + tmp = realloc(mapent, + ((MAPENT_MAX_LEN + 1) * + ++alloci)); + if (!tmp) { + alloci--; + logerr(MODPREFIX "realloc: %s", + strerror(errno)); + break; + } + mapent = tmp; + mapp = tmp + distance; + } + /* + * Eat \ quoting \n, otherwise pass it + * through for the parser + */ + if (quoted) { + if (ch == '\n') + *mapp++ = ' '; + else { + *mapp++ = '\\'; + *mapp++ = ch; + } + } else + *mapp++ = ch; + break; + case st_done: + /* Eat characters till there's no more output */ + break; + } + quoted = 0; + goto cont; + } + quoted = 0; +next: + /* Deal with stderr */ + if (pfd[1].revents) { + while (1) { + bytes = read(epipefd[0], &ch, 1); + if (bytes == 0) + break; + else if (bytes < 0) { + pfd[1].fd = -1; + break; + } else if (ch == '\n') { + *errp = '\0'; + if (errbuf[0]) + logmsg(">> %s", errbuf); + errp = errbuf; + } else { + if (errp >= &errbuf[1023]) { + *errp = '\0'; + logmsg(">> %s", errbuf); + errp = errbuf; + } + *(errp++) = ch; + } + } + } + } + + if (mapp) + *mapp = '\0'; + if (errp > errbuf) { + *errp = '\0'; + logmsg(">> %s", errbuf); + } + + close(pipefd[0]); + close(epipefd[0]); + + if (waitpid(f, &status, 0) != f) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "waitpid: %s", estr); + goto out_error; + } + + if (mapp == mapent || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + info(ap->logopt, MODPREFIX "lookup for %s failed", name); + goto out_error; + } + + return mapent; + +out_error: + if (mapent) + free(mapent); + + return NULL; +} + +static int lookup_amd_defaults(struct autofs_point *ap, + struct map_source *source, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc = source->mc; + char *ment = lookup_one(ap, "/defaults", 9, ctxt); + if (ment) { + char *start = ment + 9; + int ret; + + while (isblank(*start)) + start++; + cache_writelock(mc); + ret = cache_update(mc, source, "/defaults", start, monotonic_time(NULL)); + cache_unlock(mc); + if (ret == CHE_FAIL) { + free(ment); + return NSS_STATUS_UNAVAIL; + } + free(ment); + } + return NSS_STATUS_SUCCESS; +} + +static int match_key(struct autofs_point *ap, + struct map_source *source, + const char *name, int name_len, + char **mapent, struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + struct mapent_cache *mc = source->mc; + char buf[MAX_ERR_BUF]; + char *ment; + char *lkp_key; + size_t lkp_len; + char *prefix; + int ret; + + if (is_amd_format) { + ret = lookup_amd_defaults(ap, source, ctxt); + if (ret != NSS_STATUS_SUCCESS) { + warn(ap->logopt, + MODPREFIX "failed to save /defaults entry"); + } + } + + if (!is_amd_format) { + lkp_key = strdup(name); + lkp_len = name_len; + } else { + size_t len; + + if (ap->pref) + len = strlen(ap->pref) + strlen(name); + else + len = strlen(name); + + lkp_key = malloc(len + 1); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + if (ap->pref) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, name); + } else + strcpy(lkp_key, name); + + lkp_len = len; + } + + ment = lookup_one(ap, lkp_key, lkp_len, ctxt); + if (ment) { + char *start = ment; + if (is_amd_format) { + start = ment + lkp_len; + while (isblank(*start)) + start++; + } + cache_writelock(mc); + ret = cache_update(mc, source, lkp_key, start, monotonic_time(NULL)); + cache_unlock(mc); + if (ret == CHE_FAIL) { + free(ment); + free(lkp_key); + return NSS_STATUS_UNAVAIL; + } + *mapent = strdup(start); + if (!*mapent) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + free(lkp_key); + free(ment); + return NSS_STATUS_UNAVAIL; + } + free(lkp_key); + free(ment); + return NSS_STATUS_SUCCESS; + } + + if (!is_amd_format) { + free(lkp_key); + return NSS_STATUS_NOTFOUND; + } + + ret = NSS_STATUS_NOTFOUND; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + char *match; + size_t len; + *prefix = '\0'; + len = strlen(lkp_key) + 3; + match = malloc(len); + if (!match) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + free(lkp_key); + return NSS_STATUS_UNAVAIL; + } + len--; + strcpy(match, lkp_key); + strcat(match, "/*"); + ment = lookup_one(ap, match, len, ctxt); + if (ment) { + char *start = ment + len; + while (isblank(*start)) + start++; + cache_writelock(mc); + ret = cache_update(mc, source, match, start, monotonic_time(NULL)); + cache_unlock(mc); + if (ret == CHE_FAIL) { + free(match); + free(ment); + free(lkp_key); + return NSS_STATUS_UNAVAIL; + } + free(match); + *mapent = strdup(start); + if (!*mapent) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + free(ment); + free(lkp_key); + return NSS_STATUS_UNAVAIL; + } + free(ment); + free(lkp_key); + return NSS_STATUS_SUCCESS; + } + free(match); + } + free(lkp_key); + + return ret; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + char *mapent = NULL; + struct mapent *me; + int ret = 1; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, name, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, name); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, name); + } + cache_unlock(smc); + } + } + } + + /* Catch installed direct offset triggers */ + cache_readlock(mc); + me = cache_lookup_distinct(mc, name); + if (!me) { + cache_unlock(mc); + /* + * If there's a '/' in the name and the offset is not in + * the cache then it's not a valid path in the mount tree. + */ + if (strchr(name, '/')) { + debug(ap->logopt, + MODPREFIX "offset %s not found", name); + return NSS_STATUS_NOTFOUND; + } + } else { + /* Otherwise we found a valid offset so try mount it */ + debug(ap->logopt, MODPREFIX "%s -> %s", name, me->mapent); + + /* + * If this is a request for an offset mount (whose entry + * must be present in the cache to be valid) or the entry + * is newer than the negative timeout value then just + * try and mount it. Otherwise try and remove it and + * proceed with the program map lookup. + */ + if (strchr(name, '/') || + me->age + ap->negative_timeout > monotonic_time(NULL)) { + char *ent = NULL; + + if (me->mapent) { + ent = alloca(strlen(me->mapent) + 1); + strcpy(ent, me->mapent); + } + cache_unlock(mc); + master_source_current_wait(ap->entry); + ap->entry->current = source; + ret = ctxt->parse->parse_mount(ap, name, + name_len, ent, ctxt->parse->context); + goto out_free; + } else { + if (me->multi) { + cache_unlock(mc); + warn(ap->logopt, MODPREFIX + "unexpected lookup for active multi-mount" + " key %s, returning fail", name); + return NSS_STATUS_UNAVAIL; + } + cache_unlock(mc); + cache_writelock(mc); + me = cache_lookup_distinct(mc, name); + if (me) + cache_delete(mc, name); + cache_unlock(mc); + } + } + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + ret = match_key(ap, source, name, name_len, &mapent, ctxt); + if (ret != NSS_STATUS_SUCCESS) + goto out_free; + + debug(ap->logopt, MODPREFIX "%s -> %s", name, mapent); + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + ret = ctxt->parse->parse_mount(ap, name, name_len, + mapent, ctxt->parse->context); +out_free: + if (mapent) + free(mapent); + + if (ret) { + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, name, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + if (ctxt->mapfmt) + free(ctxt->mapfmt); + free(ctxt); + return rv; +} diff --git a/modules/lookup_sss.c b/modules/lookup_sss.c new file mode 100644 index 0000000..15a5ca4 --- /dev/null +++ b/modules/lookup_sss.c @@ -0,0 +1,850 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_sss.c - module for Linux automount to query sss service + * + * Copyright 2012 Ian Kent + * Copyright 2012 Red Hat, Inc. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" + +/* Half a second between retries */ +#define SETAUTOMOUNTENT_MASTER_INTERVAL 500000000 + +#define MODPREFIX "lookup(sss): " + +#define SSS_SO_NAME "libsss_autofs" + +int _sss_setautomntent(const char *, void **); +int _sss_getautomntent_r(char **, char **, void *); +int _sss_getautomntbyname_r(char *, char **, void *); +int _sss_endautomntent(void **); + +typedef int (*setautomntent_t) (const char *, void **); +typedef int (*getautomntent_t) (char **, char **, void *); +typedef int (*getautomntbyname_t) (char *, char **, void *); +typedef int (*endautomntent_t) (void **); + +struct lookup_context { + const char *mapname; + void *dlhandle; + setautomntent_t setautomntent; + getautomntent_t getautomntent_r; + getautomntbyname_t getautomntbyname_r; + endautomntent_t endautomntent; + struct parse_mod *parse; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static int open_sss_lib(struct lookup_context *ctxt) +{ + char dlbuf[PATH_MAX]; + char *estr; + void *dh; + size_t size; + + size = snprintf(dlbuf, sizeof(dlbuf), + "%s/%s.so", SSS_LIB_DIR, SSS_SO_NAME); + if (size >= sizeof(dlbuf)) { + logmsg(MODPREFIX "sss library path too long"); + return 1; + } + + dh = dlopen(dlbuf, RTLD_LAZY); + if (!dh) { + logerr(MODPREFIX "failed to open %s: %s", dlbuf, dlerror()); + return 1; + } + ctxt->dlhandle = dh; + + ctxt->setautomntent = (setautomntent_t) dlsym(dh, "_sss_setautomntent"); + if (!ctxt->setautomntent) + goto lib_names_fail; + + ctxt->getautomntent_r = (getautomntent_t) dlsym(dh, "_sss_getautomntent_r"); + if (!ctxt->getautomntent_r) + goto lib_names_fail; + + ctxt->getautomntbyname_r = (getautomntbyname_t) dlsym(dh, "_sss_getautomntbyname_r"); + if (!ctxt->getautomntbyname_r) + goto lib_names_fail; + + ctxt->endautomntent = (endautomntent_t) dlsym(dh, "_sss_endautomntent"); + if (!ctxt->setautomntent) + goto lib_names_fail; + + return 0; + +lib_names_fail: + if ((estr = dlerror()) == NULL) + logmsg(MODPREFIX "failed to locate sss library entry points"); + else + logerr(MODPREFIX "dlsym: %s", estr); + dlclose(dh); + + return 1; +} + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + int ret = 0; + + if (argc < 1) { + logerr(MODPREFIX "No map name"); + ret = 1; + goto out; + } + ctxt->mapname = argv[0]; + + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + if (!reinit) { + ret = open_sss_lib(ctxt); + if (ret) + goto out; + } + + if (reinit) { + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc - 1, argv + 1); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parse) { + logmsg(MODPREFIX "failed to open parse context"); + dlclose(ctxt->dlhandle); + ret = 1; + } + } +out: + return ret; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + char *estr; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + + if (do_init(mapfmt, argc, argv, ctxt, 0)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + new->dlhandle = ctxt->dlhandle; + new->setautomntent = ctxt->setautomntent; + new->getautomntent_r = ctxt->getautomntent_r; + new->getautomntbyname_r = ctxt->getautomntbyname_r; + new->endautomntent = ctxt->endautomntent; + + *context = new; + + free(ctxt); + + return 0; +} + +static int setautomntent(unsigned int logopt, + struct lookup_context *ctxt, const char *mapname, + void **sss_ctxt) +{ + int ret = ctxt->setautomntent(mapname, sss_ctxt); + if (ret) { + char buf[MAX_ERR_BUF]; + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "setautomntent: %s", estr); + if (*sss_ctxt) + free(*sss_ctxt); + } + return ret; +} + +static int setautomntent_wait(unsigned int logopt, + struct lookup_context *ctxt, + const char *mapname, + void **sss_ctxt, unsigned int retries) +{ + unsigned int retry = 0; + int ret = 0; + + *sss_ctxt = NULL; + + while (++retry < retries) { + struct timespec t = { 0, SETAUTOMOUNTENT_MASTER_INTERVAL }; + struct timespec r; + + ret = ctxt->setautomntent(mapname, sss_ctxt); + if (ret != ENOENT) + break; + + if (*sss_ctxt) { + free(*sss_ctxt); + *sss_ctxt = NULL; + } + + while (nanosleep(&t, &r) == -1 && errno == EINTR) + memcpy(&t, &r, sizeof(struct timespec)); + } + + + if (ret) { + char buf[MAX_ERR_BUF]; + char *estr; + + if (*sss_ctxt) { + free(*sss_ctxt); + *sss_ctxt = NULL; + } + + if (retry == retries) + ret = ETIMEDOUT; + + estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "setautomntent: %s", estr); + } + + return ret; +} + +static int endautomntent(unsigned int logopt, + struct lookup_context *ctxt, void **sss_ctxt) +{ + int ret = ctxt->endautomntent(sss_ctxt); + if (ret) { + char buf[MAX_ERR_BUF]; + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "endautomntent: %s", estr); + } + return ret; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + unsigned int timeout = master->default_timeout; + unsigned int logging = master->default_logging; + unsigned int logopt = master->logopt; + void *sss_ctxt = NULL; + char buf[MAX_ERR_BUF]; + char *buffer; + size_t buffer_len; + char *key; + char *value = NULL; + int count, ret; + + ret = setautomntent(logopt, ctxt, ctxt->mapname, &sss_ctxt); + if (ret) { + unsigned int retries; + + if (ret != ENOENT) + return NSS_STATUS_UNAVAIL; + + retries = defaults_get_sss_master_map_wait() * 2; + if (retries <= 0) + return NSS_STATUS_NOTFOUND; + + ret = setautomntent_wait(logopt, + ctxt, ctxt->mapname, &sss_ctxt, + retries); + if (ret) { + if (ret == ENOENT) + return NSS_STATUS_NOTFOUND; + return NSS_STATUS_UNAVAIL; + } + } + + count = 0; + while (1) { + key = NULL; + value = NULL; + ret = ctxt->getautomntent_r(&key, &value, sss_ctxt); + if (ret && ret != ENOENT) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "getautomntent_r: %s", estr); + endautomntent(logopt, ctxt, &sss_ctxt); + if (key) + free(key); + if (value) + free(value); + return NSS_STATUS_UNAVAIL; + } + if (ret == ENOENT) { + if (!count) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "getautomntent_r: %s", estr); + endautomntent(logopt, ctxt, &sss_ctxt); + if (key) + free(key); + if (value) + free(value); + return NSS_STATUS_NOTFOUND; + } + break; + } + count++; + + buffer_len = strlen(key) + 1 + strlen(value) + 2; + buffer = malloc(buffer_len); + if (!buffer) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "malloc: %s", estr); + endautomntent(logopt, ctxt, &sss_ctxt); + free(key); + free(value); + return NSS_STATUS_UNAVAIL; + } + + /* + * TODO: implement sun % hack for key translation for + * mixed case keys in schema that are single case only. + */ + + strcpy(buffer, key); + strcat(buffer, " "); + strcat(buffer, value); + + /* + * TODO: handle cancelation. This almost certainly isn't + * handled properly by other lookup modules either so it + * should be done when cancelation is reviewed for the + * other modules. Ditto for the other lookup module entry + * points. + */ + master_parse_entry(buffer, timeout, logging, age); + + free(buffer); + free(key); + free(value); + } + + endautomntent(logopt, ctxt, &sss_ctxt); + + return NSS_STATUS_SUCCESS; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + void *sss_ctxt = NULL; + char buf[MAX_ERR_BUF]; + char *key; + char *value = NULL; + char *s_key; + int count, ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + /* + * If we don't need to create directories (or don't need + * to read an amd cache:=all map) then there's no use + * reading the map. We always need to read the whole map + * for direct mounts in order to mount the triggers. + */ + if (ap->type != LKP_DIRECT && + !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) { + debug(ap->logopt, "map read not needed, so not done"); + return NSS_STATUS_SUCCESS; + } + + ret = setautomntent(ap->logopt, ctxt, ctxt->mapname, &sss_ctxt); + if (ret) { + if (ret == ENOENT) + return NSS_STATUS_NOTFOUND; + return NSS_STATUS_UNAVAIL; + } + + count = 0; + while (1) { + key = NULL; + value = NULL; + ret = ctxt->getautomntent_r(&key, &value, sss_ctxt); + if (ret && ret != ENOENT) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "getautomntent_r: %s", estr); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + if (key) + free(key); + if (value) + free(value); + return NSS_STATUS_UNAVAIL; + } + if (ret == ENOENT) { + if (!count) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "getautomntent_r: %s", estr); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + if (key) + free(key); + if (value) + free(value); + return NSS_STATUS_NOTFOUND; + } + break; + } + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*key == '+') { + warn(ap->logopt, + MODPREFIX "ignoring '+' map entry - not in file map"); + free(key); + free(value); + continue; + } + + if (*key == '/' && strlen(key) == 1) { + if (ap->type == LKP_DIRECT) { + free(key); + free(value); + continue; + } + *key = '*'; + } + + /* + * TODO: implement sun % hack for key translation for + * mixed case keys in schema that are single case only. + */ + + s_key = sanitize_path(key, strlen(key), ap->type, ap->logopt); + if (!s_key) { + error(ap->logopt, MODPREFIX "invalid path %s", key); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + free(key); + free(value); + return NSS_STATUS_NOTFOUND; + } + + count++; + + cache_writelock(mc); + cache_update(mc, source, s_key, value, age); + cache_unlock(mc); + + free(s_key); + free(key); + free(value); + } + + endautomntent(ap->logopt, ctxt, &sss_ctxt); + + source->age = age; + + return NSS_STATUS_SUCCESS; +} + +static int lookup_one(struct autofs_point *ap, + char *qKey, int qKey_len, struct lookup_context *ctxt) +{ + struct map_source *source; + struct mapent_cache *mc; + struct mapent *we; + void *sss_ctxt = NULL; + time_t age = monotonic_time(NULL); + char buf[MAX_ERR_BUF]; + char *value = NULL; + char *s_key; + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + ret = setautomntent(ap->logopt, ctxt, ctxt->mapname, &sss_ctxt); + if (ret) { + if (ret == ENOENT) + return NSS_STATUS_NOTFOUND; + return NSS_STATUS_UNAVAIL; + } + + ret = ctxt->getautomntbyname_r(qKey, &value, sss_ctxt); + if (ret && ret != ENOENT) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "getautomntbyname_r: %s", estr); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + if (value) + free(value); + return NSS_STATUS_UNAVAIL; + } + if (ret != ENOENT) { + /* + * TODO: implement sun % hack for key translation for + * mixed case keys in schema that are single case only. + */ + s_key = sanitize_path(qKey, qKey_len, ap->type, ap->logopt); + if (!s_key) { + free(value); + value = NULL; + goto wild; + } + cache_writelock(mc); + ret = cache_update(mc, source, s_key, value, age); + cache_unlock(mc); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + free(s_key); + free(value); + return NSS_STATUS_SUCCESS; + } + +wild: + ret = ctxt->getautomntbyname_r("/", &value, sss_ctxt); + if (ret && ret != ENOENT) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "getautomntbyname_r: %s", estr); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + if (value) + free(value); + return NSS_STATUS_UNAVAIL; + } + if (ret == ENOENT) { + ret = ctxt->getautomntbyname_r("*", &value, sss_ctxt); + if (ret && ret != ENOENT) { + char *estr = strerror_r(ret, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "getautomntbyname_r: %s", estr); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + if (value) + free(value); + return NSS_STATUS_UNAVAIL; + } + } + + if (ret == ENOENT) { + /* Failed to find wild entry, update cache if needed */ + cache_writelock(mc); + we = cache_lookup_distinct(mc, "*"); + if (we) { + /* Wildcard entry existed and is now gone */ + if (we->source == source) { + cache_delete(mc, "*"); + source->stale = 1; + } + } + + /* Not found in the map but found in the cache */ + struct mapent *exists = cache_lookup_distinct(mc, qKey); + if (exists && exists->source == source) { + if (exists->mapent) { + free(exists->mapent); + exists->mapent = NULL; + source->stale = 1; + exists->status = 0; + } + } + cache_unlock(mc); + endautomntent(ap->logopt, ctxt, &sss_ctxt); + return NSS_STATUS_NOTFOUND; + } + + cache_writelock(mc); + /* Wildcard not in map but now is */ + we = cache_lookup_distinct(mc, "*"); + if (!we) + source->stale = 1; + ret = cache_update(mc, source, "*", value, age); + cache_unlock(mc); + + endautomntent(ap->logopt, ctxt, &sss_ctxt); + free(value); + + return NSS_STATUS_SUCCESS; +} + +static int check_map_indirect(struct autofs_point *ap, + char *key, int key_len, + struct lookup_context *ctxt) +{ + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + time_t now = monotonic_time(NULL); + time_t t_last_read; + int ret, cur_state; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + ret = lookup_one(ap, key, key_len, ctxt); + if (ret == NSS_STATUS_NOTFOUND) { + pthread_setcancelstate(cur_state, NULL); + return ret; + } else if (ret == NSS_STATUS_UNAVAIL) { + /* + * If the server is down and the entry exists in the cache + * and belongs to this map return success and use the entry. + */ + struct mapent *exists = cache_lookup(mc, key); + if (exists && exists->source == source) { + pthread_setcancelstate(cur_state, NULL); + return NSS_STATUS_SUCCESS; + } + pthread_setcancelstate(cur_state, NULL); + + warn(ap->logopt, + MODPREFIX "lookup for %s failed: connection failed", key); + + return ret; + } + pthread_setcancelstate(cur_state, NULL); + + /* + * Check for map change and update as needed for + * following cache lookup. + */ + cache_readlock(mc); + t_last_read = ap->exp_runfreq + 1; + me = cache_lookup_first(mc); + while (me) { + if (me->source == source) { + t_last_read = now - me->age; + break; + } + me = cache_lookup_next(mc, me); + } + cache_unlock(mc); + + if (t_last_read > ap->exp_runfreq && ret & CHE_UPDATED) + source->stale = 1; + + cache_readlock(mc); + me = cache_lookup_distinct(mc, "*"); + if (ret == CHE_MISSING && (!me || me->source != source)) { + cache_unlock(mc); + return NSS_STATUS_NOTFOUND; + } + cache_unlock(mc); + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + char key[KEY_MAX_LEN + 1]; + int key_len; + char *mapent = NULL; + char mapent_buf[MAPENT_MAX_LEN + 1]; + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, key, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, key); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, key); + } + cache_unlock(smc); + } + } + } + + /* + * We can't check the direct mount map as if it's not in + * the map cache already we never get a mount lookup, so + * we never know about it. + */ + if (ap->type == LKP_INDIRECT && *key != '/') { + int status; + char *lkp_key; + + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me && me->multi) + lkp_key = strdup(me->multi->key); + else + lkp_key = strdup(key); + cache_unlock(mc); + + if (!lkp_key) + return NSS_STATUS_UNKNOWN; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + status = check_map_indirect(ap, lkp_key, strlen(lkp_key), ctxt); + free(lkp_key); + if (status) + return status; + } + + /* + * We can't take the writelock for direct mounts. If we're + * starting up or trying to re-connect to an existing direct + * mount we could be iterating through the map entries with + * the readlock held. But we don't need to update the cache + * when we're starting up so just take the readlock in that + */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + cache_writelock(mc); + else + cache_readlock(mc); + me = cache_lookup(mc, key); + /* Stale mapent => check for entry in alternate source or wildcard */ + if (me && !me->mapent) { + while ((me = cache_lookup_key_next(me))) + if (me->source == source) + break; + if (!me) + me = cache_lookup_distinct(mc, "*"); + } + if (me && me->mapent) { + /* + * If this is a lookup add wildcard match for later validation + * checks and negative cache lookups. + */ + if (ap->type == LKP_INDIRECT && *me->key == '*' && + !(ap->flags & MOUNT_FLAG_REMOUNT)) { + ret = cache_update(mc, source, key, me->mapent, me->age); + if (!(ret & (CHE_OK | CHE_UPDATED))) + me = NULL; + } + if (me && (me->source == source || *me->key == '/')) { + strcpy(mapent_buf, me->mapent); + mapent = mapent_buf; + } + } + cache_unlock(mc); + + if (!mapent) + return NSS_STATUS_TRYAGAIN; + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); + ret = ctxt->parse->parse_mount(ap, key, key_len, + mapent, ctxt->parse->context); + if (ret) { + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, key, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + dlclose(ctxt->dlhandle); + free(ctxt); + return rv; +} diff --git a/modules/lookup_userhome.c b/modules/lookup_userhome.c new file mode 100644 index 0000000..8117640 --- /dev/null +++ b/modules/lookup_userhome.c @@ -0,0 +1,109 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_userhome.c - module for Linux automount to generate symlinks + * to user home directories + * + * Copyright 1999 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MODPREFIX "lookup(userhome): " + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + return 0; /* Nothing to do */ +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + return 0; /* Nothing to do */ +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + return NSS_STATUS_UNKNOWN; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + return NSS_STATUS_UNKNOWN; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct map_source *source; + struct mapent_cache *mc; + struct passwd *pw; + char buf[MAX_ERR_BUF]; + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + /* Get the equivalent username */ + pw = getpwnam(name); + if (!pw) { + warn(ap->logopt, MODPREFIX "not found: %s", name); + return NSS_STATUS_NOTFOUND; /* Unknown user or error */ + } + + /* Create the appropriate symlink */ + if (chdir(ap->path)) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "chdir failed: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + cache_writelock(mc); + ret = cache_update(mc, source, name, NULL, monotonic_time(NULL)); + cache_unlock(mc); + + if (ret == CHE_FAIL) { + ret = chdir("/"); + return NSS_STATUS_UNAVAIL; + } + + if (symlink(pw->pw_dir, name) && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "symlink failed: %s", estr); + return NSS_STATUS_UNAVAIL; + } + + ret = chdir("/"); + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + return 0; +} diff --git a/modules/lookup_yp.c b/modules/lookup_yp.c new file mode 100644 index 0000000..b139b28 --- /dev/null +++ b/modules/lookup_yp.c @@ -0,0 +1,965 @@ +/* ----------------------------------------------------------------------- * + * + * lookup_yp.c - module for Linux automountd to access a YP (NIS) + * automount map + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2001-2003 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_LOOKUP +#include "automount.h" +#include "nsswitch.h" + +#define MAPFMT_DEFAULT "sun" + +#define MODPREFIX "lookup(yp): " + +struct lookup_context { + char *domainname; + const char *mapname; + unsigned long order; + unsigned int check_defaults; + struct parse_mod *parse; +}; + +struct callback_master_data { + unsigned timeout; + unsigned logging; + unsigned logopt; + time_t age; +}; + +struct callback_data { + struct autofs_point *ap; + struct map_source *source; + unsigned logopt; + time_t age; +}; + +int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ + +static unsigned int get_map_order(const char *domain, const char *map) +{ + char key[] = "YP_LAST_MODIFIED"; + int key_len = strlen(key); + char *order; + int order_len; + char *mapname; + long last_changed; + int err; + + mapname = alloca(strlen(map) + 1); + if (!mapname) + return 0; + + strcpy(mapname, map); + + err = yp_match(domain, mapname, key, key_len, &order, &order_len); + if (err != YPERR_SUCCESS) { + if (err == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + err = yp_match(domain, mapname, + key, key_len, &order, &order_len); + + if (err != YPERR_SUCCESS) + return 0; + + last_changed = atol(order); + free(order); + + return (unsigned int) last_changed; + } + return 0; + } + + last_changed = atol(order); + free(order); + + return (unsigned int) last_changed; +} + +static int do_init(const char *mapfmt, + int argc, const char *const *argv, + struct lookup_context *ctxt, unsigned int reinit) +{ + char buf[MAX_ERR_BUF]; + int err; + int ret = 0; + + if (argc < 1) { + logerr(MODPREFIX "no map name"); + ret = 1; + goto out; + } + ctxt->mapname = argv[0]; + ctxt->check_defaults = 1; + + if (mapfmt && !strcmp(mapfmt, "amd")) + ctxt->domainname = conf_amd_get_nis_domain(); + + if (!ctxt->domainname) { + char *domainname; + /* This should, but doesn't, take a const char ** */ + err = yp_get_default_domain(&domainname); + if (err) { + logerr(MODPREFIX + "map %s: %s", ctxt->mapname, yperr_string(err)); + ret = 1; + goto out; + } + ctxt->domainname = strdup(domainname); + if (!ctxt->domainname) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "strdup: %s", estr); + ret = 1; + goto out; + } + } + + ctxt->order = get_map_order(ctxt->domainname, ctxt->mapname); + + if (!mapfmt) + mapfmt = MAPFMT_DEFAULT; + + if (reinit) { + ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc - 1, argv + 1); + if (ret) + logmsg(MODPREFIX "failed to reinit parse context"); + } else { + ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); + if (!ctxt->parse) { + logmsg(MODPREFIX "failed to open parse context"); + ret = 1; + } + } +out: + if (ret && ctxt->domainname) + free(ctxt->domainname); + + return ret; +} + +int lookup_init(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + ctxt = malloc(sizeof(struct lookup_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(ctxt, 0, sizeof(struct lookup_context)); + + if (do_init(mapfmt, argc, argv, ctxt, 0)) { + free(ctxt); + return 1; + } + + *context = ctxt; + + return 0; +} + +int lookup_reinit(const char *mapfmt, + int argc, const char *const *argv, void **context) +{ + struct lookup_context *ctxt = (struct lookup_context *) *context; + struct lookup_context *new; + char buf[MAX_ERR_BUF]; + int ret; + + new = malloc(sizeof(struct lookup_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(new, 0, sizeof(struct lookup_context)); + + new->parse = ctxt->parse; + ret = do_init(mapfmt, argc, argv, new, 1); + if (ret) { + free(new); + return 1; + } + + *context = new; + + free(ctxt->domainname); + free(ctxt); + + return 0; +} + +int yp_all_master_callback(int status, char *ypkey, int ypkeylen, + char *val, int vallen, char *ypcb_data) +{ + struct callback_master_data *cbdata = + (struct callback_master_data *) ypcb_data; + unsigned int timeout = cbdata->timeout; + unsigned int logging = cbdata->logging; + unsigned int logopt = cbdata->logopt; + time_t age = cbdata->age; + char *buffer; + unsigned int len; + + if (status != YP_TRUE) + return status; + + /* Ignore zero length and single non-printable char keys */ + if (ypkeylen == 0 || (ypkeylen == 1 && !isprint(*ypkey))) { + warn(logopt, MODPREFIX + "ignoring invalid map entry, zero length or " + "single character non-printable key"); + return 0; + } + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*ypkey == '+') + return 0; + + *(ypkey + ypkeylen) = '\0'; + *(val + vallen) = '\0'; + + len = ypkeylen + 1 + vallen + 2; + + buffer = alloca(len); + if (!buffer) { + error(logopt, MODPREFIX "could not malloc parse buffer"); + return 0; + } + memset(buffer, 0, len); + + strcat(buffer, ypkey); + strcat(buffer, " "); + strcat(buffer, val); + + master_parse_entry(buffer, timeout, logging, age); + + return 0; +} + +int lookup_read_master(struct master *master, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct ypall_callback ypcb; + struct callback_master_data ypcb_data; + unsigned int logging = master->default_logging; + unsigned int logopt = master->logopt; + char *mapname; + int err; + + mapname = malloc(strlen(ctxt->mapname) + 1); + if (!mapname) + return NSS_STATUS_UNKNOWN; + + strcpy(mapname, ctxt->mapname); + + ypcb_data.timeout = master->default_timeout; + ypcb_data.logging = logging; + ypcb_data.logopt = logopt; + ypcb_data.age = age; + + ypcb.foreach = yp_all_master_callback; + ypcb.data = (char *) &ypcb_data; + + err = yp_all((char *) ctxt->domainname, mapname, &ypcb); + + if (err != YPERR_SUCCESS) { + if (err == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + err = yp_all((char *) ctxt->domainname, mapname, &ypcb); + } + + if (err == YPERR_SUCCESS) { + free(mapname); + return NSS_STATUS_SUCCESS; + } + + info(logopt, + MODPREFIX "read of master map %s failed: %s", + mapname, yperr_string(err)); + + free(mapname); + + if (err == YPERR_YPSERV || err == YPERR_DOMAIN) + return NSS_STATUS_UNAVAIL; + + return NSS_STATUS_NOTFOUND; + } + + free(mapname); + return NSS_STATUS_SUCCESS; +} + +int yp_all_callback(int status, char *ypkey, int ypkeylen, + char *val, int vallen, char *ypcb_data) +{ + struct callback_data *cbdata = (struct callback_data *) ypcb_data; + struct autofs_point *ap = cbdata->ap; + struct map_source *source = cbdata->source; + struct mapent_cache *mc = source->mc; + unsigned int logopt = cbdata->logopt; + time_t age = cbdata->age; + char *key, *mapent; + int ret; + + if (status != YP_TRUE) + return status; + + /* Ignore zero length and single non-printable char keys */ + if (ypkeylen == 0 || (ypkeylen == 1 && !isprint(*ypkey))) { + warn(logopt, MODPREFIX + "ignoring invalid map entry, zero length or " + "single character non-printable key"); + return 0; + } + + /* + * Ignore keys beginning with '+' as plus map + * inclusion is only valid in file maps. + */ + if (*ypkey == '+') + return 0; + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) + key = sanitize_path(ypkey, ypkeylen, ap->type, ap->logopt); + else + /* Don't fail on "/" in key => type == 0 */ + key = sanitize_path(ypkey, ypkeylen, 0, ap->logopt); + + if (!key) { + error(logopt, MODPREFIX "invalid path %s", ypkey); + return 0; + } + + mapent = alloca(vallen + 1); + strncpy(mapent, val, vallen); + *(mapent + vallen) = '\0'; + + cache_writelock(mc); + ret = cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + + free(key); + + if (ret == CHE_FAIL) + return -1; + + return 0; +} + +int lookup_read_map(struct autofs_point *ap, time_t age, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct ypall_callback ypcb; + struct callback_data ypcb_data; + unsigned int logopt = ap->logopt; + struct map_source *source; + char *mapname; + int err; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + /* + * If we don't need to create directories (or don't need + * to read an amd cache:=all map) then there's no use + * reading the map. We always need to read the whole map + * for direct mounts in order to mount the triggers. + */ + if (ap->type != LKP_DIRECT && + !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) { + debug(ap->logopt, "map read not needed, so not done"); + return NSS_STATUS_SUCCESS; + } + + ypcb_data.ap = ap; + ypcb_data.source = source; + ypcb_data.logopt = logopt; + ypcb_data.age = age; + + ypcb.foreach = yp_all_callback; + ypcb.data = (char *) &ypcb_data; + + mapname = alloca(strlen(ctxt->mapname) + 1); + if (!mapname) + return NSS_STATUS_UNKNOWN; + + strcpy(mapname, ctxt->mapname); + + err = yp_all((char *) ctxt->domainname, mapname, &ypcb); + + if (err != YPERR_SUCCESS) { + if (err == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + err = yp_all((char *) ctxt->domainname, mapname, &ypcb); + } + + if (err != YPERR_SUCCESS) { + warn(ap->logopt, + MODPREFIX "read of map %s failed: %s", + ap->path, yperr_string(err)); + + if (err == YPERR_PMAP || err == YPERR_YPSERV) + return NSS_STATUS_UNAVAIL; + + return NSS_STATUS_NOTFOUND; + } + } + + source->age = age; + pthread_mutex_lock(&ap->entry->current_mutex); + ctxt->check_defaults = 0; + pthread_mutex_unlock(&ap->entry->current_mutex); + + return NSS_STATUS_SUCCESS; +} + +static int lookup_one(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char *mapname; + char *mapent; + int mapent_len; + time_t age = monotonic_time(NULL); + int ret; + + mc = source->mc; + + mapname = alloca(strlen(ctxt->mapname) + 1); + if (!mapname) + return 0; + + strcpy(mapname, ctxt->mapname); + + /* + * For reasons unknown, the standard YP definitions doesn't + * define input strings as const char *. However, my + * understanding is that they will not be modified by the + * library. + */ + ret = yp_match((char *) ctxt->domainname, mapname, + (char *) key, key_len, &mapent, &mapent_len); + + if (ret != YPERR_SUCCESS) { + if (ret == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + ret = yp_match((char *) ctxt->domainname, + mapname, key, key_len, &mapent, &mapent_len); + } + + if (ret != YPERR_SUCCESS) { + if (ret == YPERR_KEY) + return CHE_MISSING; + + return -ret; + } + } + + cache_writelock(mc); + ret = cache_update(mc, source, key, mapent, age); + cache_unlock(mc); + free(mapent); + + return ret; +} + +static int match_key(struct autofs_point *ap, + struct map_source *source, + const char *key, int key_len, + struct lookup_context *ctxt) +{ + unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; + char buf[MAX_ERR_BUF]; + char *lkp_key; + char *prefix; + int ret; + + ret = lookup_one(ap, source, key, strlen(key), ctxt); + if (ret < 0) + return ret; + if (ret == CHE_OK || ret == CHE_UPDATED || !is_amd_format) + return ret; + + lkp_key = strdup(key); + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "strdup: %s", estr); + return CHE_FAIL; + } + + ret = CHE_MISSING; + + /* + * Now strip successive directory components and try a + * match against map entries ending with a wildcard and + * finally try the wilcard entry itself. + */ + while ((prefix = strrchr(lkp_key, '/'))) { + char *match; + size_t len; + *prefix = '\0'; + len = strlen(lkp_key) + 3; + match = malloc(len); + if (!match) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + ret = CHE_FAIL; + goto done; + } + len--; + strcpy(match, lkp_key); + strcat(match, "/*"); + ret = lookup_one(ap, source, match, len, ctxt); + free(match); + if (ret < 0) + goto done; + if (ret == CHE_OK || ret == CHE_UPDATED) + goto done; + } +done: + free(lkp_key); + return ret; +} + +static int lookup_wild(struct autofs_point *ap, + struct map_source *source, struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + char *mapname; + char *mapent; + int mapent_len; + time_t age = monotonic_time(NULL); + int ret; + + mc = source->mc; + + mapname = alloca(strlen(ctxt->mapname) + 1); + if (!mapname) + return 0; + + strcpy(mapname, ctxt->mapname); + + ret = yp_match((char *) ctxt->domainname, + mapname, "*", 1, &mapent, &mapent_len); + + if (ret != YPERR_SUCCESS) { + if (ret == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + ret = yp_match((char *) ctxt->domainname, + mapname, "*", 1, &mapent, &mapent_len); + } + + if (ret != YPERR_SUCCESS) { + if (ret == YPERR_KEY) + return CHE_MISSING; + + return -ret; + } + } + + cache_writelock(mc); + ret = cache_update(mc, source, "*", mapent, age); + cache_unlock(mc); + free(mapent); + + return ret; +} + +static int lookup_amd_defaults(struct autofs_point *ap, + struct map_source *source, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc = source->mc; + char *mapname; + char *mapent; + int mapent_len; + int ret; + + mapname = malloc(strlen(ctxt->mapname) + 1); + if (!mapname) + return 0; + + strcpy(mapname, ctxt->mapname); + + ret = yp_match((char *) ctxt->domainname, mapname, + (char *) "/defaults", 9, &mapent, &mapent_len); + + if (ret != YPERR_SUCCESS) { + if (ret == YPERR_MAP) { + char *usc; + + while ((usc = strchr(mapname, '_'))) + *usc = '.'; + + ret = yp_match((char *) ctxt->domainname, mapname, + "/defaults", 9, &mapent, &mapent_len); + } + } + free(mapname); + + /* No /defaults entry */ + if (ret == YPERR_KEY) + return CHE_OK; + + if (ret != YPERR_SUCCESS) + return CHE_FAIL; + + cache_writelock(mc); + ret = cache_update(mc, source, "/defaults", mapent, monotonic_time(NULL)); + cache_unlock(mc); + + return ret; +} + +static int check_map_indirect(struct autofs_point *ap, + struct map_source *source, + char *key, int key_len, + struct lookup_context *ctxt) +{ + struct mapent_cache *mc; + struct mapent *exists; + unsigned int map_order; + int ret = 0; + + mc = source->mc; + + /* Only read map if it has been modified */ + pthread_mutex_lock(&ap->entry->current_mutex); + map_order = get_map_order(ctxt->domainname, ctxt->mapname); + if (map_order > ctxt->order) { + ctxt->order = map_order; + source->stale = 1; + ctxt->check_defaults = 1; + } + + if (source->flags & MAP_FLAG_FORMAT_AMD && ctxt->check_defaults) { + /* Check for a /defaults entry to update the map source */ + if (lookup_amd_defaults(ap, source, ctxt) == CHE_FAIL) { + warn(ap->logopt, MODPREFIX + "error getting /defaults from map %s", + ctxt->mapname); + } else + ctxt->check_defaults = 0; + } + pthread_mutex_unlock(&ap->entry->current_mutex); + + /* check map and if change is detected re-read map */ + ret = match_key(ap, source, key, key_len, ctxt); + if (ret == CHE_FAIL) + return NSS_STATUS_NOTFOUND; + + if (ret < 0) { + /* + * If the server is down and the entry exists in the cache + * and belongs to this map return success and use the entry. + */ + cache_readlock(mc); + if (source->flags & MAP_FLAG_FORMAT_AMD) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup(mc, key); + if (exists && exists->source == source) { + cache_unlock(mc); + return NSS_STATUS_SUCCESS; + } + cache_unlock(mc); + + warn(ap->logopt, + MODPREFIX "lookup for %s failed: %s", + key, yperr_string(-ret)); + + return NSS_STATUS_UNAVAIL; + } + + cache_writelock(mc); + if (source->flags & MAP_FLAG_FORMAT_AMD) + exists = match_cached_key(ap, MODPREFIX, source, key); + else + exists = cache_lookup_distinct(mc, key); + /* Not found in the map but found in the cache */ + if (exists && exists->source == source && ret & CHE_MISSING) { + if (exists->mapent) { + free(exists->mapent); + exists->mapent = NULL; + source->stale = 1; + exists->status = 0; + } + } + cache_unlock(mc); + + if (ret == CHE_MISSING) { + struct mapent *we; + int wild = CHE_MISSING; + + wild = lookup_wild(ap, source, ctxt); + /* + * Check for map change and update as needed for + * following cache lookup. + */ + cache_writelock(mc); + we = cache_lookup_distinct(mc, "*"); + if (we) { + /* Wildcard entry existed and is now gone */ + if (we->source == source && (wild & CHE_MISSING)) { + cache_delete(mc, "*"); + source->stale = 1; + } + } else { + /* Wildcard not in map but now is */ + if (wild & (CHE_OK | CHE_UPDATED)) + source->stale = 1; + } + cache_unlock(mc); + + if (wild & (CHE_OK | CHE_UPDATED)) + return NSS_STATUS_SUCCESS; + } + + if (ret == CHE_MISSING) + return NSS_STATUS_NOTFOUND; + + return NSS_STATUS_SUCCESS; +} + +int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + struct map_source *source; + struct mapent_cache *mc; + char key[KEY_MAX_LEN + 1]; + int key_len; + char *lkp_key; + char *mapent = NULL; + int mapent_len; + struct mapent *me; + char buf[MAX_ERR_BUF]; + int status = 0; + int ret = 1; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + debug(ap->logopt, MODPREFIX "looking up %s", name); + + if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { + key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + } else { + key_len = expandamdent(name, NULL, NULL); + if (key_len > KEY_MAX_LEN) + return NSS_STATUS_NOTFOUND; + memset(key, 0, KEY_MAX_LEN + 1); + expandamdent(name, key, NULL); + debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); + } + + /* Check if we recorded a mount fail for this key anywhere */ + me = lookup_source_mapent(ap, key, LKP_DISTINCT); + if (me) { + if (me->status >= monotonic_time(NULL)) { + cache_unlock(me->mc); + return NSS_STATUS_NOTFOUND; + } else { + struct mapent_cache *smc = me->mc; + struct mapent *sme; + + if (me->mapent) + cache_unlock(smc); + else { + cache_unlock(smc); + cache_writelock(smc); + sme = cache_lookup_distinct(smc, key); + /* Negative timeout expired for non-existent entry. */ + if (sme && !sme->mapent) { + if (cache_pop_mapent(sme) == CHE_FAIL) + cache_delete(smc, key); + } + cache_unlock(smc); + } + } + } + + /* + * We can't check the direct mount map as if it's not in + * the map cache already we never get a mount lookup, so + * we never know about it. + */ + if (ap->type == LKP_INDIRECT && *key != '/') { + cache_readlock(mc); + me = cache_lookup_distinct(mc, key); + if (me && me->multi) + lkp_key = strdup(me->multi->key); + else if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + cache_unlock(mc); + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + return NSS_STATUS_UNKNOWN; + } + + status = check_map_indirect(ap, source, + lkp_key, strlen(lkp_key), ctxt); + free(lkp_key); + if (status) + return status; + } + + /* + * We can't take the writelock for direct mounts. If we're + * starting up or trying to re-connect to an existing direct + * mount we could be iterating through the map entries with + * the readlock held. But we don't need to update the cache + * when we're starting up so just take the readlock in that + * case. + */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + cache_readlock(mc); + else + cache_writelock(mc); + + if (!ap->pref) + lkp_key = strdup(key); + else { + lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); + if (lkp_key) { + strcpy(lkp_key, ap->pref); + strcat(lkp_key, key); + } + } + + if (!lkp_key) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + cache_unlock(mc); + return NSS_STATUS_UNKNOWN; + } + + me = match_cached_key(ap, MODPREFIX, source, lkp_key); + /* Stale mapent => check for entry in alternate source or wildcard */ + if (me && !me->mapent) { + while ((me = cache_lookup_key_next(me))) + if (me->source == source) + break; + if (!me) + me = cache_lookup_distinct(mc, "*"); + } + if (me && me->mapent) { + /* + * If this is a lookup add wildcard match for later validation + * checks and negative cache lookups. + */ + if (ap->type == LKP_INDIRECT && *me->key == '*' && + !(ap->flags & MOUNT_FLAG_REMOUNT)) { + ret = cache_update(mc, source, key, me->mapent, me->age); + if (!(ret & (CHE_OK | CHE_UPDATED))) + me = NULL; + } + if (me && (me->source == source || *me->key == '/')) { + mapent_len = strlen(me->mapent); + mapent = alloca(mapent_len + 1); + strcpy(mapent, me->mapent); + } + } + cache_unlock(mc); + free(lkp_key); + + if (mapent) { + master_source_current_wait(ap->entry); + ap->entry->current = source; + + debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); + ret = ctxt->parse->parse_mount(ap, key, key_len, + mapent, ctxt->parse->context); + if (ret) { + /* Don't update negative cache when re-connecting */ + if (ap->flags & MOUNT_FLAG_REMOUNT) + return NSS_STATUS_TRYAGAIN; + cache_writelock(mc); + cache_update_negative(mc, source, key, ap->negative_timeout); + cache_unlock(mc); + return NSS_STATUS_TRYAGAIN; + } + } + + if (ret) + return NSS_STATUS_TRYAGAIN; + + return NSS_STATUS_SUCCESS; +} + +int lookup_done(void *context) +{ + struct lookup_context *ctxt = (struct lookup_context *) context; + int rv = close_parse(ctxt->parse); + free(ctxt->domainname); + free(ctxt); + return rv; +} diff --git a/modules/mount_afs.c b/modules/mount_afs.c new file mode 100644 index 0000000..2a776bd --- /dev/null +++ b/modules/mount_afs.c @@ -0,0 +1,64 @@ +/* + * mount_afs.c + * + * Module for Linux automountd to "mount" AFS filesystems. We don't bother + * with any of the things "attach" would do (making sure there are tokens, + * subscribing to ops messages if Zephyr is installed), but it works for me. + * + */ + +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(afs): " +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +int mount_init(void **context) +{ + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, void *context) +{ + /* PATH_MAX is allegedly longest path allowed */ + char dest[PATH_MAX + 1]; + size_t r_len = strlen(root); + size_t d_len = r_len + name_len + 2; + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + if (d_len > PATH_MAX) + return 1; + + /* Convert the name to a mount point. */ + strcpy(dest, root); + strcat(dest, "/"); + strcat(dest, name); + + /* remove trailing slash (http://bugs.debian.org/141775) */ + if (dest[strlen(dest)-1] == '/') + dest[strlen(dest)-1] = '\0'; + + debug(ap->logopt, MODPREFIX "mounting AFS %s -> %s", dest, what); + + return symlink(what, dest); /* Try it. If it fails, return the error. */ +} + +int mount_done(void *context) +{ + return 0; +} diff --git a/modules/mount_autofs.c b/modules/mount_autofs.c new file mode 100644 index 0000000..076ab85 --- /dev/null +++ b/modules/mount_autofs.c @@ -0,0 +1,356 @@ +/* ----------------------------------------------------------------------- * + * + * mount_autofs.c - Module for recursive autofs mounts. + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2006 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(autofs): " + +/* Attribute to create detached thread */ +extern pthread_attr_t th_attr_detached; +extern struct startup_cond suc; + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +int mount_init(void **context) +{ + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, + int name_len, const char *what, const char *fstype, + const char *c_options, void *context) +{ + struct startup_cond suc; + pthread_t thid; + char realpath[PATH_MAX]; + char mountpoint[PATH_MAX]; + const char **argv; + int argc, status; + int nobind = ap->flags & MOUNT_FLAG_NOBIND; + int ghost = ap->flags & MOUNT_FLAG_GHOST; + int symlnk = ap->flags & MOUNT_FLAG_SYMLINK; + time_t timeout = get_exp_timeout(ap, ap->entry->maps); + unsigned logopt = ap->logopt; + struct map_type_info *info; + struct master *master; + struct master_mapent *entry; + struct map_source *source; + struct autofs_point *nap; + char buf[MAX_ERR_BUF]; + char *options, *p; + int len, ret; + int hosts = 0; + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + strcpy(realpath, ap->path); + strcat(realpath, "/"); + strcat(realpath, name); + len--; + strncpy(mountpoint, root, len); + mountpoint[len] = '\0'; + } else if (*name == '/') { + if (ap->flags & MOUNT_FLAG_REMOUNT) { + strcpy(mountpoint, name); + strcpy(realpath, name); + } else { + strcpy(mountpoint, root); + strcpy(realpath, name); + } + } else { + strcpy(mountpoint, root); + strcat(mountpoint, "/"); + strcpy(realpath, mountpoint); + strcat(mountpoint, name); + strcat(realpath, name); + } + + options = NULL; + if (c_options) { + char *noptions; + const char *comma; + char *np; + int len = strlen(c_options) + 1; + + noptions = np = alloca(len); + if (!np) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "alloca: %s", estr); + return 1; + } + memset(np, 0, len); + + /* Grab the autofs specific options */ + for (comma = c_options; *comma != '\0';) { + const char *cp; + + while (*comma == ',') + comma++; + + cp = comma; + + while (*comma != '\0' && *comma != ',') + comma++; + + if (_strncmp("nobrowse", cp, 8) == 0 || + _strncmp("nobrowsable", cp, 11) == 0) + ghost = 0; + else if (_strncmp("nobind", cp, 6) == 0) + nobind = 1; + else if (_strncmp("browse", cp, 6) == 0 || + _strncmp("browsable", cp, 9) == 0) + ghost = 1; + else if (_strncmp("symlink", cp, 7) == 0) + symlnk = 1; + else if (_strncmp("hosts", cp, 5) == 0) + hosts = 1; + else if (_strncmp("timeout=", cp, 8) == 0) { + char *val = strchr(cp, '='); + unsigned tout; + if (val) { + int ret = sscanf(cp, "timeout=%u", &tout); + if (ret) + timeout = tout; + } + } else { + memcpy(np, cp, comma - cp + 1); + np += comma - cp + 1; + } + } + options = noptions; + } + + debug(ap->logopt, + MODPREFIX "mountpoint=%s what=%s options=%s", + mountpoint, what, options); + + master = ap->entry->master; + + entry = master_new_mapent(master, realpath, ap->entry->age); + if (!entry) { + error(ap->logopt, + MODPREFIX "failed to malloc master_mapent struct"); + return 1; + } + + ret = master_add_autofs_point(entry, logopt, nobind, ghost, 1); + if (!ret) { + error(ap->logopt, + MODPREFIX "failed to add autofs_point to entry"); + master_free_mapent(entry); + return 1; + } + nap = entry->ap; + nap->parent = ap; + if (symlnk) + nap->flags |= MOUNT_FLAG_SYMLINK; + + if (hosts) + argc = 0; + else + argc = 1; + + if (options) { + char *t = options; + while ((t = strchr(t, ',')) != NULL) { + argc++; + if (*t == ',') + t++; + } + } + argv = (const char **) alloca((argc + 1) * sizeof(char *)); + + if (hosts) + argc = 0; + else + argc = 1; + + /* + * If a mount of a hosts map is being requested it will come + * ro us via the options. Catch that below when processing the + * option and create type info struct then. + */ + if (hosts) + info = parse_map_type_info("hosts:"); + else + info = parse_map_type_info(what); + if (!info) { + error(ap->logopt, MODPREFIX "failed to parse map info"); + master_free_mapent(entry); + return 1; + } + if (info->map) + argv[0] = info->map; + + if (options) { + p = options; + while ((p = strchr(p, ',')) != NULL) { + if (*p == ',') { + *p = '\0'; + p++; + } + argv[argc++] = p; + } + } + argv[argc] = NULL; + + /* + * For amd type "auto" the map is often re-used so check + * if the the parent map can be used and use it if it + * matches. + * + * Also if the parent map format is amd and the format + * isn't specified in the map entry set it from the parent + * map source. + */ + source = NULL; + if (ap->entry->maps && ap->entry->maps->flags & MAP_FLAG_FORMAT_AMD) { + struct map_source *s = ap->entry->maps; + + /* + * For amd maps, if the format and source type aren't + * specified try and set them from the parent. + */ + if (!info->format) { + info->format = strdup("amd"); + if (!info->format) + warn(ap->logopt, MODPREFIX + "failed to set amd map format"); + if (!info->type && s->type) { + info->type = strdup(s->type); + if (!info->type) + warn(ap->logopt, MODPREFIX + "failed to set amd map type"); + } + } + + source = master_get_map_source(ap->entry, + info->type, info->format, + argc, argv); + if (source) + entry->maps = source; + } + + if (!source) + source = master_add_map_source(entry, + info->type, info->format, + monotonic_time(NULL), + argc, argv); + if (!source) { + error(ap->logopt, + MODPREFIX "failed to add map source to entry"); + master_free_mapent(entry); + free_map_type_info(info); + return 1; + } + free_map_type_info(info); + + set_exp_timeout(nap, NULL, timeout); + nap->exp_runfreq = (timeout + CHECK_RATIO - 1) / CHECK_RATIO; + + mounts_mutex_lock(ap); + + if (source->flags & MAP_FLAG_FORMAT_AMD) { + struct amd_entry *am_entry = __master_find_amdmount(ap, entry->path); + + if (am_entry) { + if (am_entry->pref) { + nap->pref = am_entry->pref; + am_entry->pref = NULL; + } + + if (am_entry->cache_opts & AMD_CACHE_OPTION_ALL) + nap->flags |= MOUNT_FLAG_AMD_CACHE_ALL; + } + } + + if (handle_mounts_startup_cond_init(&suc)) { + crit(ap->logopt, MODPREFIX + "failed to init startup cond for mount %s", entry->path); + mounts_mutex_unlock(ap); + master_free_map_source(source, 1); + master_free_mapent(entry); + return 1; + } + + suc.ap = nap; + suc.root = mountpoint; + suc.done = 0; + suc.status = 0; + + if (pthread_create(&thid, &th_attr_detached, handle_mounts, &suc)) { + crit(ap->logopt, + MODPREFIX + "failed to create mount handler thread for %s", + realpath); + handle_mounts_startup_cond_destroy(&suc); + mounts_mutex_unlock(ap); + master_free_map_source(source, 1); + master_free_mapent(entry); + return 1; + } + + while (!suc.done) { + status = pthread_cond_wait(&suc.cond, &suc.mutex); + if (status) { + handle_mounts_startup_cond_destroy(&suc); + mounts_mutex_unlock(ap); + master_free_map_source(source, 1); + master_free_mapent(entry); + fatal(status); + } + } + + if (suc.status) { + crit(ap->logopt, + MODPREFIX "failed to create submount for %s", realpath); + handle_mounts_startup_cond_destroy(&suc); + mounts_mutex_unlock(ap); + master_free_map_source(source, 1); + master_free_mapent(entry); + return 1; + } + nap->thid = thid; + + ap->submnt_count++; + list_add(&nap->mounts, &ap->submounts); + + handle_mounts_startup_cond_destroy(&suc); + mounts_mutex_unlock(ap); + + return 0; +} + +int mount_done(void *context) +{ + return 0; +} diff --git a/modules/mount_bind.c b/modules/mount_bind.c new file mode 100644 index 0000000..4864ea5 --- /dev/null +++ b/modules/mount_bind.c @@ -0,0 +1,244 @@ +/* ----------------------------------------------------------------------- * + * + * mount_bind.c - module to mount a local filesystem if possible; + * otherwise create a symlink. + * + * Copyright 2000 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(bind): " + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +static int bind_works = 0; + +int mount_init(void **context) +{ + char tmp1[] = "/tmp/autoXXXXXX", *t1_dir; + char tmp2[] = "/tmp/autoXXXXXX", *t2_dir; + int err; + struct stat st1, st2; + + t1_dir = mkdtemp(tmp1); + t2_dir = mkdtemp(tmp2); + if (t1_dir == NULL || t2_dir == NULL) { + if (t1_dir) + rmdir(t1_dir); + if (t2_dir) + rmdir(t2_dir); + return 0; + } + + if (lstat(t1_dir, &st1) == -1) + goto out; + + err = spawn_mount(LOGOPT_NONE, "-n", "--bind", t1_dir, t2_dir, NULL); + if (err == 0 && + lstat(t2_dir, &st2) == 0 && + st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) { + bind_works = 1; + } + + if (spawn_umount(LOGOPT_NONE, "-n", t2_dir, NULL) != 0) + debug(LOGOPT_ANY, MODPREFIX "umount failed for %s", t2_dir); + +out: + rmdir(t1_dir); + rmdir(t2_dir); + + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, void *context) +{ + char fullpath[PATH_MAX]; + char buf[MAX_ERR_BUF]; + int err; + int i, len; + int symlnk = (*name != '/' && (ap->flags & MOUNT_FLAG_SYMLINK)); + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + /* Extract "symlink" pseudo-option which forces local filesystems + * to be symlinked instead of bound. + */ + if (*name != '/' && !symlnk && options) { + const char *comma; + int o_len = strlen(options) + 1; + + for (comma = options; *comma != '\0';) { + const char *cp; + const char *end; + + while (*comma == ',') + comma++; + + /* Skip leading white space */ + while (*comma == ' ' || *comma == '\t') + comma++; + + cp = comma; + while (*comma != '\0' && *comma != ',') + comma++; + + /* Skip trailing white space */ + end = comma - 1; + while (*comma == ' ' || *comma == '\t') + end--; + + o_len = end - cp + 1; + if (_strncmp("symlink", cp, o_len) == 0) + symlnk = 1; + } + } + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + len = snprintf(fullpath, len, "%s", root); + } else if (*name == '/') { + /* + * Direct or offset mount, name is absolute path so + * don't use root (but with move mount changes root + * is now the same as name). + */ + len = sprintf(fullpath, "%s", root); + } else { + len = sprintf(fullpath, "%s/%s", root, name); + } + fullpath[len] = '\0'; + + i = len; + while (--i > 0 && fullpath[i] == '/') + fullpath[i] = '\0'; + + if (options == NULL || *options == '\0') + options = "defaults"; + + if (!strcmp(what, fullpath)) { + debug(ap->logopt, MODPREFIX + "cannot mount or symlink %s to itself", fullpath); + return 1; + } + + if (!symlnk && bind_works) { + int status, existed = 1; + + debug(ap->logopt, MODPREFIX "calling mkdir_path %s", fullpath); + + status = mkdir_path(fullpath, 0555); + if (status && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", + fullpath, estr); + return 1; + } + + if (!status) + existed = 0; + + debug(ap->logopt, MODPREFIX + "calling mount --bind -o %s %s %s", + options, what, fullpath); + + err = spawn_bind_mount(ap->logopt, "-o", + options, what, fullpath, NULL); + + if (err) { + if (ap->type != LKP_INDIRECT) + return 1; + + if (!existed && + (!(ap->flags & MOUNT_FLAG_GHOST) && name_len)) + rmdir_path(ap, fullpath, ap->dev); + + return err; + } else { + debug(ap->logopt, + MODPREFIX "mounted %s type %s on %s", + what, fstype, fullpath); + return 0; + } + } else { + char *cp; + char basepath[PATH_MAX]; + int status; + struct stat st; + + strcpy(basepath, fullpath); + cp = strrchr(basepath, '/'); + + if (cp != NULL && cp != basepath) + *cp = '\0'; + + if ((status = stat(fullpath, &st)) == 0) { + if (S_ISDIR(st.st_mode)) + rmdir(fullpath); + } else { + debug(ap->logopt, + MODPREFIX "calling mkdir_path %s", basepath); + if (mkdir_path(basepath, 0555) && errno != EEXIST) { + char *estr; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", + basepath, estr); + return 1; + } + } + + if (symlink(what, fullpath) && errno != EEXIST) { + error(ap->logopt, + MODPREFIX + "failed to create symlink %s -> %s", + fullpath, what); + if ((ap->flags & MOUNT_FLAG_GHOST) && !status) { + if (mkdir_path(fullpath, 0555) && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", + fullpath, estr); + } + } else { + if (ap->type == LKP_INDIRECT) + rmdir_path(ap, fullpath, ap->dev); + } + return 1; + } else { + debug(ap->logopt, + MODPREFIX "symlinked %s -> %s", fullpath, what); + return 0; + } + } +} + +int mount_done(void *context) +{ + return 0; +} diff --git a/modules/mount_changer.c b/modules/mount_changer.c new file mode 100644 index 0000000..798f23b --- /dev/null +++ b/modules/mount_changer.c @@ -0,0 +1,191 @@ +/* ----------------------------------------------------------------------- * + * + * mount_changer.c - module for Linux automountd to mount filesystems + * from cd changers + * + * Copyright 1999 Toby Jaffey - All Rights Reserved + * CD swapping code from linux kernel in Documentation/cdrom/ide-cd + * Based on code originally from Gerhard Zuber . + * Changer status information, and rewrite for the new Uniform CDROM driver + * interface by Erik Andersen . + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(changer): " + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +int swapCD(const char *device, const char *slotName); + +int mount_init(void **context) +{ + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, void *context) +{ + char fullpath[PATH_MAX]; + char buf[MAX_ERR_BUF]; + int err; + int len, status, existed = 1; + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + fstype = "iso9660"; + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + len = snprintf(fullpath, len, "%s", root); + } else if (*name == '/') { + /* + * Direct or offset mount, name is absolute path so + * don't use root (but with move mount changes root + * is now the same as name). + */ + len = sprintf(fullpath, "%s", root); + } else { + len = sprintf(fullpath, "%s/%s", root, name); + } + fullpath[len] = '\0'; + + debug(ap->logopt, MODPREFIX "calling umount %s", what); + + err = spawn_umount(ap->logopt, what, NULL); + if (err) { + error(ap->logopt, + MODPREFIX + "umount of %s failed (all may be unmounted)", + what); + } + + debug(ap->logopt, MODPREFIX "calling mkdir_path %s", fullpath); + + status = mkdir_path(fullpath, 0555); + if (status && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", fullpath, estr); + return 1; + } + + if (!status) + existed = 0; + + debug(ap->logopt, MODPREFIX "Swapping CD to slot %s", name); + + err = swapCD(what, name); + if (err) { + error(ap->logopt, + MODPREFIX "failed to swap CD to slot %s", name); + return 1; + } + + if (options && options[0]) { + debug(ap->logopt, MODPREFIX + "calling mount -t %s -o %s %s %s", + fstype, options, what, fullpath); + + err = spawn_mount(ap->logopt, "-t", fstype, + "-o", options, what, fullpath, NULL); + } else { + debug(ap->logopt, + MODPREFIX "calling mount -t %s %s %s", + fstype, what, fullpath); + + err = spawn_mount(ap->logopt, "-t", fstype, what, fullpath, NULL); + } + + if (err) { + info(ap->logopt, MODPREFIX "failed to mount %s (type %s) on %s", + what, fstype, fullpath); + + if (ap->type != LKP_INDIRECT) + return 1; + + if ((!(ap->flags & MOUNT_FLAG_GHOST) && name_len) || !existed) + rmdir_path(ap, fullpath, ap->dev); + + return 1; + } else { + debug(ap->logopt, MODPREFIX "mounted %s type %s on %s", + what, fstype, fullpath); + return 0; + } +} + +int mount_done(void *context) +{ + return 0; +} + +int swapCD(const char *device, const char *slotName) +{ + int fd; /* file descriptor for CD-ROM device */ + int status; /* return status for system calls */ + int slot = -1; + int total_slots_available; + + slot = atoi(slotName) - 1; + + /* open device */ + fd = open_fd(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + logerr(MODPREFIX "Opening device %s failed : %s", + device, strerror(errno)); + return 1; + } + + /* Check CD player status */ + total_slots_available = ioctl(fd, CDROM_CHANGER_NSLOTS); + if (total_slots_available <= 1) { + logerr(MODPREFIX + "Device %s is not an ATAPI compliant CD changer.", + device); + close(fd); + return 1; + } + + /* load */ + slot = ioctl(fd, CDROM_SELECT_DISC, slot); + if (slot < 0) { + logerr(MODPREFIX "CDROM_SELECT_DISC failed"); + close(fd); + return 1; + } + + /* close device */ + status = close(fd); + if (status != 0) { + logerr(MODPREFIX "close failed for `%s': %s", + device, strerror(errno)); + return 1; + } + return 0; +} diff --git a/modules/mount_ext2.c b/modules/mount_ext2.c new file mode 100644 index 0000000..90fc087 --- /dev/null +++ b/modules/mount_ext2.c @@ -0,0 +1,158 @@ +/* ----------------------------------------------------------------------- * + * + * mount_ext2.c - module for Linux automountd to mount ext2 filesystems + * after running fsck on them. + * + * Copyright 1998 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(ext2): " + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +int mount_init(void **context) +{ + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, void *context) +{ + char fullpath[PATH_MAX]; + char buf[MAX_ERR_BUF]; + const char *p, *p1; + int err, ro = 0; + const char *fsck_prog; + int len, status, existed = 1; + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + len = snprintf(fullpath, len, "%s", root); + } else if (*name == '/') { + /* + * Direct or offset mount, name is absolute path so + * don't use root (but with move mount changes root + * is now the same as name). + */ + len = sprintf(fullpath, "%s", root); + } else { + len = sprintf(fullpath, "%s/%s", root, name); + } + fullpath[len] = '\0'; + + debug(ap->logopt, MODPREFIX "calling mkdir_path %s", fullpath); + + status = mkdir_path(fullpath, 0555); + if (status && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", fullpath, estr); + return 1; + } + + if (!status) + existed = 0; + + if (options && options[0]) { + for (p = options; (p1 = strchr(p, ',')); p = p1) + if (!_strncmp("ro", p, p1 - p) && ++p1 - p == sizeof("ro")) + ro = 1; + if (!strcmp(p, "ro")) + ro = 1; + } + + fsck_prog = PATH_E2FSCK; +#ifdef HAVE_E3FSCK + if (!strcmp(fstype,"ext3")) + fsck_prog = PATH_E3FSCK; +#endif +#ifdef HAVE_E4FSCK + if (!strcmp(fstype,"ext4")) + fsck_prog = PATH_E4FSCK; +#endif + if (ro) { + debug(ap->logopt, + MODPREFIX "calling %s -n %s", fsck_prog, what); + err = spawnl(ap->logopt, fsck_prog, fsck_prog, "-n", what, NULL); + } else { + debug(ap->logopt, + MODPREFIX "calling %s -p %s", fsck_prog, what); + err = spawnl(ap->logopt, fsck_prog, fsck_prog, "-p", what, NULL); + } + + /* + * spawnl returns the error code, left shifted by 8 bits. We are + * interested in the following error bits from the fsck program: + * 2 - File system errors corrected, system should be rebooted + * 4 - File system errors left uncorrected + */ + if ((err >> 8) & 6) { + error(ap->logopt, + MODPREFIX "%s: filesystem needs repair, won't mount", + what); + return 1; + } + + if (options) { + debug(ap->logopt, MODPREFIX + "calling mount -t %s -o %s %s %s", + fstype, options, what, fullpath); + err = spawn_mount(ap->logopt, "-t", fstype, + "-o", options, what, fullpath, NULL); + } else { + debug(ap->logopt, + MODPREFIX "calling mount -t %s %s %s", + fstype, what, fullpath); + err = spawn_mount(ap->logopt, "-t", fstype, what, fullpath, NULL); + } + + if (err) { + info(ap->logopt, MODPREFIX "failed to mount %s (type %s) on %s", + what, fstype, fullpath); + + if (ap->type != LKP_INDIRECT) + return 1; + + if ((!(ap->flags & MOUNT_FLAG_GHOST) && name_len) || !existed) + rmdir_path(ap, fullpath, ap->dev); + + return 1; + } else { + debug(ap->logopt, + MODPREFIX "mounted %s type %s on %s", + what, fstype, fullpath); + return 0; + } +} + +int mount_done(void *context) +{ + return 0; +} diff --git a/modules/mount_generic.c b/modules/mount_generic.c new file mode 100644 index 0000000..ae63787 --- /dev/null +++ b/modules/mount_generic.c @@ -0,0 +1,116 @@ +/* ----------------------------------------------------------------------- * + * + * mount_generic.c - module for Linux automountd to mount filesystems + * for which no special magic is required + * + * Copyright 1997-1999 Transmeta Corporation - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" + +#define MODPREFIX "mount(generic): " + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +int mount_init(void **context) +{ + return 0; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, + void *context) +{ + char fullpath[PATH_MAX]; + char buf[MAX_ERR_BUF]; + int err; + int len, status, existed = 1; + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + len = snprintf(fullpath, len, "%s", root); + } else if (*name == '/') { + /* + * Direct or offset mount, name is absolute path so + * don't use root (but with move mount changes root + * is now the same as name). + */ + len = sprintf(fullpath, "%s", root); + } else { + len = sprintf(fullpath, "%s/%s", root, name); + } + fullpath[len] = '\0'; + + debug(ap->logopt, MODPREFIX "calling mkdir_path %s", fullpath); + + status = mkdir_path(fullpath, 0555); + if (status && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", fullpath, estr); + return 1; + } + + if (!status) + existed = 0; + + if (options && options[0]) { + debug(ap->logopt, + MODPREFIX "calling mount -t %s -o %s %s %s", + fstype, options, what, fullpath); + + err = spawn_mount(ap->logopt, "-t", fstype, + "-o", options, what, fullpath, NULL); + } else { + debug(ap->logopt, MODPREFIX "calling mount -t %s %s %s", + fstype, what, fullpath); + err = spawn_mount(ap->logopt, "-t", fstype, what, fullpath, NULL); + } + + if (err) { + info(ap->logopt, MODPREFIX "failed to mount %s (type %s) on %s", + what, fstype, fullpath); + + if (ap->type != LKP_INDIRECT) + return 1; + + if ((!(ap->flags & MOUNT_FLAG_GHOST) && name_len) || !existed) + rmdir_path(ap, fullpath, ap->dev); + + return 1; + } else { + debug(ap->logopt, MODPREFIX "mounted %s type %s on %s", + what, fstype, fullpath); + return 0; + } +} + +int mount_done(void *context) +{ + return 0; +} diff --git a/modules/mount_nfs.c b/modules/mount_nfs.c new file mode 100644 index 0000000..c558a73 --- /dev/null +++ b/modules/mount_nfs.c @@ -0,0 +1,418 @@ +/* ----------------------------------------------------------------------- * + * + * mount_nfs.c - Module for Linux automountd to mount an NFS filesystem, + * with fallback to symlinking if the path is local + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_MOUNT +#include "automount.h" +#include "replicated.h" + +#define MODPREFIX "mount(nfs): " + +int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */ + +static struct mount_mod *mount_bind = NULL; +static int init_ctr = 0; + +int mount_init(void **context) +{ + /* Make sure we have the local mount method available */ + if (!mount_bind) { + if ((mount_bind = open_mount("bind", MODPREFIX))) + init_ctr++; + } else + init_ctr++; + + seed_random(); + + return !mount_bind; +} + +int mount_reinit(void **context) +{ + return 0; +} + +int mount_mount(struct autofs_point *ap, const char *root, const char *name, int name_len, + const char *what, const char *fstype, const char *options, + void *context) +{ + char fullpath[PATH_MAX]; + char buf[MAX_ERR_BUF]; + struct host *this, *hosts = NULL; + unsigned int mount_default_proto, vers; + char *nfsoptions = NULL; + const char *port_opt = NULL; + unsigned int flags = ap->flags & + (MOUNT_FLAG_RANDOM_SELECT | MOUNT_FLAG_USE_WEIGHT_ONLY); + int nobind = ap->flags & MOUNT_FLAG_NOBIND; + int len, status, err, existed = 1; + int nosymlink = 0; + int port = -1; + int ro = 0; /* Set if mount bind should be read-only */ + int rdma = 0; + + if (ap->flags & MOUNT_FLAG_REMOUNT) + return 0; + + debug(ap->logopt, + MODPREFIX "root=%s name=%s what=%s, fstype=%s, options=%s", + root, name, what, fstype, options); + + mount_default_proto = defaults_get_mount_nfs_default_proto(); + vers = NFS_VERS_MASK | NFS_PROTO_MASK; + if (strcmp(fstype, "nfs4") == 0) + vers = NFS4_VERS_MASK | TCP_SUPPORTED; + else if (mount_default_proto == 4) + vers = vers | NFS4_VERS_MASK; + + /* Extract "nosymlink" pseudo-option which stops local filesystems + * from being symlinked. + * + * "nosymlink" is not used anymore. It is left for compatibility + * only (so we don't choke on it). + */ + if (options) { + const char *comma; + char *nfsp; + int o_len = strlen(options) + 1; + + nfsp = nfsoptions = alloca(o_len + 1); + if (!nfsoptions) + return 1; + + memset(nfsoptions, '\0', o_len + 1); + + for (comma = options; *comma != '\0';) { + const char *cp; + const char *end; + + while (*comma == ',') + comma++; + + /* Skip leading white space */ + while (*comma == ' ' || *comma == '\t') + comma++; + + cp = comma; + while (*comma != '\0' && *comma != ',') + comma++; + + /* Skip trailing white space */ + end = comma - 1; + while (*comma == ' ' || *comma == '\t') + end--; + + o_len = end - cp + 1; + + if (_strncmp("proto=rdma", cp, o_len) == 0 || + _strncmp("rdma", cp, o_len) == 0) + rdma = 1; + + if (_strncmp("nosymlink", cp, o_len) == 0) { + warn(ap->logopt, MODPREFIX + "the \"nosymlink\" option is depricated " + "and will soon be removed, " + "use the \"nobind\" option instead"); + nosymlink = 1; + } else if (_strncmp("nobind", cp, o_len) == 0) { + nobind = 1; + } else if (_strncmp("no-use-weight-only", cp, o_len) == 0) { + flags &= ~MOUNT_FLAG_USE_WEIGHT_ONLY; + } else if (_strncmp("use-weight-only", cp, o_len) == 0) { + flags |= MOUNT_FLAG_USE_WEIGHT_ONLY; + } else { + if (_strncmp("vers=4", cp, o_len) == 0 || + _strncmp("nfsvers=4", cp, o_len) == 0) + vers = NFS4_VERS_MASK | TCP_SUPPORTED; + else if (_strncmp("vers=3", cp, o_len) == 0 || + _strncmp("nfsvers=3", cp, o_len) == 0) { + vers &= ~(NFS4_VERS_MASK | NFS_VERS_MASK); + vers |= NFS3_REQUESTED; + } else if (_strncmp("vers=2", cp, o_len) == 0 || + _strncmp("nfsvers=2", cp, o_len) == 0) { + vers &= ~(NFS4_VERS_MASK | NFS_VERS_MASK); + vers |= NFS2_REQUESTED; + } else if (strstr(cp, "port=") == cp && + o_len - 5 < 25) { + char optport[25]; + + strncpy(optport, cp + 5, o_len - 5); + optport[o_len - 5] = '\0'; + port = atoi(optport); + if (port < 0) + port = 0; + port_opt = cp; + } else if (_strncmp("proto=udp", cp, o_len) == 0 || + _strncmp("udp", cp, o_len) == 0) { + vers &= ~TCP_SUPPORTED; + } else if (_strncmp("proto=tcp", cp, o_len) == 0 || + _strncmp("tcp", cp, o_len) == 0) { + vers &= ~UDP_SUPPORTED; + } + /* Check for options that also make sense + with bind mounts */ + else if (_strncmp("ro", cp, o_len) == 0) + ro = 1; + else if (_strncmp("rw", cp, o_len) == 0) + ro = 0; + /* and jump over trailing white space */ + memcpy(nfsp, cp, comma - cp + 1); + nfsp += comma - cp + 1; + } + } + + /* In case both tcp and udp options were given */ + if ((vers & NFS_PROTO_MASK) == 0) + vers |= NFS_PROTO_MASK; + + debug(ap->logopt, MODPREFIX + "nfs options=\"%s\", nobind=%d, nosymlink=%d, ro=%d", + nfsoptions, nobind, nosymlink, ro); + } + + if (!parse_location(ap->logopt, &hosts, what, flags)) { + info(ap->logopt, MODPREFIX "no hosts available"); + return 1; + } + /* + * We can't probe protocol rdma so leave it to mount.nfs(8) + * and and suffer the delay if a server isn't available. + */ + if (rdma) + goto dont_probe; + + /* + * If this is a singleton mount, and NFSv4 only hasn't been asked + * for, and the default NFS protocol is set to v4 in the autofs + * configuration only probe NFSv4 and let mount.nfs(8) do fallback + * to NFSv3 (if it can). If the NFSv4 probe fails then probe as + * normal. + */ + if ((hosts && !hosts->next) && + mount_default_proto == 4 && + (vers & NFS_VERS_MASK) != 0 && + (vers & NFS4_VERS_MASK) != 0) { + unsigned int v4_probe_ok = 0; + struct host *tmp = new_host(hosts->name, + hosts->addr, hosts->addr_len, + hosts->proximity, + hosts->weight, hosts->options); + if (tmp) { + tmp->rr = hosts->rr; + prune_host_list(ap->logopt, &tmp, + NFS4_VERS_MASK|TCP_SUPPORTED, port); + /* If probe succeeds just try the mount with host in hosts */ + if (tmp) { + v4_probe_ok = 1; + free_host_list(&tmp); + } + } + if (!v4_probe_ok) + prune_host_list(ap->logopt, &hosts, vers, port); + } else { + prune_host_list(ap->logopt, &hosts, vers, port); + } + +dont_probe: + if (!hosts) { + info(ap->logopt, MODPREFIX "no hosts available"); + return 1; + } + + /* Construct and perhaps create mount point directory */ + + /* Root offset of multi-mount */ + len = strlen(root); + if (root[len - 1] == '/') { + len = snprintf(fullpath, len, "%s", root); + } else if (*name == '/') { + len = sprintf(fullpath, "%s", root); + } else { + len = sprintf(fullpath, "%s/%s", root, name); + } + fullpath[len] = '\0'; + + debug(ap->logopt, MODPREFIX "calling mkdir_path %s", fullpath); + + status = mkdir_path(fullpath, 0555); + if (status && errno != EEXIST) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "mkdir_path %s failed: %s", fullpath, estr); + free_host_list(&hosts); + return 1; + } + + if (!status) + existed = 0; + + /* + * If any *port= option is specified, then we don't want + * a bind mount. Use the "port" option if you want to + * avoid attempting a local bind mount, such as when + * tunneling NFS via localhost. + */ + if (nfsoptions && *nfsoptions && !port_opt) + port_opt = strstr(nfsoptions, "port="); + + this = hosts; + while (this) { + char *loc; + + /* Port option specified, don't try to bind */ + if (!(nosymlink || nobind) && + !port_opt && this->proximity == PROXIMITY_LOCAL) { + /* Local host -- do a "bind" */ + const char *bind_options = ro ? "ro" : ""; + + debug(ap->logopt, + MODPREFIX "%s is local, attempt bind mount", + name); + + err = mount_bind->mount_mount(ap, root, name, name_len, + this->path, "bind", bind_options, + mount_bind->context); + + /* Success - we're done */ + if (!err) { + free_host_list(&hosts); + return 0; + } + + /* Failed to update mtab, don't try any more */ + if (err == MNT_FORCE_FAIL) + goto forced_fail; + + /* No hostname, can't be NFS */ + if (!this->name) { + this = this->next; + continue; + } + } + + /* Not a local host - do an NFS mount */ + + if (this->rr && this->addr && + !defaults_use_hostname_for_mounts()) { + socklen_t len = INET6_ADDRSTRLEN; + char n_buf[len + 1]; + const char *n_addr; + n_addr = get_addr_string(this->addr, n_buf, len); + loc = malloc(strlen(n_addr) + strlen(this->path) + 4); + if (!loc) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + goto forced_fail; + } + if (this->addr->sa_family == AF_INET6) { + strcpy(loc, "["); + strcat(loc, n_addr); + strcat(loc, "]"); + } else + strcpy(loc, n_addr); + } else { + char *host = this->name ? this->name : "localhost"; + loc = malloc(strlen(host) + strlen(this->path) + 2); + if (!loc) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, "malloc: %s", estr); + goto forced_fail; + } + strcpy(loc, host); + } + strcat(loc, ":"); + strcat(loc, this->path); + + /* If this is a fallback from a bind mount failure + * check if the local NFS server is available to try + * and prevent lengthy mount failure waits. + */ + if (this->proximity == PROXIMITY_LOCAL) { + char *host = this->name ? this->name : "localhost"; + int ret; + + ret = rpc_ping(host, 2, 0, RPC_CLOSE_DEFAULT); + if (ret <= 0) + goto next; + } + + if (nfsoptions && *nfsoptions) { + debug(ap->logopt, + MODPREFIX "calling mount -t %s " SLOPPY + "-o %s %s %s", fstype, nfsoptions, loc, fullpath); + + err = spawn_mount(ap->logopt, + "-t", fstype, SLOPPYOPT "-o", + nfsoptions, loc, fullpath, NULL); + } else { + debug(ap->logopt, + MODPREFIX "calling mount -t %s %s %s", + fstype, loc, fullpath); + err = spawn_mount(ap->logopt, + "-t", fstype, loc, fullpath, NULL); + } + + if (!err) { + debug(ap->logopt, MODPREFIX "mounted %s on %s", loc, fullpath); + free(loc); + free_host_list(&hosts); + return 0; + } +next: + free(loc); + this = this->next; + } + +forced_fail: + free_host_list(&hosts); + + /* If we get here we've failed to complete the mount */ + + info(ap->logopt, MODPREFIX "nfs: mount failure %s on %s", what, fullpath); + + if (ap->type != LKP_INDIRECT) + return 1; + + if ((!(ap->flags & MOUNT_FLAG_GHOST) && name_len) || !existed) + rmdir_path(ap, fullpath, ap->dev); + + return 1; +} + +int mount_done(void *context) +{ + int rv = 0; + + if (--init_ctr == 0) { + rv = close_mount(mount_bind); + mount_bind = NULL; + } + return rv; +} diff --git a/modules/parse_amd.c b/modules/parse_amd.c new file mode 100644 index 0000000..2ff43fa --- /dev/null +++ b/modules/parse_amd.c @@ -0,0 +1,2025 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2013 Ian Kent + * Copyright 2013 Red Hat, Inc. + * All rights reserved. + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_PARSE +#include "automount.h" +#include "nsswitch.h" + +#define MODPREFIX "parse(amd): " + +int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */ + +static struct mount_mod *mount_nfs = NULL; +static int init_ctr = 0; +static pthread_mutex_t instance_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void instance_mutex_lock(void) +{ + int status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); +} + +static void instance_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); +} + +extern const char *global_options; + +struct parse_context { + char *optstr; /* Mount options */ + char *macros; /* Map wide macro defines */ + struct substvar *subst; /* $-substitutions */ +}; + +struct multi_mnt { + char *path; + char *options; + char *location; + struct multi_mnt *next; +}; + +/* Default context */ + +static struct parse_context default_context = { + NULL, /* No mount options */ + NULL, /* No map wide macros */ + NULL /* The substvar local vars table */ +}; + +/* Free all storage associated with this context */ +static void kill_context(struct parse_context *ctxt) +{ + macro_lock(); + macro_free_table(ctxt->subst); + macro_unlock(); + if (ctxt->optstr) + free(ctxt->optstr); + if (ctxt->macros) + free(ctxt->macros); + free(ctxt); +} + +int parse_init(int argc, const char *const *argv, void **context) +{ + struct parse_context *ctxt; + char buf[MAX_ERR_BUF]; + + sel_hash_init(); + + /* Set up context and escape chain */ + + if (!(ctxt = (struct parse_context *) malloc(sizeof(struct parse_context)))) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + *context = NULL; + return 1; + } + *context = (void *) ctxt; + + *ctxt = default_context; + + /* We only need this once. NFS mounts are so common that we cache + this module. */ + instance_mutex_lock(); + if (mount_nfs) + init_ctr++; + else { + if ((mount_nfs = open_mount("nfs", MODPREFIX))) { + init_ctr++; + } else { + kill_context(ctxt); + *context = NULL; + instance_mutex_unlock(); + return 1; + } + } + instance_mutex_unlock(); + + return 0; +} + +int parse_reinit(int argc, const char *const *argv, void **context) +{ + return 0; +} + +static struct substvar *add_lookup_vars(struct autofs_point *ap, + const char *key, int key_len, + struct map_source *source, + struct substvar *sv) +{ + struct substvar *list = sv; + struct thread_stdenv_vars *tsv; + char lkp_key[PATH_MAX + 1]; + char path[PATH_MAX + 1]; + struct mapent *me; + int len; + + len = strlen(ap->path) + 1 + key_len + 1; + if (len > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: lookup key is greater than PATH_MAX"); + return NULL; + } + + if (ap->pref) { + if (snprintf(lkp_key, sizeof(lkp_key), "%s%s", + ap->pref, key) >= sizeof(lkp_key)) { + error(ap->logopt, MODPREFIX "key too long"); + return NULL; + } + } else { + if (snprintf(lkp_key, sizeof(lkp_key), "%s", + key) >= sizeof(lkp_key)) { + error(ap->logopt, MODPREFIX "key too long"); + return NULL; + } + } + + if (*key == '/') + strcpy(path, key); + else { + strcpy(path, ap->path); + strcat(path, "/"); + strcat(path, key); + } + list = macro_addvar(list, "path", 4, path); + + me = cache_lookup_distinct(source->mc, lkp_key); + if (me) + list = macro_addvar(list, "key", 3, me->key); + + while (!me) { + char match[PATH_MAX + 1]; + char *prefix; + + strcpy(match, lkp_key); + while ((prefix = strrchr(match, '/'))) { + *prefix = '\0'; + me = cache_partial_match_wild(source->mc, match); + if (me) { + list = macro_addvar(list, "key", 3, lkp_key); + break; + } + } + + if (!me) { + me = cache_lookup_distinct(source->mc, "*"); + if (me) + list = macro_addvar(list, "key", 3, lkp_key); + } + + break; + } + + if (source->name) + list = macro_addvar(list, "map", 3, source->name); + else if (source->argv[0][0]) + list = macro_addvar(list, "map", 3, source->argv[0]); + + tsv = pthread_getspecific(key_thread_stdenv_vars); + if (tsv) { + char numbuf[16]; + long num; + int ret; + + num = (long) tsv->uid; + ret = sprintf(numbuf, "%ld", num); + if (ret > 0) + list = macro_addvar(list, "uid", 3, numbuf); + num = (long) tsv->gid; + ret = sprintf(numbuf, "%ld", num); + if (ret > 0) + list = macro_addvar(list, "gid", 3, numbuf); + } + + list = macro_addvar(list, "fs", 2, "${autodir}/${rhost}${rfs}"); + list = macro_addvar(list, "rfs", 3, path); + + return list; +} + +static int match_my_name(unsigned int logopt, const char *name, struct substvar *sv) +{ + struct addrinfo hints, *cni, *ni, *haddr; + char host[NI_MAXHOST + 1], numeric[NI_MAXHOST + 1]; + const struct substvar *v; + int rv = 0, ret; + + v = macro_findvar(sv, "host", 4); + if (v) { + if (!strcmp(v->val, name)) + return 1; + } + + if (!v || !v->val) { + error(logopt, "error: ${host} not set"); + goto out; + } + + /* Check if comparison value is an alias */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + /* Get host canonical name */ + ret = getaddrinfo(v->val, NULL, &hints, &cni); + if (ret) { + error(logopt, + "hostname lookup failed: %s\n", gai_strerror(ret)); + goto out; + } + + hints.ai_flags = 0; + + /* Resolve comparison name to its names and compare */ + ret = getaddrinfo(name, NULL, &hints, &ni); + if (ret) { + error(logopt, + "hostname lookup failed: %s\n", gai_strerror(ret)); + freeaddrinfo(cni); + goto out; + } + + haddr = ni; + while (haddr) { + /* Translate the host address into a numeric string form */ + ret = getnameinfo(haddr->ai_addr, haddr->ai_addrlen, + numeric, sizeof(numeric), NULL, 0, + NI_NUMERICHOST); + if (ret) { + error(logopt, + "host address info lookup failed: %s\n", + gai_strerror(ret)); + goto next; + } + + /* Try to resolve back again to get the canonical name */ + ret = getnameinfo(haddr->ai_addr, haddr->ai_addrlen, + host, NI_MAXHOST, NULL, 0, 0); + if (ret) { + error(logopt, + "host address info lookup failed: %s\n", + gai_strerror(ret)); + goto next; + } + + if (!strcmp(host, cni->ai_canonname)) { + rv = 1; + break; + } +next: + haddr = haddr->ai_next; + } + freeaddrinfo(ni); + freeaddrinfo(cni); +out: + return rv; +} + +static int eval_selector(unsigned int logopt, + struct amd_entry *this, struct substvar *sv) +{ + struct selector *s = this->selector; + const struct substvar *v; + unsigned int s_type; + unsigned int v_type; + struct stat st; + char *host; + int res, val, ret = 0; + + s_type = s->sel->flags & SEL_FLAGS_TYPE_MASK; + + switch (s_type) { + case SEL_FLAG_MACRO: + v = macro_findvar(sv, s->sel->name, strlen(s->sel->name)); + if (!v) { + error(logopt, "failed to get selector %s", s->sel->name); + return 0; + } + + v_type = s->sel->flags & SEL_FLAGS_VALUE_MASK; + + switch (v_type) { + case SEL_FLAG_STR: + res = strcmp(v->val, s->comp.value); + if (s->compare & SEL_COMP_EQUAL && !res) { + debug(logopt, MODPREFIX + "matched selector %s(%s) == %s", + v->def, v->val, s->comp.value); + ret = 1; + break; + } else if (s->compare & SEL_COMP_NOTEQUAL && res) { + debug(logopt, MODPREFIX + "matched selector %s(%s) != %s", + v->def, v->val, s->comp.value); + ret = 1; + break; + } + + debug(logopt, MODPREFIX + "did not match selector %s(%s) %s %s", + v->def, v->val, + (s->compare & SEL_COMP_EQUAL ? "==" : "!="), + s->comp.value); + break; + + case SEL_FLAG_NUM: + if (!*s->comp.value) { + res = 1; + val = 0; + } else { + res = atoi(v->val); + val = atoi(s->comp.value); + } + if (s->compare & SEL_COMP_EQUAL && res == val) { + debug(logopt, MODPREFIX + "matched selector %s(%s) equal to %s", + v->def, v->val, s->comp.value); + ret = 1; + break; + } else if (s->compare & SEL_COMP_NOTEQUAL && res != val) { + debug(logopt, MODPREFIX + "matched selector %s(%s) not equal to %s", + v->def, v->val, s->comp.value); + ret = 1; + break; + } + + debug(logopt, MODPREFIX + "did not match selector %s(%s) %s %s", + v->def, v->val, + (s->compare & SEL_COMP_EQUAL ? "==" : "!="), + s->comp.value); + break; + + default: + break; + } + break; + + case SEL_FLAG_FUNC1: + if (s->sel->selector != SEL_TRUE && + s->sel->selector != SEL_FALSE && + !s->func.arg1) { + error(logopt, MODPREFIX + "expected argument missing for selector %s", + s->sel->name); + break; + } + + switch (s->sel->selector) { + case SEL_TRUE: + ret = 1; + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) + debug(logopt, MODPREFIX + "matched selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s)", + s->sel->name, s->func.arg1); + break; + + case SEL_FALSE: + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) + debug(logopt, MODPREFIX + "matched selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s)", + s->sel->name, s->func.arg1); + break; + + case SEL_XHOST: + ret = match_my_name(logopt, s->func.arg1, sv); + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) + debug(logopt, MODPREFIX + "matched selector %s(%s) to host name", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s) to host name", + s->sel->name, s->func.arg1); + break; + + case SEL_EXISTS: + /* Sould be OK to fail on any error here */ + ret = !lstat(s->func.arg1, &st); + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) + debug(logopt, MODPREFIX + "matched selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s)", + s->sel->name, s->func.arg1); + break; + + case SEL_IN_NETWORK: + ret = in_network(s->func.arg1); + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) + debug(logopt, MODPREFIX + "matched selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s)", + s->sel->name, s->func.arg1); + break; + + default: + break; + } + break; + + case SEL_FLAG_FUNC2: + if (!s->func.arg1) { + error(logopt, MODPREFIX + "expected argument missing for selector %s", + s->sel->name); + break; + } + + switch (s->sel->selector) { + case SEL_NETGRP: + case SEL_NETGRPD: + if (s->func.arg2) + host = s->func.arg2; + else { + if (s->sel->selector == SEL_NETGRP) + v = macro_findvar(sv, "host", 4); + else + v = macro_findvar(sv, "hostd", 5); + if (!v || !*v->val) { + error(logopt, + "failed to get value of ${host}"); + break; + } + host = v->val; + } + ret = innetgr(s->func.arg1, host, NULL, NULL); + if (s->compare == SEL_COMP_NOT) + ret = !ret; + if (ret) { + if (!s->func.arg2) + debug(logopt, MODPREFIX + "matched selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "matched selector %s(%s,%s)", + s->sel->name, s->func.arg1, + s->func.arg2); + } else { + if (!s->func.arg2) + debug(logopt, MODPREFIX + "did not match selector %s(%s)", + s->sel->name, s->func.arg1); + else + debug(logopt, MODPREFIX + "did not match selector %s(%s,%s)", + s->sel->name, s->func.arg1, s->func.arg2); + } + break; + + default: + break; + } + break; + + default: + break; + } + + return ret; +} + +static void update_with_defaults(struct amd_entry *defaults, + struct amd_entry *entry, + struct substvar *sv) +{ + const struct substvar *v; + unsigned long fstype = entry->flags & AMD_MOUNT_TYPE_MASK; + char *tmp; + + if (fstype == AMD_MOUNT_TYPE_NONE) { + unsigned long deftype = defaults->flags & AMD_MOUNT_TYPE_MASK; + if (deftype != AMD_MOUNT_TYPE_NONE) + entry->flags |= (defaults->flags & AMD_MOUNT_TYPE_MASK); + else { + entry->flags = AMD_MOUNT_TYPE_NFS; + tmp = strdup("nfs"); + if (tmp) + entry->type = tmp; + } + } + + if (!entry->type && defaults->type) { + tmp = strdup(defaults->type); + if (tmp) + entry->type = tmp; + } + + if (!entry->map_type && defaults->map_type) { + tmp = strdup(defaults->map_type); + if (tmp) + entry->map_type = tmp; + } + + if (!entry->pref && defaults->pref) { + tmp = strdup(defaults->pref); + if (tmp) + entry->pref = tmp; + } + + if (!entry->fs) { + if (defaults->fs) { + tmp = strdup(defaults->fs); + if (tmp) + entry->fs = tmp; + } else { + v = macro_findvar(sv, "fs", 2); + if (v) + entry->fs = strdup(v->val); + } + } + + if (!entry->rfs) { + if (defaults->rfs) { + tmp = strdup(defaults->rfs); + if (tmp) + entry->rfs = tmp; + } else { + v = macro_findvar(sv, "rfs", 3); + if (v) + entry->rfs = strdup(v->val); + } + } + + if (!entry->rhost) { + if (defaults->rhost) { + tmp = strdup(defaults->rhost); + if (tmp) + entry->rhost = tmp; + } else { + v = macro_findvar(sv, "host", 4); + if (v) + entry->rhost = strdup(v->val); + } + } + + if (!entry->dev && defaults->dev) { + tmp = strdup(defaults->dev); + if (tmp) + entry->dev = tmp; + } + + if (!entry->opts && defaults->opts) { + tmp = merge_options(defaults->opts, entry->opts); + if (tmp) + entry->opts = tmp; + } + + if (!entry->addopts && defaults->addopts) { + tmp = merge_options(defaults->addopts, entry->addopts); + if (tmp) + entry->addopts = tmp; + } + + if (!entry->remopts) { + if (defaults->remopts) { + tmp = strdup(defaults->remopts); + if (tmp) + entry->remopts = tmp; + } else { + v = macro_findvar(sv, "remopts", 7); + if (v) + entry->remopts = strdup(v->val); + } + } + + return; +} + +static char *normalize_hostname(unsigned int logopt, const char *host, + unsigned int flags, struct substvar *sv) +{ + struct addrinfo hints, *ni; + char *name; + int ret; + + if (!(flags & CONF_NORMALIZE_HOSTNAMES)) + name = strdup(host); + else { + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + ret = getaddrinfo(host, NULL, &hints, &ni); + if (ret) { + error(logopt, "hostname lookup failed: %s", gai_strerror(ret)); + return NULL; + } + name = strdup(ni->ai_canonname); + freeaddrinfo(ni); + } + + if (!name) + return NULL; + + if (flags & CONF_DOMAIN_STRIP) { + const struct substvar *v = macro_findvar(sv, "hostd", 5); + if (v) { + char *d1 = strchr(name, '.'); + if (d1) { + char *d2 = strchr(v->val, '.'); + if (d2 && !strcmp(d1, d2)) + *d1 = '\0'; + } + } + } + + return name; +} + +static struct substvar *expand_entry(struct autofs_point *ap, + struct amd_entry *entry, + unsigned int flags, + struct substvar *sv) +{ + unsigned int logopt = ap->logopt; + char *expand; + + if (entry->rhost && *entry->rhost) { + char *host = strdup(entry->rhost); + char *nn; + if (!host) { + error(ap->logopt, MODPREFIX + "failed to allocate storage for rhost"); + goto next; + } + if (expand_selectors(ap, host, &expand, sv)) { + free(host); + host = expand; + } + nn = normalize_hostname(ap->logopt, host, flags, sv); + if (!nn) + sv = macro_addvar(sv, "rhost", 5, host); + else { + sv = macro_addvar(sv, "rhost", 5, nn); + free(host); + host = nn; + } + debug(logopt, MODPREFIX + "rhost expand(\"%s\") -> %s", entry->rhost, host); + free(entry->rhost); + entry->rhost = host; + } +next: + if (entry->sublink) { + if (expand_selectors(ap, entry->sublink, &expand, sv)) { + debug(logopt, MODPREFIX + "sublink expand(\"%s\") -> %s", + entry->sublink, expand); + free(entry->sublink); + entry->sublink = expand; + } + sv = macro_addvar(sv, "sublink", 7, entry->sublink); + } + + if (entry->rfs && *entry->rfs) { + if (expand_selectors(ap, entry->rfs, &expand, sv)) { + debug(logopt, MODPREFIX + "rfs expand(\"%s\") -> %s", entry->rfs, expand); + free(entry->rfs); + entry->rfs = expand; + } + sv = macro_addvar(sv, "rfs", 3, entry->rfs); + } + + if (entry->fs && *entry->fs) { + if (expand_selectors(ap, entry->fs, &expand, sv)) { + debug(logopt, MODPREFIX + "fs expand(\"%s\") -> %s", entry->fs, expand); + free(entry->fs); + entry->fs = expand; + } + sv = macro_addvar(sv, "fs", 2, entry->fs); + } + + if (entry->opts && *entry->opts) { + if (expand_selectors(ap, entry->opts, &expand, sv)) { + debug(logopt, MODPREFIX + "ops expand(\"%s\") -> %s", entry->opts, expand); + free(entry->opts); + entry->opts = expand; + } + sv = macro_addvar(sv, "opts", 4, entry->opts); + } + + if (entry->addopts && *entry->addopts) { + if (expand_selectors(ap, entry->addopts, &expand, sv)) { + debug(logopt, MODPREFIX + "addopts expand(\"%s\") -> %s", + entry->addopts, expand); + free(entry->addopts); + entry->addopts = expand; + } + sv = macro_addvar(sv, "addopts", 7, entry->addopts); + } + + if (entry->remopts && *entry->remopts) { + if (expand_selectors(ap, entry->remopts, &expand, sv)) { + debug(logopt, MODPREFIX + "remopts expand(\"%s\") -> %s", + entry->remopts, expand); + free(entry->remopts); + entry->remopts = expand; + } + sv = macro_addvar(sv, "remopts", 7, entry->remopts); + } + + return sv; +} + +static void expand_merge_options(struct autofs_point *ap, + struct amd_entry *entry, + struct substvar *sv) +{ + char *tmp; + + if (entry->opts && *entry->opts) { + if (!expand_selectors(ap, entry->opts, &tmp, sv)) + error(ap->logopt, MODPREFIX "failed to expand opts"); + else { + free(entry->opts); + entry->opts = tmp; + } + } + + if (entry->addopts && *entry->addopts) { + if (!expand_selectors(ap, entry->addopts, &tmp, sv)) + error(ap->logopt, MODPREFIX "failed to expand addopts"); + else { + free(entry->addopts); + entry->addopts = tmp; + } + } + + if (entry->remopts && *entry->remopts) { + if (!expand_selectors(ap, entry->remopts, &tmp, sv)) + error(ap->logopt, MODPREFIX "failed to expand remopts"); + else { + free(entry->remopts); + entry->remopts = tmp; + } + } + + return; +} + +static struct substvar *merge_entry_options(struct autofs_point *ap, + struct amd_entry *entry, + struct substvar *sv) +{ + char *tmp; + + if (!entry->addopts) + return sv; + + if (entry->opts && entry->remopts && + !strcmp(entry->opts, entry->remopts)) { + expand_merge_options(ap, entry, sv); + tmp = merge_options(entry->opts, entry->addopts); + if (tmp) { + info(ap->logopt, MODPREFIX + "merge remopts \"%s\" addopts \"%s\" => \"%s\"", + entry->opts, entry->addopts, tmp); + free(entry->opts); + entry->opts = tmp; + sv = macro_addvar(sv, "opts", 4, entry->opts); + } + if (*entry->opts) { + tmp = strdup(entry->opts); + if (tmp) { + free(entry->remopts); + entry->remopts = tmp; + sv = macro_addvar(sv, "remopts", 7, entry->remopts); + } + } + return sv; + } + + expand_merge_options(ap, entry, sv); + + if (entry->opts && entry->addopts) { + tmp = merge_options(entry->opts, entry->addopts); + if (tmp) { + info(ap->logopt, MODPREFIX + "merge opts \"%s\" addopts \"%s\" => \"%s\"", + entry->opts, entry->addopts, tmp); + free(entry->opts); + entry->opts = tmp; + sv = macro_addvar(sv, "opts", 4, entry->opts); + } + } else if (entry->addopts && *entry->addopts) { + tmp = strdup(entry->addopts); + if (tmp) { + info(ap->logopt, MODPREFIX + "opts add addopts \"%s\" => \"%s\"", entry->addopts, tmp); + entry->opts = tmp; + sv = macro_addvar(sv, "opts", 4, entry->opts); + } + } + + expand_merge_options(ap, entry, sv); + + if (entry->remopts && entry->addopts) { + tmp = merge_options(entry->remopts, entry->addopts); + if (tmp) { + info(ap->logopt, MODPREFIX + "merge remopts \"%s\" addopts \"%s\" => \"%s\"", + entry->remopts, entry->addopts, tmp); + free(entry->remopts); + entry->remopts = tmp; + sv = macro_addvar(sv, "remopts", 7, entry->remopts); + } + } else if (entry->addopts && *entry->addopts) { + tmp = strdup(entry->addopts); + if (tmp) { + info(ap->logopt, MODPREFIX + "remopts add addopts \"%s\" => \"%s\"", + entry->addopts, tmp); + entry->remopts = tmp; + sv = macro_addvar(sv, "remopts", 7, entry->remopts); + } + } + + return sv; +} + +static int do_auto_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, unsigned int flags) +{ + char target[PATH_MAX + 1]; + + if (!entry->map_type) { + if (strlen(entry->fs) > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: fs option length is too long"); + return 0; + } + strcpy(target, entry->fs); + } else { + if (strlen(entry->fs) + + strlen(entry->map_type) + 5 > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: fs + maptype options length is too long"); + return 0; + } + strcpy(target, entry->map_type); + strcat(target, ",amd:"); + strcat(target, entry->fs); + } + + return do_mount(ap, ap->path, + name, strlen(name), target, "autofs", entry->opts); +} + +static int do_link_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, unsigned int flags) +{ + char target[PATH_MAX + 1]; + const char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL; + int ret; + + if (entry->sublink) { + if (strlen(entry->sublink) > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: sublink option length is too long"); + return 0; + } + strcpy(target, entry->sublink); + } else { + if (strlen(entry->fs) > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: fs option length is too long"); + return 0; + } + strcpy(target, entry->fs); + } + + if (!(flags & CONF_AUTOFS_USE_LOFS)) + goto symlink; + + /* For a sublink this might cause an external mount */ + ret = do_mount(ap, ap->path, + name, strlen(name), target, "bind", opts); + if (!ret) + goto out; + + debug(ap->logopt, MODPREFIX "bind mount failed, symlinking"); + +symlink: + ret = do_mount(ap, ap->path, + name, strlen(name), target, "bind", "symlink"); + if (!ret) + goto out; + + error(ap->logopt, MODPREFIX + "failed to symlink %s to %s", entry->path, target); + + if (entry->sublink) { + /* failed to complete sublink mount */ + if (ext_mount_remove(&entry->ext_mount, entry->fs)) + umount_ent(ap, entry->fs); + } +out: + return ret; +} + +static int do_linkx_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, unsigned int flags) +{ + struct stat st; + char *target; + + if (entry->sublink) + target = entry->sublink; + else + target = entry->fs; + + if (lstat(target, &st) < 0) + return errno; + + return do_link_mount(ap, name, entry, flags); +} + +static int do_generic_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, const char *target, + unsigned int flags) +{ + const char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL; + unsigned int umount = 0; + int ret = 0; + + if (!entry->fs) { + ret = do_mount(ap, ap->path, name, + strlen(name), target, entry->type, opts); + } else { + /* + * Careful, external mounts may get mounted + * multiple times since they are outside of + * the automount filesystem. + */ + if (!is_mounted(_PATH_MOUNTED, entry->fs, MNTS_REAL)) { + ret = do_mount(ap, entry->fs, "/", 1, + target, entry->type, opts); + if (ret) + goto out; + umount = 1; + } + /* We have an external mount */ + ext_mount_add(&entry->ext_mount, entry->fs, umount); + ret = do_link_mount(ap, name, entry, flags); + } +out: + return ret; +} + +static int do_nfs_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, unsigned int flags) +{ + char target[PATH_MAX + 1]; + unsigned int proximity; + char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL; + unsigned int umount = 0; + int ret = 0; + + if (strlen(entry->rhost) + strlen(entry->rfs) + 1 > PATH_MAX) { + error(ap->logopt, MODPREFIX + "error: rhost + rfs options length is too long"); + return 0; + } + + strcpy(target, entry->rhost); + strcat(target, ":"); + strcat(target, entry->rfs); + + proximity = get_network_proximity(entry->rhost); + if (proximity == PROXIMITY_OTHER && entry->remopts && *entry->remopts) + opts = entry->remopts; + + if (!entry->fs) { + ret = mount_nfs->mount_mount(ap, ap->path, name, strlen(name), + target, entry->type, opts, + mount_nfs->context); + } else { + if (!is_mounted(_PATH_MOUNTED, entry->fs, MNTS_REAL)) { + ret = mount_nfs->mount_mount(ap, entry->fs, "/", 1, + target, entry->type, opts, + mount_nfs->context); + if (ret) + goto out; + umount = 1; + } + /* We might be using an external mount */ + ext_mount_add(&entry->ext_mount, entry->fs, umount); + ret = do_link_mount(ap, name, entry, flags); + } +out: + return ret; +} + +static int do_nfsl_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, struct substvar *sv, + unsigned int flags) +{ + const struct substvar *host, *hostd; + struct stat st; + char *target; + + host = macro_findvar(sv, "host", 4); + if (!host) + return do_nfs_mount(ap, name, entry, flags); + hostd = macro_findvar(sv, "hostd", 5); + if (!hostd || !*hostd->val) + return do_nfs_mount(ap, name, entry, flags); + + if (entry->sublink) + target = entry->sublink; + else + target = entry->fs; + + if (strcasecmp(host->val, entry->rhost) || + strcasecmp(hostd->val, entry->rhost)) + return do_nfs_mount(ap, name, entry, flags); + else if (lstat(target, &st) < 0) + return do_nfs_mount(ap, name, entry, flags); + + return do_link_mount(ap, name, entry, flags); +} + +static int wait_for_expire(struct autofs_point *ap) +{ + int ret = 0; + + st_wait_task(ap, ST_EXPIRE, 0); + + st_mutex_lock(); + if (ap->state != ST_SHUTDOWN && + ap->state != ST_SHUTDOWN_PENDING && + ap->state != ST_SHUTDOWN_FORCE) { + ret = 1; + } + st_mutex_unlock(); + + return ret; +} + +static int do_host_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, struct map_source *source, + unsigned int flags) +{ + struct lookup_mod *lookup; + struct map_source *instance; + struct mapent *me; + const char *argv[2]; + const char **pargv = NULL; + int status; + int argc = 0; + int ret = 1; + + /* + * If the mount point name isn't the same as the host name + * then we need to symlink to it after the mount. Attempt + * the allocation and set entry->path to the base location + * of the hosts mount tree so we can find it in + * lookup_nss_mount() later. + */ + if (strcmp(name, entry->rhost)) { + char *target; + size_t len = strlen(ap->path) + strlen(entry->rhost) + 2; + target = malloc(len); + if (!target) { + warn(ap->logopt, MODPREFIX + "failed to alloc target to hosts mount base"); + goto out; + } + strcpy(target, ap->path); + strcat(target, "/"); + strcat(target, entry->rhost); + if (entry->path) + free(entry->path); + entry->path = target; + /* + * Wait for any expire before racing to mount the + * export tree or bail out if we're shutting down. + */ + if (!wait_for_expire(ap)) + goto out; + } + + if (entry->opts && *entry->opts) { + argv[0] = entry->opts; + argv[1] = NULL; + pargv = argv; + argc = 1; + } + + instance_mutex_lock(); + status = open_lookup("hosts", MODPREFIX, NULL, argc, pargv, &lookup); + if (status != NSS_STATUS_SUCCESS) { + debug(ap->logopt, "open lookup module hosts failed"); + instance_mutex_unlock(); + goto out; + } + + instance = master_find_source_instance(source, + "hosts", "sun", argc, pargv); + if (!instance) { + instance = master_add_source_instance(source, + "hosts", "sun", monotonic_time(NULL), argc, pargv); + if (!instance) { + error(ap->logopt, MODPREFIX + "failed to create source instance for hosts map"); + instance_mutex_unlock(); + close_lookup(lookup); + goto out; + } + } + instance->lookup = lookup; + instance_mutex_unlock(); + + cache_writelock(source->mc); + me = cache_lookup_distinct(source->mc, name); + if (me) + cache_push_mapent(me, NULL); + cache_unlock(source->mc); + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + ret = lookup->lookup_mount(ap, entry->rhost, + strlen(entry->rhost), lookup->context); + + if (!strcmp(name, entry->rhost)) + goto out; + + if (do_mount(ap, ap->path, + name, strlen(name), entry->path, "bind", "symlink")) + warn(ap->logopt, MODPREFIX + "failed to create symlink to hosts mount base"); +out: + return ret; +} + +static unsigned int validate_auto_options(unsigned int logopt, + struct amd_entry *entry) +{ + /* + * The amd manual implies all the mount type auto options + * are optional but I don't think there's much point if + * no map is given. If the option has been intentionally + * left blank the mount must be expected to fail so don't + * report the error. + */ + if (!entry->fs) { + error(logopt, MODPREFIX + "%s: file system not given", entry->type); + return 0; + } else if (!*entry->fs) + return 0; + return 1; +} + +static unsigned int validate_link_options(unsigned int logopt, + struct amd_entry *entry) +{ + /* fs is the destimation of the link */ + return validate_auto_options(logopt, entry); +} + +static unsigned int validate_nfs_options(unsigned int logopt, + struct amd_entry *entry) +{ + /* + * Required option rhost will always have a value unless + * it has been intentionally left blank. It is set from + * ${host} if it is found to be NULL earlier in the parsing + * process. Don't report the error if it has been left blank + * or if the fs option has been left blank since the mount is + * expected to fail. + */ + if (!entry->rfs || !*entry->rfs) { + if (entry->rfs && !*entry->rfs) + return 0; + /* Map option fs has been intentionally left blank */ + if (entry->fs && !*entry->fs) + return 0; + entry->rfs = strdup(entry->fs); + if (!entry->rfs) { + error(logopt, MODPREFIX + "%s: remote file system not given", entry->type); + return 0; + } + } + if (entry->sublink && !entry->fs) { + error(logopt, MODPREFIX + "%s: sublink option requires option fs"); + return 0; + } + return 1; +} + +static unsigned int validate_generic_options(unsigned int logopt, + unsigned long fstype, + struct amd_entry *entry) +{ + /* + * If dev or rfs are empty in the map entry the mount is + * expected to fail so don't report the error. + */ + if (fstype != AMD_MOUNT_TYPE_LOFS) { + if (!entry->dev) { + error(logopt, MODPREFIX + "%s: mount device not given", entry->type); + return 0; + } else if (!*entry->dev) + return 0; + } else { + if (!entry->rfs) { + /* + * Can't use entry->type as the mount type to reprot + * the error since entry->type == "bind" not "lofs". + */ + error(logopt, "lofs: mount device not given"); + return 0; + } else if (!*entry->rfs) + return 0; + } + if (entry->sublink && !entry->fs) { + error(logopt, MODPREFIX + "%s: sublink option requires option fs"); + return 0; + } + return 1; +} + +static unsigned int validate_ufs_fstype(unsigned int logopt, + struct amd_entry *entry) +{ + const char *type = (const char *) entry->type; + + if (strcmp(type, "ext") && strcmp(type, "ext2") && + strcmp(type, "ext3") && strcmp(type, "ext4") && + strcmp(type, "xfs") && strcmp(type, "jfs")) { + error(logopt, MODPREFIX + "%s: mount type %s not valid as ufs mount type on Linux", + type); + return 0; + } + return 1; +} + +static unsigned int validate_host_options(unsigned int logopt, + struct amd_entry *entry) +{ + /* + * rhost is always non-null, unless it is intentionally left + * empty, because it will have the the value of the host name + * if it isn't given in the map entry. Don't report an error + * if it has been left empty since it's expected to fail. + */ + if (!entry->rhost) { + error(logopt, MODPREFIX + "%s: remote host name not given", entry->type); + return 0; + } else if (!*entry->rhost) + return 0; + return 1; +} + +static int amd_mount(struct autofs_point *ap, const char *name, + struct amd_entry *entry, struct map_source *source, + struct substvar *sv, unsigned int flags, + struct parse_context *ctxt) +{ + unsigned long fstype = entry->flags & AMD_MOUNT_TYPE_MASK; + int ret = 1; + + switch (fstype) { + case AMD_MOUNT_TYPE_AUTO: + if (!validate_auto_options(ap->logopt, entry)) + return 1; + ret = do_auto_mount(ap, name, entry, flags); + break; + + case AMD_MOUNT_TYPE_LOFS: + if (!validate_generic_options(ap->logopt, fstype, entry)) + return 1; + ret = do_generic_mount(ap, name, entry, entry->rfs, flags); + break; + + case AMD_MOUNT_TYPE_UFS: + if (!validate_ufs_fstype(ap->logopt, entry)) + return 1; + /* fall through to validate generic options */ + + case AMD_MOUNT_TYPE_EXT: + case AMD_MOUNT_TYPE_XFS: + case AMD_MOUNT_TYPE_CDFS: + if (!validate_generic_options(ap->logopt, fstype, entry)) + return 1; + ret = do_generic_mount(ap, name, entry, entry->dev, flags); + break; + + case AMD_MOUNT_TYPE_NFS: + if (!validate_nfs_options(ap->logopt, entry)) + return 1; + ret = do_nfs_mount(ap, name, entry, flags); + break; + + case AMD_MOUNT_TYPE_NFSL: + if (!validate_nfs_options(ap->logopt, entry) || + !validate_link_options(ap->logopt, entry)) + return 1; + ret = do_nfsl_mount(ap, name, entry, sv, flags); + break; + + case AMD_MOUNT_TYPE_LINK: + if (!validate_link_options(ap->logopt, entry)) + return 1; + ret = do_link_mount(ap, name, entry, flags); + break; + + case AMD_MOUNT_TYPE_LINKX: + if (!validate_link_options(ap->logopt, entry)) + return 1; + ret = do_linkx_mount(ap, name, entry, flags); + break; + + case AMD_MOUNT_TYPE_HOST: + if (!validate_host_options(ap->logopt, entry)) + return 1; + ret = do_host_mount(ap, name, entry, source, flags); + break; + + default: + info(ap->logopt, + MODPREFIX "unknown file system type %x", fstype); + break; + } + + return ret; +} + +void dequote_entry(struct autofs_point *ap, struct amd_entry *entry) +{ + char *res; + + if (entry->pref) { + res = dequote(entry->pref, strlen(entry->pref), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "pref dequote(\"%.*s\") -> %s", + strlen(entry->pref), entry->pref, res); + free(entry->pref); + entry->pref = res; + } + } + + if (entry->sublink) { + res = dequote(entry->sublink, strlen(entry->sublink), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "sublink dequote(\"%.*s\") -> %s", + strlen(entry->sublink), entry->sublink, res); + free(entry->sublink); + entry->sublink = res; + } + } + + if (entry->fs && *entry->fs) { + res = dequote(entry->fs, strlen(entry->fs), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "fs dequote(\"%.*s\") -> %s", + strlen(entry->fs), entry->fs, res); + free(entry->fs); + entry->fs = res; + } + } + + if (entry->rfs && *entry->rfs) { + res = dequote(entry->rfs, strlen(entry->rfs), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "rfs dequote(\"%.*s\") -> %s", + strlen(entry->rfs), entry->rfs, res); + free(entry->rfs); + entry->rfs = res; + } + } + + if (entry->opts && *entry->opts) { + res = dequote(entry->opts, strlen(entry->opts), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "ops dequote(\"%.*s\") -> %s", + strlen(entry->opts), entry->opts, res); + free(entry->opts); + entry->opts = res; + } + } + + if (entry->remopts && *entry->remopts) { + res = dequote(entry->remopts, strlen(entry->remopts), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "remopts dequote(\"%.*s\") -> %s", + strlen(entry->remopts), entry->remopts, res); + free(entry->remopts); + entry->remopts = res; + } + } + + if (entry->addopts && *entry->addopts) { + res = dequote(entry->addopts, strlen(entry->addopts), ap->logopt); + if (res) { + debug(ap->logopt, + MODPREFIX "addopts dequote(\"%.*s\") -> %s", + strlen(entry->addopts), entry->addopts, res); + free(entry->addopts); + entry->addopts = res; + } + } + + return; +} + +static void normalize_sublink(unsigned int logopt, + struct amd_entry *entry, struct substvar *sv) +{ + char *new; + size_t len; + + /* Normalizing sublink requires a non-blank fs option */ + if (!*entry->fs) + return; + + if (entry->sublink && *entry->sublink != '/') { + len = strlen(entry->fs) + strlen(entry->sublink) + 2; + new = malloc(len); + if (!new) { + error(logopt, MODPREFIX + "error: couldn't allocate storage for sublink"); + return; + } + strcpy(new, entry->fs); + strcat(new, "/"); + strcat(new, entry->sublink); + debug(logopt, MODPREFIX + "rfs dequote(\"%.*s\") -> %s", + strlen(entry->sublink), entry->sublink, new); + free(entry->sublink); + entry->sublink = new; + } + return; +} + +/* + * Set the prefix. + * + * This is done in a couple of places, here is as good a place as + * any to describe it. + * + * If a prefix is present in the map entry then use it. + * + * A pref option with the value none is required to use no prefix, + * otherwise the prefix of the parent map, if any, will be used. + */ +static void update_prefix(struct autofs_point *ap, + struct amd_entry *entry, const char *name) +{ + size_t len; + char *new; + + if (!entry->pref && ap->pref) { + len = strlen(ap->pref) + strlen(name) + 2; + new = malloc(len); + if (new) { + strcpy(new, ap->pref); + strcat(new, name); + strcat(new, "/"); + entry->pref = new; + } + } + return; +} + +static int match_selectors(unsigned int logopt, + struct amd_entry *entry, struct substvar *sv) +{ + struct selector *s = entry->selector; + int ret; + + /* No selectors, always match */ + if (!s) { + debug(logopt, "no selectors found in location"); + return 1; + } + + ret = 0; + + /* All selectors must match */ + while (s) { + ret = eval_selector(logopt, entry, sv); + if (!ret) + break; + s = s->next; + } + if (!s) + ret = 1; + + return ret; +} + +static struct amd_entry *dup_defaults_entry(struct amd_entry *defaults) +{ + struct amd_entry *entry; + char *tmp; + + entry = malloc(sizeof(struct amd_entry)); + if (!entry) + return NULL; + memset(entry, 0, sizeof(struct amd_entry)); + + entry->flags = defaults->flags; + + if (defaults->type) { + tmp = strdup(defaults->type); + if (tmp) + entry->type = tmp; + } + + if (defaults->map_type) { + tmp = strdup(defaults->map_type); + if (tmp) + entry->map_type = tmp; + } + + if (defaults->pref) { + tmp = strdup(defaults->pref); + if (tmp) + entry->pref = tmp; + } + + if (defaults->fs) { + tmp = strdup(defaults->fs); + if (tmp) + entry->fs = tmp; + } + + /* These shouldn't be blank in a defaults entry but ... */ + + if (defaults->rfs && *defaults->rfs) { + tmp = strdup(defaults->rfs); + if (tmp) + entry->rfs = tmp; + } + + if (defaults->rhost && *defaults->rhost) { + tmp = strdup(defaults->rhost); + if (tmp) + entry->rhost = tmp; + } + + if (defaults->dev && *defaults->dev) { + tmp = strdup(defaults->dev); + if (tmp) + entry->dev = tmp; + } + + if (defaults->opts && *defaults->opts) { + tmp = strdup(defaults->opts); + if (tmp) + entry->opts = tmp; + } + + if (defaults->addopts && *defaults->addopts) { + tmp = strdup(defaults->addopts); + if (tmp) + entry->addopts = tmp; + } + + if (defaults->remopts && *defaults->remopts) { + tmp = strdup(defaults->remopts); + if (tmp) + entry->remopts = tmp; + } + + INIT_LIST_HEAD(&entry->list); + + return entry; +} + +struct amd_entry *make_default_entry(struct autofs_point *ap, + struct substvar *sv) +{ + char *defaults = "opts:=rw,defaults"; + struct amd_entry *defaults_entry; + struct list_head dflts; + char *map_type; + + INIT_LIST_HEAD(&dflts); + if (amd_parse_list(ap, defaults, &dflts, &sv)) + return NULL; + defaults_entry = list_entry(dflts.next, struct amd_entry, list); + list_del_init(&defaults_entry->list); + /* + * If map type isn't given try to inherit from + * parent. A NULL map type is valid and means + * use configured nss sources. + */ + map_type = conf_amd_get_map_type(ap->path); + if (map_type) + defaults_entry->map_type = strdup(map_type); + /* The list should now be empty .... */ + free_amd_entry_list(&dflts); + return defaults_entry; +} + +static struct amd_entry *select_default_entry(struct autofs_point *ap, + struct list_head *entries, + struct substvar *sv) +{ + unsigned long flags = conf_amd_get_flags(ap->path); + struct amd_entry *defaults_entry = NULL; + struct amd_entry *entry_default = NULL; + struct list_head *p, *head; + + if (!(flags & CONF_SELECTORS_IN_DEFAULTS)) + goto no_sel; + + head = entries; + p = head->next; + while (p != head) { + struct amd_entry *this = list_entry(p, struct amd_entry, list); + + p = p->next; + + if (this->flags & AMD_DEFAULTS_MERGE) { + if (entry_default) + free_amd_entry(entry_default); + list_del_init(&this->list); + entry_default = this; + continue; + } else if (this->flags & AMD_DEFAULTS_RESET) { + struct amd_entry *new; + new = dup_defaults_entry(defaults_entry); + if (new) { + free_amd_entry(entry_default); + entry_default = new; + } + list_del_init(&this->list); + free_amd_entry(this); + continue; + } + + /* + * This probably should be a fail since we expect + * selectors to pick the default entry. + */ + if (!this->selector) + continue; + + if (match_selectors(ap->logopt, this, sv)) { + if (entry_default) { + /*update_with_defaults(entry_default, this, sv);*/ + free_amd_entry(entry_default); + } + list_del_init(&this->list); + defaults_entry = this; + break; + } + } + + /* Not strickly amd semantics but ... */ + if (!defaults_entry && entry_default) { + defaults_entry = entry_default; + goto done; + } + + if (!defaults_entry) { + debug(ap->logopt, MODPREFIX + "no matching selector(s) found in defaults, " + "using internal defaults"); + goto ret_default; + } + + goto done; + +no_sel: + if (list_empty(entries)) + goto ret_default; + + defaults_entry = list_entry(entries->next, struct amd_entry, list); + list_del_init(&defaults_entry->list); + if (!list_empty(entries)) { + free_amd_entry(defaults_entry); + goto ret_default; + } +done: + /*merge_entry_options(ap, defaults_entry, sv);*/ + /*normalize_sublink(ap->logopt, defaults_entry, sv);*/ + return defaults_entry; + +ret_default: + return make_default_entry(ap, sv); +} + +static struct amd_entry *get_defaults_entry(struct autofs_point *ap, + const char *defaults, + struct substvar *sv) +{ + struct amd_entry *entry; + struct list_head dflts; + + INIT_LIST_HEAD(&dflts); + + entry = NULL; + if (!defaults) + goto out; + else { + char *expand; + if (!expand_selectors(ap, defaults, &expand, sv)) + goto out; + if (amd_parse_list(ap, expand, &dflts, &sv)) { + error(ap->logopt, MODPREFIX + "failed to parse defaults entry, " + "attempting to use internal default"); + free(expand); + goto out; + } + entry = select_default_entry(ap, &dflts, sv); + if (!entry->map_type) { + /* + * If map type isn't given try to inherit from + * parent. A NULL map type is valid and means + * use configured nss sources. + */ + char *map_type = conf_amd_get_map_type(ap->path); + if (map_type) + entry->map_type = strdup(map_type); + } + free(expand); + } + + return entry; +out: + return make_default_entry(ap, sv); +} + +int parse_mount(struct autofs_point *ap, const char *name, + int name_len, const char *mapent, void *context) +{ + struct parse_context *ctxt = (struct parse_context *) context; + unsigned int flags = conf_amd_get_flags(ap->path); + struct substvar *sv = NULL; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + unsigned int at_least_one; + struct list_head entries, *p, *head; + struct amd_entry *defaults_entry; + struct amd_entry *cur_defaults; + char *defaults; + char *pmapent; + int len, rv = 1; + int cur_state; + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + if (!mapent) { + warn(ap->logopt, MODPREFIX "error: empty map entry"); + return 1; + } + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + + sv = add_lookup_vars(ap, name, name_len, source, sv); + if (!sv) { + macro_free_table(sv); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + + len = expand_selectors(ap, mapent, &pmapent, sv); + if (!len) { + macro_free_table(sv); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + + pthread_setcancelstate(cur_state, NULL); + + debug(ap->logopt, MODPREFIX "expanded mapent: %s", pmapent); + + defaults = conf_amd_get_map_defaults(ap->path); + if (defaults) { + debug(ap->logopt, MODPREFIX + "using map_defaults %s for %s", defaults, ap->path); + } else if ((me = cache_lookup_distinct(mc, "/defaults"))) { + defaults = strdup(me->mapent); + if (defaults) + debug(ap->logopt, MODPREFIX + "using /defaults %s from map", defaults); + else { + char buf[MAX_ERR_BUF]; + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "malloc: %s", estr); + } + } + + defaults_entry = get_defaults_entry(ap, defaults, sv); + if (!defaults_entry) { + error(ap->logopt, MODPREFIX "failed to get a defaults entry"); + if (defaults) + free(defaults); + free(pmapent); + macro_free_table(sv); + return 1; + } + if (defaults) + free(defaults); + + INIT_LIST_HEAD(&entries); + + ret = amd_parse_list(ap, pmapent, &entries, &sv); + if (ret) { + error(ap->logopt, + MODPREFIX "failed to parse entry: %s", pmapent); + free(pmapent); + goto done; + } + + free(pmapent); + + if (list_empty(&entries)) { + error(ap->logopt, MODPREFIX "no location found after parse"); + goto done; + } + + cur_defaults = dup_defaults_entry(defaults_entry); + if (!cur_defaults) { + error(ap->logopt, MODPREFIX + "failed to duplicate defaults entry"); + goto done; + } + + at_least_one = 0; + head = &entries; + p = head->next; + while (p != head) { + struct amd_entry *this = list_entry(p, struct amd_entry, list); + p = p->next; + + if (this->flags & AMD_DEFAULTS_MERGE) { + free_amd_entry(cur_defaults); + list_del_init(&this->list); + cur_defaults = this; + continue; + } else if (this->flags & AMD_DEFAULTS_RESET) { + struct amd_entry *new; + new = dup_defaults_entry(defaults_entry); + if (new) { + free_amd_entry(cur_defaults); + cur_defaults = new; + } + list_del_init(&this->list); + free_amd_entry(this); + continue; + } + + if (this->flags & AMD_ENTRY_CUT && at_least_one) { + info(ap->logopt, MODPREFIX + "at least one entry tried before cut selector, " + "not continuing"); + break; + } + + if (!match_selectors(ap->logopt, this, sv)) + continue; + + at_least_one = 1; + + update_with_defaults(cur_defaults, this, sv); + sv = expand_entry(ap, this, flags, sv); + sv = merge_entry_options(ap, this, sv); + normalize_sublink(ap->logopt, this, sv); + update_prefix(ap, this, name); + + dequote_entry(ap, this); + + /* + * Type "auto" needs to set the prefix at mount time so + * add parsed entry to parent amd mount list and remove + * on mount fail. + */ + mounts_mutex_lock(ap); + list_add_tail(&this->entries, &ap->amdmounts); + mounts_mutex_unlock(ap); + + rv = amd_mount(ap, name, this, source, sv, flags, ctxt); + mounts_mutex_lock(ap); + if (!rv) { + /* Mounted, remove entry from parsed list */ + list_del_init(&this->list); + mounts_mutex_unlock(ap); + break; + } + /* Not mounted, remove entry from the parent list */ + list_del_init(&this->entries); + mounts_mutex_unlock(ap); + } + free_amd_entry(cur_defaults); + + if (rv) + debug(ap->logopt, "no more locations to try, returning fail"); +done: + free_amd_entry_list(&entries); + free_amd_entry(defaults_entry); + macro_free_table(sv); + + return rv; +} + +int parse_done(void *context) +{ + int rv = 0; + struct parse_context *ctxt = (struct parse_context *) context; + + instance_mutex_lock(); + if (--init_ctr == 0) { + rv = close_mount(mount_nfs); + mount_nfs = NULL; + } + instance_mutex_unlock(); + if (ctxt) + kill_context(ctxt); + + return rv; +} diff --git a/modules/parse_hesiod.c b/modules/parse_hesiod.c new file mode 100644 index 0000000..a02da82 --- /dev/null +++ b/modules/parse_hesiod.c @@ -0,0 +1,331 @@ +/* + * parse_hesiod.c + * + * Module for Linux automountd to parse a hesiod filesystem entry. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_PARSE +#include "automount.h" + +#define MODPREFIX "parse(hesiod): " + +int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */ + +#define HESIOD_LEN 512 + +/* Break out the fields in an AFS record of the form: + "AFS /afs/athena/mit/tytso w /mit/tytso-afs" */ +static int parse_afs(struct autofs_point *ap, + const char *filsysline, const char *name, int name_len, + char *source, int source_len, char *options, int options_len) +{ + const char *p; + int i; + + p = filsysline; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Skip the filesystem type. */ + while (!isspace(*p)) + p++; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Isolate the source for this AFS fs. */ + for (i = 0; (!isspace(p[i]) && i < source_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for AFS " + "source: %s", p); + return 1; + } + source[i] = p[i]; + } + + source[i] = 0; + p += i; + + /* Skip whitespace. */ + while ((*p) && (isspace(*p))) + p++; + + /* Isolate the options for this AFS fs. */ + for (i = 0; (!isspace(p[i]) && i < options_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for AFS " + "options: %s", p); + return 1; + } + options[i] = p[i]; + } + options[i] = 0; + + /* Hack for "r" or "w" options. */ + if (!strcmp(options, "r")) + strcpy(options, "ro"); + + if (!strcmp(options, "w")) + strcpy(options, "rw"); + + debug(ap->logopt, + MODPREFIX + "parsing AFS record gives '%s'->'%s' with options" " '%s'", + name, source, options); + + return 0; +} + +/* + * Break out the fields in an NFS record of the form: + * "NFS /export/src nelson.tx.ncsu.edu w /ncsu/tx-src" + */ +static int parse_nfs(struct autofs_point *ap, + const char *filsysline, const char *name, + int name_len, char *source, int source_len, + char *options, int options_len) +{ + const char *p; + char mount[HESIOD_LEN + 1]; + int i; + + p = filsysline; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Skip the filesystem type. */ + while (!isspace(*p)) + p++; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Isolate the remote mountpoint for this NFS fs. */ + for (i = 0; (!isspace(p[i]) && i < ((int) sizeof(mount) - 1)); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for NFS " + "mountpoint: %s", p); + return 1; + } + mount[i] = p[i]; + } + + mount[i] = 0; + p += i; + + /* Skip whitespace. */ + while ((*p) && (isspace(*p))) + p++; + + /* Isolate the remote host. */ + for (i = 0; (!isspace(p[i]) && i < source_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for NFS " + "host: %s", p); + return 1; + } + source[i] = p[i]; + } + + source[i] = 0; + p += i; + + if (strlen(source) + strlen(mount) + 2 > source_len) { + error(ap->logopt, MODPREFIX "entry too log for mount source"); + return 1; + } + + /* Append ":mountpoint" to the source to get "host:mountpoint". */ + strcat(source, ":"); + strcat(source, mount); + + /* Skip whitespace. */ + while ((*p) && (isspace(*p))) + p++; + + /* Isolate the mount options. */ + for (i = 0; (!isspace(p[i]) && i < options_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for NFS " + "mount options: %s", p); + return 1; + } + options[i] = p[i]; + } + options[i] = 0; + + /* Hack for "r" or "w" options. */ + if (!strcmp(options, "r")) + strcpy(options, "ro"); + + if (!strcmp(options, "w")) + strcpy(options, "rw"); + + debug(ap->logopt, + MODPREFIX + "parsing NFS record gives '%s'->'%s' with options" "'%s'", + name, source, options); + + return 0; +} + +/* Break out the fields in a generic record of the form: + "UFS /dev/ra0g w /site" */ +static int parse_generic(struct autofs_point *ap, + const char *filsysline, const char *name, int name_len, + char *source, int source_len, char *options, int options_len) +{ + const char *p; + int i; + + p = filsysline; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Skip the filesystem type. */ + while (!isspace(*p)) + p++; + + /* Skip whitespace. */ + while (isspace(*p)) + p++; + + /* Isolate the source for this fs. */ + for (i = 0; (!isspace(p[i]) && i < source_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for generic " + "mount source: %s", p); + return 1; + } + source[i] = p[i]; + } + + source[i] = 0; + p += i; + + /* Skip whitespace. */ + while ((*p) && (isspace(*p))) + p++; + + /* Isolate the mount options. */ + for (i = 0; (!isspace(p[i]) && i < options_len); i++) { + if (!p[i]) { + error(ap->logopt, MODPREFIX + "unexpeced end of input looking for generic " + "mount options: %s", p); + return 1; + } + options[i] = p[i]; + } + options[i] = 0; + + /* Hack for "r" or "w" options. */ + if (!strcmp(options, "r")) + strcpy(options, "ro"); + + if (!strcmp(options, "w")) + strcpy(options, "rw"); + + debug(ap->logopt, + MODPREFIX + "parsing generic record gives '%s'->'%s' with options '%s'", + name, source, options); + + return 0; +} + +int parse_init(int argc, const char *const *argv, void **context) +{ + *context = NULL; + return 0; +} + +int parse_reinit(int argc, const char *const *argv, void **context) +{ + return 0; +} + +int parse_done(void *context) +{ + return 0; +} + +int parse_mount(struct autofs_point *ap, const char *name, + int name_len, const char *mapent, void *context) +{ + char source[HESIOD_LEN + 1]; + char fstype[HESIOD_LEN + 1]; + char options[HESIOD_LEN + 1]; + char *q; + const char *p; + int ret; + + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + p = mapent; + q = fstype; + + /* Skip any initial whitespace... */ + while (isspace(*p)) + p++; + + /* Isolate the filesystem type... */ + while (!isspace(*p)) { + *q++ = tolower(*p++); + } + *q = 0; + + /* If it's an error message... */ + if (!strcasecmp(fstype, "err")) { + error(ap->logopt, MODPREFIX "%s", mapent); + return 1; + /* If it's an AFS fs... */ + } else if (!strcasecmp(fstype, "afs")) + ret = parse_afs(ap, mapent, name, name_len, + source, sizeof(source), options, + sizeof(options)); + /* If it's NFS... */ + else if (!strcasecmp(fstype, "nfs")) + ret = parse_nfs(ap, mapent, name, name_len, + source, sizeof(source), options, + sizeof(options)); + /* Punt. */ + else + ret = parse_generic(ap, mapent, name, name_len, + source, sizeof(source), options, + sizeof(options)); + + if (ret) { + error(ap->logopt, MODPREFIX "failed to parse entry"); + return 1; + } else { + debug(ap->logopt, + MODPREFIX "mount %s is type %s from %s", + name, fstype, source); + } + + return do_mount(ap, ap->path, name, name_len, source, fstype, options); +} diff --git a/modules/parse_sun.c b/modules/parse_sun.c new file mode 100644 index 0000000..536a9bc --- /dev/null +++ b/modules/parse_sun.c @@ -0,0 +1,1757 @@ +/* ----------------------------------------------------------------------- * + * + * parse_sun.c - module for Linux automountd to parse a Sun-format + * automounter map + * + * Copyright 1997 Transmeta Corporation - All Rights Reserved + * Copyright 2000 Jeremy Fitzhardinge + * Copyright 2004, 2005 Ian Kent + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_PARSE +#include "automount.h" + +#define MODPREFIX "parse(sun): " + +int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */ + +static struct mount_mod *mount_nfs = NULL; +static int init_ctr = 0; +static pthread_mutex_t instance_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void instance_mutex_lock(void) +{ + int status = pthread_mutex_lock(&instance_mutex); + if (status) + fatal(status); +} + +static void instance_mutex_unlock(void) +{ + int status = pthread_mutex_unlock(&instance_mutex); + if (status) + fatal(status); +} + +extern const char *global_options; + +struct parse_context { + char *optstr; /* Mount options */ + char *macros; /* Map wide macro defines */ + struct substvar *subst; /* $-substitutions */ + int slashify_colons; /* Change colons to slashes? */ +}; + +struct multi_mnt { + char *path; + char *options; + char *location; + struct multi_mnt *next; +}; + +/* Default context */ + +static struct parse_context default_context = { + NULL, /* No mount options */ + NULL, /* No map wide macros */ + NULL, /* The substvar local vars table */ + 1 /* Do slashify_colons */ +}; + +int destroy_logpri_fifo(struct autofs_point *ap); +static char *concat_options(char *left, char *right); + +/* Free all storage associated with this context */ +static void kill_context(struct parse_context *ctxt) +{ + macro_lock(); + macro_free_table(ctxt->subst); + macro_unlock(); + if (ctxt->optstr) + free(ctxt->optstr); + if (ctxt->macros) + free(ctxt->macros); + free(ctxt); +} + +/* + * $- and &-expand a Sun-style map entry and return the length of the entry. + * If "dst" is NULL, just count the length. + */ +int expandsunent(const char *src, char *dst, const char *key, + const struct substvar *svc, int slashify_colons) +{ + const struct substvar *sv; + int len, l, seen_colons; + const char *p; + char ch; + + len = 0; + seen_colons = 0; + + while ((ch = *src++)) { + switch (ch) { + case '&': + l = strlen(key); + /* + * In order to ensure that any isspace() characters + * in the key are preserved, we need to escape them + * here. + */ + const char *keyp = key; + while (*keyp) { + if (isspace(*keyp)) { + if (dst) { + *dst++ = '\\'; + *dst++ = *keyp++; + } else + keyp++; + l++; + } else { + if (dst) + *dst++ = *keyp++; + else + keyp++; + } + } + len += l; + break; + + case '$': + if (*src == '{') { + p = strchr(++src, '}'); + if (!p) { + /* Ignore rest of string */ + if (dst) + *dst = '\0'; + return len; + } + sv = macro_findvar(svc, src, p - src); + if (sv) { + l = strlen(sv->val); + if (dst) { + strcpy(dst, sv->val); + dst += l; + } + len += l; + } + src = p + 1; + } else { + p = src; + while (isalnum(*p) || *p == '_') + p++; + sv = macro_findvar(svc, src, p - src); + if (sv) { + l = strlen(sv->val); + if (dst) { + strcpy(dst, sv->val); + dst += l; + } + len += l; + } + src = p; + } + break; + + case '\\': + len++; + if (dst) + *dst++ = ch; + + if (*src) { + len++; + if (dst) + *dst++ = *src; + src++; + } + break; + + case '"': + len++; + if (dst) + *dst++ = ch; + + while (*src && *src != '"') { + len++; + if (dst) + *dst++ = *src; + src++; + } + if (*src && dst) { + len++; + *dst++ = *src++; + } + break; + + case ':': + if (dst) + *(dst++) = + (seen_colons && slashify_colons) ? '/' : ':'; + len++; + /* Were looking for the colon preceeding a path */ + if (*src == '/') + seen_colons = 1; + break; + + default: + if (isspace(ch)) + seen_colons = 0; + + if (dst) + *(dst++) = ch; + len++; + break; + } + } + if (dst) + *dst = '\0'; + return len; +} + +static int do_init(int argc, const char *const *argv, struct parse_context *ctxt) +{ + char *noptstr, *def, *val, *macros, *gbl_options; + char buf[MAX_ERR_BUF]; + int optlen, len, offset; + const char *xopt; + int i, bval; + unsigned int append_options; + + optlen = 0; + + /* Look for options and capture, and create new defines if we need to */ + + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-' && + (argv[i][1] == 'D' || argv[i][1] == '-') ) { + switch (argv[i][1]) { + case 'D': + if (argv[i][2]) + def = strdup(argv[i] + 2); + else if (++i < argc) + def = strdup(argv[i]); + else + break; + + if (!def) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "strdup: %s", estr); + break; + } + + val = strchr(def, '='); + if (val) + *(val++) = '\0'; + else + val = ""; + + macro_lock(); + + ctxt->subst = macro_addvar(ctxt->subst, + def, strlen(def), val); + + macro_unlock(); + + /* we use 5 for the "-D", "=", "," and the null */ + if (ctxt->macros) { + len = strlen(ctxt->macros) + strlen(def) + strlen(val); + macros = realloc(ctxt->macros, len + 5); + if (!macros) { + free(def); + break; + } + strcat(macros, ","); + } else { /* No comma, so only +4 */ + len = strlen(def) + strlen(val); + macros = malloc(len + 4); + if (!macros) { + free(def); + break; + } + *macros = '\0'; + } + ctxt->macros = macros; + + strcat(ctxt->macros, "-D"); + strcat(ctxt->macros, def); + strcat(ctxt->macros, "="); + strcat(ctxt->macros, val); + free(def); + break; + + case '-': + if (!strncmp(argv[i] + 2, "no-", 3)) { + xopt = argv[i] + 5; + bval = 0; + } else { + xopt = argv[i] + 2; + bval = 1; + } + + if (!strmcmp(xopt, "slashify-colons", 1)) + ctxt->slashify_colons = bval; + else + error(LOGOPT_ANY, + MODPREFIX "unknown option: %s", + argv[i]); + break; + + default: + error(LOGOPT_ANY, + MODPREFIX "unknown option: %s", argv[i]); + break; + } + } else { + offset = (argv[i][0] == '-' ? 1 : 0); + len = strlen(argv[i] + offset); + if (ctxt->optstr) { + noptstr = + (char *) realloc(ctxt->optstr, optlen + len + 2); + if (noptstr) { + noptstr[optlen] = ','; + strcpy(noptstr + optlen + 1, argv[i] + offset); + optlen += len + 1; + } + } else { + noptstr = (char *) malloc(len + 1); + if (noptstr) { + strcpy(noptstr, argv[i] + offset); + optlen = len; + } + } + if (!noptstr) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "%s", estr); + return 1; + } + ctxt->optstr = noptstr; + } + } + + gbl_options = NULL; + if (global_options) { + if (ctxt->optstr && strstr(ctxt->optstr, global_options)) + goto options_done; + gbl_options = strdup(global_options); + } + + if (gbl_options) { + append_options = defaults_get_append_options(); + if (append_options) { + char *tmp = concat_options(gbl_options, ctxt->optstr); + if (!tmp) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "concat_options: %s", estr); + free(gbl_options); + } else + ctxt->optstr = tmp; + } else { + if (!ctxt->optstr) + ctxt->optstr = gbl_options; + else + free(gbl_options); + } + } +options_done: + + debug(LOGOPT_NONE, + MODPREFIX "init gathered global options: %s", ctxt->optstr); + + return 0; +} + +int parse_init(int argc, const char *const *argv, void **context) +{ + struct parse_context *ctxt; + char buf[MAX_ERR_BUF]; + + *context = NULL; + + /* Set up context and escape chain */ + + ctxt = (struct parse_context *) malloc(sizeof(struct parse_context)); + if (!ctxt) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + + *ctxt = default_context; + + if (do_init(argc, argv, ctxt)) { + free(ctxt); + return 1; + } + + /* We only need this once. NFS mounts are so common that we cache + this module. */ + instance_mutex_lock(); + if (mount_nfs) + init_ctr++; + else { + if ((mount_nfs = open_mount("nfs", MODPREFIX))) { + init_ctr++; + } else { + kill_context(ctxt); + instance_mutex_unlock(); + return 1; + } + } + instance_mutex_unlock(); + + *context = (void *) ctxt; + + return 0; +} + +int parse_reinit(int argc, const char *const *argv, void **context) +{ + struct parse_context *ctxt = (struct parse_context *) *context; + struct parse_context *new; + char buf[MAX_ERR_BUF]; + + new = (struct parse_context *) malloc(sizeof(struct parse_context)); + if (!new) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + + *new = default_context; + + if (do_init(argc, argv, new)) + return 1; + + kill_context(ctxt); + + *context = (void *) new; + + return 0; +} + +static const char *parse_options(const char *str, char **ret, unsigned int logopt) +{ + const char *cp = str; + int len; + + if (*cp++ != '-') + return str; + + if (*ret != NULL) + free(*ret); + + len = chunklen(cp, 0); + *ret = dequote(cp, len, logopt); + + return cp + len; +} + +static char *concat_options(char *left, char *right) +{ + char buf[MAX_ERR_BUF]; + char *ret; + + if (left == NULL || *left == '\0') { + ret = strdup(right); + free(right); + return ret; + } + + if (right == NULL || *right == '\0') { + ret = strdup(left); + free(left); + return ret; + } + + ret = malloc(strlen(left) + strlen(right) + 2); + + if (ret == NULL) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return NULL; + } + + strcpy(ret, left); + strcat(ret, ","); + strcat(ret, right); + + free(left); + free(right); + + return ret; +} + +static int sun_mount(struct autofs_point *ap, const char *root, + const char *name, int namelen, + const char *loc, int loclen, const char *options, + struct parse_context *ctxt) +{ + char *fstype = "nfs"; /* Default filesystem type */ + int nonstrict = 1; + int use_weight_only = ap->flags & MOUNT_FLAG_USE_WEIGHT_ONLY; + int rv, cur_state; + char *mountpoint; + char *what; + char *type; + + if (*options == '\0') + options = NULL; + + if (options) { + char *noptions; + const char *comma; + char *np; + int len = strlen(options) + 1; + + noptions = np = alloca(len); + *np = '\0'; + + /* Extract fstype= pseudo option */ + for (comma = options; *comma != '\0';) { + const char *cp; + + while (*comma == ',') + comma++; + + cp = comma; + + while (*comma != '\0' && *comma != ',') + comma++; + + if (_strncmp("fstype=", cp, 7) == 0) { + int typelen = comma - (cp + 7); + fstype = alloca(typelen + 1); + memcpy(fstype, cp + 7, typelen); + fstype[typelen] = '\0'; + } else if (_strncmp("nonstrict", cp, 9) == 0) { + nonstrict = 1; + } else if (_strncmp("strict", cp, 6) == 0 && + comma - cp == 6) { + nonstrict = 0; + } else if (_strncmp("nobrowse", cp, 8) == 0 || + _strncmp("browse", cp, 6) == 0 || + _strncmp("timeout=", cp, 8) == 0) { + if (strcmp(fstype, "autofs") == 0 || + strstr(cp, "fstype=autofs")) { + memcpy(np, cp, comma - cp + 1); + np += comma - cp + 1; + } + } else if (_strncmp("no-use-weight-only", cp, 18) == 0) { + use_weight_only = -1; + } else if (_strncmp("use-weight-only", cp, 15) == 0) { + use_weight_only = MOUNT_FLAG_USE_WEIGHT_ONLY; + } else if (_strncmp("bg", cp, 2) == 0 || + _strncmp("nofg", cp, 4) == 0) { + continue; + } else { + memcpy(np, cp, comma - cp + 1); + np += comma - cp + 1; + } + } + + if (np > noptions + len) { + warn(ap->logopt, MODPREFIX "options string truncated"); + np[len] = '\0'; + } else + *(np - 1) = '\0'; + + options = noptions; + } + + if (!strcmp(fstype, "autofs") && ctxt->macros) { + char *noptions = NULL; + + if (!options || *options == '\0') { + noptions = alloca(strlen(ctxt->macros) + 1); + *noptions = '\0'; + } else { + int len = strlen(options) + strlen(ctxt->macros) + 2; + noptions = alloca(len); + + if (noptions) { + strcpy(noptions, options); + strcat(noptions, ","); + } + } + + if (noptions && *noptions != '\0') { + strcat(noptions, ctxt->macros); + options = noptions; + } else { + error(ap->logopt, + MODPREFIX "alloca failed for options"); + } + } + + mountpoint = alloca(namelen + 1); + sprintf(mountpoint, "%.*s", namelen, name); + + type = ap->entry->maps->type; + if (type && !strcmp(type, "hosts")) { + if (options && *options != '\0') { + int len = strlen(options); + int suid = strstr(options, "suid") ? 0 : 7; + int dev = strstr(options, "dev") ? 0 : 6; + int nointr = strstr(options, "nointr") ? 0 : 5; + + if (suid || dev || nointr) { + char *tmp = alloca(len + suid + dev + nointr + 1); + if (!tmp) { + error(ap->logopt, MODPREFIX + "alloca failed for options"); + if (nonstrict) + return -1; + return 1; + } + + strcpy(tmp, options); + if (suid) + strcat(tmp, ",nosuid"); + if (dev) + strcat(tmp, ",nodev"); + if (nointr) + strcat(tmp, ",intr"); + options = tmp; + } + } else { + char *tmp = alloca(18); + if (!tmp) { + error(ap->logopt, + MODPREFIX "alloca failed for options"); + if (nonstrict) + return -1; + return 1; + } + strcpy(tmp, "nosuid,nodev,intr"); + options = tmp; + } + } + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + if (!strcmp(fstype, "nfs") || !strcmp(fstype, "nfs4")) { + what = alloca(loclen + 1); + memcpy(what, loc, loclen); + what[loclen] = '\0'; + + /* Add back "[no-]use-weight-only" for NFS mounts only */ + if (use_weight_only) { + char *tmp; + int len; + + if (options && *options != '\0') { + len = strlen(options) + 19; + tmp = alloca(len); + strcpy(tmp, options); + strcat(tmp, ","); + if (use_weight_only == MOUNT_FLAG_USE_WEIGHT_ONLY) + strcat(tmp, "use-weight-only"); + else + strcat(tmp, "no-use-weight-only"); + } else { + tmp = alloca(19); + if (use_weight_only == MOUNT_FLAG_USE_WEIGHT_ONLY) + strcpy(tmp, "use-weight-only"); + else + strcpy(tmp, "no-use-weight-only"); + } + options = tmp; + } + + debug(ap->logopt, MODPREFIX + "mounting root %s, mountpoint %s, " + "what %s, fstype %s, options %s", + root, mountpoint, what, fstype, options); + + rv = mount_nfs->mount_mount(ap, root, mountpoint, strlen(mountpoint), + what, fstype, options, mount_nfs->context); + } else { + if (!loclen) + what = NULL; + else { + what = alloca(loclen + 1); + if (*loc == ':') { + loclen--; + memcpy(what, loc + 1, loclen); + what[loclen] = '\0'; + } else { + memcpy(what, loc, loclen); + what[loclen] = '\0'; + } + } + + debug(ap->logopt, MODPREFIX + "mounting root %s, mountpoint %s, " + "what %s, fstype %s, options %s", + root, mountpoint, what, fstype, options); + + /* Generic mount routine */ + rv = do_mount(ap, root, mountpoint, strlen(mountpoint), what, fstype, + options); + } + pthread_setcancelstate(cur_state, NULL); + + if (nonstrict && rv) + return -rv; + + return rv; +} + +/* + * Scan map entry looking for evidence it has multiple key/mapent + * pairs. + */ +static int check_is_multi(const char *mapent) +{ + const char *p = mapent; + int multi = 0; + int not_first_chunk = 0; + + if (!p) { + logerr(MODPREFIX "unexpected NULL map entry pointer"); + return 0; + } + + if (*p == '"') + p++; + + /* If first character is "/" it's a multi-mount */ + if (*p == '/') + return 1; + + while (*p) { + p = skipspace(p); + + /* + * After the first chunk there can be additional + * locations (possibly not multi) or possibly an + * options string if the first entry includes the + * optional '/' (is multi). Following this any + * path that begins with '/' indicates a mutil-mount + * entry. + */ + if (not_first_chunk) { + if (*p == '"') + p++; + if (*p == '/' || *p == '-') { + multi = 1; + break; + } + } + + while (*p == '-') { + p += chunklen(p, 0); + p = skipspace(p); + } + + /* + * Expect either a path or location + * after which it's a multi mount. + */ + p += chunklen(p, check_colon(p)); + not_first_chunk++; + } + + return multi; +} + +static int +update_offset_entry(struct autofs_point *ap, const char *name, + const char *m_root, int m_root_len, + const char *path, const char *myoptions, const char *loc, + time_t age) +{ + struct map_source *source; + struct mapent_cache *mc; + char m_key[PATH_MAX + 1]; + char m_mapent[MAPENT_MAX_LEN + 1]; + int p_len, m_key_len, m_options_len, m_mapent_len; + int ret; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + memset(m_mapent, 0, MAPENT_MAX_LEN + 1); + + /* Internal hosts map may have loc == NULL */ + if (!*path) { + error(ap->logopt, + MODPREFIX "syntax error in offset %s -> %s", path, loc); + return CHE_FAIL; + } + + p_len = strlen(path); + /* Trailing '/' causes us pain */ + if (p_len > 1) { + while (p_len > 1 && path[p_len - 1] == '/') + p_len--; + } + m_key_len = m_root_len + p_len; + if (m_key_len > PATH_MAX) { + error(ap->logopt, MODPREFIX "multi mount key too long"); + return CHE_FAIL; + } + strcpy(m_key, m_root); + strncat(m_key, path, p_len); + m_key[m_key_len] = '\0'; + + m_options_len = 0; + if (*myoptions) + m_options_len = strlen(myoptions) + 2; + + m_mapent_len = loc ? strlen(loc) : 0; + if (m_mapent_len + m_options_len > MAPENT_MAX_LEN) { + error(ap->logopt, MODPREFIX "multi mount mapent too long"); + return CHE_FAIL; + } + + if (*myoptions) { + strcpy(m_mapent, "-"); + strcat(m_mapent, myoptions); + if (loc) { + strcat(m_mapent, " "); + if (loc) + strcat(m_mapent, loc); + } + } else { + if (loc) + strcpy(m_mapent, loc); + } + + ret = cache_update_offset(mc, name, m_key, m_mapent, age); + if (ret == CHE_DUPLICATE) { + warn(ap->logopt, MODPREFIX + "syntax error or duplicate offset %s -> %s", path, loc); + ret = CHE_OK; + } else if (ret == CHE_FAIL) + debug(ap->logopt, MODPREFIX + "failed to update multi-mount offset %s -> %s", path, m_mapent); + else { + ret = CHE_OK; + debug(ap->logopt, MODPREFIX + "updated multi-mount offset %s -> %s", path, m_mapent); + } + + return ret; +} + +static int validate_location(unsigned int logopt, char *loc) +{ + char *ptr = loc; + + /* We don't know much about these */ + if (*ptr == ':') + return 1; + + /* + * If a ':/' is present now it must be a host name, except + * for those special file systems like sshfs which use "#" + * and "@" in the host name part and ipv6 addresses that + * have ":", "[" and "]". + */ + if (!check_colon(ptr)) { + char *esc; + /* + * Don't forget cases where a colon is present but + * not followed by a "/" or, if there is no colon at + * all, we don't know if it is actually invalid since + * it may be a map name by itself, for example. + */ + if (!strchr(ptr, ':') || + ((esc = strchr(ptr, '\\')) && *(esc + 1) == ':') || + !strncmp(ptr, "file:", 5) || !strncmp(ptr, "yp:", 3) || + !strncmp(ptr, "nis:", 4) || !strncmp(ptr, "nisplus:", 8) || + !strncmp(ptr, "ldap:", 5) || !strncmp(ptr, "ldaps:", 6) || + !strncmp(ptr, "sss:", 4) || !strncmp(ptr, "dir:", 4)) + return 1; + error(logopt, + "expected colon delimeter not found in location %s", + loc); + return 0; + } else { + while (*ptr && strncmp(ptr, ":/", 2)) { + if (!(isalnum(*ptr) || + *ptr == '-' || *ptr == '.' || *ptr == '_' || + *ptr == ',' || *ptr == '(' || *ptr == ')' || + *ptr == '#' || *ptr == '@' || *ptr == ':' || + *ptr == '[' || *ptr == ']' || *ptr == '%')) { + error(logopt, "invalid character \"%c\" " + "found in location %s", *ptr, loc); + return 0; + } + ptr++; + } + + if (*ptr && !strncmp(ptr, ":/", 2)) + ptr++; + } + + /* Must always be something following */ + if (!*ptr) { + error(logopt, "invalid location %s", loc); + return 0; + } + + return 1; +} + +static int parse_mapent(const char *ent, char *g_options, char **options, char **location, int logopt) +{ + char buf[MAX_ERR_BUF]; + const char *p; + char *myoptions, *loc; + int l; + + p = ent; + + myoptions = strdup(g_options); + if (!myoptions) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX "strdup: %s", estr); + return 0; + } + + /* Local options are appended to per-map options */ + if (*p == '-') { + do { + char *tmp, *newopt = NULL; + + p = parse_options(p, &newopt, logopt); + if (newopt && strstr(newopt, myoptions)) { + free(myoptions); + myoptions = newopt; + } else { + tmp = concat_options(myoptions, newopt); + if (!tmp) { + char *estr; + estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(logopt, MODPREFIX + "concat_options: %s", estr); + if (newopt) + free(newopt); + free(myoptions); + return 0; + } + myoptions = tmp; + } + + p = skipspace(p); + } while (*p == '-'); + } + + debug(logopt, MODPREFIX "gathered options: %s", myoptions); + + l = chunklen(p, check_colon(p)); + loc = dequote(p, l, logopt); + if (!loc) { + if (strstr(myoptions, "fstype=autofs") && + strstr(myoptions, "hosts")) { + warn(logopt, MODPREFIX "possible missing location"); + free(myoptions); + return 0; + } + *options = myoptions; + *location = NULL; + return (p - ent); + } + + /* Location can't begin with a '/' */ + if (*p == '/') { + warn(logopt, MODPREFIX "error location begins with \"/\""); + free(myoptions); + free(loc); + return 0; + } + + if (!validate_location(logopt, loc)) { + free(myoptions); + free(loc); + return 0; + } + + debug(logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, loc); + + p += l; + p = skipspace(p); + + while (*p && ((*p == '"' && *(p + 1) != '/') || (*p != '"' && *p != '/'))) { + char *tmp, *ent_chunk; + + l = chunklen(p, check_colon(p)); + ent_chunk = dequote(p, l, logopt); + if (!ent_chunk) { + if (strstr(myoptions, "fstype=autofs") && + strstr(myoptions, "hosts")) { + warn(logopt, MODPREFIX + "null location or out of memory"); + free(myoptions); + free(loc); + return 0; + } + goto next; + } + + /* Location can't begin with a '/' */ + if (*p == '/') { + warn(logopt, + MODPREFIX "error location begins with \"/\""); + free(ent_chunk); + free(myoptions); + free(loc); + return 0; + } + + if (!validate_location(logopt, ent_chunk)) { + free(ent_chunk); + free(myoptions); + free(loc); + return 0; + } + + debug(logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, ent_chunk); + + tmp = realloc(loc, strlen(loc) + l + 2); + if (!tmp) { + error(logopt, MODPREFIX "out of memory"); + free(ent_chunk); + free(myoptions); + free(loc); + return 0; + } + loc = tmp; + + strcat(loc, " "); + strcat(loc, ent_chunk); + + free(ent_chunk); +next: + p += l; + p = skipspace(p); + } + + *options = myoptions; + *location = loc; + + return (p - ent); +} + +static void cleanup_multi_triggers(struct autofs_point *ap, + struct mapent *me, const char *root, int start, + const char *base) +{ + char path[PATH_MAX + 1]; + char offset[PATH_MAX + 1]; + char *poffset = offset; + struct mapent *oe; + struct list_head *mm_root, *pos; + const char o_root[] = "/"; + const char *mm_base; + + mm_root = &me->multi->multi_list; + + if (!base) + mm_base = o_root; + else + mm_base = base; + + pos = NULL; + + /* Make sure "none" of the offsets have an active mount. */ + while ((poffset = cache_get_offset(mm_base, poffset, start, mm_root, &pos))) { + oe = cache_lookup_offset(mm_base, poffset, start, &me->multi_list); + /* root offset is a special case */ + if (!oe || !oe->mapent || (strlen(oe->key) - start) == 1) + continue; + + strcpy(path, root); + strcat(path, poffset); + if (umount(path)) { + error(ap->logopt, "error recovering from mount fail"); + error(ap->logopt, "cannot umount offset %s", path); + } + } + + return; +} + +static int mount_subtree(struct autofs_point *ap, struct mapent *me, + const char *name, char *loc, char *options, void *ctxt) +{ + struct mapent *mm; + struct mapent *ro; + char *mm_root, *mm_base, *mm_key; + const char *mnt_root; + unsigned int mm_root_len, mnt_root_len; + int start, ret = 0, rv; + + rv = 0; + + mm = me->multi; + mm_key = mm->key; + + if (*mm_key == '/') { + mm_root = mm_key; + start = strlen(mm_key); + } else { + start = strlen(ap->path) + strlen(mm_key) + 1; + mm_root = alloca(start + 3); + strcpy(mm_root, ap->path); + strcat(mm_root, "/"); + strcat(mm_root, mm_key); + } + mm_root_len = strlen(mm_root); + + mnt_root = mm_root; + mnt_root_len = mm_root_len; + + if (me == me->multi) { + /* name = NULL */ + /* destination = mm_root */ + mm_base = "/"; + + /* Mount root offset if it exists */ + ro = cache_lookup_offset(mm_base, mm_base, strlen(mm_root), &me->multi_list); + if (ro) { + char *myoptions, *ro_loc, *tmp; + int namelen = name ? strlen(name) : 0; + const char *root; + int ro_len; + + rv = parse_mapent(ro->mapent, + options, &myoptions, &ro_loc, ap->logopt); + if (!rv) { + warn(ap->logopt, + MODPREFIX "failed to parse root offset"); + cache_delete_offset_list(me->mc, name); + return 1; + } + ro_len = 0; + if (ro_loc) + ro_len = strlen(ro_loc); + + tmp = alloca(mnt_root_len + 2); + strcpy(tmp, mnt_root); + tmp[mnt_root_len] = '/'; + tmp[mnt_root_len + 1] = '\0'; + root = tmp; + + rv = sun_mount(ap, root, name, namelen, ro_loc, ro_len, myoptions, ctxt); + + free(myoptions); + if (ro_loc) + free(ro_loc); + } + + if (ro && rv == 0) { + ret = mount_multi_triggers(ap, me, mnt_root, start, mm_base); + if (ret == -1) { + error(ap->logopt, MODPREFIX + "failed to mount offset triggers"); + cleanup_multi_triggers(ap, me, mnt_root, start, mm_base); + return 1; + } + } else if (rv <= 0) { + ret = mount_multi_triggers(ap, me, mm_root, start, mm_base); + if (ret == -1) { + error(ap->logopt, MODPREFIX + "failed to mount offset triggers"); + cleanup_multi_triggers(ap, me, mm_root, start, mm_base); + return 1; + } + } + } else { + int loclen = strlen(loc); + int namelen = strlen(name); + + mnt_root = name; + + /* name = mm_root + mm_base */ + /* destination = mm_root + mm_base = name */ + mm_base = &me->key[start]; + + rv = sun_mount(ap, mnt_root, name, namelen, loc, loclen, options, ctxt); + if (rv == 0) { + ret = mount_multi_triggers(ap, me->multi, mnt_root, start, mm_base); + if (ret == -1) { + error(ap->logopt, MODPREFIX + "failed to mount offset triggers"); + cleanup_multi_triggers(ap, me, mnt_root, start, mm_base); + return 1; + } + } else if (rv < 0) { + char *mm_root_base = alloca(strlen(mm_root) + strlen(mm_base) + 1); + + strcpy(mm_root_base, mm_root); + strcat(mm_root_base, mm_base); + + ret = mount_multi_triggers(ap, me->multi, mm_root_base, start, mm_base); + if (ret == -1) { + error(ap->logopt, MODPREFIX + "failed to mount offset triggers"); + cleanup_multi_triggers(ap, me, mm_root, start, mm_base); + return 1; + } + } + } + + /* Mount for base of tree failed */ + if (rv > 0) + return rv; + + /* + * Convert fail on nonstrict, non-empty multi-mount + * to success + */ + if (rv < 0 && ret > 0) + rv = 0; + + return rv; +} + +static char *do_expandsunent(const char *src, const char *key, + const struct substvar *svc, int slashify_colons) +{ + char *mapent; + int len; + + len = expandsunent(src, NULL, key, svc, slashify_colons); + if (len == 0) { + errno = EINVAL; + return NULL; + } + len++; + + mapent = malloc(len); + if (!mapent) + return NULL; + memset(mapent, 0, len); + + expandsunent(src, mapent, key, svc, slashify_colons); + + return mapent; +} + +/* + * syntax is: + * [-options] location [location] ... + * [-options] [mountpoint [-options] location [location] ... ]... + * + * There are three ways this routine can be called. One where we parse + * offsets in a multi-mount entry adding them to the cache for later lookups. + * Another where we parse a multi-mount entry looking for a root offset mount + * and mount it if it exists and also mount its offsets down to the first + * level nexting point. Finally to mount non multi-mounts and to mount a + * lower level multi-mount nesting point and its offsets. + */ +int parse_mount(struct autofs_point *ap, const char *name, + int name_len, const char *mapent, void *context) +{ + struct parse_context *ctxt = (struct parse_context *) context; + char buf[MAX_ERR_BUF]; + struct map_source *source; + struct mapent_cache *mc; + struct mapent *me; + char *pmapent, *options; + const char *p; + int mapent_len, rv = 0; + int cur_state; + int slashify = ctxt->slashify_colons; + unsigned int append_options; + + source = ap->entry->current; + ap->entry->current = NULL; + master_source_current_signal(ap->entry); + + mc = source->mc; + + if (!mapent) { + warn(ap->logopt, MODPREFIX "error: empty map entry"); + return 1; + } + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + + /* Offset map entries have been expanded already, avoid expanding + * them again so that the quote handling is consistent between map + * entry locations and (previously expanded) offset map entry + * locations. + */ + if (*name == '/') { + cache_readlock(mc); + me = cache_lookup_distinct(mc, name); + if (me && me->multi && me->multi != me) { + cache_unlock(mc); + mapent_len = strlen(mapent) + 1; + pmapent = malloc(mapent_len + 1); + if (!pmapent) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + logerr(MODPREFIX "malloc: %s", estr); + return 1; + } + memset(pmapent, 0, mapent_len + 1); + strcpy(pmapent, mapent); + goto dont_expand; + } + cache_unlock(mc); + } + + macro_lock(); + ctxt->subst = addstdenv(ctxt->subst, NULL); + + pmapent = do_expandsunent(mapent, name, ctxt->subst, slashify); + if (!pmapent) { + error(ap->logopt, MODPREFIX "failed to expand map entry"); + ctxt->subst = removestdenv(ctxt->subst, NULL); + macro_unlock(); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + mapent_len = strlen(pmapent) + 1; + + ctxt->subst = removestdenv(ctxt->subst, NULL); + macro_unlock(); + +dont_expand: + pthread_setcancelstate(cur_state, NULL); + + debug(ap->logopt, MODPREFIX "expanded entry: %s", pmapent); + + append_options = defaults_get_append_options(); + options = strdup(ctxt->optstr ? ctxt->optstr : ""); + if (!options) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + free(pmapent); + logerr(MODPREFIX "strdup: %s", estr); + return 1; + } + + p = skipspace(pmapent); + + /* Deal with 0 or more options */ + if (*p == '-') { + char *tmp, *mnt_options = NULL; + + do { + char *noptions = NULL; + + p = parse_options(p, &noptions, ap->logopt); + if (mnt_options && noptions && strstr(noptions, mnt_options)) { + free(mnt_options); + mnt_options = noptions; + } else { + tmp = concat_options(mnt_options, noptions); + if (!tmp) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, + MODPREFIX "concat_options: %s", estr); + if (noptions) + free(noptions); + if (mnt_options) + free(mnt_options); + free(options); + free(pmapent); + return 1; + } + mnt_options = tmp; + } + + p = skipspace(p); + } while (*p == '-'); + + if (options && !append_options) { + free(options); + options = NULL; + } + + if (append_options) { + if (options && mnt_options && strstr(mnt_options, options)) { + free(options); + options = mnt_options; + } else { + tmp = concat_options(options, mnt_options); + if (!tmp) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(ap->logopt, MODPREFIX "concat_options: %s", estr); + if (options) + free(options); + if (mnt_options) + free(mnt_options); + free(pmapent); + return 1; + } + options = tmp; + } + } else + options = mnt_options; + } + + debug(ap->logopt, MODPREFIX "gathered options: %s", options); + + if (check_is_multi(p)) { + char *m_root = NULL; + int m_root_len; + time_t age; + int l; + + /* If name starts with "/" it's a direct mount */ + if (*name == '/') { + m_root_len = name_len; + m_root = alloca(m_root_len + 1); + if (!m_root) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + free(options); + free(pmapent); + logerr(MODPREFIX "alloca: %s", estr); + return 1; + } + strcpy(m_root, name); + } else { + m_root_len = strlen(ap->path) + name_len + 1; + m_root = alloca(m_root_len + 1); + if (!m_root) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + free(options); + free(pmapent); + logerr(MODPREFIX "alloca: %s", estr); + return 1; + } + strcpy(m_root, ap->path); + strcat(m_root, "/"); + strcat(m_root, name); + } + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); + cache_readlock(mc); + me = cache_lookup_distinct(mc, name); + if (!me) { + free(options); + free(pmapent); + cache_unlock(mc); + pthread_setcancelstate(cur_state, NULL); + error(ap->logopt, + MODPREFIX "can't find multi root %s", name); + return 1; + } + + cache_multi_writelock(me); + /* So we know we're the multi-mount root */ + if (!me->multi) + me->multi = me; + else { + /* + * The amd host mount type assumes the lookup name + * is the host name for the host mount but amd uses + * ${rhost} for this. + * + * This introduces the possibility of multiple + * concurrent mount requests since constructing a + * mount tree that isn't under the lookup name can't + * take advantage of the kernel queuing of other + * concurrent lookups while the mount tree is + * constructed. + * + * Consequently multi-mount updates (currently only + * done for the internal hosts map which the amd + * parser also uses for its hosts map) can't be + * allowed for amd mounts. + */ + if (source->flags & MAP_FLAG_FORMAT_AMD) { + free(options); + free(pmapent); + cache_multi_unlock(me); + cache_unlock(mc); + pthread_setcancelstate(cur_state, NULL); + return 0; + } + } + + age = me->age; + + /* It's a multi-mount; deal with it */ + do { + char *path, *myoptions, *loc; + int status; + + if ((*p == '"' && *(p + 1) != '/') || (*p != '"' && *p != '/')) { + l = 0; + path = dequote("/", 1, ap->logopt); + debug(ap->logopt, + MODPREFIX "dequote(\"/\") -> %s", path); + } else { + l = span_space(p, mapent_len - (p - pmapent)); + path = sanitize_path(p, l, LKP_MULTI, ap->logopt); + debug(ap->logopt, MODPREFIX + "dequote(\"%.*s\") -> %s", l, p, path); + } + + if (!path) { + warn(ap->logopt, MODPREFIX "null path or out of memory"); + cache_delete_offset_list(mc, name); + cache_multi_unlock(me); + cache_unlock(mc); + free(options); + free(pmapent); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + + p += l; + p = skipspace(p); + + l = parse_mapent(p, options, &myoptions, &loc, ap->logopt); + if (!l) { + cache_delete_offset_list(mc, name); + cache_multi_unlock(me); + cache_unlock(mc); + free(path); + free(options); + free(pmapent); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + + p += l; + p = skipspace(p); + + master_source_current_wait(ap->entry); + ap->entry->current = source; + + status = update_offset_entry(ap, name, + m_root, m_root_len, + path, myoptions, loc, age); + + if (status != CHE_OK) { + warn(ap->logopt, MODPREFIX "error adding multi-mount"); + cache_delete_offset_list(mc, name); + cache_multi_unlock(me); + cache_unlock(mc); + free(path); + free(options); + free(pmapent); + free(myoptions); + if (loc) + free(loc); + pthread_setcancelstate(cur_state, NULL); + return 1; + } + + if (loc) + free(loc); + free(path); + free(myoptions); + } while (*p == '/' || (*p == '"' && *(p + 1) == '/')); + + /* + * We've got the ordered list of multi-mount entries so go + * through and remove any stale entries if this is the top + * of the multi-mount and set the parent entry of each. + */ + if (me == me->multi) + clean_stale_multi_triggers(ap, me, NULL, NULL); + cache_set_parents(me); + + rv = mount_subtree(ap, me, name, NULL, options, ctxt); + + cache_multi_unlock(me); + cache_unlock(mc); + + free(options); + free(pmapent); + pthread_setcancelstate(cur_state, NULL); + + return rv; + } else { + /* Normal (and non-root multi-mount) entries */ + char *loc; + int loclen; + int l; + + /* + * If this is an offset belonging to a multi-mount entry + * it's already been parsed (above) and any option string + * has already been stripped so just use the remainder. + */ + cache_readlock(mc); + if (*name == '/' && + (me = cache_lookup_distinct(mc, name)) && me->multi) { + loc = strdup(p); + if (!loc) { + free(options); + free(pmapent); + cache_unlock(mc); + warn(ap->logopt, MODPREFIX "out of memory"); + return 1; + } + cache_multi_writelock(me); + rv = mount_subtree(ap, me, name, loc, options, ctxt); + cache_multi_unlock(me); + cache_unlock(mc); + free(loc); + free(options); + free(pmapent); + return rv; + } + cache_unlock(mc); + + l = chunklen(p, check_colon(p)); + loc = dequote(p, l, ap->logopt); + if (!loc) { + free(options); + free(pmapent); + warn(ap->logopt, MODPREFIX "null location or out of memory"); + return 1; + } + + /* Location can't begin with a '/' */ + if (*p == '/') { + free(options); + free(pmapent); + free(loc); + warn(ap->logopt, + MODPREFIX "error location begins with \"/\""); + return 1; + } + + if (!validate_location(ap->logopt, loc)) { + free(loc); + free(options); + free(pmapent); + return 1; + } + + debug(ap->logopt, + MODPREFIX "dequote(\"%.*s\") -> %s", l, p, loc); + + p += l; + p = skipspace(p); + + while (*p) { + char *tmp, *ent; + + l = chunklen(p, check_colon(p)); + ent = dequote(p, l, ap->logopt); + if (!ent) { + free(loc); + free(options); + free(pmapent); + warn(ap->logopt, + MODPREFIX "null location or out of memory"); + return 1; + } + + if (!validate_location(ap->logopt, ent)) { + free(ent); + free(loc); + free(options); + free(pmapent); + return 1; + } + + debug(ap->logopt, + MODPREFIX "dequote(\"%.*s\") -> %s", l, p, ent); + + tmp = realloc(loc, strlen(loc) + l + 2); + if (!tmp) { + free(ent); + free(loc); + free(options); + free(pmapent); + error(ap->logopt, MODPREFIX "out of memory"); + return 1; + } + loc = tmp; + + strcat(loc, " "); + strcat(loc, ent); + + free(ent); + + p += l; + p = skipspace(p); + } + + /* + * If options are asking for a hosts map loc should be + * NULL but we see it can contain junk, so .... + */ + if ((strstr(options, "fstype=autofs") && + strstr(options, "hosts"))) { + if (loc) { + free(loc); + loc = NULL; + } + loclen = 0; + } else { + loclen = strlen(loc); + if (loclen == 0) { + free(loc); + free(options); + free(pmapent); + error(ap->logopt, + MODPREFIX "entry %s is empty!", name); + return 1; + } + } + + debug(ap->logopt, + MODPREFIX "core of entry: options=%s, loc=%.*s", + options, loclen, loc); + + if (!strcmp(ap->path, "/-")) + rv = sun_mount(ap, name, name, name_len, + loc, loclen, options, ctxt); + else + rv = sun_mount(ap, ap->path, name, name_len, + loc, loclen, options, ctxt); + + if (loc) + free(loc); + free(options); + free(pmapent); + pthread_setcancelstate(cur_state, NULL); + } + return rv; +} + +int parse_done(void *context) +{ + int rv = 0; + struct parse_context *ctxt = (struct parse_context *) context; + + instance_mutex_lock(); + if (--init_ctr == 0) { + rv = close_mount(mount_nfs); + mount_nfs = NULL; + } + instance_mutex_unlock(); + if (ctxt) + kill_context(ctxt); + + return rv; +} diff --git a/modules/replicated.c b/modules/replicated.c new file mode 100644 index 0000000..315e300 --- /dev/null +++ b/modules/replicated.c @@ -0,0 +1,1178 @@ +/* ----------------------------------------------------------------------- * + * + * repl_list.h - routines for replicated mount server selection + * + * Copyright 2004 Jeff Moyer - All Rights Reserved + * Copyright 2004-2006 Ian Kent - All Rights Reserved + * + * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * A priority ordered list of hosts is created by using the following + * selection rules. + * + * 1) Highest priority in selection is proximity. + * Proximity, in order of precedence is: + * - PROXIMITY_LOCAL, host corresponds to a local interface. + * - PROXIMITY_SUBNET, host is located in a subnet reachable + * through a local interface. + * - PROXIMITY_NETWORK, host is located in a network reachable + * through a local interface. + * - PROXIMITY_OTHER, host is on a network not directlty + * reachable through a local interface. + * + * 2) NFS version and protocol is selected by caclculating the largest + * number of hosts supporting an NFS version and protocol that + * have the closest proximity. These hosts are added to the list + * in response time order. Hosts may have a corresponding weight + * which essentially increaes response time and so influences the + * host order. + * + * 3) Hosts at further proximity that support the selected NFS version + * and protocol are also added to the list in response time order as + * in 2 above. + * + * ----------------------------------------------------------------------- */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpc_subs.h" +#include "replicated.h" +#include "automount.h" + +#ifndef MAX_ERR_BUF +#define MAX_ERR_BUF 512 +#endif + +#define mymax(x, y) (x >= y ? x : y) +#define mmax(x, y, z) (mymax(x, y) == x ? mymax(x, z) : mymax(y, z)) + +void seed_random(void) +{ + int fd; + unsigned int seed; + + fd = open_fd("/dev/urandom", O_RDONLY); + if (fd < 0) { + srandom(monotonic_time(NULL)); + return; + } + + if (read(fd, &seed, sizeof(seed)) != -1) + srandom(seed); + else + srandom(monotonic_time(NULL)); + + close(fd); + + return; +} + +struct host *new_host(const char *name, + struct sockaddr *addr, size_t addr_len, + unsigned int proximity, unsigned int weight, + unsigned int options) +{ + struct host *new; + struct sockaddr *tmp2; + char *tmp1; + + if (!name || !addr) + return NULL; + + tmp1 = strdup(name); + if (!tmp1) + return NULL; + + tmp2 = malloc(addr_len); + if (!tmp2) { + free(tmp1); + return NULL; + } + memcpy(tmp2, addr, addr_len); + + new = malloc(sizeof(struct host)); + if (!new) { + free(tmp1); + free(tmp2); + return NULL; + } + + memset(new, 0, sizeof(struct host)); + + new->name = tmp1; + new->addr_len = addr_len; + new->addr = tmp2; + new->proximity = proximity; + new->weight = weight; + new->options = options; + + return new; +} + +static int add_host(struct host **list, struct host *host) +{ + struct host *this, *last; + + if (!*list) { + *list = host; + return 1; + } + + this = *list; + last = this; + while (this) { + if (this->proximity >= host->proximity) + break; + last = this; + this = this->next; + } + + if (host->cost) { + while (this) { + if (this->proximity != host->proximity) + break; + if (this->cost >= host->cost) + break; + last = this; + this = this->next; + } + } + + if (last == this) { + host->next = last; + *list = host; + return 1; + } + + last->next = host; + host->next = this; + + return 1; +} + +static void free_host(struct host *host) +{ + free(host->name); + free(host->addr); + free(host->path); + free(host); +} + +static void remove_host(struct host **hosts, struct host *host) +{ + struct host *last, *this; + + if (host == *hosts) { + *hosts = (*hosts)->next; + host->next = NULL; + return; + } + + this = *hosts; + last = NULL; + while (this) { + if (this == host) + break; + last = this; + this = this->next; + } + + if (!last || !this) + return; + + last->next = this->next; + host->next = NULL; + + return; +} + +static void delete_host(struct host **hosts, struct host *host) +{ + remove_host(hosts, host); + free_host(host); + return; +} + +void free_host_list(struct host **list) +{ + struct host *this; + + this = *list; + while (this) { + struct host *next = this->next; + free_host(this); + this = next; + } + *list = NULL; +} + +static unsigned int get_nfs_info(unsigned logopt, struct host *host, + struct conn_info *pm_info, struct conn_info *rpc_info, + int proto, unsigned int version, int port) +{ + unsigned int random_selection = host->options & MOUNT_FLAG_RANDOM_SELECT; + unsigned int use_weight_only = host->options & MOUNT_FLAG_USE_WEIGHT_ONLY; + socklen_t len = INET6_ADDRSTRLEN; + char buf[len + 1]; + struct pmap parms; + struct timespec start, end; + unsigned int supported = 0; + double taken = 0; + int status, count = 0; + + if (host->addr) + debug(logopt, "called with host %s(%s) proto %d version 0x%x", + host->name, get_addr_string(host->addr, buf, len), + proto, version); + else + debug(logopt, + "called for host %s proto %d version 0x%x", + host->name, proto, version); + + rpc_info->proto = proto; + if (port < 0) { + if (version & NFS4_REQUESTED) + rpc_info->port = NFS_PORT; + else + port = 0; + } else if (port > 0) + rpc_info->port = port; + + memset(&parms, 0, sizeof(struct pmap)); + parms.pm_prog = NFS_PROGRAM; + parms.pm_prot = proto; + + if (!(version & NFS4_REQUESTED)) + goto v3_ver; + + if (!port) { + status = rpc_portmap_getclient(pm_info, + host->name, host->addr, host->addr_len, + proto, RPC_CLOSE_DEFAULT); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (status) + goto done_ver; + parms.pm_vers = NFS4_VERSION; + status = rpc_portmap_getport(pm_info, &parms, &rpc_info->port); + if (status == -EHOSTUNREACH || status == -ETIMEDOUT) { + supported = status; + goto done_ver; + } else if (status < 0) { + if (version & NFS_VERS_MASK) + goto v3_ver; /* MOUNT_NFS_DEFAULT_PROTOCOL=4 */ + else + goto done_ver; + } + } + + if (rpc_info->proto == IPPROTO_UDP) + status = rpc_udp_getclient(rpc_info, NFS_PROGRAM, NFS4_VERSION); + else + status = rpc_tcp_getclient(rpc_info, NFS_PROGRAM, NFS4_VERSION); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (!status) { + clock_gettime(CLOCK_MONOTONIC, &start); + status = rpc_ping_proto(rpc_info); + clock_gettime(CLOCK_MONOTONIC, &end); + if (status == -ETIMEDOUT) { + supported = status; + goto done_ver; + } else if (status > 0) { + double reply; + if (random_selection) { + /* Random value between 0 and 1 */ + reply = ((float) random())/((float) RAND_MAX+1); + debug(logopt, + "nfs v4 random selection time: %f", reply); + } else { + reply = monotonic_elapsed(start, end); + debug(logopt, "nfs v4 rpc ping time: %f", reply); + } + taken += reply; + count++; + supported = NFS4_SUPPORTED; + } + } + + if (!(version & NFS_VERS_MASK)) + goto done_ver; + +v3_ver: + if (!(version & NFS3_REQUESTED)) + goto v2_ver; + + if (!port && !pm_info->client) { + status = rpc_portmap_getclient(pm_info, + host->name, host->addr, host->addr_len, + proto, RPC_CLOSE_DEFAULT); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (status) + goto done_ver; + } + + if (!port) { + parms.pm_vers = NFS3_VERSION; + status = rpc_portmap_getport(pm_info, &parms, &rpc_info->port); + if (status == -EHOSTUNREACH || status == -ETIMEDOUT) { + supported = status; + goto done_ver; + } else if (status < 0) + goto v2_ver; + } + + if (rpc_info->proto == IPPROTO_UDP) + status = rpc_udp_getclient(rpc_info, NFS_PROGRAM, NFS3_VERSION); + else + status = rpc_tcp_getclient(rpc_info, NFS_PROGRAM, NFS3_VERSION); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (!status) { + clock_gettime(CLOCK_MONOTONIC, &start); + status = rpc_ping_proto(rpc_info); + clock_gettime(CLOCK_MONOTONIC, &end); + if (status == -ETIMEDOUT) { + supported = status; + goto done_ver; + } else if (status > 0) { + double reply; + if (random_selection) { + /* Random value between 0 and 1 */ + reply = ((float) random())/((float) RAND_MAX+1); + debug(logopt, + "nfs v3 random selection time: %f", reply); + } else { + reply = monotonic_elapsed(start, end); + debug(logopt, "nfs v3 rpc ping time: %f", reply); + } + taken += reply; + count++; + supported |= NFS3_SUPPORTED; + } + } + +v2_ver: + if (!(version & NFS2_REQUESTED)) + goto done_ver; + + if (!port && !pm_info->client) { + status = rpc_portmap_getclient(pm_info, + host->name, host->addr, host->addr_len, + proto, RPC_CLOSE_DEFAULT); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (status) + goto done_ver; + } + + if (!port) { + parms.pm_vers = NFS2_VERSION; + status = rpc_portmap_getport(pm_info, &parms, &rpc_info->port); + if (status == -EHOSTUNREACH || status == -ETIMEDOUT) { + supported = status; + goto done_ver; + } else if (status < 0) + goto done_ver; + } + + if (rpc_info->proto == IPPROTO_UDP) + status = rpc_udp_getclient(rpc_info, NFS_PROGRAM, NFS2_VERSION); + else + status = rpc_tcp_getclient(rpc_info, NFS_PROGRAM, NFS2_VERSION); + if (status == -EHOSTUNREACH) { + supported = status; + goto done_ver; + } else if (!status) { + clock_gettime(CLOCK_MONOTONIC, &start); + status = rpc_ping_proto(rpc_info); + clock_gettime(CLOCK_MONOTONIC, &end); + if (status == -ETIMEDOUT) + supported = status; + else if (status > 0) { + double reply; + if (random_selection) { + /* Random value between 0 and 1 */ + reply = ((float) random())/((float) RAND_MAX+1); + debug(logopt, + "nfs v2 random selection time: %f", reply); + } else { + reply = monotonic_elapsed(start, end);; + debug(logopt, "nfs v2 rpc ping time: %f", reply); + } + taken += reply; + count++; + supported |= NFS2_SUPPORTED; + } + } + +done_ver: + if (rpc_info->proto == IPPROTO_UDP) { + rpc_destroy_udp_client(rpc_info); + rpc_destroy_udp_client(pm_info); + } else { + rpc_destroy_tcp_client(rpc_info); + rpc_destroy_tcp_client(pm_info); + } + + if (count) { + /* + * Average response time to 7 significant places as + * integral type. + */ + if (use_weight_only) + host->cost = 1; + else + host->cost = (unsigned long) ((taken * 1000000) / count); + + /* Allow for user bias */ + if (host->weight) + host->cost *= (host->weight + 1); + + debug(logopt, "host %s cost %ld weight %d", + host->name, host->cost, host->weight); + } + + return supported; +} + +static int get_vers_and_cost(unsigned logopt, struct host *host, + unsigned int version, int port) +{ + struct conn_info pm_info, rpc_info; + time_t timeout = RPC_TIMEOUT; + unsigned int supported, vers = (NFS_VERS_MASK | NFS4_VERS_MASK); + int ret = 0; + + memset(&pm_info, 0, sizeof(struct conn_info)); + memset(&rpc_info, 0, sizeof(struct conn_info)); + + if (host->proximity == PROXIMITY_NET) + timeout = RPC_TIMEOUT * 2; + else if (host->proximity == PROXIMITY_OTHER) + timeout = RPC_TIMEOUT * 8; + + rpc_info.host = host->name; + rpc_info.addr = host->addr; + rpc_info.addr_len = host->addr_len; + rpc_info.program = NFS_PROGRAM; + rpc_info.timeout.tv_sec = timeout; + rpc_info.close_option = RPC_CLOSE_DEFAULT; + rpc_info.client = NULL; + + vers &= version; + + if (version & TCP_REQUESTED) { + supported = get_nfs_info(logopt, host, + &pm_info, &rpc_info, IPPROTO_TCP, vers, port); + if (IS_ERR(supported)) { + if (ERR(supported) == EHOSTUNREACH || + ERR(supported) == ETIMEDOUT) + return ret; + } else if (supported) { + ret = 1; + host->version |= supported; + } + } + + if (version & UDP_REQUESTED) { + supported = get_nfs_info(logopt, host, + &pm_info, &rpc_info, IPPROTO_UDP, vers, port); + if (IS_ERR(supported)) { + if (!ret && ERR(supported) == ETIMEDOUT) + return ret; + } else if (supported) { + ret = 1; + host->version |= (supported << 8); + } + } + + return ret; +} + +static int get_supported_ver_and_cost(unsigned logopt, struct host *host, + unsigned int version, int port) +{ + unsigned int random_selection = host->options & MOUNT_FLAG_RANDOM_SELECT; + unsigned int use_weight_only = host->options & MOUNT_FLAG_USE_WEIGHT_ONLY; + socklen_t len = INET6_ADDRSTRLEN; + char buf[len + 1]; + struct conn_info pm_info, rpc_info; + int proto; + unsigned int vers; + struct timespec start, end; + double taken = 0; + time_t timeout = RPC_TIMEOUT; + int status = 0; + + if (host->addr) + debug(logopt, "called with host %s(%s) version 0x%x", + host->name, get_addr_string(host->addr, buf, len), + version); + else + debug(logopt, "called with host %s version 0x%x", + host->name, version); + + memset(&pm_info, 0, sizeof(struct conn_info)); + memset(&rpc_info, 0, sizeof(struct conn_info)); + + if (host->proximity == PROXIMITY_NET) + timeout = RPC_TIMEOUT * 2; + else if (host->proximity == PROXIMITY_OTHER) + timeout = RPC_TIMEOUT * 8; + + rpc_info.host = host->name; + rpc_info.addr = host->addr; + rpc_info.addr_len = host->addr_len; + rpc_info.program = NFS_PROGRAM; + rpc_info.timeout.tv_sec = timeout; + rpc_info.close_option = RPC_CLOSE_DEFAULT; + rpc_info.client = NULL; + + /* + * The version passed in is the version as defined in + * include/replicated.h. However, the version we want to send + * off to the rpc calls should match the program version of NFS. + * So, we do the conversion here. + */ + if (version & UDP_SELECTED_MASK) { + proto = IPPROTO_UDP; + version >>= 8; + } else + proto = IPPROTO_TCP; + + switch (version) { + case NFS2_SUPPORTED: + vers = NFS2_VERSION; + break; + case NFS3_SUPPORTED: + vers = NFS3_VERSION; + break; + case NFS4_SUPPORTED: + vers = NFS4_VERSION; + break; + default: + crit(logopt, "called with invalid version: 0x%x\n", version); + return 0; + } + + rpc_info.proto = proto; + + if (port > 0) + rpc_info.port = port; + else if (vers & NFS4_VERSION && port < 0) + rpc_info.port = NFS_PORT; + else { + struct pmap parms; + int ret = rpc_portmap_getclient(&pm_info, + host->name, host->addr, host->addr_len, + proto, RPC_CLOSE_DEFAULT); + if (ret) + return 0; + + memset(&parms, 0, sizeof(struct pmap)); + parms.pm_prog = NFS_PROGRAM; + parms.pm_prot = rpc_info.proto; + parms.pm_vers = vers; + ret = rpc_portmap_getport(&pm_info, &parms, &rpc_info.port); + if (ret < 0) + goto done; + } + + if (rpc_info.proto == IPPROTO_UDP) + status = rpc_udp_getclient(&rpc_info, NFS_PROGRAM, vers); + else + status = rpc_tcp_getclient(&rpc_info, NFS_PROGRAM, vers); + if (status == -EHOSTUNREACH) + goto done; + else if (!status) { + clock_gettime(CLOCK_MONOTONIC, &start); + status = rpc_ping_proto(&rpc_info); + clock_gettime(CLOCK_MONOTONIC, &end); + if (status > 0) { + if (random_selection) { + /* Random value between 0 and 1 */ + taken = ((float) random())/((float) RAND_MAX+1); + debug(logopt, "random selection time %f", taken); + } else { + taken = monotonic_elapsed(start, end); + debug(logopt, "rpc ping time %f", taken); + } + } + } +done: + if (rpc_info.proto == IPPROTO_UDP) { + rpc_destroy_udp_client(&rpc_info); + rpc_destroy_udp_client(&pm_info); + } else { + rpc_destroy_tcp_client(&rpc_info); + rpc_destroy_tcp_client(&pm_info); + } + + if (status) { + /* Response time to 7 significant places as integral type. */ + if (use_weight_only) + host->cost = 1; + else + host->cost = (unsigned long) (taken * 1000000); + + /* Allow for user bias */ + if (host->weight) + host->cost *= (host->weight + 1); + + debug(logopt, "cost %ld weight %d", host->cost, host->weight); + + return 1; + } + + return 0; +} + +int prune_host_list(unsigned logopt, struct host **list, + unsigned int vers, int port) +{ + struct host *this, *last, *first; + struct host *new = NULL; + unsigned int proximity, selected_version = 0; + unsigned int v2_tcp_count, v3_tcp_count, v4_tcp_count; + unsigned int v2_udp_count, v3_udp_count, v4_udp_count; + unsigned int max_udp_count, max_tcp_count, max_count; + int status; + int kern_vers; + + if (!*list) + return 0; + + /* If we're using the host name then there's no point probing + * avialability and respose time. + */ + if (defaults_use_hostname_for_mounts()) + return 1; + + /* Use closest hosts to choose NFS version */ + + first = *list; + + /* Get proximity of first entry after local entries */ + this = first; + while (this && this->proximity == PROXIMITY_LOCAL) + this = this->next; + first = this; + + /* + * Check for either a list containing only proximity local hosts + * or a single host entry whose proximity isn't local. If so + * return immediately as we don't want to add probe latency for + * the common case of a single filesystem mount request. + * + * But, if the kernel understands text nfs mount options then + * mount.nfs most likely bypasses its probing and lets the kernel + * do all the work. This can lead to long timeouts for hosts that + * are not available so check the kernel version and mount.nfs + * version and probe singleton mounts if the kernel version is + * greater than 2.6.22 and mount.nfs version is greater than 1.1.1. + * But also allow the MOUNT_WAIT configuration parameter to override + * the probing. + */ + if (nfs_mount_uses_string_options && + defaults_get_mount_wait() == -1 && + (kern_vers = linux_version_code()) > KERNEL_VERSION(2, 6, 22)) { + if (!this) + return 1; + } else { + if (!this || !this->next) + return 1; + } + + proximity = this->proximity; + while (this) { + struct host *next = this->next; + + if (this->proximity != proximity) + break; + + if (this->name) { + status = get_vers_and_cost(logopt, this, vers, port); + if (!status) { + if (this == first) { + first = next; + if (next) + proximity = next->proximity; + } + delete_host(list, this); + } + } + this = next; + } + + /* + * The list of hosts that aren't proximity local may now + * be empty if we haven't been able probe any so we need + * to check again for a list containing only proximity + * local hosts. + */ + if (!first) + return 1; + + last = this; + + /* Select NFS version of highest number of closest servers */ + + v4_tcp_count = v3_tcp_count = v2_tcp_count = 0; + v4_udp_count = v3_udp_count = v2_udp_count = 0; + + this = first; + do { + if (this->version & NFS4_TCP_SUPPORTED) + v4_tcp_count++; + + if (this->version & NFS3_TCP_SUPPORTED) + v3_tcp_count++; + + if (this->version & NFS2_TCP_SUPPORTED) + v2_tcp_count++; + + if (this->version & NFS4_UDP_SUPPORTED) + v4_udp_count++; + + if (this->version & NFS3_UDP_SUPPORTED) + v3_udp_count++; + + if (this->version & NFS2_UDP_SUPPORTED) + v2_udp_count++; + + this = this->next; + } while (this && this != last); + + max_tcp_count = mmax(v4_tcp_count, v3_tcp_count, v2_tcp_count); + max_udp_count = mmax(v4_udp_count, v3_udp_count, v2_udp_count); + max_count = mymax(max_tcp_count, max_udp_count); + + if (max_count == v4_tcp_count) { + selected_version = NFS4_TCP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS4 over TCP"); + } else if (max_count == v3_tcp_count) { + selected_version = NFS3_TCP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS3 over TCP"); + } else if (max_count == v2_tcp_count) { + selected_version = NFS2_TCP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS2 over TCP"); + } else if (max_count == v4_udp_count) { + selected_version = NFS4_UDP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS4 over UDP"); + } else if (max_count == v3_udp_count) { + selected_version = NFS3_UDP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS3 over UDP"); + } else if (max_count == v2_udp_count) { + selected_version = NFS2_UDP_SUPPORTED; + debug(logopt, + "selected subset of hosts that support NFS2 over UDP"); + } + + /* Add local and hosts with selected version to new list */ + this = *list; + do { + struct host *next = this->next; + if (this->version & selected_version || + this->proximity == PROXIMITY_LOCAL) { + this->version = selected_version; + remove_host(list, this); + add_host(&new, this); + } + this = next; + } while (this && this != last); + + /* + * Now go through rest of list and check for chosen version + * and add to new list if selected version is supported. + */ + + first = last; + this = first; + while (this) { + struct host *next = this->next; + if (!this->name) { + remove_host(list, this); + add_host(&new, this); + } else { + status = get_supported_ver_and_cost(logopt, this, + selected_version, port); + if (status) { + this->version = selected_version; + remove_host(list, this); + add_host(&new, this); + } + } + this = next; + } + + free_host_list(list); + *list = new; + + return 1; +} + +static int add_new_host(struct host **list, + const char *host, unsigned int weight, + struct addrinfo *host_addr, + unsigned int rr, unsigned int options) +{ + struct host *new; + unsigned int prx; + int addr_len; + + prx = get_proximity(host_addr->ai_addr); + + /* + * If we want the weight to be the determining factor + * when selecting a host, or we are using random selection, + * then all hosts must have the same proximity. However, + * if this is the local machine it should always be used + * since it is certainly available. + */ + if (prx != PROXIMITY_LOCAL && + (options & (MOUNT_FLAG_USE_WEIGHT_ONLY | + MOUNT_FLAG_RANDOM_SELECT))) + prx = PROXIMITY_SUBNET; + + /* + * If we tried to add an IPv6 address and we don't have IPv6 + * support return success in the hope of getting an IPv4 + * address later. + */ + if (prx == PROXIMITY_UNSUPPORTED) + return 1; + if (prx == PROXIMITY_ERROR) + return 0; + + if (host_addr->ai_addr->sa_family == AF_INET) + addr_len = INET_ADDRSTRLEN; + else if (host_addr->ai_addr->sa_family == AF_INET6) + addr_len = INET6_ADDRSTRLEN; + else + return 0; + + new = new_host(host, host_addr->ai_addr, addr_len, prx, weight, options); + if (!new) + return 0; + + if (!add_host(list, new)) { + free_host(new); + return 0; + } + new->rr = rr; + + return 1; +} + +static int add_host_addrs(struct host **list, const char *host, + unsigned int weight, unsigned int options) +{ + struct addrinfo hints, *ni, *this; + char *n_ptr; + char *name = n_ptr = strdup(host); + int len; + char buf[MAX_ERR_BUF]; + int rr = 0, rr4 = 0, rr6 = 0; + int ret; + + if (!name) { + char *estr = strerror_r(errno, buf, MAX_ERR_BUF); + error(LOGOPT_ANY, "strdup: %s", estr); + error(LOGOPT_ANY, "failed to add host %s", host); + return 0; + } + len = strlen(name); + + if (name[0] == '[' && name[--len] == ']') { + name[len] = '\0'; + name++; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + ret = getaddrinfo(name, NULL, &hints, &ni); + if (ret) + goto try_name; + + this = ni; + while (this) { + ret = add_new_host(list, host, weight, this, 0, options); + if (!ret) + break; + this = this->ai_next; + } + freeaddrinfo(ni); + goto done; + +try_name: + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + ret = getaddrinfo(name, NULL, &hints, &ni); + if (ret) { + error(LOGOPT_ANY, "hostname lookup failed: %s", + gai_strerror(ret)); + free(name); + return 0; + } + + this = ni; + while (this) { + if (this->ai_family == AF_INET) { + struct sockaddr_in *addr = (struct sockaddr_in *) this->ai_addr; + if (addr->sin_addr.s_addr != INADDR_LOOPBACK) + rr4++; + } else if (this->ai_family == AF_INET6) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) this->ai_addr; + if (!IN6_IS_ADDR_LOOPBACK(addr->sin6_addr.s6_addr32)) + rr6++; + } + this = this->ai_next; + } + if (rr4 > 1 || rr6 > 1) + rr++; + this = ni; + while (this) { + ret = add_new_host(list, host, weight, this, rr, options); + if (!ret) + break; + this = this->ai_next; + } + freeaddrinfo(ni); +done: + free(n_ptr); + return ret; +} + +static int add_path(struct host *hosts, const char *path, int len) +{ + struct host *this; + char *tmp, *tmp2; + + tmp = alloca(len + 1); + if (!tmp) + return 0; + + strncpy(tmp, path, len); + tmp[len] = '\0'; + + this = hosts; + while (this) { + if (!this->path) { + tmp2 = strdup(tmp); + if (!tmp2) + return 0; + this->path = tmp2; + } + this = this->next; + } + + return 1; +} + +static int add_local_path(struct host **hosts, const char *path) +{ + struct host *new; + char *tmp; + + tmp = strdup(path); + if (!tmp) + return 0; + + new = malloc(sizeof(struct host)); + if (!new) { + free(tmp); + return 0; + } + + memset(new, 0, sizeof(struct host)); + + new->path = tmp; + new->proximity = PROXIMITY_LOCAL; + new->version = NFS_VERS_MASK; + new->name = NULL; + new->addr = NULL; + new->weight = new->cost = 0; + + add_host(hosts, new); + + return 1; +} + +static char *seek_delim(const char *s) +{ + const char *p = s; + char *delim; + + delim = strpbrk(p, "(, \t:"); + if (delim && *delim != ':' && (delim == s || *(delim - 1) != '\\')) + return delim; + + while (*p) { + if (*p != ':') { + p++; + continue; + } + if (!strncmp(p, ":/", 2)) + return (char *) p; + p++; + } + + return NULL; +} + +int parse_location(unsigned logopt, struct host **hosts, + const char *list, unsigned int options) +{ + char *str, *p, *delim; + unsigned int empty = 1; + + if (!list) + return 0; + + str = strdup(list); + if (!str) + return 0; + + p = str; + + while (p && *p) { + char *next = NULL; + int weight = 0; + + p += strspn(p, " \t,"); + delim = seek_delim(p); + + if (delim) { + if (*delim == '(') { + char *w = delim + 1; + + *delim = '\0'; + + delim = strchr(w, ')'); + if (delim) { + *delim = '\0'; + weight = atoi(w); + } + else { + /* syntax error - Mismatched brackets */ + free_host_list(hosts); + free(str); + return 0; + } + delim++; + } + + if (*delim == ':') { + char *path; + + *delim = '\0'; + path = delim + 1; + + /* Oh boy - might have spaces in the path */ + next = path; + while (*next && strncmp(next, ":/", 2)) + next++; + + /* No spaces in host names at least */ + if (*next == ':') { + while (*next && + (*next != ' ' && *next != '\t')) + next--; + *next++ = '\0'; + } + + if (p != delim) { + if (!add_host_addrs(hosts, p, weight, options)) { + if (empty) { + p = next; + continue; + } + } + + if (!add_path(*hosts, path, strlen(path))) { + free_host_list(hosts); + free(str); + return 0; + } + } else { + if (!add_local_path(hosts, path)) { + p = next; + continue; + } + } + } else if (*delim != '\0') { + *delim = '\0'; + next = delim + 1; + + if (!add_host_addrs(hosts, p, weight, options)) { + p = next; + continue; + } + + empty = 0; + } + } else { + /* syntax error - no mount path */ + free_host_list(hosts); + free(str); + return 0; + } + + p = next; + } + + free(str); + return 1; +} + +void dump_host_list(struct host *hosts) +{ + struct host *this; + + if (!hosts) + return; + + this = hosts; + while (this) { + logmsg("name %s path %s version %x proximity %u weight %u cost %u", + this->name, this->path, this->version, + this->proximity, this->weight, this->cost); + this = this->next; + } + return; +} + diff --git a/patches/autofs4-2.6.18-v5-update-20090903.patch b/patches/autofs4-2.6.18-v5-update-20090903.patch new file mode 100644 index 0000000..1fd315a --- /dev/null +++ b/patches/autofs4-2.6.18-v5-update-20090903.patch @@ -0,0 +1,3928 @@ +--- linux-2.6.18.orig/fs/autofs4/root.c ++++ linux-2.6.18/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ struct inode_operations autofs4_dir_inod + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_dentry; +- struct vfsmount *mnt = file->f_vfsmnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,155 +82,30 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (!ret) { +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -ENOENT; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -279,9 +122,6 @@ static int try_to_fill_dentry(struct den + + DPRINTK("mount done status=%d", status); + +- if (status && dentry->d_inode) +- return status; /* Try to get the kernel to invalidate this dentry */ +- + /* Turn this into a real negative dentry? */ + if (status == -ENOENT) { + spin_lock(&dentry->d_lock); +@@ -293,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -320,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -335,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && list_empty(&dentry->d_subdirs)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -400,14 +254,36 @@ static int autofs4_revalidate(struct den + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + int oz_mode = autofs4_oz_mode(sbi); + int flags = nd ? nd->flags : 0; +- int status = 0; ++ int status; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { +- if (!oz_mode) +- status = try_to_fill_dentry(dentry, flags); +- return !status; ++ /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ ++ if (oz_mode) ++ return 1; ++ ++ /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* ++ * A zero status is success otherwise we have a ++ * negative error code. ++ */ ++ status = try_to_fill_dentry(dentry, flags); ++ if (status == 0) ++ return 1; ++ ++ return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -421,9 +297,20 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); +- if (!oz_mode) +- status = try_to_fill_dentry(dentry, flags); +- return !status; ++ ++ /* The daemon never causes a mount to trigger */ ++ if (oz_mode) ++ return 1; ++ ++ /* ++ * A zero status is success otherwise we have a ++ * negative error code. ++ */ ++ status = try_to_fill_dentry(dentry, flags); ++ if (status == 0) ++ return 1; ++ ++ return status; + } + spin_unlock(&dcache_lock); + +@@ -440,6 +327,17 @@ void autofs4_dentry_release(struct dentr + de->d_fsdata = NULL; + + if (inf) { ++ struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); ++ ++ if (sbi) { ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ } ++ + inf->dentry = NULL; + inf->inode = NULL; + +@@ -459,10 +357,116 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Bad luck, we've already been dentry_iput */ ++ if (!dentry->d_inode) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ + /* Lookups in the root directory */ + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -478,29 +482,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. +- */ +- dentry->d_op = &autofs4_root_dentry_operations; ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { ++ /* ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. ++ */ ++ dentry->d_op = &autofs4_root_dentry_operations; ++ ++ /* ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. ++ */ ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); ++ } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -515,19 +557,47 @@ static struct dentry *autofs4_lookup(str + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { ++ if (unhashed) ++ dput(unhashed); + return ERR_PTR(-ERESTARTNOINTR); + } + } ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this +- * lookup even if the dentry is positive. Returning ENOENT here +- * doesn't do the right thing for all system calls, but it should +- * be OK for the operations we permit from an autofs. ++ * lookup. Returning ENOENT here doesn't do the right thing ++ * for all system calls, but it should be OK for the operations ++ * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) +- return ERR_PTR(-ENOENT); ++ if (!oz_mode && d_unhashed(dentry)) { ++ /* ++ * A user space application can (and has done in the past) ++ * remove and re-create this directory during the callback. ++ * This can leave us with an unhashed dentry, but a ++ * successful mount! So we need to perform another ++ * cached lookup in case the dentry now exists. ++ */ ++ struct dentry *parent = dentry->d_parent; ++ struct dentry *new = d_lookup(parent, &dentry->d_name); ++ if (new != NULL) ++ dentry = new; ++ else ++ dentry = ERR_PTR(-ENOENT); ++ ++ if (unhashed) ++ dput(unhashed); ++ ++ return dentry; ++ } ++ ++ if (unhashed) ++ return unhashed; + + return NULL; + } +@@ -549,21 +619,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -578,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -589,9 +671,9 @@ static int autofs4_dir_symlink(struct in + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want +- * this, because since the unlink is probably the result of an expire. +- * We simply d_drop it, which allows the dentry lookup to remount it +- * if necessary. ++ * this, because the unlink is probably the result of an expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -620,7 +702,15 @@ static int autofs4_dir_unlink(struct ino + + dir->i_mtime = CURRENT_TIME; + +- d_drop(dentry); ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); ++ spin_lock(&dentry->d_lock); ++ __d_drop(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&dcache_lock); + + return 0; + } +@@ -631,6 +721,9 @@ static int autofs4_dir_rmdir(struct inod + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct autofs_info *p_ino; + ++ DPRINTK("dentry %p, removing %.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ + if (!autofs4_oz_mode(sbi)) + return -EACCES; + +@@ -639,6 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -673,11 +770,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -729,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -830,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_vfsmnt, p); + +--- linux-2.6.18.orig/fs/namei.c ++++ linux-2.6.18/fs/namei.c +@@ -372,6 +372,29 @@ void release_open_intent(struct nameidat + fput(nd->intent.open.file); + } + ++static inline struct dentry *do_revalidate(struct dentry *dentry, struct nameidata *nd) ++{ ++ int status = dentry->d_op->d_revalidate(dentry, nd); ++ if (unlikely(status <= 0)) { ++ /* ++ * The dentry failed validation. ++ * If d_revalidate returned 0 attempt to invalidate ++ * the dentry otherwise d_revalidate is asking us ++ * to return a fail status. ++ */ ++ if (!status) { ++ if (!d_invalidate(dentry)) { ++ dput(dentry); ++ dentry = NULL; ++ } ++ } else { ++ dput(dentry); ++ dentry = ERR_PTR(status); ++ } ++ } ++ return dentry; ++} ++ + /* + * Internal lookup() using the new generic dcache. + * SMP-safe +@@ -386,12 +409,9 @@ static struct dentry * cached_lookup(str + if (!dentry) + dentry = d_lookup(parent, name); + +- if (dentry && dentry->d_op && dentry->d_op->d_revalidate) { +- if (!dentry->d_op->d_revalidate(dentry, nd) && !d_invalidate(dentry)) { +- dput(dentry); +- dentry = NULL; +- } +- } ++ if (dentry && dentry->d_op && dentry->d_op->d_revalidate) ++ dentry = do_revalidate(dentry, nd); ++ + return dentry; + } + +@@ -484,10 +504,9 @@ static struct dentry * real_lookup(struc + */ + mutex_unlock(&dir->i_mutex); + if (result->d_op && result->d_op->d_revalidate) { +- if (!result->d_op->d_revalidate(result, nd) && !d_invalidate(result)) { +- dput(result); ++ result = do_revalidate(result, nd); ++ if (!result) + result = ERR_PTR(-ENOENT); +- } + } + return result; + } +@@ -767,12 +786,12 @@ need_lookup: + goto done; + + need_revalidate: +- if (dentry->d_op->d_revalidate(dentry, nd)) +- goto done; +- if (d_invalidate(dentry)) +- goto done; +- dput(dentry); +- goto need_lookup; ++ dentry = do_revalidate(dentry, nd); ++ if (!dentry) ++ goto need_lookup; ++ if (IS_ERR(dentry)) ++ goto fail; ++ goto done; + + fail: + return PTR_ERR(dentry); +--- linux-2.6.18.orig/fs/autofs/init.c ++++ linux-2.6.18/fs/autofs/init.c +@@ -24,7 +24,7 @@ static struct file_system_type autofs_fs + .owner = THIS_MODULE, + .name = "autofs", + .get_sb = autofs_get_sb, +- .kill_sb = kill_anon_super, ++ .kill_sb = autofs_kill_sb, + }; + + static int __init init_autofs_fs(void) +--- linux-2.6.18.orig/fs/autofs/inode.c ++++ linux-2.6.18/fs/autofs/inode.c +@@ -19,11 +19,20 @@ + #include "autofs_i.h" + #include + +-static void autofs_put_super(struct super_block *sb) ++void autofs4_kill_sb(struct super_block *sb) + { + struct autofs_sb_info *sbi = autofs_sbi(sb); + unsigned int n; + ++ /* ++ * In the event of a failure in get_sb_nodev the superblock ++ * info is not present so nothing else has been setup, so ++ * just call kill_anon_super when we are called from ++ * deactivate_super. ++ */ ++ if (!sbi) ++ goto out_kill_sb; ++ + if ( !sbi->catatonic ) + autofs_catatonic_mode(sbi); /* Free wait queues, close pipe */ + +@@ -35,14 +44,15 @@ static void autofs_put_super(struct supe + + kfree(sb->s_fs_info); + ++out_kill_sb: + DPRINTK(("autofs: shutting down\n")); ++ kill_anon_super(sb); + } + + static void autofs_read_inode(struct inode *inode); + + static struct super_operations autofs_sops = { + .read_inode = autofs_read_inode, +- .put_super = autofs_put_super, + .statfs = simple_statfs, + }; + +@@ -136,7 +146,8 @@ int autofs_fill_super(struct super_block + + s->s_fs_info = sbi; + sbi->magic = AUTOFS_SBI_MAGIC; +- sbi->catatonic = 0; ++ sbi->pipe = NULL; ++ sbi->catatonic = 1; + sbi->exp_timeout = 0; + sbi->oz_pgrp = process_group(current); + autofs_initialize_hash(&sbi->dirhash); +@@ -180,6 +191,7 @@ int autofs_fill_super(struct super_block + if ( !pipe->f_op || !pipe->f_op->write ) + goto fail_fput; + sbi->pipe = pipe; ++ sbi->catatonic = 0; + + /* + * Success! Install the root dentry now to indicate completion. +@@ -198,6 +210,7 @@ fail_iput: + iput(root_inode); + fail_free: + kfree(sbi); ++ s->s_fs_info = NULL; + fail_unlock: + return -EINVAL; + } +--- linux-2.6.18.orig/fs/autofs/autofs_i.h ++++ linux-2.6.18/fs/autofs/autofs_i.h +@@ -151,6 +151,7 @@ extern const struct file_operations auto + /* Initializing function */ + + int autofs_fill_super(struct super_block *, void *, int); ++void autofs_kill_sb(struct super_block *); + + /* Queue management functions */ + +--- linux-2.6.18.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.18/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else + #define DPRINTK(fmt,args...) do {} while(0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + #define AUTOFS_SUPER_MAGIC 0x0187 + + /* Unified info structure. This is pointed to by both the dentry and +@@ -54,10 +74,18 @@ struct autofs_info { + + int flags; + ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; ++ + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +96,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,18 +112,13 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; +- struct dentry *root; + int pipefd; + struct file *pipe; + pid_t oz_pgrp; +@@ -113,6 +135,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -137,18 +162,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -162,11 +183,23 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +@@ -231,4 +264,4 @@ out: + } + + void autofs4_dentry_release(struct dentry *); +- ++extern void autofs4_kill_sb(struct super_block *); +--- linux-2.6.18.orig/fs/autofs4/init.c ++++ linux-2.6.18/fs/autofs4/init.c +@@ -24,16 +24,25 @@ static struct file_system_type autofs_fs + .owner = THIS_MODULE, + .name = "autofs", + .get_sb = autofs_get_sb, +- .kill_sb = kill_anon_super, ++ .kill_sb = autofs4_kill_sb, + }; + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- linux-2.6.18.orig/fs/autofs4/inode.c ++++ linux-2.6.18/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,14 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -95,9 +103,12 @@ void autofs4_free_ino(struct autofs_info + */ + static void autofs4_force_release(struct autofs_sb_info *sbi) + { +- struct dentry *this_parent = sbi->root; ++ struct dentry *this_parent = sbi->sb->s_root; + struct list_head *next; + ++ if (!sbi->sb->s_root) ++ return; ++ + spin_lock(&dcache_lock); + repeat: + next = this_parent->d_subdirs.next; +@@ -126,7 +137,7 @@ resume: + spin_lock(&dcache_lock); + } + +- if (this_parent != sbi->root) { ++ if (this_parent != sbi->sb->s_root) { + struct dentry *dentry = this_parent; + + next = this_parent->d_u.d_child.next; +@@ -139,29 +150,34 @@ resume: + goto resume; + } + spin_unlock(&dcache_lock); +- +- dput(sbi->root); +- sbi->root = NULL; + shrink_dcache_sb(sbi->sb); +- +- return; + } + +-static void autofs4_put_super(struct super_block *sb) ++void autofs4_kill_sb(struct super_block *sb) + { + struct autofs_sb_info *sbi = autofs4_sbi(sb); + +- sb->s_fs_info = NULL; ++ /* ++ * In the event of a failure in get_sb_nodev the superblock ++ * info is not present so nothing else has been setup, so ++ * just call kill_anon_super when we are called from ++ * deactivate_super. ++ */ ++ if (!sbi) ++ goto out_kill_sb; + +- if ( !sbi->catatonic ) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); + ++ sb->s_fs_info = NULL; + kfree(sbi); + ++out_kill_sb: + DPRINTK("shutting down"); ++ kill_anon_super(sb); + } + + static int autofs4_show_options(struct seq_file *m, struct vfsmount *mnt) +@@ -177,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -188,7 +204,6 @@ static int autofs4_show_options(struct s + } + + static struct super_operations autofs4_sops = { +- .put_super = autofs4_put_super, + .statfs = simple_statfs, + .show_options = autofs4_show_options, + }; +@@ -266,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -314,20 +329,23 @@ int autofs4_fill_super(struct super_bloc + + s->s_fs_info = sbi; + sbi->magic = AUTOFS_SBI_MAGIC; +- sbi->root = NULL; + sbi->pipefd = -1; +- sbi->catatonic = 0; ++ sbi->pipe = NULL; ++ sbi->catatonic = 1; + sbi->exp_timeout = 0; + sbi->oz_pgrp = process_group(current); + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -362,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +@@ -394,13 +412,7 @@ int autofs4_fill_super(struct super_bloc + goto fail_fput; + sbi->pipe = pipe; + sbi->pipefd = pipefd; +- +- /* +- * Take a reference to the root dentry so we get a chance to +- * clean up the dentry tree on umount. +- * See autofs4_force_release. +- */ +- sbi->root = dget(root); ++ sbi->catatonic = 0; + + /* + * Success! Install the root dentry now to indicate completion. +@@ -425,6 +437,7 @@ fail_ino: + kfree(ino); + fail_free: + kfree(sbi); ++ s->s_fs_info = NULL; + fail_unlock: + return -EINVAL; + } +--- linux-2.6.18.orig/fs/autofs4/waitq.c ++++ linux-2.6.18/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,15 +42,17 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } +- if (sbi->pipe) { +- fput(sbi->pipe); /* Close the pipe */ +- sbi->pipe = NULL; +- } ++ fput(sbi->pipe); /* Close the pipe */ ++ sbi->pipe = NULL; ++ mutex_unlock(&sbi->wq_mutex); + shrink_dcache_sb(sbi->sb); + } + +@@ -87,11 +95,16 @@ static void autofs4_notify_daemon(struct + struct autofs_wait_queue *wq, + int type) + { +- union autofs_packet_union pkt; ++ union { ++ struct autofs_packet_hdr hdr; ++ union autofs_packet_union v4_pkt; ++ union autofs_v5_packet_union v5_pkt; ++ } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -101,26 +114,26 @@ static void autofs4_notify_daemon(struct + /* Kernel protocol v4 missing and expire packets */ + case autofs_ptype_missing: + { +- struct autofs_packet_missing *mp = &pkt.missing; ++ struct autofs_packet_missing *mp = &pkt.v4_pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: + { +- struct autofs_packet_expire_multi *ep = &pkt.expire_multi; ++ struct autofs_packet_expire_multi *ep = &pkt.v4_pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -132,14 +145,14 @@ static void autofs4_notify_daemon(struct + case autofs_ptype_missing_direct: + case autofs_ptype_expire_direct: + { +- struct autofs_v5_packet *packet = &pkt.v5_packet; ++ struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; + + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -153,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -170,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -190,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -252,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -266,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -288,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -298,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -308,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -350,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -362,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -386,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.18.orig/fs/autofs/waitq.c ++++ linux-2.6.18/fs/autofs/waitq.c +@@ -41,6 +41,7 @@ void autofs_catatonic_mode(struct autofs + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ ++ sbi->pipe = NULL; + autofs_hash_dputall(&sbi->dirhash); /* Remove all dentry pointers */ + } + +--- linux-2.6.18.orig/include/linux/auto_fs4.h ++++ linux-2.6.18/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -59,6 +118,13 @@ struct autofs_packet_expire_multi { + char name[NAME_MAX+1]; + }; + ++union autofs_packet_union { ++ struct autofs_packet_hdr hdr; ++ struct autofs_packet_missing missing; ++ struct autofs_packet_expire expire; ++ struct autofs_packet_expire_multi expire_multi; ++}; ++ + /* autofs v5 common packet struct */ + struct autofs_v5_packet { + struct autofs_packet_hdr hdr; +@@ -78,20 +144,19 @@ typedef struct autofs_v5_packet autofs_p + typedef struct autofs_v5_packet autofs_packet_missing_direct_t; + typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +-union autofs_packet_union { ++union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; +- struct autofs_packet_missing missing; +- struct autofs_packet_expire expire; +- struct autofs_packet_expire_multi expire_multi; + struct autofs_v5_packet v5_packet; ++ autofs_packet_missing_indirect_t missing_indirect; ++ autofs_packet_expire_indirect_t expire_indirect; ++ autofs_packet_missing_direct_t missing_direct; ++ autofs_packet_expire_direct_t expire_direct; + }; + + #define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.18.orig/fs/autofs4/expire.c ++++ linux-2.6.18/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if ( !sbi->exp_timeout || !root ) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,9 +469,15 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + + /* Call repeatedly until it returns -EAGAIN, meaning there's nothing +@@ -425,7 +492,7 @@ int autofs4_expire_multi(struct super_bl + if (arg && get_user(do_now, arg)) + return -EFAULT; + +- if (sbi->type & AUTOFS_TYPE_DIRECT) ++ if (autofs_type_trigger(sbi->type)) + dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); + else + dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); +@@ -435,9 +502,16 @@ int autofs4_expire_multi(struct super_bl + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + +--- linux-2.6.18.orig/include/linux/compat_ioctl.h ++++ linux-2.6.18/include/linux/compat_ioctl.h +@@ -565,8 +565,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.18/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.18.orig/fs/autofs4/Makefile ++++ linux-2.6.18/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.18/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,867 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct dentry *dentry; ++ struct vfsmount *mnt; ++ int err = -EAGAIN; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_vfsmnt; ++ ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); ++ else ++ dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); ++ ++ if (dentry) { ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ ++ /* ++ * This is synchronous because it makes the daemon a ++ * little easier ++ */ ++ err = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ sbi->sb->s_root->d_mounted++; ++ } ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ dput(dentry); ++ } ++ ++ return err; ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_vfsmnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t devid = new_encode_dev(sbi->sb->s_dev); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ devid = autofs4_get_dev(sbi); ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- /dev/null ++++ linux-2.6.18/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.18.orig/include/linux/auto_fs.h ++++ linux-2.6.18/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.18-v5-update-20100114.patch b/patches/autofs4-2.6.18-v5-update-20100114.patch new file mode 100644 index 0000000..961c104 --- /dev/null +++ b/patches/autofs4-2.6.18-v5-update-20100114.patch @@ -0,0 +1,3929 @@ +--- linux-2.6.18.orig/fs/autofs4/root.c ++++ linux-2.6.18/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ struct inode_operations autofs4_dir_inod + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_dentry; +- struct vfsmount *mnt = file->f_vfsmnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,155 +82,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (!ret) { +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -ENOENT; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -279,9 +123,6 @@ static int try_to_fill_dentry(struct den + + DPRINTK("mount done status=%d", status); + +- if (status && dentry->d_inode) +- return status; /* Try to get the kernel to invalidate this dentry */ +- + /* Turn this into a real negative dentry? */ + if (status == -ENOENT) { + spin_lock(&dentry->d_lock); +@@ -293,7 +134,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -320,7 +162,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -335,50 +178,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && list_empty(&dentry->d_subdirs)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -400,14 +255,36 @@ static int autofs4_revalidate(struct den + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + int oz_mode = autofs4_oz_mode(sbi); + int flags = nd ? nd->flags : 0; +- int status = 0; ++ int status; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { +- if (!oz_mode) +- status = try_to_fill_dentry(dentry, flags); +- return !status; ++ /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ ++ if (oz_mode) ++ return 1; ++ ++ /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* ++ * A zero status is success otherwise we have a ++ * negative error code. ++ */ ++ status = try_to_fill_dentry(dentry, flags); ++ if (status == 0) ++ return 1; ++ ++ return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -421,9 +298,20 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); +- if (!oz_mode) +- status = try_to_fill_dentry(dentry, flags); +- return !status; ++ ++ /* The daemon never causes a mount to trigger */ ++ if (oz_mode) ++ return 1; ++ ++ /* ++ * A zero status is success otherwise we have a ++ * negative error code. ++ */ ++ status = try_to_fill_dentry(dentry, flags); ++ if (status == 0) ++ return 1; ++ ++ return status; + } + spin_unlock(&dcache_lock); + +@@ -440,6 +328,17 @@ void autofs4_dentry_release(struct dentr + de->d_fsdata = NULL; + + if (inf) { ++ struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); ++ ++ if (sbi) { ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ } ++ + inf->dentry = NULL; + inf->inode = NULL; + +@@ -459,10 +358,116 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Bad luck, we've already been dentry_iput */ ++ if (!dentry->d_inode) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ + /* Lookups in the root directory */ + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -478,29 +483,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. +- */ +- dentry->d_op = &autofs4_root_dentry_operations; ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { ++ /* ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. ++ */ ++ dentry->d_op = &autofs4_root_dentry_operations; ++ ++ /* ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. ++ */ ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); ++ } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -515,19 +558,47 @@ static struct dentry *autofs4_lookup(str + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { ++ if (unhashed) ++ dput(unhashed); + return ERR_PTR(-ERESTARTNOINTR); + } + } ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this +- * lookup even if the dentry is positive. Returning ENOENT here +- * doesn't do the right thing for all system calls, but it should +- * be OK for the operations we permit from an autofs. ++ * lookup. Returning ENOENT here doesn't do the right thing ++ * for all system calls, but it should be OK for the operations ++ * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) +- return ERR_PTR(-ENOENT); ++ if (!oz_mode && d_unhashed(dentry)) { ++ /* ++ * A user space application can (and has done in the past) ++ * remove and re-create this directory during the callback. ++ * This can leave us with an unhashed dentry, but a ++ * successful mount! So we need to perform another ++ * cached lookup in case the dentry now exists. ++ */ ++ struct dentry *parent = dentry->d_parent; ++ struct dentry *new = d_lookup(parent, &dentry->d_name); ++ if (new != NULL) ++ dentry = new; ++ else ++ dentry = ERR_PTR(-ENOENT); ++ ++ if (unhashed) ++ dput(unhashed); ++ ++ return dentry; ++ } ++ ++ if (unhashed) ++ return unhashed; + + return NULL; + } +@@ -549,21 +620,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -578,6 +660,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -589,9 +672,9 @@ static int autofs4_dir_symlink(struct in + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want +- * this, because since the unlink is probably the result of an expire. +- * We simply d_drop it, which allows the dentry lookup to remount it +- * if necessary. ++ * this, because the unlink is probably the result of an expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -620,7 +703,15 @@ static int autofs4_dir_unlink(struct ino + + dir->i_mtime = CURRENT_TIME; + +- d_drop(dentry); ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); ++ spin_lock(&dentry->d_lock); ++ __d_drop(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&dcache_lock); + + return 0; + } +@@ -631,6 +722,9 @@ static int autofs4_dir_rmdir(struct inod + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct autofs_info *p_ino; + ++ DPRINTK("dentry %p, removing %.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ + if (!autofs4_oz_mode(sbi)) + return -EACCES; + +@@ -639,6 +733,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -673,11 +771,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -729,44 +837,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -830,11 +900,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_vfsmnt, p); + +--- linux-2.6.18.orig/fs/namei.c ++++ linux-2.6.18/fs/namei.c +@@ -372,6 +372,29 @@ void release_open_intent(struct nameidat + fput(nd->intent.open.file); + } + ++static inline struct dentry *do_revalidate(struct dentry *dentry, struct nameidata *nd) ++{ ++ int status = dentry->d_op->d_revalidate(dentry, nd); ++ if (unlikely(status <= 0)) { ++ /* ++ * The dentry failed validation. ++ * If d_revalidate returned 0 attempt to invalidate ++ * the dentry otherwise d_revalidate is asking us ++ * to return a fail status. ++ */ ++ if (!status) { ++ if (!d_invalidate(dentry)) { ++ dput(dentry); ++ dentry = NULL; ++ } ++ } else { ++ dput(dentry); ++ dentry = ERR_PTR(status); ++ } ++ } ++ return dentry; ++} ++ + /* + * Internal lookup() using the new generic dcache. + * SMP-safe +@@ -386,12 +409,9 @@ static struct dentry * cached_lookup(str + if (!dentry) + dentry = d_lookup(parent, name); + +- if (dentry && dentry->d_op && dentry->d_op->d_revalidate) { +- if (!dentry->d_op->d_revalidate(dentry, nd) && !d_invalidate(dentry)) { +- dput(dentry); +- dentry = NULL; +- } +- } ++ if (dentry && dentry->d_op && dentry->d_op->d_revalidate) ++ dentry = do_revalidate(dentry, nd); ++ + return dentry; + } + +@@ -484,10 +504,9 @@ static struct dentry * real_lookup(struc + */ + mutex_unlock(&dir->i_mutex); + if (result->d_op && result->d_op->d_revalidate) { +- if (!result->d_op->d_revalidate(result, nd) && !d_invalidate(result)) { +- dput(result); ++ result = do_revalidate(result, nd); ++ if (!result) + result = ERR_PTR(-ENOENT); +- } + } + return result; + } +@@ -767,12 +786,12 @@ need_lookup: + goto done; + + need_revalidate: +- if (dentry->d_op->d_revalidate(dentry, nd)) +- goto done; +- if (d_invalidate(dentry)) +- goto done; +- dput(dentry); +- goto need_lookup; ++ dentry = do_revalidate(dentry, nd); ++ if (!dentry) ++ goto need_lookup; ++ if (IS_ERR(dentry)) ++ goto fail; ++ goto done; + + fail: + return PTR_ERR(dentry); +--- linux-2.6.18.orig/fs/autofs/init.c ++++ linux-2.6.18/fs/autofs/init.c +@@ -24,7 +24,7 @@ static struct file_system_type autofs_fs + .owner = THIS_MODULE, + .name = "autofs", + .get_sb = autofs_get_sb, +- .kill_sb = kill_anon_super, ++ .kill_sb = autofs_kill_sb, + }; + + static int __init init_autofs_fs(void) +--- linux-2.6.18.orig/fs/autofs/inode.c ++++ linux-2.6.18/fs/autofs/inode.c +@@ -19,11 +19,20 @@ + #include "autofs_i.h" + #include + +-static void autofs_put_super(struct super_block *sb) ++void autofs4_kill_sb(struct super_block *sb) + { + struct autofs_sb_info *sbi = autofs_sbi(sb); + unsigned int n; + ++ /* ++ * In the event of a failure in get_sb_nodev the superblock ++ * info is not present so nothing else has been setup, so ++ * just call kill_anon_super when we are called from ++ * deactivate_super. ++ */ ++ if (!sbi) ++ goto out_kill_sb; ++ + if ( !sbi->catatonic ) + autofs_catatonic_mode(sbi); /* Free wait queues, close pipe */ + +@@ -35,14 +44,15 @@ static void autofs_put_super(struct supe + + kfree(sb->s_fs_info); + ++out_kill_sb: + DPRINTK(("autofs: shutting down\n")); ++ kill_anon_super(sb); + } + + static void autofs_read_inode(struct inode *inode); + + static struct super_operations autofs_sops = { + .read_inode = autofs_read_inode, +- .put_super = autofs_put_super, + .statfs = simple_statfs, + }; + +@@ -136,7 +146,8 @@ int autofs_fill_super(struct super_block + + s->s_fs_info = sbi; + sbi->magic = AUTOFS_SBI_MAGIC; +- sbi->catatonic = 0; ++ sbi->pipe = NULL; ++ sbi->catatonic = 1; + sbi->exp_timeout = 0; + sbi->oz_pgrp = process_group(current); + autofs_initialize_hash(&sbi->dirhash); +@@ -180,6 +191,7 @@ int autofs_fill_super(struct super_block + if ( !pipe->f_op || !pipe->f_op->write ) + goto fail_fput; + sbi->pipe = pipe; ++ sbi->catatonic = 0; + + /* + * Success! Install the root dentry now to indicate completion. +@@ -198,6 +210,7 @@ fail_iput: + iput(root_inode); + fail_free: + kfree(sbi); ++ s->s_fs_info = NULL; + fail_unlock: + return -EINVAL; + } +--- linux-2.6.18.orig/fs/autofs/autofs_i.h ++++ linux-2.6.18/fs/autofs/autofs_i.h +@@ -151,6 +151,7 @@ extern const struct file_operations auto + /* Initializing function */ + + int autofs_fill_super(struct super_block *, void *, int); ++void autofs_kill_sb(struct super_block *); + + /* Queue management functions */ + +--- linux-2.6.18.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.18/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else + #define DPRINTK(fmt,args...) do {} while(0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + #define AUTOFS_SUPER_MAGIC 0x0187 + + /* Unified info structure. This is pointed to by both the dentry and +@@ -54,10 +74,18 @@ struct autofs_info { + + int flags; + ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; ++ + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +96,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,18 +112,13 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; +- struct dentry *root; + int pipefd; + struct file *pipe; + pid_t oz_pgrp; +@@ -113,6 +135,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -137,18 +162,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -162,11 +183,23 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +@@ -231,4 +264,4 @@ out: + } + + void autofs4_dentry_release(struct dentry *); +- ++extern void autofs4_kill_sb(struct super_block *); +--- linux-2.6.18.orig/fs/autofs4/init.c ++++ linux-2.6.18/fs/autofs4/init.c +@@ -24,16 +24,25 @@ static struct file_system_type autofs_fs + .owner = THIS_MODULE, + .name = "autofs", + .get_sb = autofs_get_sb, +- .kill_sb = kill_anon_super, ++ .kill_sb = autofs4_kill_sb, + }; + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- linux-2.6.18.orig/fs/autofs4/inode.c ++++ linux-2.6.18/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,14 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -95,9 +103,12 @@ void autofs4_free_ino(struct autofs_info + */ + static void autofs4_force_release(struct autofs_sb_info *sbi) + { +- struct dentry *this_parent = sbi->root; ++ struct dentry *this_parent = sbi->sb->s_root; + struct list_head *next; + ++ if (!sbi->sb->s_root) ++ return; ++ + spin_lock(&dcache_lock); + repeat: + next = this_parent->d_subdirs.next; +@@ -126,7 +137,7 @@ resume: + spin_lock(&dcache_lock); + } + +- if (this_parent != sbi->root) { ++ if (this_parent != sbi->sb->s_root) { + struct dentry *dentry = this_parent; + + next = this_parent->d_u.d_child.next; +@@ -139,29 +150,34 @@ resume: + goto resume; + } + spin_unlock(&dcache_lock); +- +- dput(sbi->root); +- sbi->root = NULL; + shrink_dcache_sb(sbi->sb); +- +- return; + } + +-static void autofs4_put_super(struct super_block *sb) ++void autofs4_kill_sb(struct super_block *sb) + { + struct autofs_sb_info *sbi = autofs4_sbi(sb); + +- sb->s_fs_info = NULL; ++ /* ++ * In the event of a failure in get_sb_nodev the superblock ++ * info is not present so nothing else has been setup, so ++ * just call kill_anon_super when we are called from ++ * deactivate_super. ++ */ ++ if (!sbi) ++ goto out_kill_sb; + +- if ( !sbi->catatonic ) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); + ++ sb->s_fs_info = NULL; + kfree(sbi); + ++out_kill_sb: + DPRINTK("shutting down"); ++ kill_anon_super(sb); + } + + static int autofs4_show_options(struct seq_file *m, struct vfsmount *mnt) +@@ -177,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -188,7 +204,6 @@ static int autofs4_show_options(struct s + } + + static struct super_operations autofs4_sops = { +- .put_super = autofs4_put_super, + .statfs = simple_statfs, + .show_options = autofs4_show_options, + }; +@@ -266,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -314,20 +329,23 @@ int autofs4_fill_super(struct super_bloc + + s->s_fs_info = sbi; + sbi->magic = AUTOFS_SBI_MAGIC; +- sbi->root = NULL; + sbi->pipefd = -1; +- sbi->catatonic = 0; ++ sbi->pipe = NULL; ++ sbi->catatonic = 1; + sbi->exp_timeout = 0; + sbi->oz_pgrp = process_group(current); + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -362,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +@@ -394,13 +412,7 @@ int autofs4_fill_super(struct super_bloc + goto fail_fput; + sbi->pipe = pipe; + sbi->pipefd = pipefd; +- +- /* +- * Take a reference to the root dentry so we get a chance to +- * clean up the dentry tree on umount. +- * See autofs4_force_release. +- */ +- sbi->root = dget(root); ++ sbi->catatonic = 0; + + /* + * Success! Install the root dentry now to indicate completion. +@@ -425,6 +437,7 @@ fail_ino: + kfree(ino); + fail_free: + kfree(sbi); ++ s->s_fs_info = NULL; + fail_unlock: + return -EINVAL; + } +--- linux-2.6.18.orig/fs/autofs4/waitq.c ++++ linux-2.6.18/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,15 +42,17 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } +- if (sbi->pipe) { +- fput(sbi->pipe); /* Close the pipe */ +- sbi->pipe = NULL; +- } ++ fput(sbi->pipe); /* Close the pipe */ ++ sbi->pipe = NULL; ++ mutex_unlock(&sbi->wq_mutex); + shrink_dcache_sb(sbi->sb); + } + +@@ -87,11 +95,16 @@ static void autofs4_notify_daemon(struct + struct autofs_wait_queue *wq, + int type) + { +- union autofs_packet_union pkt; ++ union { ++ struct autofs_packet_hdr hdr; ++ union autofs_packet_union v4_pkt; ++ union autofs_v5_packet_union v5_pkt; ++ } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -101,26 +114,26 @@ static void autofs4_notify_daemon(struct + /* Kernel protocol v4 missing and expire packets */ + case autofs_ptype_missing: + { +- struct autofs_packet_missing *mp = &pkt.missing; ++ struct autofs_packet_missing *mp = &pkt.v4_pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: + { +- struct autofs_packet_expire_multi *ep = &pkt.expire_multi; ++ struct autofs_packet_expire_multi *ep = &pkt.v4_pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -132,14 +145,14 @@ static void autofs4_notify_daemon(struct + case autofs_ptype_missing_direct: + case autofs_ptype_expire_direct: + { +- struct autofs_v5_packet *packet = &pkt.v5_packet; ++ struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; + + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -153,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -170,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -190,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -252,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -266,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -288,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -298,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -308,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -350,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -362,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -386,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.18.orig/fs/autofs/waitq.c ++++ linux-2.6.18/fs/autofs/waitq.c +@@ -41,6 +41,7 @@ void autofs_catatonic_mode(struct autofs + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ ++ sbi->pipe = NULL; + autofs_hash_dputall(&sbi->dirhash); /* Remove all dentry pointers */ + } + +--- linux-2.6.18.orig/include/linux/auto_fs4.h ++++ linux-2.6.18/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -59,6 +118,13 @@ struct autofs_packet_expire_multi { + char name[NAME_MAX+1]; + }; + ++union autofs_packet_union { ++ struct autofs_packet_hdr hdr; ++ struct autofs_packet_missing missing; ++ struct autofs_packet_expire expire; ++ struct autofs_packet_expire_multi expire_multi; ++}; ++ + /* autofs v5 common packet struct */ + struct autofs_v5_packet { + struct autofs_packet_hdr hdr; +@@ -78,20 +144,19 @@ typedef struct autofs_v5_packet autofs_p + typedef struct autofs_v5_packet autofs_packet_missing_direct_t; + typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +-union autofs_packet_union { ++union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; +- struct autofs_packet_missing missing; +- struct autofs_packet_expire expire; +- struct autofs_packet_expire_multi expire_multi; + struct autofs_v5_packet v5_packet; ++ autofs_packet_missing_indirect_t missing_indirect; ++ autofs_packet_expire_indirect_t expire_indirect; ++ autofs_packet_missing_direct_t missing_direct; ++ autofs_packet_expire_direct_t expire_direct; + }; + + #define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.18.orig/fs/autofs4/expire.c ++++ linux-2.6.18/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if ( !sbi->exp_timeout || !root ) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,9 +469,15 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + + /* Call repeatedly until it returns -EAGAIN, meaning there's nothing +@@ -425,7 +492,7 @@ int autofs4_expire_multi(struct super_bl + if (arg && get_user(do_now, arg)) + return -EFAULT; + +- if (sbi->type & AUTOFS_TYPE_DIRECT) ++ if (autofs_type_trigger(sbi->type)) + dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); + else + dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); +@@ -435,9 +502,16 @@ int autofs4_expire_multi(struct super_bl + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + +--- linux-2.6.18.orig/include/linux/compat_ioctl.h ++++ linux-2.6.18/include/linux/compat_ioctl.h +@@ -565,8 +565,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.18/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.18.orig/fs/autofs4/Makefile ++++ linux-2.6.18/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.18/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,867 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct dentry *dentry; ++ struct vfsmount *mnt; ++ int err = -EAGAIN; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_vfsmnt; ++ ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); ++ else ++ dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); ++ ++ if (dentry) { ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ ++ /* ++ * This is synchronous because it makes the daemon a ++ * little easier ++ */ ++ err = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ sbi->sb->s_root->d_mounted++; ++ } ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ dput(dentry); ++ } ++ ++ return err; ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_vfsmnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t devid = new_encode_dev(sbi->sb->s_dev); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ devid = autofs4_get_dev(sbi); ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- /dev/null ++++ linux-2.6.18/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.18.orig/include/linux/auto_fs.h ++++ linux-2.6.18/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.19-v5-update-20090903.patch b/patches/autofs4-2.6.19-v5-update-20090903.patch new file mode 100644 index 0000000..87102db --- /dev/null +++ b/patches/autofs4-2.6.19-v5-update-20090903.patch @@ -0,0 +1,3667 @@ +--- linux-2.6.19.orig/fs/autofs/inode.c ++++ linux-2.6.19/fs/autofs/inode.c +@@ -28,10 +28,11 @@ void autofs_kill_sb(struct super_block * + /* + * In the event of a failure in get_sb_nodev the superblock + * info is not present so nothing else has been setup, so +- * just exit when we are called from deactivate_super. ++ * just call kill_anon_super when we are called from ++ * deactivate_super. + */ + if (!sbi) +- return; ++ goto out_kill_sb; + + if ( !sbi->catatonic ) + autofs_catatonic_mode(sbi); /* Free wait queues, close pipe */ +@@ -44,6 +45,7 @@ void autofs_kill_sb(struct super_block * + + kfree(sb->s_fs_info); + ++out_kill_sb: + DPRINTK(("autofs: shutting down\n")); + kill_anon_super(sb); + } +@@ -209,7 +211,6 @@ fail_iput: + fail_free: + kfree(sbi); + s->s_fs_info = NULL; +- kill_anon_super(s); + fail_unlock: + return -EINVAL; + } +--- linux-2.6.19.orig/fs/autofs4/inode.c ++++ linux-2.6.19/fs/autofs4/inode.c +@@ -25,8 +25,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -42,14 +44,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -152,21 +160,22 @@ void autofs4_kill_sb(struct super_block + /* + * In the event of a failure in get_sb_nodev the superblock + * info is not present so nothing else has been setup, so +- * just exit when we are called from deactivate_super. ++ * just call kill_anon_super when we are called from ++ * deactivate_super. + */ + if (!sbi) +- return; +- +- sb->s_fs_info = NULL; ++ goto out_kill_sb; + +- if ( !sbi->catatonic ) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); + ++ sb->s_fs_info = NULL; + kfree(sbi); + ++out_kill_sb: + DPRINTK("shutting down"); + kill_anon_super(sb); + } +@@ -184,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -272,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -328,12 +337,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -368,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +@@ -426,7 +438,6 @@ fail_ino: + fail_free: + kfree(sbi); + s->s_fs_info = NULL; +- kill_anon_super(s); + fail_unlock: + return -EINVAL; + } +--- linux-2.6.19.orig/fs/autofs4/waitq.c ++++ linux-2.6.19/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -84,11 +95,16 @@ static void autofs4_notify_daemon(struct + struct autofs_wait_queue *wq, + int type) + { +- union autofs_packet_union pkt; ++ union { ++ struct autofs_packet_hdr hdr; ++ union autofs_packet_union v4_pkt; ++ union autofs_v5_packet_union v5_pkt; ++ } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -98,26 +114,26 @@ static void autofs4_notify_daemon(struct + /* Kernel protocol v4 missing and expire packets */ + case autofs_ptype_missing: + { +- struct autofs_packet_missing *mp = &pkt.missing; ++ struct autofs_packet_missing *mp = &pkt.v4_pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: + { +- struct autofs_packet_expire_multi *ep = &pkt.expire_multi; ++ struct autofs_packet_expire_multi *ep = &pkt.v4_pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -129,14 +145,14 @@ static void autofs4_notify_daemon(struct + case autofs_ptype_missing_direct: + case autofs_ptype_expire_direct: + { +- struct autofs_v5_packet *packet = &pkt.v5_packet; ++ struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; + + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -150,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -167,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -187,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -249,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -263,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -285,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -295,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -305,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -347,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -359,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -383,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.19.orig/include/linux/auto_fs4.h ++++ linux-2.6.19/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -59,6 +118,13 @@ struct autofs_packet_expire_multi { + char name[NAME_MAX+1]; + }; + ++union autofs_packet_union { ++ struct autofs_packet_hdr hdr; ++ struct autofs_packet_missing missing; ++ struct autofs_packet_expire expire; ++ struct autofs_packet_expire_multi expire_multi; ++}; ++ + /* autofs v5 common packet struct */ + struct autofs_v5_packet { + struct autofs_packet_hdr hdr; +@@ -78,20 +144,19 @@ typedef struct autofs_v5_packet autofs_p + typedef struct autofs_v5_packet autofs_packet_missing_direct_t; + typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +-union autofs_packet_union { ++union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; +- struct autofs_packet_missing missing; +- struct autofs_packet_expire expire; +- struct autofs_packet_expire_multi expire_multi; + struct autofs_v5_packet v5_packet; ++ autofs_packet_missing_indirect_t missing_indirect; ++ autofs_packet_expire_indirect_t expire_indirect; ++ autofs_packet_missing_direct_t missing_direct; ++ autofs_packet_expire_direct_t expire_direct; + }; + + #define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.19.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.19/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,10 +72,18 @@ struct autofs_info { + + int flags; + ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; ++ + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -66,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -83,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -110,6 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -134,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -159,11 +181,23 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.19.orig/fs/autofs4/root.c ++++ linux-2.6.19/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ struct inode_operations autofs4_dir_inod + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_dentry; +- struct vfsmount *mnt = file->f_vfsmnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,157 +82,30 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -ENOENT; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -292,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -319,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -334,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -402,21 +257,33 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ + status = try_to_fill_dentry(dentry, flags); + if (status == 0) +- return 1; ++ return 1; + + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -430,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -459,6 +327,17 @@ void autofs4_dentry_release(struct dentr + de->d_fsdata = NULL; + + if (inf) { ++ struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); ++ ++ if (sbi) { ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ } ++ + inf->dentry = NULL; + inf->inode = NULL; + +@@ -478,10 +357,116 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Bad luck, we've already been dentry_iput */ ++ if (!dentry->d_inode) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ + /* Lookups in the root directory */ + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -497,29 +482,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. +- */ +- dentry->d_op = &autofs4_root_dentry_operations; ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { ++ /* ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. ++ */ ++ dentry->d_op = &autofs4_root_dentry_operations; ++ ++ /* ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. ++ */ ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); ++ } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -534,22 +557,47 @@ static struct dentry *autofs4_lookup(str + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { ++ if (unhashed) ++ dput(unhashed); + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this +- * lookup even if the dentry is positive. Returning ENOENT here +- * doesn't do the right thing for all system calls, but it should +- * be OK for the operations we permit from an autofs. ++ * lookup. Returning ENOENT here doesn't do the right thing ++ * for all system calls, but it should be OK for the operations ++ * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) +- return ERR_PTR(-ENOENT); ++ if (!oz_mode && d_unhashed(dentry)) { ++ /* ++ * A user space application can (and has done in the past) ++ * remove and re-create this directory during the callback. ++ * This can leave us with an unhashed dentry, but a ++ * successful mount! So we need to perform another ++ * cached lookup in case the dentry now exists. ++ */ ++ struct dentry *parent = dentry->d_parent; ++ struct dentry *new = d_lookup(parent, &dentry->d_name); ++ if (new != NULL) ++ dentry = new; ++ else ++ dentry = ERR_PTR(-ENOENT); ++ ++ if (unhashed) ++ dput(unhashed); ++ ++ return dentry; ++ } ++ ++ if (unhashed) ++ return unhashed; + + return NULL; + } +@@ -571,21 +619,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -600,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -611,9 +671,9 @@ static int autofs4_dir_symlink(struct in + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want +- * this, because since the unlink is probably the result of an expire. +- * We simply d_drop it, which allows the dentry lookup to remount it +- * if necessary. ++ * this, because the unlink is probably the result of an expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -642,7 +702,15 @@ static int autofs4_dir_unlink(struct ino + + dir->i_mtime = CURRENT_TIME; + +- d_drop(dentry); ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); ++ spin_lock(&dentry->d_lock); ++ __d_drop(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&dcache_lock); + + return 0; + } +@@ -653,6 +721,9 @@ static int autofs4_dir_rmdir(struct inod + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct autofs_info *p_ino; + ++ DPRINTK("dentry %p, removing %.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ + if (!autofs4_oz_mode(sbi)) + return -EACCES; + +@@ -661,6 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -695,11 +770,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -751,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -852,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_vfsmnt, p); + +--- linux-2.6.19.orig/fs/autofs4/expire.c ++++ linux-2.6.19/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,9 +469,15 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + + /* Call repeatedly until it returns -EAGAIN, meaning there's nothing +@@ -425,7 +492,7 @@ int autofs4_expire_multi(struct super_bl + if (arg && get_user(do_now, arg)) + return -EFAULT; + +- if (sbi->type & AUTOFS_TYPE_DIRECT) ++ if (autofs_type_trigger(sbi->type)) + dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); + else + dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); +@@ -435,9 +502,16 @@ int autofs4_expire_multi(struct super_bl + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + +--- linux-2.6.19.orig/include/linux/compat_ioctl.h ++++ linux-2.6.19/include/linux/compat_ioctl.h +@@ -568,8 +568,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.19/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.19.orig/fs/autofs4/Makefile ++++ linux-2.6.19/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.19/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,867 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct dentry *dentry; ++ struct vfsmount *mnt; ++ int err = -EAGAIN; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_vfsmnt; ++ ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); ++ else ++ dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); ++ ++ if (dentry) { ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ ++ /* ++ * This is synchronous because it makes the daemon a ++ * little easier ++ */ ++ err = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ sbi->sb->s_root->d_mounted++; ++ } ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ dput(dentry); ++ } ++ ++ return err; ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_vfsmnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t devid = new_encode_dev(sbi->sb->s_dev); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ devid = autofs4_get_dev(sbi); ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.19.orig/fs/autofs4/init.c ++++ linux-2.6.19/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.19/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.19.orig/include/linux/auto_fs.h ++++ linux-2.6.19/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.19-v5-update-20100114.patch b/patches/autofs4-2.6.19-v5-update-20100114.patch new file mode 100644 index 0000000..793aae6 --- /dev/null +++ b/patches/autofs4-2.6.19-v5-update-20100114.patch @@ -0,0 +1,3668 @@ +--- linux-2.6.19.orig/fs/autofs/inode.c ++++ linux-2.6.19/fs/autofs/inode.c +@@ -28,10 +28,11 @@ void autofs_kill_sb(struct super_block * + /* + * In the event of a failure in get_sb_nodev the superblock + * info is not present so nothing else has been setup, so +- * just exit when we are called from deactivate_super. ++ * just call kill_anon_super when we are called from ++ * deactivate_super. + */ + if (!sbi) +- return; ++ goto out_kill_sb; + + if ( !sbi->catatonic ) + autofs_catatonic_mode(sbi); /* Free wait queues, close pipe */ +@@ -44,6 +45,7 @@ void autofs_kill_sb(struct super_block * + + kfree(sb->s_fs_info); + ++out_kill_sb: + DPRINTK(("autofs: shutting down\n")); + kill_anon_super(sb); + } +@@ -209,7 +211,6 @@ fail_iput: + fail_free: + kfree(sbi); + s->s_fs_info = NULL; +- kill_anon_super(s); + fail_unlock: + return -EINVAL; + } +--- linux-2.6.19.orig/fs/autofs4/inode.c ++++ linux-2.6.19/fs/autofs4/inode.c +@@ -25,8 +25,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -42,14 +44,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -152,21 +160,22 @@ void autofs4_kill_sb(struct super_block + /* + * In the event of a failure in get_sb_nodev the superblock + * info is not present so nothing else has been setup, so +- * just exit when we are called from deactivate_super. ++ * just call kill_anon_super when we are called from ++ * deactivate_super. + */ + if (!sbi) +- return; +- +- sb->s_fs_info = NULL; ++ goto out_kill_sb; + +- if ( !sbi->catatonic ) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); + ++ sb->s_fs_info = NULL; + kfree(sbi); + ++out_kill_sb: + DPRINTK("shutting down"); + kill_anon_super(sb); + } +@@ -184,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -272,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -328,12 +337,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -368,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +@@ -426,7 +438,6 @@ fail_ino: + fail_free: + kfree(sbi); + s->s_fs_info = NULL; +- kill_anon_super(s); + fail_unlock: + return -EINVAL; + } +--- linux-2.6.19.orig/fs/autofs4/waitq.c ++++ linux-2.6.19/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -84,11 +95,16 @@ static void autofs4_notify_daemon(struct + struct autofs_wait_queue *wq, + int type) + { +- union autofs_packet_union pkt; ++ union { ++ struct autofs_packet_hdr hdr; ++ union autofs_packet_union v4_pkt; ++ union autofs_v5_packet_union v5_pkt; ++ } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -98,26 +114,26 @@ static void autofs4_notify_daemon(struct + /* Kernel protocol v4 missing and expire packets */ + case autofs_ptype_missing: + { +- struct autofs_packet_missing *mp = &pkt.missing; ++ struct autofs_packet_missing *mp = &pkt.v4_pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: + { +- struct autofs_packet_expire_multi *ep = &pkt.expire_multi; ++ struct autofs_packet_expire_multi *ep = &pkt.v4_pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -129,14 +145,14 @@ static void autofs4_notify_daemon(struct + case autofs_ptype_missing_direct: + case autofs_ptype_expire_direct: + { +- struct autofs_v5_packet *packet = &pkt.v5_packet; ++ struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; + + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -150,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -167,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -187,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -249,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -263,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -285,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -295,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -305,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -347,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -359,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -383,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.19.orig/include/linux/auto_fs4.h ++++ linux-2.6.19/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -59,6 +118,13 @@ struct autofs_packet_expire_multi { + char name[NAME_MAX+1]; + }; + ++union autofs_packet_union { ++ struct autofs_packet_hdr hdr; ++ struct autofs_packet_missing missing; ++ struct autofs_packet_expire expire; ++ struct autofs_packet_expire_multi expire_multi; ++}; ++ + /* autofs v5 common packet struct */ + struct autofs_v5_packet { + struct autofs_packet_hdr hdr; +@@ -78,20 +144,19 @@ typedef struct autofs_v5_packet autofs_p + typedef struct autofs_v5_packet autofs_packet_missing_direct_t; + typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +-union autofs_packet_union { ++union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; +- struct autofs_packet_missing missing; +- struct autofs_packet_expire expire; +- struct autofs_packet_expire_multi expire_multi; + struct autofs_v5_packet v5_packet; ++ autofs_packet_missing_indirect_t missing_indirect; ++ autofs_packet_expire_indirect_t expire_indirect; ++ autofs_packet_missing_direct_t missing_direct; ++ autofs_packet_expire_direct_t expire_direct; + }; + + #define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.19.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.19/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,10 +72,18 @@ struct autofs_info { + + int flags; + ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; ++ + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -66,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -83,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -110,6 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -134,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -159,11 +181,23 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.19.orig/fs/autofs4/root.c ++++ linux-2.6.19/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ struct inode_operations autofs4_dir_inod + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_dentry; +- struct vfsmount *mnt = file->f_vfsmnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,157 +82,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -ENOENT; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -292,7 +134,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -319,7 +162,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -334,50 +178,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -402,21 +258,33 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ + status = try_to_fill_dentry(dentry, flags); + if (status == 0) +- return 1; ++ return 1; + + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -430,6 +298,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -459,6 +328,17 @@ void autofs4_dentry_release(struct dentr + de->d_fsdata = NULL; + + if (inf) { ++ struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); ++ ++ if (sbi) { ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ } ++ + inf->dentry = NULL; + inf->inode = NULL; + +@@ -478,10 +358,116 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Bad luck, we've already been dentry_iput */ ++ if (!dentry->d_inode) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ + /* Lookups in the root directory */ + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -497,29 +483,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. +- */ +- dentry->d_op = &autofs4_root_dentry_operations; ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { ++ /* ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. ++ */ ++ dentry->d_op = &autofs4_root_dentry_operations; ++ ++ /* ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. ++ */ ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); ++ } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -534,22 +558,47 @@ static struct dentry *autofs4_lookup(str + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { ++ if (unhashed) ++ dput(unhashed); + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this +- * lookup even if the dentry is positive. Returning ENOENT here +- * doesn't do the right thing for all system calls, but it should +- * be OK for the operations we permit from an autofs. ++ * lookup. Returning ENOENT here doesn't do the right thing ++ * for all system calls, but it should be OK for the operations ++ * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) +- return ERR_PTR(-ENOENT); ++ if (!oz_mode && d_unhashed(dentry)) { ++ /* ++ * A user space application can (and has done in the past) ++ * remove and re-create this directory during the callback. ++ * This can leave us with an unhashed dentry, but a ++ * successful mount! So we need to perform another ++ * cached lookup in case the dentry now exists. ++ */ ++ struct dentry *parent = dentry->d_parent; ++ struct dentry *new = d_lookup(parent, &dentry->d_name); ++ if (new != NULL) ++ dentry = new; ++ else ++ dentry = ERR_PTR(-ENOENT); ++ ++ if (unhashed) ++ dput(unhashed); ++ ++ return dentry; ++ } ++ ++ if (unhashed) ++ return unhashed; + + return NULL; + } +@@ -571,21 +620,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -600,6 +660,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -611,9 +672,9 @@ static int autofs4_dir_symlink(struct in + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want +- * this, because since the unlink is probably the result of an expire. +- * We simply d_drop it, which allows the dentry lookup to remount it +- * if necessary. ++ * this, because the unlink is probably the result of an expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -642,7 +703,15 @@ static int autofs4_dir_unlink(struct ino + + dir->i_mtime = CURRENT_TIME; + +- d_drop(dentry); ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); ++ spin_lock(&dentry->d_lock); ++ __d_drop(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&dcache_lock); + + return 0; + } +@@ -653,6 +722,9 @@ static int autofs4_dir_rmdir(struct inod + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct autofs_info *p_ino; + ++ DPRINTK("dentry %p, removing %.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ + if (!autofs4_oz_mode(sbi)) + return -EACCES; + +@@ -661,6 +733,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -695,11 +771,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -751,44 +837,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -852,11 +900,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_vfsmnt, p); + +--- linux-2.6.19.orig/fs/autofs4/expire.c ++++ linux-2.6.19/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,9 +469,15 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + + /* Call repeatedly until it returns -EAGAIN, meaning there's nothing +@@ -425,7 +492,7 @@ int autofs4_expire_multi(struct super_bl + if (arg && get_user(do_now, arg)) + return -EFAULT; + +- if (sbi->type & AUTOFS_TYPE_DIRECT) ++ if (autofs_type_trigger(sbi->type)) + dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); + else + dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); +@@ -435,9 +502,16 @@ int autofs4_expire_multi(struct super_bl + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + +--- linux-2.6.19.orig/include/linux/compat_ioctl.h ++++ linux-2.6.19/include/linux/compat_ioctl.h +@@ -568,8 +568,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.19/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.19.orig/fs/autofs4/Makefile ++++ linux-2.6.19/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.19/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,867 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct dentry *dentry; ++ struct vfsmount *mnt; ++ int err = -EAGAIN; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_vfsmnt; ++ ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); ++ else ++ dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); ++ ++ if (dentry) { ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ ++ /* ++ * This is synchronous because it makes the daemon a ++ * little easier ++ */ ++ err = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ sbi->sb->s_root->d_mounted++; ++ } ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ dput(dentry); ++ } ++ ++ return err; ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_vfsmnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t devid = new_encode_dev(sbi->sb->s_dev); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ devid = autofs4_get_dev(sbi); ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.19.orig/fs/autofs4/init.c ++++ linux-2.6.19/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.19/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.19.orig/include/linux/auto_fs.h ++++ linux-2.6.19/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.20-v5-update-20090903.patch b/patches/autofs4-2.6.20-v5-update-20090903.patch new file mode 100644 index 0000000..f0605ea --- /dev/null +++ b/patches/autofs4-2.6.20-v5-update-20090903.patch @@ -0,0 +1,3621 @@ +--- linux-2.6.20.orig/fs/autofs4/waitq.c ++++ linux-2.6.20/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -84,11 +95,16 @@ static void autofs4_notify_daemon(struct + struct autofs_wait_queue *wq, + int type) + { +- union autofs_packet_union pkt; ++ union { ++ struct autofs_packet_hdr hdr; ++ union autofs_packet_union v4_pkt; ++ union autofs_v5_packet_union v5_pkt; ++ } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -98,26 +114,26 @@ static void autofs4_notify_daemon(struct + /* Kernel protocol v4 missing and expire packets */ + case autofs_ptype_missing: + { +- struct autofs_packet_missing *mp = &pkt.missing; ++ struct autofs_packet_missing *mp = &pkt.v4_pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: + { +- struct autofs_packet_expire_multi *ep = &pkt.expire_multi; ++ struct autofs_packet_expire_multi *ep = &pkt.v4_pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -129,14 +145,14 @@ static void autofs4_notify_daemon(struct + case autofs_ptype_missing_direct: + case autofs_ptype_expire_direct: + { +- struct autofs_v5_packet *packet = &pkt.v5_packet; ++ struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; + + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -150,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -167,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -187,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -249,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -263,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -285,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -295,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -305,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -347,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -359,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -383,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.20.orig/include/linux/auto_fs4.h ++++ linux-2.6.20/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -59,6 +118,13 @@ struct autofs_packet_expire_multi { + char name[NAME_MAX+1]; + }; + ++union autofs_packet_union { ++ struct autofs_packet_hdr hdr; ++ struct autofs_packet_missing missing; ++ struct autofs_packet_expire expire; ++ struct autofs_packet_expire_multi expire_multi; ++}; ++ + /* autofs v5 common packet struct */ + struct autofs_v5_packet { + struct autofs_packet_hdr hdr; +@@ -78,20 +144,19 @@ typedef struct autofs_v5_packet autofs_p + typedef struct autofs_v5_packet autofs_packet_missing_direct_t; + typedef struct autofs_v5_packet autofs_packet_expire_direct_t; + +-union autofs_packet_union { ++union autofs_v5_packet_union { + struct autofs_packet_hdr hdr; +- struct autofs_packet_missing missing; +- struct autofs_packet_expire expire; +- struct autofs_packet_expire_multi expire_multi; + struct autofs_v5_packet v5_packet; ++ autofs_packet_missing_indirect_t missing_indirect; ++ autofs_packet_expire_indirect_t expire_indirect; ++ autofs_packet_missing_direct_t missing_direct; ++ autofs_packet_expire_direct_t expire_direct; + }; + + #define AUTOFS_IOC_EXPIRE_MULTI _IOW(0x93,0x66,int) + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.20.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.20/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,10 +72,18 @@ struct autofs_info { + + int flags; + ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; ++ + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -66,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -83,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -110,6 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -134,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -160,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.20.orig/fs/autofs4/inode.c ++++ linux-2.6.20/fs/autofs4/inode.c +@@ -25,8 +25,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -42,14 +44,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -158,14 +166,13 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- sb->s_fs_info = NULL; +- +- if ( !sbi->catatonic ) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); + ++ sb->s_fs_info = NULL; + kfree(sbi); + + out_kill_sb: +@@ -186,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -274,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -330,12 +337,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -370,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.20.orig/fs/autofs4/root.c ++++ linux-2.6.20/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ struct inode_operations autofs4_dir_inod + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,157 +82,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -ENOENT; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -292,7 +134,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -319,7 +162,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -334,50 +178,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -402,21 +258,33 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ + status = try_to_fill_dentry(dentry, flags); + if (status == 0) +- return 1; ++ return 1; + + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -430,6 +298,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -459,6 +328,17 @@ void autofs4_dentry_release(struct dentr + de->d_fsdata = NULL; + + if (inf) { ++ struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); ++ ++ if (sbi) { ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ } ++ + inf->dentry = NULL; + inf->inode = NULL; + +@@ -478,10 +358,116 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Bad luck, we've already been dentry_iput */ ++ if (!dentry->d_inode) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ + /* Lookups in the root directory */ + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -497,29 +483,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. +- */ +- dentry->d_op = &autofs4_root_dentry_operations; ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { ++ /* ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. ++ */ ++ dentry->d_op = &autofs4_root_dentry_operations; ++ ++ /* ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. ++ */ ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); ++ } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -534,22 +558,47 @@ static struct dentry *autofs4_lookup(str + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { ++ if (unhashed) ++ dput(unhashed); + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this +- * lookup even if the dentry is positive. Returning ENOENT here +- * doesn't do the right thing for all system calls, but it should +- * be OK for the operations we permit from an autofs. ++ * lookup. Returning ENOENT here doesn't do the right thing ++ * for all system calls, but it should be OK for the operations ++ * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) +- return ERR_PTR(-ENOENT); ++ if (!oz_mode && d_unhashed(dentry)) { ++ /* ++ * A user space application can (and has done in the past) ++ * remove and re-create this directory during the callback. ++ * This can leave us with an unhashed dentry, but a ++ * successful mount! So we need to perform another ++ * cached lookup in case the dentry now exists. ++ */ ++ struct dentry *parent = dentry->d_parent; ++ struct dentry *new = d_lookup(parent, &dentry->d_name); ++ if (new != NULL) ++ dentry = new; ++ else ++ dentry = ERR_PTR(-ENOENT); ++ ++ if (unhashed) ++ dput(unhashed); ++ ++ return dentry; ++ } ++ ++ if (unhashed) ++ return unhashed; + + return NULL; + } +@@ -571,21 +620,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -600,6 +660,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -611,9 +672,9 @@ static int autofs4_dir_symlink(struct in + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want +- * this, because since the unlink is probably the result of an expire. +- * We simply d_drop it, which allows the dentry lookup to remount it +- * if necessary. ++ * this, because the unlink is probably the result of an expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -642,7 +703,15 @@ static int autofs4_dir_unlink(struct ino + + dir->i_mtime = CURRENT_TIME; + +- d_drop(dentry); ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); ++ spin_lock(&dentry->d_lock); ++ __d_drop(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&dcache_lock); + + return 0; + } +@@ -653,6 +722,9 @@ static int autofs4_dir_rmdir(struct inod + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct autofs_info *p_ino; + ++ DPRINTK("dentry %p, removing %.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ + if (!autofs4_oz_mode(sbi)) + return -EACCES; + +@@ -661,6 +733,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -695,11 +771,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -751,44 +837,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -852,11 +900,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.20.orig/fs/autofs4/expire.c ++++ linux-2.6.20/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.20.orig/include/linux/compat_ioctl.h ++++ linux-2.6.20/include/linux/compat_ioctl.h +@@ -568,8 +568,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.20/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.20.orig/fs/autofs4/Makefile ++++ linux-2.6.20/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.20/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.20.orig/fs/autofs4/init.c ++++ linux-2.6.20/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.20/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.20.orig/include/linux/auto_fs.h ++++ linux-2.6.20/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.21-v5-update-20090903.patch b/patches/autofs4-2.6.21-v5-update-20090903.patch new file mode 100644 index 0000000..faff95b --- /dev/null +++ b/patches/autofs4-2.6.21-v5-update-20090903.patch @@ -0,0 +1,3564 @@ +--- linux-2.6.21.orig/fs/autofs4/root.c ++++ linux-2.6.21/fs/autofs4/root.c +@@ -26,25 +26,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -71,42 +71,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -114,157 +82,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -292,7 +134,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -319,7 +162,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -334,50 +178,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -402,12 +258,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -415,17 +282,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -439,6 +298,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -471,10 +331,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -496,7 +358,59 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -504,14 +418,14 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -533,33 +447,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -569,7 +466,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -585,50 +483,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- d_rehash(unhashed); +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -648,9 +563,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -659,7 +576,7 @@ static struct dentry *autofs4_lookup(str + * for all system calls, but it should be OK for the operations + * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) { ++ if (!oz_mode && d_unhashed(dentry)) { + /* + * A user space application can (and has done in the past) + * remove and re-create this directory during the callback. +@@ -681,7 +598,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -703,21 +620,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -732,6 +660,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -744,9 +673,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -776,9 +704,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -804,9 +733,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -841,11 +771,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -897,44 +837,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if ( status ) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -998,11 +900,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.21.orig/fs/autofs4/waitq.c ++++ linux-2.6.21/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.21.orig/fs/autofs4/expire.c ++++ linux-2.6.21/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.21.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.21/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.21.orig/fs/autofs4/inode.c ++++ linux-2.6.21/fs/autofs4/inode.c +@@ -25,8 +25,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -42,16 +44,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -160,8 +166,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -187,9 +193,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -275,13 +281,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -331,14 +337,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -373,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.21.orig/include/linux/auto_fs4.h ++++ linux-2.6.21/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- linux-2.6.21.orig/include/linux/compat_ioctl.h ++++ linux-2.6.21/include/linux/compat_ioctl.h +@@ -568,8 +568,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- /dev/null ++++ linux-2.6.21/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.21.orig/fs/autofs4/Makefile ++++ linux-2.6.21/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.21/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.21.orig/fs/autofs4/init.c ++++ linux-2.6.21/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.21/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.21.orig/include/linux/auto_fs.h ++++ linux-2.6.21/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.22-v5-update-20090903.patch b/patches/autofs4-2.6.22-v5-update-20090903.patch new file mode 100644 index 0000000..d78a915 --- /dev/null +++ b/patches/autofs4-2.6.22-v5-update-20090903.patch @@ -0,0 +1,3564 @@ +--- linux-2.6.22.orig/fs/autofs4/root.c ++++ linux-2.6.22/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -495,7 +357,59 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -503,14 +417,14 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -568,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -584,50 +482,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- d_rehash(unhashed); +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -647,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -658,7 +575,7 @@ static struct dentry *autofs4_lookup(str + * for all system calls, but it should be OK for the operations + * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) { ++ if (!oz_mode && d_unhashed(dentry)) { + /* + * A user space application can (and has done in the past) + * remove and re-create this directory during the callback. +@@ -680,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -702,21 +619,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -731,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -743,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -775,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -803,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -840,11 +770,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -896,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -997,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.22.orig/fs/autofs4/waitq.c ++++ linux-2.6.22/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.22.orig/fs/autofs4/expire.c ++++ linux-2.6.22/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.22.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.22/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.22.orig/fs/autofs4/inode.c ++++ linux-2.6.22/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -186,9 +192,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -273,13 +279,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -329,14 +335,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -370,7 +377,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.22.orig/fs/compat_ioctl.c ++++ linux-2.6.22/fs/compat_ioctl.c +@@ -2998,8 +2998,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.22.orig/include/linux/auto_fs4.h ++++ linux-2.6.22/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.22/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.22.orig/fs/autofs4/Makefile ++++ linux-2.6.22/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.22/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.22.orig/fs/autofs4/init.c ++++ linux-2.6.22/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.22/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.22.orig/include/linux/auto_fs.h ++++ linux-2.6.22/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.22.17-v5-update-20090903.patch b/patches/autofs4-2.6.22.17-v5-update-20090903.patch new file mode 100644 index 0000000..a11dd18 --- /dev/null +++ b/patches/autofs4-2.6.22.17-v5-update-20090903.patch @@ -0,0 +1,3564 @@ +--- linux-2.6.22.17.orig/fs/autofs4/root.c ++++ linux-2.6.22.17/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -495,7 +357,59 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -503,14 +417,14 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -568,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -584,50 +482,67 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* +- * Mark the dentry incomplete, but add it. This is needed so +- * that the VFS layer knows about the dentry, and we can count +- * on catching any lookups through the revalidate. +- * +- * Let all the hard work be done by the revalidate function that +- * needs to be able to do this anyway.. +- * +- * We need to do this before we release the directory semaphore. ++ * Mark the dentry incomplete but don't hash it. We do this ++ * to serialize our inode creation operations (symlink and ++ * mkdir) which prevents deadlock during the callback to ++ * the daemon. Subsequent user space lookups for the same ++ * dentry are placed on the wait queue while the daemon ++ * itself is allowed passage unresticted so the create ++ * operation itself can then hash the dentry. Finally, ++ * we check for the hashed dentry and return the newly ++ * hashed dentry. + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_add(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- d_rehash(unhashed); +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -647,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -658,7 +575,7 @@ static struct dentry *autofs4_lookup(str + * for all system calls, but it should be OK for the operations + * we permit from an autofs. + */ +- if (dentry->d_inode && d_unhashed(dentry)) { ++ if (!oz_mode && d_unhashed(dentry)) { + /* + * A user space application can (and has done in the past) + * remove and re-create this directory during the callback. +@@ -680,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -702,21 +619,32 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -731,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -743,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -775,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -803,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -840,11 +770,21 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); +- d_instantiate(dentry, inode); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } ++ d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; +@@ -896,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -997,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.22.17.orig/fs/autofs4/waitq.c ++++ linux-2.6.22.17/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.22.17.orig/fs/autofs4/expire.c ++++ linux-2.6.22.17/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.22.17.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.22.17/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.22.17.orig/fs/autofs4/inode.c ++++ linux-2.6.22.17/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -186,9 +192,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -273,13 +279,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -329,14 +335,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -370,7 +377,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.22.17.orig/fs/compat_ioctl.c ++++ linux-2.6.22.17/fs/compat_ioctl.c +@@ -2998,8 +2998,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.22.17.orig/include/linux/auto_fs4.h ++++ linux-2.6.22.17/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.22.17/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.22.17.orig/fs/autofs4/Makefile ++++ linux-2.6.22.17/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.22.17/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.22.17.orig/fs/autofs4/init.c ++++ linux-2.6.22.17/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.22.17/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.22.17.orig/include/linux/auto_fs.h ++++ linux-2.6.22.17/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.23-v5-update-20090903.patch b/patches/autofs4-2.6.23-v5-update-20090903.patch new file mode 100644 index 0000000..f37af94 --- /dev/null +++ b/patches/autofs4-2.6.23-v5-update-20090903.patch @@ -0,0 +1,3539 @@ +--- linux-2.6.23.orig/fs/autofs4/waitq.c ++++ linux-2.6.23/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.23.orig/fs/autofs4/expire.c ++++ linux-2.6.23/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.23.orig/fs/autofs4/root.c ++++ linux-2.6.23/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -495,7 +357,7 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -503,14 +365,66 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -568,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -584,8 +482,10 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* + * Mark the dentry incomplete but don't hash it. We do this + * to serialize our inode creation operations (symlink and +@@ -599,38 +499,50 @@ static struct dentry *autofs4_lookup(str + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_instantiate(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- * We need to ensure the AUTOFS_INF_EXPIRING flag is clear +- * before continuing as revalidate may fail when calling +- * try_to_fill_dentry (returning EAGAIN) if we don't. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -650,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -683,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -705,20 +619,31 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -734,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -746,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -778,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -806,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -843,10 +770,20 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -899,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -1000,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.23.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.23/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.23.orig/fs/autofs4/inode.c ++++ linux-2.6.23/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -186,9 +192,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -273,13 +279,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -329,14 +335,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -370,7 +377,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.23.orig/fs/compat_ioctl.c ++++ linux-2.6.23/fs/compat_ioctl.c +@@ -3017,8 +3017,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.23.orig/include/linux/auto_fs4.h ++++ linux-2.6.23/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.23/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.23.orig/fs/autofs4/Makefile ++++ linux-2.6.23/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.23/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = process_group(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.23.orig/fs/autofs4/init.c ++++ linux-2.6.23/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.23/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.23.orig/include/linux/auto_fs.h ++++ linux-2.6.23/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.24-v5-update-20090903.patch b/patches/autofs4-2.6.24-v5-update-20090903.patch new file mode 100644 index 0000000..448bcd3 --- /dev/null +++ b/patches/autofs4-2.6.24-v5-update-20090903.patch @@ -0,0 +1,3539 @@ +--- linux-2.6.24.orig/fs/autofs4/waitq.c ++++ linux-2.6.24/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.24.orig/fs/autofs4/expire.c ++++ linux-2.6.24/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.24.orig/fs/autofs4/root.c ++++ linux-2.6.24/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -495,7 +357,7 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -503,14 +365,66 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -568,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -584,8 +482,10 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* + * Mark the dentry incomplete but don't hash it. We do this + * to serialize our inode creation operations (symlink and +@@ -599,38 +499,50 @@ static struct dentry *autofs4_lookup(str + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_instantiate(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- * We need to ensure the AUTOFS_INF_EXPIRING flag is clear +- * before continuing as revalidate may fail when calling +- * try_to_fill_dentry (returning EAGAIN) if we don't. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -650,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -683,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -705,20 +619,31 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -734,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -746,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -778,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -806,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -843,10 +770,20 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -899,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -1000,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.24.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.24/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.24.orig/fs/autofs4/inode.c ++++ linux-2.6.24/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -186,9 +192,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -273,13 +279,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -327,14 +333,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -368,7 +375,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.24.orig/fs/compat_ioctl.c ++++ linux-2.6.24/fs/compat_ioctl.c +@@ -2384,8 +2384,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.24.orig/include/linux/auto_fs4.h ++++ linux-2.6.24/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.24/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.24.orig/fs/autofs4/Makefile ++++ linux-2.6.24/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.24/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.24.orig/fs/autofs4/init.c ++++ linux-2.6.24/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.24/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.24.orig/include/linux/auto_fs.h ++++ linux-2.6.24/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.24.4-v5-update-20090903.patch b/patches/autofs4-2.6.24.4-v5-update-20090903.patch new file mode 100644 index 0000000..349c13e --- /dev/null +++ b/patches/autofs4-2.6.24.4-v5-update-20090903.patch @@ -0,0 +1,3505 @@ +--- linux-2.6.24.4.orig/fs/autofs4/waitq.c ++++ linux-2.6.24.4/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.24.4.orig/fs/autofs4/expire.c ++++ linux-2.6.24.4/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.24.4.orig/fs/autofs4/root.c ++++ linux-2.6.24.4/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->mnt, &nd->dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -495,7 +357,7 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -503,14 +365,66 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -568,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -584,8 +482,10 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* + * Mark the dentry incomplete but don't hash it. We do this + * to serialize our inode creation operations (symlink and +@@ -599,38 +499,50 @@ static struct dentry *autofs4_lookup(str + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_instantiate(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- * We need to ensure the AUTOFS_INF_EXPIRING flag is clear +- * before continuing as revalidate may fail when calling +- * try_to_fill_dentry (returning EAGAIN) if we don't. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -650,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -683,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -705,20 +619,31 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -734,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -746,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -778,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -806,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -843,10 +770,20 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -899,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -1000,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.24.4.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.24.4/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.24.4.orig/fs/autofs4/inode.c ++++ linux-2.6.24.4/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -186,9 +192,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -273,13 +279,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(*type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(*type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(*type); + break; + default: + return 1; +@@ -327,14 +333,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -368,7 +375,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.24.4.orig/fs/compat_ioctl.c ++++ linux-2.6.24.4/fs/compat_ioctl.c +@@ -2384,8 +2384,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.24.4.orig/include/linux/auto_fs4.h ++++ linux-2.6.24.4/include/linux/auto_fs4.h +@@ -23,12 +23,37 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++#define set_autofs_type_indirect(type) (type = AUTOFS_TYPE_INDIRECT) ++#define autofs_type_indirect(type) (type == AUTOFS_TYPE_INDIRECT) ++ ++#define set_autofs_type_direct(type) (type = AUTOFS_TYPE_DIRECT) ++#define autofs_type_direct(type) (type == AUTOFS_TYPE_DIRECT) ++ ++#define set_autofs_type_offset(type) (type = AUTOFS_TYPE_OFFSET) ++#define autofs_type_offset(type) (type == AUTOFS_TYPE_OFFSET) ++ ++#define autofs_type_trigger(type) \ ++ (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET) ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++#define set_autofs_type_any(type) (type = AUTOFS_TYPE_ANY) ++#define autofs_type_any(type) (type == AUTOFS_TYPE_ANY) ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +123,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.24.4/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.24.4.orig/fs/autofs4/Makefile ++++ linux-2.6.24.4/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.24.4/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ inode = nd->dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->dentry); ++ nd->dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->mnt, &nd->dentry)) { ++ ino = autofs4_dentry_ino(nd->dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_release(&nd); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.dentry->d_inode && ++ nd.mnt->mnt_root == nd.dentry) { ++ err = 1; ++ magic = nd.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.dentry); ++ ++ if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { ++ if (follow_down(&nd.mnt, &nd.dentry)) { ++ struct inode *inode = nd.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_release(&nd); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.24.4.orig/fs/autofs4/init.c ++++ linux-2.6.24.4/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.24.4/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.24.4.orig/include/linux/auto_fs.h ++++ linux-2.6.24.4/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.25-v5-update-20090903.patch b/patches/autofs4-2.6.25-v5-update-20090903.patch new file mode 100644 index 0000000..0de06fe --- /dev/null +++ b/patches/autofs4-2.6.25-v5-update-20090903.patch @@ -0,0 +1,3541 @@ +--- linux-2.6.25.orig/fs/autofs4/waitq.c ++++ linux-2.6.25/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + +- if (--len > NAME_MAX) { ++ if (!len || --len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.25.orig/fs/autofs4/expire.c ++++ linux-2.6.25/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs + status = 0; + done: + DPRINTK("returning = %d", status); +- mntput(mnt); + dput(dentry); ++ mntput(mnt); + return status; + } + +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind + /* Can we expire this guy */ + if (autofs4_can_expire(dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } + goto next; + } +@@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; +- break; ++ goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +- break; ++ goto found; + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } ++ spin_unlock(&dcache_lock); ++ return NULL; + +- if (expired) { +- DPRINTK("returning %p %.*s", +- expired, (int)expired->d_name.len, expired->d_name.name); +- spin_lock(&dcache_lock); +- list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); +- spin_unlock(&dcache_lock); +- return expired; +- } ++found: ++ DPRINTK("returning %p %.*s", ++ expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ spin_lock(&dcache_lock); ++ list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); ++ return expired; ++} + +- return NULL; ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; + } + + /* Perform an expiry operation */ +@@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.25.orig/fs/autofs4/root.c ++++ linux-2.6.25/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct vfsmount *fp_mnt = mntget(mnt); +- struct dentry *fp_dentry = dget(dentry); +- +- if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { +- dput(fp_dentry); +- mntput(fp_mnt); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; +- } + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- int status = 0; +- +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } ++ int status; + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); +@@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- return status; ++ ++ return 0; + } + + /* For autofs direct mounts the follow link triggers the mount */ +@@ -333,51 +177,63 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->path.mnt, &nd->path.dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->path.mnt, +- &nd->path.dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->path.mnt, ++ &nd->path.dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -402,12 +258,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -415,17 +282,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -439,6 +298,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -471,10 +331,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -496,7 +358,7 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -504,14 +366,66 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -533,33 +447,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct inode *inode = dentry->d_inode; +- +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -569,7 +466,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -585,8 +483,10 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* + * Mark the dentry incomplete but don't hash it. We do this + * to serialize our inode creation operations (symlink and +@@ -600,38 +500,50 @@ static struct dentry *autofs4_lookup(str + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_instantiate(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- * We need to ensure the AUTOFS_INF_EXPIRING flag is clear +- * before continuing as revalidate may fail when calling +- * try_to_fill_dentry (returning EAGAIN) if we don't. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -651,9 +563,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -684,7 +598,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -706,20 +620,31 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -735,6 +660,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -747,9 +673,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -779,9 +704,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -807,9 +733,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -844,10 +771,20 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -900,44 +837,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -1001,11 +900,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.25.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.25/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __FUNCTION__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.25.orig/fs/autofs4/inode.c ++++ linux-2.6.25/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -191,9 +197,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -278,13 +284,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -332,14 +338,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -373,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.25.orig/fs/compat_ioctl.c ++++ linux-2.6.25/fs/compat_ioctl.c +@@ -2350,8 +2350,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.25.orig/include/linux/auto_fs4.h ++++ linux-2.6.25/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.25/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.25.orig/fs/autofs4/Makefile ++++ linux-2.6.25/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.25/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ inode = nd->path.dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ ino = autofs4_dentry_ino(nd->path.dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_put(&nd.path); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.path.dentry, nd.path.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.path.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.path.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.path.dentry->d_inode && ++ nd.path.mnt->mnt_root == nd.path.dentry) { ++ err = 1; ++ magic = nd.path.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.path.dentry); ++ ++ if (nd.path.mnt->mnt_mountpoint != nd.path.mnt->mnt_root) { ++ if (follow_down(&nd.path.mnt, &nd.path.dentry)) { ++ struct inode *inode = nd.path.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.25.orig/fs/autofs4/init.c ++++ linux-2.6.25/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.25/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.25.orig/include/linux/auto_fs.h ++++ linux-2.6.25/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.26-v5-update-20090903.patch b/patches/autofs4-2.6.26-v5-update-20090903.patch new file mode 100644 index 0000000..d62ebb1 --- /dev/null +++ b/patches/autofs4-2.6.26-v5-update-20090903.patch @@ -0,0 +1,3519 @@ +--- linux-2.6.26.orig/fs/autofs4/root.c ++++ linux-2.6.26/fs/autofs4/root.c +@@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod + static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); + static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); + static int autofs4_dir_open(struct inode *inode, struct file *file); +-static int autofs4_dir_close(struct inode *inode, struct file *file); +-static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +-static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); + static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); + static void *autofs4_follow_link(struct dentry *, struct nameidata *); + ++#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) ++#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) ++ + const struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_root_readdir, ++ .readdir = dcache_readdir, + .ioctl = autofs4_root_ioctl, + }; + + const struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, +- .release = autofs4_dir_close, ++ .release = dcache_dir_close, + .read = generic_read_dir, +- .readdir = autofs4_dir_readdir, ++ .readdir = dcache_readdir, + }; + + const struct inode_operations autofs4_indirect_root_inode_operations = { +@@ -70,42 +70,10 @@ const struct inode_operations autofs4_di + .rmdir = autofs4_dir_rmdir, + }; + +-static int autofs4_root_readdir(struct file *file, void *dirent, +- filldir_t filldir) +-{ +- struct autofs_sb_info *sbi = autofs4_sbi(file->f_path.dentry->d_sb); +- int oz_mode = autofs4_oz_mode(sbi); +- +- DPRINTK("called, filp->f_pos = %lld", file->f_pos); +- +- /* +- * Don't set reghost flag if: +- * 1) f_pos is larger than zero -- we've already been here. +- * 2) we haven't even enabled reghosting in the 1st place. +- * 3) this is the daemon doing a readdir +- */ +- if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) +- sbi->needs_reghost = 1; +- +- DPRINTK("needs_reghost = %d", sbi->needs_reghost); +- +- return dcache_readdir(file, dirent, filldir); +-} +- + static int autofs4_dir_open(struct inode *inode, struct file *file) + { + struct dentry *dentry = file->f_path.dentry; +- struct vfsmount *mnt = file->f_path.mnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor; +- int status; +- +- status = dcache_dir_open(inode, file); +- if (status) +- goto out; +- +- cursor = file->private_data; +- cursor->d_fsdata = NULL; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); +@@ -113,159 +81,31 @@ static int autofs4_dir_open(struct inode + if (autofs4_oz_mode(sbi)) + goto out; + +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- dcache_dir_close(inode, file); +- status = -EBUSY; +- goto out; +- } +- +- status = -ENOENT; +- if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { +- struct nameidata nd; +- int empty, ret; +- +- /* In case there are stale directory dentrys from a failed mount */ +- spin_lock(&dcache_lock); +- empty = list_empty(&dentry->d_subdirs); ++ /* ++ * An empty directory in an autofs file system is always a ++ * mount point. The daemon must have failed to mount this ++ * during lookup so it doesn't exist. This can happen, for ++ * example, if user space returns an incorrect status for a ++ * mount request. Otherwise we're doing a readdir on the ++ * autofs file system so just let the libfs routines handle ++ * it. ++ */ ++ spin_lock(&dcache_lock); ++ if (!d_mountpoint(dentry) && __simple_empty(dentry)) { + spin_unlock(&dcache_lock); +- +- if (!empty) +- d_invalidate(dentry); +- +- nd.flags = LOOKUP_DIRECTORY; +- ret = (dentry->d_op->d_revalidate)(dentry, &nd); +- +- if (ret <= 0) { +- if (ret < 0) +- status = ret; +- dcache_dir_close(inode, file); +- goto out; +- } +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = NULL; +- struct path fp_path = { .dentry = dentry, .mnt = mnt }; +- +- path_get(&fp_path); +- +- if (!autofs4_follow_mount(&fp_path.mnt, &fp_path.dentry)) { +- path_put(&fp_path); +- dcache_dir_close(inode, file); +- goto out; +- } +- +- fp = dentry_open(fp_path.dentry, fp_path.mnt, file->f_flags); +- status = PTR_ERR(fp); +- if (IS_ERR(fp)) { +- dcache_dir_close(inode, file); +- goto out; +- } +- cursor->d_fsdata = fp; +- } +- return 0; +-out: +- return status; +-} +- +-static int autofs4_dir_close(struct inode *inode, struct file *file) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status = 0; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- status = -EBUSY; +- goto out; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- if (!fp) { +- status = -ENOENT; +- goto out; +- } +- filp_close(fp, current->files); +- } +-out: +- dcache_dir_close(inode, file); +- return status; +-} +- +-static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +-{ +- struct dentry *dentry = file->f_path.dentry; +- struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); +- struct dentry *cursor = file->private_data; +- int status; +- +- DPRINTK("file=%p dentry=%p %.*s", +- file, dentry, dentry->d_name.len, dentry->d_name.name); +- +- if (autofs4_oz_mode(sbi)) +- goto out; +- +- if (autofs4_ispending(dentry)) { +- DPRINTK("dentry busy"); +- return -EBUSY; +- } +- +- if (d_mountpoint(dentry)) { +- struct file *fp = cursor->d_fsdata; +- +- if (!fp) +- return -ENOENT; +- +- if (!fp->f_op || !fp->f_op->readdir) +- goto out; +- +- status = vfs_readdir(fp, filldir, dirent); +- file->f_pos = fp->f_pos; +- if (status) +- autofs4_copy_atime(file, fp); +- return status; ++ return -ENOENT; + } ++ spin_unlock(&dcache_lock); + out: +- return dcache_readdir(file, dirent, filldir); ++ return dcache_dir_open(inode, file); + } + + static int try_to_fill_dentry(struct dentry *dentry, int flags) + { + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); +- struct dentry *new; + int status; + +- /* Block on any pending expiry here; invalidate the dentry +- when expiration is done to trigger mount request with a new +- dentry */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for expire %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); +- +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("expire done status=%d", status); +- +- /* +- * If the directory still exists the mount request must +- * continue otherwise it can't be followed at the right +- * time during the walk. +- */ +- status = d_invalidate(dentry); +- if (status != -EBUSY) +- return -EAGAIN; +- } +- + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); + +@@ -292,7 +132,8 @@ static int try_to_fill_dentry(struct den + return status; + } + /* Trigger mount for path component or follow link */ +- } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || ++ } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); +@@ -320,26 +161,6 @@ static int try_to_fill_dentry(struct den + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + +- /* +- * The dentry that is passed in from lookup may not be the one +- * we end up using, as mkdir can create a new one. If this +- * happens, and another process tries the lookup at the same time, +- * it will set the PENDING flag on this new dentry, but add itself +- * to our waitq. Then, if after the lookup succeeds, the first +- * process that requested the mount performs another lookup of the +- * same directory, it will show up as still pending! So, we need +- * to redo the lookup here and clear pending on that dentry. +- */ +- if (d_unhashed(dentry)) { +- new = d_lookup(dentry->d_parent, &dentry->d_name); +- if (new) { +- spin_lock(&new->d_lock); +- new->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&new->d_lock); +- dput(new); +- } +- } +- + return 0; + } + +@@ -355,51 +176,63 @@ static void *autofs4_follow_link(struct + DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", + dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, + nd->flags); +- +- /* If it's our master or we shouldn't trigger a mount we're done */ +- lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); +- if (oz_mode || !lookup_type) ++ /* ++ * For an expire of a covered direct or offset mount we need ++ * to beeak out of follow_down() at the autofs mount trigger ++ * (d_mounted--), so we can see the expiring flag, and manage ++ * the blocking and following here until the expire is completed. ++ */ ++ if (oz_mode) { ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ /* Follow down to our covering mount. */ ++ if (!follow_down(&nd->path.mnt, &nd->path.dentry)) ++ goto done; ++ goto follow; ++ } ++ spin_unlock(&sbi->fs_lock); + goto done; ++ } + +- /* If an expire request is pending wait for it. */ +- if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("waiting for active request %p name=%.*s", +- dentry, dentry->d_name.len, dentry->d_name.name); ++ /* If an expire request is pending everyone must wait. */ ++ autofs4_expire_wait(dentry); + +- status = autofs4_wait(sbi, dentry, NFY_NONE); +- +- DPRINTK("request done status=%d", status); +- } ++ /* We trigger a mount for almost all flags */ ++ lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); ++ if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) ++ goto follow; + + /* +- * If the dentry contains directories then it is an +- * autofs multi-mount with no root mount offset. So +- * don't try to mount it again. ++ * If the dentry contains directories then it is an autofs ++ * multi-mount with no root mount offset. So don't try to ++ * mount it again. + */ + spin_lock(&dcache_lock); +- if (!d_mountpoint(dentry) && __simple_empty(dentry)) { ++ if (dentry->d_flags & DCACHE_AUTOFS_PENDING || ++ (!d_mountpoint(dentry) && __simple_empty(dentry))) { + spin_unlock(&dcache_lock); + + status = try_to_fill_dentry(dentry, 0); + if (status) + goto out_error; + +- /* +- * The mount succeeded but if there is no root mount +- * it must be an autofs multi-mount with no root offset +- * so we don't need to follow the mount. +- */ +- if (d_mountpoint(dentry)) { +- if (!autofs4_follow_mount(&nd->path.mnt, +- &nd->path.dentry)) { +- status = -ENOENT; +- goto out_error; +- } +- } +- +- goto done; ++ goto follow; + } + spin_unlock(&dcache_lock); ++follow: ++ /* ++ * If there is no root mount it must be an autofs ++ * multi-mount with no root offset so we don't need ++ * to follow it. ++ */ ++ if (d_mountpoint(dentry)) { ++ if (!autofs4_follow_mount(&nd->path.mnt, ++ &nd->path.dentry)) { ++ status = -ENOENT; ++ goto out_error; ++ } ++ } + + done: + return NULL; +@@ -424,12 +257,23 @@ static int autofs4_revalidate(struct den + int status = 1; + + /* Pending dentry */ ++ spin_lock(&sbi->fs_lock); + if (autofs4_ispending(dentry)) { + /* The daemon never causes a mount to trigger */ ++ spin_unlock(&sbi->fs_lock); ++ + if (oz_mode) + return 1; + + /* ++ * If the directory has gone away due to an expire ++ * we have been called as ->d_revalidate() and so ++ * we need to return false and proceed to ->lookup(). ++ */ ++ if (autofs4_expire_wait(dentry) == -EAGAIN) ++ return 0; ++ ++ /* + * A zero status is success otherwise we have a + * negative error code. + */ +@@ -437,17 +281,9 @@ static int autofs4_revalidate(struct den + if (status == 0) + return 1; + +- /* +- * A status of EAGAIN here means that the dentry has gone +- * away while waiting for an expire to complete. If we are +- * racing with expire lookup will wait for it so this must +- * be a revalidate and we need to send it to lookup. +- */ +- if (status == -EAGAIN) +- return 0; +- + return status; + } ++ spin_unlock(&sbi->fs_lock); + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) +@@ -461,6 +297,7 @@ static int autofs4_revalidate(struct den + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); ++ + /* The daemon never causes a mount to trigger */ + if (oz_mode) + return 1; +@@ -493,10 +330,12 @@ void autofs4_dentry_release(struct dentr + struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); + + if (sbi) { +- spin_lock(&sbi->rehash_lock); +- if (!list_empty(&inf->rehash)) +- list_del(&inf->rehash); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&inf->active)) ++ list_del(&inf->active); ++ if (!list_empty(&inf->expiring)) ++ list_del(&inf->expiring); ++ spin_unlock(&sbi->lookup_lock); + } + + inf->dentry = NULL; +@@ -518,7 +357,59 @@ static struct dentry_operations autofs4_ + .d_release = autofs4_dentry_release, + }; + +-static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) ++{ ++ unsigned int len = name->len; ++ unsigned int hash = name->hash; ++ const unsigned char *str = name->name; ++ struct list_head *p, *head; ++ ++ spin_lock(&dcache_lock); ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->active_list; ++ list_for_each(p, head) { ++ struct autofs_info *ino; ++ struct dentry *dentry; ++ struct qstr *qstr; ++ ++ ino = list_entry(p, struct autofs_info, active); ++ dentry = ino->dentry; ++ ++ spin_lock(&dentry->d_lock); ++ ++ /* Already gone? */ ++ if (atomic_read(&dentry->d_count) == 0) ++ goto next; ++ ++ qstr = &dentry->d_name; ++ ++ if (dentry->d_name.hash != hash) ++ goto next; ++ if (dentry->d_parent != parent) ++ goto next; ++ ++ if (qstr->len != len) ++ goto next; ++ if (memcmp(qstr->name, str, len)) ++ goto next; ++ ++ if (d_unhashed(dentry)) { ++ dget(dentry); ++ spin_unlock(&dentry->d_lock); ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ return dentry; ++ } ++next: ++ spin_unlock(&dentry->d_lock); ++ } ++ spin_unlock(&sbi->lookup_lock); ++ spin_unlock(&dcache_lock); ++ ++ return NULL; ++} ++ ++static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) + { + unsigned int len = name->len; + unsigned int hash = name->hash; +@@ -526,14 +417,14 @@ static struct dentry *autofs4_lookup_unh + struct list_head *p, *head; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- head = &sbi->rehash_list; ++ spin_lock(&sbi->lookup_lock); ++ head = &sbi->expiring_list; + list_for_each(p, head) { + struct autofs_info *ino; + struct dentry *dentry; + struct qstr *qstr; + +- ino = list_entry(p, struct autofs_info, rehash); ++ ino = list_entry(p, struct autofs_info, expiring); + dentry = ino->dentry; + + spin_lock(&dentry->d_lock); +@@ -555,33 +446,16 @@ static struct dentry *autofs4_lookup_unh + goto next; + + if (d_unhashed(dentry)) { +- struct inode *inode = dentry->d_inode; +- +- ino = autofs4_dentry_ino(dentry); +- list_del_init(&ino->rehash); + dget(dentry); +- /* +- * Make the rehashed dentry negative so the VFS +- * behaves as it should. +- */ +- if (inode) { +- dentry->d_inode = NULL; +- list_del_init(&dentry->d_alias); +- spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); +- spin_unlock(&dcache_lock); +- iput(inode); +- return dentry; +- } + spin_unlock(&dentry->d_lock); +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + return dentry; + } + next: + spin_unlock(&dentry->d_lock); + } +- spin_unlock(&sbi->rehash_lock); ++ spin_unlock(&sbi->lookup_lock); + spin_unlock(&dcache_lock); + + return NULL; +@@ -591,7 +465,8 @@ next: + static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) + { + struct autofs_sb_info *sbi; +- struct dentry *unhashed; ++ struct autofs_info *ino; ++ struct dentry *expiring, *unhashed; + int oz_mode; + + DPRINTK("name = %.*s", +@@ -607,8 +482,10 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); +- if (!unhashed) { ++ unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); ++ if (unhashed) ++ dentry = unhashed; ++ else { + /* + * Mark the dentry incomplete but don't hash it. We do this + * to serialize our inode creation operations (symlink and +@@ -622,38 +499,50 @@ static struct dentry *autofs4_lookup(str + */ + dentry->d_op = &autofs4_root_dentry_operations; + +- dentry->d_fsdata = NULL; +- d_instantiate(dentry, NULL); +- } else { +- struct autofs_info *ino = autofs4_dentry_ino(unhashed); +- DPRINTK("rehash %p with %p", dentry, unhashed); + /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- * We need to ensure the AUTOFS_INF_EXPIRING flag is clear +- * before continuing as revalidate may fail when calling +- * try_to_fill_dentry (returning EAGAIN) if we don't. ++ * And we need to ensure that the same dentry is used for ++ * all following lookup calls until it is hashed so that ++ * the dentry flags are persistent throughout the request. + */ +- while (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { +- DPRINTK("wait for incomplete expire %p name=%.*s", +- unhashed, unhashed->d_name.len, +- unhashed->d_name.name); +- autofs4_wait(sbi, unhashed, NFY_NONE); +- DPRINTK("request completed"); +- } +- dentry = unhashed; ++ ino = autofs4_init_ino(NULL, sbi, 0555); ++ if (!ino) ++ return ERR_PTR(-ENOMEM); ++ ++ dentry->d_fsdata = ino; ++ ino->dentry = dentry; ++ ++ spin_lock(&sbi->lookup_lock); ++ list_add(&ino->active, &sbi->active_list); ++ spin_unlock(&sbi->lookup_lock); ++ ++ d_instantiate(dentry, NULL); + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- } +- +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); +- (dentry->d_op->d_revalidate)(dentry, nd); ++ if (dentry->d_op && dentry->d_op->d_revalidate) ++ (dentry->d_op->d_revalidate)(dentry, nd); + mutex_lock(&dir->i_mutex); + } + +@@ -673,9 +562,11 @@ static struct dentry *autofs4_lookup(str + return ERR_PTR(-ERESTARTNOINTR); + } + } +- spin_lock(&dentry->d_lock); +- dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; +- spin_unlock(&dentry->d_lock); ++ if (!oz_mode) { ++ spin_lock(&dentry->d_lock); ++ dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; ++ spin_unlock(&dentry->d_lock); ++ } + } + + /* +@@ -706,7 +597,7 @@ static struct dentry *autofs4_lookup(str + } + + if (unhashed) +- return dentry; ++ return unhashed; + + return NULL; + } +@@ -728,20 +619,31 @@ static int autofs4_dir_symlink(struct in + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; + +- ino->size = strlen(symname); +- ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + +- if (cp == NULL) { +- kfree(ino); +- return -ENOSPC; ++ ino->size = strlen(symname); ++ cp = kmalloc(ino->size + 1, GFP_KERNEL); ++ if (!cp) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ kfree(cp); ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -757,6 +659,7 @@ static int autofs4_dir_symlink(struct in + atomic_inc(&p_ino->count); + ino->inode = inode; + ++ ino->u.symlink = cp; + dir->i_mtime = CURRENT_TIME; + + return 0; +@@ -769,9 +672,8 @@ static int autofs4_dir_symlink(struct in + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because the unlink is probably the result of an expire. +- * We simply d_drop it and add it to a rehash candidates list in the +- * super block, which allows the dentry lookup to reuse it retaining +- * the flags, such as expire in progress, in case we're racing with expire. ++ * We simply d_drop it and add it to a expiring list in the super block, ++ * which allows the dentry lookup to check for an incomplete expire. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. +@@ -801,9 +703,10 @@ static int autofs4_dir_unlink(struct ino + dir->i_mtime = CURRENT_TIME; + + spin_lock(&dcache_lock); +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -829,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } +- spin_lock(&sbi->rehash_lock); +- list_add(&ino->rehash, &sbi->rehash_list); +- spin_unlock(&sbi->rehash_lock); ++ spin_lock(&sbi->lookup_lock); ++ if (list_empty(&ino->expiring)) ++ list_add(&ino->expiring, &sbi->expiring_list); ++ spin_unlock(&sbi->lookup_lock); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); +@@ -866,10 +770,20 @@ static int autofs4_dir_mkdir(struct inod + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); +- if (ino == NULL) +- return -ENOSPC; ++ if (!ino) ++ return -ENOMEM; ++ ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->active)) ++ list_del_init(&ino->active); ++ spin_unlock(&sbi->lookup_lock); + + inode = autofs4_get_inode(dir->i_sb, ino); ++ if (!inode) { ++ if (!dentry->d_fsdata) ++ kfree(ino); ++ return -ENOMEM; ++ } + d_add(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) +@@ -922,44 +836,6 @@ static inline int autofs4_get_protosubve + } + + /* +- * Tells the daemon whether we need to reghost or not. Also, clears +- * the reghost_needed flag. +- */ +-static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- +- DPRINTK("returning %d", sbi->needs_reghost); +- +- status = put_user(sbi->needs_reghost, p); +- if (status) +- return status; +- +- sbi->needs_reghost = 0; +- return 0; +-} +- +-/* +- * Enable / Disable reghosting ioctl() operation +- */ +-static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +-{ +- int status; +- int val; +- +- status = get_user(val, p); +- +- DPRINTK("reghost = %d", val); +- +- if (status) +- return status; +- +- /* turn on/off reghosting, with the val */ +- sbi->reghost_enabled = val; +- return 0; +-} +- +-/* + * Tells the daemon whether it can umount the autofs mount. + */ + static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +@@ -1023,11 +899,6 @@ static int autofs4_root_ioctl(struct ino + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + +- case AUTOFS_IOC_TOGGLEREGHOST: +- return autofs4_toggle_reghost(sbi, p); +- case AUTOFS_IOC_ASKREGHOST: +- return autofs4_ask_reghost(sbi, p); +- + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_path.mnt, p); + +--- linux-2.6.26.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.26/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __func__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -52,12 +72,18 @@ struct autofs_info { + + int flags; + +- struct list_head rehash; ++ struct completion expire_complete; ++ ++ struct list_head active; ++ struct list_head expiring; + + struct autofs_sb_info *sbi; + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -68,15 +94,14 @@ struct autofs_info { + }; + + #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ ++#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ + + struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ +- unsigned int hash; +- unsigned int len; +- char *name; ++ struct qstr name; + u32 dev; + u64 ino; + uid_t uid; +@@ -85,15 +110,11 @@ struct autofs_wait_queue { + pid_t tgid; + /* This is for status reporting upon return */ + int status; +- atomic_t wait_ctr; ++ unsigned int wait_ctr; + }; + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -112,8 +133,9 @@ struct autofs_sb_info { + struct mutex wq_mutex; + spinlock_t fs_lock; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +- spinlock_t rehash_lock; +- struct list_head rehash_list; ++ spinlock_t lookup_lock; ++ struct list_head active_list; ++ struct list_head expiring_list; + }; + + static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +@@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct + static inline int autofs4_ispending(struct dentry *dentry) + { + struct autofs_info *inf = autofs4_dentry_ino(dentry); +- int pending = 0; + + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 1; + +- if (inf) { +- spin_lock(&inf->sbi->fs_lock); +- pending = inf->flags & AUTOFS_INF_EXPIRING; +- spin_unlock(&inf->sbi->fs_lock); +- } ++ if (inf->flags & AUTOFS_INF_EXPIRING) ++ return 1; + +- return pending; ++ return 0; + } + + static inline void autofs4_copy_atime(struct file *src, struct file *dst) +@@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info + + /* Expiration */ + int is_autofs4_dentry(struct dentry *); ++int autofs4_expire_wait(struct dentry *dentry); + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.26.orig/fs/autofs4/inode.c ++++ linux-2.6.26/fs/autofs4/inode.c +@@ -24,8 +24,10 @@ + + static void ino_lnkfree(struct autofs_info *ino) + { +- kfree(ino->u.symlink); +- ino->u.symlink = NULL; ++ if (ino->u.symlink) { ++ kfree(ino->u.symlink); ++ ino->u.symlink = NULL; ++ } + } + + struct autofs_info *autofs4_init_ino(struct autofs_info *ino, +@@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str + if (ino == NULL) + return NULL; + +- ino->flags = 0; +- ino->mode = mode; +- ino->inode = NULL; +- ino->dentry = NULL; +- ino->size = 0; +- +- INIT_LIST_HEAD(&ino->rehash); ++ if (!reinit) { ++ ino->flags = 0; ++ ino->inode = NULL; ++ ino->dentry = NULL; ++ ino->size = 0; ++ INIT_LIST_HEAD(&ino->active); ++ INIT_LIST_HEAD(&ino->expiring); ++ atomic_set(&ino->count, 0); ++ } + ++ ino->uid = 0; ++ ino->gid = 0; ++ ino->mode = mode; + ino->last_used = jiffies; +- atomic_set(&ino->count, 0); + + ino->sbi = sbi; + +@@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block + if (!sbi) + goto out_kill_sb; + +- if (!sbi->catatonic) +- autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ ++ /* Free wait queues, close pipe */ ++ autofs4_catatonic_mode(sbi); + + /* Clean up and release dangling references */ + autofs4_force_release(sbi); +@@ -191,9 +197,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -278,13 +284,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -332,14 +338,15 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); + spin_lock_init(&sbi->fs_lock); + sbi->queues = NULL; +- spin_lock_init(&sbi->rehash_lock); +- INIT_LIST_HEAD(&sbi->rehash_list); ++ spin_lock_init(&sbi->lookup_lock); ++ INIT_LIST_HEAD(&sbi->active_list); ++ INIT_LIST_HEAD(&sbi->expiring_list); + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; +@@ -373,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.26.orig/fs/autofs4/waitq.c ++++ linux-2.6.26/fs/autofs4/waitq.c +@@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof + { + struct autofs_wait_queue *wq, *nwq; + ++ mutex_lock(&sbi->wq_mutex); ++ if (sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return; ++ } ++ + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; +@@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof + while (wq) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ +- kfree(wq->name); +- wq->name = NULL; ++ if (wq->name.name) { ++ kfree(wq->name.name); ++ wq->name.name = NULL; ++ } ++ wq->wait_ctr--; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; ++ sbi->pipefd = -1; ++ mutex_unlock(&sbi->wq_mutex); + } + + static int autofs4_write(struct file *file, const void *addr, int bytes) +@@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct + union autofs_packet_union v4_pkt; + union autofs_v5_packet_union v5_pkt; + } pkt; ++ struct file *pipe = NULL; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", +- wq->wait_queue_token, wq->len, wq->name, type); ++ wq->wait_queue_token, wq->name.len, wq->name.name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + +@@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; +- mp->len = wq->len; +- memcpy(mp->name, wq->name, wq->len); +- mp->name[wq->len] = '\0'; ++ mp->len = wq->name.len; ++ memcpy(mp->name, wq->name.name, wq->name.len); ++ mp->name[wq->name.len] = '\0'; + break; + } + case autofs_ptype_expire_multi: +@@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; +- ep->len = wq->len; +- memcpy(ep->name, wq->name, wq->len); +- ep->name[wq->len] = '\0'; ++ ep->len = wq->name.len; ++ memcpy(ep->name, wq->name.name, wq->name.len); ++ ep->name[wq->name.len] = '\0'; + break; + } + /* +@@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct + pktsz = sizeof(*packet); + + packet->wait_queue_token = wq->wait_queue_token; +- packet->len = wq->len; +- memcpy(packet->name, wq->name, wq->len); +- packet->name[wq->len] = '\0'; ++ packet->len = wq->name.len; ++ memcpy(packet->name, wq->name.name, wq->name.len); ++ packet->name[wq->name.len] = '\0'; + packet->dev = wq->dev; + packet->ino = wq->ino; + packet->uid = wq->uid; +@@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct + return; + } + +- if (autofs4_write(sbi->pipe, &pkt, pktsz)) +- autofs4_catatonic_mode(sbi); ++ /* Check if we have become catatonic */ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ pipe = sbi->pipe; ++ get_file(pipe); ++ } ++ mutex_unlock(&sbi->wq_mutex); ++ ++ if (pipe) { ++ if (autofs4_write(pipe, &pkt, pktsz)) ++ autofs4_catatonic_mode(sbi); ++ fput(pipe); ++ } + } + + static int autofs4_getpath(struct autofs_sb_info *sbi, +@@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs + } + + static struct autofs_wait_queue * +-autofs4_find_wait(struct autofs_sb_info *sbi, +- char *name, unsigned int hash, unsigned int len) ++autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) + { + struct autofs_wait_queue *wq; + + for (wq = sbi->queues; wq; wq = wq->next) { +- if (wq->hash == hash && +- wq->len == len && +- wq->name && !memcmp(wq->name, name, len)) ++ if (wq->name.hash == qstr->hash && ++ wq->name.len == qstr->len && ++ wq->name.name && ++ !memcmp(wq->name.name, qstr->name, qstr->len)) + break; + } + return wq; + } + +-int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, +- enum autofs_notify notify) ++/* ++ * Check if we have a valid request. ++ * Returns ++ * 1 if the request should continue. ++ * In this case we can return an autofs_wait_queue entry if one is ++ * found or NULL to idicate a new wait needs to be created. ++ * 0 or a negative errno if the request shouldn't continue. ++ */ ++static int validate_request(struct autofs_wait_queue **wait, ++ struct autofs_sb_info *sbi, ++ struct qstr *qstr, ++ struct dentry*dentry, enum autofs_notify notify) + { +- struct autofs_info *ino; + struct autofs_wait_queue *wq; +- char *name; +- unsigned int len = 0; +- unsigned int hash = 0; +- int status, type; +- +- /* In catatonic mode, we don't wait for nobody */ +- if (sbi->catatonic) +- return -ENOENT; +- +- name = kmalloc(NAME_MAX + 1, GFP_KERNEL); +- if (!name) +- return -ENOMEM; ++ struct autofs_info *ino; + +- /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) +- len = sprintf(name, "%p", dentry); +- else { +- len = autofs4_getpath(sbi, dentry, &name); +- if (!len) { +- kfree(name); +- return -ENOENT; +- } ++ /* Wait in progress, continue; */ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- hash = full_name_hash(name, len); + +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); +- return -EINTR; +- } ++ *wait = NULL; + +- wq = autofs4_find_wait(sbi, name, hash, len); ++ /* If we don't yet have any info this is a new request */ + ino = autofs4_dentry_ino(dentry); +- if (!wq && ino && notify == NFY_NONE) { ++ if (!ino) ++ return 1; ++ ++ /* ++ * If we've been asked to wait on an existing expire (NFY_NONE) ++ * but there is no wait in the queue ... ++ */ ++ if (notify == NFY_NONE) { + /* + * Either we've betean the pending expire to post it's + * wait or it finished while we waited on the mutex. +@@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * + while (ino->flags & AUTOFS_INF_EXPIRING) { + mutex_unlock(&sbi->wq_mutex); + schedule_timeout_interruptible(HZ/10); +- if (mutex_lock_interruptible(&sbi->wq_mutex)) { +- kfree(name); ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) + return -EINTR; ++ ++ wq = autofs4_find_wait(sbi, qstr); ++ if (wq) { ++ *wait = wq; ++ return 1; + } +- wq = autofs4_find_wait(sbi, name, hash, len); +- if (wq) +- break; + } + + /* +@@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * + * cases where we wait on NFY_NONE neither depend on the + * return status of the wait. + */ +- if (!wq) { +- kfree(name); +- mutex_unlock(&sbi->wq_mutex); ++ return 0; ++ } ++ ++ /* ++ * If we've been asked to trigger a mount and the request ++ * completed while we waited on the mutex ... ++ */ ++ if (notify == NFY_MOUNT) { ++ /* ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) + return 0; ++ } ++ ++ return 1; ++} ++ ++int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, ++ enum autofs_notify notify) ++{ ++ struct autofs_wait_queue *wq; ++ struct qstr qstr; ++ char *name; ++ int status, ret, type; ++ ++ /* In catatonic mode, we don't wait for nobody */ ++ if (sbi->catatonic) ++ return -ENOENT; ++ ++ if (!dentry->d_inode) { ++ /* ++ * A wait for a negative dentry is invalid for certain ++ * cases. A direct or offset mount "always" has its mount ++ * point directory created and so the request dentry must ++ * be positive or the map key doesn't exist. The situation ++ * is very similar for indirect mounts except only dentrys ++ * in the root of the autofs file system may be negative. ++ */ ++ if (autofs_type_trigger(sbi->type)) ++ return -ENOENT; ++ else if (!IS_ROOT(dentry->d_parent)) ++ return -ENOENT; ++ } ++ ++ name = kmalloc(NAME_MAX + 1, GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ++ /* If this is a direct mount request create a dummy name */ ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) ++ qstr.len = sprintf(name, "%p", dentry); ++ else { ++ qstr.len = autofs4_getpath(sbi, dentry, &name); ++ if (!qstr.len) { ++ kfree(name); ++ return -ENOENT; + } + } ++ qstr.name = name; ++ qstr.hash = full_name_hash(name, qstr.len); ++ ++ if (mutex_lock_interruptible(&sbi->wq_mutex)) { ++ kfree(qstr.name); ++ return -EINTR; ++ } ++ ++ ret = validate_request(&wq, sbi, &qstr, dentry, notify); ++ if (ret <= 0) { ++ if (ret == 0) ++ mutex_unlock(&sbi->wq_mutex); ++ kfree(qstr.name); ++ return ret; ++ } + + if (!wq) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if (!wq) { +- kfree(name); ++ kfree(qstr.name); + mutex_unlock(&sbi->wq_mutex); + return -ENOMEM; + } +@@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); +- wq->hash = hash; +- wq->name = name; +- wq->len = len; ++ memcpy(&wq->name, &qstr, sizeof(struct qstr)); + wq->dev = autofs4_get_dev(sbi); + wq->ino = autofs4_get_ino(sbi); + wq->uid = current->uid; +@@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * + wq->pid = current->pid; + wq->tgid = current->tgid; + wq->status = -EINTR; /* Status return if interrupted */ +- atomic_set(&wq->wait_ctr, 2); ++ wq->wait_ctr = 2; + mutex_unlock(&sbi->wq_mutex); + + if (sbi->version < 5) { +@@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + + /* autofs4_notify_daemon() may block */ + autofs4_notify_daemon(sbi, wq, type); + } else { +- atomic_inc(&wq->wait_ctr); ++ wq->wait_ctr++; + mutex_unlock(&sbi->wq_mutex); +- kfree(name); ++ kfree(qstr.name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", +- (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); ++ (unsigned long) wq->wait_queue_token, wq->name.len, ++ wq->name.name, notify); + } + +- /* wq->name is NULL if and only if the lock is already released */ +- +- if (sbi->catatonic) { +- /* We might have slept, so check again for catatonic mode */ +- wq->status = -ENOENT; +- kfree(wq->name); +- wq->name = NULL; +- } +- +- if (wq->name) { ++ /* ++ * wq->name.name is NULL iff the lock is already released ++ * or the mount has been made catatonic. ++ */ ++ if (wq->name.name) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; +@@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + +- wait_event_interruptible(wq->queue, wq->name == NULL); ++ wait_event_interruptible(wq->queue, wq->name.name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; +@@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ +- if (atomic_dec_and_test(&wq->wait_ctr)) ++ mutex_lock(&sbi->wq_mutex); ++ if (!--wq->wait_ctr) + kfree(wq); ++ mutex_unlock(&sbi->wq_mutex); + + return status; + } +@@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s + } + + *wql = wq->next; /* Unlink from chain */ +- mutex_unlock(&sbi->wq_mutex); +- kfree(wq->name); +- wq->name = NULL; /* Do not wait on this queue */ +- ++ kfree(wq->name.name); ++ wq->name.name = NULL; /* Do not wait on this queue */ + wq->status = status; +- +- if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ ++ wake_up_interruptible(&wq->queue); ++ if (!--wq->wait_ctr) + kfree(wq); +- else +- wake_up_interruptible(&wq->queue); ++ mutex_unlock(&sbi->wq_mutex); + + return 0; + } +--- linux-2.6.26.orig/fs/autofs4/expire.c ++++ linux-2.6.26/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir + now = jiffies; + timeout = sbi->exp_timeout; + +- /* Lock the tree as we must expire as a whole */ + spin_lock(&sbi->fs_lock); + if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { + struct autofs_info *ino = autofs4_dentry_ino(root); +- +- /* Set this flag early to catch sys_chdir and the like */ ++ if (d_mountpoint(root)) { ++ ino->flags |= AUTOFS_INF_MOUNTPOINT; ++ root->d_mounted--; ++ } + ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); + spin_unlock(&sbi->fs_lock); + return root; + } +@@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; ++ struct autofs_info *ino; ++ unsigned int ino_count; + + if (!root) + return NULL; +@@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind + dentry = dget(dentry); + spin_unlock(&dcache_lock); + ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ + /* + * Case 1: (i) indirect mount or top level pseudo direct mount + * (autofs-4.1). +@@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 2; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + /* Can we umount this guy */ + if (autofs4_mount_busy(mnt, dentry)) + goto next; +@@ -343,23 +368,25 @@ static struct dentry *autofs4_expire_ind + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { +- /* Lock the tree as we must expire as a whole */ +- spin_lock(&sbi->fs_lock); +- if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { +- struct autofs_info *inf = autofs4_dentry_ino(dentry); ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; + +- /* Set this flag early to catch sys_chdir and the like */ +- inf->flags |= AUTOFS_INF_EXPIRING; +- spin_unlock(&sbi->fs_lock); ++ if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { + expired = dentry; + goto found; + } +- spin_unlock(&sbi->fs_lock); + /* + * Case 3: pseudo direct mount, expire individual leaves + * (autofs-4.1). + */ + } else { ++ /* Path walk currently on this dentry? */ ++ ino_count = atomic_read(&ino->count) + 1; ++ if (atomic_read(&dentry->d_count) > ino_count) ++ goto next; ++ + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); +@@ -367,6 +394,7 @@ static struct dentry *autofs4_expire_ind + } + } + next: ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; +@@ -377,12 +405,45 @@ next: + found: + DPRINTK("returning %p %.*s", + expired, (int)expired->d_name.len, expired->d_name.name); ++ ino = autofs4_dentry_ino(expired); ++ ino->flags |= AUTOFS_INF_EXPIRING; ++ init_completion(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + spin_lock(&dcache_lock); + list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); + spin_unlock(&dcache_lock); + return expired; + } + ++int autofs4_expire_wait(struct dentry *dentry) ++{ ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ struct autofs_info *ino = autofs4_dentry_ino(dentry); ++ int status; ++ ++ /* Block on any pending expire */ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_EXPIRING) { ++ spin_unlock(&sbi->fs_lock); ++ ++ DPRINTK("waiting for expire %p name=%.*s", ++ dentry, dentry->d_name.len, dentry->d_name.name); ++ ++ status = autofs4_wait(sbi, dentry, NFY_NONE); ++ wait_for_completion(&ino->expire_complete); ++ ++ DPRINTK("expire done status=%d", status); ++ ++ if (d_unhashed(dentry)) ++ return -EAGAIN; ++ ++ return status; ++ } ++ spin_unlock(&sbi->fs_lock); ++ ++ return 0; ++} ++ + /* Perform an expiry operation */ + int autofs4_expire_run(struct super_block *sb, + struct vfsmount *mnt, +@@ -390,7 +451,9 @@ int autofs4_expire_run(struct super_bloc + struct autofs_packet_expire __user *pkt_p) + { + struct autofs_packet_expire pkt; ++ struct autofs_info *ino; + struct dentry *dentry; ++ int ret = 0; + + memset(&pkt,0,sizeof pkt); + +@@ -406,39 +469,59 @@ int autofs4_expire_run(struct super_bloc + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) +- return -EFAULT; ++ ret = -EFAULT; + +- return 0; ++ spin_lock(&sbi->fs_lock); ++ ino = autofs4_dentry_ino(dentry); ++ ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); ++ ++ return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; +- +- if (arg && get_user(do_now, arg)) +- return -EFAULT; + +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ +- ino->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ++ ++ spin_lock(&sbi->fs_lock); ++ if (ino->flags & AUTOFS_INF_MOUNTPOINT) { ++ sb->s_root->d_mounted++; ++ ino->flags &= ~AUTOFS_INF_MOUNTPOINT; ++ } + ino->flags &= ~AUTOFS_INF_EXPIRING; ++ complete_all(&ino->expire_complete); ++ spin_unlock(&sbi->fs_lock); + dput(dentry); + } + + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.26.orig/fs/compat_ioctl.c ++++ linux-2.6.26/fs/compat_ioctl.c +@@ -2350,8 +2350,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) + COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) + COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) +-COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) +-COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) + COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) + /* Raw devices */ + COMPATIBLE_IOCTL(RAW_SETBIND) +--- linux-2.6.26.orig/include/linux/auto_fs4.h ++++ linux-2.6.26/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +@@ -98,8 +157,6 @@ union autofs_v5_packet_union { + #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI + #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) +-#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) +-#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) + #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) + + +--- /dev/null ++++ linux-2.6.26/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.26.orig/fs/autofs4/Makefile ++++ linux-2.6.26/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.26/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,840 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ inode = nd->path.dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ ino = autofs4_dentry_ino(nd->path.dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_put(&nd.path); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.path.dentry, nd.path.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.path.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.path.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.path.dentry->d_inode && ++ nd.path.mnt->mnt_root == nd.path.dentry) { ++ err = 1; ++ magic = nd.path.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.path.dentry); ++ ++ if (nd.path.mnt->mnt_mountpoint != nd.path.mnt->mnt_root) { ++ if (follow_down(&nd.path.mnt, &nd.path.dentry)) { ++ struct inode *inode = nd.path.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.26.orig/fs/autofs4/init.c ++++ linux-2.6.26/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.26/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.26.orig/include/linux/auto_fs.h ++++ linux-2.6.26/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.27-v5-update-20090903.patch b/patches/autofs4-2.6.27-v5-update-20090903.patch new file mode 100644 index 0000000..d0d92e0 --- /dev/null +++ b/patches/autofs4-2.6.27-v5-update-20090903.patch @@ -0,0 +1,2041 @@ +--- linux-2.6.27.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.27/fs/autofs4/autofs_i.h +@@ -14,6 +14,7 @@ + /* Internal header file for autofs */ + + #include ++#include + #include + #include + +@@ -21,6 +22,9 @@ + #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY + #define AUTOFS_IOC_COUNT 32 + ++#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) ++#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) ++ + #include + #include + #include +@@ -35,11 +39,27 @@ + /* #define DEBUG */ + + #ifdef DEBUG +-#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __func__ , ##args); } while(0) ++#define DPRINTK(fmt, args...) \ ++do { \ ++ printk(KERN_DEBUG "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) + #else +-#define DPRINTK(fmt,args...) do {} while(0) ++#define DPRINTK(fmt, args...) do {} while (0) + #endif + ++#define AUTOFS_WARN(fmt, args...) \ ++do { \ ++ printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) ++ ++#define AUTOFS_ERROR(fmt, args...) \ ++do { \ ++ printk(KERN_ERR "pid %d: %s: " fmt "\n", \ ++ current->pid, __func__, ##args); \ ++} while (0) ++ + /* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never +@@ -61,6 +81,9 @@ struct autofs_info { + unsigned long last_used; + atomic_t count; + ++ uid_t uid; ++ gid_t gid; ++ + mode_t mode; + size_t size; + +@@ -92,10 +115,6 @@ struct autofs_wait_queue { + + #define AUTOFS_SBI_MAGIC 0x6d4a556d + +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 +- + struct autofs_sb_info { + u32 magic; + int pipefd; +@@ -167,8 +186,21 @@ int autofs4_expire_wait(struct dentry *d + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int how); ++ ++/* Device node initialization */ ++ ++int autofs_dev_ioctl_init(void); ++void autofs_dev_ioctl_exit(void); + + /* Operations structures */ + +--- linux-2.6.27.orig/fs/autofs4/expire.c ++++ linux-2.6.27/fs/autofs4/expire.c +@@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs + mntget(mnt); + dget(dentry); + +- if (!autofs4_follow_mount(&mnt, &dentry)) ++ if (!follow_down(&mnt, &dentry)) + goto done; + +- /* This is an autofs submount, we can't expire it */ +- if (is_autofs4_dentry(dentry)) +- goto done; ++ if (is_autofs4_dentry(dentry)) { ++ struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); ++ ++ /* This is an autofs submount, we can't expire it */ ++ if (autofs_type_indirect(sbi->type)) ++ goto done; ++ ++ /* ++ * Otherwise it's an offset mount and we need to check ++ * if we can umount its mount, if there is one. ++ */ ++ if (!d_mountpoint(dentry)) { ++ status = 0; ++ goto done; ++ } ++ } + + /* Update the expiry counter if fs is busy */ + if (!may_umount_tree(mnt)) { +@@ -244,10 +257,10 @@ cont: + } + + /* Check if we can expire a direct mount (possibly a tree) */ +-static struct dentry *autofs4_expire_direct(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_direct(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = dget(sb->s_root); +@@ -283,10 +296,10 @@ static struct dentry *autofs4_expire_dir + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +-static struct dentry *autofs4_expire_indirect(struct super_block *sb, +- struct vfsmount *mnt, +- struct autofs_sb_info *sbi, +- int how) ++struct dentry *autofs4_expire_indirect(struct super_block *sb, ++ struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, ++ int how) + { + unsigned long timeout; + struct dentry *root = sb->s_root; +@@ -467,22 +480,16 @@ int autofs4_expire_run(struct super_bloc + return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_DIRECT) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); +@@ -505,3 +512,16 @@ int autofs4_expire_multi(struct super_bl + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.27.orig/fs/autofs4/inode.c ++++ linux-2.6.27/fs/autofs4/inode.c +@@ -53,6 +53,8 @@ struct autofs_info *autofs4_init_ino(str + atomic_set(&ino->count, 0); + } + ++ ino->uid = 0; ++ ino->gid = 0; + ino->mode = mode; + ino->last_used = jiffies; + +@@ -195,9 +197,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -282,13 +284,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -336,7 +338,7 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = 0; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); +@@ -378,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.27.orig/fs/autofs4/waitq.c ++++ linux-2.6.27/fs/autofs4/waitq.c +@@ -297,20 +297,14 @@ static int validate_request(struct autof + */ + if (notify == NFY_MOUNT) { + /* +- * If the dentry isn't hashed just go ahead and try the +- * mount again with a new wait (not much else we can do). +- */ +- if (!d_unhashed(dentry)) { +- /* +- * But if the dentry is hashed, that means that we +- * got here through the revalidate path. Thus, we +- * need to check if the dentry has been mounted +- * while we waited on the wq_mutex. If it has, +- * simply return success. +- */ +- if (d_mountpoint(dentry)) +- return 0; +- } ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) ++ return 0; + } + + return 1; +@@ -337,7 +331,7 @@ int autofs4_wait(struct autofs_sb_info * + * is very similar for indirect mounts except only dentrys + * in the root of the autofs file system may be negative. + */ +- if (sbi->type & (AUTOFS_TYPE_DIRECT|AUTOFS_TYPE_OFFSET)) ++ if (autofs_type_trigger(sbi->type)) + return -ENOENT; + else if (!IS_ROOT(dentry->d_parent)) + return -ENOENT; +@@ -348,7 +342,7 @@ int autofs4_wait(struct autofs_sb_info * + return -ENOMEM; + + /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) + qstr.len = sprintf(name, "%p", dentry); + else { + qstr.len = autofs4_getpath(sbi, dentry, &name); +@@ -406,11 +400,11 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_DIRECT) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } +@@ -457,6 +451,40 @@ int autofs4_wait(struct autofs_sb_info * + + status = wq->status; + ++ /* ++ * For direct and offset mounts we need to track the requester's ++ * uid and gid in the dentry info struct. This is so it can be ++ * supplied, on request, by the misc device ioctl interface. ++ * This is needed during daemon resatart when reconnecting ++ * to existing, active, autofs mounts. The uid and gid (and ++ * related string values) may be used for macro substitution ++ * in autofs mount maps. ++ */ ++ if (!status) { ++ struct autofs_info *ino; ++ struct dentry *de = NULL; ++ ++ /* direct mount or browsable map */ ++ ino = autofs4_dentry_ino(dentry); ++ if (!ino) { ++ /* If not lookup actual dentry used */ ++ de = d_lookup(dentry->d_parent, &dentry->d_name); ++ if (de) ++ ino = autofs4_dentry_ino(de); ++ } ++ ++ /* Set mount requester */ ++ if (ino) { ++ spin_lock(&sbi->fs_lock); ++ ino->uid = wq->uid; ++ ino->gid = wq->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++ if (de) ++ dput(de); ++ } ++ + /* Are we the last process to need status? */ + mutex_lock(&sbi->wq_mutex); + if (!--wq->wait_ctr) +--- linux-2.6.27.orig/include/linux/auto_fs4.h ++++ linux-2.6.27/include/linux/auto_fs4.h +@@ -23,12 +23,71 @@ + #define AUTOFS_MIN_PROTO_VERSION 3 + #define AUTOFS_MAX_PROTO_VERSION 5 + +-#define AUTOFS_PROTO_SUBVERSION 0 ++#define AUTOFS_PROTO_SUBVERSION 1 + + /* Mask for expire behaviour */ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} ++ + /* Daemon notification packet types */ + enum autofs_notify { + NFY_NONE, +--- /dev/null ++++ linux-2.6.27/Documentation/filesystems/autofs4-mount-control.txt +@@ -0,0 +1,414 @@ ++ ++Miscellaneous Device control operations for the autofs4 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//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 it's 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. ++ ++ ++autofs4 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[0]; ++}; ++ ++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 anonymous 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 autofs4 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 autofs4 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 structure field protover.version ++and ptotosubver.sub_version respectively. These commands return 0 on ++success or one of the negative error codes if validation fails. ++ ++ ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ ++ ++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 the path field set and the size field adjusted appropriately as well ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. ++ ++ ++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 setpipefd.pipefd field 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 withing 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. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. ++ ++ ++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 as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. ++ ++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, independent of the mount timeout, can be ++requested by setting the expire.how field to 1. 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 askumount.may_umount field, 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 must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 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 the located path is the ++root of a mount 1 is returned along with the super magic of the mount ++or 0 otherwise. ++ +--- linux-2.6.27.orig/fs/autofs4/Makefile ++++ linux-2.6.27/fs/autofs4/Makefile +@@ -4,4 +4,4 @@ + + obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +-autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o ++autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o +--- /dev/null ++++ linux-2.6.27/fs/autofs4/dev-ioctl.c +@@ -0,0 +1,841 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "autofs_i.h" ++ ++/* ++ * This module implements an interface for routing autofs ioctl control ++ * commands via a miscellaneous device file. ++ * ++ * The alternate interface is needed because we need to be able open ++ * an ioctl file descriptor on an autofs mount that may be covered by ++ * another mount. This situation arises when starting automount(8) ++ * or other user space daemon which uses direct mounts or offset ++ * mounts (used for autofs lazy mount/umount of nested mount trees), ++ * which have been left busy at at service shutdown. ++ */ ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++typedef int (*ioctl_fn)(struct file *, ++struct autofs_sb_info *, struct autofs_dev_ioctl *); ++ ++static int check_name(const char *name) ++{ ++ if (!strchr(name, '/')) ++ return -EINVAL; ++ return 0; ++} ++ ++/* ++ * Check a string doesn't overrun the chunk of ++ * memory we copied from user land. ++ */ ++static int invalid_str(char *str, void *end) ++{ ++ while ((void *) str <= end) ++ if (!*str++) ++ return 0; ++ return -EINVAL; ++} ++ ++/* ++ * Check that the user compiled against correct version of autofs ++ * misc device code. ++ * ++ * As well as checking the version compatibility this always copies ++ * the kernel interface version out. ++ */ ++static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err = 0; ++ ++ if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || ++ (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { ++ AUTOFS_WARN("ioctl control interface version mismatch: " ++ "kernel(%u.%u), user(%u.%u), cmd(%d)", ++ AUTOFS_DEV_IOCTL_VERSION_MAJOR, ++ AUTOFS_DEV_IOCTL_VERSION_MINOR, ++ param->ver_major, param->ver_minor, cmd); ++ err = -EINVAL; ++ } ++ ++ /* Fill in the kernel version. */ ++ param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ ++ return err; ++} ++ ++/* ++ * Copy parameter control struct, including a possible path allocated ++ * at the end of the struct. ++ */ ++static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) ++{ ++ struct autofs_dev_ioctl tmp, *ads; ++ ++ if (copy_from_user(&tmp, in, sizeof(tmp))) ++ return ERR_PTR(-EFAULT); ++ ++ if (tmp.size < sizeof(tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ ads = kmalloc(tmp.size, GFP_KERNEL); ++ if (!ads) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(ads, in, tmp.size)) { ++ kfree(ads); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ return ads; ++} ++ ++static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) ++{ ++ kfree(param); ++ return; ++} ++ ++/* ++ * Check sanity of parameter control fields and if a path is present ++ * check that it is terminated and contains at least one "/". ++ */ ++static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) ++{ ++ int err; ++ ++ if ((err = check_dev_ioctl_version(cmd, param))) { ++ AUTOFS_WARN("invalid device control module version " ++ "supplied for cmd(0x%08x)", cmd); ++ goto out; ++ } ++ ++ if (param->size > sizeof(*param)) { ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); ++ if (err) { ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ ++ err = check_name(param->path); ++ if (err) { ++ AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", ++ cmd); ++ goto out; ++ } ++ } ++ ++ err = 0; ++out: ++ return err; ++} ++ ++/* ++ * Get the autofs super block info struct from the file opened on ++ * the autofs mount point. ++ */ ++static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) ++{ ++ struct autofs_sb_info *sbi = NULL; ++ struct inode *inode; ++ ++ if (f) { ++ inode = f->f_path.dentry->d_inode; ++ sbi = autofs4_sbi(inode->i_sb); ++ } ++ return sbi; ++} ++ ++/* Return autofs module protocol version */ ++static int autofs_dev_ioctl_protover(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protover.version = sbi->version; ++ return 0; ++} ++ ++/* Return autofs module protocol sub version */ ++static int autofs_dev_ioctl_protosubver(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->protosubver.sub_version = sbi->sub_version; ++ return 0; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested device number (aka. new_encode_dev(sb->s_dev). ++ */ ++static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) ++{ ++ struct dentry *dentry; ++ struct inode *inode; ++ struct super_block *sb; ++ dev_t s_dev; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ inode = nd->path.dentry->d_inode; ++ if (!inode) ++ break; ++ ++ sb = inode->i_sb; ++ s_dev = new_encode_dev(sb->s_dev); ++ if (devno == s_dev) { ++ if (sb->s_magic == AUTOFS_SUPER_MAGIC) { ++ err = 0; ++ break; ++ } ++ } ++ } ++out: ++ return err; ++} ++ ++/* ++ * Walk down the mount stack looking for an autofs mount that ++ * has the requested mount type (ie. indirect, direct or offset). ++ */ ++static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) ++{ ++ struct dentry *dentry; ++ struct autofs_info *ino; ++ unsigned int err; ++ ++ err = -ENOENT; ++ ++ /* Lookup the dentry name at the base of our mount point */ ++ dentry = d_lookup(nd->path.dentry, &nd->last); ++ if (!dentry) ++ goto out; ++ ++ dput(nd->path.dentry); ++ nd->path.dentry = dentry; ++ ++ /* And follow the mount stack looking for our autofs mount */ ++ while (follow_down(&nd->path.mnt, &nd->path.dentry)) { ++ ino = autofs4_dentry_ino(nd->path.dentry); ++ if (ino && ino->sbi->type & type) { ++ err = 0; ++ break; ++ } ++ } ++out: ++ return err; ++} ++ ++static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) ++{ ++ struct files_struct *files = current->files; ++ struct fdtable *fdt; ++ ++ spin_lock(&files->file_lock); ++ fdt = files_fdtable(files); ++ BUG_ON(fdt->fd[fd] != NULL); ++ rcu_assign_pointer(fdt->fd[fd], file); ++ FD_SET(fd, fdt->close_on_exec); ++ spin_unlock(&files->file_lock); ++} ++ ++ ++/* ++ * Open a file descriptor on the autofs mount point corresponding ++ * to the given path and device number (aka. new_encode_dev(sb->s_dev)). ++ */ ++static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) ++{ ++ struct file *filp; ++ struct nameidata nd; ++ int err, fd; ++ ++ fd = get_unused_fd(); ++ if (likely(fd >= 0)) { ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ /* ++ * Search down, within the parent, looking for an ++ * autofs super block that has the device number ++ * corresponding to the autofs fs we want to open. ++ */ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) { ++ path_put(&nd.path); ++ goto out; ++ } ++ ++ filp = dentry_open(nd.path.dentry, nd.path.mnt, O_RDONLY); ++ if (IS_ERR(filp)) { ++ err = PTR_ERR(filp); ++ goto out; ++ } ++ ++ autofs_dev_ioctl_fd_install(fd, filp); ++ } ++ ++ return fd; ++ ++out: ++ put_unused_fd(fd); ++ return err; ++} ++ ++/* Open a file descriptor on an autofs mount point */ ++static int autofs_dev_ioctl_openmount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ const char *path; ++ dev_t devid; ++ int err, fd; ++ ++ /* param->path has already been checked */ ++ if (!param->openmount.devid) ++ return -EINVAL; ++ ++ param->ioctlfd = -1; ++ ++ path = param->path; ++ devid = param->openmount.devid; ++ ++ err = 0; ++ fd = autofs_dev_ioctl_open_mountpoint(path, devid); ++ if (unlikely(fd < 0)) { ++ err = fd; ++ goto out; ++ } ++ ++ param->ioctlfd = fd; ++out: ++ return err; ++} ++ ++/* Close file descriptor allocated above (user can also use close(2)). */ ++static int autofs_dev_ioctl_closemount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ return sys_close(param->ioctlfd); ++} ++ ++/* ++ * Send "ready" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_ready(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ ++ token = (autofs_wqt_t) param->ready.token; ++ return autofs4_wait_release(sbi, token, 0); ++} ++ ++/* ++ * Send "fail" status for an existing wait (either a mount or an expire ++ * request). ++ */ ++static int autofs_dev_ioctl_fail(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs_wqt_t token; ++ int status; ++ ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; ++ return autofs4_wait_release(sbi, token, status); ++} ++ ++/* ++ * Set the pipe fd for kernel communication to the daemon. ++ * ++ * Normally this is set at mount using an option but if we ++ * are reconnecting to a busy mount then we need to use this ++ * to tell the autofs mount about the new kernel pipe fd. In ++ * order to protect mounts against incorrectly setting the ++ * pipefd we also require that the autofs mount be catatonic. ++ * ++ * This also sets the process group id used to identify the ++ * controlling process (eg. the owning automount(8) daemon). ++ */ ++static int autofs_dev_ioctl_setpipefd(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ int pipefd; ++ int err = 0; ++ ++ if (param->setpipefd.pipefd == -1) ++ return -EINVAL; ++ ++ pipefd = param->setpipefd.pipefd; ++ ++ mutex_lock(&sbi->wq_mutex); ++ if (!sbi->catatonic) { ++ mutex_unlock(&sbi->wq_mutex); ++ return -EBUSY; ++ } else { ++ struct file *pipe = fget(pipefd); ++ if (!pipe->f_op || !pipe->f_op->write) { ++ err = -EPIPE; ++ fput(pipe); ++ goto out; ++ } ++ sbi->oz_pgrp = task_pgrp_nr(current); ++ sbi->pipefd = pipefd; ++ sbi->pipe = pipe; ++ sbi->catatonic = 0; ++ } ++out: ++ mutex_unlock(&sbi->wq_mutex); ++ return err; ++} ++ ++/* ++ * Make the autofs mount point catatonic, no longer responsive to ++ * mount requests. Also closes the kernel pipe file descriptor. ++ */ ++static int autofs_dev_ioctl_catatonic(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ autofs4_catatonic_mode(sbi); ++ return 0; ++} ++ ++/* Set the autofs mount timeout */ ++static int autofs_dev_ioctl_timeout(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ unsigned long timeout; ++ ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; ++ sbi->exp_timeout = timeout * HZ; ++ return 0; ++} ++ ++/* ++ * Return the uid and gid of the last request for the mount ++ * ++ * 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. ++ */ ++static int autofs_dev_ioctl_requester(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct autofs_info *ino; ++ struct nameidata nd; ++ const char *path; ++ dev_t devid; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ devid = sbi->sb->s_dev; ++ ++ param->requester.uid = param->requester.gid = -1; ++ ++ /* Get nameidata of the parent directory */ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, devid); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ if (ino) { ++ err = 0; ++ autofs4_expire_wait(nd.path.dentry); ++ spin_lock(&sbi->fs_lock); ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; ++ spin_unlock(&sbi->fs_lock); ++ } ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ * more that can be done. ++ */ ++static int autofs_dev_ioctl_expire(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct vfsmount *mnt; ++ int how; ++ ++ how = param->expire.how; ++ mnt = fp->f_path.mnt; ++ ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); ++} ++ ++/* Check if autofs mount point is in use */ ++static int autofs_dev_ioctl_askumount(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ param->askumount.may_umount = 0; ++ if (may_umount(fp->f_path.mnt)) ++ param->askumount.may_umount = 1; ++ return 0; ++} ++ ++/* ++ * Check if the given path is a mountpoint. ++ * ++ * If we are supplied with the file descriptor of an autofs ++ * mount we're looking for a specific mount. In this case ++ * the path 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 ++ * path is a mount point and the super magic of the covering ++ * mount if there is one or 0 if it isn't a mountpoint. ++ * ++ * If we aren't supplied with a file descriptor then we ++ * lookup the nameidata of the path and check if it is the ++ * root of a mount. If a type is given we are looking for ++ * a particular autofs mount and if we don't find a match ++ * we return fail. If the located nameidata path is the ++ * root of a mount we return 1 along with the super magic ++ * of the mount or 0 otherwise. ++ * ++ * In both cases the the device number (as returned by ++ * new_encode_dev()) is also returned. ++ */ ++static int autofs_dev_ioctl_ismountpoint(struct file *fp, ++ struct autofs_sb_info *sbi, ++ struct autofs_dev_ioctl *param) ++{ ++ struct nameidata nd; ++ const char *path; ++ unsigned int type; ++ unsigned int devid, magic; ++ int err = -ENOENT; ++ ++ if (param->size <= sizeof(*param)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ path = param->path; ++ type = param->ismountpoint.in.type; ++ ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; ++ ++ if (!fp || param->ioctlfd == -1) { ++ if (autofs_type_any(type)) { ++ struct super_block *sb; ++ ++ err = path_lookup(path, LOOKUP_FOLLOW, &nd); ++ if (err) ++ goto out; ++ ++ sb = nd.path.dentry->d_sb; ++ devid = new_encode_dev(sb->s_dev); ++ } else { ++ struct autofs_info *ino; ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_sbi_type(&nd, type); ++ if (err) ++ goto out_release; ++ ++ ino = autofs4_dentry_ino(nd.path.dentry); ++ devid = autofs4_get_dev(ino->sbi); ++ } ++ ++ err = 0; ++ if (nd.path.dentry->d_inode && ++ nd.path.mnt->mnt_root == nd.path.dentry) { ++ err = 1; ++ magic = nd.path.dentry->d_inode->i_sb->s_magic; ++ } ++ } else { ++ dev_t dev = autofs4_get_dev(sbi); ++ ++ err = path_lookup(path, LOOKUP_PARENT, &nd); ++ if (err) ++ goto out; ++ ++ err = autofs_dev_ioctl_find_super(&nd, dev); ++ if (err) ++ goto out_release; ++ ++ devid = dev; ++ ++ err = have_submounts(nd.path.dentry); ++ ++ if (nd.path.mnt->mnt_mountpoint != nd.path.mnt->mnt_root) { ++ if (follow_down(&nd.path.mnt, &nd.path.dentry)) { ++ struct inode *inode = nd.path.dentry->d_inode; ++ magic = inode->i_sb->s_magic; ++ } ++ } ++ } ++ ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ ++out_release: ++ path_put(&nd.path); ++out: ++ return err; ++} ++ ++/* ++ * Our range of ioctl numbers isn't 0 based so we need to shift ++ * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table ++ * lookup. ++ */ ++#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) ++ ++static ioctl_fn lookup_dev_ioctl(unsigned int cmd) ++{ ++ static struct { ++ int cmd; ++ ioctl_fn fn; ++ } _ioctls[] = { ++ {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), ++ autofs_dev_ioctl_protover}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), ++ autofs_dev_ioctl_protosubver}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), ++ autofs_dev_ioctl_openmount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), ++ autofs_dev_ioctl_closemount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), ++ autofs_dev_ioctl_ready}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), ++ autofs_dev_ioctl_fail}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), ++ autofs_dev_ioctl_setpipefd}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), ++ autofs_dev_ioctl_catatonic}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), ++ autofs_dev_ioctl_timeout}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), ++ autofs_dev_ioctl_requester}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), ++ autofs_dev_ioctl_expire}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), ++ autofs_dev_ioctl_askumount}, ++ {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), ++ autofs_dev_ioctl_ismountpoint} ++ }; ++ unsigned int idx = cmd_idx(cmd); ++ ++ return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; ++} ++ ++/* ioctl dispatcher */ ++static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) ++{ ++ struct autofs_dev_ioctl *param; ++ struct file *fp; ++ struct autofs_sb_info *sbi; ++ unsigned int cmd_first, cmd; ++ ioctl_fn fn = NULL; ++ int err = 0; ++ ++ /* only root can play with this */ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); ++ cmd = _IOC_NR(command); ++ ++ if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || ++ cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { ++ return -ENOTTY; ++ } ++ ++ /* Copy the parameters into kernel space. */ ++ param = copy_dev_ioctl(user); ++ if (IS_ERR(param)) ++ return PTR_ERR(param); ++ ++ err = validate_dev_ioctl(command, param); ++ if (err) ++ goto out; ++ ++ /* The validate routine above always sets the version */ ++ if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) ++ goto done; ++ ++ fn = lookup_dev_ioctl(cmd); ++ if (!fn) { ++ AUTOFS_WARN("unknown command 0x%08x", command); ++ return -ENOTTY; ++ } ++ ++ fp = NULL; ++ sbi = NULL; ++ ++ /* ++ * For obvious reasons the openmount can't have a file ++ * descriptor yet. We don't take a reference to the ++ * file during close to allow for immediate release. ++ */ ++ if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && ++ cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { ++ fp = fget(param->ioctlfd); ++ if (!fp) { ++ if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) ++ goto cont; ++ err = -EBADF; ++ goto out; ++ } ++ ++ if (!fp->f_op) { ++ err = -ENOTTY; ++ fput(fp); ++ goto out; ++ } ++ ++ sbi = autofs_dev_ioctl_sbi(fp); ++ if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { ++ err = -EINVAL; ++ fput(fp); ++ goto out; ++ } ++ ++ /* ++ * Admin needs to be able to set the mount catatonic in ++ * order to be able to perform the re-open. ++ */ ++ if (!autofs4_oz_mode(sbi) && ++ cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { ++ err = -EACCES; ++ fput(fp); ++ goto out; ++ } ++ } ++cont: ++ err = fn(fp, sbi, param); ++ ++ if (fp) ++ fput(fp); ++done: ++ if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) ++ err = -EFAULT; ++out: ++ free_dev_ioctl(param); ++ return err; ++} ++ ++static long autofs_dev_ioctl(struct file *file, uint command, ulong u) ++{ ++ int err; ++ err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); ++ return (long) err; ++} ++ ++#ifdef CONFIG_COMPAT ++static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) ++{ ++ return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); ++} ++#else ++#define autofs_dev_ioctl_compat NULL ++#endif ++ ++static const struct file_operations _dev_ioctl_fops = { ++ .unlocked_ioctl = autofs_dev_ioctl, ++ .compat_ioctl = autofs_dev_ioctl_compat, ++ .owner = THIS_MODULE, ++}; ++ ++static struct miscdevice _autofs_dev_ioctl_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = AUTOFS_DEVICE_NAME, ++ .fops = &_dev_ioctl_fops ++}; ++ ++/* Register/deregister misc character device */ ++int autofs_dev_ioctl_init(void) ++{ ++ int r; ++ ++ r = misc_register(&_autofs_dev_ioctl_misc); ++ if (r) { ++ AUTOFS_ERROR("misc_register failed for control device"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++void autofs_dev_ioctl_exit(void) ++{ ++ misc_deregister(&_autofs_dev_ioctl_misc); ++ return; ++} ++ +--- linux-2.6.27.orig/fs/autofs4/init.c ++++ linux-2.6.27/fs/autofs4/init.c +@@ -29,11 +29,20 @@ static struct file_system_type autofs_fs + + static int __init init_autofs4_fs(void) + { +- return register_filesystem(&autofs_fs_type); ++ int err; ++ ++ err = register_filesystem(&autofs_fs_type); ++ if (err) ++ return err; ++ ++ autofs_dev_ioctl_init(); ++ ++ return err; + } + + static void __exit exit_autofs4_fs(void) + { ++ autofs_dev_ioctl_exit(); + unregister_filesystem(&autofs_fs_type); + } + +--- /dev/null ++++ linux-2.6.27/include/linux/auto_dev-ioctl.h +@@ -0,0 +1,229 @@ ++/* ++ * Copyright 2008 Red Hat, Inc. All rights reserved. ++ * Copyright 2008 Ian Kent ++ * ++ * This file is part of the Linux kernel and is made available under ++ * the terms of the GNU General Public License, version 2, or at your ++ * option, any later version, incorporated herein by reference. ++ */ ++ ++#ifndef _LINUX_AUTO_DEV_IOCTL_H ++#define _LINUX_AUTO_DEV_IOCTL_H ++ ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ ++ ++#define AUTOFS_DEVICE_NAME "autofs" ++ ++#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 ++#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 ++ ++#define AUTOFS_DEVID_LEN 16 ++ ++#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) ++ ++/* ++ * An ioctl interface for autofs mount point control. ++ */ ++ ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ ++/* ++ * All the ioctls use this structure. ++ * When sending a path size must account for the total length ++ * of the chunk of memory otherwise is is the size of the ++ * structure. ++ */ ++ ++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[0]; ++}; ++ ++static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) ++{ ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); ++ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; ++ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; ++ in->size = sizeof(struct autofs_dev_ioctl); ++ in->ioctlfd = -1; ++ return; ++} ++ ++/* ++ * If you change this make sure you make the corresponding change ++ * to autofs-dev-ioctl.c:lookup_ioctl() ++ */ ++enum { ++ /* Get various version info */ ++ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, ++ ++ /* Open mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, ++ ++ /* Close mount ioctl fd */ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, ++ ++ /* Mount/expire status returns */ ++ AUTOFS_DEV_IOCTL_READY_CMD, ++ AUTOFS_DEV_IOCTL_FAIL_CMD, ++ ++ /* Activate/deactivate autofs mount */ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, ++ ++ /* Expiry timeout */ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, ++ ++ /* Get mount last requesting uid and gid */ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, ++ ++ /* Check for eligible expire candidates */ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, ++ ++ /* Request busy status */ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, ++ ++ /* Check if path is a mountpoint */ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, ++}; ++ ++#define AUTOFS_IOCTL 0x93 ++ ++#define AUTOFS_DEV_IOCTL_VERSION \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_OPENMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_READY \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_FAIL \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_SETPIPEFD \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_CATATONIC \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_TIMEOUT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_REQUESTER \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_EXPIRE \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) ++ ++#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ ++ _IOWR(AUTOFS_IOCTL, \ ++ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) ++ ++#endif /* _LINUX_AUTO_DEV_IOCTL_H */ +--- linux-2.6.27.orig/fs/autofs4/root.c ++++ linux-2.6.27/fs/autofs4/root.c +@@ -485,22 +485,6 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- expiring = autofs4_lookup_expiring(sbi, dentry->d_parent, &dentry->d_name); +- if (expiring) { +- /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- */ +- ino = autofs4_dentry_ino(expiring); +- autofs4_expire_wait(expiring); +- spin_lock(&sbi->lookup_lock); +- if (!list_empty(&ino->expiring)) +- list_del_init(&ino->expiring); +- spin_unlock(&sbi->lookup_lock); +- dput(expiring); +- } +- + unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); + if (unhashed) + dentry = unhashed; +@@ -538,14 +522,31 @@ static struct dentry *autofs4_lookup(str + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); ++ if (dentry->d_op && dentry->d_op->d_revalidate) + (dentry->d_op->d_revalidate)(dentry, nd); +- mutex_lock(&dir->i_mutex); +- } ++ mutex_lock(&dir->i_mutex); + } + + /* +--- linux-2.6.27.orig/include/linux/auto_fs.h ++++ linux-2.6.27/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.28-v5-update-20090903.patch b/patches/autofs4-2.6.28-v5-update-20090903.patch new file mode 100644 index 0000000..84757cb --- /dev/null +++ b/patches/autofs4-2.6.28-v5-update-20090903.patch @@ -0,0 +1,908 @@ +--- linux-2.6.28.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.28/fs/autofs4/autofs_i.h +@@ -25,8 +25,6 @@ + #define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) + #define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) + +-#define AUTOFS_TYPE_TRIGGER (AUTOFS_TYPE_DIRECT|AUTOFS_TYPE_OFFSET) +- + #include + #include + #include +@@ -188,6 +186,8 @@ int autofs4_expire_wait(struct dentry *d + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); + struct dentry *autofs4_expire_direct(struct super_block *sb, +--- linux-2.6.28.orig/fs/autofs4/dev-ioctl.c ++++ linux-2.6.28/fs/autofs4/dev-ioctl.c +@@ -124,7 +124,7 @@ static inline void free_dev_ioctl(struct + + /* + * Check sanity of parameter control fields and if a path is present +- * check that it has a "/" and is terminated. ++ * check that it is terminated and contains at least one "/". + */ + static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) + { +@@ -138,15 +138,16 @@ static int validate_dev_ioctl(int cmd, s + } + + if (param->size > sizeof(*param)) { +- err = check_name(param->path); ++ err = invalid_str(param->path, ++ (void *) ((size_t) param + param->size)); + if (err) { +- AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", +- cmd); ++ AUTOFS_WARN( ++ "path string terminator missing for cmd(0x%08x)", ++ cmd); + goto out; + } + +- err = invalid_str(param->path, +- (void *) ((size_t) param + param->size)); ++ err = check_name(param->path); + if (err) { + AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", + cmd); +@@ -180,7 +181,7 @@ static int autofs_dev_ioctl_protover(str + struct autofs_sb_info *sbi, + struct autofs_dev_ioctl *param) + { +- param->arg1 = sbi->version; ++ param->protover.version = sbi->version; + return 0; + } + +@@ -189,7 +190,7 @@ static int autofs_dev_ioctl_protosubver( + struct autofs_sb_info *sbi, + struct autofs_dev_ioctl *param) + { +- param->arg1 = sbi->sub_version; ++ param->protosubver.sub_version = sbi->sub_version; + return 0; + } + +@@ -334,13 +335,13 @@ static int autofs_dev_ioctl_openmount(st + int err, fd; + + /* param->path has already been checked */ +- if (!param->arg1) ++ if (!param->openmount.devid) + return -EINVAL; + + param->ioctlfd = -1; + + path = param->path; +- devid = param->arg1; ++ devid = param->openmount.devid; + + err = 0; + fd = autofs_dev_ioctl_open_mountpoint(path, devid); +@@ -372,7 +373,7 @@ static int autofs_dev_ioctl_ready(struct + { + autofs_wqt_t token; + +- token = (autofs_wqt_t) param->arg1; ++ token = (autofs_wqt_t) param->ready.token; + return autofs4_wait_release(sbi, token, 0); + } + +@@ -387,8 +388,8 @@ static int autofs_dev_ioctl_fail(struct + autofs_wqt_t token; + int status; + +- token = (autofs_wqt_t) param->arg1; +- status = param->arg2 ? param->arg2 : -ENOENT; ++ token = (autofs_wqt_t) param->fail.token; ++ status = param->fail.status ? param->fail.status : -ENOENT; + return autofs4_wait_release(sbi, token, status); + } + +@@ -411,10 +412,10 @@ static int autofs_dev_ioctl_setpipefd(st + int pipefd; + int err = 0; + +- if (param->arg1 == -1) ++ if (param->setpipefd.pipefd == -1) + return -EINVAL; + +- pipefd = param->arg1; ++ pipefd = param->setpipefd.pipefd; + + mutex_lock(&sbi->wq_mutex); + if (!sbi->catatonic) { +@@ -456,8 +457,8 @@ static int autofs_dev_ioctl_timeout(stru + { + unsigned long timeout; + +- timeout = param->arg1; +- param->arg1 = sbi->exp_timeout / HZ; ++ timeout = param->timeout.timeout; ++ param->timeout.timeout = sbi->exp_timeout / HZ; + sbi->exp_timeout = timeout * HZ; + return 0; + } +@@ -488,7 +489,7 @@ static int autofs_dev_ioctl_requester(st + path = param->path; + devid = sbi->sb->s_dev; + +- param->arg1 = param->arg2 = -1; ++ param->requester.uid = param->requester.gid = -1; + + /* Get nameidata of the parent directory */ + err = path_lookup(path, LOOKUP_PARENT, &nd); +@@ -504,8 +505,8 @@ static int autofs_dev_ioctl_requester(st + err = 0; + autofs4_expire_wait(nd.path.dentry); + spin_lock(&sbi->fs_lock); +- param->arg1 = ino->uid; +- param->arg2 = ino->gid; ++ param->requester.uid = ino->uid; ++ param->requester.gid = ino->gid; + spin_unlock(&sbi->fs_lock); + } + +@@ -523,40 +524,13 @@ static int autofs_dev_ioctl_expire(struc + struct autofs_sb_info *sbi, + struct autofs_dev_ioctl *param) + { +- struct dentry *dentry; + struct vfsmount *mnt; +- int err = -EAGAIN; + int how; + +- how = param->arg1; ++ how = param->expire.how; + mnt = fp->f_path.mnt; + +- if (sbi->type & AUTOFS_TYPE_TRIGGER) +- dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); +- else +- dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); +- +- if (dentry) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- +- /* +- * This is synchronous because it makes the daemon a +- * little easier +- */ +- err = autofs4_wait(sbi, dentry, NFY_EXPIRE); +- +- spin_lock(&sbi->fs_lock); +- if (ino->flags & AUTOFS_INF_MOUNTPOINT) { +- ino->flags &= ~AUTOFS_INF_MOUNTPOINT; +- sbi->sb->s_root->d_mounted++; +- } +- ino->flags &= ~AUTOFS_INF_EXPIRING; +- complete_all(&ino->expire_complete); +- spin_unlock(&sbi->fs_lock); +- dput(dentry); +- } +- +- return err; ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); + } + + /* Check if autofs mount point is in use */ +@@ -564,9 +538,9 @@ static int autofs_dev_ioctl_askumount(st + struct autofs_sb_info *sbi, + struct autofs_dev_ioctl *param) + { +- param->arg1 = 0; ++ param->askumount.may_umount = 0; + if (may_umount(fp->f_path.mnt)) +- param->arg1 = 1; ++ param->askumount.may_umount = 1; + return 0; + } + +@@ -599,6 +573,7 @@ static int autofs_dev_ioctl_ismountpoint + struct nameidata nd; + const char *path; + unsigned int type; ++ unsigned int devid, magic; + int err = -ENOENT; + + if (param->size <= sizeof(*param)) { +@@ -607,13 +582,13 @@ static int autofs_dev_ioctl_ismountpoint + } + + path = param->path; +- type = param->arg1; ++ type = param->ismountpoint.in.type; + +- param->arg1 = 0; +- param->arg2 = 0; ++ param->ismountpoint.out.devid = devid = 0; ++ param->ismountpoint.out.magic = magic = 0; + + if (!fp || param->ioctlfd == -1) { +- if (type == AUTOFS_TYPE_ANY) { ++ if (autofs_type_any(type)) { + struct super_block *sb; + + err = path_lookup(path, LOOKUP_FOLLOW, &nd); +@@ -621,7 +596,7 @@ static int autofs_dev_ioctl_ismountpoint + goto out; + + sb = nd.path.dentry->d_sb; +- param->arg1 = new_encode_dev(sb->s_dev); ++ devid = new_encode_dev(sb->s_dev); + } else { + struct autofs_info *ino; + +@@ -634,38 +609,41 @@ static int autofs_dev_ioctl_ismountpoint + goto out_release; + + ino = autofs4_dentry_ino(nd.path.dentry); +- param->arg1 = autofs4_get_dev(ino->sbi); ++ devid = autofs4_get_dev(ino->sbi); + } + + err = 0; + if (nd.path.dentry->d_inode && + nd.path.mnt->mnt_root == nd.path.dentry) { + err = 1; +- param->arg2 = nd.path.dentry->d_inode->i_sb->s_magic; ++ magic = nd.path.dentry->d_inode->i_sb->s_magic; + } + } else { +- dev_t devid = new_encode_dev(sbi->sb->s_dev); ++ dev_t dev = autofs4_get_dev(sbi); + + err = path_lookup(path, LOOKUP_PARENT, &nd); + if (err) + goto out; + +- err = autofs_dev_ioctl_find_super(&nd, devid); ++ err = autofs_dev_ioctl_find_super(&nd, dev); + if (err) + goto out_release; + +- param->arg1 = autofs4_get_dev(sbi); ++ devid = dev; + + err = have_submounts(nd.path.dentry); + + if (nd.path.mnt->mnt_mountpoint != nd.path.mnt->mnt_root) { + if (follow_down(&nd.path.mnt, &nd.path.dentry)) { + struct inode *inode = nd.path.dentry->d_inode; +- param->arg2 = inode->i_sb->s_magic; ++ magic = inode->i_sb->s_magic; + } + } + } + ++ param->ismountpoint.out.devid = devid; ++ param->ismountpoint.out.magic = magic; ++ + out_release: + path_put(&nd.path); + out: +--- linux-2.6.28.orig/fs/autofs4/expire.c ++++ linux-2.6.28/fs/autofs4/expire.c +@@ -63,15 +63,17 @@ static int autofs4_mount_busy(struct vfs + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + + /* This is an autofs submount, we can't expire it */ +- if (sbi->type == AUTOFS_TYPE_INDIRECT) ++ if (autofs_type_indirect(sbi->type)) + goto done; + + /* + * Otherwise it's an offset mount and we need to check + * if we can umount its mount, if there is one. + */ +- if (!d_mountpoint(dentry)) ++ if (!d_mountpoint(dentry)) { ++ status = 0; + goto done; ++ } + } + + /* Update the expiry counter if fs is busy */ +@@ -478,22 +480,16 @@ int autofs4_expire_run(struct super_bloc + return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; + +- if (arg && get_user(do_now, arg)) +- return -EFAULT; +- +- if (sbi->type & AUTOFS_TYPE_TRIGGER) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ if (autofs_type_trigger(sbi->type)) ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); +@@ -516,3 +512,16 @@ int autofs4_expire_multi(struct super_bl + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.28.orig/fs/autofs4/inode.c ++++ linux-2.6.28/fs/autofs4/inode.c +@@ -197,9 +197,9 @@ static int autofs4_show_options(struct s + seq_printf(m, ",minproto=%d", sbi->min_proto); + seq_printf(m, ",maxproto=%d", sbi->max_proto); + +- if (sbi->type & AUTOFS_TYPE_OFFSET) ++ if (autofs_type_offset(sbi->type)) + seq_printf(m, ",offset"); +- else if (sbi->type & AUTOFS_TYPE_DIRECT) ++ else if (autofs_type_direct(sbi->type)) + seq_printf(m, ",direct"); + else + seq_printf(m, ",indirect"); +@@ -284,13 +284,13 @@ static int parse_options(char *options, + *maxproto = option; + break; + case Opt_indirect: +- *type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(type); + break; + case Opt_direct: +- *type = AUTOFS_TYPE_DIRECT; ++ set_autofs_type_direct(type); + break; + case Opt_offset: +- *type = AUTOFS_TYPE_OFFSET; ++ set_autofs_type_offset(type); + break; + default: + return 1; +@@ -338,7 +338,7 @@ int autofs4_fill_super(struct super_bloc + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; +- sbi->type = AUTOFS_TYPE_INDIRECT; ++ set_autofs_type_indirect(&sbi->type); + sbi->min_proto = 0; + sbi->max_proto = 0; + mutex_init(&sbi->wq_mutex); +@@ -380,7 +380,7 @@ int autofs4_fill_super(struct super_bloc + } + + root_inode->i_fop = &autofs4_root_operations; +- root_inode->i_op = sbi->type & AUTOFS_TYPE_TRIGGER ? ++ root_inode->i_op = autofs_type_trigger(sbi->type) ? + &autofs4_direct_root_inode_operations : + &autofs4_indirect_root_inode_operations; + +--- linux-2.6.28.orig/fs/autofs4/waitq.c ++++ linux-2.6.28/fs/autofs4/waitq.c +@@ -297,20 +297,14 @@ static int validate_request(struct autof + */ + if (notify == NFY_MOUNT) { + /* +- * If the dentry isn't hashed just go ahead and try the +- * mount again with a new wait (not much else we can do). +- */ +- if (!d_unhashed(dentry)) { +- /* +- * But if the dentry is hashed, that means that we +- * got here through the revalidate path. Thus, we +- * need to check if the dentry has been mounted +- * while we waited on the wq_mutex. If it has, +- * simply return success. +- */ +- if (d_mountpoint(dentry)) +- return 0; +- } ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) ++ return 0; + } + + return 1; +@@ -337,7 +331,7 @@ int autofs4_wait(struct autofs_sb_info * + * is very similar for indirect mounts except only dentrys + * in the root of the autofs file system may be negative. + */ +- if (sbi->type & AUTOFS_TYPE_TRIGGER) ++ if (autofs_type_trigger(sbi->type)) + return -ENOENT; + else if (!IS_ROOT(dentry->d_parent)) + return -ENOENT; +@@ -348,7 +342,7 @@ int autofs4_wait(struct autofs_sb_info * + return -ENOMEM; + + /* If this is a direct mount request create a dummy name */ +- if (IS_ROOT(dentry) && sbi->type & AUTOFS_TYPE_TRIGGER) ++ if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) + qstr.len = sprintf(name, "%p", dentry); + else { + qstr.len = autofs4_getpath(sbi, dentry, &name); +@@ -406,11 +400,11 @@ int autofs4_wait(struct autofs_sb_info * + type = autofs_ptype_expire_multi; + } else { + if (notify == NFY_MOUNT) +- type = (sbi->type & AUTOFS_TYPE_TRIGGER) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_missing_direct : + autofs_ptype_missing_indirect; + else +- type = (sbi->type & AUTOFS_TYPE_TRIGGER) ? ++ type = autofs_type_trigger(sbi->type) ? + autofs_ptype_expire_direct : + autofs_ptype_expire_indirect; + } +--- linux-2.6.28.orig/include/linux/auto_fs4.h ++++ linux-2.6.28/include/linux/auto_fs4.h +@@ -29,10 +29,64 @@ + #define AUTOFS_EXP_IMMEDIATE 1 + #define AUTOFS_EXP_LEAVES 2 + +-#define AUTOFS_TYPE_ANY 0x0000 +-#define AUTOFS_TYPE_INDIRECT 0x0001 +-#define AUTOFS_TYPE_DIRECT 0x0002 +-#define AUTOFS_TYPE_OFFSET 0x0004 ++#define AUTOFS_TYPE_ANY 0U ++#define AUTOFS_TYPE_INDIRECT 1U ++#define AUTOFS_TYPE_DIRECT 2U ++#define AUTOFS_TYPE_OFFSET 4U ++ ++static inline void set_autofs_type_indirect(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_INDIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_indirect(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_INDIRECT); ++} ++ ++static inline void set_autofs_type_direct(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_DIRECT; ++ return; ++} ++ ++static inline unsigned int autofs_type_direct(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT); ++} ++ ++static inline void set_autofs_type_offset(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_OFFSET; ++ return; ++} ++ ++static inline unsigned int autofs_type_offset(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_OFFSET); ++} ++ ++static inline unsigned int autofs_type_trigger(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); ++} ++ ++/* ++ * This isn't really a type as we use it to say "no type set" to ++ * indicate we want to search for "any" mount in the ++ * autofs_dev_ioctl_ismountpoint() device ioctl function. ++ */ ++static inline void set_autofs_type_any(unsigned int *type) ++{ ++ *type = AUTOFS_TYPE_ANY; ++ return; ++} ++ ++static inline unsigned int autofs_type_any(unsigned int type) ++{ ++ return (type == AUTOFS_TYPE_ANY); ++} + + /* Daemon notification packet types */ + enum autofs_notify { +--- linux-2.6.28.orig/Documentation/filesystems/autofs4-mount-control.txt ++++ linux-2.6.28/Documentation/filesystems/autofs4-mount-control.txt +@@ -179,8 +179,21 @@ struct autofs_dev_ioctl { + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + +- __u32 arg1; /* Command parameters */ +- __u32 arg2; ++ /* 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[0]; + }; +@@ -192,8 +205,8 @@ optionally be used to check a specific m + 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 fields arg1 and arg2 are used to communicate parameters and results of +-calls made as described below. ++The anonymous 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 +@@ -245,25 +258,27 @@ AUTOFS_DEV_IOCTL_PROTOVER_CMD and AUTOFS + Get the major and minor version of the autofs4 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 structure field arg1. These +-commands return 0 on success or one of the negative error codes if +-validation fails. ++and sets the requested version number in structure field protover.version ++and ptotosubver.sub_version respectively. 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 +----------------------------------------------------------- ++AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD ++------------------------------------------------------------------ + + 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 the path field set and the size field adjusted appropriately as well +-as the arg1 field 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. ++as the openmount.devid field set to the device number of the autofs mount. ++The device number of an autofs mounted filesystem can be obtained by using ++the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path ++and autofs mount type, as described below. 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 +@@ -272,10 +287,10 @@ AUTOFS_DEV_IOCTL_READY_CMD and AUTOFS_DE + 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 arg1 field set to the wait queue token number, received +-by user space in the foregoing mount or expire request. The arg2 field +-is set to the status to be returned. For the ready call this is always +-0 and for the fail call it is set to the errno of the operation. ++call and the ready.token or fail.token field set to the wait queue ++token number, received by user space in the foregoing mount or expire ++request. The fail.status field is set to the status to be returned when ++sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. + + + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD +@@ -290,9 +305,10 @@ 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 arg1 field 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. ++the setpipefd.pipefd field 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 +@@ -313,6 +329,9 @@ Set the expire timeout for mounts within + + The call requires an initialized struct autofs_dev_ioctl with the + ioctlfd field set to the descriptor obtained from the open call. ++The timeout.timeout field is set to the desired timeout and this ++field is set to the value of the value of the current timeout of ++the mount upon successful completion. + + + AUTOFS_DEV_IOCTL_REQUESTER_CMD +@@ -323,9 +342,9 @@ 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 as well as the arg1 field set to the device number of the +-containing autofs mount. Upon return the struct field arg1 contains the +-uid and arg2 the gid. ++appropriately as well as the ioctlfd field set to the descriptor obtained ++from the open call. Upon return the struct fields requester.uid and ++requester.gid contain the uid and gid respectively. + + 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 +@@ -343,8 +362,8 @@ this ioctl is called until no further ex + 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, independent of the mount timeout, can be +-requested by setting the arg1 field to 1. If no expire candidates can +-be found the ioctl returns -1 with errno set to EAGAIN. ++requested by setting the expire.how field to 1. 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 +@@ -357,7 +376,8 @@ Checks if an autofs mount point is in us + + 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 arg1 field, 1 for busy and 0 otherwise. ++it returns the result in the askumount.may_umount field, 1 for busy ++and 0 otherwise. + + + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD +@@ -367,14 +387,15 @@ 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 optionaly arg1 set to an autofs mount type. +-The call returns 1 if this is a mount point and sets arg1 to the device +-number of the mount and field arg2 to the relevant super block magic +-number (described below) or 0 if it isn't a mountpoint. In both cases +-the the device number (as returned by new_encode_dev()) is returned +-in field arg1. ++point to check and the size field must be adjusted appropriately. One uses ++the ioctlfd field to identify a specific mount point to check while the ++other variation uses the path and optionaly the ismountpoint.in.type ++field set to an autofs mount type. The call returns 1 if this is a mount ++point and sets the ismountpoint.out.devid field to the device number of ++the mount and the ismountpoint.out.magic field to the relevant super ++block magic number (described below) or 0 if it isn't a mountpoint. In ++both cases the the device number (as returned by new_encode_dev()) is ++returned in the ismountpoint.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 +--- linux-2.6.28.orig/include/linux/auto_dev-ioctl.h ++++ linux-2.6.28/include/linux/auto_dev-ioctl.h +@@ -10,7 +10,13 @@ + #ifndef _LINUX_AUTO_DEV_IOCTL_H + #define _LINUX_AUTO_DEV_IOCTL_H + +-#include ++#include ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif /* __KERNEL__ */ + + #define AUTOFS_DEVICE_NAME "autofs" + +@@ -25,6 +31,60 @@ + * An ioctl interface for autofs mount point control. + */ + ++struct args_protover { ++ __u32 version; ++}; ++ ++struct args_protosubver { ++ __u32 sub_version; ++}; ++ ++struct args_openmount { ++ __u32 devid; ++}; ++ ++struct args_ready { ++ __u32 token; ++}; ++ ++struct args_fail { ++ __u32 token; ++ __s32 status; ++}; ++ ++struct args_setpipefd { ++ __s32 pipefd; ++}; ++ ++struct args_timeout { ++ __u64 timeout; ++}; ++ ++struct args_requester { ++ __u32 uid; ++ __u32 gid; ++}; ++ ++struct args_expire { ++ __u32 how; ++}; ++ ++struct args_askumount { ++ __u32 may_umount; ++}; ++ ++struct args_ismountpoint { ++ union { ++ struct args_in { ++ __u32 type; ++ } in; ++ struct args_out { ++ __u32 devid; ++ __u32 magic; ++ } out; ++ }; ++}; ++ + /* + * All the ioctls use this structure. + * When sending a path size must account for the total length +@@ -39,20 +99,32 @@ struct autofs_dev_ioctl { + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + +- __u32 arg1; /* Command parameters */ +- __u32 arg2; ++ /* 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[0]; + }; + + static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) + { ++ memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; +- in->arg1 = 0; +- in->arg2 = 0; + return; + } + +--- linux-2.6.28.orig/fs/autofs4/root.c ++++ linux-2.6.28/fs/autofs4/root.c +@@ -485,22 +485,6 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- expiring = autofs4_lookup_expiring(sbi, dentry->d_parent, &dentry->d_name); +- if (expiring) { +- /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- */ +- ino = autofs4_dentry_ino(expiring); +- autofs4_expire_wait(expiring); +- spin_lock(&sbi->lookup_lock); +- if (!list_empty(&ino->expiring)) +- list_del_init(&ino->expiring); +- spin_unlock(&sbi->lookup_lock); +- dput(expiring); +- } +- + unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); + if (unhashed) + dentry = unhashed; +@@ -538,14 +522,31 @@ static struct dentry *autofs4_lookup(str + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); ++ if (dentry->d_op && dentry->d_op->d_revalidate) + (dentry->d_op->d_revalidate)(dentry, nd); +- mutex_lock(&dir->i_mutex); +- } ++ mutex_lock(&dir->i_mutex); + } + + /* +--- linux-2.6.28.orig/include/linux/auto_fs.h ++++ linux-2.6.28/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + diff --git a/patches/autofs4-2.6.29-v5-update-20090903.patch b/patches/autofs4-2.6.29-v5-update-20090903.patch new file mode 100644 index 0000000..9701be5 --- /dev/null +++ b/patches/autofs4-2.6.29-v5-update-20090903.patch @@ -0,0 +1,240 @@ +--- linux-2.6.29.orig/fs/autofs4/autofs_i.h ++++ linux-2.6.29/fs/autofs4/autofs_i.h +@@ -186,6 +186,8 @@ int autofs4_expire_wait(struct dentry *d + int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when); + int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); + struct dentry *autofs4_expire_direct(struct super_block *sb, +--- linux-2.6.29.orig/fs/autofs4/dev-ioctl.c ++++ linux-2.6.29/fs/autofs4/dev-ioctl.c +@@ -525,40 +525,13 @@ static int autofs_dev_ioctl_expire(struc + struct autofs_sb_info *sbi, + struct autofs_dev_ioctl *param) + { +- struct dentry *dentry; + struct vfsmount *mnt; +- int err = -EAGAIN; + int how; + + how = param->expire.how; + mnt = fp->f_path.mnt; + +- if (autofs_type_trigger(sbi->type)) +- dentry = autofs4_expire_direct(sbi->sb, mnt, sbi, how); +- else +- dentry = autofs4_expire_indirect(sbi->sb, mnt, sbi, how); +- +- if (dentry) { +- struct autofs_info *ino = autofs4_dentry_ino(dentry); +- +- /* +- * This is synchronous because it makes the daemon a +- * little easier +- */ +- err = autofs4_wait(sbi, dentry, NFY_EXPIRE); +- +- spin_lock(&sbi->fs_lock); +- if (ino->flags & AUTOFS_INF_MOUNTPOINT) { +- ino->flags &= ~AUTOFS_INF_MOUNTPOINT; +- sbi->sb->s_root->d_mounted++; +- } +- ino->flags &= ~AUTOFS_INF_EXPIRING; +- complete_all(&ino->expire_complete); +- spin_unlock(&sbi->fs_lock); +- dput(dentry); +- } +- +- return err; ++ return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); + } + + /* Check if autofs mount point is in use */ +--- linux-2.6.29.orig/fs/autofs4/expire.c ++++ linux-2.6.29/fs/autofs4/expire.c +@@ -70,8 +70,10 @@ static int autofs4_mount_busy(struct vfs + * Otherwise it's an offset mount and we need to check + * if we can umount its mount, if there is one. + */ +- if (!d_mountpoint(dentry)) ++ if (!d_mountpoint(dentry)) { ++ status = 0; + goto done; ++ } + } + + /* Update the expiry counter if fs is busy */ +@@ -478,22 +480,16 @@ int autofs4_expire_run(struct super_bloc + return ret; + } + +-/* Call repeatedly until it returns -EAGAIN, meaning there's nothing +- more to be done */ +-int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, +- struct autofs_sb_info *sbi, int __user *arg) ++int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int when) + { + struct dentry *dentry; + int ret = -EAGAIN; +- int do_now = 0; +- +- if (arg && get_user(do_now, arg)) +- return -EFAULT; + + if (autofs_type_trigger(sbi->type)) +- dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_direct(sb, mnt, sbi, when); + else +- dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); ++ dentry = autofs4_expire_indirect(sb, mnt, sbi, when); + + if (dentry) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); +@@ -516,3 +512,16 @@ int autofs4_expire_multi(struct super_bl + return ret; + } + ++/* Call repeatedly until it returns -EAGAIN, meaning there's nothing ++ more to be done */ ++int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ++ struct autofs_sb_info *sbi, int __user *arg) ++{ ++ int do_now = 0; ++ ++ if (arg && get_user(do_now, arg)) ++ return -EFAULT; ++ ++ return autofs4_do_expire_multi(sb, mnt, sbi, do_now); ++} ++ +--- linux-2.6.29.orig/fs/autofs4/root.c ++++ linux-2.6.29/fs/autofs4/root.c +@@ -485,22 +485,6 @@ static struct dentry *autofs4_lookup(str + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, task_pgrp_nr(current), sbi->catatonic, oz_mode); + +- expiring = autofs4_lookup_expiring(sbi, dentry->d_parent, &dentry->d_name); +- if (expiring) { +- /* +- * If we are racing with expire the request might not +- * be quite complete but the directory has been removed +- * so it must have been successful, so just wait for it. +- */ +- ino = autofs4_dentry_ino(expiring); +- autofs4_expire_wait(expiring); +- spin_lock(&sbi->lookup_lock); +- if (!list_empty(&ino->expiring)) +- list_del_init(&ino->expiring); +- spin_unlock(&sbi->lookup_lock); +- dput(expiring); +- } +- + unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); + if (unhashed) + dentry = unhashed; +@@ -538,14 +522,31 @@ static struct dentry *autofs4_lookup(str + } + + if (!oz_mode) { ++ mutex_unlock(&dir->i_mutex); ++ expiring = autofs4_lookup_expiring(sbi, ++ dentry->d_parent, ++ &dentry->d_name); ++ if (expiring) { ++ /* ++ * If we are racing with expire the request might not ++ * be quite complete but the directory has been removed ++ * so it must have been successful, so just wait for it. ++ */ ++ ino = autofs4_dentry_ino(expiring); ++ autofs4_expire_wait(expiring); ++ spin_lock(&sbi->lookup_lock); ++ if (!list_empty(&ino->expiring)) ++ list_del_init(&ino->expiring); ++ spin_unlock(&sbi->lookup_lock); ++ dput(expiring); ++ } ++ + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); +- if (dentry->d_op && dentry->d_op->d_revalidate) { +- mutex_unlock(&dir->i_mutex); ++ if (dentry->d_op && dentry->d_op->d_revalidate) + (dentry->d_op->d_revalidate)(dentry, nd); +- mutex_lock(&dir->i_mutex); +- } ++ mutex_lock(&dir->i_mutex); + } + + /* +--- linux-2.6.29.orig/include/linux/auto_dev-ioctl.h ++++ linux-2.6.29/include/linux/auto_dev-ioctl.h +@@ -10,8 +10,13 @@ + #ifndef _LINUX_AUTO_DEV_IOCTL_H + #define _LINUX_AUTO_DEV_IOCTL_H + ++#include ++ ++#ifdef __KERNEL__ + #include +-#include ++#else ++#include ++#endif /* __KERNEL__ */ + + #define AUTOFS_DEVICE_NAME "autofs" + +--- linux-2.6.29.orig/include/linux/auto_fs.h ++++ linux-2.6.29/include/linux/auto_fs.h +@@ -17,11 +17,13 @@ + #ifdef __KERNEL__ + #include + #include ++#include ++#include ++#else + #include ++#include + #endif /* __KERNEL__ */ + +-#include +- + /* This file describes autofs v3 */ + #define AUTOFS_PROTO_VERSION 3 + +--- linux-2.6.29.orig/fs/autofs4/waitq.c ++++ linux-2.6.29/fs/autofs4/waitq.c +@@ -297,20 +297,14 @@ static int validate_request(struct autof + */ + if (notify == NFY_MOUNT) { + /* +- * If the dentry isn't hashed just go ahead and try the +- * mount again with a new wait (not much else we can do). +- */ +- if (!d_unhashed(dentry)) { +- /* +- * But if the dentry is hashed, that means that we +- * got here through the revalidate path. Thus, we +- * need to check if the dentry has been mounted +- * while we waited on the wq_mutex. If it has, +- * simply return success. +- */ +- if (d_mountpoint(dentry)) +- return 0; +- } ++ * If the dentry was successfully mounted while we slept ++ * on the wait queue mutex we can return success. If it ++ * isn't mounted (doesn't have submounts for the case of ++ * a multi-mount with no mount at it's base) we can ++ * continue on and create a new request. ++ */ ++ if (have_submounts(dentry)) ++ return 0; + } + + return 1; diff --git a/patches/util-linux-2.12a-flock.patch b/patches/util-linux-2.12a-flock.patch new file mode 100644 index 0000000..a33e321 --- /dev/null +++ b/patches/util-linux-2.12a-flock.patch @@ -0,0 +1,30 @@ +--- util-linux-2.12a/mount/fstab.c.flock 2005-09-17 01:36:03.000000000 +0800 ++++ util-linux-2.12a/mount/fstab.c 2005-09-17 01:41:12.000000000 +0800 +@@ -488,7 +488,7 @@ lock_mtab (void) { + } + /* proceed anyway */ + } +- we_created_lockfile = 1; ++ we_created_lockfile = fd; + } else { + static int tries = 0; + +@@ -510,9 +510,8 @@ lock_mtab (void) { + MOUNTED_LOCK); + sleep(1); + } ++ close(fd); + } +- +- close(fd); + } + } + +@@ -520,6 +519,7 @@ lock_mtab (void) { + void + unlock_mtab (void) { + if (we_created_lockfile) { ++ close(we_created_lockfile); + unlink (MOUNTED_LOCK); + we_created_lockfile = 0; + } diff --git a/patches/util-linux-2.12q-flock.patch b/patches/util-linux-2.12q-flock.patch new file mode 100644 index 0000000..15b60f7 --- /dev/null +++ b/patches/util-linux-2.12q-flock.patch @@ -0,0 +1,29 @@ +--- util-linux-2.12q/mount/fstab.c.flock 2005-09-17 01:10:37.000000000 +0800 ++++ util-linux-2.12q/mount/fstab.c 2005-09-17 01:16:51.000000000 +0800 +@@ -417,6 +417,7 @@ + unlock_mtab (void) { + if (we_created_lockfile) { + unlink (MOUNTED_LOCK); ++ close(we_created_lock_file); + we_created_lockfile = 0; + } + } +@@ -528,6 +529,7 @@ + } + /* proceed anyway */ + } ++ we_created_lock_file = fd; + } else { + static int tries = 0; + +@@ -549,9 +551,8 @@ + MOUNTED_LOCK); + sleep(1); + } ++ close(fd); + } +- +- close(fd); + } + } + diff --git a/redhat/Makefile b/redhat/Makefile new file mode 100644 index 0000000..6cc1411 --- /dev/null +++ b/redhat/Makefile @@ -0,0 +1,26 @@ + +-include ../Makefile.conf +include ../Makefile.rules + +all: autofs.init autofs.conf autofs.service + +autofs.init: autofs.init.in + sed -e "s|@@sbindir@@|$(sbindir)|g" \ + -e "s|@@autofslibdir@@|$(autofslibdir)|g" \ + -e "s|@@autofsconfdir@@|$(autofsconfdir)|g" \ + -e "s|@@autofspiddir@@|$(autofspiddir)|g" \ + -e "s|@@initdir@@|$(initdir)|g" < autofs.init.in > autofs.init + +autofs.conf: autofs.conf.default.in + sed -e "s|@@autofsmapdir@@|$(autofsmapdir)|g" \ + < autofs.conf.default.in > autofs.conf + +autofs.service: ../samples/autofs.service.in + sed -e "s|@@sbindir@@|$(sbindir)|g" \ + -e "s|@@autofsconfdir@@|$(autofsconfdir)|g" \ + -e "s|@@autofspiddir@@|$(autofspiddir)|g" \ + < ../samples/autofs.service.in > autofs.service + +clean: + rm -f autofs.init autofs.sysconfig autofs.service + diff --git a/redhat/autofs.conf.default.in b/redhat/autofs.conf.default.in new file mode 100644 index 0000000..3c6a316 --- /dev/null +++ b/redhat/autofs.conf.default.in @@ -0,0 +1,407 @@ +# +# Define default options for autofs. +# +[ autofs ] +# +# master_map_name - default map name for the master map. +# +#master_map_name = auto.master +# +# timeout - set the default mount timeout in secons. The internal +# program default is 10 minutes, but the default installed +# configuration overrides this and sets the timeout to 5 +# minutes to be consistent with earlier autofs releases. +# +timeout = 300 +# +# master_wait - set the default maximum time to wait for the +# master map to become available if it cannot +# be read at program start (default 10, wait +# for 10 seconds then continue). +# +#master_wait = 10 +# +# negative_timeout - set the default negative timeout for +# failed mount attempts (default 60). +# +#negative_timeout = 60 +# +# mount_wait - time to wait for a response from mount(8). +# Setting this timeout can cause problems when +# mount would otherwise wait for a server that +# is temporarily unavailable, such as when it's +# restarting. The default setting (-1) of waiting +# for mount(8) usually results in a wait of around +# 3 minutes. +# +#mount_wait = -1 +# +# umount_wait - time to wait for a response from umount(8). +# +#umount_wait = 12 +# +# browse_mode - maps are browsable by default. +# +browse_mode = no +# +# mount_nfs_default_protocol - specify the default protocol used by +# mount.nfs(8). Since we can't identify +# the default automatically we need to +# set it in our configuration. +# +#mount_nfs_default_protocol = 3 +mount_nfs_default_protocol = 4 +# +# append_options - append to global options instead of replace. +# +#append_options = yes +# +# logging - set default log level "none", "verbose" or "debug" +# +#logging = none +# +# force_standard_program_map_env - disable the use of the "AUTOFS_" +# prefix for standard environemt variables when +# executing a program map. Since program maps +# are run as the privileded user this opens +# automount(8) to potential user privilege +# escalation when the program map is written +# in a language that can load components from, +# for example, a user home directory. +# +# force_standard_program_map_env = no +# +# use_mount_request_log_id - Set whether to use a mount request log +# id so that log entries for specific mount +# requests can be easily identified in logs +# that have multiple conncurrent requests. +# +#use_mount_request_log_id = no +# +# Define base dn for map dn lookup. +# +# Define server URIs +# +# ldap_uri - space seperated list of server uris of the form +# ://[/] where can be ldap +# or ldaps. The option can be given multiple times. +# Map entries that include a server name override +# this option. +# +# This configuration option can also be used to +# request autofs lookup SRV RRs for a domain of +# the form :///[]. Note that a +# trailing "/" is not allowed when using this form. +# If the domain dn is not specified the dns domain +# name (if any) is used to construct the domain dn +# for the SRV RR lookup. The server list returned +# from an SRV RR lookup is refreshed according to +# the minimum ttl found in the SRV RR records or +# after one hour, whichever is less. +# +#ldap_uri = "" +# +# ldap_timeout - timeout value for the synchronous API calls +# (default is LDAP library default). +# +#ldap_timeout = -1 +# +# ldap_network_timeout - set the network response timeout (default 8). +# +#ldap_network_timeout = 8 +# +# search_base - base dn to use for searching for map search dn. +# Multiple entries can be given and they are checked +# in the order they occur here. +# +#search_base = "" +# +# Define the LDAP schema to used for lookups +# +# If no schema is set autofs will check each of the schemas +# below in the order given to try and locate an appropriate +# basdn for lookups. If you want to minimize the number of +# queries to the server set the values here. +# +#map_object_class = nisMap +#entry_object_class = nisObject +#map_attribute = nisMapName +#entry_attribute = cn +#value_attribute= nisMapEntry +# +# Other common LDAP naming +# +#map_object_class = automountMap +#entry_object_class = automount +#map_attribute = ou +#entry_attribute = cn +#value_attribute= automountInformation +# +#map_object_class = automountMap +#entry_object_class = automount +#map_attribute = automountMapName +#entry_attribute = automountKey +#value_attribute= automountInformation +# +# auth_conf_file - set the default location for the SASL +# authentication configuration file. +# +#auth_conf_file = @@autofsmapdir@@/autofs_ldap_auth.conf +# +# map_hash_table_size - set the map cache hash table size. +# Should be a power of 2 with a ratio of +# close to 1:8 for acceptable performance +# with maps up to around 8000 entries. +# See autofs.conf(5) for more details. +# +#map_hash_table_size = 1024 +# +# use_hostname_for_mounts - nfs mounts where the host name resolves +# to more than one IP address normally need +# to use the IP address to esure a mount to +# a host that isn't responding isn't done. +# If that behaviour is not wanted then set +# ths to "yes", default is "no". +# +#use_hostname_for_mounts = "no" +# +# disable_not_found_message - The original request to add this log message +# needed it to be unconditional. That produces, IMHO, +# unnecessary noise in the log so a configuration option +# has been added to provide the ability to turn it off. +# The default is "no" to maintain the current behaviour. +# +#disable_not_found_message = "no" +# +# sss_master_map_wait - When sssd is starting up it can sometimes return +# "no such entry" for a short time until it has read +# in the LDAP map information. Internal default is 0 +# seconds, don't wait but if there is a problem with +# autofs not finding the master map at startup (when +# it should) then try setting this to 10 to work +# around it. +# +#sss_master_map_wait = 0 +# +# Otions for the amd parser within autofs. +# +# amd configuration options that are aren't used, haven't been +# implemented or have different behaviour within autofs. +# +# A number of the amd configuration options are not used by autofs, +# some because they are not relevant within autofs, some because +# they are done differently in autofs and others that are not yet +# implemented. +# +# Since "mount_type" is always autofs (because there's no user space +# NFS server) the configuration entries relating to that aren't used. +# Also, server availability is done differently within autofs so the +# options that relate to the amd server monitoring sub-system are +# also not used. +# +# These options are mount_type, auto_attrcache, portmap_program, +# nfs_vers_ping, nfs_allow_any_interface, nfs_allow_insecure_port, +# nfs_proto, nfs_retransmit_counter, nfs_retransmit_counter_udp, +# nfs_retransmit_counter_tcp, nfs_retransmit_counter_toplvl, +# nfs_retry_interval, nfs_retry_interval_udp, nfs_retry_interval_tcp, +# nfs_retry_interval_toplvl and nfs_vers. +# +# +# Other options that are not used within the autofs implementation: +# +# log_file, truncate_log - autofs used either stderr when running in +# the foreground or sends its output to syslog so an alternate +# log file (or truncating the log) can't be used. +# +# print_pid - there's no corresponding option for this within autofs. +# +# use_tcpwrappers, show_statfs_entries - there's no user space NFS +# server to control access to so this option isn't relevant. +# The show_statfs_entries can't be implemented for the same +# reason. +# +# debug_mtab_file - there's no user space NFS server and autofs +# avoids using file based mtab whenever possible. +# +# sun_map_syntax - obviously, are provided by autofs itself. +# +# plock, show_statfs_entries, preferred_amq_port - not supported. +# +# ldap_cache_maxmem, ldap_cache_seconds - external ldap caching +# is not used by autofs. +# +# ldap_proto_version - autofs always attempts to use the highest +# available ldap protocol version. +# +# cache_duration, map_reload_interval, map_options - the map +# entry cache is continually updated and stale entries +# cleaned on re-load, which is done when map changes are +# detected so these configuration entries are not used +# by autofs. +# +# localhost_address - is not used within autofs. This +# configuration option was only used in the amd user +# space server code and is not relevant within autofs. +# +# +# Options that are handled differently within autofs: +# +# pid_file - must be given as a command line option on startup. +# +# print_version - program version and feature information is obtained +# by using the automount command line option "-V". +# +# debug_options, log_options - autofs has somewhat more limited +# logging and debug logging options. When the log_options +# options is encountered it is converted to the nearest +# matching autofs logging option. Since the configuration +# option debug_options would be handled the same way it +# is ignored. +# +# restart_mounts - has no sensible meaning within autofs because autofs +# always tries to re-connect to existing mounts. While this +# has its own set of problems not re-connecting to existing +# mounts always results in a non-functional automount tree if +# mounts were busy at the last shutdown (as is also the case +# with amd when using mount_type autofs). +# +# forced_unmounts - detaching mounts often causes serious problems +# for users of existing mounts. It is used by autofs in some +# cases, either at the explicit request of the user (with a +# command line or init option) and in some special cases during +# program operation but is avoided whenever possible. +# +# +# A number of configuration options are not yet implemented: +# +# fully_qualified_hosts - not yet implemented. +# +# unmount_on_exit - since autofs always tries to re-connect +# to mounts left mounted from a previous shutdown this +# is a sensible option to implement and that will be +# done. +# +# exec_map_timeout - a timeout is not currently used for +# for program maps, might be implemented. +# +# tag - the tag option is not implemented within autofs. +# +# +# Supported options: +# +# arch, karch, os, osver - these options default to what is returned +# from uname(2) and can be overridden if required. +# +# full_os - has no default and must be set in the configuration +# if used in maps. +# +# cluster - if not set defaults to the host domain name. This option +# corresponds to the HP_UX cluster name (according to the amd +# source) and is probably not used in Linux but is set anyway. +# +# vendor - has a default value of "unknown", it must be set in the +# configuration if used in maps. +# +# auto_dir - is the base name of the mount tree used for external +# mounts that are sometimes needed by amd maps. Its default +# value is "/a". +# +# map_type - specifies the autofs map source, such as file, nis, +# ldap etc. and has no default value set. +# +# map_defaults - is used to override /defaults entries within maps +# and can be used to provide different defaults on specific +# machines without having to modify centrally managed maps. +# It is empty by default. +# +# search_path - colon seperated paths to search for maps that +# are not specified as a full path. +# +# dismount_interval - is equivalent to the autofs timeout option. It +# is only possible to use this with type "auto" mounts due +# to the way the autofs kernel module performs expiry. It +# takes its default value from the autofs internal default +# of 600 seconds. +# +# browsable_dirs - make map keys visible in directory listings. +# Note that support for the "fullybrowsable" option cannot +# be added using the existing kernel to user space autofs +# implementation. +# +# autofs_use_lofs - if set to "yes" autofs will attempt to use bind +# mounts for type "auto" when possible. +# +# nis_domain - allows setting of a domain name other than the system +# default. +# +# local_domain - is used to override (or set) the host domain name. +# +# normalize_hostnames - if set to "yes" then the contents of ${rhost} +# is translated in its official host name. +# +# domain_strip - if set to "yes" the domain name part of the host +# is strippped when normalizing hostnames. This can be useful +# when using of the same maps in a multiple domain environment. +# +# normalize_slashes - is set to "yes" by default and will collapse +# multiple unescaped occurrences of "/" to a single "/". +# +# selectors_in_defaults, selectors_on_default - has a default value +# of "no". If set to "yes" then any defaults entry will be +# checked for selectors to determine the values to be used. +# selectors_in_defaults is the preferred option to use. +# +# ldap_base - has no default value. It must be set to the base dn +# that is used for queries if ldap is to be used as a map +# source. +# +# ldap_hostports - has no default value set. It must be set to +# the URI of the LDAP server to be used for lookups when +# ldap is used a map source. It may contain a comma or +# space seperated list of LDAP URIs. +# +# hesiod_base - the base name used for hesiod map sources. +# +# Additional configuration options added: +# +# linux_ufs_mount_type - set the default system filesystem type that's +# used for mount type ufs. There's no simple way to determine +# what the system default filesystem is and am-utils needs to +# be continually updated to do this and can easily get it wrong +# anyway. +# +# +# Define global options for the amd parser within autofs. +# +[ amd ] +# +# Override the internal default with the same timeout that +# is used by the override in the autofs configuration, sanity +# only change. +# +dismount_interval = 300 +# +# map_type = file +# +# Overriding this can cause autofs to use less resources because +# it will use symlinks instead of bind mounts in certain cases. +# You should ensure that the autofs kernel module your using +# supports expration of symlinks for best results (although this +# appears to work reasonably well most of the time without the +# update). +# +# autofs_use_lofs = yes +# +# Several configuration options can be set per mount point. +# In particulr map_type, map_name, map_defaults, search_path, +# browsable_dirs, dismount_interval and selectors_in_defaults +# (not all of which are currently implemented, see above). +# +# Also, if a section for an amd mount point is defined here +# it isn't necessary to specify the format in the corresponding +# master map entry and the format will be inherited for type +# "auto" mounts. +# +# [ /expamle/mount ] +# dismount_interval = 60 +# map_type = nis diff --git a/redhat/autofs.init.in b/redhat/autofs.init.in new file mode 100644 index 0000000..9d008ff --- /dev/null +++ b/redhat/autofs.init.in @@ -0,0 +1,222 @@ +#!/bin/bash +# +# rc file for automount using a Sun-style "master map". +# +# chkconfig: 345 28 72 +# processname: /usr/sbin/automount +# config: /etc/auto.master +# description: Automounts filesystems on demand +# +### BEGIN INIT INFO +# Provides: autofs +# Required-Start: $network ypbind +# Required-Stop: $network ypbind +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# Short-Description: Automounts filesystems on demand +# Description: Automounts filesystems on demand +### END INIT INFO +# +# Location of the automount daemon and the init directory +# +DAEMON=@@sbindir@@/automount +prog=`basename $DAEMON` +MODULE="autofs4" +DEVICE="autofs" +initdir=@@initdir@@ +confdir=@@autofsconfdir@@ + +test -e $DAEMON || exit 0 + +if [ -r $initdir/functions ]; then + . $initdir/functions +fi + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +export PATH + +# +# load customized configuation settings +# +if [ -r $confdir/autofs ]; then + . $confdir/autofs +fi + +function start() { + # Make sure autofs4 module is loaded + if ! grep -q autofs /proc/filesystems + then + echo -n "Loading $MODULE: " + # Try load the autofs4 module fail if we can't + modprobe $MODULE >/dev/null 2>&1 + RETVAL=$? + if [ $RETVAL -eq 1 ] + then + failure "Load $MODULE" + echo + return $RETVAL + else + success "Load $MODULE" + echo + fi + elif ([ -f /proc/modules ] && lsmod) | grep -q autofs[^4] + then + RETVAL=1 + failure "Load $MODULE" + echo + return $RETVAL + fi + + # Check misc device + if [ -n "$USE_MISC_DEVICE" -a "x$USE_MISC_DEVICE" = "xyes" ]; then + sleep 1 + if [ -e "/proc/misc" ]; then + MINOR=`awk "/$DEVICE/ {print \\$1}" /proc/misc` + if [ -n "$MINOR" -a ! -c "/dev/$DEVICE" ]; then + mknod -m 0600 /dev/$DEVICE c 10 $MINOR + fi + fi + if [ -x /sbin/restorecon -a -c /dev/$DEVICE ]; then + /sbin/restorecon /dev/$DEVICE + fi + else + if [ -c /dev/$DEVICE ]; then + rm /dev/$DEVICE + fi + fi + + echo -n $"Starting $prog: " + $prog $OPTIONS --pid-file @@autofspiddir@@/autofs.pid + RETVAL=$? + if [ $RETVAL -eq 0 ] ; then + success "$prog startup" + else + failure "$prog startup" + fi + if [ $RETVAL -eq 0 ]; then + touch /var/lock/subsys/autofs + else + RETVAL=1 + fi + echo + return $RETVAL +} + +function stop() { + echo -n $"Stopping $prog: " + count=0 + while [ -n "`pidof $prog`" -a $count -lt 15 ] ; do + killproc $prog -TERM >& /dev/null + RETVAL=$? + [ $RETVAL = 0 -a -z "`pidof $prog`" ] || sleep 20 + count=`expr $count + 1` + done + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/autofs + else + RETVAL=1 + fi + if [ -n "`pidof $prog`" ] ; then + failure "$prog shutdown" + else + success "$prog shutdown" + fi + echo + return $RETVAL +} + +function restart() { + status autofs > /dev/null 2>&1 + if [ $? -eq 0 ]; then + stop + if [ -n "`pidof $prog`" ]; then + # If we failed to stop, try at least one more time + # after waiting a little while + sleep 20 + stop + auto_pid=`pidof $prog` + if [ -n "$auto_pid" ]; then + kill -9 $auto_pid + fi + fi + fi + start +} + +function reload() { + if [ ! -f /var/lock/subsys/autofs ]; then + echo $"$prog not running" + RETVAL=1 + return $RETVAL + fi + pid=`pidof $prog` + if [ -z $pid ]; then + echo $"$prog not running" + RETVAL=1 + else + kill -HUP $pid 2> /dev/null + echo $"Reloading maps" + RETVAL=0 + fi + return $RETVAL +} + +function usage_message() { + echo $"Usage: $0 {start|forcestart|stop|status|restart|force-reload|forcerestart|reload|condrestart|try-restart|usage}" +} + +RETVAL=0 + +# allow non-root users to read status / usage + +case "$1" in + status) + status -p @@autofspiddir@@/autofs.pid -l autofs $prog + exit $?; + ;; + usage) + usage_message + exit 0; + ;; +esac + +# Only the root user may change the service status +if [ `id -u` -ne 0 ] && [ "$1" != "status" ]; then + echo "insufficient privilege to change service status" + exit 4 +fi + +case "$1" in + start) + start + ;; + forcestart) + OPTIONS="$OPTIONS --force" + start + ;; + stop) + stop + ;; + restart|force-reload) + restart + ;; + forcerestart) + OPTIONS="$OPTIONS --force" + restart + ;; + reload) + reload + ;; + condrestart|try-restart) + if [ -f /var/lock/subsys/autofs ]; then + restart + fi + ;; + *) + usage_message + exit 2 + ;; +esac + +exit $? + diff --git a/redhat/autofs.sysconfig b/redhat/autofs.sysconfig new file mode 100644 index 0000000..2ca53ff --- /dev/null +++ b/redhat/autofs.sysconfig @@ -0,0 +1,14 @@ +# +# Init syatem options +# +# If the kernel supports using the autofs miscellanous device +# and you wish to use it you must set this configuration option +# to "yes" otherwise it will not be used. +# +USE_MISC_DEVICE="yes" +# +# Use OPTIONS to add automount(8) command line options that +# will be used when the daemon is started. +# +#OPTIONS="" +# diff --git a/samples/Makefile b/samples/Makefile new file mode 100644 index 0000000..e7f242a --- /dev/null +++ b/samples/Makefile @@ -0,0 +1,219 @@ +# +-include ../Makefile.conf +include ../Makefile.rules + +SAMPLES = auto.master auto.misc auto.net auto.smb + +all: rc.autofs autofs.conf.default autofs.init.conf autofs.service + +rc.autofs: rc.autofs.in + sed -e "s|@@sbindir@@|$(sbindir)|g" \ + -e "s|@@autofslibdir@@|$(autofslibdir)|g" \ + -e "s|@@autofsconfdir@@|$(autofsconfdir)|g" \ + -e "s|@@initdir@@|$(initdir)|g" < rc.autofs.in > rc.autofs + +autofs.conf.default: autofs.conf.default.in + sed -e "s|@@autofsmapdir@@|$(autofsmapdir)|g" \ + < autofs.conf.default.in > autofs.conf.default + +autofs.service: autofs.service.in + sed -e "s|@@sbindir@@|$(sbindir)|g" \ + -e "s|@@autofsconfdir@@|$(autofsconfdir)|g" \ + -e "s|@@autofspiddir@@|$(autofspiddir)|g" \ + < autofs.service.in > autofs.service + +.PHONY: dirs +dirs: + install -d -m 755 $(INSTALLROOT)$(autofsmapdir) + install -d -m 755 $(INSTALLROOT)$(autofsconfdir) + install -d -m 755 $(INSTALLROOT)$(autofslibdir) + install -d -m 755 $(INSTALLROOT)$(autofspiddir) + +.PHONY: autofs.init +autofs.init: + @echo +ifneq ($(systemddir),) + install -d -m 755 $(INSTALLROOT)$(systemddir) + install autofs.service -m 644 $(INSTALLROOT)$(systemddir)/autofs.service +else + ifneq ($(initdir),) + install -d -m 755 $(INSTALLROOT)$(initdir) + install rc.autofs -m 755 $(INSTALLROOT)$(initdir)/autofs + else + if test -d $(INSTALLROOT)/etc/rc.d ; then \ + install -c rc.autofs -m 755 $(INSTALLROOT)/etc/rc.d ; \ + fi + endif +endif + +# +# The map directory contains the main autofs configuration ... +# +CONFIG = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/autofs.conf.orig || echo "-b --suffix=.orig") +CEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/autofs || echo "no") + +.PHONY: autofs.conf +autofs.conf: autofs.conf.default + @echo + @echo "Installing autofs default configuation in $(autofsmapdir)" + @if test -z "$(CONFIG)" ; \ + then \ + install -v autofs.conf.default -m 644 \ + $(INSTALLROOT)$(autofsmapdir)/autofs.conf.new ; \ + echo "Found existing backup of configuration file." ; \ + echo "Installed package default configuration file as \"autofs.conf.new\"." ; \ + else \ + install -v autofs.conf.default -m 644 $(CONFIG) \ + $(INSTALLROOT)$(autofsmapdir)/autofs.conf ; \ + echo "Installed package default configuration as \"autofs.conf\"." ; \ + if test -z "$(CEXISTS)" ; \ + then \ + echo "Backup of existing configuration made to \"autofs.conf.orig\"." ; \ + fi ; \ + fi + +CINIT = $(shell test -e $(INSTALLROOT)$(autofsconfdir)/autofs.orig || echo "-b --suffix=.orig") +CIEXISTS = $(shell test -e $(INSTALLROOT)$(autofsconfdir)/autofs || echo "no") + +.PHONY: autofs.sysinit +autofs.sysinit: autofs.init.conf + @echo + @echo "Installing autofs init configuation in $(autofsconfdir)" + @if test -z "$(CINIT)" ; \ + then \ + install -v autofs.init.conf -m 644 \ + $(INSTALLROOT)$(autofsconfdir)/autofs.new ; \ + echo "Found existing backup of init configuration file." ; \ + echo "Installed package init configuration file as \"autofs.new\"." ; \ + else \ + install -v autofs.init.conf -m 644 $(CINIT) \ + $(INSTALLROOT)$(autofsconfdir)/autofs ; \ + echo "Installed package init configuration as \"autofs\"." ; \ + if test -z "$(CIEXISTS)" ; \ + then \ + echo "Backup of existing init configuration made to \"autofs.orig\"." ; \ + fi ; \ + fi + +AUTH = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/autofs_ldap_auth.conf.orig || echo "-b --suffix=.orig") +AEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/autofs_ldap_auth.conf || echo "no") + +.PHONY: autofs_ldap_auth.conf +autofs_ldap_auth.conf: + @echo + @echo "Installing autofs ldap auth config \"autofs_ldap_auth.conf\" in $(autofsmapdir)" + @if test -z "$(AUTH)" ; \ + then \ + install -v autofs_ldap_auth.conf -m 600 \ + $(INSTALLROOT)$(autofsmapdir)/autofs_ldap_auth.conf.new ; \ + echo "Found existing backup of auth config \"autofs_ldap_auth.conf\"." ; \ + echo "Installed package auth config as \"autofs_ldap_auth.conf.new\"." ; \ + else \ + install -v autofs_ldap_auth.conf -m 600 $(AUTH) \ + $(INSTALLROOT)$(autofsmapdir)/autofs_ldap_auth.conf ; \ + echo "Installed package auth config as \"autofs_ldap_auth.conf\"." ; \ + if test -z "$(SEXISTS)" ; \ + then \ + echo "Backup of existing auth config made to \".utofs_ldap_auth.conf.orig\"." ; \ + fi ; \ + fi + +MASTER = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.master.orig || echo "-b --suffix=.orig") +MEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.master || echo "no") + +.PHONY: auto.master +auto.master: + @echo + @echo "Installing autofs default master map in $(autofsmapdir)" + @if test -z "$(MASTER)" ; \ + then \ + install -v auto.master -m 644 \ + $(INSTALLROOT)$(autofsmapdir)/auto.master.new ; \ + echo "Found existing backup of master map." ; \ + echo "Installed package default master map as \"auto.master.new\"." ; \ + else \ + install -v auto.master -m 644 $(MASTER) \ + $(INSTALLROOT)$(autofsmapdir)/auto.master ; \ + echo "Installed package master map as \"auto.master\"." ; \ + if test -z "$(MEXISTS)" ; \ + then \ + echo "Backup of existing map made to \"auto.master.orig\"." ; \ + fi ; \ + fi + +MISC = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.misc.orig || echo "-b --suffix=.orig") +IEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.misc || echo "no") + +.PHONY: auto.misc +auto.misc: + @echo + @echo "Installing autofs sample map \"auto.misc\" in $(autofsmapdir)" + @if test -z "$(MISC)" ; \ + then \ + install -v auto.misc -m 644 \ + $(INSTALLROOT)$(autofsmapdir)/auto.misc.new ; \ + echo "Found existing backup of sample map \"auto.misc\"." ; \ + echo "Installed package sample as \"auto.misc.new\"." ; \ + else \ + install -v auto.misc -m 644 $(MISC) \ + $(INSTALLROOT)$(autofsmapdir)/auto.misc ; \ + echo "Installed package sample map as \"auto.misc\"." ; \ + if test -z "$(MEXISTS)" ; \ + then \ + echo "Backup of existing map made to \"auto.misc.orig\"." ; \ + fi ; \ + fi + +NET = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.net.orig || echo "-b --suffix=.orig") +NEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.net || echo "no") + +.PHONY: auto.net +auto.net: + @echo + @echo "Installing autofs sample map \"auto.net\" in $(autofsmapdir)" + @if test -z "$(NET)" ; \ + then \ + install -v auto.net -m 755 \ + $(INSTALLROOT)$(autofsmapdir)/auto.net.new ; \ + echo "Found existing backup of sample map \"auto.net\"." ; \ + echo "Installed package sample as \"auto.net.new\"." ; \ + else \ + install -v auto.net -m 755 $(NET) \ + $(INSTALLROOT)$(autofsmapdir)/auto.net ; \ + echo "Installed package sample map as \"auto.net\"." ; \ + if test -z "$(NEXISTS)" ; \ + then \ + echo "Backup of existing map made to \"auto.net.orig\"." ; \ + fi ; \ + fi + +SMB = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.smb.orig || echo "-b --suffix=.orig") +SEXISTS = $(shell test -e $(INSTALLROOT)$(autofsmapdir)/auto.smb || echo "no") + +.PHONY: auto.smb +auto.smb: + @echo + @echo "Installing autofs sample map \"auto.smb\" in $(autofsmapdir)" + @if test -z "$(SMB)" ; \ + then \ + install -v auto.smb -m 755 \ + $(INSTALLROOT)$(autofsmapdir)/auto.smb.new ; \ + echo "Found existing backup of sample map \"auto.smb\"." ; \ + echo "Installed package sample as \"auto.smb.new\"." ; \ + else \ + install -v auto.smb -m 755 $(SMB) \ + $(INSTALLROOT)$(autofsmapdir)/auto.smb ; \ + echo "Installed package sample map as \"auto.smb\"." ; \ + if test -z "$(SEXISTS)" ; \ + then \ + echo "Backup of existing map made to \"auto.smb.orig\"." ; \ + fi ; \ + fi + +install: rc.autofs autofs.conf.default dirs autofs.init autofs.service \ + autofs.conf autofs.sysinit autofs_ldap_auth.conf $(SAMPLES) + @echo + +clean: + rm -f *.o *.s rc.autofs autofs.conf.default autofs.service + diff --git a/samples/auto.master b/samples/auto.master new file mode 100644 index 0000000..0f2c8ab --- /dev/null +++ b/samples/auto.master @@ -0,0 +1,28 @@ +# +# Sample auto.master file +# This is a 'master' automounter map and it has the following format: +# mount-point [map-type[,format]:]map [options] +# For details of the format look at auto.master(5). +# +/misc /etc/auto.misc +# +# NOTE: mounts done from a hosts map will be mounted with the +# "nosuid" and "nodev" options unless the "suid" and "dev" +# options are explicitly given. +# +/net -hosts +# +# Include /etc/auto.master.d/*.autofs +# The included files must conform to the format of this file. +# ++dir:/etc/auto.master.d +# +# Include central master map if it can be found using +# nsswitch sources. +# +# Note that if there are entries for /net or /misc (as +# above) in the included master map any keys that are the +# same will not be seen as the first read key seen takes +# precedence. +# ++auto.master diff --git a/samples/auto.master.ldap b/samples/auto.master.ldap new file mode 100644 index 0000000..bb3d7d9 --- /dev/null +++ b/samples/auto.master.ldap @@ -0,0 +1,22 @@ +# +# Sample auto.master file that uses an LDAP server +# +# autofs can use either the automountMap or the nisMap schema. +# +# When using the automountMap schema use an entry like +# +# /home ldap://budgie/ou=auto.indirect,dc=themaw,dc=net +# +# or if the default LDAP server is set in your LDAP config +# you can use this +# +/home ldap:ou=auto.indirect,dc=themaw,dc=net +# +# When using the nisMap schema use an entry like +# +#/other ldap://budgie/nisMapName=auto.indirect,dc=themaw,dc=net +# +# or if the default LDAP server is set in your LDAP config +# you can use this +# +/other ldap:nisMapName=auto.indirect,dc=themaw,dc=net diff --git a/samples/auto.misc b/samples/auto.misc new file mode 100644 index 0000000..0ee5e75 --- /dev/null +++ b/samples/auto.misc @@ -0,0 +1,15 @@ +# +# This is an automounter map and it has the following format +# key [ -mount-options-separated-by-comma ] location +# Details may be found in the autofs(5) manpage + +cd -fstype=iso9660,ro,nosuid,nodev :/dev/cdrom + +# the following entries are samples to pique your imagination +#linux -ro,soft,intr ftp.example.org:/pub/linux +#boot -fstype=ext2 :/dev/hda1 +#floppy -fstype=auto :/dev/fd0 +#floppy -fstype=ext2 :/dev/fd0 +#e2floppy -fstype=ext2 :/dev/fd0 +#jaz -fstype=ext2 :/dev/sdc1 +#removable -fstype=ext2 :/dev/hdd diff --git a/samples/auto.net b/samples/auto.net new file mode 100755 index 0000000..0384f61 --- /dev/null +++ b/samples/auto.net @@ -0,0 +1,36 @@ +#!/bin/bash + +# This file must be executable to work! chmod 755! + +# Look at what a host is exporting to determine what we can mount. +# This is very simple, but it appears to work surprisingly well + +key="$1" + +# add "nosymlink" here if you want to suppress symlinking local filesystems +# add "nonstrict" to make it OK for some filesystems to not mount +opts="-fstype=nfs,hard,intr,nodev,nosuid" + +for P in /bin /sbin /usr/bin /usr/sbin +do + for M in showmount kshowmount + do + if [ -x $P/$M ] + then + SMNT=$P/$M + break 2 + fi + done +done + +[ -x $SMNT ] || exit 1 + +# Newer distributions get this right +SHOWMOUNT="$SMNT --no-headers -e $key" + +$SHOWMOUNT | LC_ALL=C cut -d' ' -f1 | LC_ALL=C sort -u | \ + awk -v key="$key" -v opts="$opts" -- ' + BEGIN { ORS=""; first=1 } + { if (first) { print opts; first=0 }; print " \\\n\t" $1, key ":" $1 } + END { if (!first) print "\n"; else exit 1 } + ' | sed 's/#/\\#/g' diff --git a/samples/auto.smb b/samples/auto.smb new file mode 100755 index 0000000..6af5d85 --- /dev/null +++ b/samples/auto.smb @@ -0,0 +1,85 @@ +#!/bin/bash + +# This file must be executable to work! chmod 755! + +# Automagically mount CIFS shares in the network, similar to +# what autofs -hosts does for NFS. + +# Put a line like the following in /etc/auto.master: +# /cifs /etc/auto.smb --timeout=300 +# You'll be able to access Windows and Samba shares in your network +# under /cifs/host.domain/share + +# "smbclient -L" is used to obtain a list of shares from the given host. +# In some environments, this requires valid credentials. + +# This script knows 2 methods to obtain credentials: +# 1) if a credentials file (see mount.cifs(8)) is present +# under /etc/creds/$key, use it. +# 2) Otherwise, try to find a usable kerberos credentials cache +# for the uid of the user that was first to trigger the mount +# and use that. +# If both methods fail, the script will try to obtain the list +# of shares anonymously. + +get_krb5_cache() { + cache= + uid=${UID} + for x in $(ls -d /run/user/$uid/krb5cc_* 2>/dev/null); do + if [ -d "$x" ] && klist -s DIR:"$x"; then + cache=DIR:$x + return + fi + done + if [ -f /tmp/krb5cc_$uid ] && klist -s /tmp/krb5cc_$uid; then + cache=/tmp/krb5cc_$uid + return + fi +} + +key="$1" +opts="-fstype=cifs" + +for P in /bin /sbin /usr/bin /usr/sbin +do + if [ -x $P/smbclient ] + then + SMBCLIENT=$P/smbclient + break + fi +done + +[ -x $SMBCLIENT ] || exit 1 + +creds=/etc/creds/$key +if [ -f "$creds" ]; then + opts="$opts"',uid=$UID,gid=$GID,credentials='"$creds" + smbopts="-A $creds" +else + get_krb5_cache + if [ -n "$cache" ]; then + opts="$opts"',multiuser,cruid=$UID,sec=krb5i' + smbopts="-k" + export KRB5CCNAME=$cache + else + opts="$opts"',guest' + smbopts="-N" + fi +fi + +$SMBCLIENT $smbopts -gL "$key" 2>/dev/null| awk -v "key=$key" -v "opts=$opts" -F '|' -- ' + BEGIN { ORS=""; first=1 } + /Disk/ { + if (first) + print opts; first=0 + dir = $2 + loc = $2 + # Enclose mount dir and location in quotes + # Double quote "$" in location as it is special + gsub(/\$$/, "\\$", loc); + gsub(/\&/,"\\\\&",loc) + print " \\\n\t \"/" dir "\"", "\"://" key "/" loc "\"" + } + END { if (!first) print "\n"; else exit 1 } + ' + diff --git a/samples/autofs.conf.default.in b/samples/autofs.conf.default.in new file mode 100644 index 0000000..3ea3dd1 --- /dev/null +++ b/samples/autofs.conf.default.in @@ -0,0 +1,406 @@ +# +# Define default options for autofs. +# +[ autofs ] +# +# master_map_name - default map name for the master map. +# +#master_map_name = auto.master +# +# timeout - set the default mount timeout in secons. The internal +# program default is 10 minutes, but the default installed +# configuration overrides this and sets the timeout to 5 +# minutes to be consistent with earlier autofs releases. +# +timeout = 300 +# +# master_wait - set the default maximum time to wait for the +# master map to become available if it cannot +# be read at program start (default 10, wait +# for 10 seconds then continue). +# +# master_wait = 10 +# +# negative_timeout - set the default negative timeout for +# failed mount attempts (default 60). +# +#negative_timeout = 60 +# +# mount_wait - time to wait for a response from mount(8). +# Setting this timeout can cause problems when +# mount would otherwise wait for a server that +# is temporarily unavailable, such as when it's +# restarting. The default setting (-1) of waiting +# for mount(8) usually results in a wait of around +# 3 minutes. +# +#mount_wait = -1 +# +# umount_wait - time to wait for a response from umount(8). +# +#umount_wait = 12 +# +# browse_mode - maps are browsable by default. +# +browse_mode = no +# +# mount_nfs_default_protocol - specify the default protocol used by +# mount.nfs(8). Since we can't identify +# the default automatically we need to +# set it in our configuration. +# +#mount_nfs_default_protocol = 3 +# +# append_options - append to global options instead of replace. +# +#append_options = yes +# +# logging - set default log level "none", "verbose" or "debug" +# +#logging = none +# +# force_standard_program_map_env - disable the use of the "AUTOFS_" +# prefix for standard environemt variables when +# executing a program map. Since program maps +# are run as the privileded user this opens +# automount(8) to potential user privilege +# escalation when the program map is written +# in a language that can load components from, +# for example, a user home directory. +# +# force_standard_program_map_env = no +# +# use_mount_request_log_id - Set whether to use a mount request log +# id so that log entries for specific mount +# requests can be easily identified in logs +# that have multiple conncurrent requests. +# +#use_mount_request_log_id = no +# +# Define base dn for map dn lookup. +# +# Define server URIs +# +# ldap_uri - space seperated list of server uris of the form +# ://[/] where can be ldap +# or ldaps. The option can be given multiple times. +# Map entries that include a server name override +# this option. +# +# This configuration option can also be used to +# request autofs lookup SRV RRs for a domain of +# the form :///[]. Note that a +# trailing "/" is not allowed when using this form. +# If the domain dn is not specified the dns domain +# name (if any) is used to construct the domain dn +# for the SRV RR lookup. The server list returned +# from an SRV RR lookup is refreshed according to +# the minimum ttl found in the SRV RR records or +# after one hour, whichever is less. +# +#ldap_uri = "" +# +# ldap_timeout - timeout value for the synchronous API calls +# (default is LDAP library default). +# +#ldap_timeout = -1 +# +# ldap_network_timeout - set the network response timeout (default 8). +# +#ldap_network_timeout = 8 +# +# search_base - base dn to use for searching for map search dn. +# Multiple entries can be given and they are checked +# in the order they occur here. +# +#search_base = "" +# +# Define the LDAP schema to used for lookups +# +# If no schema is set autofs will check each of the schemas +# below in the order given to try and locate an appropriate +# basdn for lookups. If you want to minimize the number of +# queries to the server set the values here. +# +#map_object_class = nisMap +#entry_object_class = nisObject +#map_attribute = nisMapName +#entry_attribute = cn +#value_attribute= nisMapEntry +# +# Other common LDAP naming +# +#map_object_class = automountMap +#entry_object_class = automount +#map_attribute = ou +#entry_attribute = cn +#value_attribute= automountInformation +# +#map_object_class = automountMap +#entry_object_class = automount +#map_attribute = automountMapName +#entry_attribute = automountKey +#value_attribute= automountInformation +# +# auth_conf_file - set the default location for the SASL +# authentication configuration file. +# +#auth_conf_file = @@autofsmapdir@@/autofs_ldap_auth.conf +# +# map_hash_table_size - set the map cache hash table size. +# Should be a power of 2 with a ratio of +# close to 1:8 for acceptable performance +# with maps up to around 8000 entries. +# See autofs.conf(5) for more details. +# +#map_hash_table_size = 1024 +# +# use_hostname_for_mounts - nfs mounts where the host name resolves +# to more than one IP address normally need +# to use the IP address to esure a mount to +# a host that isn't responding isn't done. +# If that behaviour is not wanted then set +# ths to "yes", default is "no". +# +#use_hostname_for_mounts = "no" +# +# disable_not_found_message - The original request to add this log message +# needed it to be unconditional. That produces, IMHO, +# unnecessary noise in the log so a configuration option +# has been added to provide the ability to turn it off. +# The default is "no" to maintain the current behaviour. +# +#disable_not_found_message = "no" +# +# sss_master_map_wait - When sssd is starting up it can sometimes return +# "no such entry" for a short time until it has read +# in the LDAP map information. Internal default is 0 +# seconds, don't wait but if there is a problem with +# autofs not finding the master map at startup (when +# it should) then try setting this to 10 to work +# around it. +# +#sss_master_map_wait = 0 +# +# Otions for the amd parser within autofs. +# +# amd configuration options that are aren't used, haven't been +# implemented or have different behaviour within autofs. +# +# A number of the amd configuration options are not used by autofs, +# some because they are not relevant within autofs, some because +# they are done differently in autofs and others that are not yet +# implemented. +# +# Since "mount_type" is always autofs (because there's no user space +# NFS server) the configuration entries relating to that aren't used. +# Also, server availability is done differently within autofs so the +# options that relate to the amd server monitoring sub-system are +# also not used. +# +# These options are mount_type, auto_attrcache, portmap_program, +# nfs_vers_ping, nfs_allow_any_interface, nfs_allow_insecure_port, +# nfs_proto, nfs_retransmit_counter, nfs_retransmit_counter_udp, +# nfs_retransmit_counter_tcp, nfs_retransmit_counter_toplvl, +# nfs_retry_interval, nfs_retry_interval_udp, nfs_retry_interval_tcp, +# nfs_retry_interval_toplvl and nfs_vers. +# +# +# Other options that are not used within the autofs implementation: +# +# log_file, truncate_log - autofs used either stderr when running in +# the foreground or sends its output to syslog so an alternate +# log file (or truncating the log) can't be used. +# +# print_pid - there's no corresponding option for this within autofs. +# +# use_tcpwrappers, show_statfs_entries - there's no user space NFS +# server to control access to so this option isn't relevant. +# The show_statfs_entries can't be implemented for the same +# reason. +# +# debug_mtab_file - there's no user space NFS server and autofs +# avoids using file based mtab whenever possible. +# +# sun_map_syntax - obviously, are provided by autofs itself. +# +# plock, show_statfs_entries, preferred_amq_port - not supported. +# +# ldap_cache_maxmem, ldap_cache_seconds - external ldap caching +# is not used by autofs. +# +# ldap_proto_version - autofs always attempts to use the highest +# available ldap protocol version. +# +# cache_duration, map_reload_interval, map_options - the map +# entry cache is continually updated and stale entries +# cleaned on re-load, which is done when map changes are +# detected so these configuration entries are not used +# by autofs. +# +# localhost_address - is not used within autofs. This +# configuration option was only used in the amd user +# space server code and is not relevant within autofs. +# +# +# Options that are handled differently within autofs: +# +# pid_file - must be given as a command line option on startup. +# +# print_version - program version and feature information is obtained +# by using the automount command line option "-V". +# +# debug_options, log_options - autofs has somewhat more limited +# logging and debug logging options. When the log_options +# options is encountered it is converted to the nearest +# matching autofs logging option. Since the configuration +# option debug_options would be handled the same way it +# is ignored. +# +# restart_mounts - has no sensible meaning within autofs because autofs +# always tries to re-connect to existing mounts. While this +# has its own set of problems not re-connecting to existing +# mounts always results in a non-functional automount tree if +# mounts were busy at the last shutdown (as is also the case +# with amd when using mount_type autofs). +# +# forced_unmounts - detaching mounts often causes serious problems +# for users of existing mounts. It is used by autofs in some +# cases, either at the explicit request of the user (with a +# command line or init option) and in some special cases during +# program operation but is avoided whenever possible. +# +# +# A number of configuration options are not yet implemented: +# +# fully_qualified_hosts - not yet implemented. +# +# unmount_on_exit - since autofs always tries to re-connect +# to mounts left mounted from a previous shutdown this +# is a sensible option to implement and that will be +# done. +# +# exec_map_timeout - a timeout is not currently used for +# for program maps, might be implemented. +# +# tag - the tag option is not implemented within autofs. +# +# +# Supported options: +# +# arch, karch, os, osver - these options default to what is returned +# from uname(2) and can be overridden if required. +# +# full_os - has no default and must be set in the configuration +# if used in maps. +# +# cluster - if not set defaults to the host domain name. This option +# corresponds to the HP_UX cluster name (according to the amd +# source) and is probably not used in Linux but is set anyway. +# +# vendor - has a default value of "unknown", it must be set in the +# configuration if used in maps. +# +# auto_dir - is the base name of the mount tree used for external +# mounts that are sometimes needed by amd maps. Its default +# value is "/a". +# +# map_type - specifies the autofs map source, such as file, nis, +# ldap etc. and has no default value set. +# +# map_defaults - is used to override /defaults entries within maps +# and can be used to provide different defaults on specific +# machines without having to modify centrally managed maps. +# It is empty by default. +# +# search_path - colon seperated paths to search for maps that +# are not specified as a full path. +# +# dismount_interval - is equivalent to the autofs timeout option. It +# is only possible to use this with type "auto" mounts due +# to the way the autofs kernel module performs expiry. It +# takes its default value from the autofs internal default +# of 600 seconds. +# +# browsable_dirs - make map keys visible in directory listings. +# Note that support for the "fullybrowsable" option cannot +# be added using the existing kernel to user space autofs +# implementation. +# +# autofs_use_lofs - if set to "yes" autofs will attempt to use bind +# mounts for type "auto" when possible. +# +# nis_domain - allows setting of a domain name other than the system +# default. +# +# local_domain - is used to override (or set) the host domain name. +# +# normalize_hostnames - if set to "yes" then the contents of ${rhost} +# is translated in its official host name. +# +# domain_strip - if set to "yes" the domain name part of the host +# is strippped when normalizing hostnames. This can be useful +# when using of the same maps in a multiple domain environment. +# +# normalize_slashes - is set to "yes" by default and will collapse +# multiple unescaped occurrences of "/" to a single "/". +# +# selectors_in_defaults, selectors_on_default - has a default value +# of "no". If set to "yes" then any defaults entry will be +# checked for selectors to determine the values to be used. +# selectors_in_defaults is the preferred option to use. +# +# ldap_base - has no default value. It must be set to the base dn +# that is used for queries if ldap is to be used as a map +# source. +# +# ldap_hostports - has no default value set. It must be set to +# the URI of the LDAP server to be used for lookups when +# ldap is used a map source. It may contain a comma or +# space seperated list of LDAP URIs. +# +# hesiod_base - the base name used for hesiod map sources. +# +# Additional configuration options added: +# +# linux_ufs_mount_type - set the default system filesystem type that's +# used for mount type ufs. There's no simple way to determine +# what the system default filesystem is and am-utils needs to +# be continually updated to do this and can easily get it wrong +# anyway. +# +# +# Define global options for the amd parser within autofs. +# +[ amd ] +# +# Override the internal default with the same timeout that +# is used by the override in the autofs configuration, sanity +# only change. +# +dismount_interval = 300 +# +# map_type = file +# +# Overriding this can cause autofs to use less resources because +# it will use symlinks instead of bind mounts in certain cases. +# You should ensure that the autofs kernel module your using +# supports expration of symlinks for best results (although this +# appears to work reasonably well most of the time without the +# update). +# +# autofs_use_lofs = yes +# +# Several configuration options can be set per mount point. +# In particulr map_type, map_name, map_defaults, search_path, +# browsable_dirs, dismount_interval and selectors_in_defaults +# (not all of which are currently implemented, see above). +# +# Also, if a section for an amd mount point is defined here +# it isn't necessary to specify the format in the corresponding +# master map entry and the format will be inherited for type +# "auto" mounts. +# +# [ /expamle/mount ] +# dismount_interval = 60 +# map_type = nis diff --git a/samples/autofs.init.conf b/samples/autofs.init.conf new file mode 100644 index 0000000..2ca53ff --- /dev/null +++ b/samples/autofs.init.conf @@ -0,0 +1,14 @@ +# +# Init syatem options +# +# If the kernel supports using the autofs miscellanous device +# and you wish to use it you must set this configuration option +# to "yes" otherwise it will not be used. +# +USE_MISC_DEVICE="yes" +# +# Use OPTIONS to add automount(8) command line options that +# will be used when the daemon is started. +# +#OPTIONS="" +# diff --git a/samples/autofs.schema b/samples/autofs.schema new file mode 100644 index 0000000..07e23b4 --- /dev/null +++ b/samples/autofs.schema @@ -0,0 +1,23 @@ +# Depends upon core.schema and cosine.schema + +# OID Base is 1.3.6.1.4.1.2312.4 +# +# Attribute types are under 1.3.6.1.4.1.2312.4.1 +# Object classes are under 1.3.6.1.4.1.2312.4.2 +# Syntaxes are under 1.3.6.1.4.1.2312.4.3 + +# Attribute Type Definitions + +attributetype ( 1.3.6.1.4.1.2312.4.1.2 NAME 'automountInformation' + DESC 'Information used by the autofs automounter' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) + +objectclass ( 1.3.6.1.4.1.2312.4.2.3 NAME 'automount' SUP top STRUCTURAL + DESC 'An entry in an automounter map' + MUST ( cn $ automountInformation $ objectclass ) + MAY ( description ) ) + +objectclass ( 1.3.6.1.4.1.2312.4.2.2 NAME 'automountMap' SUP top STRUCTURAL + DESC 'An group of related automount objects' + MUST ( ou ) ) diff --git a/samples/autofs.service.in b/samples/autofs.service.in new file mode 100644 index 0000000..f9fa96c --- /dev/null +++ b/samples/autofs.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Automounts filesystems on demand +After=network.target ypbind.service sssd.service network-online.target remote-fs.target +Wants=network-online.target + +[Service] +Type=forking +PIDFile=@@autofspiddir@@/autofs.pid +EnvironmentFile=-@@autofsconfdir@@/autofs +ExecStart=@@sbindir@@/automount $OPTIONS --pid-file @@autofspiddir@@/autofs.pid +ExecReload=/usr/bin/kill -HUP $MAINPID +TimeoutSec=180 + +[Install] +WantedBy=multi-user.target diff --git a/samples/autofs_ldap_auth.conf b/samples/autofs_ldap_auth.conf new file mode 100644 index 0000000..4033ba0 --- /dev/null +++ b/samples/autofs_ldap_auth.conf @@ -0,0 +1,11 @@ + + + + diff --git a/samples/ldap-automount-auto.direct b/samples/ldap-automount-auto.direct new file mode 100644 index 0000000..88d5d26 --- /dev/null +++ b/samples/ldap-automount-auto.direct @@ -0,0 +1,46 @@ +# +dn: ou=auto.direct,dc=bogus +objectClass: top +objectClass: automountMap +ou: auto.direct + +dn: cn=/nfs/budgie/man,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/budgie/man +automountInformation: budgie:/usr/local/man + +dn: cn=/nfs/budgie/bin,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/budgie/bin +automountInformation: budgie:/local/data/bin + +dn: cn=/nfs/budgie/etc,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/budgie/etc +automountInformation: budgie:/usr/local/etc + +dn: cn=/nfs/sbin,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/sbin +automountInformation: budgie:/usr/local/sbin + +dn: cn=/tst/budgie/man,ou=auto.direct,dc=bogus +objectClass: automount +cn: /tst/budgie/man +automountInformation: budgie:/usr/local/man + +dn: cn=/nfs/budgie/sbin,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/budgie/sbin +automountInformation: budgie:/usr/local/sbin + +dn: cn=/nfs/budgie/test,ou=auto.direct,dc=bogus +objectClass: automount +cn: /nfs/budgie/test +automountInformation: budgie:/usr/local/test + +dn: cn=/tst/budgie/etc,ou=auto.direct,dc=bogus +objectClass: automount +cn: /tst/budgie/etc +automountInformation: budgie:/usr/local/etc + diff --git a/samples/ldap-automount-auto.indirect b/samples/ldap-automount-auto.indirect new file mode 100644 index 0000000..c4db2ed --- /dev/null +++ b/samples/ldap-automount-auto.indirect @@ -0,0 +1,26 @@ +# +dn: ou=auto.indirect,dc=bogus +objectClass: top +objectClass: automountMap +ou: auto.indirect + +dn: cn=bin,ou=auto.indirect,dc=bogus +objectClass: automount +cn: bin +automountInformation: budgie:/usr/local/bin + +dn: cn=etc,ou=auto.indirect,dc=bogus +objectClass: automount +cn: etc +automountInformation: budgie:/usr/local/etc + +dn: cn=lib,ou=auto.indirect,dc=bogus +objectClass: automount +cn: lib +automountInformation: budgie:/usr/local/lib + +dn: cn=/,ou=auto.indirect,dc=bogus +objectClass: automount +cn: / +automountInformation: budgie:/usr/local/& + diff --git a/samples/ldap-automount-auto.master b/samples/ldap-automount-auto.master new file mode 100644 index 0000000..92afeac --- /dev/null +++ b/samples/ldap-automount-auto.master @@ -0,0 +1,16 @@ +# +dn: ou=auto.master,dc=bogus +objectClass: top +objectClass: automountMap +ou: auto.master + +dn: cn=/ldap,ou=auto.master,dc=bogus +objectClass: automount +cn: /ldap +automountInformation: ldap://budgie/ou=auto.indirect,dc=bogus + +dn: cn=/-,ou=auto.master,dc=bogus +objectClass: automount +cn: /- +automountInformation: ldap://budgie/ou=auto.direct,dc=bogus + diff --git a/samples/ldap-automount-rfc2307-bis-auto.direct b/samples/ldap-automount-rfc2307-bis-auto.direct new file mode 100644 index 0000000..7bde24b --- /dev/null +++ b/samples/ldap-automount-rfc2307-bis-auto.direct @@ -0,0 +1,46 @@ +# +dn: automountMapName=auto_direct,dc=themaw,dc=net +objectClass: top +objectClass: automountMap +automountMapName: auto_direct + +dn: automountKey=/nfs/budgie/man,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/budgie/man +automountInformation: budgie:/usr/local/man + +dn: automountKey=/nfs/budgie/bin,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/budgie/bin +automountInformation: budgie:/local/data/bin + +dn: automountKey=/nfs/budgie/etc,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/budgie/etc +automountInformation: budgie:/usr/local/etc + +dn: automountKey=/nfs/sbin,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/sbin +automountInformation: budgie:/usr/local/sbin + +dn: automountKey=/tst/budgie/man,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /tst/budgie/man +automountInformation: budgie:/usr/local/man + +dn: automountKey=/nfs/budgie/sbin,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/budgie/sbin +automountInformation: budgie:/usr/local/sbin + +dn: automountKey=/nfs/budgie/test,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /nfs/budgie/test +automountInformation: budgie:/usr/local/test + +dn: automountKey=/tst/budgie/etc,automountMapName=auto_direct,dc=themaw,dc=net +objectClass: automount +automountKey: /tst/budgie/etc +automountInformation: budgie:/usr/local/etc + diff --git a/samples/ldap-automount-rfc2307-bis-auto.indirect b/samples/ldap-automount-rfc2307-bis-auto.indirect new file mode 100644 index 0000000..9d43915 --- /dev/null +++ b/samples/ldap-automount-rfc2307-bis-auto.indirect @@ -0,0 +1,30 @@ +# +dn: automountMapName=auto_indirect,dc=themaw,dc=net +objectClass: top +objectClass: automountMap +automountMapName: auto_indirect + +dn: automountKey=bin,automountMapName=auto_indirect,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: bin +automountInformation: budgie:/usr/local/bin + +dn: automountKey=etc,automountMapName=auto_indirect,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: etc +automountInformation: budgie:/usr/local/etc + +dn: automountKey=lib,automountMapName=auto_indirect,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: lib +automountInformation: budgie:/usr/local/lib + +dn: automountKey=/,automountMapName=auto_indirect,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: / +automountInformation: budgie:/usr/local/& + diff --git a/samples/ldap-automount-rfc2307-bis-auto.master b/samples/ldap-automount-rfc2307-bis-auto.master new file mode 100644 index 0000000..b7b6237 --- /dev/null +++ b/samples/ldap-automount-rfc2307-bis-auto.master @@ -0,0 +1,19 @@ +dn: automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automountMap +automountMapName: auto_master + +dn: description=/ldap, automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: /ldap +automountInformation: auto_indirect +description: /ldap + +dn: description=/-,automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: /- +automountInformation: auto_direct +description: /- + diff --git a/samples/ldap-automount-rfc2307-bis-old-style-auto.master b/samples/ldap-automount-rfc2307-bis-old-style-auto.master new file mode 100644 index 0000000..0880ff0 --- /dev/null +++ b/samples/ldap-automount-rfc2307-bis-old-style-auto.master @@ -0,0 +1,19 @@ +dn: automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automountMap +automountMapName: auto_master + +dn: description=/ldap, automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: /ldap +automountInformation: ldap:budgie:automountMapName=auto_indirect,dc=themaw,dc=net +description: /ldap + +dn: description=/-,automountMapName=auto_master,dc=themaw,dc=net +objectClass: top +objectClass: automount +automountKey: /- +automountInformation: ldap:budgie:automountMap=auto_direct,dc=themaw,dc=net +description: /- + diff --git a/samples/ldap-nis-auto.direct b/samples/ldap-nis-auto.direct new file mode 100644 index 0000000..40e87e9 --- /dev/null +++ b/samples/ldap-nis-auto.direct @@ -0,0 +1,54 @@ +# +dn: nisMapName=auto.direct,dc=bogus +objectClass: top +objectClass: nisMap +nisMapName: auto.direct + +dn: cn=/nfs/budgie/man,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/budgie/man +nisMapEntry: budgie:/usr/local/man +nisMapName: auto.direct + +dn: cn=/nfs/budgie/bin,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/budgie/bin +nisMapEntry: budgie:/local/data/bin +nisMapName: auto.direct + +dn: cn=/nfs/budgie/etc,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/budgie/etc +nisMapEntry: budgie:/usr/local/etc +nisMapName: auto.direct + +dn: cn=/nfs/sbin,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/sbin +nisMapEntry: budgie:/usr/local/sbin +nisMapName: auto.direct + +dn: cn=/tst/budgie/man,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /tst/budgie/man +nisMapEntry: budgie:/usr/local/man +nisMapName: auto.direct + +dn: cn=/nfs/budgie/sbin,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/budgie/sbin +nisMapEntry: budgie:/usr/local/sbin +nisMapName: auto.direct + +dn: cn=/nfs/budgie/test,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /nfs/budgie/test +nisMapEntry: budgie:/usr/local/test +nisMapName: auto.direct + +dn: cn=/tst/budgie/etc,nisMapName=auto.direct,dc=bogus +objectClass: nisObject +cn: /tst/budgie/etc +nisMapEntry: budgie:/usr/local/etc +nisMapName: auto.direct + diff --git a/samples/ldap-nis-auto.indirect b/samples/ldap-nis-auto.indirect new file mode 100644 index 0000000..5e67170 --- /dev/null +++ b/samples/ldap-nis-auto.indirect @@ -0,0 +1,30 @@ +# +dn: nisMapName=auto.indirect,dc=bogus +objectClass: top +objectClass: nisMap +nisMapName: auto.indirect + +dn: cn=bin,nisMapName=auto.indirect,dc=bogus +objectClass: nisObject +cn: bin +nisMapEntry: budgie:/usr/local/bin +nisMapName: auto.indirect + +dn: cn=etc,nisMapName=auto.indirect,dc=bogus +objectClass: nisObject +cn: etc +nisMapEntry: budgie:/usr/local/etc +nisMapName: auto.indirect + +dn: cn=lib,nisMapName=auto.indirect,dc=bogus +objectClass: nisObject +cn: lib +nisMapEntry: budgie:/usr/local/lib +nisMapName: auto.indirect + +dn: cn=/,nisMapName=auto.indirect,dc=bogus +objectClass: nisObject +cn: / +nisMapEntry: budgie:/usr/local/& +nisMapName: auto.indirect + diff --git a/samples/ldap-nis-auto.master b/samples/ldap-nis-auto.master new file mode 100644 index 0000000..7cdc33c --- /dev/null +++ b/samples/ldap-nis-auto.master @@ -0,0 +1,18 @@ +# +dn: nisMapName=auto.master,dc=bogus +objectClass: top +objectClass: nisMap +nisMapName: auto.master + +dn: cn=/ldap,nisMapName=auto.master,dc=bogus +objectClass: nisObject +cn: /ldap +nisMapEntry: ldap://budgie/nisMapName=auto.indirect,dc=bogus +nisMapName: auto.master + +dn: cn=/-,nisMapName=auto.master,dc=bogus +objectClass: nisObject +cn: /- +nisMapEntry: ldap://budgie/nisMapName=auto.direct,dc=bogus +nisMapName: auto.master + diff --git a/samples/rc.autofs.in b/samples/rc.autofs.in new file mode 100644 index 0000000..487669f --- /dev/null +++ b/samples/rc.autofs.in @@ -0,0 +1,156 @@ +#!/bin/bash +# +# rc file for automount using a Sun-style "master map". +# +# On most distributions, this file should be called: +# /etc/rc.d/init.d/autofs or /etc/init.d/autofs or /etc/rc.d/rc.autofs +# +# +### BEGIN INIT INFO +# Provides: autofs +# Required-Start: $network ypbind +# Required-Stop: $network ypbind +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# Short-Description: Automounts filesystems on demand +# Description: Automounts filesystems on demand +### END INIT INFO +# +# Location of the automount daemon and the init directory +# +DAEMON=@@sbindir@@/automount +prog=`basename $DAEMON` +MODULE="autofs4" +DEVICE="autofs" +confdir=@@autofsconfdir@@ + +test -e $DAEMON || exit 0 + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +export PATH + +# +# load customized configuation settings +# +if [ -r $confdir/autofs ]; then + . $confdir/autofs +fi + +function start() { + echo -n "Starting $prog: " + + # Make sure autofs4 module is loaded + if ! grep -q autofs /proc/filesystems + then + # Try load the autofs4 module fail if we can't + modprobe $MODULE >/dev/null 2>&1 + if [ $? -eq 1 ] + then + echo "Error: failed to load autofs4 module." + return 1 + fi + elif ([ -f /proc/modules ] && lsmod) | grep -q autofs[^4] + then + # wrong autofs filesystem module loaded + echo + echo "Error: autofs kernel module is loaded, autofs4 required" + return 1 + fi + + # Check misc device + if [ -n "$USE_MISC_DEVICE" -a "x$USE_MISC_DEVICE" = "xyes" ]; then + sleep 1 + if [ -e "/proc/misc" ]; then + MINOR=`awk "/$DEVICE/ {print \\$1}" /proc/misc` + if [ -n "$MINOR" -a ! -c "/dev/$DEVICE" ]; then + mknod -m 0600 /dev/$DEVICE c 10 $MINOR + fi + fi + if [ -x /sbin/restorecon -a -c /dev/$DEVICE ]; then + /sbin/restorecon /dev/$DEVICE + fi + else + if [ -c /dev/$DEVICE ]; then + rm /dev/$DEVICE + fi + fi + + $prog $OPTIONS + RETVAL=$? + if [ $RETVAL -eq 0 ] ; then + echo "done." + else + echo "failed." + fi + return $RETVAL +} + +function stop() { + echo -n $"Stopping $prog: " + count=0 + while [ -n "`pidof $prog`" -a $count -lt 15 ] ; do + killall -TERM $prog >& /dev/null + RETVAL=$? + [ $RETVAL = 0 -a -z "`pidof $prog`" ] || sleep 20 + count=`expr $count + 1` + done + if [ -z "`pidof $prog`" ] ; then + echo "done." + else + echo "failed." + fi + return $RETVAL +} + +function restart() { + stop + while [ -n "`pidof $prog`" ] ; do + sleep 5 + done + start +} + +function reload() { + pid=`pidof $prog` + if [ -z $pid ]; then + echo $"$prog not running" + RETVAL=1 + else + kill -HUP $pid 2> /dev/null + echo $"Reloading maps" + RETVAL=0 + fi + return $RETVAL +} + +RETVAL=0 + +case "$1" in + start) + start + ;; + forcestart) + OPTIONS="$OPTIONS --force" + start + ;; + stop) + stop + ;; + restart) + restart + ;; + forcerestart) + OPTIONS="$OPTIONS --force" + restart + ;; + reload) + reload + ;; + *) + echo $"Usage: $0 {start|forcestart|stop|restart|forcerestart|reload}" + exit 1; + ;; +esac + +exit $? +