]> git.ipfire.org Git - people/ms/network.git/blobdiff - src/functions/functions.zone
Always destroy zones immediately
[people/ms/network.git] / src / functions / functions.zone
index b18d4548fad2c0e47b649fd56410d89eb81bc850..b79127a2106e78ca8e57d412877f77604fab3b54 100644 (file)
 #                                                                             #
 ###############################################################################
 
-function zone_dir() {
+zone_dir() {
        local zone=${1}
 
        echo "${NETWORK_ZONE_DIR}/zones/${zone}"
 }
 
-function zone_exists() {
+zone_exists() {
        local zone=${1}
        assert isset zone
 
        [ -d "$(zone_dir ${zone})" ]
 }
 
-function zone_match() {
+zone_match() {
        local match
 
        local i
@@ -43,7 +43,7 @@ function zone_match() {
        echo "${match:1:${#match}}"
 }
 
-function zone_name_is_valid() {
+zone_name_is_valid() {
        local zone=${1}
 
        # Don't accept empty strings.
@@ -52,26 +52,26 @@ function zone_name_is_valid() {
        [[ ${zone} =~ $(zone_match) ]]
 }
 
-function zone_is_local() {
+zone_is_local() {
        local zone=${1}
 
        [[ "${zone:0:${#ZONE_LOCAL}}" = "${ZONE_LOCAL}" ]]
 }
 
-function zone_is_nonlocal() {
+zone_is_nonlocal() {
        local zone=${1}
 
        [[ "${zone:0:${#ZONE_NONLOCAL}}" = "${ZONE_NONLOCAL}" ]]
 }
 
-function zone_get_hook() {
+zone_get_hook() {
        local zone=${1}
        assert isset zone
 
        config_get_hook $(zone_dir ${zone})/settings
 }
 
-function zone_start() {
+zone_start() {
        # This function will bring up the zone
        # 'asynchronously' with help of systemd.
 
@@ -81,7 +81,29 @@ function zone_start() {
        service_start "network@${zone}.service"
 }
 
-function zone_stop() {
+zone_start_auto() {
+       local zone="${1}"
+       assert zone_exists "${zone}"
+
+       # If the zone has already been started, we
+       # will reload it so the current configuration
+       # is re-applied.
+       if zone_is_active "${zone}"; then
+               zone_reload "${zone}"
+               return ${?}
+
+       # If the zone is still down, but in auto-start mode,
+       # we will start it.
+       elif zone_is_enabled "${zone}"; then
+               zone_start "${zone}"
+               return ${?}
+       fi
+
+       # Otherwise, nothing will be done.
+       return ${EXIT_OK}
+}
+
+zone_stop() {
        # This function will bring down the zone
        # 'asynchronously' with help of systemd.
 
@@ -91,7 +113,23 @@ function zone_stop() {
        service_stop "network@${zone}.service"
 }
 
-function zone_enable() {
+zone_reload() {
+       local zone="${1}"
+       assert zone_exists "${zone}"
+
+       service_reload "network@${zone}.service"
+}
+
+zone_hotplug_event() {
+       local zone="${1}"
+       assert isset zone
+
+       hotplug_assert_in_hotplug_event
+
+       zone_cmd "hotplug" "${zone}"
+}
+
+zone_enable() {
        # This function will enable the zone
        # with help of systemd.
 
@@ -111,7 +149,7 @@ function zone_enable() {
        return ${ret}
 }
 
-function zone_disable() {
+zone_disable() {
        # This function will disable the zone
        # with help of systemd.
 
@@ -131,7 +169,7 @@ function zone_disable() {
        return ${ret}
 }
 
-function zone_is_enabled() {
+zone_is_enabled() {
        local zone="${1}"
        assert isset zone
 
@@ -143,7 +181,39 @@ function zone_is_enabled() {
        return ${EXIT_FALSE}
 }
 
-function zone_create() {
+zone_is_active() {
+       local zone="${1}"
+       assert isset zone
+
+       if service_is_active "network@${zone}.service"; then
+               return ${EXIT_TRUE}
+       fi
+
+       return ${EXIT_FALSE}
+}
+
+zone_is_enabled_or_active() {
+       local zone="${1}"
+       assert isset zone
+
+       zone_is_enabled "${zone}" || zone_is_active "${zone}"
+}
+
+zone_cmd() {
+       local cmd="${1}"
+       local port="${2}"
+       shift 2
+
+       assert isset cmd
+       assert isset zone
+
+       local hook="$(zone_get_hook ${zone})"
+       assert isset hook
+
+       hook_exec zone "${hook}" "${cmd}" "${zone}" $@
+}
+
+zone_new() {
        local zone=${1}
        local hook=${2}
        shift 2
@@ -168,21 +238,24 @@ function zone_create() {
        # Create directories for configs and ports
        mkdir -p $(zone_dir ${zone})/{configs,ports}
 
-       hook_zone_exec ${hook} create ${zone} $@
+       hook_zone_exec "${hook}" "new" "${zone}" $@
        local ret=$?
 
-       # Maybe the zone create hook did not exit correctly.
+       # Maybe the zone new hook did not exit correctly.
        # If this is the case we remove the created zone immediately.
-       if [ "${ret}" = "${EXIT_ERROR}" ]; then
-               zone_remove_now ${zone}
+       if [ "${ret}" != "${EXIT_OK}" ]; then
+               zone_destroy "${zone}"
                return ${EXIT_ERROR}
        fi
 
        # Automatically enable zone.
        zone_enable "${zone}"
+
+       # Bring up the zone immediately after
+       zone_start "${zone}"
 }
 
-function zone_edit() {
+zone_edit() {
        local zone=${1}
        shift
 
@@ -191,14 +264,7 @@ function zone_edit() {
                return ${EXIT_ERROR}
        fi
 
-       # Check if the zone is tagged for removal.
-       if zone_has_remove_tag ${zone}; then
-               error "You cannot edit a zone that is tagged for removal."
-               return ${EXIT_ERROR}
-       fi
-
-       local hook=$(config_get_hook $(zone_dir ${zone})/settings)
-
+       local hook="$(zone_get_hook "${zone}")"
        if [ -z "${hook}" ]; then
                error "Config file did not provide any hook."
                return ${EXIT_ERROR}
@@ -212,43 +278,67 @@ function zone_edit() {
        hook_zone_exec ${hook} edit ${zone} $@
 }
 
+zone_rename() {
+       assert [ $# -eq 2 ]
 
-function zone_remove() {
-       local zone=${1}
-       assert zone_exists ${zone}
+       local zone="${1}"
+       local name="${2}"
 
-       # Make the zone for removal.
-       touch $(zone_dir ${zone})/.remove
+       assert zone_exists "${zone}"
+       assert not zone_exists "${name}"
 
-       log INFO "Zone '${zone}' has been tagged for removal."
-}
+       # The zone must be shut down before, is then renamed and
+       # potentially brought up again
 
-function zone_has_remove_tag() {
-       local zone=${1}
-       assert zone_exists ${zone}
+       # Save if the zone is running right now
+       zone_is_active "${zone}"
+       local zone_was_active="${?}"
+
+       # Save if the zone is enabled (i.e. auto-start)
+       zone_is_enabled "${zone}"
+       local zone_was_enabled="${?}"
+
+       # Stop the zone
+       zone_stop "${zone}"
+
+       # Disable the zone
+       zone_disable "${zone}"
+
+       # Rename the configuration files
+       mv -f "$(zone_dir "${zone}")" "$(zone_dir "${name}")"
+
+       # Enable the zone if it was enabled before
+       [ ${zone_was_enabled} -eq ${EXIT_TRUE} ] && zone_enable "${name}"
+
+       # Start the zone if it was up before
+       [ ${zone_was_active} -eq ${EXIT_TRUE} ] && zone_start "${name}"
 
-       [ -e "$(zone_dir ${zone})/.remove" ]
+       log INFO "Zone ${zone} was renamed to ${name}"
+       return ${EXIT_OK}
 }
 
-# This function will remove the given zone
-# RIGHT NOW. Use zone_remove to remove it
-# at the next status change.
-function zone_remove_now() {
-       local zone=${1}
-       assert zone_exists ${zone}
 
-       log INFO "Removing zone '${zone}' right now."
+zone_destroy() {
+       local zone="${1}"
+
+       # Cannot delete a zone that does not exist
+       if ! zone_exists "${zone}"; then
+               log ERROR "Zone ${zone} does not exist"
+               return ${EXIT_ERROR}
+       fi
+
+       log INFO "Destroying zone ${zone}"
 
        # Force the zone down.
-       zone_is_up ${zone} && zone_set_down ${zone}
+       zone_is_active "${zone}" && zone_stop "${zone}"
 
-       # Disable zone.
+       # Disable zone auto-start
        zone_disable "${zone}"
 
-       rm -rf $(zone_dir ${zone})
+       rm -rf "$(zone_dir "${zone}")"
 }
 
-function zone_up() {
+zone_up() {
        local zone=${1}
        shift
 
@@ -257,14 +347,7 @@ function zone_up() {
                return ${EXIT_ERROR}
        fi
 
-       # Check if a zone has got the remove tag.
-       if zone_has_remove_tag ${zone}; then
-               error "Cannot bring up any zone which is to be removed."
-               return ${EXIT_ERROR}
-       fi
-
-       local hook=$(config_get_hook $(zone_dir ${zone})/settings)
-
+       local hook="$(zone_get_hook "${zone}")"
        if [ -z "${hook}" ]; then
                error "Config file did not provide any hook."
                return ${EXIT_ERROR}
@@ -280,9 +363,12 @@ function zone_up() {
        hook_zone_exec ${hook} up ${zone} $@
 
        zone_db ${zone} started
+
+       # Execute all triggers after the zone got up
+       triggers_execute_all "up" ZONE="${zone}"
 }
 
-function zone_down() {
+zone_down() {
        local zone=${1}
        shift
 
@@ -291,8 +377,7 @@ function zone_down() {
                return ${EXIT_ERROR}
        fi
 
-       local hook=$(config_get_hook $(zone_dir ${zone})/settings)
-
+       local hook="$(zone_get_hook "${zone}")"
        if [ -z "${hook}" ]; then
                error "Config file did not provide any hook."
                return ${EXIT_ERROR}
@@ -309,117 +394,64 @@ function zone_down() {
 
        zone_db ${zone} stopped
 
-       # Remove the zone, if it has got a remove tag.
-       if zone_has_remove_tag ${zone}; then
-               zone_remove_now ${zone}
-       fi
+       # Execute all triggers after the zone went down
+       triggers_execute_all "down" ZONE="${zone}"
 }
 
-function zone_status() {
-       local zone=${1}
+zone_status() {
+       local zone="${1}"
+       assert isset zone
        shift
 
-       if ! zone_exists ${zone}; then
+       if ! zone_exists "${zone}"; then
                error "Zone '${zone}' does not exist."
                return ${EXIT_ERROR}
        fi
 
-       local hook=$(config_get_hook $(zone_dir ${zone})/settings)
-
+       local hook="$(zone_get_hook "${zone}")"
        if [ -z "${hook}" ]; then
                error "Config file did not provide any hook."
                return ${EXIT_ERROR}
        fi
 
-       if ! hook_zone_exists ${hook}; then
+       if ! hook_zone_exists "${hook}"; then
                error "Hook '${hook}' does not exist."
                return ${EXIT_ERROR}
        fi
 
-       hook_zone_exec ${hook} status ${zone} $@
-
-       # Show that the zone it to be removed soon.
-       if zone_has_remove_tag ${zone}; then
-               warning "This zone is tagged for removal."
-       fi
+       hook_zone_exec "${hook}" "status" "${zone}" "$@"
 }
 
-function zone_port() {
-       local zone=${1}
-       local action=${2}
-       shift 2
+zone_identify() {
+       assert [ $# -ge 1 ]
 
-       assert isset zone
-       assert isset action
-       assert zone_exists ${zone}
-
-       # Aliases
-       case "${action}" in
-               del|delete|remove)
-                       action="rem"
-                       ;;
-       esac
-
-       case "${action}" in
-               add|edit|rem)
-                       zone_port_${action} ${zone} $@
-                       ;;
-               *)
-                       error "Unrecognized argument: ${action}"
-                       cli_usage root-zone-port-subcommands
-                       exit ${EXIT_ERROR}
-                       ;;              
-       esac
-}
-
-function zone_port_add() {
-       local zone=${1}
+       local zone="${1}"
        shift
 
-       assert isset zone
-
-       local hook=$(zone_get_hook ${zone})
-
-       assert isset hook
-
-       hook_zone_exec ${hook} port_add ${zone} $@
-}
-
-function zone_port_edit() {
-       zone_port_cmd edit $@
-}
-
-function zone_port_rem() {
-       zone_port_cmd rem $@
-}
-
-function zone_port_cmd() {
-       local cmd=${1}
-       local zone=${2}
-       local port=${3}
-       shift 3
-
-       assert isset zone
-       assert isset port
+       assert zone_exists "${zone}"
 
-       local hook_zone=$(zone_get_hook ${zone})
-       local hook_port=$(port_get_hook ${port})
+       log INFO "Identifying zone ${zone}"
+       local pids
 
-       assert isset hook_zone
-       assert isset hook_port
+       local pid
+       local port
+       for port in $(zone_get_ports "${zone}"); do
+               # Identify all the ports
+               port_identify "${port}" --background $@
 
-       hook_zone_port_exec ${hook_zone} ${hook_port} ${cmd} ${zone} ${port} $@
-}
+               # Save the PIDs of the subprocesses
+               list_append pids "$(cmd_background_get_pid)"
+       done
 
-function zone_port_up() {
-       zone_port_cmd up $@
-}
+       # Wait until all port_identfy processes have finished
+       for pid in ${pids}; do
+               cmd_background_result "${pid}"
+       done
 
-function zone_port_down() {
-       zone_port_cmd down $@
+       return ${EXIT_OK}
 }
 
-function zone_get_ports() {
+zone_get_ports() {
        local zone=${1}
 
        assert isset zone
@@ -434,7 +466,25 @@ function zone_get_ports() {
        done
 }
 
-function zone_has_port() {
+zone_get_ports_num() {
+       local zone="${1}"
+       assert isset zone
+
+       local counter=0
+       local port
+       for port in $(zone_dir "${zone}")/ports/*; do
+               port="$(basename "${port}")"
+
+               if port_exists "${port}"; then
+                       counter=$(( ${counter} + 1 ))
+               fi
+       done
+
+       echo "${counter}"
+       return ${EXIT_OK}
+}
+
+zone_has_port() {
        # Check, if the given port is configured
        # in this zone.
 
@@ -448,91 +498,214 @@ function zone_has_port() {
        [ -e "$(zone_dir ${zone})/ports/${port}" ]
 }
 
-# XXX overwritten some lines below
-function zone_config() {
-       local zone=${1}
-       shift
+zone_config() {
+       local zone="${1}"
+       local cmd="${2}"
+       shift 2
 
-       if ! zone_exists ${zone}; then
-               error "Zone '${zone}' does not exist."
-               return ${EXIT_ERROR}
-       fi
+       assert isset zone
+       assert isset cmd
+       assert zone_exists "${zone}"
 
-       local hook=$(config_get_hook $(zone_dir ${zone})/settings)
+       case "${cmd}" in
+               new)
+                       zone_config_new "${zone}" "$@"
+                       ;;
+               destroy)
+                       # usually ${1} is a valid hid
+                       local hid=${1}
+                       shift 1
+
+                       # We convert the hid into an id
+                       local id=$(zone_config_convert_hid_to_id ${zone} ${hid})
+
+                       # If id isset the hid is valid and we can go on with the id
+                       if isset id; then
+                                zone_config_destroy "${zone}" "${id}" "$@"
+
+                       # If we can't get a valid hid we check if we got a valid id
+                       else
+                               if zone_config_id_is_valid ${zone} ${hid}; then
+                                       zone_config_destroy "${zone}" ${hid} "$@"
+                               else
+                                       log ERROR "${id} is not a valid id or hid"
+                               fi
+                       fi
+                       ;;
+               list)
+                       zone_config_list "${zone}" "$@"
+                       ;;
+               *)
+                       # usually ${1} is a valid hid
+                       local hid=${cmd}
+                       local cmd=${1}
+                       shift 1
+
+                       local id=$(zone_config_convert_hid_to_id ${zone} ${hid})
+
+                       # If id isset the hid is valid and we can go on with the id
+                       if isset id && [[ ${cmd} == "edit" ]]; then
+                                zone_config_edit "${zone}" "${id}" "$@"
+
+                       # If we didn't get a valid hid we check if we got a valid id
+                       else
+                               if zone_config_id_is_valid ${zone} ${id} && [[ ${cmd} == "edit" ]]; then
+                                       shift 1
+                                       zone_config_edit "${zone}" "${id}" "$@"
+                               else
+                                       # in ${hid} is saved the command after network zone ${zone} config
+                                       error "Unrecognized argument: ${hid}"
+                                       cli_usage root-zone-config-subcommands
+                                       exit ${EXIT_ERROR}
+                               fi
+                       fi
+                       ;;
+       esac
+}
 
-       if [ -z "${hook}" ]; then
-               error "Config file did not provide any hook."
-               return ${EXIT_ERROR}
-       fi
+zone_config_cmd() {
+       assert [ $# -gt 2 ]
 
-       if ! hook_zone_exists ${hook}; then
-               error "Hook '${hook}' does not exist."
-               return ${EXIT_ERROR}
+       local cmd="${1}"
+       local zone="${2}"
+       shift 2
+
+       local hook="$(zone_get_hook "${zone}")"
+       assert isset hook
+
+       hook_zone_exec "${hook}" "config_${cmd}" "${zone}" "$@"
+}
+
+zone_config_new() {
+       local zone="${1}"
+       shift
+
+       # Create a new configuration, but exit when that was
+       # not successful.
+       zone_config_cmd "new" "${zone}" "$@" || return ${?}
+
+       # If the config could be created, we will try to bring
+       # it up if the zone is up, too.
+       if zone_is_up "${zone}"; then
+               zone_configs_up "${zone}"
        fi
+}
 
-       hook_zone_exec ${hook} config ${zone} $@
+zone_config_destroy() {
+       zone_config_cmd "destroy" "$@"
 }
 
-function zone_config() {
-       local zone=${1}
-       local action=${2}
-       shift 2
+zone_config_edit() {
+       zone_config_cmd "edit" "$@"
+}
 
+zone_config_list() {
+       # This function list in an nice way all configs of a zone
+       local zone=${1}
        assert isset zone
-       assert isset action
-       assert zone_exists ${zone}
 
-       # Aliases
-       case "${action}" in
-               del|delete|remove)
-                       action="rem"
-                       ;;
-       esac
+       # Print a nice header
+       local format="%-3s %-20s %-20s"
+       print "${format}" "ID" "HOOK" "HID"
 
-       case "${action}" in
-               create|edit|rem)
-                       zone_config_${action} ${zone} $@
-                       ;;
-               *)
-                       error "Unrecognized argument: ${action}"
-                       cli_usage root-zone-config-subcommands
-                       exit ${EXIT_ERROR}
-                       ;;
-       esac
+       local config
+       local hook
+       local id
+       local hid
+
+       # Print for all config:
+       # id and hook
+       for config in $(zone_configs_list "${zone}"); do
+               id=${config##*.}
+               hook=$(zone_config_get_hook "${zone}" "${config}")
+               hid=$(zone_config_get_hid "${zone}" "${config}")
+               assert isset hook
+               print "${format}" "${id}" "${hook}" "${hid}"
+       done
 }
 
-function zone_config_option() {
+zone_config_show() {
+       zone_config_cmd "show" "$@"
+}
+
+# Returns a list of all used ids for a zone
+zone_config_list_ids() {
+       assert [ $# -eq 1 ]
+
        local zone=${1}
-       local option=${2}
-       local default=${3}
-       shift 2
+       local config
+       local ids
 
-       assert isset zone
-       assert isset option
+       for config in $(zone_configs_list ${zone}); do
+               list_append ids "$(config_get_id_from_config ${config})"
+       done
 
-       (
-               VALUE="${default}"
-               zone_config_read ${zone}
+       echo ${ids}
+}
 
-               VALUE="${!option}"
-               echo "${VALUE}"
-       )
+# List all hids of a zone
+zone_config_list_hids() {
+       assert [ $# -eq 1 ]
+
+       local zone=${1}
+
+       local config
+       for config in $(zone_configs_list ${zone}); do
+               zone_config_get_hid "${zone}" "${config}"
+       done
 }
 
-function zone_config_create() {
+# get the hid from a given config
+zone_config_get_hid() {
+       assert [ $# -eq 2 ]
+
        local zone=${1}
-       shift
+       local config=${2}
 
-       assert isset zone
+       local hook="$(zone_config_get_hook "${zone}" "${config}")"
 
-       local hook=$(zone_get_hook ${zone})
+       hook_exec "config" "${hook}" "hid" "${zone}" "${config}"
+}
 
-       assert isset hook
+# Checks if a hid is valid for a given zone
+zone_config_hid_is_valid() {
+       assert [ $# -eq 2]
+
+       local zone=${1}
+       local hid=${2}
+
+       local _hid
+       for _hid in $(zone_config_list_hids "${zone}"); do
+               if [[ ${_hid} = ${hid} ]]; then
+                       return ${EXIT_TRUE}
+               fi
+       done
 
-       hook_zone_exec ${hook} config_create ${zone} $@
+       return ${EXIT_FALSE}
 }
 
-function zone_show() {
+# This function converts a hid to a id
+zone_config_convert_hid_to_id() {
+       assert [ $# -eq 2 ]
+
+       local zone=${1}
+       local hid=${2}
+
+       local config
+       for config in $(zone_configs_list ${zone}); do
+               # Get hook from config
+               local hook="$(zone_config_get_hook "${zone}" "${config}")"
+
+               if [[ "$(hook_exec "config" "${hook}" "hid" "${zone}" "${config}")" == "${hid}" ]]; then
+                       config_get_id_from_config "${config}"
+                       return ${EXIT_TRUE}
+               fi
+       done
+
+       return ${EXIT_FALSE}
+}
+
+zone_show() {
        local zone=${1}
 
        echo "${zone}"
@@ -540,7 +713,7 @@ function zone_show() {
        echo
 }
 
-function zones_show() {
+zones_show() {
        local zone
 
        for zone in $(zones_get $@); do
@@ -548,7 +721,7 @@ function zones_show() {
        done
 }
 
-function zones_get_all() {
+zones_get_all() {
        local zone
        for zone in $(zone_dir)/*; do
                zone=$(basename ${zone})
@@ -558,21 +731,41 @@ function zones_get_all() {
        done
 }
 
-function zones_get_local() {
+zones_get_next_free() {
+       # This function return the next free zones.
+       # Example net0 upl0 upl1 are configured so the next free zones are:
+       # net1 upl2
+       local i
+       local zone_name
+       for zone_name in ${VALID_ZONES}; do
+               i=0
+
+               while true; do
+                       local zone="${zone_name}${i}"
+                       if ! zone_exists ${zone}; then
+                               echo "${zone}"
+                               break
+                       fi
+                       i=$(( i + 1 ))
+               done
+       done
+}
+
+zones_get_local() {
        local zone
        for zone in $(zones_get_all); do
                zone_is_local ${zone} && echo "${zone}"
        done
 }
 
-function zones_get_nonlocal() {
+zones_get_nonlocal() {
        local zone
        for zone in $(zones_get_all); do
                zone_is_nonlocal ${zone} && echo "${zone}"
        done
 }
 
-function zones_get() {
+zones_get() {
        local local=1
        local remote=1
 
@@ -620,7 +813,7 @@ function zones_get() {
        fi
 }
 
-function zone_ports_list() {
+zone_ports_list() {
        local zone=${1}
 
        local port
@@ -631,37 +824,266 @@ function zone_ports_list() {
        done
 }
 
-function zone_ports_cmd() {
-       local cmd=${1}
-       local zone=${2}
+zone_port_attach() {
+       local zone="${1}"
+       assert isset zone
+
+       local port="${2}"
+       assert isset port
+
        shift 2
 
+       # Check if the port actually exists.
+       if ! port_exists "${port}"; then
+               error "Cannot attach port '${port}' which does not exist"
+               return ${EXIT_ERROR}
+       fi
+
+       # Check if the port is already connected to this or any other zone.
+       local z
+       for z in $(zones_get_all); do
+               if zone_has_port "${z}" "${port}"; then
+                       error "Port '${port}' is already attached to zone '${z}'"
+                       return ${EXIT_ERROR}
+               fi
+       done
+
+       local hook="$(zone_get_hook "${zone}")"
+       assert isset hook
+
+       # Make the port briefly flash if supported
+       if device_exists ${port}; then
+               port_identify "${port}" --background
+       fi
+
+       hook_zone_exec "${hook}" "port_attach" "${zone}" "${port}" "$@"
+       local ret="${?}"
+
+       case "${ret}" in
+               ${EXIT_OK})
+                       log INFO "${port} has been attached to ${zone}"
+
+                       # Automatically connect the port
+                       zone_port_start "${zone}" "${port}"
+                       ;;
+               *)
+                       log CRITICAL "${port} could not be attached to ${zone}"
+                       ;;
+       esac
+
+       return ${ret}
+}
+
+zone_port_edit() {
+       local zone="${1}"
+       assert isset zone
+
+       local port="${2}"
+       assert isset port
+
+       shift 2
+
+       # Check if the port actually exists.
+       if ! port_exists "${port}"; then
+               error "Port '${port}' does not exist"
+               return ${EXIT_ERROR}
+       fi
+
+       # Check if the zone actually has this port.
+       if ! zone_has_port "${zone}" "${port}"; then
+               error "Port '${port}' is not attached to zone '${zone}'"
+               return ${EXIT_ERROR}
+       fi
+
+       local hook=$(zone_get_hook "${zone}")
+       assert isset hook
+
+       hook_zone_exec "${hook}" "port_edit" "${zone}" "${port}" "$@"
+}
+
+zone_port_detach() {
+       local zone="${1}"
+       assert isset zone
+
+       local port="${2}"
+       assert isset port
+
+       shift 2
+
+       # Check if the zone actually has this port.
+       if ! zone_has_port "${zone}" "${port}"; then
+               error "Port '${port}' is not attached to zone '${zone}'"
+               return ${EXIT_ERROR}
+       fi
+
+       local hook=$(zone_get_hook "${zone}")
+       assert isset hook
+
+       # Make the port briefly flash if supported
+       port_identify "${port}" --background
+
+       hook_zone_exec "${hook}" "port_detach" "${zone}" "${port}" "$@"
+       local ret="${?}"
+
+       case "${ret}" in
+               ${EXIT_OK})
+                       log INFO "${port} has been detached from ${zone}"
+
+                       # Bring down the port if needed
+                       zone_port_stop "${zone}" "${port}"
+                       ;;
+               *)
+                       log CRITICAL "${port} could not be detached from ${zone}"
+                       ;;
+       esac
+
+       return ${ret}
+}
+
+zone_port_cmd() {
+       local cmd="${1}"
        assert isset cmd
+
+       local zone="${2}"
        assert isset zone
 
-       assert zone_exists ${zone}
+       local port="${3}"
+       assert isset port
 
-       local hook=$(zone_get_hook ${zone})
+       shift 3
+
+       local hook="$(zone_get_hook "${zone}")"
+       assert isset hook
+
+       # Dispatch command to hook
+       hook_zone_exec "${hook}" "${cmd}" "${zone}" "${port}" $@
+}
+
+zone_port_create() {
+       zone_port_cmd "port_create" $@
+}
+
+zone_port_remove() {
+       zone_port_cmd "port_remove" $@
+}
+
+zone_port_up() {
+       zone_port_cmd "port_up" $@
+}
+
+zone_port_down() {
+       zone_port_cmd "port_down" $@
+}
+
+# The next two functions automagically bring up and down
+# port that are attached to a bridge or similar.
+# The problem that is tried to overcome here is that there
+# are ports which exist all the time (like ethernet ports)
+# and therefore do not dispatch a hotplug event when
+# port_create is called.
+
+zone_port_start() {
+       local zone="${1}"
+       local port="${2}"
+
+       if zone_is_active "${zone}"; then
+               if device_exists "${port}"; then
+                       zone_port_up "${zone}" "${port}"
+                       return ${?}
+               else
+                       zone_port_create "${zone}" "${port}"
+                       return ${?}
+               fi
+       fi
+
+       return ${EXIT_OK}
+}
+
+zone_port_stop() {
+       local zone="${1}"
+       local port="${2}"
+
+       # Shut down the port if necessary
+       if zone_is_active "${zone}" && port_is_up "${port}"; then
+               zone_port_down "${zone}" "${port}"
+       fi
+
+       # Remove the port
+       zone_port_remove "${zone}" "${port}"
+}
+
+zone_port_status() {
+       zone_port_cmd "port_status" $@
+}
+
+zone_ports_cmd() {
+       local cmd="${1}"
+       assert isset cmd
+
+       local zone="${2}"
+       assert isset zone
+
+       shift 2
+
+       local hook="$(zone_get_hook "${zone}")"
 
        local port
        for port in $(zone_get_ports ${zone}); do
-               hook_zone_exec ${hook} ${cmd} ${zone} ${port} $@
+               hook_zone_exec "${hook}" "${cmd}" "${zone}" "${port}" $@
+       done
+}
+
+zone_ports_create() {
+       zone_ports_cmd "port_create" $@
+}
+
+zone_ports_remove() {
+       zone_ports_cmd "port_remove" $@
+}
+
+zone_ports_up() {
+       zone_ports_cmd "port_up" $@
+}
+
+zone_ports_down() {
+       zone_ports_cmd "port_down" $@
+}
+
+zone_ports_status() {
+       zone_ports_cmd "port_status" $@
+}
+
+zone_configs_cmd() {
+       assert [ $# -ge 2 ]
+
+       local cmd="${1}"
+       local zone="${2}"
+       shift 2
+
+       assert zone_exists "${zone}"
+
+       local config
+       for config in $(zone_configs_list "${zone}"); do
+               local config_hook="$(zone_config_get_hook "${zone}" "${config}")"
+               assert isset config_hook
+
+               hook_config_exec "${config_hook}" "${cmd}" "${zone}" "${config}" $@
        done
 }
 
-function zone_ports_up() {
-       zone_ports_cmd port_up $@
+zone_configs_up() {
+       zone_configs_cmd "up" $@
 }
 
-function zone_ports_down() {
-       zone_ports_cmd port_down $@
+zone_configs_down() {
+       zone_configs_cmd "down" $@
 }
 
-function zone_ports_status() {
-       zone_ports_cmd port_status $@
+zone_configs_status() {
+       zone_configs_cmd "status" $@
 }
 
-function zone_configs_list() {
+zone_configs_list() {
        local zone=${1}
 
        local config
@@ -672,39 +1094,150 @@ function zone_configs_list() {
        done
 }
 
-function zone_configs_cmd() {
-       local cmd=${1}
-       local zone=${2}
-       shift 2
+zone_config_get_new_id() {
+       # This functions returns the next free id for a zone
+
+       assert [ $# -eq 1 ]
+       local zone=${1}
 
-       local hook_zone=$(config_get_hook $(zone_dir ${zone})/settings)
+       local zone_path=$(zone_dir ${zone})
+       local i=0
+
+       while true; do
+               if [ ! -f ${zone_path}/configs/*.${i} ]; then
+                       echo "${i}"
+                       return ${EXIT_OK}
+               fi
+               (( i++ ))
+       done
+}
 
-       local hook_config
+zone_config_check_same_setting() {
+       # This functions checks if a config hook
+       # with the same setting is already configured for this zone.
+       # Returns True when yes and False when no.
+
+       assert [ $# -eq 4 ]
+
+       local zone=${1}
+       local hook=${2}
+       local key=${3}
+       local value=${4}
+
+       # The key should be local for this function
+       local ${key}
        local config
+
        for config in $(zone_configs_list ${zone}); do
-               hook_config=$(config_get_hook $(zone_dir ${zone})/configs/${config})
+               # Check if the config is from the given hook, when not continue
+               if  [[ $(zone_config_get_hook "${zone}" "${config}") != ${hook} ]]; then
+                       continue
+               fi
+               # Get the value of the key for a given function
+               zone_config_settings_read "${zone}" "${config}" \
+                --ignore-superfluous-settings "${key}"
+               # Check if the value of the config and the passed value are eqal
+               if [[ "${value}" == "${!key}" ]]; then
+                       return ${EXIT_TRUE}
+               fi
+       done
+
+       return ${EXIT_FALSE}
+}
+
+zone_config_get_hook() {
+       assert [ $# -eq 2 ]
+
+       local zone="${1}"
+       assert isset zone
+
+       local config="${2}"
+       assert isset config
+
+       local HOOK
+       zone_config_settings_read "${zone}" "${config}" \
+               --ignore-superfluous-settings HOOK
+
+       print "${HOOK}"
+}
+
+zone_config_hook_is_configured() {
+       # Checks if a zone has already at least one config with the given hook.
+       # Returns True when yes and False when no
+
+       assert [ $# -eq 2 ]
+       local zone=${1}
+       local hook=${2}
+
+       local config
+       for config in $(zone_configs_list "${zone}"); do
+               local config_hook="$(zone_config_get_hook "${zone}" "${config}")"
+               assert isset config_hook
+               if [[ ${hook} == ${config_hook} ]]; then
+                       return ${EXIT_TRUE}
+               fi
 
-               hook_zone_config_exec ${hook_zone} ${hook_config} ${cmd} ${zone} ${config} $@
        done
+
+       # If we get here the zone has no config with the given hook
+       return ${EXIT_FALSE}
 }
 
-function zone_configs_up() {
-       zone_configs_cmd up $@
+zone_config_id_is_valid() {
+       # This function checks if a given id is valid for a zone
+       # Return True when yes and false when no
+
+       assert [ $# -eq 2 ]
+       local zone=${1}
+       local id=${2}
+
+       local zone_path=$(zone_dir ${zone})
+
+       [ -f ${zone_path}/configs/*.${id} ];
 }
 
-function zone_configs_down() {
-       zone_configs_cmd down $@
+# This function checks if a given hid is valid for a zone
+# Return True when yes and false when no
+zone_config_hid_is_valid() {
+       assert [ $# -eq 2 ]
+       local zone=${1}
+       local hid=${2}
+
+       local _hid
+       for _hid in $(zone_config_list_hids ${zone}); do
+               if [[ ${_hid} == ${hid} ]]; then
+                       return ${EXIT_TRUE}
+               fi
+       done
+
+       return ${EXIT_FALSE}
 }
 
-function zone_configs_status() {
-       zone_configs_cmd config_status $@
+zone_config_get_hook_from_id() {
+       # Returns the hook for a given id
+       assert [ $# -eq 2 ]
+       local zone=${1}
+       local id=${2}
+
+       local config
+       for config in $(zone_configs_list "${zone}"); do
+               if [[ ${config} == *.${id} ]]; then
+                       local config_hook="$(zone_config_get_hook "${zone}" "${config}")"
+                       assert isset config_hook
+                       print "${config_hook}"
+                       return "${EXIT_OK}"
+               fi
+       done
+
+       # If we get here the zone has no config with the given id
+       return ${EXIT_ERROR}
 }
 
-function zone_has_ip() {
+zone_has_ip() {
        device_has_ip $@
 }
 
-function zone_db() {
+zone_db() {
        local zone=${1}
        local action=${2}
        shift 2
@@ -716,17 +1249,17 @@ function zone_db() {
        esac
 }
 
-function zone_is_up() {
+zone_is_up() {
        local zone=${1}
 
        device_is_up ${zone}
 }
 
-function zone_is_down() {
+zone_is_down() {
        ! zone_is_up $@
 }
 
-function zone_get_supported_port_hooks() {
+zone_get_supported_port_hooks() {
        local zone=${1}
 
        local hook=$(zone_get_hook ${zone})
@@ -734,15 +1267,11 @@ function zone_get_supported_port_hooks() {
        hook_zone_ports_get_all ${hook}
 }
 
-function zone_get_supported_config_hooks() {
-       local zone=${1}
-
-       local hook=$(zone_get_hook ${zone})
-
-       hook_zone_configs_get_all ${hook}
+zone_get_supported_config_hooks() {
+       hook_config_get_all
 }
 
-function zone_file() {
+zone_file() {
        local zone=${1}
 
        assert isset zone
@@ -750,29 +1279,41 @@ function zone_file() {
        echo "$(zone_dir ${zone})/settings"
 }
 
-function zone_config_read() {
+zone_settings_read() {
        local zone=${1}
-
        assert isset zone
+       shift
+
+       local args
+       if [ $# -eq 0 ] && [ -n "${HOOK_SETTINGS}" ]; then
+               list_append args ${HOOK_SETTINGS}
+       else
+               list_append args $@
+       fi
 
        # Save the HOOK variable.
        local hook="${HOOK}"
 
-       config_read $(zone_file ${zone})
+       settings_read "$(zone_file "${zone}")" ${args}
 
        # Restore hook.
        HOOK="${hook}"
 }
 
-function zone_config_write() {
-       local zone=${1}
-
+zone_settings_write() {
+       local zone="${1}"
        assert isset zone
 
-       config_write $(zone_file ${zone}) ${HOOK_SETTINGS}
+       local args
+       if function_exists "hook_check_settings"; then
+               list_append args "--check=\"hook_check_settings\""
+       fi
+       list_append args ${HOOK_SETTINGS}
+
+       settings_write "$(zone_file ${zone})" ${args}
 }
 
-function zone_config_set() {
+zone_settings_set() {
        local zone=${1}
        shift
        local args="$@"
@@ -780,17 +1321,17 @@ function zone_config_set() {
        assert isset zone
 
        (
-               zone_config_read ${zone}
+               zone_settings_read ${zone}
 
                for arg in ${args}; do
                        eval "${arg}"
                done
        
-               zone_config_write ${zone}
+               zone_settings_write ${zone}
        )
 }
 
-function zone_config_get() {
+zone_settings_get() {
        local zone=${1}
        local key=${2}
 
@@ -798,8 +1339,127 @@ function zone_config_get() {
        assert isset key
 
        (
-               zone_config_read ${zone}
+               zone_settings_read "${zone}" "${key}" \
+                       --ignore-superfluous-settings
 
                echo "${!key}"
        )
 }
+
+zone_config_settings_read() {
+       assert [ $# -ge 2 ]
+
+       local zone="${1}"
+       local config="${2}"
+       shift 2
+
+       local args
+       if [ $# -eq 0 ] && [ -n "${HOOK_CONFIG_SETTINGS}" ]; then
+               list_append args ${HOOK_CONFIG_SETTINGS}
+       else
+               list_append args $@
+       fi
+
+       local path="$(zone_dir "${zone}")/configs/${config}"
+       settings_read "${path}" ${args}
+}
+
+zone_config_settings_write() {
+       assert [ $# -ge 2 ]
+
+       local zone="${1}"
+       local hook="${2}"
+       local id=${3}
+
+       if ! isset id; then
+               id=$(zone_config_get_new_id ${zone})
+               log DEBUG "ID for the config is: ${id}"
+       fi
+
+       local args
+       if function_exists "hook_check_config_settings"; then
+               list_append args "--check=\"hook_check_config_settings\""
+       fi
+       list_append args ${HOOK_CONFIG_SETTINGS}
+
+       local path="$(zone_dir "${zone}")/configs/${hook}.${id}"
+       settings_write "${path}" ${args}
+}
+
+zone_config_settings_destroy() {
+       # This function deletes the config file for a given zone and config
+       assert [ $# -ge 2 ]
+       local zone="${1}"
+       local config="${2}"
+
+       local path="$(zone_dir "${zone}")/configs/${config}"
+
+       # Check if path is valid
+       if [ ! -f ${path} ]; then
+               log ERROR "Path: '${path}' is not valid"
+               return ${EXIT_ERROR}
+       fi
+
+       log DEBUG "Deleting config file ${path}"
+       rm -f "${path}"
+
+}
+zone_port_settings_read() {
+       assert [ $# -ge 2 ]
+
+       local zone="${1}"
+       local port="${2}"
+       shift 2
+
+       local args
+       if [ $# -eq 0 ] && [ -n "${HOOK_PORT_SETTINGS}" ]; then
+               list_append args ${HOOK_PORT_SETTINGS}
+       else
+               list_append args $@
+       fi
+
+       local path="$(zone_dir "${zone}")/ports/${port}"
+       settings_read "${path}" ${args}
+}
+
+zone_port_settings_write() {
+       assert [ $# -ge 2 ]
+
+       local zone="${1}"
+       local port="${2}"
+       shift 2
+
+       local args
+       if function_exists "hook_check_port_settings"; then
+               list_append args "--check=\"hook_check_port_settings\""
+       fi
+       list_append args ${HOOK_PORT_SETTINGS}
+
+       local path="$(zone_dir "${zone}")/ports/${port}"
+       settings_write "${path}" ${args}
+}
+
+zone_port_settings_remove() {
+       assert [ $# -eq 2 ]
+
+       local zone="${1}"
+       local port="${2}"
+
+       local path="$(zone_dir "${zone}")/ports/${port}"
+       settings_remove "${path}"
+}
+
+zone_get_color() {
+       # This function return the color of a zone
+       assert [ $# -eq 1 ]
+
+       local name=${1}
+       color_read "zone" ${name}
+}
+
+zone_get_description_title() {
+       assert [ $# -eq 1 ]
+
+       local name=${1}
+       description_title_read $(description_format_filename "zone" "${name}")
+}