Browse code

Implement reload.

Xavier G authored on 11/04/2020 14:44:50
Showing 2 changed files

... ...
@@ -33,6 +33,12 @@ fstab syntax:
33 33
 
34 34
 Refer to `configuration.reference.yaml` to write your own configuration file.
35 35
 
36
+It is possible to reload the configuration file without remounting the filesystem.
37
+```
38
+cat /mount/point/reload
39
+```
40
+This will output either "reload ok" or "reload fail".
41
+
36 42
 ## Why?
37 43
 
38 44
 Certbot already offers hooks to handle pretty much everything, from mere concatenations to complex deployments to various kinds of clusters.
... ...
@@ -34,6 +34,12 @@ DEFAULT_DIR_MODE = 0o555
34 34
 DEFAULT_REG_MODE = 0o444
35 35
 DEFAULT_KEY_MODE = 0o400
36 36
 DEFAULT_SENSITIVE_PATTERN = '/privkey.pem$'
37
+
38
+RELOAD_PATH = '/reload'
39
+RELOAD_MSG_OK   = b'reload ok\n'
40
+RELOAD_MSG_FAIL = b'reload fail\n'
41
+RELOAD_FILESIZE = max(len(RELOAD_MSG_OK), len(RELOAD_MSG_FAIL))
42
+
37 43
 TIME_PROPS = ('st_atime', 'st_ctime', 'st_mtime')
38 44
 
39 45
 def read_mode_setting(obj, key, default):
... ...
@@ -188,6 +194,36 @@ class CombinedFS(Operations):
188 194
 			self.filedesc[new_fd_index] = file_descriptor
189 195
 		return new_fd_index
190 196
 
197
+	def reload(self, conf=None):
198
+		try:
199
+			if conf is None:
200
+				conf = self.get_conf()
201
+			new_conf = CombinedFSConfiguration(conf.path)
202
+			# Assuming CPython, this should result in a single STORE_ATTR opcode.
203
+			# Since this class features no __setattr__ implementation, the
204
+			# resulting execution should be atomic.
205
+			self.configuration = new_conf
206
+			return new_conf
207
+		except:
208
+			return None
209
+
210
+	def handle_reload_getattr(self, conf, fh):
211
+		return {
212
+			'st_nlink': 1,
213
+			'st_uid': conf.uid,
214
+			'st_gid': conf.gid,
215
+			'st_size': RELOAD_FILESIZE,
216
+			'st_mode': stat.S_IFREG | conf.key_mode,
217
+		}
218
+
219
+	def handle_reload_open(self, conf, flags):
220
+		new_conf = self.reload(conf)
221
+		new_fd = {
222
+			'conf': conf if new_conf is None else new_conf,
223
+			'data': RELOAD_MSG_FAIL if new_conf is None else RELOAD_MSG_OK,
224
+		}
225
+		return self.register_fd(new_fd)
226
+
191 227
 	# Filesystem methods
192 228
 
193 229
 	def access(self, path, mode):
... ...
@@ -202,10 +238,22 @@ class CombinedFS(Operations):
202 238
 
203 239
 	def getattr(self, path, fh=None):
204 240
 		conf = self.get_conf()
241
+		if path == RELOAD_PATH:
242
+			return self.handle_reload_getattr(conf, fh)
205 243
 		cert, filename, file_spec = conf.analyse_path(path)
206 244
 		if filename is None: # Directory
207 245
 			full_path = os.path.join(conf.root, path.lstrip('/'))
208
-			dir_attrs = self.attributes(full_path)
246
+			try:
247
+				dir_attrs = self.attributes(full_path)
248
+			except OSError as ose:
249
+				if ose.errno == errno.ENOENT and path == '/':
250
+					# Non-existent "live" directory, most likely a misconf,
251
+					# fake it to preserve access to RELOAD_PATH:
252
+					dir_attrs = {'st_nlink': 2, 'st_size': 4096}
253
+					for prop in TIME_PROPS:
254
+						dir_attrs[prop] = 0
255
+				else:
256
+					raise
209 257
 			dir_attrs['st_uid'] = conf.uid
210 258
 			dir_attrs['st_gid'] = conf.gid
211 259
 			dir_attrs['st_mode'] = stat.S_IFDIR | conf.dir_mode
... ...
@@ -276,6 +324,8 @@ class CombinedFS(Operations):
276 324
 
277 325
 	def open(self, path, flags):
278 326
 		conf = self.get_conf()
327
+		if path == RELOAD_PATH:
328
+			return self.handle_reload_open(conf, flags)
279 329
 		cert, filename, file_spec = conf.analyse_path(path)
280 330
 		if not cert or not filename:
281 331
 			raise FuseOSError(errno.ENOENT)