Author: nextgens
Date: 2006-05-24 21:31:36 +0000 (Wed, 24 May 2006)
New Revision: 8854
Added:
trunk/freenet/src/freenet/node/updater/
trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
trunk/freenet/src/freenet/node/updater/NodeUpdater.java
trunk/freenet/src/freenet/node/updater/PrivkeyHasBeenBlownException.java
trunk/freenet/src/freenet/node/updater/RevocationKeyFoundUserAlert.java
trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
trunk/freenet/src/freenet/node/updater/UpdatedVersionAvailableUserAlert.java
trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
Modified:
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/TextModeClientInterface.java
Log:
More work on the auto-updater : still not ready to be used though
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2006-05-24 21:29:39 UTC (rev
8853)
+++ trunk/freenet/src/freenet/node/Node.java 2006-05-24 21:31:36 UTC (rev
8854)
@@ -87,6 +87,7 @@
import freenet.keys.SSKBlock;
import freenet.keys.SSKVerifyException;
import freenet.node.fcp.FCPServer;
+import freenet.node.updater.NodeUpdater;
import freenet.pluginmanager.PluginManager;
import freenet.store.BerkeleyDBFreenetStore;
import freenet.store.FreenetStore;
@@ -1352,7 +1353,7 @@
try{
nodeUpdater = NodeUpdater.maybeCreate(this, config);
Logger.normal(this, "Starting the node updater");
- }catch (NodeInitException e) {
+ }catch (Exception e) {
e.printStackTrace();
throw new
NodeInitException(EXIT_COULD_NOT_START_UPDATER, "Could not start Updater: "+e);
}
Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-05-24
21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-05-24
21:31:36 UTC (rev 8854)
@@ -291,7 +291,7 @@
outsb.append("Download rate: "+rate+" bytes / second");
} catch (FetchException e) {
outsb.append("Error: "+e.getMessage());
- if(e.getMode() == e.SPLITFILE_ERROR && e.errorCodes != null) {
+ if(e.getMode() == FetchException.SPLITFILE_ERROR &&
e.errorCodes != null) {
outsb.append(e.errorCodes.toVerboseString());
}
if(e.newURI != null)
@@ -300,6 +300,9 @@
} else if(uline.startsWith("UPDATE")) {
n.getNodeUpdater().Update();
return false;
+ }else if(uline.startsWith("BLOW")) {
+ n.getNodeUpdater().blow("caught an IOException :
(Incompetent Operator) :p");
+ return false;
} else if(uline.startsWith("SHUTDOWN")) {
StringBuffer sb = new StringBuffer();
sb.append("Shutting node down.\r\n");
Added: trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,25 @@
+package freenet.node.updater;
+
+import freenet.config.BooleanCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.node.Node;
+
+public class AutoUpdateAllowedCallback implements BooleanCallback {
+
+ final Node node;
+
+ AutoUpdateAllowedCallback(Node n) {
+ this.node = n;
+ }
+
+ public boolean get() {
+ NodeUpdater nu = node.getNodeUpdater();
+ return nu.isAutoUpdateAllowed;
+ }
+
+ public void set(boolean val) throws InvalidConfigValueException {
+ if(val == get()) return;
+ // Good idea to prevent it ?
+ throw new InvalidConfigValueException("Cannot be updated on the
fly for security reasons");
+ }
+}
\ No newline at end of file
Copied: trunk/freenet/src/freenet/node/updater/NodeUpdater.java (from rev 8840,
trunk/freenet/src/freenet/node/NodeUpdater.java)
===================================================================
--- trunk/freenet/src/freenet/node/NodeUpdater.java 2006-05-23 21:47:12 UTC
(rev 8840)
+++ trunk/freenet/src/freenet/node/updater/NodeUpdater.java 2006-05-24
21:31:36 UTC (rev 8854)
@@ -0,0 +1,336 @@
+package freenet.node.updater;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.client.FetcherContext;
+import freenet.client.InserterException;
+import freenet.client.async.BaseClientPutter;
+import freenet.client.async.ClientCallback;
+import freenet.client.async.ClientGetter;
+import freenet.client.async.USKCallback;
+import freenet.config.Config;
+import freenet.config.SubConfig;
+import freenet.keys.FreenetURI;
+import freenet.keys.NodeCHK;
+import freenet.keys.USK;
+import freenet.node.Node;
+import freenet.node.RequestStarter;
+import freenet.node.Version;
+import freenet.support.ArrayBucket;
+import freenet.support.Logger;
+
+public class NodeUpdater implements ClientCallback, USKCallback {
+ private FetcherContext ctx;
+ private FetcherContext ctxRevocation;
+ private FetchResult result;
+ private ClientGetter cg;
+ private final FreenetURI URI;
+ private final FreenetURI revocationURI;
+ private final Node node;
+
+ private final int currentVersion;
+ private int availableVersion;
+
+ private String revocationMessage;
+ private boolean hasBeenBlown;
+ private int revocationDNFCounter;
+
+ private boolean isRunning = false;
+ private boolean isFetching = false;
+
+ public final boolean isAutoUpdateAllowed;
+
+ private final UpdatedVersionAvailableUserAlert alert;
+ private RevocationKeyFoundUserAlert revocationAlert;
+
+ // TODO: Start the revocation url fetching!
+ public NodeUpdater(Node n, boolean isAutoUpdateAllowed, FreenetURI URI,
FreenetURI revocationURI) {
+ super();
+ this.URI = URI;
+ this.revocationURI = revocationURI;
+ this.revocationAlert = null;
+ this.revocationDNFCounter = 0;
+ this.node = n;
+ this.currentVersion = Version.buildNumber();
+ this.availableVersion = currentVersion;
+ this.hasBeenBlown = false;
+ this.isRunning = true;
+ this.isAutoUpdateAllowed = isAutoUpdateAllowed;
+ this.cg = null;
+ this.isFetching = false;
+
+ this.alert= new
UpdatedVersionAvailableUserAlert(currentVersion);
+ alert.isValid(false);
+ node.alerts.register(alert);
+
+ FetcherContext ctx =
n.makeClient((short)0).getFetcherContext();
+ ctx.allowSplitfiles = true;
+ ctx.dontEnterImplicitArchives = false;
+ this.ctx = ctx;
+
+ FetcherContext ctx2 =
n.makeClient((short)0).getFetcherContext();
+ ctx2.allowSplitfiles = false;
+ ctx2.cacheLocalRequests = false;
+ ctx2.followRedirects = false;
+ ctx2.maxArchiveLevels = 1;
+ // big enough ?
+ ctx2.maxOutputLength = 4096;
+ ctx2.maxTempLength = 4096;
+ this.ctxRevocation = ctx2;
+
+ try{
+ USK myUsk=USK.create(URI);
+ ctx.uskManager.subscribe(myUsk, this, true);
+ ctx.uskManager.startTemporaryBackgroundFetcher(myUsk);
+
+ }catch(MalformedURLException e){
+ Logger.error(this,"The auto-update URI isn't valid and
can't be used");
+ blow("The auto-update URI isn't valid and can't be
used");
+ }
+ }
+
+ public synchronized void onFoundEdition(long l, USK key){
+ // FIXME : Check if it has been blown
+ int found = (int)key.suggestedEdition;
+
+ if(found > availableVersion){
+ this.availableVersion = found;
+ try{
+ maybeUpdate();
+ }catch (Exception e){
+
+ }
+ System.out.println("Found "+availableVersion);
+ Logger.normal(this, "Found a new version! (" +
availableVersion + ", setting up a new UpdatedVersionAviableUserAlert");
+ alert.set(availableVersion);
+ alert.isValid(true);
+ this.isRunning=true;
+ }
+ }
+
+ public synchronized void maybeUpdate(){
+ try{
+ if(isFetching || !isRunning || !isUpdatable()) return;
+ }catch (PrivkeyHasBeenBlownException e){
+ // how to handle it ? a new UserAlert or an imediate
exit?
+ Logger.error(this, "The auto-updating Private key has
been blown!");
+ node.exit();
+ }
+
+ isRunning=false;
+
+ //TODO maybe a UpdateInProgress alert ?
+ if(isAutoUpdateAllowed){
+ Logger.normal(this,"Starting the update process");
+// We fetch it
+ try{
+ if(cg==null||cg.isCancelled()){
+ cg = new ClientGetter(this,
node.chkFetchScheduler, node.sskFetchScheduler,
+
URI.setSuggestedEdition(availableVersion), ctx,
RequestStarter.BULK_SPLITFILE_PRIORITY_CLASS,
+ this, new
ArrayBucket());
+ }
+ cg.start();
+ isFetching = true;
+ }catch (Exception e) {
+ Logger.error(this, "Error while starting the
fetching");
+ }
+ }else{
+ Logger.normal(this,"Not starting the update process as
it's not allowed");
+ }
+ }
+
+ /**
+ * We try to update the node :p
+ *
+ */
+ public synchronized void Update(){
+ if((result == null) || !isAutoUpdateAllowed || hasBeenBlown)
+ return;
+
+ // FIXME: maybe we need a higher throshold
+ if(revocationDNFCounter<1){
+ Logger.normal(this, "We don't have checked if the
revocation key has been inserted or not yet : delaying update");
+ return;
+ }
+
+ Logger.normal(this, "Update in progress");
+ try{
+ ArrayBucket bucket = (ArrayBucket) result.asBucket();
+ byte[] data = bucket.toByteArray();
+
+ File f = new File("freenet-cvs-snapshot.jar.new");
+ f.delete();
+
+ FileOutputStream fos = new FileOutputStream(f);
+
+ fos.write(data);
+ fos.flush();
+ fos.close();
+ System.out.println("################## File written!
"+cg.getURI().getSuggestedEdition()+ " " +f.getAbsolutePath());
+
+ File f2 = new File("freenet-cvs-snapshot.jar");
+ f2.delete();
+
+ if(f.renameTo(f2)){
+ if(node.getNodeStarter()!=null)
+ node.getNodeStarter().restart();
+ else{
+ System.out.println("New version has
been downloaded: please restart your node!");
+ node.exit();
+ }
+ }else
+ System.out.println("ERROR renaming the file!");
+
+
+ }catch(Exception e){
+ Logger.error(this, "Error while updating the node :
"+e);
+ System.out.println("Exception : "+e);
+ e.printStackTrace();
+ }
+ }
+
+ public void onSuccess(FetchResult result, ClientGetter state) {
+ // TODO ensure it works
+ if(!state.getURI().equals(revocationURI)){
+ synchronized(this){
+ this.cg = state;
+ this.result = result;
+ Update();
+ }
+ }else{
+ // The key has been blown !
+ // FIXME: maybe we need a bigger warning message.
+ try{
+
blow(result.asBucket().getOutputStream().toString());
+ Logger.error(this, "The revocation key has been
found on the network : blocking auto-update");
+ }catch(IOException e){
+ // We stop anyway.
+ synchronized(this){
+ this.hasBeenBlown = true;
+ }
+ Logger.error(this, "Unable to set the
revocation flag");
+ }
+ }
+ }
+
+ public synchronized void onFailure(FetchException e, ClientGetter
state) {
+ int errorCode = e.getMode();
+
+ if(!state.getURI().equals(revocationURI)){
+ this.cg = state;
+
+ cg.cancel();
+ if(errorCode == FetchException.DATA_NOT_FOUND ||
+ errorCode ==
FetchException.ROUTE_NOT_FOUND ||
+ errorCode ==
FetchException.PERMANENT_REDIRECT ||
+ errorCode ==
FetchException.REJECTED_OVERLOAD){
+
+ Logger.normal(this, "Rescheduling new request");
+ maybeUpdate();
+ }else
+ Logger.error(this, "Canceling fetch : "+
e.getMessage());
+ }else{
+ if(errorCode == FetchException.DATA_NOT_FOUND){
+ revocationDNFCounter++;
+ }
+ // FIXME: else ? what do we do ?
+ }
+ }
+
+ public void onSuccess(BaseClientPutter state) {
+ // Impossible
+ }
+
+ public void onFailure(InserterException e, BaseClientPutter state) {
+ // Impossible
+ }
+
+ public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) {
+ // Impossible
+ }
+
+ public boolean isUpdatable() throws PrivkeyHasBeenBlownException{
+ if(hasBeenBlown)
+ throw new
PrivkeyHasBeenBlownException(revocationMessage);
+ else
+ return (currentVersion<availableVersion);
+ }
+
+ public boolean isRunning(){
+ return isRunning;
+ }
+
+ public synchronized void blow(String msg){
+ if(hasBeenBlown){
+ Logger.error(this, "The key has ALREADY been marked as
blown!");
+ }else{
+ this.revocationMessage = msg;
+ this.hasBeenBlown = true;
+ if(revocationAlert==null){
+ revocationAlert = new
RevocationKeyFoundUserAlert(msg);
+ node.alerts.register(revocationAlert);
+ // we don't need to advertize updates : we are
not going to do them
+ node.alerts.unregister(alert);
+ }
+ Logger.error(this, "The updater has acknoledged that it
knows the private key has been blown");
+ }
+ }
+
+ public FreenetURI getUpdateKey(){
+ return URI;
+ }
+
+ public FreenetURI getRevocationKey(){
+ return revocationURI;
+ }
+
+ public static NodeUpdater maybeCreate(Node node, Config config) throws
Exception {
+ SubConfig updaterConfig = new SubConfig("node.updater", config);
+
+ updaterConfig.register("enabled", false, 1, false, "Enable Node's
updater?",
+ "Whether to enable the node's updater. It won't
auto-update unless node.updater.autoupdate is true, it will just warn",
+ new UpdaterEnabledCallback(node));
+
+ boolean enabled = updaterConfig.getBoolean("enabled");
+
+ if(enabled) {
+ // is the auto-update allowed ?
+ updaterConfig.register("autoupdate", false, 2, false, "Is the
node allowed to auto-update?", "Is the node allowed to auto-update?",
+ new AutoUpdateAllowedCallback(node));
+ boolean autoUpdateAllowed =
updaterConfig.getBoolean("autoupdate");
+
+ updaterConfig.register("URI",
+ "freenet:USK at
SIDKS6l-eOU8IQqDo03d~3qqBd-69WG60aDgg4nWqss,CPFqYi95Is3GwzAdAKtAuFMCXDZFFWC3~uPoidCD67s,AQABAAE/update/"+Version.buildNumber()+"/",
+ 3, true, "Where should the node look for
updates?",
+ "Where should the node look for updates?",
+ new UpdateURICallback(node));
+
+ String URI = updaterConfig.getString("URI");
+
+
+ updaterConfig.register("revocationURI",
+ "freenet:SSK at
VOfCZVTYPaatJ~eB~4lu2cPrWEmGyt4bfbB1v15Z6qQ,B6EynLhm7QE0se~rMgWWhl7wh3rFWjxJsEUcyohAm8A,AQABAAE/revoked/",
+ 3, true, "Where should the node look for
revocation ?",
+ "Where should the node look for revocation ?",
+ new UpdateRevocationURICallback(node));
+
+ String revURI = updaterConfig.getString("revocationURI");
+
+
+ updaterConfig.finishedInitialization();
+ try{
+ return new NodeUpdater(node , autoUpdateAllowed, new
FreenetURI(URI), new FreenetURI(revURI));
+ }catch(Exception e){
+ Logger.error(node, "Error starting the NodeUpdater:
"+e);
+ throw new Exception("Unable to start the NodeUpdater
up");
+ }
+ } else {
+ updaterConfig.finishedInitialization();
+ return null;
+ }
+ }
+}
Added: trunk/freenet/src/freenet/node/updater/PrivkeyHasBeenBlownException.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/PrivkeyHasBeenBlownException.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/PrivkeyHasBeenBlownException.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,11 @@
+package freenet.node.updater;
+
+public class PrivkeyHasBeenBlownException extends Exception{
+ private static final long serialVersionUID = -1;
+
+ PrivkeyHasBeenBlownException(String msg) {
+ super("The project's private key has been blown, meaning that
it has been compromized"+
+ "and shouldn't be trusted anymore. Please get a new
build by hand and verify CAREFULLY"+
+ "its signature and CRC. Here is the revocation
message: "+msg);
+ }
+}
Added: trunk/freenet/src/freenet/node/updater/RevocationKeyFoundUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/RevocationKeyFoundUserAlert.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/RevocationKeyFoundUserAlert.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,41 @@
+package freenet.node.updater;
+
+import freenet.node.UserAlert;
+
+public class RevocationKeyFoundUserAlert implements UserAlert {
+ private final String msg;
+
+ RevocationKeyFoundUserAlert(String msg){
+ this.msg=msg;
+ }
+
+ public boolean userCanDismiss() {
+ return false;
+ }
+
+ public String getTitle() {
+ return "The private key of the project has been compromized!";
+ }
+
+ public String getText() {
+ //TODO: reformulate : maybe put the GPG key fingerprint of
"trusted devs"
+ return "Your node has found the auto-updater's revocation key
on the network. "+
+ "It means that our auto-updating system is likely to
have been COMPROMIZED! "+
+ "Consequently, it has been disabled on your node to
prevent \"bad things\" to "+
+ "be installed. We strongly advise you to check the
project's website for updates. "+
+ "Please take care of verifying that the website hasn't
been spoofed either. "+
+ "The revocation message is the following : "+msg;
+ }
+
+ public short getPriorityClass() {
+ return UserAlert.CRITICAL_ERROR;
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ public void isValid(boolean b){
+ // We ignore it : it's ALWAYS valid !
+ }
+}
\ No newline at end of file
Added: trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,32 @@
+package freenet.node.updater;
+
+import freenet.config.StringCallback;
+import freenet.node.Node;
+import freenet.support.Logger;
+
+public class UpdateRevocationURICallback implements StringCallback{
+
+ private final Node node;
+ private final String baseURI = "SSK at
VOfCZVTYPaatJ~eB~4lu2cPrWEmGyt4bfbB1v15Z6qQ,B6EynLhm7QE0se~rMgWWhl7wh3rFWjxJsEUcyohAm8A,AQABAAE/revoked/";
+
+ public UpdateRevocationURICallback(Node node) {
+ this.node = node;
+ }
+
+ public String get() {
+ NodeUpdater nu = node.getNodeUpdater();
+ if (nu != null)
+ return nu.getRevocationKey().toString(true);
+ else
+ return baseURI;
+ }
+
+ public void set(String val) {
+ if(val == get()) return;
+ // Good idea to prevent it ?
+ //
+ // Maybe it NEEDS to be implemented
+ Logger.error(this, "Node's updater revocationURI can't be
updated on the fly");
+ return;
+ }
+}
Added: trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,33 @@
+package freenet.node.updater;
+
+import freenet.config.StringCallback;
+import freenet.node.Node;
+import freenet.node.Version;
+import freenet.support.Logger;
+
+public class UpdateURICallback implements StringCallback{
+
+ private final Node node;
+ private final String baseURI = "freenet:USK at
SIDKS6l-eOU8IQqDo03d~3qqBd-69WG60aDgg4nWqss,CPFqYi95Is3GwzAdAKtAuFMCXDZFFWC3~uPoidCD67s,AQABAAE/update/";
+
+ public UpdateURICallback(Node node) {
+ this.node = node;
+ }
+
+ public String get() {
+ NodeUpdater nu = node.getNodeUpdater();
+ if (nu != null)
+ return nu.getUpdateKey().toString(true);
+ else
+ return baseURI+Version.buildNumber()+"/";
+ }
+
+ public void set(String val) {
+ if(val == get()) return;
+ // Good idea to prevent it ?
+ //
+ // Maybe it NEEDS to be implemented
+ Logger.error(this, "Node's updater URI can't be updated on the
fly");
+ return;
+ }
+}
\ No newline at end of file
Added:
trunk/freenet/src/freenet/node/updater/UpdatedVersionAvailableUserAlert.java
===================================================================
---
trunk/freenet/src/freenet/node/updater/UpdatedVersionAvailableUserAlert.java
2006-05-24 21:29:39 UTC (rev 8853)
+++
trunk/freenet/src/freenet/node/updater/UpdatedVersionAvailableUserAlert.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,42 @@
+package freenet.node.updater;
+
+import freenet.node.UserAlert;
+
+public class UpdatedVersionAvailableUserAlert implements UserAlert {
+ private boolean isValid;
+ private int version;
+
+ UpdatedVersionAvailableUserAlert(int version){
+ this.version=version;
+ isValid=false;
+ }
+
+ public synchronized void set(int v){
+ version = v;
+ }
+
+ public boolean userCanDismiss() {
+ return false;
+ }
+
+ public String getTitle() {
+ return "A new stable version of Freenet is available";
+ }
+
+ public String getText() {
+ return "It seems that your node isn't running the latest
version of the software. "+
+ "Updating to "+version+" is advised.";
+ }
+
+ public short getPriorityClass() {
+ return UserAlert.MINOR;
+ }
+
+ public boolean isValid() {
+ return isValid;
+ }
+
+ public void isValid(boolean b){
+ isValid=b;
+ }
+}
\ No newline at end of file
Added: trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
2006-05-24 21:29:39 UTC (rev 8853)
+++ trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
2006-05-24 21:31:36 UTC (rev 8854)
@@ -0,0 +1,24 @@
+package freenet.node.updater;
+
+import freenet.config.BooleanCallback;
+import freenet.config.InvalidConfigValueException;
+import freenet.node.Node;
+
+public class UpdaterEnabledCallback implements BooleanCallback {
+
+ final Node node;
+
+ UpdaterEnabledCallback(Node n) {
+ this.node = n;
+ }
+
+ public boolean get() {
+ return node.getNodeUpdater() != null;
+ }
+
+ public void set(boolean val) throws InvalidConfigValueException {
+ if(val == get()) return;
+ // FIXME implement
+ throw new InvalidConfigValueException("Cannot be updated on the
fly");
+ }
+}
\ No newline at end of file