diff options
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 758 |
1 files changed, 758 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..c2fb58be --- /dev/null +++ b/src/main.c @@ -0,0 +1,758 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2004 - 2010 Red Hat, Inc. + * Copyright (C) 2005 - 2008 Novell, Inc. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <glib.h> +#include <dbus/dbus.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <dbus/dbus-glib.h> +#include <getopt.h> +#include <errno.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <glib/gi18n.h> +#include <gmodule.h> +#include <string.h> + +#include "NetworkManager.h" +#include "NetworkManagerUtils.h" +#include "nm-manager.h" +#include "nm-policy.h" +#include "nm-system.h" +#include "nm-named-manager.h" +#include "nm-dbus-manager.h" +#include "nm-supplicant-manager.h" +#include "nm-dhcp-manager.h" +#include "nm-hostname-provider.h" +#include "nm-netlink-monitor.h" +#include "nm-vpn-manager.h" +#include "nm-logging.h" + +#if !defined(NM_DIST_VERSION) +# define NM_DIST_VERSION VERSION +#endif + +#define NM_DEFAULT_PID_FILE LOCALSTATEDIR"/run/NetworkManager.pid" +#define NM_DEFAULT_SYSTEM_CONF_FILE SYSCONFDIR"/NetworkManager/NetworkManager.conf" +#define NM_OLD_SYSTEM_CONF_FILE SYSCONFDIR"/NetworkManager/nm-system-settings.conf" +#define NM_DEFAULT_SYSTEM_STATE_FILE LOCALSTATEDIR"/lib/NetworkManager/NetworkManager.state" + +/* + * Globals + */ +static NMManager *manager = NULL; +static GMainLoop *main_loop = NULL; + +typedef struct { + time_t time; + GQuark domain; + guint32 code; + guint32 count; +} MonitorInfo; + +static gboolean +detach_monitor (gpointer data) +{ + nm_log_warn (LOGD_HW, "detaching netlink event monitor"); + nm_netlink_monitor_detach (NM_NETLINK_MONITOR (data)); + return FALSE; +} + +static void +nm_error_monitoring_device_link_state (NMNetlinkMonitor *monitor, + GError *error, + gpointer user_data) +{ + MonitorInfo *info = (MonitorInfo *) user_data; + time_t now; + + now = time (NULL); + + if ( (info->domain != error->domain) + || (info->code != error->code) + || (info->time && now > info->time + 10)) { + /* FIXME: Try to handle the error instead of just printing it. */ + nm_log_warn (LOGD_HW, "error monitoring device for netlink events: %s\n", error->message); + + info->time = now; + info->domain = error->domain; + info->code = error->code; + info->count = 0; + } + + info->count++; + if (info->count > 100) { + /* Broken drivers will sometimes cause a flood of netlink errors. + * rh #459205, novell #443429, lp #284507 + */ + nm_log_warn (LOGD_HW, "excessive netlink errors ocurred, disabling netlink monitor."); + nm_log_warn (LOGD_HW, "link change events will not be processed."); + g_idle_add_full (G_PRIORITY_HIGH, detach_monitor, monitor, NULL); + } +} + +static gboolean +nm_monitor_setup (GError **error) +{ + NMNetlinkMonitor *monitor; + MonitorInfo *info; + + monitor = nm_netlink_monitor_get (); + if (!nm_netlink_monitor_open_connection (monitor, error)) { + g_object_unref (monitor); + return FALSE; + } + + info = g_new0 (MonitorInfo, 1); + g_signal_connect_data (G_OBJECT (monitor), "error", + G_CALLBACK (nm_error_monitoring_device_link_state), + info, + (GClosureNotify) g_free, + 0); + nm_netlink_monitor_attach (monitor); + + /* Request initial status of cards */ + nm_netlink_monitor_request_status (monitor, NULL); + + return TRUE; +} + +static gboolean quit_early = FALSE; + +static void +nm_signal_handler (int signo) +{ + static int in_fatal = 0; + + /* avoid loops */ + if (in_fatal > 0) + return; + ++in_fatal; + + switch (signo) + { + case SIGSEGV: + case SIGBUS: + case SIGILL: + case SIGABRT: + nm_log_warn (LOGD_CORE, "caught signal %d. Generating backtrace...", signo); + nm_logging_backtrace (); + exit (1); + break; + + case SIGFPE: + case SIGPIPE: + /* let the fatal signals interrupt us */ + --in_fatal; + + nm_log_warn (LOGD_CORE, "caught signal %d, shutting down abnormally. Generating backtrace...", signo); + nm_logging_backtrace (); + g_main_loop_quit (main_loop); + break; + + case SIGINT: + case SIGTERM: + /* let the fatal signals interrupt us */ + --in_fatal; + + nm_log_info (LOGD_CORE, "caught signal %d, shutting down normally.", signo); + quit_early = TRUE; + g_main_loop_quit (main_loop); + break; + + case SIGHUP: + --in_fatal; + /* FIXME: + * Reread config stuff like system config files, VPN service files, etc + */ + break; + + case SIGUSR1: + --in_fatal; + /* FIXME: + * Play with log levels or something + */ + break; + + default: + signal (signo, nm_signal_handler); + break; + } +} + +static void +setup_signals (void) +{ + struct sigaction action; + sigset_t mask; + + sigemptyset (&mask); + action.sa_handler = nm_signal_handler; + action.sa_mask = mask; + action.sa_flags = 0; + sigaction (SIGTERM, &action, NULL); + sigaction (SIGINT, &action, NULL); + sigaction (SIGILL, &action, NULL); + sigaction (SIGBUS, &action, NULL); + sigaction (SIGFPE, &action, NULL); + sigaction (SIGHUP, &action, NULL); + sigaction (SIGSEGV, &action, NULL); + sigaction (SIGABRT, &action, NULL); + sigaction (SIGUSR1, &action, NULL); +} + +static gboolean +write_pidfile (const char *pidfile) +{ + char pid[16]; + int fd; + gboolean success = FALSE; + + if ((fd = open (pidfile, O_CREAT|O_WRONLY|O_TRUNC, 00644)) < 0) { + fprintf (stderr, "Opening %s failed: %s\n", pidfile, strerror (errno)); + return FALSE; + } + + snprintf (pid, sizeof (pid), "%d", getpid ()); + if (write (fd, pid, strlen (pid)) < 0) + fprintf (stderr, "Writing to %s failed: %s\n", pidfile, strerror (errno)); + else + success = TRUE; + + if (close (fd)) + fprintf (stderr, "Closing %s failed: %s\n", pidfile, strerror (errno)); + + return success; +} + +/* Check whether the pidfile already exists and contains PID of a running NetworkManager + * Returns: FALSE - specified pidfile doesn't exist or doesn't contain PID of a running NM process + * TRUE - specified pidfile already exists and contains PID of a running NM process + */ +static gboolean +check_pidfile (const char *pidfile) +{ + char *contents = NULL; + gsize len = 0; + glong pid; + char *proc_cmdline = NULL; + gboolean nm_running = FALSE; + const char *process_name; + + if (!g_file_get_contents (pidfile, &contents, &len, NULL)) + return FALSE; + + if (len <= 0) + goto done; + + errno = 0; + pid = strtol (contents, NULL, 10); + if (pid <= 0 || pid > 65536 || errno) + goto done; + + g_free (contents); + proc_cmdline = g_strdup_printf ("/proc/%ld/cmdline", pid); + if (!g_file_get_contents (proc_cmdline, &contents, &len, NULL)) + goto done; + + process_name = strrchr (contents, '/'); + if (process_name) + process_name++; + else + process_name = contents; + if (strcmp (process_name, "NetworkManager") == 0) { + /* Check that the process exists */ + if (kill (pid, 0) == 0) { + fprintf (stderr, "NetworkManager is already running (pid %ld)\n", pid); + nm_running = TRUE; + } + } + +done: + g_free (proc_cmdline); + g_free (contents); + return nm_running; +} + +static gboolean +parse_config_file (const char *filename, + char **plugins, + char **dhcp_client, + char **log_level, + char **log_domains, + GError **error) +{ + GKeyFile *config; + + config = g_key_file_new (); + if (!config) { + g_set_error (error, 0, 0, + "Not enough memory to load config file."); + return FALSE; + } + + g_key_file_set_list_separator (config, ','); + if (!g_key_file_load_from_file (config, filename, G_KEY_FILE_NONE, error)) + return FALSE; + + *plugins = g_key_file_get_value (config, "main", "plugins", error); + if (*error) + return FALSE; + + *dhcp_client = g_key_file_get_value (config, "main", "dhcp", NULL); + + *log_level = g_key_file_get_value (config, "logging", "level", NULL); + *log_domains = g_key_file_get_value (config, "logging", "domains", NULL); + + g_key_file_free (config); + return TRUE; +} + +static gboolean +parse_state_file (const char *filename, + gboolean *net_enabled, + gboolean *wifi_enabled, + gboolean *wwan_enabled, + GError **error) +{ + GKeyFile *state_file; + GError *tmp_error = NULL; + gboolean wifi, net, wwan; + + g_return_val_if_fail (net_enabled != NULL, FALSE); + g_return_val_if_fail (wifi_enabled != NULL, FALSE); + g_return_val_if_fail (wwan_enabled != NULL, FALSE); + + state_file = g_key_file_new (); + if (!state_file) { + g_set_error (error, 0, 0, + "Not enough memory to load state file."); + return FALSE; + } + + g_key_file_set_list_separator (state_file, ','); + if (!g_key_file_load_from_file (state_file, filename, G_KEY_FILE_KEEP_COMMENTS, &tmp_error)) { + /* This is kinda ugly; create the file and directory if it doesn't + * exist yet. We can't rely on distros necessarily creating the + * /var/lib/NetworkManager for us since we have to ensure that + * users upgrading NM get this working too. + */ + if ( tmp_error->domain == G_FILE_ERROR + && tmp_error->code == G_FILE_ERROR_NOENT) { + char *data, *dirname; + gsize len = 0; + gboolean ret = FALSE; + + /* try to create the directory if it doesn't exist */ + dirname = g_path_get_dirname (filename); + errno = 0; + if (mkdir (dirname, 0755) != 0) { + if (errno != EEXIST) { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES, + "Error creating state directory %s: %d", dirname, errno); + g_free (dirname); + return FALSE; + } + } + g_free (dirname); + + /* Write out the initial state to the state file */ + g_key_file_set_boolean (state_file, "main", "NetworkingEnabled", *net_enabled); + g_key_file_set_boolean (state_file, "main", "WirelessEnabled", *wifi_enabled); + g_key_file_set_boolean (state_file, "main", "WWANEnabled", *wwan_enabled); + + data = g_key_file_to_data (state_file, &len, NULL); + if (data) + ret = g_file_set_contents (filename, data, len, error); + g_free (data); + + return ret; + } else { + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + g_clear_error (&tmp_error); + } + + /* Otherwise, file probably corrupt or inaccessible */ + return FALSE; + } + + /* Reading state bits of NetworkManager; an error leaves the passed-in state + * value unchanged. + */ + net = g_key_file_get_boolean (state_file, "main", "NetworkingEnabled", &tmp_error); + if (tmp_error) + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + else + *net_enabled = net; + g_clear_error (&tmp_error); + + wifi = g_key_file_get_boolean (state_file, "main", "WirelessEnabled", &tmp_error); + if (tmp_error) { + g_clear_error (error); + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + } else + *wifi_enabled = wifi; + g_clear_error (&tmp_error); + + wwan = g_key_file_get_boolean (state_file, "main", "WWANEnabled", &tmp_error); + if (tmp_error) { + g_clear_error (error); + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + } else + *wwan_enabled = wwan; + g_clear_error (&tmp_error); + + g_key_file_free (state_file); + + return TRUE; +} + +/* + * main + * + */ +int +main (int argc, char *argv[]) +{ + GOptionContext *opt_ctx = NULL; + gboolean become_daemon = FALSE; + gboolean g_fatal_warnings = FALSE; + char *pidfile = NULL, *state_file = NULL, *dhcp = NULL; + char *config = NULL, *plugins = NULL, *conf_plugins = NULL; + char *log_level = NULL, *log_domains = NULL; + gboolean wifi_enabled = TRUE, net_enabled = TRUE, wwan_enabled = TRUE; + gboolean success; + NMPolicy *policy = NULL; + NMVPNManager *vpn_manager = NULL; + NMNamedManager *named_mgr = NULL; + NMDBusManager *dbus_mgr = NULL; + NMSupplicantManager *sup_mgr = NULL; + NMDHCPManager *dhcp_mgr = NULL; + GError *error = NULL; + gboolean wrote_pidfile = FALSE; + char *cfg_log_level = NULL, *cfg_log_domains = NULL; + + GOptionEntry options[] = { + { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &become_daemon, "Don't become a daemon", NULL }, + { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL }, + { "pid-file", 0, 0, G_OPTION_ARG_FILENAME, &pidfile, "Specify the location of a PID file", "filename" }, + { "state-file", 0, 0, G_OPTION_ARG_FILENAME, &state_file, "State file location", "/path/to/state.file" }, + { "config", 0, 0, G_OPTION_ARG_FILENAME, &config, "Config file location", "/path/to/config.file" }, + { "plugins", 0, 0, G_OPTION_ARG_STRING, &plugins, "List of plugins separated by ','", "plugin1,plugin2" }, + { "log-level", 0, 0, G_OPTION_ARG_STRING, &log_level, "Log level: one of [ERR, WARN, INFO, DEBUG]", "INFO" }, + { "log-domains", 0, 0, G_OPTION_ARG_STRING, &log_domains, + "Log domains separated by ',': any combination of [NONE,HW,RKILL,ETHER,WIFI,BT,MB,DHCP4,DHCP6,PPP,WIFI_SCAN,IP4,IP6,AUTOIP4,DNS,VPN,SHARING,SUPPLICANT,USER_SET,SYS_SET,SUSPEND,CORE,DEVICE,OLPC]", + "HW,RFKILL,WIFI" }, + {NULL} + }; + + if (getuid () != 0) { + fprintf (stderr, "You must be root to run NetworkManager!\n"); + exit (1); + } + + if (!g_module_supported ()) { + fprintf (stderr, "GModules are not supported on your platform!\n"); + exit (1); + } + + bindtextdomain (GETTEXT_PACKAGE, NMLOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + /* Parse options */ + opt_ctx = g_option_context_new (""); + g_option_context_set_translation_domain (opt_ctx, "UTF-8"); + g_option_context_set_ignore_unknown_options (opt_ctx, FALSE); + g_option_context_set_help_enabled (opt_ctx, TRUE); + g_option_context_add_main_entries (opt_ctx, options, NULL); + + g_option_context_set_summary (opt_ctx, + "NetworkManager monitors all network connections and automatically\nchooses the best connection to use. It also allows the user to\nspecify wireless access points which wireless cards in the computer\nshould associate with."); + + success = g_option_context_parse (opt_ctx, &argc, &argv, NULL); + g_option_context_free (opt_ctx); + + if (!success) { + fprintf (stderr, _("Invalid option. Please use --help to see a list of valid options.\n")); + exit (1); + } + + /* Make GIO ignore the remote VFS service; otherwise it tries to use the + * session bus to contact the remote service, and NM shouldn't ever be + * talking on the session bus. See rh #588745 + */ + setenv ("GIO_USE_VFS", "local", 1); + + pidfile = pidfile ? pidfile : g_strdup (NM_DEFAULT_PID_FILE); + state_file = state_file ? state_file : g_strdup (NM_DEFAULT_SYSTEM_STATE_FILE); + + /* check pid file */ + if (check_pidfile (pidfile)) + exit (1); + + /* Parse the config file */ + if (config) { + if (!parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error)) { + fprintf (stderr, "Config file %s invalid: (%d) %s\n", + config, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + exit (1); + } + } else { + gboolean parsed = FALSE; + + /* Even though we prefer NetworkManager.conf, we need to check the + * old nm-system-settings.conf first to preserve compat with older + * setups. In package managed systems dropping a NetworkManager.conf + * onto the system would make NM use it instead of nm-system-settings.conf, + * changing behavior during an upgrade. We don't want that. + */ + + /* Try deprecated nm-system-settings.conf first */ + if (g_file_test (NM_OLD_SYSTEM_CONF_FILE, G_FILE_TEST_EXISTS)) { + config = g_strdup (NM_OLD_SYSTEM_CONF_FILE); + parsed = parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error); + if (!parsed) { + fprintf (stderr, "Default config file %s invalid: (%d) %s\n", + config, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + g_free (config); + config = NULL; + g_clear_error (&error); + } + } + + /* Try the preferred NetworkManager.conf last */ + if (!parsed && g_file_test (NM_DEFAULT_SYSTEM_CONF_FILE, G_FILE_TEST_EXISTS)) { + config = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE); + parsed = parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error); + if (!parsed) { + fprintf (stderr, "Default config file %s invalid: (%d) %s\n", + config, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + g_free (config); + config = NULL; + g_clear_error (&error); + } + } + } + /* Logging setup */ + if (!nm_logging_setup (log_level ? log_level : cfg_log_level, + log_domains ? log_domains : cfg_log_domains, + &error)) { + fprintf (stderr, + _("%s. Please use --help to see a list of valid options.\n"), + error->message); + exit (1); + } + + /* Plugins specified with '--plugins' override those of config file */ + plugins = plugins ? plugins : g_strdup (conf_plugins); + g_free (conf_plugins); + + /* Parse the state file */ + if (!parse_state_file (state_file, &net_enabled, &wifi_enabled, &wwan_enabled, &error)) { + fprintf (stderr, "State file %s parsing failed: (%d) %s\n", + state_file, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + /* Not a hard failure */ + } + g_clear_error (&error); + + /* Tricky: become_daemon is FALSE by default, so unless it's TRUE because + * of a CLI option, it'll become TRUE after this + */ + become_daemon = !become_daemon; + if (become_daemon) { + if (daemon (0, 0) < 0) { + int saved_errno; + + saved_errno = errno; + fprintf (stderr, "Could not daemonize: %s [error %u]\n", + g_strerror (saved_errno), + saved_errno); + exit (1); + } + if (write_pidfile (pidfile)) + wrote_pidfile = TRUE; + } + + if (g_fatal_warnings) { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + /* + * Set the umask to 0022, which results in 0666 & ~0022 = 0644. + * Otherwise, if root (or an su'ing user) has a wacky umask, we could + * write out an unreadable resolv.conf. + */ + umask (022); + + g_type_init (); + if (!g_thread_supported ()) + g_thread_init (NULL); + dbus_g_thread_init (); + + setup_signals (); + + nm_logging_start (become_daemon); + + nm_log_info (LOGD_CORE, "NetworkManager (version " NM_DIST_VERSION ") is starting..."); + success = FALSE; + + if (config) + nm_log_info (LOGD_CORE, "Read config file %s", config); + + main_loop = g_main_loop_new (NULL, FALSE); + + /* Create watch functions that monitor cards for link status. */ + if (!nm_monitor_setup (&error)) { + nm_log_err (LOGD_CORE, "failed to start monitoring devices: %s.", + error && error->message ? error->message : "(unknown)"); + goto done; + } + + /* Initialize our DBus service & connection */ + dbus_mgr = nm_dbus_manager_get (); + + vpn_manager = nm_vpn_manager_get (); + if (!vpn_manager) { + nm_log_err (LOGD_CORE, "failed to start the VPN manager."); + goto done; + } + + named_mgr = nm_named_manager_get (); + if (!named_mgr) { + nm_log_err (LOGD_CORE, "failed to start the named manager."); + goto done; + } + + manager = nm_manager_get (config, + plugins, + state_file, + net_enabled, + wifi_enabled, + wwan_enabled, + &error); + if (manager == NULL) { + nm_log_err (LOGD_CORE, "failed to initialize the network manager: %s", + error && error->message ? error->message : "(unknown)"); + goto done; + } + + policy = nm_policy_new (manager, vpn_manager); + if (policy == NULL) { + nm_log_err (LOGD_CORE, "failed to initialize the policy."); + goto done; + } + + /* Initialize the supplicant manager */ + sup_mgr = nm_supplicant_manager_get (); + if (!sup_mgr) { + nm_log_err (LOGD_CORE, "failed to initialize the supplicant manager."); + goto done; + } + + /* Initialize DHCP manager */ + dhcp_mgr = nm_dhcp_manager_new (dhcp, &error); + if (!dhcp_mgr) { + nm_log_err (LOGD_CORE, "failed to start the DHCP manager: %s.", error->message); + goto done; + } + + nm_dhcp_manager_set_hostname_provider (dhcp_mgr, NM_HOSTNAME_PROVIDER (manager)); + + /* Start our DBus service */ + if (!nm_dbus_manager_start_service (dbus_mgr)) { + nm_log_err (LOGD_CORE, "failed to start the dbus service."); + goto done; + } + + nm_manager_start (manager); + + /* Bring up the loopback interface. */ + nm_system_enable_loopback (); + + success = TRUE; + + /* Told to quit before getting to the mainloop by the signal handler */ + if (quit_early == TRUE) + goto done; + + g_main_loop_run (main_loop); + +done: + if (policy) + nm_policy_destroy (policy); + + if (manager) + g_object_unref (manager); + + if (vpn_manager) + g_object_unref (vpn_manager); + + if (named_mgr) + g_object_unref (named_mgr); + + if (dhcp_mgr) + g_object_unref (dhcp_mgr); + + if (sup_mgr) + g_object_unref (sup_mgr); + + if (dbus_mgr) + g_object_unref (dbus_mgr); + + nm_logging_shutdown (); + + if (pidfile && wrote_pidfile) + unlink (pidfile); + + /* Free options */ + g_free (pidfile); + g_free (state_file); + g_free (config); + g_free (plugins); + g_free (dhcp); + g_free (log_level); + g_free (log_domains); + g_free (cfg_log_level); + g_free (cfg_log_domains); + + nm_log_info (LOGD_CORE, "exiting (%s)", success ? "success" : "error"); + exit (success ? 0 : 1); +} |