コード例 #1
0
ファイル: nmp-netns.c プロジェクト: lkundrak/NetworkManager
static gboolean
_netns_switch_push (NMPNetns *self, int ns_types)
{
	int errsv;

	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
	    && !_stack_current_ns_types (self, CLONE_NEWNET)
	    && _setns (self, CLONE_NEWNET) != 0) {
		errsv = errno;
		_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
		return FALSE;
	}
	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
	    && !_stack_current_ns_types (self, CLONE_NEWNS)
	    && _setns (self, CLONE_NEWNS) != 0) {
		errsv = errno;
		_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));

		/* try to fix the mess by returning to the previous netns. */
		if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
	        && !_stack_current_ns_types (self, CLONE_NEWNET)) {
			self = _stack_current_netns (CLONE_NEWNET);
			if (   self
			    && _setns (self, CLONE_NEWNET) != 0) {
				errsv = errno;
				_LOGE (self, "failed to restore netns: %s", g_strerror (errsv));
			}
		}
		return FALSE;
	}

	return TRUE;
}
コード例 #2
0
ファイル: nmp-netns.c プロジェクト: lkundrak/NetworkManager
static gboolean
_netns_switch_pop (NMPNetns *self, int ns_types)
{
	int errsv;
	NMPNetns *current;
	int success = TRUE;

	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNET))) {
		current = _stack_current_netns (CLONE_NEWNET);
		if (!current) {
			g_warn_if_reached ();
			success = FALSE;
		} else if (_setns (current, CLONE_NEWNET) != 0) {
			errsv = errno;
			_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
			success = FALSE;
		}
	}
	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNS))) {
		current = _stack_current_netns (CLONE_NEWNS);
		if (!current) {
			g_warn_if_reached ();
			success = FALSE;
		} else if (_setns (current, CLONE_NEWNS) != 0) {
			errsv = errno;
			_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
			success = FALSE;
		}
	}

	return success;
}
コード例 #3
0
ファイル: nmp-netns.c プロジェクト: lkundrak/NetworkManager
static int
_stack_current_ns_types (NMPNetns *netns, int ns_types)
{
	const int ns_types_check[] = { _CLONE_NS_ALL_V };
	guint i, j;
	int res = 0;

	nm_assert (netns);
	nm_assert (netns_stack && netns_stack->len > 0);

	/* we search the stack top-down to check which of @ns_types
	 * are already set to @netns. */
	for (j = netns_stack->len; ns_types && j >= 1; ) {
		NetnsInfo *info;

		info = &g_array_index (netns_stack, NetnsInfo, --j);
		if (info->netns != netns) {
			ns_types = NM_FLAGS_UNSET (ns_types, info->ns_types);
			continue;
		}

		for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) {
			if (   NM_FLAGS_HAS (ns_types, ns_types_check[i])
			    && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) {
				res = NM_FLAGS_SET (res, ns_types_check[i]);
				ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]);
			}
		}
	}

	return res;
}
コード例 #4
0
ファイル: main.c プロジェクト: thom311/NetworkManager
static void
_init_nm_debug (const char *debug)
{
	const guint D_RLIMIT_CORE = 1;
	const guint D_FATAL_WARNINGS = 2;
	GDebugKey keys[] = {
		{ "RLIMIT_CORE", D_RLIMIT_CORE },
		{ "fatal-warnings", D_FATAL_WARNINGS },
	};
	guint flags = 0;
	const char *env = getenv ("NM_DEBUG");

	if (env && strcasecmp (env, "help") != 0) {
		/* g_parse_debug_string() prints options to stderr if the variable
		 * is set to "help". Don't allow that. */
		flags = g_parse_debug_string (env,  keys, G_N_ELEMENTS (keys));
	}

	if (debug && strcasecmp (debug, "help") != 0)
		flags |= g_parse_debug_string (debug,  keys, G_N_ELEMENTS (keys));

	if (NM_FLAGS_HAS (flags, D_RLIMIT_CORE)) {
		/* only enable this, if explicitly requested, because it might
		 * expose sensitive data. */

		struct rlimit limit = {
			.rlim_cur = RLIM_INFINITY,
			.rlim_max = RLIM_INFINITY,
		};
		setrlimit (RLIMIT_CORE, &limit);
	}
	if (NM_FLAGS_HAS (flags, D_FATAL_WARNINGS))
		_set_g_fatal_warnings ();
}
コード例 #5
0
ファイル: main.c プロジェクト: GalliumOS/network-manager
static void
_init_nm_debug (const char *debug)
{
    const guint D_RLIMIT_CORE = 1;
    const guint D_FATAL_WARNINGS = 2;
    GDebugKey keys[] = {
        { "RLIMIT_CORE", D_RLIMIT_CORE },
        { "fatal-warnings", D_FATAL_WARNINGS },
    };
    guint flags;
    const char *env = getenv ("NM_DEBUG");

    flags  = nm_utils_parse_debug_string (env, keys, G_N_ELEMENTS (keys));
    flags |= nm_utils_parse_debug_string (debug, keys, G_N_ELEMENTS (keys));

    if (NM_FLAGS_HAS (flags, D_RLIMIT_CORE)) {
        /* only enable this, if explicitly requested, because it might
         * expose sensitive data. */

        struct rlimit limit = {
            .rlim_cur = RLIM_INFINITY,
            .rlim_max = RLIM_INFINITY,
        };
        setrlimit (RLIMIT_CORE, &limit);
    }
    if (NM_FLAGS_HAS (flags, D_FATAL_WARNINGS))
        _set_g_fatal_warnings ();
}
コード例 #6
0
static void
config_changed_cb (NMConfig *config,
                   NMConfigData *config_data,
                   NMConfigChangeFlags changes,
                   NMConfigData *old_data,
                   NMAuditManager *self)
{
	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_VALUES))
		init_auditd (self);
}
コード例 #7
0
gboolean
_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
{
	guint i;

	for (i = 0; i < G_N_ELEMENTS (bond_unsupp_modes); i++) {
		if (nm_streq (option, bond_unsupp_modes[i].option))
		    return !NM_FLAGS_HAS (bond_unsupp_modes[i].unsupp_modes, BIT (mode));
	}

	return TRUE;
}
コード例 #8
0
ファイル: reader.c プロジェクト: sujithpshankar/nm
NMConnection *
nm_keyfile_plugin_connection_from_file (const char *filename, GError **error)
{
	GKeyFile *key_file;
	struct stat statbuf;
	NMConnection *connection = NULL;
	GError *verify_error = NULL;

	if (stat (filename, &statbuf) != 0 || !S_ISREG (statbuf.st_mode)) {
		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
		                     "File did not exist or was not a regular file");
		return NULL;
	}

	if (statbuf.st_mode & 0077) {
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
		             "File permissions (%o) were insecure",
		             statbuf.st_mode);
		return NULL;
	}

	if (!NM_FLAGS_HAS (nm_utils_get_testing (), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
		if (statbuf.st_uid != 0) {
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
			             "File owner (%o) is insecure",
			             statbuf.st_mode);
			return NULL;
		}
	}

	key_file = g_key_file_new ();
	if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
		goto out;

	connection = nm_keyfile_read (key_file, filename, NULL, _handler_read, NULL, error);
	if (!connection)
		goto out;

	/* Normalize and verify the connection */
	if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) {
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
		             "invalid connection: %s",
		             verify_error->message);
		g_clear_error (&verify_error);
		g_object_unref (connection);
		connection = NULL;
	}

