From 1a4b7ee28bf7413af6513fb45ad0d0736048f866 Mon Sep 17 00:00:00 2001 From: Brad Bishop Date: Sun, 16 Dec 2018 17:11:34 -0800 Subject: reset upstream subtrees to yocto 2.6 Reset the following subtrees on thud HEAD: poky: 87e3a9739d meta-openembedded: 6094ae18c8 meta-security: 31dc4e7532 meta-raspberrypi: a48743dc36 meta-xilinx: c42016e2e6 Also re-apply backports that didn't make it into thud: poky: 17726d0 systemd-systemctl-native: handle Install wildcards meta-openembedded: 4321a5d libtinyxml2: update to 7.0.1 042f0a3 libcereal: Add native and nativesdk classes e23284f libcereal: Allow empty package 030e8d4 rsyslog: curl-less build with fmhttp PACKAGECONFIG 179a1b9 gtest: update to 1.8.1 Squashed OpenBMC subtree compatibility updates: meta-aspeed: Brad Bishop (1): aspeed: add yocto 2.6 compatibility meta-ibm: Brad Bishop (1): ibm: prepare for yocto 2.6 meta-ingrasys: Brad Bishop (1): ingrasys: set layer compatibility to yocto 2.6 meta-openpower: Brad Bishop (1): openpower: set layer compatibility to yocto 2.6 meta-phosphor: Brad Bishop (3): phosphor: set layer compatibility to thud phosphor: libgpg-error: drop patches phosphor: react to fitimage artifact rename Ed Tanous (4): Dropbear: upgrade options for latest upgrade yocto2.6: update openssl options busybox: remove upstream watchdog patch systemd: Rebase CONFIG_CGROUP_BPF patch Change-Id: I7b1fe71cca880d0372a82d94b5fd785323e3a9e7 Signed-off-by: Brad Bishop --- .../0017-mpathpersist-fix-aptpl-support.patch | 543 +++++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 meta-openembedded/meta-oe/recipes-support/multipath-tools/files/0017-mpathpersist-fix-aptpl-support.patch (limited to 'meta-openembedded/meta-oe/recipes-support/multipath-tools/files/0017-mpathpersist-fix-aptpl-support.patch') diff --git a/meta-openembedded/meta-oe/recipes-support/multipath-tools/files/0017-mpathpersist-fix-aptpl-support.patch b/meta-openembedded/meta-oe/recipes-support/multipath-tools/files/0017-mpathpersist-fix-aptpl-support.patch new file mode 100644 index 0000000000..b98d310a13 --- /dev/null +++ b/meta-openembedded/meta-oe/recipes-support/multipath-tools/files/0017-mpathpersist-fix-aptpl-support.patch @@ -0,0 +1,543 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Benjamin Marzinski +Date: Mon, 4 Jun 2018 22:04:44 -0500 +Subject: [PATCH] mpathpersist: fix aptpl support + +The "Active Persist Through Power Loss" flag must be set whenever a key +is registered. However, there is no way for multipathd to know if this +was set by mpathpersist. The result is that if a path goes down and +comes back up (or if it wasn't up when mpathpersist was first run) +multipathd will clear the aptpl flag when it reregisters the key on it. + +To fix this, multipath.conf now accepts an optional ":aptpl" appended +on the reservation_key value. If this is added to the reservation_key +multipathd will set the aptpl flag when it reregisters the key. If +reservation_key is set to "file", this will automatically be tracked +in the /etc/multipath/prkeys file. + +To track this flag in the prkeys file, without changing the format +I've made "0x" stand for non-aptpl keys, and "0X" stand +for aptpl keys. Since previously, all keys used a lower-case x, this +will default to the current behavior for existing keys. Obviously, the +next time mpathpersist is run, this will be changed if --param-aptpl +is used. Since there are no more flags that are in sg_persist that +multipathd needs to care about in mpathpersist, there shouldn't need +to be any more flags added to the prkeys file. + +Signed-off-by: Benjamin Marzinski +--- + libmpathpersist/mpath_persist.c | 3 ++- + libmpathpersist/mpath_updatepr.c | 11 +++++++---- + libmpathpersist/mpathpr.h | 3 ++- + libmultipath/Makefile | 2 +- + libmultipath/config.h | 2 ++ + libmultipath/dict.c | 23 +++++++++++++++++++---- + libmultipath/dict.h | 3 ++- + libmultipath/prkey.c | 27 ++++++++++++++++++++++++--- + libmultipath/prkey.h | 6 ++++-- + libmultipath/propsel.c | 6 ++++-- + libmultipath/structs.h | 1 + + libmultipath/util.c | 16 ++++++++++++++++ + libmultipath/util.h | 1 + + multipath/multipath.conf.5 | 7 +++++-- + multipathd/cli_handlers.c | 15 ++++++++++----- + multipathd/main.c | 1 + + 16 files changed, 101 insertions(+), 26 deletions(-) + +diff --git a/libmpathpersist/mpath_persist.c b/libmpathpersist/mpath_persist.c +index ca91c55..6e9e67f 100644 +--- a/libmpathpersist/mpath_persist.c ++++ b/libmpathpersist/mpath_persist.c +@@ -344,7 +344,8 @@ int mpath_persistent_reserve_out ( int fd, int rq_servact, int rq_scope, + rq_servact == MPATH_PROUT_REG_SA) || + rq_servact == MPATH_PROUT_REG_IGN_SA)) { + memcpy(&mpp->reservation_key, paramp->sa_key, 8); +- if (update_prkey(alias, get_be64(mpp->reservation_key))) { ++ if (update_prkey_flags(alias, get_be64(mpp->reservation_key), ++ paramp->sa_flags)) { + condlog(0, "%s: failed to set prkey for multipathd.", + alias); + ret = MPATH_PR_DMMP_ERROR; +diff --git a/libmpathpersist/mpath_updatepr.c b/libmpathpersist/mpath_updatepr.c +index 8063e90..0aca28e 100644 +--- a/libmpathpersist/mpath_updatepr.c ++++ b/libmpathpersist/mpath_updatepr.c +@@ -1,7 +1,5 @@ + #include + #include +-#include +- + #include + #include + #include +@@ -11,6 +9,8 @@ + #include + #include + #include ++#include ++#include + #include "debug.h" + #include "mpath_cmd.h" + #include "uxsock.h" +@@ -59,11 +59,14 @@ int update_prflag(char *mapname, int set) { + return do_update_pr(mapname, (set)? "setprstatus" : "unsetprstatus"); + } + +-int update_prkey(char *mapname, uint64_t prkey) { ++int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags) { + char str[256]; ++ char *flagstr = ""; + ++ if (sa_flags & MPATH_F_APTPL_MASK) ++ flagstr = ":aptpl"; + if (prkey) +- sprintf(str, "setprkey key %" PRIx64, prkey); ++ sprintf(str, "setprkey key %" PRIx64 "%s", prkey, flagstr); + else + sprintf(str, "unsetprkey"); + return do_update_pr(mapname, str); +diff --git a/libmpathpersist/mpathpr.h b/libmpathpersist/mpathpr.h +index 72feb60..5ea8cd6 100644 +--- a/libmpathpersist/mpathpr.h ++++ b/libmpathpersist/mpathpr.h +@@ -46,7 +46,8 @@ int send_prout_activepath(char * dev, int rq_servact, int rq_scope, + unsigned int rq_type, struct prout_param_descriptor * paramp, int noisy); + + int update_prflag(char *mapname, int set); +-int update_prkey(char *mapname, uint64_t prkey); ++int update_prkey_flags(char *mapname, uint64_t prkey, uint8_t sa_flags); ++#define update_prkey(mapname, prkey) update_prkey_flags(mapname, prkey, 0) + void * mpath_alloc_prin_response(int prin_sa); + int update_map_pr(struct multipath *mpp); + +diff --git a/libmultipath/Makefile b/libmultipath/Makefile +index f51786d..33f5269 100644 +--- a/libmultipath/Makefile ++++ b/libmultipath/Makefile +@@ -7,7 +7,7 @@ SONAME = 0 + DEVLIB = libmultipath.so + LIBS = $(DEVLIB).$(SONAME) + +-CFLAGS += $(LIB_CFLAGS) -I$(mpathcmddir) ++CFLAGS += $(LIB_CFLAGS) -I$(mpathcmddir) -I$(mpathpersistdir) + + LIBDEPS += -lpthread -ldl -ldevmapper -ludev -L$(mpathcmddir) -lmpathcmd -lurcu -laio + +diff --git a/libmultipath/config.h b/libmultipath/config.h +index 1bf708a..fcbe3fc 100644 +--- a/libmultipath/config.h ++++ b/libmultipath/config.h +@@ -98,6 +98,7 @@ struct mpentry { + char * prio_args; + int prkey_source; + struct be64 reservation_key; ++ uint8_t sa_flags; + int pgpolicy; + int pgfailback; + int rr_weight; +@@ -197,6 +198,7 @@ struct config { + int prkey_source; + int all_tg_pt; + struct be64 reservation_key; ++ uint8_t sa_flags; + + vector keywords; + vector mptable; +diff --git a/libmultipath/dict.c b/libmultipath/dict.c +index 2557b8a..7ad0f5a 100644 +--- a/libmultipath/dict.c ++++ b/libmultipath/dict.c +@@ -22,6 +22,8 @@ + #include "util.h" + #include + #include ++#include ++#include + #include "mpath_cmd.h" + #include "dict.h" + +@@ -1012,10 +1014,12 @@ snprint_def_log_checker_err (struct config *conf, char * buff, int len, + } + + static int +-set_reservation_key(vector strvec, struct be64 *be64_ptr, int *source_ptr) ++set_reservation_key(vector strvec, struct be64 *be64_ptr, uint8_t *flags_ptr, ++ int *source_ptr) + { + char *buff; + uint64_t prkey; ++ uint8_t sa_flags; + + buff = set_value(strvec); + if (!buff) +@@ -1023,35 +1027,43 @@ set_reservation_key(vector strvec, struct be64 *be64_ptr, int *source_ptr) + + if (strcmp(buff, "file") == 0) { + *source_ptr = PRKEY_SOURCE_FILE; ++ *flags_ptr = 0; + put_be64(*be64_ptr, 0); + FREE(buff); + return 0; + } + +- if (parse_prkey(buff, &prkey) != 0) { ++ if (parse_prkey_flags(buff, &prkey, &sa_flags) != 0) { + FREE(buff); + return 1; + } + *source_ptr = PRKEY_SOURCE_CONF; ++ *flags_ptr = sa_flags; + put_be64(*be64_ptr, prkey); + FREE(buff); + return 0; + } + + int +-print_reservation_key(char * buff, int len, struct be64 key, int source) ++print_reservation_key(char * buff, int len, struct be64 key, uint8_t flags, ++ int source) + { ++ char *flagstr = ""; + if (source == PRKEY_SOURCE_NONE) + return 0; + if (source == PRKEY_SOURCE_FILE) + return snprintf(buff, len, "file"); +- return snprintf(buff, len, "0x%" PRIx64, get_be64(key)); ++ if (flags & MPATH_F_APTPL_MASK) ++ flagstr = ":aptpl"; ++ return snprintf(buff, len, "0x%" PRIx64 "%s", get_be64(key), ++ flagstr); + } + + static int + def_reservation_key_handler(struct config *conf, vector strvec) + { + return set_reservation_key(strvec, &conf->reservation_key, ++ &conf->sa_flags, + &conf->prkey_source); + } + +@@ -1060,6 +1072,7 @@ snprint_def_reservation_key (struct config *conf, char * buff, int len, + const void * data) + { + return print_reservation_key(buff, len, conf->reservation_key, ++ conf->sa_flags, + conf->prkey_source); + } + +@@ -1070,6 +1083,7 @@ mp_reservation_key_handler(struct config *conf, vector strvec) + if (!mpe) + return 1; + return set_reservation_key(strvec, &mpe->reservation_key, ++ &mpe->sa_flags, + &mpe->prkey_source); + } + +@@ -1079,6 +1093,7 @@ snprint_mp_reservation_key (struct config *conf, char * buff, int len, + { + const struct mpentry * mpe = (const struct mpentry *)data; + return print_reservation_key(buff, len, mpe->reservation_key, ++ mpe->sa_flags, + mpe->prkey_source); + } + +diff --git a/libmultipath/dict.h b/libmultipath/dict.h +index 7564892..a40ac66 100644 +--- a/libmultipath/dict.h ++++ b/libmultipath/dict.h +@@ -15,6 +15,7 @@ int print_pgpolicy(char *buff, int len, long v); + int print_no_path_retry(char *buff, int len, long v); + int print_fast_io_fail(char *buff, int len, long v); + int print_dev_loss(char *buff, int len, unsigned long v); +-int print_reservation_key(char * buff, int len, struct be64 key, int source); ++int print_reservation_key(char * buff, int len, struct be64 key, uint8_t ++ flags, int source); + int print_off_int_undef(char *buff, int len, long v); + #endif /* _DICT_H */ +diff --git a/libmultipath/prkey.c b/libmultipath/prkey.c +index 89b90ed..d645f81 100644 +--- a/libmultipath/prkey.c ++++ b/libmultipath/prkey.c +@@ -11,6 +11,8 @@ + #include + #include + #include ++#include ++#include + + #define PRKEY_READ 0 + #define PRKEY_WRITE 1 +@@ -108,7 +110,8 @@ static int do_prkey(int fd, char *wwid, char *keystr, int cmd) + return 0; + } + +-int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey) ++int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, ++ uint8_t *sa_flags) + { + int fd; + int unused; +@@ -124,6 +127,9 @@ int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey) + ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_READ); + if (ret) + goto out_file; ++ *sa_flags = 0; ++ if (strchr(keystr, 'X')) ++ *sa_flags = MPATH_F_APTPL_MASK; + ret = !!parse_prkey(keystr, prkey); + out_file: + close(fd); +@@ -131,7 +137,8 @@ out: + return ret; + } + +-int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey) ++int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, ++ uint8_t sa_flags) + { + int fd; + int can_write = 1; +@@ -141,6 +148,12 @@ int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey) + if (!strlen(mpp->wwid)) + goto out; + ++ if (sa_flags & ~MPATH_F_APTPL_MASK) { ++ condlog(0, "unsupported pr flags, 0x%x", ++ sa_flags & ~MPATH_F_APTPL_MASK); ++ sa_flags &= MPATH_F_APTPL_MASK; ++ } ++ + fd = open_file(conf->prkeys_file, &can_write, PRKEYS_FILE_HEADER); + if (fd < 0) + goto out; +@@ -149,7 +162,15 @@ int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey) + goto out_file; + } + if (prkey) { +- snprintf(keystr, PRKEY_SIZE, "0x%016" PRIx64, prkey); ++ /* using the capitalization of the 'x' is a hack, but ++ * it's unlikely that mpath_persist will support more options ++ * since sg_persist doesn't, and this lets us keep the ++ * same file format as before instead of needing to change ++ * the format of the prkeys file */ ++ if (sa_flags) ++ snprintf(keystr, PRKEY_SIZE, "0X%016" PRIx64, prkey); ++ else ++ snprintf(keystr, PRKEY_SIZE, "0x%016" PRIx64, prkey); + keystr[PRKEY_SIZE - 1] = '\0'; + ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_WRITE); + } +diff --git a/libmultipath/prkey.h b/libmultipath/prkey.h +index 4028e70..6739191 100644 +--- a/libmultipath/prkey.h ++++ b/libmultipath/prkey.h +@@ -13,7 +13,9 @@ + "# prkey wwid\n" \ + "#\n" + +-int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey); +-int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey); ++int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, ++ uint8_t sa_flags); ++int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, ++ uint8_t *sa_flags); + + #endif /* _PRKEY_H */ +diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c +index 9ca1355..62a6893 100644 +--- a/libmultipath/propsel.c ++++ b/libmultipath/propsel.c +@@ -106,6 +106,7 @@ do { \ + if (src && src->prkey_source != PRKEY_SOURCE_NONE) { \ + mp->prkey_source = src->prkey_source; \ + mp->reservation_key = src->reservation_key; \ ++ mp->sa_flags = src->sa_flags; \ + origin = msg; \ + goto out; \ + } \ +@@ -703,18 +704,19 @@ int select_reservation_key(struct config *conf, struct multipath *mp) + do_prkey_set(mp->mpe, multipaths_origin); + do_prkey_set(conf, conf_origin); + put_be64(mp->reservation_key, 0); ++ mp->sa_flags = 0; + mp->prkey_source = PRKEY_SOURCE_NONE; + return 0; + out: + if (mp->prkey_source == PRKEY_SOURCE_FILE) { + from_file = " (from prkeys file)"; +- if (get_prkey(conf, mp, &prkey) != 0) ++ if (get_prkey(conf, mp, &prkey, &mp->sa_flags) != 0) + put_be64(mp->reservation_key, 0); + else + put_be64(mp->reservation_key, prkey); + } + print_reservation_key(buff, PRKEY_SIZE, mp->reservation_key, +- mp->prkey_source); ++ mp->sa_flags, mp->prkey_source); + condlog(3, "%s: reservation_key = %s %s%s", mp->alias, buff, origin, + from_file); + return 0; +diff --git a/libmultipath/structs.h b/libmultipath/structs.h +index 0194b1e..987479f 100644 +--- a/libmultipath/structs.h ++++ b/libmultipath/structs.h +@@ -367,6 +367,7 @@ struct multipath { + /* persistent management data*/ + int prkey_source; + struct be64 reservation_key; ++ uint8_t sa_flags; + unsigned char prflag; + int all_tg_pt; + struct gen_multipath generic_mp; +diff --git a/libmultipath/util.c b/libmultipath/util.c +index 7251ad0..8d8fcc8 100644 +--- a/libmultipath/util.c ++++ b/libmultipath/util.c +@@ -10,6 +10,8 @@ + #include + #include + #include ++#include ++#include + + #include "util.h" + #include "debug.h" +@@ -435,6 +437,20 @@ int parse_prkey(char *ptr, uint64_t *prkey) + return 0; + } + ++int parse_prkey_flags(char *ptr, uint64_t *prkey, uint8_t *flags) ++{ ++ char *flagstr; ++ ++ flagstr = strchr(ptr, ':'); ++ *flags = 0; ++ if (flagstr) { ++ *flagstr++ = '\0'; ++ if (strlen(flagstr) == 5 && strcmp(flagstr, "aptpl") == 0) ++ *flags = MPATH_F_APTPL_MASK; ++ } ++ return parse_prkey(ptr, prkey); ++} ++ + int safe_write(int fd, const void *buf, size_t count) + { + while (count > 0) { +diff --git a/libmultipath/util.h b/libmultipath/util.h +index a3ab894..56cec76 100644 +--- a/libmultipath/util.h ++++ b/libmultipath/util.h +@@ -19,6 +19,7 @@ void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached); + int systemd_service_enabled(const char *dev); + int get_linux_version_code(void); + int parse_prkey(char *ptr, uint64_t *prkey); ++int parse_prkey_flags(char *ptr, uint64_t *prkey, uint8_t *flags); + int safe_write(int fd, const void *buf, size_t count); + + #define KERNEL_VERSION(maj, min, ptc) ((((maj) * 256) + (min)) * 256 + (ptc)) +diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5 +index 31f4585..30d8598 100644 +--- a/multipath/multipath.conf.5 ++++ b/multipath/multipath.conf.5 +@@ -726,14 +726,17 @@ This is the service action reservation key used by mpathpersist. It must be + set for all multipath devices using persistent reservations, and it must be + the same as the RESERVATION KEY field of the PERSISTENT RESERVE OUT parameter + list which contains an 8-byte value provided by the application client to the +-device server to identify the I_T nexus. ++device server to identify the I_T nexus. If the \fI--param-aptpl\fR option is ++used when registering the key with mpathpersist, \fB:aptpl\fR must be appended ++to the end of the reservation key. + .RS + .PP + Alternatively, this can be set to \fBfile\fR, which will store the RESERVATION + KEY registered by mpathpersist in the \fIprkeys_file\fR. multipathd will then + use this key to register additional paths as they appear. When the + registration is removed, the RESERVATION KEY is removed from the +-\fIprkeys_file\fR. ++\fIprkeys_file\fR. The prkeys file will automatically keep track of whether ++the key was registered with \fI--param-aptpl\fR. + .TP + The default is: \fB\fR + .RE +diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c +index ba50fb8..6452796 100644 +--- a/multipathd/cli_handlers.c ++++ b/multipathd/cli_handlers.c +@@ -21,6 +21,7 @@ + #include "sysfs.h" + #include + #include ++#include + #include "util.h" + #include "prkey.h" + #include "propsel.h" +@@ -1463,6 +1464,7 @@ cli_getprkey(void * v, char ** reply, int * len, void * data) + struct multipath * mpp; + struct vectors * vecs = (struct vectors *)data; + char *mapname = get_keyparam(v, MAP); ++ char *flagstr = ""; + + mapname = convert_dev(mapname, 0); + condlog(3, "%s: get persistent reservation key (operator)", mapname); +@@ -1478,8 +1480,10 @@ cli_getprkey(void * v, char ** reply, int * len, void * data) + *len = strlen(*reply) + 1; + return 0; + } +- snprintf(*reply, 20, "0x%" PRIx64 "\n", +- get_be64(mpp->reservation_key)); ++ if (mpp->sa_flags & MPATH_F_APTPL_MASK) ++ flagstr = ":aptpl"; ++ snprintf(*reply, 20, "0x%" PRIx64 "%s\n", ++ get_be64(mpp->reservation_key), flagstr); + (*reply)[19] = '\0'; + *len = strlen(*reply) + 1; + return 0; +@@ -1503,7 +1507,7 @@ cli_unsetprkey(void * v, char ** reply, int * len, void * data) + + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); +- ret = set_prkey(conf, mpp, 0); ++ ret = set_prkey(conf, mpp, 0, 0); + pthread_cleanup_pop(1); + + return ret; +@@ -1517,6 +1521,7 @@ cli_setprkey(void * v, char ** reply, int * len, void * data) + char *mapname = get_keyparam(v, MAP); + char *keyparam = get_keyparam(v, KEY); + uint64_t prkey; ++ uint8_t flags; + int ret; + struct config *conf; + +@@ -1527,14 +1532,14 @@ cli_setprkey(void * v, char ** reply, int * len, void * data) + if (!mpp) + return 1; + +- if (parse_prkey(keyparam, &prkey) != 0) { ++ if (parse_prkey_flags(keyparam, &prkey, &flags) != 0) { + condlog(0, "%s: invalid prkey : '%s'", mapname, keyparam); + return 1; + } + + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); +- ret = set_prkey(conf, mpp, prkey); ++ ret = set_prkey(conf, mpp, prkey, flags); + pthread_cleanup_pop(1); + + return ret; +diff --git a/multipathd/main.c b/multipathd/main.c +index d40c416..6b1e782 100644 +--- a/multipathd/main.c ++++ b/multipathd/main.c +@@ -3089,6 +3089,7 @@ void * mpath_pr_event_handler_fn (void * pathp ) + + param= malloc(sizeof(struct prout_param_descriptor)); + memset(param, 0 , sizeof(struct prout_param_descriptor)); ++ param->sa_flags = mpp->sa_flags; + memcpy(param->sa_key, &mpp->reservation_key, 8); + param->num_transportid = 0; + +-- +2.7.4 + -- cgit v1.2.3