Repository: ansible/ansible

Issue:
Gathering facts fails if a cifs mount is present on the remote machine
##### Issue Type:

“Bug Report”
##### Ansible Version:

ansible 1.6
##### Environment:

Debian 7
##### Summary:

The setup script hangs on gathering mount facts when a cifs drive is mounted
##### Steps To Reproduce:

mount a windows share with these options in fstab:

```
//<address>/<share>  /mnt/net  cifs  rw,uid=1000,gid=1000,user=<id>,password=<pass>
```
##### Expected Results:

ansible should probably skip unreadable mounts
##### Actual Results:

the _get_mount_facts_ function in the setup script to hang on the line containing _os.statvfs_



Discussion:
Hmm, I would have expected more reports of this -- we'll investigate.

Thanks!


Resolving patch:
diff --git a/lib/ansible/module_utils/facts.py b/lib/ansible/module_utils/facts.py
--- a/lib/ansible/module_utils/facts.py
+++ b/lib/ansible/module_utils/facts.py
@@ -17,11 +17,13 @@
 
 import os
 import array
+import errno
 import fcntl
 import fnmatch
 import glob
 import platform
 import re
+import signal
 import socket
 import struct
 import datetime
@@ -40,6 +42,33 @@
 except ImportError:
     import simplejson as json
 
+# --------------------------------------------------------------
+# timeout function to make sure some fact gathering 
+# steps do not exceed a time limit
+
+class TimeoutError(Exception):
+    pass
+
+def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
+    def decorator(func):
+        def _handle_timeout(signum, frame):
+            raise TimeoutError(error_message)
+
+        def wrapper(*args, **kwargs):
+            signal.signal(signal.SIGALRM, _handle_timeout)
+            signal.alarm(seconds)
+            try:
+                result = func(*args, **kwargs)
+            finally:
+                signal.alarm(0)
+            return result
+
+        return wrapper
+
+    return decorator
+
+# --------------------------------------------------------------
+
 class Facts(object):
     """
     This class should only attempt to populate those facts that
@@ -498,7 +527,10 @@ def populate(self):
         self.get_memory_facts()
         self.get_dmi_facts()
         self.get_device_facts()
-        self.get_mount_facts()
+        try:
+            self.get_mount_facts()
+        except TimeoutError:
+            pass
         return self.facts
 
     def get_memory_facts(self):
@@ -622,6 +654,7 @@ def get_dmi_facts(self):
                 else:
                     self.facts[k] = 'NA'
 
+    @timeout(10)
     def get_mount_facts(self):
         self.facts['mounts'] = []
         mtab = get_file_content('/etc/mtab', '')
@@ -919,7 +952,10 @@ def populate(self):
         self.get_memory_facts()
         self.get_dmi_facts()
         self.get_device_facts()
-        self.get_mount_facts()
+        try:
+            self.get_mount_facts()
+        except TimeoutError:
+            pass
         return self.facts
 
     def get_cpu_facts(self):
@@ -962,6 +998,7 @@ def get_memory_facts(self):
         self.facts['swaptotal_mb'] = data[1]
         self.facts['swapfree_mb'] = data[3]
 
+    @timeout(10)
     def get_mount_facts(self):
         self.facts['mounts'] = []
         fstab = get_file_content('/etc/fstab')
@@ -1041,7 +1078,10 @@ def __init__(self):
     def populate(self):
         self.get_cpu_facts()
         self.get_memory_facts()
-        self.get_mount_facts()
+        try:
+            self.get_mount_facts()
+        except TimeoutError:
+            pass
         return self.facts
 
     def get_cpu_facts(self):
@@ -1085,6 +1125,7 @@ def get_memory_facts(self):
                 val = data[1].strip().split(' ')[0]
                 self.facts["%s_mb" % key.lower()] = long(val) / 1024
 
+    @timeout(10)
     def get_mount_facts(self):
         self.facts['mounts'] = []
         fstab = get_file_content('/etc/fstab')


Test patch: