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 $? +