out:
	g_key_file_free (key_file);
	return connection;
}
コード例 #9
0
static char *
build_message (GPtrArray *fields, AuditBackend backend)
{
	GString *string;
	AuditField *field;
	gboolean first = TRUE;
	guint i;

	string = g_string_new (NULL);

	for (i = 0; i < fields->len; i++) {
		field = fields->pdata[i];

		if (!NM_FLAGS_HAS (field->backends, backend))
			continue;

		if (first)
			first = FALSE;
		else
			g_string_append_c (string, ' ');

		if (G_VALUE_HOLDS_STRING (&field->value)) {
			const char *str = g_value_get_string (&field->value);

#if HAVE_LIBAUDIT
			if (backend == BACKEND_AUDITD) {
				if (field->need_encoding) {
					char *value;

					value = audit_encode_nv_string (field->name, str, 0);
					g_string_append (string, value);
					g_free (value);
				} else
					g_string_append_printf (string, "%s=%s", field->name, str);
				continue;
			}
#endif /* HAVE_LIBAUDIT */
			g_string_append_printf (string, "%s=\"%s\"", field->name, str);
		} else if (G_VALUE_HOLDS_UINT (&field->value)) {
			g_string_append_printf (string, "%s=%u", field->name,
			                        g_value_get_uint (&field->value));
		} else
			g_assert_not_reached ();
	}
	return g_string_free (string, FALSE);
}
コード例 #10
0
static gboolean
compare_property (NMSetting *setting,
                  NMSetting *other,
                  const GParamSpec *prop_spec,
                  NMSettingCompareFlags flags)
{
	NMSettingClass *parent_class;

	/* If we are trying to match a connection in order to assume it (and thus
	 * @flags contains INFERRABLE), use the "relaxed" matching for team
	 * configuration. Otherwise, for all other purposes (including connection
	 * comparison before an update), resort to the default string comparison.
	 */
	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
	    && nm_streq0 (prop_spec->name, NM_SETTING_TEAM_PORT_CONFIG)) {
		return _nm_utils_team_config_equal (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->config,
		                                    NM_SETTING_TEAM_PORT_GET_PRIVATE (other)->config,
		                                    TRUE);
	}

	/* Otherwise chain up to parent to handle generic compare */
	parent_class = NM_SETTING_CLASS (nm_setting_team_port_parent_class);
	return parent_class->compare_property (setting, other, prop_spec, flags);
}
コード例 #11
0
gboolean
nmp_utils_ethtool_set_wake_on_lan (const char *ifname,
                                   NMSettingWiredWakeOnLan wol,
                                   const char *wol_password)
{
	struct ethtool_wolinfo wol_info = { };

	if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
		return TRUE;

	nm_log_dbg (LOGD_PLATFORM, "setting Wake-on-LAN options 0x%x, password '%s'",
	            (unsigned int) wol, wol_password);

	wol_info.cmd = ETHTOOL_SWOL;
	wol_info.wolopts = 0;

	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
		wol_info.wolopts |= WAKE_PHY;
	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
		wol_info.wolopts |= WAKE_UCAST;
	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
		wol_info.wolopts |= WAKE_MCAST;
	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
		wol_info.wolopts |= WAKE_BCAST;
	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
		wol_info.wolopts |= WAKE_ARP;
	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
		wol_info.wolopts |= WAKE_MAGIC;

	if (wol_password) {
		if (!nm_utils_hwaddr_aton (wol_password, wol_info.sopass, ETH_ALEN)) {
			nm_log_dbg (LOGD_PLATFORM, "couldn't parse Wake-on-LAN password '%s'", wol_password);
			return FALSE;
		}
		wol_info.wolopts |= WAKE_MAGICSECURE;
	}

	return ethtool_get (ifname, &wol_info);
}
コード例 #12
0
static void
update_properties_from_ifindex (NMDevice *device, int ifindex)
{
	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
	GObject *object = G_OBJECT (device);
	NMDevice *parent;
	int parent_ifindex;
	in_addr_t local4, remote4;
	struct in6_addr local6, remote6;
	guint8 ttl = 0, tos = 0, encap_limit = 0;
	gboolean pmtud = FALSE;
	guint32 flow_label = 0;
	char *key;

	if (ifindex <= 0) {
clear:
		if (priv->parent || priv->parent_ifindex) {
			g_clear_object (&priv->parent);
			priv->parent_ifindex = 0;
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_PARENT);
		}
		if (priv->local) {
			g_clear_pointer (&priv->local, g_free);
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_LOCAL);
		}
		if (priv->remote) {
			g_clear_pointer (&priv->remote, g_free);
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_REMOTE);
		}
		if (priv->input_key) {
			g_clear_pointer (&priv->input_key, g_free);
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_INPUT_KEY);
		}
		if (priv->output_key) {
			g_clear_pointer (&priv->output_key, g_free);
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_OUTPUT_KEY);
		}

		goto out;
	}

	if (priv->mode == NM_IP_TUNNEL_MODE_GRE) {
		const NMPlatformLnkGre *lnk;

		lnk = nm_platform_link_get_lnk_gre (NM_PLATFORM_GET, ifindex, NULL);
		if (!lnk) {
			_LOGW (LOGD_HW, "could not read %s properties", "gre");
			goto clear;
		}

		parent_ifindex = lnk->parent_ifindex;
		local4 = lnk->local;
		remote4 = lnk->remote;
		ttl = lnk->ttl;
		tos = lnk->tos;
		pmtud = lnk->path_mtu_discovery;

		if (NM_FLAGS_HAS (lnk->input_flags, NM_GRE_KEY)) {
			key = g_strdup_printf ("%u", lnk->input_key);
			if (g_strcmp0 (priv->input_key, key)) {
				g_free (priv->input_key);
				priv->input_key = key;
				g_object_notify (object, NM_DEVICE_IP_TUNNEL_INPUT_KEY);
			} else
				g_free (key);
		} else {
			if (priv->input_key) {
				g_clear_pointer (&priv->input_key, g_free);
				g_object_notify (object, NM_DEVICE_IP_TUNNEL_INPUT_KEY);
			}
		}

		if (NM_FLAGS_HAS (lnk->output_flags, NM_GRE_KEY)) {
			key = g_strdup_printf ("%u", lnk->output_key);
			if (g_strcmp0 (priv->output_key, key)) {
				g_free (priv->output_key);
				priv->output_key = key;
				g_object_notify (object, NM_DEVICE_IP_TUNNEL_OUTPUT_KEY);
			} else
				g_free (key);
		} else {
			if (priv->output_key) {
				g_clear_pointer (&priv->output_key, g_free);
				g_object_notify (object, NM_DEVICE_IP_TUNNEL_OUTPUT_KEY);
			}
		}
	} else if (priv->mode == NM_IP_TUNNEL_MODE_SIT) {
		const NMPlatformLnkSit *lnk;

		lnk = nm_platform_link_get_lnk_sit (NM_PLATFORM_GET, ifindex, NULL);
		if (!lnk) {
			_LOGW (LOGD_HW, "could not read %s properties", "sit");
			goto clear;
		}

		parent_ifindex = lnk->parent_ifindex;
		local4 = lnk->local;
		remote4 = lnk->remote;
		ttl = lnk->ttl;
		tos = lnk->tos;
		pmtud = lnk->path_mtu_discovery;
	} else if (priv->mode == NM_IP_TUNNEL_MODE_IPIP) {
		const NMPlatformLnkIpIp *lnk;

		lnk = nm_platform_link_get_lnk_ipip (NM_PLATFORM_GET, ifindex, NULL);
		if (!lnk) {
			_LOGW (LOGD_HW, "could not read %s properties", "ipip");
			goto clear;
		}

		parent_ifindex = lnk->parent_ifindex;
		local4 = lnk->local;
		remote4 = lnk->remote;
		ttl = lnk->ttl;
		tos = lnk->tos;
		pmtud = lnk->path_mtu_discovery;
	} else if (   priv->mode == NM_IP_TUNNEL_MODE_IPIP6
	           || priv->mode == NM_IP_TUNNEL_MODE_IP6IP6) {
		const NMPlatformLnkIp6Tnl *lnk;

		lnk = nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL);
		if (!lnk) {
			_LOGW (LOGD_HW, "could not read %s properties", "ip6tnl");
			goto clear;
		}

		parent_ifindex = lnk->parent_ifindex;
		local6 = lnk->local;
		remote6 = lnk->remote;
		ttl = lnk->ttl;
		tos = lnk->tclass;
		encap_limit = lnk->encap_limit;
		flow_label = lnk->flow_label;
	} else
		g_return_if_reached ();

	if (priv->parent_ifindex != parent_ifindex) {
		g_clear_object (&priv->parent);
		priv->parent_ifindex = parent_ifindex;
		parent = nm_manager_get_device_by_ifindex (nm_manager_get (), parent_ifindex);
		if (parent)
			priv->parent = g_object_ref (parent);
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_PARENT);
	}

	if (priv->addr_family == AF_INET) {
		if (!address_equal_pn (AF_INET, priv->local, &local4)) {
			g_clear_pointer (&priv->local, g_free);
			if (local4)
				priv->local = g_strdup (nm_utils_inet4_ntop (local4, NULL));
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_LOCAL);
		}

		if (!address_equal_pn (AF_INET, priv->remote, &remote4)) {
			g_clear_pointer (&priv->remote, g_free);
			if (remote4)
				priv->remote = g_strdup (nm_utils_inet4_ntop (remote4, NULL));
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_REMOTE);
		}
	} else {
		if (!address_equal_pn (AF_INET6, priv->local, &local6)) {
			g_clear_pointer (&priv->local, g_free);
			if (memcmp (&local6, &in6addr_any, sizeof (in6addr_any)))
				priv->local = g_strdup (nm_utils_inet6_ntop (&local6, NULL));
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_LOCAL);
		}

		if (!address_equal_pn (AF_INET6, priv->remote, &remote6)) {
			g_clear_pointer (&priv->remote, g_free);
			if (memcmp (&remote6, &in6addr_any, sizeof (in6addr_any)))
				priv->remote = g_strdup (nm_utils_inet6_ntop (&remote6, NULL));
			g_object_notify (object, NM_DEVICE_IP_TUNNEL_REMOTE);
		}
	}

