Misc s/func/certmaster/ replacements
[certmaster.git] / certmaster / minion / modules / filetracker.py
1 ## func
2 ##
3 ## filetracker
4 ## maintains a manifest of files of which to keep track
5 ## provides file meta-data (and optionally full data) to func-inventory
6 ##
7 ## (C) Vito Laurenza <vitolaurenza@gmail.com>
8 ## + Michael DeHaan <mdehaan@redhat.com>
9 ##
10 ## This software may be freely redistributed under the terms of the GNU
11 ## general public license.
12 ##
13 ## You should have received a copy of the GNU General Public License
14 ## along with this program; if not, write to the Free Software
15 ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 ##
17
18 # func modules
19 import func_module
20
21 # other modules
22 from stat import *
23 import glob
24 import os
25 import md5
26
27 # defaults
28 CONFIG_FILE='/etc/func/modules/filetracker.conf'
29
30 class FileTracker(func_module.FuncModule):
31
32 version = "0.0.1"
33 api_version = "0.0.1"
34 description = "Maintains a manifest of files to keep track of."
35
36 def __load(self):
37 """
38 Parse file and return data structure.
39 """
40
41 filehash = {}
42 if os.path.exists(CONFIG_FILE):
43 config = open(CONFIG_FILE, "r")
44 data = config.read()
45 lines = data.split("\n")
46 for line in lines:
47 tokens = line.split(None)
48 if len(tokens) < 2:
49 continue
50 scan_mode = tokens[0]
51 path = " ".join(tokens[1:])
52 if str(scan_mode).lower() == "0":
53 scan_mode = 0
54 else:
55 scan_mode = 1
56 filehash[path] = scan_mode
57 return filehash
58
59 #==========================================================
60
61 def __save(self, filehash):
62 """
63 Write data structure to file.
64 """
65
66 config = open(CONFIG_FILE, "w+")
67 for (path, scan_mode) in filehash.iteritems():
68 config.write("%s %s\n" % (scan_mode, path))
69 config.close()
70
71 #==========================================================
72
73 def track(self, file_name, full_scan=0):
74 """
75 Adds files to keep track of.
76 full_scan implies tracking the full contents of the file, defaults to off
77 """
78
79 filehash = self.__load()
80 filehash[file_name] = full_scan
81 self.__save(filehash)
82 return 1
83
84 #==========================================================
85
86 def untrack(self, file_name):
87 """
88 Stop keeping track of a file.
89 This routine is tolerant of most errors since we're forgetting about the file anyway.
90 """
91
92 filehash = self.__load()
93 if file_name in filehash.keys():
94 del filehash[file_name]
95 self.__save(filehash)
96 return 1
97
98 #==========================================================
99
100 def inventory(self, flatten=1, checksum_enabled=1):
101 """
102 Returns information on all tracked files
103 By default, 'flatten' is passed in as True, which makes printouts very clean in diffs
104 for use by func-inventory. If you are writting another software application, using flatten=False will
105 prevent the need to parse the returns.
106 """
107
108 # XMLRPC feeds us strings from the CLI when it shouldn't
109 flatten = int(flatten)
110 checksum_enabled = int(checksum_enabled)
111
112 filehash = self.__load()
113
114 # we'll either return a very flat string (for clean diffs)
115 # or a data structure
116 if flatten:
117 results = ""
118 else:
119 results = []
120
121 for (file_name, scan_type) in filehash.iteritems():
122
123 if not os.path.exists(file_name):
124 if flatten:
125 results = results + "%s: does not exist\n" % file_name
126 else:
127 results.append("%s: does not exist\n" % file_name)
128 continue
129
130 this_result = []
131
132 # ----- always process metadata
133 filestat = os.stat(file_name)
134 mode = filestat[ST_MODE]
135 mtime = filestat[ST_MTIME]
136 uid = filestat[ST_UID]
137 gid = filestat[ST_GID]
138 if not os.path.isdir(file_name) and checksum_enabled:
139 sum_handle = open(file_name)
140 hash = self.__sumfile(sum_handle)
141 sum_handle.close()
142 else:
143 hash = "N/A"
144
145 # ------ what we return depends on flatten
146 if flatten:
147 this_result = "%s: mode=%s mtime=%s uid=%s gid=%s md5sum=%s\n" % (file_name,mode,mtime,uid,gid,hash)
148 else:
149 this_result = [file_name,mode,mtime,uid,gid,hash]
150
151 # ------ add on file data only if requested
152 if scan_type != 0 and os.path.isfile(file_name):
153 tracked_file = open(file_name)
154 data = tracked_file.read()
155 if flatten:
156 this_result = this_result + "*** DATA ***\n" + data + "\n*** END DATA ***\n\n"
157 else:
158 this_result.append(data)
159 tracked_file.close()
160
161 if os.path.isdir(file_name):
162 if not file_name.endswith("/"):
163 file_name = file_name + "/"
164 files = glob.glob(file_name + "*")
165 if flatten:
166 this_result = this_result + "*** FILES ***\n" + "\n".join(files) + "\n*** END FILES ***\n\n"
167 else:
168 this_result.append({"files" : files})
169
170 if flatten:
171 results = results + "\n" + this_result
172 else:
173 results.append(this_result)
174
175
176 return results
177
178 #==========================================================
179
180 def __sumfile(self, fobj):
181 """
182 Returns an md5 hash for an object with read() method.
183 credit: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266486
184 """
185
186 m = md5.new()
187 while True:
188 d = fobj.read(8096)
189 if not d:
190 break
191 m.update(d)
192 return m.hexdigest()