out:

	if (priv->ttl != ttl) {
		priv->ttl = ttl;
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_TTL);
	}

	if (priv->tos != tos) {
		priv->tos = tos;
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_TOS);
	}

	if (priv->path_mtu_discovery != pmtud) {
		priv->path_mtu_discovery = pmtud;
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY);
	}

	if (priv->encap_limit != encap_limit) {
		priv->encap_limit = encap_limit;
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT);
	}

	if (priv->flow_label != flow_label) {
		priv->flow_label = flow_label;
		g_object_notify (object, NM_DEVICE_IP_TUNNEL_FLOW_LABEL);
	}
}
コード例 #13
0
NMCheckpoint *
nm_checkpoint_manager_create (NMCheckpointManager *self,
                              const char *const *device_paths,
                              guint32 rollback_timeout,
                              NMCheckpointCreateFlags flags,
                              GError **error)
{
	NMManager *manager;
	NMCheckpoint *checkpoint;
	const char * const *path;
	gs_unref_ptrarray GPtrArray *devices = NULL;
	NMDevice *device;
	const char *checkpoint_path;
	gs_free const char **device_paths_free = NULL;
	guint i;

	g_return_val_if_fail (self, FALSE);
	g_return_val_if_fail (!error || !*error, FALSE);
	manager = GET_MANAGER (self);

	if (!device_paths || !device_paths[0]) {
		device_paths_free = nm_manager_get_device_paths (manager);
		device_paths = (const char *const *) device_paths_free;
	} else if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
		                     "the DISCONNECT_NEW_DEVICES flag can only be used with an empty device list");
		return NULL;
	}

	devices = g_ptr_array_new ();
	for (path = device_paths; *path; path++) {
		device = nm_manager_get_device_by_path (manager, *path);
		if (!device) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			             "device %s does not exist", *path);
			return NULL;
		}
		g_ptr_array_add (devices, device);
	}

	if (!NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL)) {
		for (i = 0; i < devices->len; i++) {
			device = devices->pdata[i];
			if (find_checkpoint_for_device (self, device)) {
				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
				             "a checkpoint for device '%s' already exists",
				             nm_device_get_iface (device));
				return NULL;
			}
		}
	}

	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags, error);
	if (!checkpoint)
		return NULL;

	if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL))
		g_hash_table_remove_all (self->checkpoints);

	nm_exported_object_export (NM_EXPORTED_OBJECT (checkpoint));
	checkpoint_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint));

	if (!nm_g_hash_table_insert (self->checkpoints,
	                             (gpointer) checkpoint_path,
	                             checkpoint))
		g_return_val_if_reached (NULL);

	update_rollback_timeout (self);

	return checkpoint;
}