# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: rousskov@measurement-factory.com-20100701225845-\ # aag59oouzt15rooa # target_branch: http://www.squid-cache.org/bzr/squid3/trunk # testament_sha1: ed276a8bb90cc89b3eefb9cb9203b2291eea5e5a # timestamp: 2010-07-01 17:01:42 -0600 # base_revision_id: squid3@treenet.co.nz-20100303093849-\ # ndhnc129fhg9xuie # # Begin patch === modified file 'configure.in' --- configure.in 2010-02-03 12:36:21 +0000 +++ configure.in 2010-03-30 21:54:40 +0000 @@ -4,7 +4,7 @@ dnl dnl dnl -AC_INIT([Squid Web Proxy],[3.HEAD-BZR],[http://www.squid-cache.org/bugs/],[squid]) +AC_INIT([Squid Web Proxy],[3.HEAD-SMP-BZR],[http://www.squid-cache.org/bugs/],[squid]) AC_PREREQ(2.61) AC_CONFIG_HEADERS([include/autoconf.h]) AC_CONFIG_AUX_DIR(cfgaux) @@ -4233,6 +4233,7 @@ src/ident/Makefile \ src/ip/Makefile \ src/log/Makefile \ + src/ipc/Makefile \ contrib/Makefile \ snmplib/Makefile \ icons/Makefile \ === modified file 'src/Makefile.am' --- src/Makefile.am 2010-01-12 21:16:29 +0000 +++ src/Makefile.am 2010-03-30 21:54:40 +0000 @@ -34,7 +34,7 @@ LoadableModules.h \ LoadableModules.cc -SUBDIRS = base comm eui acl fs repl auth ip icmp ident log +SUBDIRS = base comm eui acl fs repl auth ip icmp ident log ipc if USE_ADAPTATION SUBDIRS += adaptation @@ -164,7 +164,8 @@ auth/libauth.la \ acl/libapi.la \ ip/libip.la \ - fs/libfs.la + fs/libfs.la \ + ipc/libipc.la EXTRA_PROGRAMS = \ DiskIO/DiskDaemon/diskd \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2010-02-06 00:13:14 +0000 +++ src/cache_cf.cc 2010-03-30 21:54:40 +0000 @@ -69,6 +69,7 @@ #include "StoreFileSystem.h" #include "SwapDir.h" #include "wordlist.h" +#include "ipc/Kids.h" #if HAVE_GLOB_H #include @@ -258,6 +259,110 @@ return error_count; } +static void +ReplaceSubstr(char*& str, int& len, unsigned substrIdx, unsigned substrLen, const char* newSubstr) +{ + assert(str != NULL); + assert(newSubstr != NULL); + + unsigned newSubstrLen = strlen(newSubstr); + if (newSubstrLen > substrLen) + str = (char*)realloc(str, len - substrLen + newSubstrLen + 1); + + // move tail part including zero + memmove(str + substrIdx + newSubstrLen, str + substrIdx + substrLen, len - substrIdx - substrLen + 1); + // copy new substring in place + memcpy(str + substrIdx, newSubstr, newSubstrLen); + + len = strlen(str); +} + +static void +SubstituteMacro(char*& line, int& len, const char* macroName, const char* substStr) +{ + assert(line != NULL); + assert(macroName != NULL); + assert(substStr != NULL); + unsigned macroNameLen = strlen(macroName); + while (const char* macroPos = strstr(line, macroName)) // we would replace all occurrences + ReplaceSubstr(line, len, macroPos - line, macroNameLen, substStr); +} + +static void +ProcessMacros(char*& line, int& len) +{ + SubstituteMacro(line, len, "${process_name}", KidName); + SubstituteMacro(line, len, "${process_number}", xitoa(KidIdentifier)); +} + +static void +trim_trailing_ws(char* str) +{ + assert(str != NULL); + unsigned i = strlen(str); + while ((i > 0) && xisspace(str[i - 1])) + --i; + str[i] = '\0'; +} + +static const char* +FindStatement(const char* line, const char* statement) +{ + assert(line != NULL); + assert(statement != NULL); + + const char* str = skip_ws(line); + unsigned len = strlen(statement); + if (strncmp(str, statement, len) == 0) { + str += len; + if (*str == '\0') + return str; + else if (xisspace(*str)) + return skip_ws(str); + } + + return NULL; +} + +static bool +StrToInt(const char* str, long& number) +{ + assert(str != NULL); + + char* end; + number = strtol(str, &end, 0); + + return (end != str) && (*end == '\0'); // returns true if string contains nothing except number +} + +static bool +EvalBoolExpr(const char* expr) +{ + assert(expr != NULL); + if (strcmp(expr, "true") == 0) { + return true; + } else if (strcmp(expr, "false") == 0) { + return false; + } else if (const char* equation = strchr(expr, '=')) { + const char* rvalue = skip_ws(equation + 1); + char* lvalue = (char*)xmalloc(equation - expr + 1); + xstrncpy(lvalue, expr, equation - expr + 1); + trim_trailing_ws(lvalue); + + long number1; + if (!StrToInt(lvalue, number1)) + fatalf("String is not a integer number: '%s'\n", lvalue); + long number2; + if (!StrToInt(rvalue, number2)) + fatalf("String is not a integer number: '%s'\n", rvalue); + + xfree(lvalue); + return number1 == number2; + } + fatalf("Unable to evaluate expression '%s'\n", expr); + return false; // this place cannot be reached +} + static int parseOneConfigFile(const char *file_name, unsigned int depth) { @@ -298,6 +403,7 @@ config_lineno = 0; + Vector if_states; while (fgets(config_input_line, BUFSIZ, fp)) { config_lineno++; @@ -357,20 +463,38 @@ continue; } + trim_trailing_ws(tmp_line); + ProcessMacros(tmp_line, tmp_line_len); debugs(3, 5, "Processing: '" << tmp_line << "'"); - /* Handle includes here */ - if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) { - err_count += parseManyConfigFiles(tmp_line + 8, depth + 1); - } else if (!parse_line(tmp_line)) { - debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'"); - err_count++; + if (const char* expr = FindStatement(tmp_line, "if")) { + if_states.push_back(EvalBoolExpr(expr)); // store last if-statement meaning + } else if (FindStatement(tmp_line, "endif")) { + if (!if_states.empty()) + if_states.pop_back(); // remove last if-statement meaning + else + fatalf("'endif' without 'if'\n"); + } else if (FindStatement(tmp_line, "else")) { + if (!if_states.empty()) + if_states.back() = !if_states.back(); + else + fatalf("'else' without 'if'\n"); + } else if (if_states.empty() || if_states.back()) { // test last if-statement meaning if present + /* Handle includes here */ + if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) { + err_count += parseManyConfigFiles(tmp_line + 8, depth + 1); + } else if (!parse_line(tmp_line)) { + debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'"); + err_count++; + } } safe_free(tmp_line); tmp_line_len = 0; } + if (!if_states.empty()) + fatalf("if-statement without 'endif'\n"); if (is_pipe) { int ret = pclose(fp); === modified file 'src/cf.data.pre' --- src/cf.data.pre 2010-03-03 09:38:49 +0000 +++ src/cf.data.pre 2010-03-26 13:52:36 +0000 @@ -57,6 +57,43 @@ This arbitrary restriction is to prevent recursive include references from causing Squid entering an infinite loop whilst trying to load configuration files. + + + Conditional configuration + + If-statements can be used to make configuration directives + depend on conditions: + + if + ... regular configuration directives ... + [else + ... regular configuration directives ...] + endif + + The else part is optional. The keywords "if", "else", and "endif" + must be typed on their own lines, as if they were regular + configuration directives. + + These individual conditions types are supported: + + true + Always evaluates to true. + false + Always evaluates to false. + = + Equality comparison of two integer numbers. + + + SMP-Related Macros + + The following SMP-related preprocessor macros can be used. + + ${process_name} expands to the current Squid process "name" + (e.g., squid1, squid2, or cache1). + + ${process_number} expands to the current Squid process + identifier, which is an integer number (e.g., 1, 2, 3) unique + across all Squid processes. COMMENT_END COMMENT_START @@ -6807,4 +6844,15 @@ Whether to lookup the EUI or MAC address of a connected client. DOC_END +NAME: main_processes +TYPE: int +LOC: Config.main_processes +DEFAULT: 1 +DOC_START + Number of main Squid processes to fork. + 0: "no daemon" mode, like running "squid -N ..." + 1: "no SMP" mode, start one main Squid process daemon (default) + N: start N main Squid process daemons +DOC_END + EOF === modified file 'src/client_side.cc' --- src/client_side.cc 2010-01-29 01:13:09 +0000 +++ src/client_side.cc 2010-07-01 21:20:38 +0000 @@ -102,6 +102,7 @@ #include "ident/Config.h" #include "ident/Ident.h" #include "ip/IpIntercept.h" +#include "ipc/StartListening.h" #include "MemBuf.h" #include "MemObject.h" #include "ProtoPort.h" @@ -113,6 +114,33 @@ #define comm_close comm_lingering_close #endif +/// dials clientHttpConnectionOpened or clientHttpsConnectionOpened call +class ListeningStartedDialer: public CallDialer, public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo, http_port_list *portCfg); + ListeningStartedDialer(Handler aHandler, http_port_list *aPortCfg): + handler(aHandler), portCfg(aPortCfg) {} + + virtual void print(std::ostream &os) const { startPrint(os) << + ", port=" << (void*)portCfg << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo, portCfg); } + +public: + Handler handler; + +private: + http_port_list *portCfg; ///< from Config.Sockaddr.http +}; + + +static void clientHttpConnectionOpened(int fd, int errNo, http_port_list *s); +#if USE_SSL +static void clientHttpsConnectionOpened(int fd, int errNo, http_port_list *s); +#endif + /* our socket-related context */ @@ -3329,12 +3357,39 @@ #endif /* USE_SSL */ +/// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed +static bool +OpenedHttpSocket(int fd, const char *msgIfFail) +{ + if (fd < 0) { + Must(NHttpSockets > 0); // we tried to open some + --NHttpSockets; // there will be fewer sockets than planned + Must(HttpSockets[NHttpSockets] < 0); // no extra fds received + + if (!NHttpSockets) // we could not open any listen sockets at all + fatal(msgIfFail); + + return false; + } + return true; +} + +/// find any unused HttpSockets[] slot and store fd there or return false +static bool +AddOpenedHttpSocket(int fd) +{ + bool found = false; + for (int i = 0; i < NHttpSockets && !found; i++) { + if ((found = HttpSockets[i] < 0)) + HttpSockets[i] = fd; + } + return found; +} static void clientHttpConnectionsOpen(void) { http_port_list *s = NULL; - int fd = -1; #if USE_SSL int bumpCount = 0; // counts http_ports with sslBump option #endif @@ -3358,18 +3413,34 @@ /* AYJ: 2009-12-27: bit bumpy. new ListenStateData(...) should be doing all the Comm:: stuff ... */ - enter_suid(); - - if (s->spoof_client_ip) { - fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, (COMM_NONBLOCKING|COMM_TRANSPARENT), "HTTP Socket"); - } else { - fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, COMM_NONBLOCKING, "HTTP Socket"); - } - - leave_suid(); - - if (fd < 0) - continue; + const int openFlags = COMM_NONBLOCKING | + (s->spoof_client_ip ? COMM_TRANSPARENT : 0); + + AsyncCall::Pointer callback = asyncCall(33,2, + "clientHttpConnectionOpened", + ListeningStartedDialer(&clientHttpConnectionOpened, s)); + Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->s, openFlags, + Ipc::fdnHttpSocket, callback); + + HttpSockets[NHttpSockets++] = -1; // set in clientHttpConnectionOpened + } + +#if USE_SSL + if (bumpCount && !Config.accessList.ssl_bump) + debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " << + std::endl << "\tssl_bump ACL configured. No requests will be " << + "bumped."); +#endif +} + +/// process clientHttpConnectionsOpen result +static void +clientHttpConnectionOpened(int fd, int, http_port_list *s) +{ + if (!OpenedHttpSocket(fd, "Cannot open HTTP Port")) + return; + + Must(s); AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpAccept)", CommAcceptCbPtrFun(httpAccept, s)); @@ -3384,15 +3455,7 @@ << " HTTP connections at " << s->s << ", FD " << fd << "." ); - HttpSockets[NHttpSockets++] = fd; - } - -#if USE_SSL - if (bumpCount && !Config.accessList.ssl_bump) - debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " << - std::endl << "\tssl_bump ACL configured. No requests will be " << - "bumped."); -#endif + Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for } #if USE_SSL @@ -3400,7 +3463,6 @@ clientHttpsConnectionsOpen(void) { https_port_list *s; - int fd; for (s = Config.Sockaddr.https; s; s = (https_port_list *)s->http.next) { if (MAXHTTPPORTS == NHttpSockets) { @@ -3415,25 +3477,33 @@ continue; } - enter_suid(); - fd = comm_open_listener(SOCK_STREAM, - IPPROTO_TCP, - s->http.s, - COMM_NONBLOCKING, "HTTPS Socket"); - leave_suid(); - - if (fd < 0) - continue; + AsyncCall::Pointer call = asyncCall(33, 2, "clientHttpsConnectionOpened", + ListeningStartedDialer(&clientHttpsConnectionOpened, &s->http)); + + Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->http.s, COMM_NONBLOCKING, + Ipc::fdnHttpsSocket, call); + + HttpSockets[NHttpSockets++] = -1; + } +} + +/// process clientHttpsConnectionsOpen result +static void +clientHttpsConnectionOpened(int fd, int, http_port_list *s) +{ + if (!OpenedHttpSocket(fd, "Cannot open HTTPS Port")) + return; + + Must(s); AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpsAccept)", CommAcceptCbPtrFun(httpsAccept, s)); s->listener = new Comm::ListenStateData(fd, call, true); - debugs(1, 1, "Accepting HTTPS connections at " << s->http.s << ", FD " << fd << "."); + debugs(1, 1, "Accepting HTTPS connections at " << s->s << ", FD " << fd << "."); - HttpSockets[NHttpSockets++] = fd; - } + Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for } #endif @@ -3447,7 +3517,7 @@ #endif if (NHttpSockets < 1) - fatal("Cannot open HTTP Port"); + fatal("No HTTP or HTTPS ports configured"); } void === modified file 'src/comm.cc' --- src/comm.cc 2010-01-02 10:17:00 +0000 +++ src/comm.cc 2010-05-04 15:34:46 +0000 @@ -71,6 +71,8 @@ static void commStopHalfClosedMonitor(int fd); static IOCB commHalfClosedReader; +static void comm_init_opened(int new_socket, IpAddress &addr, unsigned char TOS, const char *note, struct addrinfo *AI); +static int comm_apply_flags(int new_socket, IpAddress &addr, int flags, struct addrinfo *AI); struct comm_io_callback_t { @@ -687,7 +689,6 @@ const char *note) { int new_socket; - fde *F = NULL; int tos = 0; struct addrinfo *AI = NULL; @@ -743,6 +744,29 @@ #endif + comm_init_opened(new_socket, addr, TOS, note, AI); + new_socket = comm_apply_flags(new_socket, addr, flags, AI); + + addr.FreeAddrInfo(AI); + + PROF_stop(comm_open); + + return new_socket; +} + +/// update FD tables after a local or remote (IPC) comm_openex(); +void +comm_init_opened(int new_socket, + IpAddress &addr, + unsigned char TOS, + const char *note, + struct addrinfo *AI) +{ + assert(new_socket >= 0); + assert(AI); + + fde *F = NULL; + /* update fdstat */ debugs(5, 5, "comm_open: FD " << new_socket << " is a new socket"); @@ -760,6 +784,19 @@ F->tos = TOS; F->sock_family = AI->ai_family; +} + +/// apply flags after a local comm_open*() call; +/// returns new_socket or -1 on error +static int +comm_apply_flags(int new_socket, + IpAddress &addr, + int flags, + struct addrinfo *AI) +{ + assert(new_socket >= 0); + assert(AI); + const int sock_type = AI->ai_socktype; if (!(flags & COMM_NOCLOEXEC)) commSetCloseOnExec(new_socket); @@ -790,18 +827,14 @@ if (commBind(new_socket, *AI) != COMM_OK) { comm_close(new_socket); - addr.FreeAddrInfo(AI); return -1; - PROF_stop(comm_open); } } - addr.FreeAddrInfo(AI); - if (flags & COMM_NONBLOCKING) if (commSetNonBlocking(new_socket) == COMM_ERROR) { + comm_close(new_socket); return -1; - PROF_stop(comm_open); } #ifdef TCP_NODELAY @@ -813,11 +846,50 @@ if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM) commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz); - PROF_stop(comm_open); - return new_socket; } +void +comm_import_opened(int fd, + IpAddress &addr, + int flags, + const char *note, + struct addrinfo *AI) +{ + debugs(5, 2, HERE << " FD " << fd << " at " << addr); + assert(fd >= 0); + assert(AI); + + comm_init_opened(fd, addr, 0, note, AI); + + if (!(flags & COMM_NOCLOEXEC)) + fd_table[fd].flags.close_on_exec = 1; + + if (addr.GetPort() > (u_short) 0) { +#ifdef _SQUID_MSWIN_ + if (sock_type != SOCK_DGRAM) +#endif + fd_table[fd].flags.nolinger = 1; + } + + if ((flags & COMM_TRANSPARENT)) + fd_table[fd].flags.transparent = 1; + + if (flags & COMM_NONBLOCKING) + fd_table[fd].flags.nonblocking = 1; + +#ifdef TCP_NODELAY + if (AI->ai_socktype == SOCK_STREAM) + fd_table[fd].flags.nodelay = 1; +#endif + + /* no fd_table[fd].flags. updates needed for these conditions: + * if ((flags & COMM_REUSEADDR)) ... + * if ((flags & COMM_DOBIND) ...) ... + */ +} + + CBDATA_CLASS_INIT(ConnectStateData); void * @@ -2409,3 +2481,97 @@ return EVENT_ERROR; }; } + +/// Create a unix-domain socket (UDS) that only supports FD_MSGHDR I/O. +int +comm_open_uds(int sock_type, + int proto, + struct sockaddr_un* addr, + int flags) +{ + // TODO: merge with comm_openex() when IpAddress becomes NetAddress + + int new_socket; + + PROF_start(comm_open); + /* Create socket for accepting new connections. */ + statCounter.syscalls.sock.sockets++; + + /* Setup the socket addrinfo details for use */ + struct addrinfo AI; + AI.ai_flags = 0; + AI.ai_family = PF_UNIX; + AI.ai_socktype = sock_type; + AI.ai_protocol = proto; + AI.ai_addrlen = SUN_LEN(addr); + AI.ai_addr = (sockaddr*)addr; + AI.ai_canonname = NULL; + AI.ai_next = NULL; + + debugs(50, 3, HERE << "Attempt open socket for: " << addr->sun_path); + + if ((new_socket = socket(AI.ai_family, AI.ai_socktype, AI.ai_protocol)) < 0) { + /* Increase the number of reserved fd's if calls to socket() + * are failing because the open file table is full. This + * limits the number of simultaneous clients */ + + if (limitError(errno)) { + debugs(50, DBG_IMPORTANT, HERE << "socket failure: " << xstrerror()); + fdAdjustReserved(); + } else { + debugs(50, DBG_CRITICAL, HERE << "socket failure: " << xstrerror()); + } + + PROF_stop(comm_open); + return -1; + } + + debugs(50, 3, HERE "Opened UDS FD " << new_socket << " : family=" << AI.ai_family << ", type=" << AI.ai_socktype << ", protocol=" << AI.ai_protocol); + + /* update fdstat */ + debugs(50, 5, HERE << "FD " << new_socket << " is a new socket"); + + assert(!isOpen(new_socket)); + fd_open(new_socket, FD_MSGHDR, NULL); + + fdd_table[new_socket].close_file = NULL; + + fdd_table[new_socket].close_line = 0; + + fd_table[new_socket].sock_family = AI.ai_family; + + if (!(flags & COMM_NOCLOEXEC)) + commSetCloseOnExec(new_socket); + + if (flags & COMM_REUSEADDR) + commSetReuseAddr(new_socket); + + if (flags & COMM_NONBLOCKING) { + if (commSetNonBlocking(new_socket) != COMM_OK) { + comm_close(new_socket); + PROF_stop(comm_open); + return -1; + } + } + + if (flags & COMM_DOBIND) { + if (commBind(new_socket, AI) != COMM_OK) { + comm_close(new_socket); + PROF_stop(comm_open); + return -1; + } + } + +#ifdef TCP_NODELAY + if (sock_type == SOCK_STREAM) + commSetTcpNoDelay(new_socket); + +#endif + + if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM) + commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz); + + PROF_stop(comm_open); + + return new_socket; +} === modified file 'src/comm.h' --- src/comm.h 2009-12-31 02:35:01 +0000 +++ src/comm.h 2010-05-02 01:42:47 +0000 @@ -55,6 +55,9 @@ SQUIDCEXTERN void comm_exit(void); SQUIDCEXTERN int comm_open(int, int, IpAddress &, int, const char *note); +SQUIDCEXTERN int comm_open_uds(int sock_type, int proto, struct sockaddr_un* addr, int flags); +/// update Comm state after getting a comm_open() FD from another process +SQUIDCEXTERN void comm_import_opened(int fd, IpAddress &addr, int flags, const char *note, struct addrinfo *AI); /** * Open a port specially bound for listening or sending through a specific port. === modified file 'src/debug.cc' --- src/debug.cc 2009-11-22 20:37:27 +0000 +++ src/debug.cc 2010-07-01 22:01:14 +0000 @@ -36,6 +36,7 @@ #include "Debug.h" #include "SquidTime.h" #include "util.h" +#include "ipc/Kids.h" /* for shutting_down flag in xassert() */ #include "globals.h" @@ -52,6 +53,7 @@ static char *debug_log_file = NULL; static int Ctx_Lock = 0; static const char *debugLogTime(void); +static const char *debugLogKid(void); static void ctx_print(void); #if HAVE_SYSLOG #ifdef LOG_LOCAL4 @@ -117,8 +119,9 @@ va_start(args2, format); va_start(args3, format); - snprintf(f, BUFSIZ, "%s| %s", + snprintf(f, BUFSIZ, "%s%s| %s", debugLogTime(), + debugLogKid(), format); _db_print_file(f, args1); @@ -543,6 +546,19 @@ return buf; } +static const char * +debugLogKid(void) +{ + if (KidIdentifier != 0) { + static char buf[16]; + if (!*buf) // optimization: fill only once after KidIdentifier is set + snprintf(buf, sizeof(buf), " kid%d", KidIdentifier); + return buf; + } + + return ""; +} + void xassert(const char *msg, const char *file, int line) { === modified file 'src/enums.h' --- src/enums.h 2010-01-01 21:16:57 +0000 +++ src/enums.h 2010-04-29 20:12:03 +0000 @@ -69,6 +69,7 @@ FD_FILE, FD_SOCKET, FD_PIPE, + FD_MSGHDR, FD_UNKNOWN }; === modified file 'src/fd.cc' --- src/fd.cc 2009-03-06 13:26:57 +0000 +++ src/fd.cc 2010-04-29 20:12:03 +0000 @@ -45,6 +45,9 @@ int socket_write_method(int, const char *, int); int file_read_method(int, char *, int); int file_write_method(int, const char *, int); +#else +int msghdr_read_method(int, char *, int); +int msghdr_write_method(int, const char *, int); #endif const char *fdTypeStr[] = { @@ -53,6 +56,7 @@ "File", "Socket", "Pipe", + "MsgHdr", "Unknown" }; @@ -169,6 +173,24 @@ return i; } +int +msghdr_read_method(int fd, char *buf, int len) +{ + PROF_start(read); + const int i = recvmsg(fd, reinterpret_cast(buf), MSG_DONTWAIT); + PROF_stop(read); + return i; +} + +int +msghdr_write_method(int fd, const char *buf, int len) +{ + PROF_start(write); + const int i = sendmsg(fd, reinterpret_cast(buf), MSG_NOSIGNAL); + PROF_stop(write); + return i > 0 ? len : i; // len is imprecise but the caller expects a match +} + #endif void @@ -213,9 +235,18 @@ } #else - F->read_method = &default_read_method; - - F->write_method = &default_write_method; + switch (type) { + + case FD_MSGHDR: + F->read_method = &msghdr_read_method; + F->write_method = &msghdr_write_method; + break; + + default: + F->read_method = &default_read_method; + F->write_method = &default_write_method; + break; + } #endif === modified file 'src/htcp.cc' --- src/htcp.cc 2010-02-11 23:48:51 +0000 +++ src/htcp.cc 2010-06-14 21:22:01 +0000 @@ -46,6 +46,24 @@ #include "http.h" #include "icmp/net_db.h" #include "AccessLogEntry.h" +#include "ipc/StartListening.h" + +/// dials htcpIncomingConnectionOpened call +class HtcpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo); + HtcpListeningStartedDialer(Handler aHandler): handler(aHandler) {} + + virtual void print(std::ostream &os) const { startPrint(os) << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo); } + +public: + Handler handler; +}; typedef struct _Countstr Countstr; @@ -225,6 +243,8 @@ RR_RESPONSE }; +static void htcpIncomingConnectionOpened(int fd, int errNo); + static u_int32_t msg_id_counter = 0; static int htcpInSocket = -1; static int htcpOutSocket = -1; @@ -1483,20 +1503,15 @@ IpAddress incomingAddr = Config.Addrs.udp_incoming; incomingAddr.SetPort(Config.Port.htcp); - enter_suid(); - htcpInSocket = comm_open_listener(SOCK_DGRAM, + AsyncCall::Pointer call = asyncCall(31, 2, + "htcpIncomingConnectionOpened", + HtcpListeningStartedDialer(&htcpIncomingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, incomingAddr, COMM_NONBLOCKING, - "HTCP Socket"); - leave_suid(); - - if (htcpInSocket < 0) - fatal("Cannot open HTCP Socket"); - - commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0); - - debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << "."); + Ipc::fdnInHtcpSocket, call); if (!Config.Addrs.udp_outgoing.IsNoAddr()) { IpAddress outgoingAddr = Config.Addrs.udp_outgoing; @@ -1518,8 +1533,6 @@ debugs(31, 1, "Outgoing HTCP messages on port " << Config.Port.htcp << ", FD " << htcpOutSocket << "."); fd_note(htcpInSocket, "Incoming HTCP socket"); - } else { - htcpOutSocket = htcpInSocket; } if (!htcpDetailPool) { @@ -1527,6 +1540,22 @@ } } +static void +htcpIncomingConnectionOpened(int fd, int errNo) +{ + htcpInSocket = fd; + + if (htcpInSocket < 0) + fatal("Cannot open HTCP Socket"); + + commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0); + + debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << "."); + + if (Config.Addrs.udp_outgoing.IsNoAddr()) + htcpOutSocket = htcpInSocket; +} + int htcpQuery(StoreEntry * e, HttpRequest * req, peer * p) { === modified file 'src/icp_v2.cc' --- src/icp_v2.cc 2009-11-09 11:25:11 +0000 +++ src/icp_v2.cc 2010-06-14 21:22:01 +0000 @@ -48,8 +48,31 @@ #include "SwapDir.h" #include "icmp/net_db.h" #include "ip/IpAddress.h" +#include "ipc/StartListening.h" #include "rfc1738.h" +/// dials icpIncomingConnectionOpened call +class IcpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo, IpAddress& addr); + IcpListeningStartedDialer(Handler aHandler, IpAddress& anAddr): + handler(aHandler), addr(anAddr) {} + + virtual void print(std::ostream &os) const { startPrint(os) << + ", address=" << addr << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo, addr); } + +public: + Handler handler; + IpAddress addr; +}; + +static void icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr); + /// \ingroup ServerProtocolICPInternal2 static void icpLogIcp(const IpAddress &, log_type, int, const char *, int); @@ -656,35 +679,22 @@ struct addrinfo *xai = NULL; int x; - wordlist *s; if ((port = Config.Port.icp) <= 0) return; - enter_suid(); - addr = Config.Addrs.udp_incoming; addr.SetPort(port); - theInIcpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(12, 2, + "icpIncomingConnectionOpened", + IcpListeningStartedDialer(&icpIncomingConnectionOpened, addr)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, addr, COMM_NONBLOCKING, - "ICP Socket"); - leave_suid(); - - if (theInIcpConnection < 0) - fatal("Cannot open ICP Port"); - - commSetSelect(theInIcpConnection, - COMM_SELECT_READ, - icpHandleUdp, - NULL, - 0); - - for (s = Config.mcast_group_list; s; s = s->next) - ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL); - - debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << "."); + Ipc::fdnInIcpSocket, call); addr.SetEmpty(); // clear for next use. addr = Config.Addrs.udp_outgoing; @@ -710,10 +720,6 @@ debugs(12, 1, "Outgoing ICP messages on port " << addr.GetPort() << ", FD " << theOutIcpConnection << "."); fd_note(theOutIcpConnection, "Outgoing ICP socket"); - - fd_note(theInIcpConnection, "Incoming ICP socket"); - } else { - theOutIcpConnection = theInIcpConnection; } theOutICPAddr.SetEmpty(); @@ -730,6 +736,31 @@ theOutICPAddr.FreeAddrInfo(xai); } +static void +icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr) +{ + theInIcpConnection = fd; + + if (theInIcpConnection < 0) + fatal("Cannot open ICP Port"); + + commSetSelect(theInIcpConnection, + COMM_SELECT_READ, + icpHandleUdp, + NULL, + 0); + + for (const wordlist *s = Config.mcast_group_list; s; s = s->next) + ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL); + + debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << "."); + + fd_note(theInIcpConnection, "Incoming ICP socket"); + + if (Config.Addrs.udp_outgoing.IsNoAddr()) + theOutIcpConnection = theInIcpConnection; +} + /** * icpConnectionShutdown only closes the 'in' socket if it is * different than the 'out' socket. === modified file 'src/ip/IpAddress.cc' --- src/ip/IpAddress.cc 2010-03-01 01:13:17 +0000 +++ src/ip/IpAddress.cc 2010-05-02 01:20:05 +0000 @@ -879,6 +879,11 @@ return 0; } +int IpAddress::compareWhole(const IpAddress &rhs) const +{ + return memcmp(this, &rhs, sizeof(*this)); +} + bool IpAddress::operator ==(const IpAddress &s) const { return (0 == matchIPAddr(s)); @@ -1041,9 +1046,9 @@ p += ToHostname(p, blen); - if (m_SocketAddr.sin6_port > 0 && p < (buf+blen-6) ) { - /* 6 is max length of expected ':port' (short int) */ - snprintf(p, 6,":%d", GetPort() ); + if (m_SocketAddr.sin6_port > 0 && p <= (buf+blen-7) ) { + // ':port' (short int) needs at most 6 bytes plus 1 for 0-terminator + snprintf(p, 7, ":%d", GetPort() ); } // force a null-terminated string === modified file 'src/ip/IpAddress.h' --- src/ip/IpAddress.h 2010-02-28 22:04:23 +0000 +++ src/ip/IpAddress.h 2010-05-02 01:20:05 +0000 @@ -326,6 +326,13 @@ */ int matchIPAddr(const IpAddress &rhs) const; + /** Compare taking IP, port, protocol, etc. into account. Returns an + integer less than, equal to, or greater than zero if the object + is found, respectively, to be less than, to match, or to be greater + than rhs. The exact ordering algorithm is not specified and may change. + */ + int compareWhole(const IpAddress &rhs) const; + /** * Get RFC 3493 addrinfo structure from the IpAddress data * for protocol-neutral socket operations. === added directory 'src/ipc' === added file 'src/ipc/Coordinator.cc' --- src/ipc/Coordinator.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Coordinator.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,172 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "ipc/Coordinator.h" +#include "ipc/FdNotes.h" +#include "ipc/SharedListen.h" + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, Coordinator); +Ipc::Coordinator* Ipc::Coordinator::TheInstance = NULL; + + +Ipc::Coordinator::Coordinator(): + Port(coordinatorAddr) +{ +} + +void Ipc::Coordinator::start() +{ + Port::start(); +} + +Ipc::StrandCoord* Ipc::Coordinator::findStrand(int kidId) +{ + typedef Strands::iterator SI; + for (SI iter = strands.begin(); iter != strands.end(); ++iter) { + if (iter->kidId == kidId) + return &(*iter); + } + return NULL; +} + +void Ipc::Coordinator::registerStrand(const StrandCoord& strand) +{ + if (StrandCoord* found = findStrand(strand.kidId)) + *found = strand; + else + strands.push_back(strand); +} + +void Ipc::Coordinator::receive(const TypedMsgHdr& message) +{ + switch (message.type()) { + case mtRegistration: + debugs(54, 6, HERE << "Registration request"); + handleRegistrationRequest(StrandCoord(message)); + break; + + case mtSharedListenRequest: + debugs(54, 6, HERE << "Shared listen request"); + handleSharedListenRequest(SharedListenRequest(message)); + break; + + case mtDescriptorGet: + debugs(54, 6, HERE << "Descriptor get request"); + handleDescriptorGet(Descriptor(message)); + break; + + default: + debugs(54, 1, HERE << "Unhandled message type: " << message.type()); + break; + } +} + +void Ipc::Coordinator::handleRegistrationRequest(const StrandCoord& strand) +{ + registerStrand(strand); + + // send back an acknowledgement; TODO: remove as not needed? + TypedMsgHdr message; + strand.pack(message); + SendMessage(MakeAddr(strandAddrPfx, strand.kidId), message); +} + +void +Ipc::Coordinator::handleSharedListenRequest(const SharedListenRequest& request) +{ + debugs(54, 4, HERE << "kid" << request.requestorId << + " needs shared listen FD for " << request.params.addr); + Listeners::const_iterator i = listeners.find(request.params); + int errNo = 0; + const int sock = (i != listeners.end()) ? + i->second : openListenSocket(request, errNo); + + debugs(54, 3, HERE << "sending shared listen FD " << sock << " for " << + request.params.addr << " to kid" << request.requestorId << + " mapId=" << request.mapId); + + SharedListenResponse response(sock, errNo, request.mapId); + TypedMsgHdr message; + response.pack(message); + SendMessage(MakeAddr(strandAddrPfx, request.requestorId), message); +} + +int +Ipc::Coordinator::openListenSocket(const SharedListenRequest& request, + int &errNo) +{ + const OpenListenerParams &p = request.params; + + debugs(54, 6, HERE << "opening listen FD at " << p.addr << " for kid" << + request.requestorId); + + IpAddress addr = p.addr; // comm_open_listener may modify it + + enter_suid(); + const int sock = comm_open_listener(p.sock_type, p.proto, addr, p.flags, + FdNote(p.fdNote)); + errNo = (sock >= 0) ? 0 : errno; + leave_suid(); + + // cache positive results + if (sock >= 0) + listeners[request.params] = sock; + + return sock; +} + +void Ipc::Coordinator::handleDescriptorGet(const Descriptor& request) +{ + // XXX: hack: create descriptor here + char buffer[64]; + snprintf(buffer, sizeof(buffer), "/tmp/squid_shared_file.txt"); + static int fd = -1; + if (fd < 0) { + fd = open(buffer, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + int n = snprintf(buffer, sizeof(buffer), "coord: created %d\n", fd); + ssize_t bytes = write(fd, buffer, n); + Must(bytes == n); + debugs(54, 6, "Created FD " << fd << " for kid" << request.fromKid); + } else { + int n = snprintf(buffer, sizeof(buffer), "coord: updated %d\n", fd); + ssize_t bytes = write(fd, buffer, n); + Must(bytes == n); + } + + debugs(54, 6, "Sending FD " << fd << " to kid" << request.fromKid); + + Descriptor response(-1, fd); + TypedMsgHdr message; + response.pack(message); + SendMessage(MakeAddr(strandAddrPfx, request.fromKid), message); + + // XXX: close(fd); fd should be opened until the message has not reached rec iver +} + +void Ipc::Coordinator::broadcastSignal(int sig) const +{ + typedef Strands::const_iterator SCI; + for (SCI iter = strands.begin(); iter != strands.end(); ++iter) { + debugs(54, 5, HERE << "signal " << sig << " to kid" << iter->kidId << + ", PID=" << iter->pid); + kill(iter->pid, sig); + } +} + +Ipc::Coordinator* Ipc::Coordinator::Instance() +{ + if (!TheInstance) + TheInstance = new Coordinator; + // XXX: if the Coordinator job quits, this pointer will become invalid + // we could make Coordinator death fatal, except during exit, but since + // Strands do not re-register, even process death would be pointless. + return TheInstance; +} === added file 'src/ipc/Coordinator.h' --- src/ipc/Coordinator.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Coordinator.h 2010-07-01 22:58:45 +0000 @@ -0,0 +1,66 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_COORDINATOR_H +#define SQUID_IPC_COORDINATOR_H + + +#include "Array.h" +#include +#include "ipc/Port.h" +#include "ipc/Messages.h" +#include "ipc/SharedListen.h" + +namespace Ipc +{ + +/// Coordinates shared activities of Strands (Squid processes or threads) +class Coordinator: public Port +{ +public: + static Coordinator* Instance(); + +public: + Coordinator(); + + void broadcastSignal(int sig) const; ///< send sig to registered strands + +protected: + virtual void start(); // Port (AsyncJob) API + virtual void receive(const TypedMsgHdr& message); // Port API + + StrandCoord* findStrand(int kidId); ///< registered strand or NULL + void registerStrand(const StrandCoord &); ///< adds or updates existing + void handleRegistrationRequest(const StrandCoord &); ///< register,ACK + + /// returns cached socket or calls openListenSocket() + void handleSharedListenRequest(const SharedListenRequest& request); + void handleDescriptorGet(const Descriptor& request); + + /// calls comm_open_listener() + int openListenSocket(const SharedListenRequest& request, int &errNo); + +private: + typedef Vector Strands; ///< unsorted strands + Strands strands; ///< registered processes and threads + + typedef std::map Listeners; ///< params:fd map + Listeners listeners; ///< cached comm_open_listener() results + + static Coordinator* TheInstance; ///< the only class instance in existence + +private: + Coordinator(const Coordinator&); // not implemented + Coordinator& operator =(const Coordinator&); // not implemented + + CBDATA_CLASS2(Coordinator); +}; + + +} // namespace Ipc + +#endif /* SQUID_IPC_COORDINATOR_H */ === added file 'src/ipc/FdNotes.cc' --- src/ipc/FdNotes.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/FdNotes.cc 2010-06-14 21:22:01 +0000 @@ -0,0 +1,31 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "Debug.h" +#include "ipc/FdNotes.h" + + +const char * +Ipc::FdNote(int fdNoteId) +{ + static const char *FdNotes[Ipc::fdnEnd] = { + "None", // fdnNone + "HTTP Socket", // fdnHttpSocket + "HTTPS Socket", // fdnHttpsSocket + "Incoming SNMP Socket", // fdnInSnmpSocket + "Outgoing SNMP Socket", // fdnOutSnmpSocket + "Incoming ICP Socket", // fdnInIcpSocket + "Incoming HTCP Socket" // fdnInHtcpSocket + }; + + if (fdnNone < fdNoteId && fdNoteId < fdnEnd) + return FdNotes[fdNoteId]; + + debugs(54, 1, HERE << "salvaged bug: wrong fd_note ID: " << fdNoteId); + return FdNotes[fdnNone]; +} === added file 'src/ipc/FdNotes.h' --- src/ipc/FdNotes.h 1970-01-01 00:00:00 +0000 +++ src/ipc/FdNotes.h 2010-06-14 21:22:01 +0000 @@ -0,0 +1,26 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_FD_NOTES_H +#define SQUID_IPC_FD_NOTES_H + +namespace Ipc +{ + +/// We cannot send char* FD notes to other processes. Pass int IDs and convert. + +/// fd_note() label ID +typedef enum { fdnNone, fdnHttpSocket, fdnHttpsSocket, + fdnInSnmpSocket, fdnOutSnmpSocket, + fdnInIcpSocket, fdnInHtcpSocket, fdnEnd } FdNoteId; + +extern const char *FdNote(int fdNodeId); ///< converts FdNoteId into a string + +} // namespace Ipc; + + +#endif /* SQUID_IPC_FD_NOTES_H */ === added file 'src/ipc/Kid.cc' --- src/ipc/Kid.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Kid.cc 2010-03-30 21:54:40 +0000 @@ -0,0 +1,122 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Kid.h" + +Kid::Kid(): + badFailures(0), + pid(-1), + startTime(0), + isRunning(false) +{ +} + +Kid::Kid(const String& kid_name): + theName(kid_name), + badFailures(0), + pid(-1), + startTime(0), + isRunning(false) +{ +} + +/// called when this kid got started, records PID +void Kid::start(pid_t cpid) +{ + assert(!running()); + assert(cpid > 0); + + isRunning = true; + pid = cpid; + time(&startTime); +} + +/// called when kid terminates, sets exiting status +void Kid::stop(status_type exitStatus) +{ + assert(running()); + assert(startTime != 0); + + isRunning = false; + + time_t stop_time; + time(&stop_time); + if ((stop_time - startTime) < fastFailureTimeLimit) + badFailures++; + else + badFailures = 0; // the failures are not "frequent" [any more] + + status = exitStatus; +} + +/// returns true if tracking of kid is stopped +bool Kid::running() const +{ + return isRunning; +} + +/// returns current pid for a running kid and last pid for a stopped kid +pid_t Kid::getPid() const +{ + assert(pid > 0); + return pid; +} + +/// whether the failures are "repeated and frequent" +bool Kid::hopeless() const +{ + return badFailures > badFailureLimit; +} + +/// returns true if the process terminated normally +bool Kid::calledExit() const +{ + return (pid > 0) && !running() && WIFEXITED(status); +} + +/// returns the exit status of the process +int Kid::exitStatus() const +{ + return WEXITSTATUS(status); +} + +/// whether the process exited with a given exit status code +bool Kid::calledExit(int code) const +{ + return calledExit() && (exitStatus() == code); +} + +/// whether the process exited with code 0 +bool Kid::exitedHappy() const +{ + return calledExit(0); +} + +/// returns true if the kid was terminated by a signal +bool Kid::signaled() const +{ + return (pid > 0) && !running() && WIFSIGNALED(status); +} + +/// returns the number of the signal that caused the kid to terminate +int Kid::termSignal() const +{ + return WTERMSIG(status); +} + +/// whether the process was terminated by a given signal +bool Kid::signaled(int sgnl) const +{ + return signaled() && (termSignal() == sgnl); +} + +/// returns kid name +const String& Kid::name() const +{ + return theName; +} === added file 'src/ipc/Kid.h' --- src/ipc/Kid.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Kid.h 2010-03-30 21:54:40 +0000 @@ -0,0 +1,85 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_IPC_KID_H +#define SQUID_IPC_KID_H + +#include "SquidString.h" + + +/// Squid child, including current forked process info and +/// info persistent across restarts +class Kid +{ +public: +#ifdef _SQUID_NEXT_ + typedef union wait status_type; +#else + typedef int status_type; +#endif + + /// keep restarting until the number of bad failures exceed this limit + enum { badFailureLimit = 4 }; + + /// slower start failures are not "frequent enough" to be counted as "bad" + enum { fastFailureTimeLimit = 10 }; // seconds + +public: + Kid(); + + Kid(const String& kid_name); + + /// called when this kid got started, records PID + void start(pid_t cpid); + + /// called when kid terminates, sets exiting status + void stop(status_type exitStatus); + + /// returns true if tracking of kid is stopped + bool running() const; + + /// returns current pid for a running kid and last pid for a stopped kid + pid_t getPid() const; + + /// whether the failures are "repeated and frequent" + bool hopeless() const; + + /// returns true if the process terminated normally + bool calledExit() const; + + /// returns the exit status of the process + int exitStatus() const; + + /// whether the process exited with a given exit status code + bool calledExit(int code) const; + + /// whether the process exited with code 0 + bool exitedHappy() const; + + /// returns true if the kid was terminated by a signal + bool signaled() const; + + /// returns the number of the signal that caused the kid to terminate + int termSignal() const; + + /// whether the process was terminated by a given signal + bool signaled(int sgnl) const; + + /// returns kid name + const String& name() const; + +private: + // Information preserved across restarts + String theName; ///< process name + int badFailures; ///< number of "repeated frequent" failures + + // Information specific to a running or stopped kid + pid_t pid; ///< current (for a running kid) or last (for stopped kid) PID + time_t startTime; ///< last start time + bool isRunning; ///< whether the kid is assumed to be alive + status_type status; ///< exit status of a stopped kid +}; + +#endif /* SQUID_IPC_KID_H */ === added file 'src/ipc/Kids.cc' --- src/ipc/Kids.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Kids.cc 2010-04-26 07:09:03 +0000 @@ -0,0 +1,98 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Kids.h" + +Kids TheKids; +char KidName[NAME_MAX]; +int KidIdentifier; + +Kids::Kids() +{ +} + +/// maintain n kids +void Kids::init(size_t n) +{ + assert(n > 0); + + if (storage.size() > 0) + storage.clean(); + + storage.reserve(n); + + char kid_name[32]; + + // add Kid records for all n main strands + for (size_t i = 1; i <= n; ++i) { + snprintf(kid_name, sizeof(kid_name), "(squid-%d)", (int)i); + storage.push_back(Kid(kid_name)); + } + + // if coordination is needed, add a Kid record for Coordinator + if (n > 1) { + snprintf(kid_name, sizeof(kid_name), "(squid-coord-%d)", (int)(n + 1)); + storage.push_back(Kid(kid_name)); + } +} + +/// returns kid by pid +Kid* Kids::find(pid_t pid) +{ + assert(pid > 0); + assert(count() > 0); + + for (size_t i = 0; i < storage.size(); ++i) { + if (storage[i].getPid() == pid) + return &storage[i]; + } + return NULL; +} + +/// returns the kid by index, useful for kids iteration +Kid& Kids::get(size_t i) +{ + assert(i >= 0 && i < count()); + return storage[i]; +} + +/// whether all kids are hopeless +bool Kids::allHopeless() const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].hopeless()) + return false; + } + return true; +} + +/// whether all kids called exited happy +bool Kids::allExitedHappy() const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].exitedHappy()) + return false; + } + return true; +} + +/// whether all kids died from a given signal +bool Kids::allSignaled(int sgnl) const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].signaled(sgnl)) + return false; + } + return true; +} + +/// returns the number of kids +size_t Kids::count() const +{ + return storage.size(); +} === added file 'src/ipc/Kids.h' --- src/ipc/Kids.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Kids.h 2010-03-30 21:54:40 +0000 @@ -0,0 +1,55 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_IPC_KIDS_H +#define SQUID_IPC_KIDS_H + +#include "Array.h" +#include "ipc/Kid.h" + + +/// a collection of kids +class Kids +{ +public: + Kids (); + +private: + Kids (const Kids&); ///< not implemented + Kids& operator= (const Kids&); ///< not implemented + +public: + /// maintain n kids + void init(size_t n); + + /// returns kid by pid + Kid* find(pid_t pid); + + /// returns the kid by index, useful for kids iteration + Kid& get(size_t i); + + /// whether all kids are hopeless + bool allHopeless() const; + + /// whether all kids called exited happy + bool allExitedHappy() const; + + /// whether all kids died from a given signal + bool allSignaled(int sgnl) const; + + /// returns the number of kids + size_t count() const; + +private: + Vector storage; +}; + +extern Kids TheKids; ///< All kids being maintained + +extern char KidName[NAME_MAX]; ///< current Squid process name (e.g., squid2) +extern int KidIdentifier; ///< current Squid process number (e.g., 4) + + +#endif /* SQUID_IPC_KIDS_H */ === added file 'src/ipc/Makefile.am' --- src/ipc/Makefile.am 1970-01-01 00:00:00 +0000 +++ src/ipc/Makefile.am 2010-05-02 18:49:25 +0000 @@ -0,0 +1,30 @@ +include $(top_srcdir)/src/Common.am +include $(top_srcdir)/src/TestHeaders.am + +noinst_LTLIBRARIES = libipc.la + +libipc_la_SOURCES = \ + FdNotes.cc \ + FdNotes.h \ + Kid.cc \ + Kid.h \ + Kids.cc \ + Kids.h \ + Messages.cc \ + Messages.h \ + StartListening.cc \ + StartListening.h \ + SharedListen.cc \ + SharedListen.h \ + TypedMsgHdr.cc \ + TypedMsgHdr.h \ + Coordinator.cc \ + Coordinator.h \ + UdsOp.cc \ + UdsOp.h \ + Port.cc \ + Port.h \ + Strand.cc \ + Strand.h + +DEFS += -DDEFAULT_PREFIX=\"$(prefix)\" === added file 'src/ipc/Messages.cc' --- src/ipc/Messages.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Messages.cc 2010-04-30 18:01:29 +0000 @@ -0,0 +1,61 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "ipc/Messages.h" +#include "ipc/TypedMsgHdr.h" + + +Ipc::StrandCoord::StrandCoord(): kidId(-1), pid(0) +{ +} + +Ipc::StrandCoord::StrandCoord(int aKidId, pid_t aPid): kidId(aKidId), pid(aPid) +{ +} + +Ipc::StrandCoord::StrandCoord(const TypedMsgHdr &hdrMsg): kidId(-1), pid(0) +{ + hdrMsg.getData(mtRegistration, this, sizeof(*this)); +} + +void Ipc::StrandCoord::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtRegistration, this, sizeof(*this)); +} + + +Ipc::Descriptor::Descriptor(): fromKid(-1), fd(-1) +{ +} + +Ipc::Descriptor::Descriptor(int aFromKid, int aFd): fromKid(aFromKid), fd(aFd) +{ +} + +Ipc::Descriptor::Descriptor(const TypedMsgHdr &hdrMsg): fromKid(-1), fd(-1) +{ + if (hdrMsg.type() == mtDescriptorGet) { + hdrMsg.getData(mtDescriptorGet, this, sizeof(*this)); + fd = -1; + } else { + hdrMsg.getData(mtDescriptorPut, this, sizeof(*this)); + fd = hdrMsg.getFd(); + } +} + +void Ipc::Descriptor::pack(TypedMsgHdr &hdrMsg) const +{ + if (fd >= 0) { + hdrMsg.putData(mtDescriptorPut, this, sizeof(*this)); + hdrMsg.putFd(fd); + } else { + hdrMsg.putData(mtDescriptorGet, this, sizeof(*this)); + } +} === added file 'src/ipc/Messages.h' --- src/ipc/Messages.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Messages.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,57 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_MESSAGES_H +#define SQUID_IPC_MESSAGES_H + +#include +#include + +/// Declare IPC messages. These classes translate between high-level +/// information and low-level TypedMsgHdr (i.e., struct msghdr) buffers. + +namespace Ipc +{ + +class TypedMsgHdr; + +typedef enum { mtNone = 0, mtRegistration, + mtSharedListenRequest, mtSharedListenResponse, + mtDescriptorGet, mtDescriptorPut } MessageType; + +/// Strand location details +class StrandCoord { +public: + StrandCoord(); ///< unknown location + StrandCoord(int akidId, pid_t aPid); ///< from registrant + explicit StrandCoord(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int kidId; ///< internal Squid process number + pid_t pid; ///< OS process or thread identifier +}; + +/// a [socket] descriptor information +class Descriptor +{ +public: + Descriptor(); ///< unknown descriptor + Descriptor(int fromKid, int fd); ///< from descriptor sender or requestor + explicit Descriptor(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int fromKid; /// the source of this message + int fd; ///< raw descriptor value +}; + + +} // namespace Ipc; + + +#endif /* SQUID_IPC_MESSAGES_H */ === added file 'src/ipc/Port.cc' --- src/ipc/Port.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Port.cc 2010-04-29 22:35:11 +0000 @@ -0,0 +1,65 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "CommCalls.h" +#include "ipc/Port.h" + +const char Ipc::coordinatorAddr[] = DEFAULT_PREFIX "/var/run/coordinator.ipc"; +const char Ipc::strandAddrPfx[] = DEFAULT_PREFIX "/var/run/squid"; + + +Ipc::Port::Port(const String& aListenAddr): + UdsOp(aListenAddr) +{ + setOptions(COMM_NONBLOCKING | COMM_DOBIND); +} + +void Ipc::Port::start() +{ + UdsOp::start(); + listen(); +} + +void Ipc::Port::listen() +{ + debugs(54, 6, HERE); + buf.prepForReading(); + AsyncCall::Pointer readHandler = asyncCall(54, 6, "Ipc::Port::noteRead", + CommCbMemFunT(this, &Port::noteRead)); + comm_read(fd(), buf.raw(), buf.size(), readHandler); +} + +bool Ipc::Port::doneAll() const +{ + return false; // listen forever +} + +String Ipc::Port::MakeAddr(const char* pathAddr, int id) +{ + assert(id >= 0); + String addr = pathAddr; + addr.append('-'); + addr.append(xitoa(id)); + addr.append(".ipc"); + return addr; +} + +void Ipc::Port::noteRead(const CommIoCbParams& params) +{ + debugs(54, 6, HERE << "FD " << params.fd << " flag " << params.flag << + " [" << this << ']'); + if (params.flag == COMM_OK) { + assert(params.buf == buf.raw()); + receive(buf); + } + // TODO: if there was a fatal error on our socket, close the socket before + // trying to listen again and print a level-1 error message. + + listen(); +} === added file 'src/ipc/Port.h' --- src/ipc/Port.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Port.h 2010-04-29 20:12:03 +0000 @@ -0,0 +1,53 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_PORT_H +#define SQUID_IPC_PORT_H + + +#include "SquidString.h" +#include "ipc/UdsOp.h" + + +namespace Ipc +{ + + +/// Waits for and receives incoming IPC messages; kids handle the messages +class Port: public UdsOp +{ +public: + Port(const String &aListenAddr); + +protected: + /// calculates IPC message address for strand #id at path + static String MakeAddr(const char *path, int id); + + virtual void start() = 0; // UdsOp (AsyncJob) API; has body + virtual bool doneAll() const; // UdsOp (AsyncJob) API + + /// read the next incoming message + void listen(); + + /// handle IPC message just read + virtual void receive(const TypedMsgHdr& message) = 0; + +private: + void noteRead(const CommIoCbParams ¶ms); // Comm callback API + +private: + TypedMsgHdr buf; ///< msghdr struct filled by Comm +}; + + +extern const char coordinatorAddr[]; ///< where coordinator listens +extern const char strandAddrPfx[]; ///< strand's listening address prefix + +} // namespace Ipc + + +#endif /* SQUID_IPC_PORT_H */ === added file 'src/ipc/SharedListen.cc' --- src/ipc/SharedListen.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/SharedListen.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,149 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include +#include "comm.h" +#include "ipc/Port.h" +#include "ipc/Messages.h" +#include "ipc/Kids.h" +#include "ipc/TypedMsgHdr.h" +#include "ipc/StartListening.h" +#include "ipc/SharedListen.h" + + +/// holds information necessary to handle JoinListen response +class PendingOpenRequest +{ +public: + Ipc::OpenListenerParams params; ///< actual comm_open_sharedListen() parameters + AsyncCall::Pointer callback; // who to notify +}; + +/// maps ID assigned at request time to the response callback +typedef std::map SharedListenRequestMap; +static SharedListenRequestMap TheSharedListenRequestMap; + +static int +AddToMap(const PendingOpenRequest &por) +{ + // find unused ID using linear seach; there should not be many entries + for (int id = 0; true; ++id) { + if (TheSharedListenRequestMap.find(id) == TheSharedListenRequestMap.end()) { + TheSharedListenRequestMap[id] = por; + return id; + } + } + assert(false); // not reached + return -1; +} + +Ipc::OpenListenerParams::OpenListenerParams() +{ + xmemset(this, 0, sizeof(*this)); +} + +bool +Ipc::OpenListenerParams::operator <(const OpenListenerParams &p) const +{ + if (sock_type != p.sock_type) + return sock_type < p.sock_type; + + if (proto != p.proto) + return proto < p.proto; + + // ignore flags and fdNote differences because they do not affect binding + + return addr.compareWhole(p.addr) < 0; +} + + + +Ipc::SharedListenRequest::SharedListenRequest(): requestorId(-1), mapId(-1) +{ + // caller will then set public data members +} + +Ipc::SharedListenRequest::SharedListenRequest(const TypedMsgHdr &hdrMsg) +{ + hdrMsg.getData(mtSharedListenRequest, this, sizeof(*this)); +} + +void Ipc::SharedListenRequest::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtSharedListenRequest, this, sizeof(*this)); +} + + +Ipc::SharedListenResponse::SharedListenResponse(int aFd, int anErrNo, int aMapId): + fd(aFd), errNo(anErrNo), mapId(aMapId) +{ +} + +Ipc::SharedListenResponse::SharedListenResponse(const TypedMsgHdr &hdrMsg): + fd(-1), errNo(0), mapId(-1) +{ + hdrMsg.getData(mtSharedListenResponse, this, sizeof(*this)); + fd = hdrMsg.getFd(); +} + +void Ipc::SharedListenResponse::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtSharedListenResponse, this, sizeof(*this)); + hdrMsg.putFd(fd); +} + + +void Ipc::JoinSharedListen(const OpenListenerParams ¶ms, + AsyncCall::Pointer &callback) +{ + PendingOpenRequest por; + por.params = params; + por.callback = callback; + + SharedListenRequest request; + request.requestorId = KidIdentifier; + request.params = por.params; + request.mapId = AddToMap(por); + + debugs(54, 3, HERE << "getting listening FD for " << request.params.addr << + " mapId=" << request.mapId); + + TypedMsgHdr message; + request.pack(message); + SendMessage(coordinatorAddr, message); +} + +void Ipc::SharedListenJoined(const SharedListenResponse &response) +{ + const int fd = response.fd; + + debugs(54, 3, HERE << "got listening FD " << fd << " errNo=" << + response.errNo << " mapId=" << response.mapId); + + Must(TheSharedListenRequestMap.find(response.mapId) != TheSharedListenRequestMap.end()); + PendingOpenRequest por = TheSharedListenRequestMap[response.mapId]; + Must(por.callback != NULL); + TheSharedListenRequestMap.erase(response.mapId); + + if (fd >= 0) { + OpenListenerParams &p = por.params; + struct addrinfo *AI = NULL; + p.addr.GetAddrInfo(AI); + AI->ai_socktype = p.sock_type; + AI->ai_protocol = p.proto; + comm_import_opened(fd, p.addr, p.flags, FdNote(p.fdNote), AI); + p.addr.FreeAddrInfo(AI); + } + + StartListeningCb *cbd = + dynamic_cast(por.callback->getDialer()); + Must(cbd); + cbd->fd = fd; + cbd->errNo = response.errNo; + ScheduleCallHere(por.callback); +} === added file 'src/ipc/SharedListen.h' --- src/ipc/SharedListen.h 1970-01-01 00:00:00 +0000 +++ src/ipc/SharedListen.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,74 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_SHARED_LISTEN_H +#define SQUID_IPC_SHARED_LISTEN_H + +#include "base/AsyncCall.h" + +namespace Ipc +{ + +/// "shared listen" is when concurrent processes are listening on the same fd + +/// comm_open_listener() parameters holder +class OpenListenerParams +{ +public: + OpenListenerParams(); + + bool operator <(const OpenListenerParams &p) const; ///< useful for map<> + + int sock_type; + int proto; + IpAddress addr; ///< will be memset and memcopied + int flags; + int fdNote; ///< index into fd_note() comment strings +}; + +class TypedMsgHdr; + +/// a request for a listen socket with given parameters +class SharedListenRequest +{ +public: + SharedListenRequest(); ///< from OpenSharedListen() which then sets public data + explicit SharedListenRequest(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int requestorId; ///< kidId of the requestor + + OpenListenerParams params; ///< actual comm_open_sharedListen() parameters + + int mapId; ///< to map future response to the requestor's callback +}; + +/// a response to SharedListenRequest +class SharedListenResponse +{ +public: + SharedListenResponse(int fd, int errNo, int mapId); + explicit SharedListenResponse(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int fd; ///< opened listening socket or -1 + int errNo; ///< errno value from comm_open_sharedListen() call + int mapId; ///< to map future response to the requestor's callback +}; + +/// prepare and send SharedListenRequest to Coordinator +extern void JoinSharedListen(const OpenListenerParams &, AsyncCall::Pointer &); + +/// process Coordinator response to SharedListenRequest +extern void SharedListenJoined(const SharedListenResponse &response); + +} // namespace Ipc; + + +#endif /* SQUID_IPC_SHARED_LISTEN_H */ === added file 'src/ipc/StartListening.cc' --- src/ipc/StartListening.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/StartListening.cc 2010-06-14 20:45:53 +0000 @@ -0,0 +1,58 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "comm.h" +#include "TextException.h" +#include "ipc/SharedListen.h" +#include "ipc/StartListening.h" + + +Ipc::StartListeningCb::StartListeningCb(): fd(-1), errNo(0) +{ +} + +Ipc::StartListeningCb::~StartListeningCb() +{ +} + +std::ostream &Ipc::StartListeningCb::startPrint(std::ostream &os) const +{ + return os << "(FD " << fd << ", err=" << errNo; +} + + +void Ipc::StartListening(int sock_type, int proto, IpAddress &addr, + int flags, FdNoteId fdNote, AsyncCall::Pointer &callback) +{ + OpenListenerParams p; + p.sock_type = sock_type; + p.proto = proto; + p.addr = addr; + p.flags = flags; + p.fdNote = fdNote; + + if (UsingSmp()) { // if SMP is on, share + Ipc::JoinSharedListen(p, callback); + return; // wait for the call back + } + + enter_suid(); + const int sock = comm_open_listener(p.sock_type, p.proto, p.addr, p.flags, + FdNote(p.fdNote)); + const int errNo = (sock >= 0) ? 0 : errno; + leave_suid(); + + debugs(54, 3, HERE << "opened listen FD " << sock << " for " << p.addr); + + StartListeningCb *cbd = + dynamic_cast(callback->getDialer()); + Must(cbd); + cbd->fd = sock; + cbd->errNo = errNo; + ScheduleCallHere(callback); +} === added file 'src/ipc/StartListening.h' --- src/ipc/StartListening.h 1970-01-01 00:00:00 +0000 +++ src/ipc/StartListening.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,43 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_START_LISTENING_H +#define SQUID_IPC_START_LISTENING_H + +#include +#include "ipc/FdNotes.h" +#include "base/AsyncCall.h" + +class IpAddress; + +namespace Ipc +{ + +/// common API for all StartListening() callbacks +class StartListeningCb +{ +public: + StartListeningCb(); + virtual ~StartListeningCb(); + + /// starts printing arguments, return os + std::ostream &startPrint(std::ostream &os) const; + +public: + int fd; ///< opened listening socket or -1 + int errNo; ///< errno value from the comm_open_listener() call +}; + +/// Depending on whether SMP is on, either ask Coordinator to send us +/// the listening FD or call comm_open_listener() directly. +extern void StartListening(int sock_type, int proto, IpAddress &addr, + int flags, FdNoteId fdNote, AsyncCall::Pointer &callback); + +} // namespace Ipc; + + +#endif /* SQUID_IPC_START_LISTENING_H */ === added file 'src/ipc/Strand.cc' --- src/ipc/Strand.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Strand.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,97 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Strand.h" +#include "ipc/Messages.h" +#include "ipc/SharedListen.h" +#include "ipc/Kids.h" + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, Strand); + + +Ipc::Strand::Strand(): + Port(MakeAddr(strandAddrPfx, KidIdentifier)), + isRegistered(false) +{ +} + +void Ipc::Strand::start() +{ + Port::start(); + registerSelf(); +} + +void Ipc::Strand::registerSelf() +{ + debugs(54, 6, HERE); + Must(!isRegistered); + TypedMsgHdr message; + StrandCoord(KidIdentifier, getpid()).pack(message); + SendMessage(coordinatorAddr, message); + setTimeout(6, "Ipc::Strand::timeoutHandler"); // TODO: make 6 configurable? +} + +void Ipc::Strand::receive(const TypedMsgHdr &message) +{ + debugs(54, 6, HERE << message.type()); + switch (message.type()) { + + case mtRegistration: + handleRegistrationResponse(StrandCoord(message)); + break; + + case mtSharedListenResponse: + SharedListenJoined(SharedListenResponse(message)); + break; + + case mtDescriptorPut: + putDescriptor(Descriptor(message)); + break; + + default: + debugs(54, 1, HERE << "Unhandled message type: " << message.type()); + break; + } +} + +void Ipc::Strand::handleRegistrationResponse(const StrandCoord &strand) +{ + // handle registration response from the coordinator; it could be stale + if (strand.kidId == KidIdentifier && strand.pid == getpid()) { + debugs(54, 6, "kid" << KidIdentifier << " registered"); + clearTimeout(); // we are done + + debugs(54, 6, HERE << "requesting FD"); + Descriptor request(KidIdentifier, -1); + TypedMsgHdr message; + request.pack(message); + SendMessage(coordinatorAddr, message); + } else { + // could be an ACK to the registration message of our dead predecessor + debugs(54, 6, "kid" << KidIdentifier << " is not yet registered"); + // keep listening, with a timeout + } +} + +/// receive descriptor we asked for +void Ipc::Strand::putDescriptor(const Descriptor &message) +{ + debugs(54, 6, HERE << "got FD " << message.fd); + char buffer[64]; + const int n = snprintf(buffer, sizeof(buffer), "strand: kid%d wrote using FD %d\n", KidIdentifier, message.fd); + ssize_t bytes = write(message.fd, buffer, n); + Must(bytes == n); +} + +void Ipc::Strand::timedout() +{ + debugs(54, 6, HERE << isRegistered); + if (!isRegistered) + fatalf("kid%d registration timed out", KidIdentifier); +} === added file 'src/ipc/Strand.h' --- src/ipc/Strand.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Strand.h 2010-07-01 22:58:45 +0000 @@ -0,0 +1,51 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_STRAND_H +#define SQUID_IPC_STRAND_H + +#include "ipc/Port.h" + + +namespace Ipc +{ + +class StrandCoord; +class Descriptor; + +/// Receives coordination messages on behalf of its process or thread +class Strand: public Port +{ +public: + Strand(); + + virtual void start(); // Port (AsyncJob) API + +protected: + virtual void timedout(); // Port (UsdOp) API + virtual void receive(const TypedMsgHdr &message); // Port API + +private: + void registerSelf(); /// let Coordinator know this strand exists + void handleRegistrationResponse(const StrandCoord &strand); + void putDescriptor(const Descriptor &message); + +private: + bool isRegistered; ///< whether Coordinator ACKed registration (unused) + +private: + Strand(const Strand&); // not implemented + Strand& operator =(const Strand&); // not implemented + + CBDATA_CLASS2(Strand); +}; + + +} + + +#endif /* SQUID_IPC_STRAND_H */ === added file 'src/ipc/TypedMsgHdr.cc' --- src/ipc/TypedMsgHdr.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/TypedMsgHdr.cc 2010-05-01 23:47:46 +0000 @@ -0,0 +1,167 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include +#include "TextException.h" +#include "ipc/TypedMsgHdr.h" + +Ipc::TypedMsgHdr::TypedMsgHdr() +{ + xmemset(this, 0, sizeof(*this)); + sync(); +} + +Ipc::TypedMsgHdr::TypedMsgHdr(const TypedMsgHdr &tmh) +{ + xmemcpy(this, &tmh, sizeof(*this)); + sync(); +} + +Ipc::TypedMsgHdr &Ipc::TypedMsgHdr::operator =(const TypedMsgHdr &tmh) +{ + if (this != &tmh) { // skip assignment to self + xmemcpy(this, &tmh, sizeof(*this)); + sync(); + } + return *this; +} + +// update msghdr and ios pointers based on msghdr counters +void Ipc::TypedMsgHdr::sync() +{ + if (msg_name) { // we have a name + msg_name = &name; + } else { + Must(!msg_namelen && !msg_name); + } + + if (msg_iov) { // we have a data component + Must(msg_iovlen == 1); + msg_iov = ios; + ios[0].iov_base = &data; + Must(ios[0].iov_len == sizeof(data)); + } else { + Must(!msg_iovlen && !msg_iov); + } + + if (msg_control) { // we have a control component + Must(msg_controllen > 0); + msg_control = &ctrl; + } else { + Must(!msg_controllen && !msg_control); + } +} + + + +int +Ipc::TypedMsgHdr::type() const +{ + Must(msg_iovlen == 1); + return data.type_; +} + +void +Ipc::TypedMsgHdr::address(const struct sockaddr_un& addr) +{ + allocName(); + name = addr; + msg_name = &name; + msg_namelen = SUN_LEN(&name); +} + +void +Ipc::TypedMsgHdr::getData(int destType, void *raw, size_t size) const +{ + Must(type() == destType); + Must(size == data.size); + xmemcpy(raw, data.raw, size); +} + +void +Ipc::TypedMsgHdr::putData(int aType, const void *raw, size_t size) +{ + Must(size <= sizeof(data.raw)); + allocData(); + data.type_ = aType; + data.size = size; + xmemcpy(data.raw, raw, size); +} + +void +Ipc::TypedMsgHdr::putFd(int fd) +{ + Must(fd >= 0); + allocControl(); + + const int fdCount = 1; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(this); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fdCount); + + int *fdStore = reinterpret_cast(CMSG_DATA(cmsg)); + xmemcpy(fdStore, &fd, fdCount * sizeof(int)); + msg_controllen = cmsg->cmsg_len; +} + +int +Ipc::TypedMsgHdr::getFd() const +{ + Must(msg_control && msg_controllen); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(this); + Must(cmsg->cmsg_level == SOL_SOCKET); + Must(cmsg->cmsg_type == SCM_RIGHTS); + + const int fdCount = 1; + const int *fdStore = reinterpret_cast(CMSG_DATA(cmsg)); + int fd = -1; + xmemcpy(&fd, fdStore, fdCount * sizeof(int)); + return fd; +} + +void +Ipc::TypedMsgHdr::prepForReading() +{ + xmemset(this, 0, sizeof(*this)); + allocName(); + allocData(); + allocControl(); +} + +/// initialize io vector with one io record +void +Ipc::TypedMsgHdr::allocData() +{ + Must(!msg_iovlen && !msg_iov); + msg_iovlen = 1; + msg_iov = ios; + ios[0].iov_base = &data; + ios[0].iov_len = sizeof(data); + data.type_ = 0; + data.size = 0; +} + +void +Ipc::TypedMsgHdr::allocName() +{ + Must(!msg_name && !msg_namelen); + msg_name = &name; + msg_namelen = sizeof(name); // is that the right size? +} + +void +Ipc::TypedMsgHdr::allocControl() +{ + Must(!msg_control && !msg_controllen); + msg_control = &ctrl; + msg_controllen = sizeof(ctrl); +} === added file 'src/ipc/TypedMsgHdr.h' --- src/ipc/TypedMsgHdr.h 1970-01-01 00:00:00 +0000 +++ src/ipc/TypedMsgHdr.h 2010-05-02 17:58:46 +0000 @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_TYPED_MSG_HDR_H +#define SQUID_IPC_TYPED_MSG_HDR_H + +#include "config.h" +#include +#include +#if HAVE_SYS_UN_H +#include +#endif + +namespace Ipc +{ + +/// struct msghdr with a known type, fixed-size I/O and control buffers +class TypedMsgHdr: public msghdr +{ +public: + TypedMsgHdr(); + TypedMsgHdr(const TypedMsgHdr &tmh); + TypedMsgHdr &operator =(const TypedMsgHdr &tmh); + + // type-safe access to message details + int type() const; ///< returns stored type or zero if none + void address(const struct sockaddr_un& addr); ///< sets [dest.] address + void getData(int ofType, void *raw, size_t size) const; ///< checks type + void putData(int aType, const void *raw, size_t size); ///< stores type + void putFd(int aFd); ///< stores descriptor + int getFd() const; ///< returns descriptor + + /// raw, type-independent access for I/O + void prepForReading(); ///< reset and provide all buffers + char *raw() { return reinterpret_cast(this); } + const char *raw() const { return reinterpret_cast(this); } + size_t size() const { return sizeof(*this); } ///< not true message size + +private: + void sync(); + void allocData(); + void allocName(); + void allocControl(); + +private: + struct sockaddr_un name; ///< same as .msg_name + + struct iovec ios[1]; ///< same as .msg_iov[] + + struct DataBuffer { + int type_; ///< Message kind, uses MessageType values + size_t size; ///< actual raw data size (for sanity checks) + char raw[250]; ///< buffer with type-specific data + } data; ///< same as .msg_iov[0].iov_base + + struct CtrlBuffer { + char raw[CMSG_SPACE(sizeof(int))]; ///< control buffer space for one fd + } ctrl; ///< same as .msg_control +}; + +} // namespace Ipc + +#endif /* SQUID_IPC_TYPED_MSG_HDR_H */ === added file 'src/ipc/UdsOp.cc' --- src/ipc/UdsOp.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/UdsOp.cc 2010-04-29 20:12:03 +0000 @@ -0,0 +1,132 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "CommCalls.h" +#include "ipc/UdsOp.h" + + +Ipc::UdsOp::UdsOp(const String& pathAddr): + AsyncJob("Ipc::UdsOp"), + address(PathToAddress(pathAddr)), + options(COMM_NONBLOCKING), + fd_(-1) +{ + debugs(54, 5, HERE << '[' << this << "] pathAddr=" << pathAddr); +} + +Ipc::UdsOp::~UdsOp() +{ + debugs(54, 5, HERE << '[' << this << ']'); + if (fd_ >= 0) + comm_close(fd_); +} + +void Ipc::UdsOp::setOptions(int newOptions) +{ + options = newOptions; +} + +int Ipc::UdsOp::fd() +{ + if (fd_ < 0) { + if (options & COMM_DOBIND) + unlink(address.sun_path); + fd_ = comm_open_uds(SOCK_DGRAM, 0, &address, options); + Must(fd_ >= 0); + } + return fd_; +} + +void Ipc::UdsOp::setTimeout(int seconds, const char *handlerName) +{ + AsyncCall::Pointer handler = asyncCall(54,5, handlerName, + CommCbMemFunT(this, + &UdsOp::noteTimeout)); + commSetTimeout(fd(), seconds, handler); +} + +void Ipc::UdsOp::clearTimeout() +{ + commSetTimeout(fd(), -1, NULL, NULL); // TODO: add Comm::ClearTimeout(fd) +} + +void Ipc::UdsOp::noteTimeout(const CommTimeoutCbParams &) +{ + timedout(); // our kid handles communication timeout +} + + +struct sockaddr_un +Ipc::PathToAddress(const String& pathAddr) +{ + assert(pathAddr.size() != 0); + struct sockaddr_un unixAddr; + memset(&unixAddr, 0, sizeof(unixAddr)); + unixAddr.sun_family = AF_LOCAL; + xstrncpy(unixAddr.sun_path, pathAddr.termedBuf(), sizeof(unixAddr.sun_path)); + return unixAddr; +} + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, UdsSender); + +Ipc::UdsSender::UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage): + UdsOp(pathAddr), + message(aMessage), + retries(10), // TODO: make configurable? + timeout(10), // TODO: make configurable? + writing(false) +{ + message.address(address); +} + +void Ipc::UdsSender::start() +{ + UdsOp::start(); + write(); + if (timeout > 0) + setTimeout(timeout, "Ipc::UdsSender::noteTimeout"); +} + +bool Ipc::UdsSender::doneAll() const +{ + return !writing && UdsOp::doneAll(); +} + +void Ipc::UdsSender::write() +{ + debugs(54, 5, HERE); + AsyncCall::Pointer writeHandler = asyncCall(54, 5, "Ipc::UdsSender::wrote", + CommCbMemFunT(this, &UdsSender::wrote)); + comm_write(fd(), message.raw(), message.size(), writeHandler); + writing = true; +} + +void Ipc::UdsSender::wrote(const CommIoCbParams& params) +{ + debugs(54, 5, HERE << "FD " << params.fd << " flag " << params.flag << " [" << this << ']'); + writing = false; + if (params.flag != COMM_OK && retries-- > 0) { + sleep(1); // do not spend all tries at once; XXX: use an async timed event instead of blocking here; store the time when we started writing so that we do not sleep if not needed? + write(); // XXX: should we close on error so that fd() reopens? + } +} + +void Ipc::UdsSender::timedout() +{ + debugs(54, 5, HERE); + mustStop("timedout"); +} + + +void Ipc::SendMessage(const String& toAddress, const TypedMsgHdr &message) +{ + AsyncJob::AsyncStart(new UdsSender(toAddress, message)); +} === added file 'src/ipc/UdsOp.h' --- src/ipc/UdsOp.h 1970-01-01 00:00:00 +0000 +++ src/ipc/UdsOp.h 2010-07-01 22:58:45 +0000 @@ -0,0 +1,97 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_ASYNCUDSOP_H +#define SQUID_IPC_ASYNCUDSOP_H + + +#include "SquidString.h" +#include "base/AsyncJob.h" +#include "ipc/TypedMsgHdr.h" + +class CommTimeoutCbParams; +class CommIoCbParams; + +namespace Ipc +{ + +/// code shared by unix-domain socket senders (e.g., UdsSender or Coordinator) +/// and receivers (e.g. Port or Coordinator) +class UdsOp: public AsyncJob +{ +public: + UdsOp(const String &pathAddr); + virtual ~UdsOp(); + +public: + struct sockaddr_un address; ///< UDS address from path; treat as read-only + +protected: + virtual void timedout() {} ///< called after setTimeout() if timed out + + int fd(); ///< creates if needed and returns raw UDS socket descriptor + + /// call timedout() if no UDS messages in a given number of seconds + void setTimeout(int seconds, const char *handlerName); + void clearTimeout(); ///< remove previously set timeout, if any + + void setOptions(int newOptions); ///< changes socket options + +private: + /// Comm timeout callback; calls timedout() + void noteTimeout(const CommTimeoutCbParams &p); + +private: + int options; ///< UDS options + int fd_; ///< UDS descriptor + +private: + UdsOp(const UdsOp &); // not implemented + UdsOp &operator= (const UdsOp &); // not implemented +}; + +/// converts human-readable filename path into UDS address +extern struct sockaddr_un PathToAddress(const String &pathAddr); + + + +// XXX: move UdsSender code to UdsSender.{cc,h} +/// attempts to send an IPC message a few times, with a timeout +class UdsSender: public UdsOp +{ +public: + UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage); + +protected: + virtual void start(); // UdsOp (AsyncJob) API + virtual bool doneAll() const; // UdsOp (AsyncJob) API + virtual void timedout(); // UdsOp API + +private: + void write(); ///< schedule writing + void wrote(const CommIoCbParams& params); ///< done writing or error + +private: + TypedMsgHdr message; ///< what to send + int retries; ///< how many times to try after a write error + int timeout; ///< total time to send the message + bool writing; ///< whether Comm started and did not finish writing + +private: + UdsSender(const UdsSender&); // not implemented + UdsSender& operator= (const UdsSender&); // not implemented + + CBDATA_CLASS2(UdsSender); +}; + + +void SendMessage(const String& toAddress, const TypedMsgHdr& message); + + +} + +#endif /* SQUID_IPC_ASYNCUDSOP_H */ === modified file 'src/main.cc' --- src/main.cc 2009-12-21 12:00:15 +0000 +++ src/main.cc 2010-06-05 19:08:44 +0000 @@ -55,6 +55,9 @@ #include "StoreFileSystem.h" #include "DiskIO/DiskIOModule.h" #include "comm.h" +#include "ipc/Kids.h" +#include "ipc/Coordinator.h" +#include "ipc/Strand.h" #if USE_EPOLL #include "comm_epoll.h" #endif @@ -124,6 +127,10 @@ static volatile int do_shutdown = 0; static volatile int shutdown_status = 0; +static int RotateSignal = -1; +static int ReconfigureSignal = -1; +static int ShutdownSignal = -1; + static void mainRotate(void); static void mainReconfigureStart(void); static void mainReconfigureFinish(void*); @@ -195,6 +202,10 @@ doShutdown(do_shutdown > 0 ? (int) Config.shutdownLifetime : 0); do_shutdown = 0; } + BroadcastSignalIfAny(DebugSignal); + BroadcastSignalIfAny(RotateSignal); + BroadcastSignalIfAny(ReconfigureSignal); + BroadcastSignalIfAny(ShutdownSignal); PROF_stop(SignalEngine_checkEvents); return EVENT_IDLE; @@ -553,6 +564,7 @@ rotate_logs(int sig) { do_rotate = 1; + RotateSignal = sig; #ifndef _SQUID_MSWIN_ #if !HAVE_SIGACTION @@ -566,6 +578,7 @@ reconfigure(int sig) { do_reconfigure = 1; + ReconfigureSignal = sig; #ifndef _SQUID_MSWIN_ #if !HAVE_SIGACTION @@ -578,6 +591,7 @@ shut_down(int sig) { do_shutdown = sig == SIGINT ? -1 : 1; + ShutdownSignal = sig; #ifdef SIGTTIN if (SIGTTIN == sig) @@ -607,6 +621,19 @@ static void serverConnectionsOpen(void) { + if (IamPrimaryProcess()) { +#if USE_WCCP + + wccpConnectionOpen(); +#endif + +#if USE_WCCPv2 + + wccp2ConnectionOpen(); +#endif + } + // Coordinator does not start proxying services + if (!IamCoordinatorProcess()) { clientOpenListenSockets(); icpConnectionsOpen(); #if USE_HTCP @@ -617,15 +644,6 @@ snmpConnectionOpen(); #endif -#if USE_WCCP - - wccpConnectionOpen(); -#endif - -#if USE_WCCPv2 - - wccp2ConnectionOpen(); -#endif clientdbInit(); icmpEngine.Open(); @@ -637,12 +655,25 @@ carpInit(); peerUserHashInit(); peerSourceHashInit(); + } } static void serverConnectionsClose(void) { assert(shutting_down || reconfiguring); + + if (IamPrimaryProcess()) { +#if USE_WCCP + + wccpConnectionClose(); +#endif +#if USE_WCCPv2 + + wccp2ConnectionClose(); +#endif + } + if (!IamCoordinatorProcess()) { clientHttpConnectionsClose(); icpConnectionShutdown(); #if USE_HTCP @@ -655,16 +686,9 @@ snmpConnectionShutdown(); #endif -#if USE_WCCP - - wccpConnectionClose(); -#endif -#if USE_WCCPv2 - - wccp2ConnectionClose(); -#endif asnFreeMemory(); + } } static void @@ -721,10 +745,17 @@ Config2.onoff.enable_purge = 2; // parse the config returns a count of errors encountered. + const int oldMainProcesses = Config.main_processes; if ( parseConfigFile(ConfigFile) != 0) { // for now any errors are a fatal condition... self_destruct(); } + if (oldMainProcesses != Config.main_processes) { + debugs(1, DBG_CRITICAL, "WARNING: Changing 'main_processes' (from " << + oldMainProcesses << " to " << Config.main_processes << + ") is not supported and ignored"); + Config.main_processes = oldMainProcesses; + } setUmask(Config.umask); Mem::Report(); @@ -753,6 +784,8 @@ redirectInit(); authenticateInit(&Config.authConfiguration); externalAclInit(); + + if (IamPrimaryProcess()) { #if USE_WCCP wccpInit(); @@ -761,6 +794,7 @@ wccp2Init(); #endif + } serverConnectionsOpen(); @@ -1015,6 +1049,7 @@ // moved to PconnModule::PconnModule() } + if (IamPrimaryProcess()) { #if USE_WCCP wccpInit(); @@ -1024,6 +1059,7 @@ wccp2Init(); #endif + } serverConnectionsOpen(); @@ -1149,9 +1185,28 @@ return -1; // not reached } +/// computes name and ID for the current kid process +static void +ConfigureCurrentKid(const char *processName) +{ + // kids are marked with parenthesis around their process names + if (processName && processName[0] == '(') { + if (const char *idStart = strrchr(processName, '-')) { + KidIdentifier = atoi(idStart + 1); + const int nameLen = idStart - (processName + 1); + xstrncpy(KidName, processName + 1, nameLen + 1); + } + } else { + xstrncpy(KidName, APP_SHORTNAME, sizeof(KidName)); + KidIdentifier = 0; + } +} + int SquidMain(int argc, char **argv) { + ConfigureCurrentKid(argv[0]); + #ifdef _SQUID_WIN32_ int WIN32_init_err; @@ -1320,7 +1375,7 @@ return 0; } - if (!opt_no_daemon) + if (!opt_no_daemon && Config.main_processes > 0) watch_child(argv); setMaxFD(); @@ -1376,6 +1431,11 @@ mainLoop.setTimeService(&time_engine); + if (IamCoordinatorProcess()) + AsyncJob::AsyncStart(Ipc::Coordinator::Instance()); + else if (UsingSmp() && IamWorkerProcess()) + AsyncJob::AsyncStart(new Ipc::Strand); + /* at this point we are finished the synchronous startup. */ starting_up = 0; @@ -1477,11 +1537,11 @@ do { #ifdef _SQUID_NEXT_ union wait status; - rpid = wait3(&status, 0, NULL); + rpid = wait4(cpid, &status, 0, NULL); #else int status; - rpid = waitpid(-1, &status, 0); + rpid = waitpid(cpid, &status, 0); #endif } while (rpid != cpid); @@ -1493,6 +1553,10 @@ static int checkRunningPid(void) { + // master process must start alone, but its kids processes may co-exist + if (!IamMasterProcess()) + return 0; + pid_t pid; if (!debug_log) @@ -1516,9 +1580,6 @@ { #ifndef _SQUID_MSWIN_ char *prog; - int failcount = 0; - time_t start; - time_t stop; #ifdef _SQUID_NEXT_ union wait status; @@ -1535,7 +1596,7 @@ int nullfd; - if (*(argv[0]) == '(') + if (!IamMasterProcess()) return; openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); @@ -1577,25 +1638,39 @@ dup2(nullfd, 2); } + if (Config.main_processes > 128) { + syslog(LOG_ALERT, "Suspiciously high main_processes value: %d", + Config.main_processes); + // but we keep going in hope that user knows best + } + TheKids.init(Config.main_processes); + + // keep [re]starting kids until it is time to quit for (;;) { mainStartScript(argv[0]); - if ((pid = fork()) == 0) { - /* child */ - openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); - prog = xstrdup(argv[0]); - argv[0] = xstrdup("(squid)"); - execvp(prog, argv); - syslog(LOG_ALERT, "execvp failed: %s", xstrerror()); + // start each kid that needs to be [re]started; once + for (int i = TheKids.count() - 1; i >= 0; --i) { + Kid& kid = TheKids.get(i); + if (kid.hopeless() || kid.exitedHappy() || kid.running()) + continue; + + if ((pid = fork()) == 0) { + /* child */ + openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); + prog = xstrdup(argv[0]); /* XXX: leak */ + argv[0] = const_cast(kid.name().termedBuf()); + execvp(prog, argv); + syslog(LOG_ALERT, "execvp failed: %s", xstrerror()); + } + + kid.start(pid); + syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid); } /* parent */ openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); - syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid); - - time(&start); - squid_signal(SIGINT, SIG_IGN, SA_RESTART); #ifdef _SQUID_NEXT_ @@ -1607,51 +1682,48 @@ pid = waitpid(-1, &status, 0); #endif - - time(&stop); - - if (WIFEXITED(status)) { - syslog(LOG_NOTICE, - "Squid Parent: child process %d exited with status %d", - pid, WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - syslog(LOG_NOTICE, - "Squid Parent: child process %d exited due to signal %d with status %d", - pid, WTERMSIG(status), WEXITSTATUS(status)); - } else { - syslog(LOG_NOTICE, "Squid Parent: child process %d exited", pid); + // Loop to collect all stopped kids before we go to sleep below. + do + { + Kid* kid = TheKids.find(pid); + if (kid) { + kid->stop(status); + if (kid->calledExit()) { + syslog(LOG_NOTICE, + "Squid Parent: child process %d exited with status %d", + kid->getPid(), kid->exitStatus()); + } else if (kid->signaled()) { + syslog(LOG_NOTICE, + "Squid Parent: child process %d exited due to signal %d with status %d", + kid->getPid(), kid->termSignal(), kid->exitStatus()); + } else { + syslog(LOG_NOTICE, "Squid Parent: child process %d exited", kid->getPid()); + } + } else { + syslog(LOG_NOTICE, "Squid Parent: unknown child process %d exited", pid); + } +#ifdef _SQUID_NEXT_ + } while ((pid = wait3(&status, WNOHANG, NULL)) > 0); +#else + } while ((pid = waitpid(-1, &status, WNOHANG)) > 0); +#endif + + if (TheKids.allExitedHappy()) { + exit(0); } - if (stop - start < 10) - failcount++; - else - failcount = 0; - - if (failcount == 5) { + if (TheKids.allHopeless()) { syslog(LOG_ALERT, "Exiting due to repeated, frequent failures"); exit(1); } - if (WIFEXITED(status)) - if (WEXITSTATUS(status) == 0) - exit(0); - - if (WIFSIGNALED(status)) { - switch (WTERMSIG(status)) { - - case SIGKILL: - exit(0); - break; - - case SIGINT: - case SIGTERM: - syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown"); - exit(1); - break; - - default: - break; - } + if (TheKids.allSignaled(SIGKILL)) { + exit(0); + } + + if (TheKids.allSignaled(SIGINT) || TheKids.allSignaled(SIGTERM)) { + syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown"); + exit(1); } squid_signal(SIGINT, SIG_DFL, SA_RESTART); @@ -1789,10 +1861,12 @@ #endif - if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) { - enter_suid(); - safeunlink(Config.pidFilename, 0); - leave_suid(); + if (IamPrimaryProcess()) { + if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) { + enter_suid(); + safeunlink(Config.pidFilename, 0); + leave_suid(); + } } debugs(1, 1, "Squid Cache (Version " << version_string << "): Exiting normally."); === modified file 'src/protos.h' --- src/protos.h 2010-01-14 09:18:00 +0000 +++ src/protos.h 2010-06-05 19:08:44 +0000 @@ -575,6 +575,22 @@ SQUIDCEXTERN pid_t readPidFile(void); SQUIDCEXTERN void keepCapabilities(void); +SQUIDCEXTERN void BroadcastSignalIfAny(int& sig); +/// whether the current process is the parent of all other Squid processes +SQUIDCEXTERN bool IamMasterProcess(); +/** + whether the current process is dedicated to doing things that only + a single process should do, such as PID file maintenance and WCCP +*/ +SQUIDCEXTERN bool IamPrimaryProcess(); +/// whether the current process coordinates worker processes +SQUIDCEXTERN bool IamCoordinatorProcess(); +/// whether the current process handles HTTP transactions and such +SQUIDCEXTERN bool IamWorkerProcess(); +/// Whether there should be more than one worker process running +SQUIDCEXTERN bool UsingSmp(); // try using specific Iam*() checks above first +SQUIDCEXTERN int DebugSignal; + /* AYJ debugs function to show locations being reset with memset() */ SQUIDCEXTERN void *xmemset(void *dst, int, size_t); === modified file 'src/snmp_core.cc' --- src/snmp_core.cc 2010-01-14 09:18:00 +0000 +++ src/snmp_core.cc 2010-06-05 18:32:22 +0000 @@ -34,10 +34,30 @@ #include "cache_snmp.h" #include "acl/FilledChecklist.h" #include "ip/IpAddress.h" +#include "ipc/StartListening.h" #define SNMP_REQUEST_SIZE 4096 #define MAX_PROTOSTAT 5 + +/// dials snmpConnectionOpened call +class SnmpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo); + SnmpListeningStartedDialer(Handler aHandler): handler(aHandler) {} + + virtual void print(std::ostream &os) const { startPrint(os) << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo); } + +public: + Handler handler; +}; + + IpAddress theOutSNMPAddr; typedef struct _mib_tree_entry mib_tree_entry; @@ -58,6 +78,9 @@ mib_tree_entry *mib_tree_head; mib_tree_entry *mib_tree_last; +static void snmpIncomingConnectionOpened(int fd, int errNo); +static void snmpOutgoingConnectionOpened(int fd, int errNo); + static mib_tree_entry * snmpAddNodeStr(const char *base_str, int o, oid_ParseFn * parsefunction, instance_Fn * instancefunction); static mib_tree_entry *snmpAddNode(oid * name, int len, oid_ParseFn * parsefunction, instance_Fn * instancefunction, int children,...); static oid *snmpCreateOid(int length,...); @@ -278,54 +301,71 @@ void snmpConnectionOpen(void) { - struct addrinfo *xaddr = NULL; - int x; - debugs(49, 5, "snmpConnectionOpen: Called"); if (Config.Port.snmp > 0) { Config.Addrs.snmp_incoming.SetPort(Config.Port.snmp); - enter_suid(); - theInSnmpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(49, 2, + "snmpIncomingConnectionOpened", + SnmpListeningStartedDialer(&snmpIncomingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, Config.Addrs.snmp_incoming, COMM_NONBLOCKING, - "SNMP Port"); - leave_suid(); - - if (theInSnmpConnection < 0) - fatal("Cannot open SNMP Port"); - - commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, 0); - - debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming << ", FD " << theInSnmpConnection << "."); + Ipc::fdnInSnmpSocket, call); if (!Config.Addrs.snmp_outgoing.IsNoAddr()) { Config.Addrs.snmp_outgoing.SetPort(Config.Port.snmp); - enter_suid(); - theOutSnmpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(49, 2, + "snmpOutgoingConnectionOpened", + SnmpListeningStartedDialer(&snmpOutgoingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, Config.Addrs.snmp_outgoing, COMM_NONBLOCKING, - "SNMP Port"); - leave_suid(); - - if (theOutSnmpConnection < 0) - fatal("Cannot open Outgoing SNMP Port"); - - commSetSelect(theOutSnmpConnection, - COMM_SELECT_READ, - snmpHandleUdp, - NULL, 0); - - debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing << ", FD " << theOutSnmpConnection << "."); - - fd_note(theOutSnmpConnection, "Outgoing SNMP socket"); - - fd_note(theInSnmpConnection, "Incoming SNMP socket"); - } else { - theOutSnmpConnection = theInSnmpConnection; + Ipc::fdnOutSnmpSocket, call); } + } +} + +static void +snmpIncomingConnectionOpened(int fd, int errNo) +{ + theInSnmpConnection = fd; + if (theInSnmpConnection < 0) + fatal("Cannot open Incoming SNMP Port"); + + commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, + 0); + + debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming << + ", FD " << theInSnmpConnection << "."); + + if (Config.Addrs.snmp_outgoing.IsNoAddr()) + theOutSnmpConnection = theInSnmpConnection; +} + +static void +snmpOutgoingConnectionOpened(int fd, int errNo) +{ + theOutSnmpConnection = fd; + if (theOutSnmpConnection < 0) + fatal("Cannot open Outgoing SNMP Port"); + + commSetSelect(theOutSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, + 0); + + debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing << + ", FD " << theOutSnmpConnection << "."); + + { + struct addrinfo *xaddr = NULL; + int x; + theOutSNMPAddr.SetEmpty(); === modified file 'src/structs.h' --- src/structs.h 2010-03-03 09:38:49 +0000 +++ src/structs.h 2010-03-04 06:25:18 +0000 @@ -617,6 +617,7 @@ char *accept_filter; int umask; + int main_processes; #if USE_LOADABLE_MODULES wordlist *loadable_module_names; === modified file 'src/tools.cc' --- src/tools.cc 2009-12-02 22:39:11 +0000 +++ src/tools.cc 2010-06-05 19:08:44 +0000 @@ -41,6 +41,8 @@ #include "SquidMath.h" #include "SquidTime.h" #include "ip/IpIntercept.h" +#include "ipc/Kids.h" +#include "ipc/Coordinator.h" #if HAVE_SYS_PRCTL_H #include @@ -64,6 +66,7 @@ extern void log_trace_init(char *); #endif static void restoreCapabilities(int keep); +int DebugSignal = -1; #ifdef _SQUID_LINUX_ /* Workaround for crappy glic header files */ @@ -397,6 +400,15 @@ abort(); } +void +BroadcastSignalIfAny(int& sig) +{ + if (sig > 0) { + if (IamCoordinatorProcess()) + Ipc::Coordinator::Instance()->broadcastSignal(sig); + sig = -1; + } +} void sigusr2_handle(int sig) @@ -404,6 +416,8 @@ static int state = 0; /* no debugs() here; bad things happen if the signal is delivered during _db_print() */ + DebugSignal = sig; + if (state == 0) { #ifndef MEM_GEN_TRACE Debug::parseOptions("ALL,7"); @@ -790,6 +804,50 @@ #endif } +bool +IamMasterProcess() +{ + return KidIdentifier == 0; +} + +bool +IamWorkerProcess() +{ + // when there is only one process, it has to be the worker + if (opt_no_daemon || Config.main_processes == 0) + return true; + + return 0 < KidIdentifier && KidIdentifier <= Config.main_processes; +} + +bool +UsingSmp() +{ + return !opt_no_daemon && Config.main_processes > 1; +} + +bool +IamCoordinatorProcess() +{ + return UsingSmp() && KidIdentifier == Config.main_processes + 1; +} + +bool +IamPrimaryProcess() +{ + // when there is only one process, it has to be primary + if (opt_no_daemon || Config.main_processes == 0) + return true; + + // when there is a master and worker process, the master delegates + // primary functions to its only kid + if (Config.main_processes == 1) + return IamWorkerProcess(); + + // in SMP mode, multiple kids delegate primary functions to the coordinator + return IamCoordinatorProcess(); +} + void writePidFile(void) { @@ -798,6 +856,9 @@ mode_t old_umask; char buf[32]; + if (!IamPrimaryProcess()) + return; + if ((f = Config.pidFilename) == NULL) return; === modified file 'test-suite/testheaders.sh' --- test-suite/testheaders.sh 2009-11-19 23:32:21 +0000 +++ test-suite/testheaders.sh 2010-05-02 18:09:21 +0000 @@ -16,6 +16,8 @@ dir="${2}" fi +exitCode=0 + for f in `cd ${dir} && ls -1 *.h 2>/dev/null`; do echo -n "Testing ${dir}/${f} ..." hdr=`echo "${f}" | sed s/.h//` @@ -32,11 +34,14 @@ fi if [ ! -f testHeaderDeps_${hdr}.o ]; then rm testHeaders - exit 1 - fi + exitCode=1 + else echo "OK." # unit-tests require an app to run. # our most-recent object suits this purpose. # let's link or some tests will fail ${cc} ./testHeaderDeps_${hdr}.o -o ./testHeaders + fi done + +exit $exitCode # Begin bundle IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWSvUATwAqz1/gG59TBD///// /////v////9g1f7zNYFXreHdLXRa2dtzF7Z69Xe6quenue3ZeBd72gx3brKvRuaAte7wGIFRQ9ex u2ujXrppp1VFCgaRfQ72AxdfV2fYwfLWgMQXTYbXcgwdcOpF9tefPqd713Z07fRgubG2+zVCr70Y HvZV1s+ikW6b33zG56wLGdrA+818UAPoUAbXfHce3qxq+L3p5XUqSdsHO9fO72vm2gqPQ+4Abi16 apVfbUpAW83vGVTTX2xAO++XT2oIUEKe8849VKAgp3V9zxCt3HAffHvHkoCAbk3RUoIB8z5iID77 PdzXvJCgC+mlSEQA9A66S7YaDQADr7mU6DL1XoO9tU3oAdEu3c50TWUKWw0c4BSrAJgBusNbuyip KIAKPRueAVzlHc4u00MXfAH03mu3nj4uWD176t7lL4+3l7QfI666hF333egl9gyb6ANt25czoQ2y 022hQrTBERUjvvveR9gaAArWE1rewADqlJA9tNUGkEUKglCIiRUXoGIokvrElKo6BVgAA2sgVNgG oSK9972e8+66Zm+r5yrQaaMGzEL7vuuGEvJ6wiqqlKpKXYGgCnWabIaUut1o1q6EoIAgAhAEE0yD UyanoyKn5T1R+mmpqegyn6oPKA0PUPU00w00AgiECaYhA0RpG0KPRH6pk9T1ADTQbUaAAAGgaYRB RFGlPU/Kj1HlM1DQ2SNDQNANAABoAAGhkAk0kREE0ZATSYaRpkap+QJplTeRqmn6aIxTGmU9T1G9 KANB6gRJEIAIAAmmmmgAI0AQCYp4g1MTRpGnogGnqBVIEaNAREJppggEmCFHtJPU0/RIGjQAAaNA AG3IC+UfTQCGiAiMSIqf4EQUKiEkIskISM7UIH76WGJWLFVYqMkiMhUqERBVFixVIskVBD7f+09v /FH/zPa+CwSS/5u3+PxP6Lc/VRWPZSEX/oXWSh8/uaiYCgq5f7IBFU/V/wqjhjVCurJ/n/ymOEMY 8NnSMyjLKGiBumiOfJNdk3H89mGGAaWOyf6/9KcQMTiNuuzyMzDq43/GwEYEmRuV1V1F/tD8hNk5 QhH81bK0o+TXV8BMElTtZj3pT+CVR6FQeuSwPQ9B8zpk91qnw4Nf7t7VVVVFUEVVVVf5unTF/jbD aLy8MM5odghMYKHxWz9zAqHb1LMQ/chx3fDJtHvs+JPndAPU/4l8GGrZ7Dv17dE6s4dPv9VwwrD+ OdNXpIb94xpYiLY6VTpv0+fWbukqZO7Xb7NcKSRUQioH/V0lYXb3M6jO28HP4+fdNatNlk0pRc85 5TuQRfmZ6HGL/m16ceM3SawTtZURRCajLs7K0RHF+pRkDXVdch8L7Q2d3F2edKSmzIBRI2FohQPe vgVrTvTyc3guwecxZx5u6xaFZSVZVgQjOivWVWnyiAZh9P89fqh1f6Nu5zoKliCMnZ1ovz6obQ7M 7euuxNMklYcZwPxuiCtMowQ9EuSuHYjZJ/IWBE+q2RT7v3/VKSQTFL+S5afyPo6gIuZYMCIPzzvL yJU/Qs0ocwP5FHIKfd3HG7Z4C5dh/lXxv5tG1UQscxG+SzP/xUeH/WPfLZGqCkavzYnAKNChuPQ2 rxCKCvVkgh++Heh5Pt9dMtDj9/2z5z2K78oSWLXjFhxBVvJBKwo+cGqjRziE4xo8z/P2h7lT2Ke1 Q93ucdYx96wlJmNYfcINsx/bw/IJ9si9P7kB+0L/DA+hrEtmvQj3j5MJiWbf0w/ndJKsI/6IhpeR +TZ9Rn3AbHc2vqBiP+huadIUdfmLoUIuCrI3DQ+ZAgsSrE1QcU8CFJ9vqx6v2x2XWGH4dOJbhmlY NuwWGmJsRhaVplCVZwGd4Uqoslr8vMNMQ9fv24+P1vVa/WzL9i+vRhlg7SG2C+0R0GjMn11iaWLD zWnbcyTyVBlRVRVREVB/T1dyHabwQ57fEc17dn+mk7QIbxwlA75zDleItSLcLAyGQxUGM2GyYRP2 KhUUgKKoiMH3qn2PnjciZUdnSQGWUkwhLWUQmRUZj8JSnQoJ/97MFF6cnGp2ZcMdpRMl17dqQdEQ B+koPK5Jh2DaBTL78FnoslRfDeRDVTFznOw+GaYr01hjB+709c6Dbywey2CqKIoqKRnkUMD3jDmx hX2lMccPpmXC0iXXe7wIQwB1NQsoKUr3dzo5kVLBylEDhvFkgBiIHmVQMilHaGClyAqOE5UfgOwf 35+bRnOunE2PdO3KAk16pD3OmIECahyCaNbNj5PG5OfHyyD9pV8H0t0qML6rtcZVhX/0gM7DQdB1 zWE/Lo+imkqDLm/nAUgOQrDybYof+p8kh/cPChBn2187xRuthukhtbE2duXs9zvj8mzObC2qNpE7 Jey0yPyJPH1a7OndVOl5667tPvyZuyOhLMGiVr36DAdWyFGK4Nc3MY/orK9y7OZrqmasVGamKjq5 +ZR/eZzR8CPQqhVB1UF7fLcIpEOYGSP6xNVFWNS+LXXZ/iPde6KVHdRQpE8iBLgUpRcg/XI37N68 p3gfKpJznqdtSSofgcT13zmH2Uq8Jol4IeaOeZQ4Y679m+f37f8wM3LlmjsldZkYkMPPNcRlzmMR 3IzIhEzFGSItWHHloKOlmi3WFE6vCM7qWL2dnHGyG0sG/H1uqXXy3hQ0dvz/4nIdA2JTkeMDXbua Nml6VLpVXOPd5ciCxRBDLw/AwPpl38qfvnZPzePbgwX62jEiBDzU9k6l3PfkOU0aq1pqWmteDvdH yp1OOvRw0tl2FTAcScTUQ0fMoifIp0qvPkcJ/R17bHHOBURimoncN7dXaaN3Fvfkv4Tgvy6h18em bH3nIl+mAoKEigvJVllKVvF4cDxwzpj6aZjjC6ZyxoK48HcRXVIgyKFQ/7zHavjAyVOfP+gITQhF LoAZsfZ81Nj3RvH74b4WUtL7zms18h7chPu9I0UejfbrDTdSFNOrQ1LaMjJDzU0hhVGBY1/o8Tkh +zvz/HfXF5/luwbzdkS/jj1m4D2bq9olZRzrYe19SdnNOkaqfePv9mQXujpgHDeso3NVBwj3wF5Q Ud+ZTNGSxGoeOEwByIUlsqrIeV8svZaLywp57ZIGUSgqqgxEICjpBCChK3buq5AQAXqzpcrnsIG8 ITZk9xFEcm4VNFaNYzp504fNuWK6d8BcCmuKtxZ0ZiWeWzZaxiIU3oJvW+upA2tTmmmLer3u7WOZ tr4fC6cdssxITJGNL0KHvUtz1ffwX6T9nzmyHAU58FS3UCPpUD9IKOUSkFFCoDTMOBBgiC5J4Hzv 00b623nbyuR8GwbxdpMNOOc/y4+6geKhir/TD3UxI3Z5oIIZuujfELQXPBDyRVJETqvQFRWEVvFb Qby9UpeAXgYlUpIJJ5Ii9rJMYOqEOjCdHhIHRAU6J8zOGaZihJUFVFVCNJrSAwqDKgmZ5dnW23hz XUnsDRrlX9oXvdbNhg4rFsD4dKk8Hkf9lQIAE4+PG/24nBnreOzxvj4e/1HrHslJYWKBYNvqlQMG lBG4WSdtL7HSTBEHSTaRGHf1fVwfBHmMZI96u8YDSI02jAWHaaq3uy9JcmKuBMNrY2lUC2w893FN WFuOJDnIuFnQ4RZpa4dbYCwHmXd3re2y8vNiYBoXyiBtCIPCNsczGQasG96PCnByt4CfgAoBq1zq SUPO9vl7u97JNEa5bJS1wJ6xE3t5NLMRGhK5uPacC1zvMY0N63Id5jy1mcHBsnN8kRGTOQNcHO5e DIu8WyUuaUBfVnO3fe8ezHwaiGzOYBrIbBYYrOBgwyzSIqzmbbE0Iq8Yt7myiztGFrt5+lvzlKR4 5N0xk/O3qsmrvooFlQE4rTOTyVXKXEz6ujG2WA+iIQgkieXyZsrjhE6IlRiwV9bWHuoUT0d3f6eU D1MCeB07qfVnryaC8UY4aXepWOqmLYOdF/C429W7oN3X8DjTWU/w2Zz8+9eWjGGd9PO4oMikU62U O5K+LKvbozL5fNBFZP5oCQrAWApCMRjAUH9NhREkUZFPZ/R+u65K5ma7hDwOYTw9b/q8xZ2vzPKe 1k2J7n5UelWSCkbKdKFDL3ZJiqyVD8mXSfyMmY0BS2new6+mJRwR37KfVqt0d8w2xx3ggIPo9Xu4 bXdMRVEHYuaII/uiqdoJFECoKiSCEiqsgijIIqwiDIAKFoCCBwUz6MGBPvIYjBcjTsJOWc7n15s0 B494Ib5DDQqu0mUUFj+73v0iH5p+aifD8mBvyI/opXKRk9yj2HiMhBgVnp3tNuC6YBqQOQi9+mwW JD+iwn/QxLYeBaWLaggfE3p/nmKzcFKGiOveXbxIkRgbo7QxawmAiQtfDu+Ml2pchWoyqkxk8HT0 dKVmWkeYiGDmCHOLHMDuhAQ+ibHSipbyQGRF3XNQvT7ii0/kNQf+qkLXoBl6DTySYTsw4k24LUln yDW4lgSj9qgUl0W1BJolEnoOVIqmJgVLKQN4qQ7BDuVkdFC7qy6AE1whDWag7rG3NJChMsqXrgm1 z57NbAIgMPkrb8kQEQGlXMy0C34F0K2IR/Z7AuZ8+e3hYNwdN17SI6wn+sNMyUmgjmqkkdhF15/x 0bsOrpnoETURRKzliw74Afmp2cZj5vUei/Lz57elZqM/GUfEjYiEImQUnDltazQJEeYEVj/Xwk0b 7QD/VWCi8/Ohaw1VrNPBNEIKiiKeBz07D5v/b05rSHby+eQSHaDA/nqrF+Bk/RIyTqwMVApZSsgY yQFCE2QBNTrO0nv+gIPYCDFAiQCSQZCCRYQQSRYIkkFhBZFkYgMYMQRkVjEREUESIwBQVQUFFFAF IRRiCyIiwWKQVZIpFAWCwFFiMFViCkRBYrERgCkURgoEWCgqDJ8Z5cHAh84UKKsGWNpaIs7sMzzU rfrpjlhYjKl0UyGAk9fOLA3FDmARkFV9ur6aLr6Pw+/Iyq9vx+/I+69qvbRX4ZNSo7NMgzvFoRhG 1Y0siBr7eaABZUURGUQWMiHZ/13wySFEA4xIZzRs0a5u306pMcrl0PbQbtYUZIYpkscGgcJ2CmYa hCgZLO0ioG3onRC8XtWJMDgcN6lIzmUnW5w6jGjSqUukZa2bG1XbRI2CFaFu98sVqsfqYJpUqSe1 vsYhrNqsNHuw7MIdEkE0LgNIDYR8XOIKEbOjASDV4G0GvE20aGAHCiyN4XuBFnZA1afFQ4ePGLPG kRCSsWiyA+oHYWz0rSDI73HRNjV4DK4Q53epfdFQjm6Qhm1U6iM05paI6QOzerPMoRKdWjk4N9sN cdA9Renoi2qDIMWc00btRQditJEHYA/Djlgw0EQJWdFGQtzvlnXgHbc1RYNys2rU5UUJe7CtgDoi FKd7DBy9215thkoNB3mowXBFFHw5wVDijQqM4uQoxcCCunRHJxk7Bs8LPYuEYZxDZGZysJ3dKx0q 4BtqmCkqpa4WOnZRAWCgFrgm5rozYUXdEOjcWY7GkODxQB3dUT20cV+I8HjmxAjSILbJpkkCbKnU jCCdbQrXi81cI5xAa7rstLTx1sga0gC2dwzhdBJBd0mBOzDUvXLlKSCTcfK+uRQ+kRva4/KPlnx5 Gn3fQchuTa4IJJFA0u1VgI3AYNOniEcjfL3y9i9sU5joXbF6JlhrjriFh+eqiaNbUBnWsUwTICMz ZuzgENGSHfGc/xbATSJVxTOLdQxTZRhLZefMBBcahLLThgJLWSrVwGFFBBAsFFexw6LgrdquNNZQ KLygDZM4IUaaIXOLr7nj2/H61QrYvRAXOtmmaHOoerQCPNqhYVIezyXIpeWQFTMRCYByROBkA08k LjLYCZcYimmVvMFYX8tNOwqSaIGRpQhyutKN3LBndgKMFLm6aazq1MrBw/jlGYJRa1FkEtpRo1TN ggNpo1ZlHIVTQEYe83aYL9ypiVKrOJZZLWWvpzEYf38Mnm6fx/gDzugx1l1aAHqfjClRI2nxdU4H 6JDoPvFqYBGjcwz6gfreHHxHbBL30q/POUOyDDPqs3DYiVBJGQEPrIKomMjQt9niyBiYYjjgqcUb fceKiyVVlozjzDlSRVNawdzFEK7m6/QuK25mOfQ5dazK2TIGLRT/haoQP5WfWhjUn8NpvNN1GRGp diRjU3zNBxauXULphJSI/ENxzSUH45d2dfdqeLsQ1VGqApaPFXNQeVYFgaM3GPnz1ogXS5lAmfee 7BEAWKG9CATMAxpV2FWCK1U9XB7ftfL/lpR8G5l9a4AqulTqjSqoc2Q4YByiKtiPckopGEeJcjx+ iGEYP5J9aC/aKrKi91bhjR0Kbt3UtYZPT7W/TkST9ifvqiQwoaKfjX35wz9p+PHuK1cS8+ppapgJ v3b392GoPsX1RIIWpl19l3yXPS+7EVrrTtK8z2XMeS+Nz+XOYpaiDAhqDhAkyIuPy9bLUkfR28SC R3dl8TJ9FxJuv7dBaukfyCxLTCXKoCF5aDMnQ6Xc2jKKdZh9hY6qv/gd+cwU49NaGZ8VFB7Yyqsr 9B5xhPVWpv4LDbuBaoZELtjAGriCd9CQoqILHGNMZMqT3PgHUsDaHQS29zMRQ9IdQxzuH6XRyqrg qOP59UUIvZrGgEN5sjmkUwM633oGKLtjHs1SezNp47mOy+/SrgW6gtc3GG8khEkCJF3KKk7ksZLC iHp5eVbehNOwsGLaIYwjePQ2RJz/l5qESn751gTG/eiAcZmQ1cmkx6up0c28eyCWEtPwa2222222 2222222+nR7Ss9R59l7DwvhDXvg6e9W6dVBVTDocyOBfJd4cRi/seu3slrl/tO2RkGcRgVQVUQ7W jRzeIY8bOiIUkUQP3bGERK9e3d5IbmHjMjghJNiBxQcXUcMz9M4JXDR5seIbmc4QwYv7r8QLNh9M +cdNMx7xMAsMZGsmY93RwVQ6+KWUy2fr9Hj1O5WUVVUVVVVdiK7B5HNdckj5pCkHhUUYTv+Y80M0 II8/eQ9kkXPm1cCkFhqymt5XGiKUeMTBJVBQXuFVBRN8SA6Rrh8pWJC+Sd/Xt07TItzHQEhyvoUJ YbHUGSfp/OW93cx1v7p1XZEMB2oXM/4uPapIvEYjy6oDMy9YHjTvEVO0idrW2Rp+9dv5UMDuXr8f urQ8E0vKl+5Xb/GVOSoBfyZM1IxnA1h6+FaXrL3sEI5v6p2MB5HF3S4vkqAwqIq58NMgHrGLRIZy 6ZukPDwmZEfMEc2kyFiC45+OqdGibn8D6TIyL4ThSyOzAoqSs8EhBx2dYmS9e3TGcg2ClERns2ss bFvKdK8wP2hPIWiPj2a27Sq5PokZeskgOL1lKERH6QQN1AXVPzIQrO2UI1u4LNB1ZPc8aIQHmS8Y mb4TvNKiVzMxRRh1Wnw46pQJhP7lMEW8R97RjBM1FUQMTY6OgyoAubTPfrW/pyl1sZVk1HyjCasr jMQypxiWkn1+qca/b7ucAZBnlx8B+VOrzSpBhPPXK3lFdZsesDLAvz4BeAQKtf/3ws5nvcV41ylC ed/ZOniuJxSMO6rHmqRXBMD/XjpnJmOv2nCAJ4IHmujMqoMDMI4AnauFdd9jHxeGc+VO+yQXcZlq yKTGJRgmBYll8mKCi5C1RQpy+XjB9iervxwY5smSR0J1ZWLX1zgSzw4hRecaH18mrzyXVXnmbhoE Eb9eKOvjSgZs8oyorUkMy7eK2S/iU7cpkttFMDBa3qy86dUR9GBJ+uGjmBSulfN6tWeYEYkjCMgQ 5OGxx0dZwb9n0N1ZjQdHHI674WvAjXo0OJRkUZKEEpcZNCQTEVMRk5I3/E3MiGZ8mxxDgdGFMU01 rXRCCCcH1m57PoucQHW+m+Lsq7rNEojAHUuqmxDgmThWj3VWPVm6cOLgy66z5wvcphCSPCwo3G7j aKPb4Q37ZQerBBeaIjmiPH08GbW8ichfJ253Ra4XiYa+bDW1pzmR6PDzFl9zV7YTEU98RlptFsBQ xKUm4i0xWnugUUx3177OuCCxBw8vljViHvolOOI5nRKswZnOqYloMtze5m3gWmmvVFineHoqJkWo jRulSOXt8Mq0czcQeRkwLCFVTu9YYxFE0KQlDLmBg0E3Po5LTxyXHNxKxzTkUQkImilMYOijV3pK SEbxhbqKgaGJVl7+KcTxVIKk3nOCPbEPv/RSBAAvU5fZiYRcx3Krov19El5eXex4+5N58JGwulY2 Cq9svz+L4a+7nXYM2DreSTLUYjAQYh5ykqiqYLLwOZYaz8X9nPxj6ggKR8DPOvac1NoUGhp9C/Gh Vq+zqzpQidyXTuwIdL143yVTM8DXLt6KZ41ijezMvQPem2RFKU8H8K9qTOVVFNc0T2e3okUuklSj l0dMcE4iJLIVfTqZGB0D2j7yTDyVEXubZME1NBUXDuTnAZDUVO1E4nuS5hjJe9vE6MTI4JwcRh0P 0y1gcRBVXp0vLI2jpRUl3FT4z6yxHXkwbDkYSOuRTSGTjO7yPCPupYn3s2R41vVd9J5y3iKzDXw9 J+6DGPbLE4TLoK471F9CBM27LBP0F3HTCSnqmq7yikqHrcYXCtiUevj0rF886hxX4CmW4CsVAX0Q DgklCCCmx8GRCiK5QElPQ9Dfl+8RouvSAu375Lr73HaWGduNfCnF9qIym+0mX6kdhxlGRVjZ3yXu oEksySoqR7emiUnC/iCDsglEEX0llvEZZSvVtLdUoetmMeTIvAh45fxxZtwwmhmZEkfN9CzGJC6O BwfEwCuxQbHgGgQaGS9J4DbCJJOKFlxxPk8KtgvgrXqKJFQ4a2y6tVJc2V03fJTCA41fvqoPAMPC yN1pVFEJv42vErPmvr5f0BIXR+za5etNuckSzs7nlQr+8hApHeOi9pX28BXDr8tcomXW05XPc6Fv Q5I90X5nuQMq5yMELKPLIm+8807lljBTozWw23MjlUVRmFWMZCaFlKCE0Scz6c+wRXXhvvwcHdVs 3t1OqSL2vAacMs2g6W3suOTMiKk9wZh5l0D9xBspeGmwTP7J9r3wNAOIHu93s/vh8H08Ai+vIWR9 MqwSuxXbj0LWQi08QwpZBcSEdwQrDIdS578cRUFEX10wYOR181ZM9WlswCUdYONjEOVWqQfzQdQU Uk7fRFLHBa3b8ZeGWHnzn0PHA1KKdI16yoYRyYSRGqLjAis0XyE55jC2bIoPl2XXCNifcuU2YVZQ +05DLNV2llpplf1y3zhrfSRmY94meAKpoo0rSUzfTxnG1FGW9XFWxLv0PH3DBU3oMuhmbcfDLRhB wyVE5ZgSKiFewIiJuooiiiiiiiiIooooooooooooooooooooooooqguD5MNuiQwLcdPTP3qddGe7 gomldIOxhsU7AgvhMGhJ+GXGRBohNv0XHr6d9r4cP3sooaI46o7ZCYEDKmHGL0/CDFZNHueG9ZYr 3RMGxDIwFFUpZKeUOm/VaG182E7B6pQh0+66SMwfaYFYhyxDCa5HukWoQI0SEIc87FkmXG2UHVVQ BwnmnrvhwQJD3Junu9y6YZ1wmVgKMQF7mbvyaV8aQM8OrQhukbX1nXjAWkEbZA8w+sgWtF0lAmny TubNPaTAqKGPiM9xiCZbCqinBud4HiMa1F9ayM9Dv8WrhmpS5WYhVBRSvrnQnZYgZFmrPXWCEFS6 W0a8hs14Oi5MZM/lZ5Z+FtzxJN8V65snHitDyS4kVSqNvD19oiCImhEmMdzEsUs0ltGqkIQirp3R 3HpjQv2+OgdKKlBjCKnQOGRhKaSNPNWtRtVGEYgY+Pwd6eEyEKFMKUfmirUr6lPrDcEcb+BNjgMr weeutSkz0+UbDQG1oJjCYEW/pwqADtoUCaOLm2NvqXCUqJ1gvFUuKql2PVlXwz7gEDv3nkQrPVG1 Gj8ItoYLQarwNJ9iG0bKPZrJboPF8jCke6Qy4Qz77MkoW4tWFhpLqqUeKn9jI7jTuzDkvHoGfZPO XjU58ICGLadhhQVUJqD+KWmZUss5Zi+XdIKkSb1p0uTN6QqusfJIUE8Yphh4WSGOneWZZ56ntVIT fUfc2WHOJPGtIIu5JEpxCt0tvotr1kLfh+t/07x7FORLaye7a4FTpWEEhBLcUOkNmh3hVYddPDoU 0GDI6MBe3BP0+ffrxGVPqqjO0IY+m9JCLNWjQiIoKAcP+oNWf4sC+R1EEhymKR3jIdGxjgsEo0Y2 FxdujBObdl8ukTlLmJOZbFp9PBiPqU0cIVc5dWnbqbHEjnhbKhaIAbqpYRZECQkdUU7xMKIDVJ/4 P59v3/pngfwrb6N5oDxUNgcF9v7z8zoP9n1qhBYxZIxkkGRiQCcfq8e1X/CKmg1lIhSf2fv/js/h +Vbv4441aY/lmtlKzBYn+0QSoiVarw5Y0tkzQprhrqrvEi1mzJgQrWn9+v1YfX+vu/1/j/PxgY8+ OP10/wfHr0/DPr44x692xntKnVsXbH3++XSjqru1rRqKwRUVIt1LinDYx+r5J8PzqAURJRrPzWgZ bIC0pSlsn/5P0WH8vX/DD6D8Hmt5Meq2JZ9Vl9AIbwQpLw+7SVb3dCB0jCRkDKNMXs6LnPdTAi9Q QcRjiF6waaYQ0XTs7Pq9DFfztGtERRUS2VRVRgjWioxUViIj5WtStcyiixRRGCkfV4dJ96KkQ86Q WiqoLFFjBFirCLBBhEVFYoiM48Fr1e5739Dy+9ry/B5f3vi9WvR5fme15YPeSXPByXP0lTybPJgp 9nVvvdvv+Kx3/avuOG2y8Rr8mBb+EuMYWcMV/MYhfyTzQ/LioJI2Z+964yCuKr5KM+ry5x4Lg40v 5jNi9zHaMV03dPNM5fHFMwVd3x4+3tvPt6RGn8V9fEjHt0Ptau2q53GdZ54xnemXR3qoWlxjW9xx mOc8reopX5bIrYduM4I3HG4vnG9tHqLxxzytg1YZXjSOccGyxwxa2Xfzt4d3PTqsTHu7pd9PB8fD jxz7d/Eevj3Dp7anFeuG4bzqX0J9JllE1iTAaoxZHw8QQb//NHtie4g+SQ/09JXIfosnrJCBPN39 2VsIfSFN325Y9+PtqrEKypdPySwYSbH1mLfT+RuwyywcpC9i0MdPxUDj9WCcUS6oCQznDu+flr0+ ju6/HfV+W9ZvH139fr1eX1+v0591ejDLx9HT08XLwAhcEOcOhQI9MixlhEBA+QlbLMZLAP8Lhqo6 kYn/oUBSNymhKpShkYkFD/pAHvtQgwSJAQRIBoYSgiyCkBgkROBKY2EgMJC0qCIQYyCBFkCLBEWL CX/1sWCKrIl4FRT/FBKiC/2RQe8U77iP+dFVVBaEj99P8GWS/tfR+1QuJAPJ30ZRKOr1yHRmZMDE hOJ9k/pOToxYsXjczg5Py5vCG5C50WUR6arpIoWZE2IREXynaek01KUmqSHp7JwCiEIe+DRX3Cnp iO3AEwIgmEagopEUYxkE58P2z4fb059SHw60Pj9v9azett7wpc7raDOQN7wm2yJJFgEYOJb1p98L PIospnoft/c4sGNz7VHPTDEE8SEcHEpAYIN6ZecPlKe5Q1k1avLDSRO9ScGH2gQJki4OfJaEiR4E YIPkLyiBv6JN5r05xqGavMJEuNYZcgOZB84IbmaBS1lwJ0kefX+TB63g36DYhf2H9W+RjiiUBslF C8kKeuNOEKiTBqBunSqBQEIfDwKbfTBBZvKc4DWj75A+6Z2HgKA31cgU+Pg7TckjxU6jdqp6i4tm 3zUvul6cMc9YfUXIeBHL8QFToYDDYGO2Yg9RTug0QGAVApxwvWKN2Om5oDcnhMMyBh/OK2nHKNxC FtI6rBUPMiopbIFiAjJBumJ1vemgEwngSGEdZ5t1THnWcvGZAIDIHZGGIooh1fWtTMDXoDSWebZ0 uXmchoTWb33jzuyM7CFmoUGqTYBPIWzn2rGzfc9Y5YjsNMwTxIC9EeGYaTPoun9TAomgkdOchCw9 nfmbFzIIYkjDX/UPXgNnQZeC1W2rb3b64bms0ow2G27UbicYtnXhXF4peLZeTLwCob/n8IYvMkPr Up42cm1xacoXjJBUbBVZBhmGY0Bon7Ty83Q6L0v4H/0GUYJqyLbKPX3/q0Zz5rsTiW3KFYd4xE2i a4DIeiK88lJ8hrwHvHWyhSQa0tkD3wmxQAehjqnAYNOmk+4vgUpfvQgsHWRg7Kqi5gQBUDKBCBi6 FoauaQw5y0jjXJPnBqWtkaKxYkyp6qOt4yKGsiUWam3+UiFZPjB1oUi6I3znEibkhkQUTy/8HSio exCKa95/f/dk47ukRNigMldK8Ll0rLQ5sMbIjJdUWLMqr0RP7TqiVyJI8hgyorJJnhXqNJSwVHHu FcK2H6j5a/fhXb1C8eOGBciQpYsIM1+eqRBQMT8vJb/G5JIvhfD/yI7EB8F/eor+eWM5+rTiAeFC 9JVkwRYIH6H+EukOqdee/+PWo/Ny6ZWsv+wEXuKDioPx6gl4nyNGP5woj9/lka68NPb1ugolUVZ+ 6LiRFFvreTTqiZnC4H4QHMWq9iXalf5uN8PQFQEC/xaS9f7O736EJLXEgYqVdFtA4njFBGFXE/AP RSBE+FO5gf3jD2MO9KhXhmewOuQ2yeBum2HrDULpWTaHOrDOCUeaB4J6HkScvcn8J4L9NdR6ESQS H+0p/KYepUONkQn7gr+vPv+f2ZxaTX3UeFRcRpKDpBWHUioN1Bo+1u39m/Bj3KN7VTBD9iahFAbm J3yZfkx5KtWSKmwuvw0cwVGU+Wjv8u5kDVUVfpWGCfKmkQLgvn+xenLDfh0nFvetEmi2Zm2Uyybu pnDdm96957+TleR3EEKVs59auQjwWtR/rNlhFmoHW/BvMS7MF0PnlnAcflDAmFon2/zjIpOZQdRW RqdoJASA0XEHz+KuYrFO/ZYbQ9vG5EyjIG2eeDe+fgczQsX7m/m/Ba7KZDawvyd/CsbHB7H3BVJz P3rgXxj0MettNfaw5DL+f/UvBb4R9SgMvkCZjtiYG16YTPcJJEkjGomPD9Gg8KKm0oVeVvlntzP3 dLWhbPTKWUHpzmVkptYetnI3aL6VxyKHbKvfqy381En5FHPec+TK2WUruOndnpQpUryt1iSiMpJW aLjdNeIxJfiZJ2c4jTU8+jYpdRqr2t57qD+PNAVxYhlDzSOgOGHg48j/IZNldXrRPjXjtjjtjh9R nTcukcqrs4uBujuwwsFtu+YKr3winfE5vdOrp3+G62NdNkWNh941AYoA0ztXHcqkGf5tdWfZw2m8 jQPv4Cx0W+3AfsKVpxjGBLPCzm+vv9XSxWKpJQwXDPs/FfYui522n0xMlgsyO7M0lTTn9lKCmX/Q yyD18Fpzs2fk6BOCmAPd0ZDl8D4gjWuTPFmJCyLSdlY6CKQQUV0xeroyhLiePGWvJ+nT7+BgnnFJ 3iJnhVUKWbVRAS1Gd3W3ISp7j+NwONnCr/zLxuHDOo8jDFzfupMoXEwQT+kH+KyaKwM5D7ZIWE4Q 0fxs/ri3h/KNQagBShAX8DxOQNMLgF0gQ9sANPJ2z6Tqoq1fZnu4kTljTA+07zNnhkKRMVEkQgnd Ef9UE/qexVlYX1nWd8P6vj95ErSVcxk//vqdD931huf1fjRkt+SNJcklrDaTw/IwNNVhwxcWLx5D 5jxfRMWHhIJNeiEBGpOfeaYwqqKXM1Ifv/Ujw/oRfaGgSCagwqJZRBM+98MjVf4L+lT2Rl2F2Krw /8dswyQgVRt9l68KKolFV2XdWHaHbsyXkjKKvY8jPIZNP0rmm7Mnr495BfZzn7dtux+fS2Ore5u9 /RsTy6an2nYXWfra6NC6/Uc5OMiveLN1KIKaBPt9w0B+rH7XxxtNaA/Hf40Xrb2ng4eBgBUKAyBf y8W75BCxmbu0IsgCifuqe8iyQRRfzJFXUyMV1heiDRYhOSfTRpiiiiRp5gtIpqexoJHtIqR+GumU EkQOE6CEH+dWqduLUe+RaDmyu7XIl06h7Cb6EwECr7uDIMwgiRLHQ08zieKVy1ZWZyF1ntJf8ZSk qrAyAwIUgEWNw8eUq6dw8BvE8EpqrClIbz2MHj6MfY5vLu94fzIBrfyNh9ReGoDnUDsV9/lvy/2/ r4f6P6Q/e/Gv3L9t+5X+phO7ze9ccLLvThzECHccL9fAImKflC+GHjbH1tkvQ+sXISaJgG2WKZhV MAyRkMEMs0sHA73gdGa3BwkRT2QhPfET7gDjJJ5EEPAIAGRqGAyhwV+iGx6K18lFyuDld96ty0WT taLM848t0RsSdxZ+UohKVok++k4UA4ooYkIUolA0LAIMEgkGAdKA2EQsNhpUGigpUaAoCEIEKFBP BQNO5UegU1G8hYBuXQq0qknIhRChMr74CkfaHs/imELouYIfsD0HO+fhGD1kfB5iYNH/oWOnCpcl z4dh6SvdwDt4hIDwA8hB5BMdR1vLyiJtTycN/KGhH2K8ZbaxEIgfpEHKHMDpxBB4/QSw0WgwiJIL 5I9Ff01x8vwnEWiG8II0iEdZjECKkVQpC5BaEUiIqhv6UVhGj1dzRoOPNy8OA7Ab4iQ09Mo968Qm tHbxiaOTlshEIiHBvJIuIogwlvtXHH08iAovhB09nPSUPdSLnh+CyfRctwD6jWZf7uiwfrwsfJRo 4/Cw+Q3EdHmhXVeVzs2uDGdv3uy4KaO0c8XzXylB7OaOUUNDMZTJDBooTGQPaHzFQY8+7171dvH2 QX09HWE5S9jrOk1FPJmzQtgbE6gypNRyVw9cjInB4iFk55zXV0NqAIBJ9pHL5L5MpvLEEcjSbjLa IyPCY36nqiq3Wad6l61Lrx7Nc9AOQlDkIclkXXXUVQq0I1E0LIqiqQzXM38St4Q+cRCIGeisbQfq nDCRLRbRWKFaCBNUQHVcqCp2qECwnJk4PCmOSJXhCUmQZCY200ofQj4xB6SMAlmy+W6JexsNv6Kd ml3WtwhyVHIXKJtbNOX53YCINLJElJQ0bJOhKQ4A0TQIHJ0OK2xjqL45hwunXfFTsMMTMI5qISSv 2J2BlryfOiZDfuzmmDCWTQ5aEOLcNs7MBLlsGGSKTnNMysxxiQtGVQSEEIyE959FrUKIWEqoaS9a oXteIYNjSmH1AL9aKgT9QpwP6z+fotIYV/uTcB9nQ3xed0ENakyGVYOWGTA4zMYLNWgLMYUzMBy0 g0UaULC3MP+3VUONjkhMxkbQIpBKWibZ/DKxEON4pymjEBlAlwxhBBRWCyKRKlIxQUURFFFFFFFk S2iooogiiijYSExFH90RRoQE3OhfcQXvWB+EBN3Bxeq33ex3/L6vn8ZK0D2q8/dLBa0/D20jGMY+ kpHylurwY+xpu7vU/ZKBt+sfE+oZIqcwKa2a0pv+H3wyjN/7wQN9pNFtvkSBAnCVHdoAIF7cm5WI IFP0eQiMyqwM1tttC2lttttkLaEtsLbbZJbZLbbbbbS2ltLbbbbbbbIW2222VKklSSSQCSSSSrY9 B7fhHw6+p8w+toT2fA/gknxTt8+/Pp6arqYYWZrvCp7qf3+P4/6N7lFP1S5X3io3Q/HdCkJyfihe H7nbHi4BkQPcqgETWGtzT3pxLFWuTeGuZtzOpJAl0463sIFYQxsZFgTaExNpDGGmaQ0yTqhDlDnW +NEOWHCSGkCHR5Q1w9N6OnNOrjDlC8WG0hiEOvWkMTecb4wXayLwwKkhwhphIs4SBiEOrDMqIKB1 8rIB0y9E6umSBrreiBygcpDoMDlOxS0CcHTqHm7I3vUTdAMJEfJRRHE37F4zbNyAgH+lOgCH5MUY BuJR8Cf7RS0EkZGQDcJAeBQNqhnusLDYbxuOa1btRqhrbGO0C0RRVILDw0fOHbxOBmh4ztMhSeJT 9JMqMEHpIgKUQVEPtNRqKIJdS6ohJJB00soQjDNMiLMkXYTh6pCNHCrVRV05N1VU3sUVXVm2V43z 0ytpERSURBBE7RNRCsRhVqsRPC19bQWaJJoFtpQkULjoEIaUMZWrYazdoyiOk0rTBKm7Nm1tNtGI M06UbxEohEMrrMKuVVk1UQtiNZWm2cRCG0MMk0WlFmIRDJCM5QiIthtCCL2X2XzvFvf/DVyq7bOG hsjZ0u6Zt73renHNp9IRFIiIUItESkgjdWa0P8WWUqxBszTB4VTRERF09s58qzglq0dNSzVqs5dP yy27l1rPdSET3iKsIRMiKxhJZDOC0BFXnzmhqWQg7iCSIQ8o0STg7SQ0VjKCablVN98IIxjJw37X XZ55qskk3TV/NDXQg7i++kISrNERBdKCaIy7dkRRnFiEm+9UDZJESSiz1YDgUmLyEcQS5BzI0NyB maC6SgzUu0LPCUqakRK6lBGaEZoI9E0EkSQ0MoorEm67dhqwZMPKbJRw/hEDGvCcs9drIXREVI4Q htMiiLcOVmSLo5cNvPnRZmiFmySrRws3dNdck57uFnbRdu/CBGO+8nOcHhCNCCpcYgSFRiaCXODU 1TiJMUmbkjQuSaPc3Ug41jacsT5rkGqEJUSikikmxdhPSIeF2TVusqLpKPDCkZum7JXtw2dKqLKp PyQh/qdAht8IiI/6UOc3hw4cqtFG6zw8nLJuk7dtvDZdhRw4bqs3CTdmkkeU1nCrcpulss1WUVYa M27hdywWVa5Mk4k2j3e7puk1bpOXaTdVhNs5dN2TRdkuz+6i7pNdmq0aM7mjNVdVR4WSVcNXaqrJ +WTd9qI1aNGF3bZdw8+e1mbNJy4aOlklmqjd6PK6390F0fvhfyA+KvrwA+SHHgoHvrwE3N1fjmE+ fCPEYZiWEoIVEwQPaJegxx9vtOOco689JQU1QQTQUA6t+XdlqEGQMSIIar0Ajpma0rVAyidWKSpy SdtMV2yqhiaTGGhLvnphBogEZeebqEQ1jVADnLHJQoDxGwF2kKEJrjfOoUOnhocNXaG8fXsa2Mzm IYOa3oXMeLQezh4NLvVnFzLkT3IRxi29a+r5Hg+RVarjjW2FBEQDv9PKiZ4bxXyl2yynaz7RZ9Qx QVDaUpqpPVJaklUNY2Zao2Ia9wjqU4gV0q8MrPeuKa3JM6GERvCTNcp0nDWhURTeQASiNsw+6Xb2 1Uay2lHdMXNMRIWNlHTVdAo+nozWQqzCMvVpEUv9LO176QIWFBWnsWHhEmSYdGngZyBRzUqXJGY5 fKpQj30rdhEh6mQlIK4c+YI9TZ0gEnY6o2dS9oMxceRAsJ1K7QfiuuUdNCjUsZIVKBKFkPA6jkPM ecopwaKBtIKGMj3pwcrJozTrRHcSRErvGrDdmkWUaMPDhZdhvTLbSyInJKWnqzQ4q5llrEJmc4iI Vg50Qu5dr6yXiGyKq65so1iIzgrjKXCqDYqfFxJqIEp4ZOPcUYoM+qwVSOA6zYdROVaEeE17OrEN kqyZJQuy2dMLetjLKBlowkgGZVNUTK6xvfWHGkMJ1Xm0do5c23xK3m2T7uGsOxrFRvHG++W5dH6P nk45Xpg6jfNK+MvAt8cZ4kjp0fVhbXplhq25x0z0bGTPQRl5XjnIwzTOLNYidnPOjuOOWaYVlmOM YXkBAFHIyLRAjgihUDqCRHHpqFBRRRVTJaqXfOAEnNDijnCLn232ljNx060QZq4DUW2Q1wMFTGht FVA7mHUZrKHnE4gM4QO4JaiTwYTiii0c2xGkbyGYpuXNhuLHEZGRN+nPIsItSypZCabQQkmjTTgZ AAmo8w32DkRtyirDTLFWkEpREcJtIJG6WjJpw4nTFNEulV5mTCmi082yEz1Sat0LqXdt0mDqFzes z5jNC6Y4cUeAXKtuQ5ByllAasm4lyliCT1LTbdxAn4QlZpwzq3JJyM0/GJCa0GGKGxg5MmOYDlTY yNGF/hkcmx9Qg67XcxKIpKDb003Zo7zlpERFWE7RBpk0gCSccLN0eY/I6BUoVkCJCk6bkJiYDGEk ozDKOtinyxNgqdDQamioXuwmimSllrzKS4wzlutCcmevsUZbt4iBJduoNpHhCJDlsVRUywQGYuMd y4EgZRTMnoMPNaXNjIGKtHCTdOy6clZFEkZUpTXbeSDKBHEViGcF+FO3SzpmoycbMREdZ3gLTs5W nZLJUh4TiERYKiRJlBHIYYjzOBtjyoTMjcqGBUcwHNi7VSbjQwdB1JwYctoojPUZGmgjBxkhlEIh sqjOVM8uqxmzatGGG7qusk6um0Rn8YhCM5QiLvLVdJq9V3arl8IiB+z1471YiiypUVFnWSSA+31d R7j0neeJococEzY2IiniVJFjUmROOnUmdDodjQsaEdUpMOmrVk1UXdqJmHaTjia5ZJo1Vjlsn8OH hk4PDlssoq0Ol1nhsm4bu2EnLdhVu4btWrRuqzZMmqjRZVq1aLtFc0vjk1cMMNE0zLJus8t3TRku hCRhJseyCbg7Ycs2r09MNnhy8mWXTRkzXMjWeXyj6kOB3+sYPghzoZIdw+kTyIdo9GCgcCHqUNXA KcqGw6B8j2xJ6iOPZox7MOxta9qjEQQUDrzxrt1IYyTXNBQCuPRfT2qkLiUVVSggQWQiBqdEkyCE spDJFTADbLbCiY82hSAaop01vW9Wuc5rmdpK061bGXQfDuzrNXNAJERsNtmaFjRCHi+a4FrRHICj hviDq6WMB2IZGpya1M8UBcTqv05ufPNWvVKrT+tDNx7j/rHcQvmV2gQbiDlu5GkQAQVVE2UXcsFI 2OYqYYwg7BiE+ZR5UiHyUiGYSE3lpERSIggjDgikDMpSqVQAZguSRVKrQaMMEOYoxdKK57oernKt 2maEuatGazNysw2WUapslCRRPQQW1VJ1GCDMZRfJ0B3hZE9nsoIQoZEjO6WdFtoNC6oFkRMx39pa d+4ztx0v6ogkj7ES5vzIgzZsGt6KUaiW5VWsTAR5CZapSEq0eisFkoDp+c0PDs9WyiijZdhZ5MiU pd0VBikbwDmbzRE1EihvUBFhtzYx1WbLjOEEcvc2URbaLxETzhBGiJZqKumH2oemIhHG0dOXG7V4 boZqOWmcdumS+mG+FVnajwzbtyjN4XUWJoa03i/idZJSzsmUSxjERA7QjKV29kVjZtTdlpJ1f3Yq 6b7t7z5UU2iCbriIRaEEdqrS5gnw4zpvjZ0tZgY0FFLQywKCqOouXFRC+48iZUAZKKDk8nasKJM0 oRaDpMuqmeTV5XbM2jkxPSc7JFZSZlRVCClb5IAQzy+DmU55iByW7FVOCRsTJZJPpqUKF2HOTdjN mEGKi9DQ6GGESxE0UsclBeLyTQrnBXpi8dZpN5qKrCAGYwAAagqCgRAhzJzBrWX7BiFRL4Aa0CQS IpM5OAxMrSbFx8VZkpbSXuBMoiFLCRHKlykpiogYilIDOTGkKSiQcUcGGLGQrpyMWKjikDIgRyKK KuCgwzw1QBJpEmW5WWCRF3Jj30RA4FmZG53WoQKaljc2MDQcgbHZeguN83FwHUIEYHRND5Gyk4kJ EkohEmFFETXM1klGTNZbNzNONlaPHsq7Uk8uDZZq0ZMOG4m5WUaM28NEMyE4QI+x+TzGWNGqScU1 UWcKJpLJJsPCzY4TSaLI8vbsw7dvY0YUWavPpdsu1cvRJ27UNVHS6pEUUT2UNUXXdrMjRkupBNRv vusRs5UZrt1Whyszbpul2SbRNdq5VarOWqbNdxxU1as2HTUmycrRy3TuydHLRRV08KrM1kk13SrN hm3ZuuqtWsQ+B/WiPzg+UB9qIZjZ+aP1I+xT8YPei/5xEe0PehP9OhkNXA2Qn16S3Xp3Rjp4wdZr E4VVTg+I9L1raF/G/qhRQTFoljlhkejEO8WjjNQh2qpGIIpAHEAtWOwKKiRWbOr0cu9Xd5Y42ey4 ll9NjHiWJuS1R1cGGuYdHRJCmXzWi8WhjmvT09IKJGkEiaywv4jvElG9Hh20mp9dRhFoB5gRANkM RECcEQkhE4QRMiyFLdJ2/DaNtcuw0qo2y7CibwkEYogaiKncelihqZl/dtcUTYrMhRsUUoGKBiYD o8lKpuL4ea+01LhoPaRIIqHYjEXUNDUmaGxwQnQRFRVlWVupvGORngQJrI3xpRZazuz909TLcDLY xuMK0BENZjtFmJSjJGIgtrfxkxWVvTwzW5Wkbtmhu2aGwRRa2mxsrq6oZvcVKkzQyy4JmJcxHwco nPeIjhy4x1OEEVXQ4bvNsb9ApFM0OVC8ihIJljaZ2TYubkCxY2KnBMYYbPqJlJVXhmioZ3WQm2XR KiESa5CIiJtKddzfAxIFTtp6daN8nls3VyUWhGFdMlFPZZCcbols1ZZkLilzAyKgVI3PSxE4Njqd CZIy6Gt9mbJU12yeIRlpCMpwk3eKRTZObpMiZk0gnZSr0E1GdE0GzcST2EBWHNBXhm7mZUxFGKHS pFuosox5Z5nh0qgoVECOzAEFAPHqFhAhGvLyrFFmig42QlMLvCZXltRSebwmMMcc4FpVpsqRi2qh eaiKgTC5gMBBWguuaufTVQu1cqa41o051c87u2rdubPLVmjK6cKmkMnLKiEJ2tWUBE+AhUpWJZkE ddNDgdC2xgPtwaGxEobFiJUxFNciylVMJCGLGO8NfR4YaeiGGTljJdKHS6scptV1Hbw8LGOpStWR JKkWhlMQjNMSNychTVTQp3O5YzKGhWli0DiGAzRJkSg8DM24JmJgMakjYgwBWEKsGCp4Xc+nD0N2 yjYwxNLhZV5ZLvF26fTDDCbffy0Wbmzd20WasP4Qijpk78Sluu2aLrOnabhos4ZKpKuFGGGbI+CG /nHCU/Pno1cpOlEWtowq5SdOGG7tVm8rN3JT4dJqKrC1ZLMMPCTRo0YUSZtHXWTNJfdu7TdCaTy2 OXhqs4YdKKFjkss+qE1l3k0fY6R9sH2wZ++uAhVA959CIdhE58U6cHA+seeYzmsry0RVWB0siDtl bEtGcGqcpQIsHMqpjVRhYi7UMFtmBNCMChvrsQiFcoK0roRd5dyr4XOu81g3uztzcTERTdTiw8A1 Wzya10HWa6+7D7O2LpEWnMVK2pDHjDWuHhp6f13BEHWwGqIiMIWDgLUjSPAjuEwvq1XvltihoM+f PYvHHddQNEfCpNZrpESt/cjtuwz2hE7vKla0fOiyIRWNnuViuST1ZOHUwQw7TfczeHLRsyRvu5iS Wc6zmiUlOEI5hRo1XXRu7+OjTNDplDezcWbbRaZZwzE1KikMUCGJq6ACSICZMVDMgSUyJZm5uajE zIwFFJkCFa9hBh7ZjupOh4shnBl9NAzgw0jimyEoiMdppKzQDm5IblpC8nJRA0IYkeTcpDSBiIIG pGFysQaYmREO2LJSbOk1FXbfSWie2s5UnBempjJEQrckcl7DGBIYhtIyHsamdS57hMNi+BEnE5PT 4IKqBY1JlemZcubFCZAHN5KYnLmGQ4oUyFqlw4kcL5KBkU46tBgaC2rJ1lRm1LGRORycEUCIc5YF MISjCRI0ImZcUiYEihWeuajKiaXa+ohpJEygVraJUk5DsPcczuso8QayXmo9TGzlh5JM2Giajwpq leNkd9HFJsyLs+FkDUEZAtFy46kCZkVjriXN29TOTWyl/CQZ5jwO8PQc3p2R5Sogaeab1WiHu0ap OiN6Joi+zthJWz0as0eEgkMNueZlxSczkYwFLGAxM7hCKCAk9yK6CpNU44KiEJ0LAwI5sYl6kdMi 0odBiM4FzGDCrsVLG5coLtC0LF3eUIqC+JmBsg+hsUOuYhGUB7OlVkOzw8tWyyjjrppPfxvfJs8p pqOXsWVejp5YaSEDwCEnm6Xhk8KE1GTZZZmq6YarKJrvLhoyJKRdqqSFk12GbU0TWbs3DhwyVulV Ry0bM7SlG7Zwq0NHKaTEYaJ5M27RuthK6zloo2crpuThNVJk1bvHs3aHSTVguq6amr39Ju2RyZNH HHazN8Q7dqHb5BuzSZJuGMbtlKdOXwf0x+yEEfH+lH4kewT1nQL3A5CPOJnUPBTkVA8Oju7ak6Or hk4X3fWwPq3weumkAzJzqoc0WmAukloGAbdZgAUtJCsK2g62O8ERh3ENcG30UAGysOY+MDB2cQNo 7C1pnRtpxhm7d77eYjcj5pUr4FWGuoOGwVzEVkGgp9AEAbYVEAGS/0an35fPLlaf38EpD926F4Di IZREUiFyO7Uryzgkmz2KDmpGwg0iUE7bGM2EREAgjbKgEUHOQ4sS3IcWOgoXkKH4cvyuRCo6l1BF hBFEEk1jQfzVotC+FNqnqOZEzPIge9SR7D34iBrER20OaQjVXpZsokyVfe4VdpNXCyWnmDgAMogF cGbCGCokeLLIoiSKJjNdxh9CNFVF4PFA5bWVWzbpNN3n0wfUPKTy1eFnabVwjyNN9+mVZtZwSRBE /DAuiEkZJpkNEndc1ERq6qhpZPwk3erNRh9o9XTaOWm20oQ0nOsupMY9cre2EQlENMRZJ3JWrCke U/Ro7UiBVvm2JK+3b1ZrLIdbMnwHNTpIoOZEjEoZlSz5pJcmRNtYJC7giojGOJgRLli9bIC3RC6s oJoRlyUGUmNOWy5kSJmWidJG5oLMUjHq2pY8kQTZqZC4M2LI+8ow0j1MVN+yk8iQ9yBY0NjIgY5c 9DYUMzDnqTZujlRw3VUdLKOmvBbmRWhTXSWjJapCEi2hjSpuYFtYinQcsRHHzIlH2ImRx0cyKEjQ LGgpmdSBEwNlSajLozDLqosmfNYCSgPBS5sxrGabFBBSaIMY8mpUKrgcqVIH3CJYsTMsJGGbZIjK bVYkqMUeZOLExzAtFhiBuHSR0U4F3LGZoIdWyJjYDmhsamHXAgTLPKiS7hwzZLOmSrd7XDCjR7H7 tWbZo1TaPRqo4VLMKuPbbXjknPl98e1s3ZLFWFnDh6OGibNuZu1nDRq7bxBnhm9PSujys1SbtnDN so2WSZNG7fNmwpFeGGtEtSZAg67EDVRSxYoOWcWhEkQFLFCZU9iDRuo5cM2yyjV4cN3SizddZhJ9 EHugx46iI/ePpERMViB8/vZIJI82grAYJ5kQc4niD1cqjz+RN5bn6NBU57c1pvtN1Oo295vWuvwF 3rD0bZJ+IeRbREHFw8m4ELdvlsOiCBsrhVAwg5tcG5y4AhtXNC1mDIGp2MwKepZ5ODkXdMaIiy7U GFthbKmjjFXnL1gs6PTmbxtpY4bbJF3379FMYfg7xHCfWQBFoPYR0iZGyJQc0g065xjK+WU88pya lK0pDaf2c5xERpJy8K2kq0vCCMmSpDMiPprq+CSELokIrvLPXy2fBImukmm878btHXmThpGkgBFx ZAEkP5+dihO3OVsmX2z3ADUYEeWje70eKXzQjtZtSBGzNujCTD0fOCnFZooYJgA60a7skopKsYwE GkL0Ja6nJvUHF0GjPZRG2SisnLtNq4ck8kvRokt1reUWZCjUhgLQgXHuXJGJa5g2hsUN9BCtomRI xPGpiaGZkZ5mLxEjpZ5qT1QYVSVQZiq8mZnku0pNpk0Qi6b7RA/c0bsnlNs7UeTAylpnzaUFUhpC xq/RwoR2oK3RRigyliw4mSUelTgYyNDMiVJFL1pWg81RYvmqmowQgSIyy10kzwg2BGZGD6FwuYDA w450NTkHPsREfQvRomD5ljqB6EMDXv4n3m5yTKHQ01KpubdSJ0zUqO24hCxc3KHU6mJ0x3XEYbA1 eLNbNZZUSFhsjUzNtjaYTiRs9shrkTR6k2iVNhTAuOZDH0ASmlFC+zJBdlJlixSFtSYMYmg40Uc5 USyZqyrRy8sMQEN2f62KPRZd2s3TcJvRkyau26jCySajw4UiH0iGGFWrOjRU8+2rNkwmuzYbM0lj Nkk8O2izloyVdJOlXseGybt+PxIq2cOnLZymqs3VbO1XKbhwo0bQYO227Vkp8+GbZJUNTEkfZmZl zAsYDnPMCQWJEYyNSQwxUdJJa1W7Noou4aLO0na7Nk+/qHw4RHOyN4gZB+pHcRH3weRQPur69+NJ +NnVPiSrLQv17R4ukcy2BZ3vYLsiyNQtgxAoLa3O6NF3irzEYl7wkSQ8sLaqzFzUsQstiQ73NVMh BOxzJHlqtEYRnBj71Eb02BYGadi1lql2h1VqxWWInQdpoTpW1FzYQCdPfh7t0K7scPR5ju/CK8Vt oMKHMySnbSHf4GyDBPdCvGeEF9ZXiX2NUN03ywRHazCtWeyMkPVo1VWsRkRw9zJSIQ1csk2qi73+ /7mBYYqTyTUddLCaQaGipC0pDlgSQhWBLME5GNDCAlYwpDpemJXBpFmHIqImOBIsXMTcqQcIikCp YoQKCncgVd7JjhWLCyI2LiSMibKIgUs4muvONLpwb5ejh2tscN1XoyVXlsZKq51kwbQjlH3+VnJ1 29fR5bWaappM3Td05vy4T60541yRZ5eXCbYS0XpthM0TyTIsqhFPC19lhZq2YTUaLKNFnDKN1Qst xcYbj7EiZp5AXEQgKLcY46WInBVO2GjaDpROLsm7RJw5sgnk168ZqyiytNFD0743zHDfcc2jtlPP zpwSnOB5HQSngESMCigohT150KHIUPgOMQMhYDiUvAKEUZbC6RVFsOZ7ULFTgsVPBETI2psjvkyq bLokik5BUmUMBzckSODEsZFDFIjnu+kQyLU3XjK5mO7ElF44zJDv0IwKDmYxuRocGW1DMlhZhuu0 YatUzl4IEfj7ZrJzYYYXVWZNHCzJ4ZpJMNFHqokwo4VUbJtUnPnZVJ4XasFFXKrtVuyNdelFKVYU ps2OWzyo4KqqpJunbDJVNJl1s5ZuFbSct2TpZRhqqzVYVSatjJJdkw4VKpPtcOFXhszUZREcuEmF XThw3VWcKJs2DChm5cpsKJLOma6TN00V6fpBZDUAdQm9XcUvoaHBEHu3ID4CY8FtWyENP2PwCh7P h+95B8AXXCa5I5AlpZm9w9s9MCWQbslFve3Yx8y7mQO1hHLtjNdLcSLDBW1NBnvFopDfVZe1Y3rk mzrSm5skd6lqGNO6ABPB9HFaXxcPbw5vZDhCLQB0QADIEkru8zDxxsk6KxrBipExMXQJNdyTZCzM CoxySgMYZ4DTYgdOQcyO7MzQIGJkUNCOuRSQ+eSUJmdhCZmY92BljWe2bRJGhDTMWhtoMUM2aqar t5cOG7dmzSyrlPNmm7bbqYbJ8LT0bps9k93jPlL8YQRsyZPRwUdt3bdmugaHzPojSscTRXgw/DSi iBFMTclEAlQQ3HqaC0KE1FMy5wO+bipuTMBxjUxJkCIaVthWVLbVnx0pTS7lnERkR9ZQduGjTCcM QGrA69fEDgkUqp2ATVGerLFeo40TkiFCbaqRF0FNjQoOWgUHJmhppIoO7mpI6G5fBIDMyMYPcw1y FLETQjsXG6EjTSBKXJiGwxge2UXxOdVYhxHdoGpMuTIk8TEz2Nw2H2zJFyJkbGZAc+NzbHQvrFSK lDUnqUKA6QNjM4IFVOlB2sROhI0MAgVIFlEbnNoeQmekzAUxzMjPMiWNBtxTUqpwOYiqbFTpQsYm pU3IFifTZ+ERA/Fcg0z1SeWHlwk22uzVZOXS7w1Pvg8dkvassowk0SYUYVbM3l9jJm7ZqNHCyTdJ qyZMmxmkm3bKt1XfeTNCpZR23WYMN2azJZuiMYqqzNU25NqYdLPugnNRO3Zu6fKqx4ejDyeV1TZ5 SSTdJrLOlnKbjCUl2jNVhs5cPPnM2VbJrWms/BD7kbxE4iBJnB8x+X6R7YPfB5HFEHoRKIM7REDt +9DCGaH3wATx+Xls+9/bsylXPnj0iYlafAd1YtgqFUwEwDHo8GA2rdLMAShCg3HwYOrh53g7NnLV 1HzdsNba0O812b5V82LSGcsjebonSCsqrCiKa3wVpcMpFwrNMQ4wLPoPU6OFzhtaGtQE0gQBJCAh BCBHFBIG5aS7k2L5GKyCPX1lIRH2uWzN7HDpWMnLtREcKq2UT4asLskBTgTOFkNlVTUtjYIjjkYB UV2M1wVsiJQyZobvE4hEKWarulm756u3LQ3YdLLqoQqs3yaxKN5p0nNvfiWdYhEXnBSaiOEohFkI z0STolAUYaOVmj4MHCiTtQKimsYzVcM7wwxkIp6vqRMTl9hZ3GLGQbEdXne0VQ74cqw1SYcnhdmo mskk3ZweOca7X2iEzUeZnuOTMzepqZjDFRxSg5cYzNSZuXIA5MzYtB212KsPckRMiiGmhXYwNn3R 0wkq2eGnDz7JcZt3SbJNwo2UVarKtnlKjNBpLsiCCaHBdIhkKR0DIsnLkFMgqYnAaGBsYEXXRdFV 6zljhyKAikxs1MDEmXCWyQq1S5sKbFDUsSGDMUccrBo1o8pcmQQqYmfGhKRuLeeRmKMOEBhRSxsM ZlZrJE310lZ1nLcxNVMTWhgSJGMdaoggjCoiFBUS18+nPLh5bumjNw7YUWdbsJNUi5kqTXWVeWbD rurRk8NW7PPZRVyk5UTVVTbLzbJxk1Uwss4brNGTNq1VXatXlZq5TVRlkSuyarOedU54VbuGSLrt m7DYyaLLtWq6bVk1WXS5WT4WZqrsOlmbRk3c89OWatZSlKrZy1bvKSqiyy7tkqx2l0223ZpO2Ift 5Qh9CPiR8Hyg3iIH2ntQ9kRA8/mhwhCi0G0HiIfVrB+E+ojEj6HIceg8/ln0n6Tc1oUuUrAYIUQs a0tLCrUtgiloXEpZZvp/DOM+tjSpM/RA/jIQ9vXl8CRH+z6/5/4aj/4fz+7YHd72DaipJS0Hmv8J yR3gI0fnkE1FW6yKVBClj/uYnRu3wPhhwIDxxFMvYPfu6EGMSJC51a+duJaPvUL0pCNyIGaOMEIw DKQiukilEFnSIaNYf4Ox3ro8GJdyb0KaViLTjgwGJo4VhtyBrpkxtqqK7WJmro1i50dnQ4xY4hrj vJIE7iMjBQERAghAiMSA56qCeAIQH1VQe2JYAEgr7gQH+VC+oAwAQKyiUACR8ATIpSkQGBmgvT79 NA/hPNo32cMa5OahJukxrMf8n1g/Emut9ae379Pj++F7n5v2N75/W1H/gM+1h6Gdyig7EpPL5rhO v9NO9gHd3XlOm5fR93j00GnvQ8nzWnZq1hU6tSAqm95QU6M0VnKLOm/T26k7BnN7uy6nKKQuqBw4 h1STsSeSKsNs6NE6JNmXzsFOxhehSpFN/O7clYeN483X8HEm+KVDzMwQNsDSAefws7kHzWdO2zoc X0tZO7ymzIdEqBYN+/5asH0DgJMBGS7v3zcmKP0QgqGq9CPxeGSmVAAxTII7iFdXu3FQnieCf4F7 jdKPkeqddBejPtS9K72a0YmMokokKih6HyfQwOOy974MHrVJ3CHf26yC9BzepkUJVXVLKIQFkwyB 3KFDsTH4mMnSzJus11zwdNfPXD8JHsaHcPrB7LAjVFoQhbVIoeq0RBi14t7a+yRP7hlsESR3EICU dcMkziIBg2qAkBiBTswQckuocpqFQIPUcrI5ENgp3HcDhdMx7lKSNXbJ2KDUfc0mS3iiD1gKWgEC AthIju4KBx5XqogozUH1CW0Cc0RURxfp1RB2TAuAabTPn/IzYnIEC8YEwEQ/eKD7xYP7yBULRUCw YKKUSVhSkW1hKjGICgwEokqIAgKiyMoFChIgQKP4oZ4ID1AhAQsKqVNZXmzEskCUuAKsvh9VwQpg IXgCH2hASEDAh/d/CXttOsEgfDk1xRotKllbRgjokxzLJMBJK1A/8nRgiHNypDDi6CJDG0KRtIH9 OFEQEQgiRdOkxQ2UrMVNODJEYKy8UvEgRS0F7NPoOw4jl2eM526gdAkkYekgpCWyQWKMGAqgiqCp FFFRUYIqiqsFAQ6gQLUVWJFIwVYoLBVkRQVRgbkAsCBZBCAgIbClljGJEjGIySwClLCW0JbbLIMY qyKEgJBCREx+BIyIMWLEGIxYsVIMQjGDFjJGMjFixYjEYjFixYsZBixYxjGMYsQYsUYhiAAqiIR+ LvhRJCfV0/R08ej0KB1AGyKLUbNtVFgQ4AQ3ghQsMiDcgoQUkQS5IOERDG7WjxKbvKz/3KaBuQEL oF1A/IFNSgSQaywqVThJUds7/zVFAw+w+tE2CD3d5/MP46Ev+IFn6J5Bf9qjS1G/1mZ9cRtoqCn+ n7vmRh8nQ/qWFhKf8yG4zK4zRMHcY6DIPsBzOj0C6XA/quj/i5m4PaHFtIbfiUQPH2Pn6HrSRfPb Yqn+saowRzCwljtNaJZWg7NfRGxjCW70TMBE/4JjsUDWZ1+R9w3NwHrMT5VPmP7mgps1aTe9q6/n Jxz2B6iAIAJIgkBYiqkAjBQixZJAUgEVIoeUH/bsT96do/hn7gT3VXr6z4GvcEx5Uv8PcV6ldBsN bHNoA0PpH/pikfXwCe2SWM6JH2A0dZpf78igLQkQ/rE+vIOh1eskIwkaowlixcN4dDwjrjsIrUQ/ agmgOs6XsX2fuAMJ+I/FFRUVFRA1z9HzmgwDpPwfgY4SH0oE90T9iTbMQUWO7KD+r4fD7IB+9F8T 9dKNpC0bK/7MM9YfiCTsJDqzyss1FpVEfdwDOBpbFxScq6c6e44zBMpznX5R+gpJFhIMJ2HkC4eS QLYWncf3A50sJ4nvzFwTqgcnqtpNOlfW6rXUumBPh9gZiDQJsc+1z2IQQKde5KbBTckI8Y7Yh1f4 MZyIlgL8k6+Kj0QL+GGVzrtZqxJbIsFuhwR9HoDSNiB8AgXDfuOL7YIHsgxKPb7auHR3FL1AnZ+R 9wZ5JocXQ5LQwMwYB5MLBk4kdJpQSkcnXohgP88CE/5tnMHebj6zOJ3OLmNZ//QOjnHsh0cvX0WL 3sp0h29VH5Zw8y6Q0wcgimeL3Q62D0FGS0efzvb0Kek3hwhAYB9yb/DYyA+ewL80YJQKon38ckOo /Yf5ov7DM+4T1OwoHMCcz1pdfSvf4W6Q97imdjRLEgz8BNeybvIIHmMjJx9Jcjg5XQS7g0nSYlHS RYReN7X2nVXSHmCg6CdgSgxrEqxYbCdiiFeA92bTxOCwyAgcoXg4mos/QuZLingD2Y91jj9JgqGC yrS/s5gMTKJ9+AVJFhJO8T2jEQt5XvDSUQcM52nme9LjwJdO3PloR2Q/L2M4CiTH7k/CMQ9Ov4JQ zmyjOGUu9NmH9qi1FVVktaPK80ejqvZ8h4nYd9UFmf6ily9mo6pYY2pMxYDGLmQx7NBIahzh+P4E +gvzzS6BjEgaE87eULWnS0G3I9iWp+TuBPapc8WJZgGlgvEzgZRh/pZC5EjJOOpT/JL0ZVnRhmVF W79VpqGuGiwnBQsSGMriUBjRWCz6upnZ/8/9OCfdOnv41+qLxUcRzPt+JIEfKPqpQxNpHVxG/s02 OZ12sVtYkQnEm51sv3jSLoJH9595cIAKp32d//phlUVF96VFkVO6yd4dtCiHn+SG/NKFPCTLZYrT weHsJXssBILCEkiwg0D9FscwR91D8n64CGmwfMcxwOtiTHCD1njthCBIxD8fJ6QDqGIjQeQIrzmm zICHUepbxv3M4w+UA3MDjI0MdyjznwGIQimBghyUUZFZaD/mgg4om4R3NgcH9OqHn5oR5+0i8xDw D/Effp55GK6itYVVCWO/z7dI5IUWyxTHYzaFOh7u0RNQoXgIZshhnCQjRJRVJGP5coGmGPmDDq9L 4Ovpfe9biP/hz2CTC/c3CzcLQkTWcZ7TObiG4zuSePlA0h9hF5IBs2GbNKLbBT9e2lccTTI+Tyh7 w7yuq/QVUtmM0KUzJ7OzgnnLmEA3Pq4++fiEOllcOiAnX3L9oB7U52DAfUWPzaO5TA61IDY6hyPW o89xozxu9T+BpY6XbHMmrtpKM3UYuJ0ePMo8UFeMmJ4wPE9BPlR7kvl1RKiDM76CeTV2P6HaOZPJ n0NB0Pv64cVqKKqVKYWIWsBUJCnrsWluYthcoY3rmG4A+sOrT6VYh/4h3TMU3yI0PQPT8SjOakNZ 5WFnz/CJCBFIkA1jXTJISuz10aCXXpA+AeUMlDsxQMDIvA8tz09YT0hT9d8l+qZjUew+YHn1j+ze /ohUYQjAkv0+cNdx5zvVTwPbRS/Wvan0+fc9/vQSB04pp8HzDDAPPhaWNji5kcDJTKGoptd0AU2/ 7W2SE9j7VOB8OI7PPjY6gwQnfEcwB9nj1nXms9YqZQUwvaatVKBpB9278+No5WBCBA4aesjfqv84 eRC0dWp/WZztC15LD7wC42vYozaypmMEeGckFzMxGMIbGxsdhxCtAVN33QZRROJHT7keVBe8d5xX GkSlKRtcR+PIcodbQenCSjpj0Kh138UYVzB7Kngi3zAOGgl9Ck/i9AHTgokGraKuwHUEnfraCl7i dAuaXCfzEpNYqiv5v5uvZs2qJ+T0fSRcmtkLgiR1bbBQL7aSGvRD/XYTzqdULpPm+j7x428Wc2JO YatcN5/vUr6yzyC6Q4VYEdf3GdMxAyp6g5zMlVRhhvk07diTaVDg3wT9KQyR47S4OZsy2hro/eTK 0B2YYBdtsoCEKmcoKVNKXsz7usgHG45V4ez0+rx9KWseNsS0lvwncORgegaB/Be82GhwH5fUfqFz Fklm0UikLp77Rr6Sg3iUZv4RBrGiUNXDSRQzG7Rk63UOsfrDvPJlyG87ZJaHZ299jyBE7Ikor2Qy maVE0g9o9XmtCNNmSU2iWPvv7lIaD7q/e5nwMyGfOUV+21kODVpEgO6JB4JQ3qTdwziCjDAWH2+S lc7yv3on+6qdiodq9pmfN5jzGkfDy4HG3Dig76o9xTYievk42chQc8Ap9iHI70YmUUvrU6SzROEU jzJv8f0XgX029RRt8eT0mxE+tD1Jf06mGIdjOBleAWC6elSHMoUpipsHQ6gmPTg/0f46IH4h7l+7 +6KfEV6RdFDpSSf3QYZZJ14jQQW0mFUcH7IuTaxWEAf6/37R9khDp+/79JX+RqJ/UwD/Gxniry++ p+ilwn/T/Kqfz+f13DIoiKEkFHR0svu/7G/yJH+V6mONuLPyX24nFi6fA+cqxBuRqSfofn9H1Ie5 vZuqH2ar9ygoAR7vvtefqL1x4cSFnMyWzLtoZmG0ywZZAKGhu3ycH+Noc/vcmGAmxxp1Gkg2sxKG YucdkDAwAu4v6rz8c3gGkyXelfLUZzSYu3fkGD0+xZnD0/knuf2mH9hyodIPz4dAdX9iOp+B/NDN +vUQ/zqoWM2a3sDXgKMYERAgIYooxlIDSsgOlNMTIG4XRAfNnnMLOCxIXcl5hOzaqqJslkFAT7ym M4LQrFdYokCqEoXLj9k2SUkgLCuZMDaZS4oZ+HfgbWEiMYQikkSIxSLIoKCMiqKKxFVUihIyDCSK 4Or/WwIB9qIaV1HQHGIyeB5hetVjBgwBAQjANk8J6Q8VhvSAwNiZkDFxKdeVySf3778vKSgtAhUG oBkRWoJIg/bKgyIT3/HJ839/t+PZ/rXNk1QSSiERKPiyWe1m/8H9n+Sz/tbLNmbpq5fBiVIGpccU ZgVq3a2s5WvQgfqHFPcACEg7ZGFYiIg2XXZJMzhhhoq3ZKJN2pN/nXVZrKJqMN0OFCrVdu0bMLLL rMLqpNGqTDGF2jZhy4bMKNXX/Mil2qeGiSqbNu3VdNlTh0wwzScs2aTRNY6UVcrNGzhJqm3WTaOH DJs4cJKKLsNWSrRJhNRhu4aLtGiyhm1bsFl2x8oh2s2S5S2Zrsm7Yybt3p6auWTwk0aqNm7s2UeC jJu8PwfijZk2YOuvDl4eHbpJy8KJrMmyjZJkq8Ntq8d6eE1V12WVraMM3lwsyetFG+/C702YdKu1 Wzy90QfuPiioIKx/oarBY/2UPl6+gU6h3HtPWbPY6ZrMl3h7XbNVZJd9hksw9nsm3e5D/R73C7pm wq97dJNwo4SWZvMe5WXz/GU5QlAnKNPap6t1WZo9qzdkWTe19p0wj+ctVFRPD8AA8tbY95s0Tg+1 nkSCKm4fBDo0EUlAEhRnlO25fyh9QCiN/2ofd9N8IiEQJRtGiSJJQPV71kyj0csn0TSZLGJADFDq JgXPEHA0DysP3uy1g9MKl5Xnqs+w+qFNss1W04w4szjjX1H8+4a5teIAwJw+L0WvgI7p/JUQqp0N kORQf2DB8QT3nJ2OT2vsYekI1fqyXe9mzfe/Fwu+1CH4sQPCTlZ/UhGzhJwmzWjZLCWr5M3pZJ7J D8kP0sJYxB0xT0RE8YohCMYwIwSKMIKpEBHbayIbwzgJ2qfwiSSTSfE9J2TAsMARBIIF/DPyllgK AH1gitOeIUBYIHzX+XEDYS5CJCDAeAxr3If9jgQ+UE2ygOkhaW5L5vrrCsAve6LkFluOAEKgyWnK QcS9yyBgCfC66ss2JhwV6YMP1AMKK9rKSLEcCUjCChfsfbhccBrqATw9Gvy7altL/z5pOZOkS8bV 2bSlHcH2Aq5gfDDkSh/mSQlEQ0if7FRiTagKNQYKKiyDEVWQIRiRQEkhGMROBIOdTPwgh4fQTEVC /IQOBhKXWQ4PfhgFVYzmmKZlQ0CxYRGRZF2wqH0NEBZBJAYQBT6xM7/WIsYMWQGBGBEZEIhYD5EQ Mxpupqd/cXedZJpcyQ2sP3Eoh93dVqjEIhJIDDTzN/qCzuEx9ymhANZgKyJCJ6oboiHUUHCn6xIQ AiRSAiLFEic/VwZ3x625ZSIQQuRESnFIqANz/HncLuvCjlWQIRAAsGh5aQ14r6ZmJMqCoWGiu55v 6VQG/TZCjcPOQDz+b9c/4oKoKRYiiCIqkdFlZJtJRKUJRD6kiN+h7zERCIGYbYF8d1XLmFLUkYRf quAhgfB/SkSIyKMGBBAiaUCruvvbPPpcxq229OJlfK2BMch5RAjZLmMchjArE5MVcDNhkWmWcgYi aMJIjxwSEUkWLmz2wATRjL6oUiLW9QzMk6rn4Mogx13WQr04pwMA8cApZGDISAIxIISnDO4qDghY dqHWJQPQIZenjHO2DMOhRzfeDwqpDZtWc2JcclGxqOrqPP512V9/2/bfE81/ly4Zs4QYQyP1ilhj 2Ez9gQPr/DxJkjAUYmOMYA5+XcQGLmPtEhUIjUy2XXBdI7VqYlT8yJ4kDU1LFSQxq0m5KsmqzdNJ VNs3aJoaNmSrJk3YcqqrOVGbVU3fiPT02dPCbl22cOXDKTtys6aaJcunhZw0VbNHuiDDDh/om0Uc tWrldqkkm4dLunRozTaJulmy7JRsooszOlGZZw3ZuElLpZJJKuE1mzcq2TaptjR6N27l90HwQ3bq tVHSTV0k6cuW66bp/JGbws+xHviIjh5e73TZpO03bd6vCrlws8quNHl8/ZESfxj+KKQijtk3UD7P 6MfMdHcyNzvJjGeYhik8E8BlF8Te/J0HfxRVsZOHhRdd7UiT7UnvMmRJRVh4ZLNC6Rm9zcpCPhAa CjCa7VNN39u7kxORhTMoKbIIn4KKsPHbksWOSEpXNzggUKJu3XV2jccapx4drKHwJHUSfkiCS8o6 mZMscG5ukRYjmoxE6HQUHNTUtM4KD/H4CDtPhLJ1DOJkZPVAH+2PtQA7Ms/ZQYZ/yXiUclfcY8WC A0dCQnmKCkkElDCBFgh/hRoevnE86N/KCFD5xLd8/Mm/iFf2PRzZ8nzHs/X1jCHCagaSMCLyASKA f2+ovwbRBxQ1nxEeayhaOLDpPkD9QURDERQ1xUAJFBJBVk/7xKi7O9E8sPapFSxqq8LwQrPYFM8F PL4c57PR7DH1e7RoJpTybZdy4UBggFKgQQCbdxTeIhIooqrj8xfgGpofYbGIxgMbkj7SR8y5ckvA zWLCmyHN4RnO2PLUVD4lDEmZDDECAxkXYSsks3SpNo5TVYKFVUkZNYPxF2bd8vz6auHTNwqskk3b ZJVZs27p23VTTZnDN5fnw3cJOGjZRw2TUeTy2TXWTLpslF3DlVw3ePHLVss3YUYbMOFEmMTVdtWF VXX9EQO3DJ5Uds2bbbVysrXZRdgum6TYSWaJs1FVUnlmuukeGTtu5WUZu27CzVk1XXeW7h20eX5I XatmbdNuqs1XdP3IQzdPT0u9YOzh5bruXhrrh6KU5bvLdmk/qiHwIh74CHvgh9wlERIkiinjV9j3 KbKPR6LPa5VeCxoTLrOjEseU8nOFcYeIQK3R0mwhxmo4kwVfTJP2EW/StAUh7n8bK6Q8GT6hAUPx fVA9ZZD20JP/jWdbCAkkhm+we5DQ5jqPKcJY8C53Hse57lkmbN+97noWfJZ9XuREE//iZAfVhWTh /RB2s1ZJu1DCztss6UfP7tH4NGj0TTYf8ZzEfx/VmpOI/U+PHKz0TeFdn6LJtHlZkuq6Tejw4TWT Zu1miajBdo+D2P1VlEAURGxokY+RO8z7vMUOw0dhvsPb7acHhJtGr3PDChxzy5f3sPLxBHyqb8Im +tGiQ58IyKDICyIhIAZQUaqvAHifqRU3OxPsdxmPoXOopA6HuY4IFjxKBEqSOevt/IFPZns1ZlwI GXNntYfUwh9H77DEMEfz/ZgXbzCqDCIeMIB9dQmkUiEdMPUf8C4Fu42zmXm3yi9LaRYomA9zCJKh IkbBAsAMRjGIgQJ+ggyWKaFslo3KudfUeffbjj5Zj8KbS1q8LvgxtbxRpoNCbhVOzRAd/C3989x0 HVplSfAbGB6gchLakSKnBx8oFkuqKBZyUbcA8IwWMdPHnq/ICM40mfLKXKatrNyPEZA0hTQUAbc1 aYLpgEGjFGwO6UaSGDZeTXSo9pIdghYigsO1JBZDGFGBMSIiyZQK94lNdmw1DQKs77vL8pk4GcuM C5iYkiA7GP6dKUkAgfPYUhtWhixZWSZVRVcE3Dtzu641vzFs0bX81gi+UPtCU/fInv0RVEVxV/32 KVVydhzET9fhrISBEbBPOoveA+8TgPQyCRhIFogVIgpgQtKIhUFiooGfwM19JDJxdJfscN2QhoLH 97BfkeKAZxT80fYJ9Sh9pOoTPzdY5ZDzKMmJmW0L4OIhhIvXDWErjrKK/ipUxGVT2aDUNpNnM2DA 7j0B0zScXgaFMS0xqYUHCikMi61l/NhWKhgBHKY4wWVbG5cActsY0jEKMqUekEM7KQpGaSJYWpRC qWDq4qOf/tjNlM6Gy524TMGMFKHLIWIvF0ImS8ZkHAjEhUo7zrqakXJaFmlBJxOOzjgRjxwTOLTR ZNQTBmJRI2xtJRFlp11darQanRqYc0sDhNGEbCkvKXhKalhEErSloc6mSTjezMvXQcmAd351+3N9 e/k1bvr4cl1Q2wIkdZwCkE2m4gaIzAuz/drMqVuXQ5q1sttEwRhabZUzd23Qa2cFNgiWMzgNGaE6 AaOAMnETNHJTd3HDyEJqCBTibON7SIhoebRkIJNjbCCLLWy4kAIeUGMp6IwQaYoW8jMg1RV43r0l 5zTcu5eBGNmUwpznXURLOacGYKyslT0muMklTTzCynF1k0Zp53OoiMh11WKOxQ5lKQWRjIEIJIpI MIX28JN58hSjgN5gcBIJjkD7joclT5fTQczP1HsJmJGwsQgbEiZDbI+UiHBfaVLeG2OxLfBzUmKS DffYzFJGJcYydKOXSajluqt4S33q0aM3Zusow5dN3K7VJy7YXfmMi1ZxonTrrrJu5WcN10nDV0k8 KtttFG7xCGiqrhNKMJcNV34IcxwsnJ4Werto0WZrpOuvKz0duVWWWG7t2mmzcrPJZumyRJs4aLrO HbVZVk4TTZmbVmw5WZO3CnHDho7UK2o1ZLKsmTDdyk5e2AhJoqTWUatCSy6IySZGHjx/qoNWhddw 9pAi98mjpy0dqLDim5AiOECEjI3KCnAo5hgq1JmBwVNj0IL38Jzs5f4EeWTw3dOknqmxjwou8u3k dTyNlgfF4xfaNlPqyxjY0oGWGKLGa7Zr4wo6o3ZmHzBk4cmrDcsTWhs0CCn25UXJguNkoFpIqULw xTiAMFjQhQqERA8gYC0IhZTF8iRCREFAPk6BwG4hsGSww+eXHRDuGEzGgbqFlihBMgjc3BvOPqOk 5TnMDMQ6DUc5crqOgubTaeHrWeSe6xTc0BT7YHx9gewoqxdCivqJdhRMGB+KvaqnQcxwmkscp4dH EdiB3nefSQPb8BzxPIkYmZ5lzAiQLjkDuJmJd2yddfFuuyIw5TbplmjZszM4522nqyNYjRy6UVdP Crlt/rImi7wkorKEpZrp0hEFgKAoKsRARIgoLIgiAgkQUIkUWDEURVIwBgCxILFEVUERFQVRBYiE oSflD+CIgvOJwfSaJygTgpBsctjSaA1FzM5HPtCGJgVwHyIdXl+3A+nkJPrlRfsClkWCjEZEUnt9 xxT44KsX22KYfGdh6FLnEQgQQsYebQ+Ysf+UeanOHXPTUVCROO/pwKnngpugh55QeUwAN38KHBPJ xhgfep8nue59Ee5u+r6qJln9Ix/If2cw/vg9EOIDgAyIn0jiK+0AEp6pkKgXTlN8o6qs2ThMU+/4 dWAn2vAhm9twRdW4Ln13QsPnFHx1oHrjJhqKhH2HwaZmQr6B/0eIzH5Cfcj2ef+IrRQMKjVLRAKZ EhFSoKwgEFkpS2ShQCyREYQpCDCMYAxJJPeDAPTz6VZ7wUWGJUsZWK+awly/WzXBT1GcKZyyd8Lk xEiCDAisgSBCMdiqlxcc90w0bnOGFPrSA3Q5QMAT88O9qv6PDTj0hN2/bB5EhpOcKdiEoe4YHhoi xh6IAzeW91VDRE6g/1HYMvOBoHYqwEQCdaKFGEo4ymi3LitVCaI/sshbZIe4QCwyVISJxzKsulDn Qm2CKCZka2hXULVzlcgJkgM8qukCdMgvjlWFdNMyebuNOKHVW3Kxrq1/IFGA0z1jdtleq02mFXH+ J9dwsQX4xuoakGu1iyMJIUAVjfPNIxF1suaKdyQ5ZBqWzGhaIERNWJPR2CdwvYvhQkykyEBCCph4 7b46PJSUZazWwEoPHHFy5N5YRYIMMAVZsHicp0R73vHRgB21LUo4IThkQa6kLOFFHLXvMUsNJwDt nZPQiteVTNEIO8q4OwOkSLt6i9hqp6L2WFTBSRt6fHPILtG7zp+MqIzwllLMY4ZLIgT15B6QGa+g 1kBrqCybneIUUUYhc4DDfBbUh7ASne9EwPn8/tfpl8vzrhurG4vD7IiatqDiyt2TkVHg4uFpgmmC moU1tm9a1hnA1ATWbDZK+vy4DqZDYbBLCw0YjE4Pl1rTQ2VJdKajFxtXtAQuq0YgoUUKm3IMOKgW AyWCvNsMpTfFwe8tTlhyk5063mk6WXAW1oKgtMrQ3mwJ4IsWkPNhwdyUxRFaILhhCBqHuXApZDJW wmAlokZILL3Aiq4xTsVZmytct+BhStjRFhYyIz6DdC45bcAgQxQbZUhZoFXkOM5Sj3HMUe8PiYlz hOc6Ch8vlE/aDv+kiYjHuHPQwMRT3ntHn02PQ0MyMsTQgQJz1pKeDbL7aU7vZ9/f+jDCVcflt4Q3 6exunRZdMvb12nnHefw1JB0GOhkQNBiI4xEgeXlEkEDyOxU7FVXxbvi3UWWVeTCTjj6KNW7Dl8Xw Ru7duXarpKebROmybJ03dddnalOG7ty0SfFhy2SeHaa7R8c4R1cSzbJuU3Shu0eGRE8/Pyn5HhCH bvv8IeMDrNatv5T6r3S+FMrWIwh6M911jJV8fjGInS4+iW8661HYY3K7j20Xjfjbfa9g5X6Wuou3 8e+MdeM/LyjrXfp98+OWZ3/Ur6rzdKPVcTxe6Cjyz8MRSry1ekqRZp+JgeA5U8SJruL5nmaBQsFD xPQZJ2s2bkos+v1zfMkwk+qTJou6SbsknSrty6ZMLNF2UmCpm5VSXbNWzVZo0aMKNU26TNZhmo7f y4aslmFtXDlkUcsKtHLvuqjt0dqu0lWE2rCqySTtZVko3aNtrrMIQ+9A/H70e8+n4RWJQr8vytnF gKBIGfIpReBWwGqMoKUZJQgEoP3Ototai96QzyS0vck0iBdEIlKEDMEHXrM4CFJfr8A1xeczrxht kIDIADICLFgqqRiJFgqiigzKSiEiZjYRsppid6LrXAhCwYBowGQbBhFhGMoF0q+cQoHmCG5kwOQ7 WVQsRKIsrKOS8I0WIiIyhAjJSLotOYe9NAAsGR3HgHoaDmpqeJHqLM6jEyJE7u7rOk9YcPOvE7q/ NCEIGkgnJ749GDcOMyC5ZQhyPPQW1F1RQKQ6RjiTmcX2cRgeQoU6gxgEzp06neRJHcWKEBzkmGkk T+1UUFBXz9nBWk1HgdYbt2B1oXMyJzyTn1eYxKOdDDlETpQ34XxexdDYfHT1PRYoJbVJ2jmBLKYC gCQmJ0T/zAZ6Vp1vIEYrVL4q6iJY+jSCP8u56uVsZDrsPoO60ruhBE9YhBsyUt9NWk+eBUBMSLCP vJ0lA+uEZvtlkymrZMxttdEAUiOCUQprYT6GlKpuRcZ5DDjj+bOOhHRgsUmWTnUo/I4BCudDWHe/ evQrEK9SFlVgp1YDJYKOKnTUQGEVSbuiiyUkyozR1lN56ep3lPUyHPaRPg+SiSbmIx+e0Uw8wmiL RCSHYtCBwhBWe7hh9frayGaIT9q+yhVG0GR7Svb2+Ww57T1FPqJJ7zM3zD6CR7AxO7oYFCwwx1uK UM5Sn9IdzpRO3mJIx2ithQiGw0mg2lnhNsyW+qcHq9X5NU3capej9Og0hHMdoRxBwPcHCLwoWe1c AyOCSQgEIjFhIBIxFiVsI0B0Ovag+QMO5knNTnzbvH9wU+/uDyo8WDxltR9/4fifAJ9f5mFuML4U FQX1H+fF0f2jvBrqJI++AcqooFWQKtRL+ZAlx5yXA+0Lj9iva3zBX+QUJIEIMlg5R95MFwpY+aOa 3sm5o2YGEpkTKkMbDFGREU0WWzhJlSq1lCiiZCZKUhRGKEJIxEkEZFIib7JY7dR2dZ937KzwJEhu Wfr0+QWwYUSIhOjpR/RFOk6THEyn9yfXQF9BSYKjwKB+PMiWSwSKqiqgALxI6lWzZmlKRXv6Aqh0 SR+MILiMN31jZrIgmFhixcIAqBZJiLQKsFOINX1rcIrPIiEfruvaFhBORAi4uvF+6V6gR+ePwQ36 ABONS7viO1PZT3jHzw0gWAHd8ytIbgsViRYRkYwhAkUiOHw9Zy247cdeNB5sQpgYNIXutXDVk+OY IzQWBRZOHnNenVvOQwOTz9deVDiSdTruHbG1s+w7UuAoQe0R1RKcYJSdYRFApKPTt2G5Ci8BQpsR umFZllzTqZByVDKZSfH35hwPJfZQwTILiGFEhC4IayzoV13YSQDSR0GXms6kIkHWpsifkS4pmJze rg7Qvv0FKps4QoF5NnZR/Ej4wJBC8D+wh0wToSb87J0cu2xrV9rvPdv3XnkYhweI+mARoEBECBYH VOYCre6O+J3xw8e5SwRaDiTFJik0UFRYKSiMOXa4WIHniIEIyxTgcPE5u8vaYF9FxL/mBqBVqwNW LDVpMkeVZV3e/2M0Buuugx7NV3g7eDvynbmFw7kuchQQSIRRFUEIMA2EblyE0GFAIXdY9rJCECIg zxnBPlc1rDvAENxFgyDGMBBncp4CVbaypKQVhBiKsWHgmZZEYAjEZKwWghSKkRUJZESIiVlJGAbJ 95FiDIIEUkSArGRjJAQGEIJEQYAgkQItDaIhIjIcXEmuayJIgGuKP8YJRmBDVnAwxcUZJZONDfAD XGiAyJPV45jJEYf4RJjGZLiEBsQ9FywgmSKEp/3frlAsTSiYqpEUJRKFhoOTx6TenIAER0bPqpMy udNEYyRU4y4ajAFXiiIXdPVa/3AXdIxvwaffe4qqXXNGRAYTFQJN8ooIYCfvnr8u+waIB7H6q/73 +zzl/hPURQqeljuhthl6wkzWCSMLveeURQNOJBkSYHY9e/9IlTtATX4AYkX1MFEPpiES3+An5ZDE KRJ5sDBjMkuNGQ86aDpa6JAYlJYxBiIMosOMM33z8ezt/O+LIGgDWkNIJqhrIECDIBAl6pSl5IGK a5jb2IkTP3v166E1lFp+ghDldJZZ/N1B/g/vf4Jv2toatUq1TiJyYexsyOmUHh/AckSGPsO4gfV9 Fjc3IQmRHfPAuNcxNTVy6MLtmjVJm22q0WSdMLrsnTwwyDtwyZpJM2+/Llw6at0nLZppq6atWG7R o4XvftNjF0I1ZWz3dmSThmk3YaLOkirdZJiSWjpVkqku0ZLFybthdq77cv4bOHjxhqu5cmFHbwzT aqN1H9kElnhk1SSZvKSayiRdyqzWUasPLCrwGGFHDdh33u2SYWPK6NGzNsu4jNKjd05YT2ZtmZou 0YVdHPPSj2x70cJPhSX2obeiXl7HL5IdOee3l07erZ5aP32fWIhEDIucxc71Dzf86NvlGpFD3oRf b7okr3lGa52Id2JuHqOnB0IHqeB1Ne6I5EU8ToKMTOpYLIUULrLpN3vXapGbhoe5hk9Y9sSfuiSe n7X1uZsPLIeTUyRhwqWPJaKKi1GRQ/MZAGE5/BcDMtsOdTY3CeUOpliiHcojSMA/X+o3WsJK4iBw yy2sV/1sZocRfB2HeeUN5wnEdgZBROJ5WPEeYdSckJBkJCf/VVRXCqcKkFoFAUFBkp27GJ5HmQ+2 B4nQ6HJH2FCh81ntYe0EN3MYcKtU03zZ/sohHdzJZLRLtq+q6rpoycOWbdww8O2Srg+wENOvA9IJ cgXPTaqeP/iuveIcKfXmTiIAPwUE9g8vqN1f0UVglK2+sFWwKtQD1E8jSlhuEexlH7ZA8eDIw+iB 0uFBKMYEWHLlrII7FKBLg+C+iSBAkpXORU2SosIpkEVQxQsUHrqn1n8H6I/RQOP0GIQhlBf0wWR3 sd/NyP2I+Q3vedwn0sGfqBCBxx5NH7BHLt9Yn7V6w9HtA8omo9BEnnggKJx3Odok+jZrISBp8k0z aaZA0wkNocggTQRgshuAk5YYMdoKRyxS1lJdaPyzRMz2rDiFjGdCIbkEMQYYyxWIJJMII8dKfZ+7 1knCI2QxBdCsH7U/2w2zNnEoQT2hEGkWi6QICWRwgBhEbOg5dL8MRD+SOCYwEQh3NZCQUCHn+5KQ AhrV9PXBJlVIqhJ2eT71bA/SZmA1yfNQPo83ieVo96HAI15xL1hsDZ976LCCYaVPeh6APkusc6Jr cR7olHqP7oPYYiEHPhCcXW/NE5Cd1nVDL4a9Cs4RFpUkPP9iIcKZ9YLE8yHKqnmA9Zw8vWeJxvzY xiWEgFEAkh1UVZ7bKy6kIG2SQtA2P2zYsU4SmfglLMiM0gM8yC0R+xCHCM33o/2kO4DHr3oX5wMy ujbNY0ImxLRVUOoPZA5cGFk/N9NRlGq0tWlvYRmH5AsPzDOyH8kCUFKoaQP0xCCAQbDoO2AwgUPw H7OhD3iHCBQJ5QTaH0PrhnLBQSb8R0/nAkQYQSEQJBCQN0RCo0vSBSlhCAwCwvHZpTfAKd7IjCSC a/0yEOjCHcdK8UhjMVwJ/YkBs/BA+TyTvOkyH1w0BsA0WCVoDTFgQZJFV+t1iV3HikVTNSFkc5nb 0m+dzwpjF65ewaLW8omwwGTvDWI5Z/b7p0aOmqLeFdvZWb5n4WTPB38D8hPZ7Aebjv9w/o7vofEx OPbxKILtECzPnlllUqKCqFbS34bPi/vkL/idWn8oe7R2dkOwFDt+obKgelD61dvnE9QPxQ8FeAC6 HxLxTt7IH9iIdP8pckQn1rU/o5h39WVISfbMqDCHppC1iXsBaxQhSKbzMV9FT4ZzTm+WK4ghv/2n s7q9lvbMjPM50p3qVEOq1P9pRCoGHZo2EPm2EJuCChxuf25SaLBQrOAGxILDLCjPILAMxJLA4GVI GAxlpZqygYIJoP3Q4g1ExYYFGKwWHWycb68mxPpeNPJxS8XlxDRQzCj0OYDJhSjQSUCWSZOGVn7A gKUYZBdoQtGQSIpIJmx/DMnkmKhMhCHN0oZH3a8kNKEUkEkiwiEGCwgOOXn8/dUlRTghlq4+PBrq M4XUbCp19uACw7UlAgejpJPyn1JERGfvGFfS3FXDMHGjBVwagVwxssMoWVsktz+jLmvVGFYCxMum GkzOJDPjwSM+ZkS52EP6FgtNE/b1xtovnHMoCbh2qpZSW819ynCiPtFoKP2j9wPPyvLoyB+wCMRI MCSEiBsM4L9iPSqBChy6We7fIw0gbYMUgQD/kCEkmWjKHyfbA0lpkBPauuBnAOFDQfkQGDJJGDFS LBBYAjEFBkFWIsikIHcf7yKIU9n5+vjIH2QfnyzvQY40YMUBRH9h8ggUq4esoIwV370ecjBkUkUV w7AUNBEO/SOqAd/oNAf8oNcwoD0BZfrMkwmDyDgjBCoG5k3tUE2Y9CEIReXgswdR1PHx9rkZB3ds cpEhUt2YeSiqsI0eDBaiRMEEwYVLSGsmYa+dyBFJatYuw4MLT0xrERBFQYmzr9YLkF7pLNeZB3xH 6c8ny+8yh0qL3HTgw4NSQdwiJMSd1vZ9HSYBywYsZDCCGlLwkkSTMmgbi6gocteO/g6eD9E6El9A b+PMG3FJLHqopqQ9lirUQIOwnUEEHRZS5mYyRD7KUyVJCylq7z6zCj03eLNCgwTBFTpyJhommlNW UMIUELRGJwJxxqbOhBqCMf6ilP4yYiIymWU0IUjX7RTQJVRp7p3IMyCgQ7CGWArywUICEQZHmKpB gIQYACRQYCIQULuxz1n+HsnIvMmFDx13tP0KJLTAtQoHlapWRRmmW49buUXcnE9gHk2/xRt1tgQ7 u43wYyHLCma4UMhEZGEWRYCvw7OeUXCsJGFBzeZ7t81Aqps78uFr9AYgertQoSohAiRIRWJEYSRm MJQBFWCCQYCxCAQgrN5xoJdiCT3CZ5mLQNjcgQ74hcCjosFgdsYGJUwIUwgNk3BsUM8EuqZAfaH2 VySR8vohM6L156f06uW4GqEYcqHFgqlzeb9dRK38VAhXamWjNXI4gXECnaYxTTAKzqaLFmE1TN8E A7dJIg+xrnET3jryNYkooVUygI1X7z6jzFVRKqVVVXXmGXvdEO0Xa6lChDlExVA0A+n2I/m58NFS T+NWrUYw6HT0D+tDn98+8dqOI7lA4BMJ+wHUbQZtOZe3PSnNLS1g7bHPGiExqEJEQqBaCwgyIetj Yhf9yIPSh09yG9R7g1dyn3AZuvYhyAazeEjCMeWAJwo3X6IDxqB0IZOUWYzhLKcnG86WAKQqjSAC futrRsJvVTuVuCnzICMn4hn+hf25+jdt0Z+t7f5QbJYM7xKIccQKYEBDJsya59V/giAx492ggnw4 Q9f8FfjCPvHuH3Qj37Nej6Ufm2QiEHz0+NyPmjHJd6HYPwFPSJY3nhQ5NaBLNg9ks2YUA8Jgb1DD AxaELhmVJ30hTUlq1ASsqWhYlNPAYdyb8qOwlThLq6uYxYazBBNRqrITTOqqIFyiiLG1VhKQWXXk SomsdZZZo+jWcm5YZV51fUJ9nELzwU4OmgRDEDnzjyulQ2Gqq2Nis1wLkG4xCx3PX0vIJ6gT4xSR Cce6HuELIXx7FknrBsQtEHfvHx6fchM50xHTSUQoMFUhhbj99Qg9ZaUb4CyaLlphjmphjlHoXmcc XNpJAM5fHvk3EvAaHMZ3IIg2QKBVvvOBP4/YejTwnGJDCJXkjeBtPaRHe6PnGh4hC371NYn9n49W 9qcw9TQeC9ICAwaWCcFuEHQBJBCEjoAhEYwZCBFomLL5OWuShPJbZ0aSWP8lAu46gI/ND6kPPfn6 vkh69Mnz4UANQHSZ6zqOI+3wzWscCyBTSSgnFVcfLHbgEMHliYoHs6ajcJnsdVYo2bQHTDoGbkkD 6gfkj7ANy8KPHiqcEi9MKQ6iCVJEkOGywoG0S880ES7ASBLkVKGJfsWkC0hADAEIHzWlfOJpF2pC BpH8pMW3P5gOmx1I6tXKXDyDyIYniRJEzmzgylFiDyId4nGam9FNSJWsabIQ41KzEgMikgsjFH3Q 5AP3IhHEEIBZ1iqQWZwQ56ts7JHV6lHpP5DzGo6ZWpfV22l9nIY4LXpoqW4jL7Bs5BiKe4fWcJvg EEqBJ8ojTCTkUulKBD2jClAYBEBRuAE+aHgBxIdlIfNQNfYiD7RGKPoA4TuIqyLFgipEYCyLFEJ3 fYJDzScg0ptEsDSPMMUoOygV5WFdsiOIv6PuSCQSH14yA/9lCohMRGH5Hk40hAt7p6tb1WEzSH6D RZgwZDYjAMREIxCLjugrCBx2xvuEgHGBmQ4CEDgJQgIhFWoiWKoICDBGAKRiMiSSEIAiQIsDJ/kq KBezoNKUhiGKbA+UJRCnQhpzeYsqqBaWgApHoAQ56dmLQDCQ6/jqxYeoiFZYIlsUMTEiDAPUEDxC B6JkEQbPGxjWcEM6fILQCwIWDtUDpXgPYDdHiENyjn55nV4gQ5gO2zkhkCIe0SIpyxU9eGd0R9qP bkhqYwjNdVw1iMkZGWp6Dg3/tGDa3qBDOCHWpeKNwYbhPIACXx5d+b+lIEhkOppfOFGAkW/mV0g/ ch9SAYiLqYCZDtPWHGMSSgKPz7/l/XX2AUPpNv4mGxYIbGwJJgthA2MI+NE0PpTsJl/DbU56BRB/ xQokVImUoj5ovLT2Kk1x+3Ir8TxQHyIdonyIJYHrfuPUWfF4WEPeyx8/1lHAsljWrBfHJiiZDTgQ 4NUjIxh5paCIcRNcawOBlQQsEaQpW/lVU5VKrVk/nGw9YGFkBrG6q7yEDbohqkw3S7ZsAcG25kwT WsylDXKyjLFkC1WmIJopgMFAtC4JSCmtINRUuUx2MmgNhREnLszUFtEcMwRpE+jJJvODBN0MLjR5 s7e6ddw1TWhEVEYoiv/1OqS50OQOgycghO1BTql77FKoIPBdHFlBFGKsLbwFpiBcUkjJVZrnopUo LCFwQtQVsE9S/QHE7DPYTEzwhtCVJqLUAIZO5FChQ+8RP89HTym5BgJDJjIFJxABhgYAYjBikgwY LItPA4A2CG4aDBjKYKjSNAYCthQsB/pPToONQ1YykCoAwyqpr+ESZqlPp7L39teyePuzC+HVmLBZ xBBKWB50IFBiYbHXEVD2Id49ajlvEHMB5Ugq581+IkktEKKqREL1cVTiPgVzshBgvPV05hNZoOp+ sJ07FkUN63JdaAUESlKsJpAKkFWLF0msp/EGSjGIIIGMGRCoYzEJrVplhCMIBEkMZaQgi0LAspf7 FLhmuUIkTvShWulIMC8kMYEIwFEwXKqhYwDIWZFQZIwBAVjA+j74ttBVhgSGLPX8hJJTilp7EEGI 2lS2AxIBoA8/XXJdmMBiBF97CHB7wQFYyD6KagEIEIR/msunBzW/Zp0wUTSgeMQRS0JCKEFARhCK SQDCNUlAd4HeOdU4gQl8DgA1CVKSBCDLQKgEGBASBBW62M8Q6VUmN1Hh01cP1IfcjVgwE2tFoGjJ fGDjPoJ7FgQIhB8oNlV1GYEIDoz+NSD0GMqHIh0eEv9tVkZ9+0uLxkc0FKjGQIMiyJ1h+gon11UU FUFfxgHWU8DwvaQ/L4nkSeXIHMFPX6bEK6yyYiwliQSBpNERTASpKVBDCBzlRCB9qGJgSwrxSylE rSsggyDBiMFgxkgCIsgjIKDEkWCEYLCLIKKRQRZGFgXZgKSgkxqSsKsCSAVJRCEClhOImEDBoSsA hTSlwS1K2QGhoq9VBiqMoUpQQLCDovZTUndEHe2cQ8XWhlAgKOcMPeD6RC5rV/QM8iM6UTET0foO Llt89kX3uAZf+7SFdUo4xZMN0IYYUKIs4PpoYxQN/8FYpYcDPBnQQh1P52gsPw/ApDsOb3zGnQ6x 78Y0NpCggsFJp8RvtxiJmxIr8FD4q8Svd0ifqVApDehmdhqKKX+jSeVS1k2AYIda4kbA6RP9/2RZ OWDfeJuPLlDmzYINsVVTMhaKPsYAwEFBUSEQVEiiwEgisiRYCCiwGDBERGEoFk7QOnmOAOiIft51 DzhAklHHxW5g2cJs4xh2eZXYBwiPrgh4AjMp8Hi06BJOxD+bRw3MP1CinWB1bGLIvF5g/vVkCGhH P7Tfyo2/ZwiVE8ESwkogKEWAqHg4oZPk9P6f6qdg6N23dpHSEH0tah1XUeGBdUUCKUOGMYSfLFBA BUIMIyDAhFQgwUk+Yknb3jJRQgItoQLbC09OVhxrHIYYUMSWe9sFwVkqjJ5rZLQUPtgoJ3EAHCPv iCh3IDbINIcCTeRFeNEZRCdElOJH8yCkRO7NcShEkAgJCCbtmYJ46VKvpxcgRiQvfWFRFzopggqG AQHATsG+xAZBT+B0D5NhnFM7FgSMRkZAkBYDFIrEGAxfwANAI+cTI5iC8wtarAdIf85ISM4VeZkR j9IUKfbTQsK/l32MIE/VRI7EBDf8xNQvzE/evnMgn3fulgzZB55DRQL6mY41Q/bShDiNPUkpgoiU sFhEWxaVRv9eY4yoMGjznMZhoo1RajJmFighXDOcKZCpDUEIq5QkAqFBEUj0jgWFbEVHMxUFpuhf PzBIo5oKgfIsHfgZm0tDvJcoou0VeF/uCe/IKJCJ/QMXXcQTOZg7VQP9R4MRBik+ZltpEYDColrI gQCDEYwgBwwkAMEQ2RVVDg34yUeMOUOkaxHiAMF41Zo6BTEdOvqA4Qz8lOgirUAYxCwg6f+TmPVh VGgswpKqmgREkQhSNggICAz6jxMFQejuEnWl93f60toojX2SaSfTNowFEYkt3ITg0vMObDjdO9QM yooGWDixk21a3rlSiQkCAgeFiM3ifFQNKRUUCL6TqnYmf5SqdvTe4d/s20QHffSoUMrP4EPwEN0S p+wTL+YH+r+s8lRJ4+pprGfT84sfhKI7QVpQb2CclNAx3BdNNahvCaEvOoai66Ha+nvvRW3U6jX6 sKfWTMG51jtcsmisXFcALWTXVYkNn1fpHiU0gwhmU+gB9QPUJ/M8Hp/LxQHgzxXh3TcCEYCEOH9Q jtuEISQhJDKpH1zKcSBYFVWLGMWIILoaUBqNtsaUkMHjAD434h1IaA4nun0gmn1hB+I3H7yyWIBJ 2yU9TZhGBKkITbqIsC2eAq/MEP5ghpBAcG/1/jS+wn8Eod0e9VQKxiHPr87IMYwJIwASHGrYAE96 LxRBOeABwDwIcRJ8xb5QUHJHCCXnQRYSRQiJES2V2Q0aQYikSIRjJEh3MkVEAgYuhD29YlgaIuiA aMnz+O/onrkooOogYgfehyclyO7FOC2fu5LhjVJqc/Fk4mCVad2uCZenhxki8dHLlCCPFdeTJHGk 4TyFh4jMQVK3To3G6EsGLOrq0stCjdZ7LhRnh5A/AIxr/KZ5MV0JoDRDADlwq05Re8OSJAaQnCPV 7IR5gxm8+K0It8BNHckfciJOUBAC4JB0QW0JFagJnJN8r+i76ICQGDAxlPB+WN3DjBDwAQgIUN77 SMJIzMlOwxIb7HGJw8fapBMA2MIQTkQ+q3fah7YUNpGEIddxFzqZP3wNSdjEeEFOcThTb1YWA77W sSs3mQ4Xb30JFLfvHeJ5EdYn1ofAeRAPRqVh7V1HP8PcjpR0oaIgSNCKxA9yH2QHOiHt/ehnBLSD n/NIOpIJzDBBPGA/2chEJECAwiDFIrH7UPMCPah2hCkI8u4PGvvHlh+5SDSqi7+GYTq2B6p7UsMN MoViSFElsEtknAFGSbNhvZNlBiZZZpumyJqwyFslVQHYIywtbRhRAYMYDpLbRuKNyCiqsTCpnGjS RiMUiREqaiGIjHVGSyBYnjrMJjKxTBoSsCu7Cgjhu5BGApBgxizaSrlLFHs7OxqI1o3W3e972TW0 RVYyVIKjRGS2iBTrksyFlK9jMv5bNia2OkCuC73k1SxSwWQrVgmUpOpZY7ra2lrRgb24umUZdHZL FSQqKCMZkQ2bcyIKqG2GZRgkZrSZMC0LC2xT1kQs1QbIwWCwjFWKkRAGCCEoxO07S2vbeo6PFnpI +8glPiOwn5+42YJEe6kEohayjCyNWIVaqDGtrWS3vuTBFFKSeVwkDAJqGRIJCGTQEGSaDJKVFYqI kA1Bg0XGjApswEscCxxVBYFzGIIcwgohBG8oRG8BbKJZfW9IymTmQSCFUIZPsDyh6YWsEO+xTsNQ PCKOtBOyVnbvvs1ACv0EGSQDaTTrvahaWI2KyiQstWXVgUbKWZKmSlPW0UUOim48m/TCH1SrEKbh miOdyP5OcOiSes4ik+RYOnANQnx3gcE/BqEn4lGh9fOUySA8nzoe4OeHwCwycpyIxOTloqFQCRsK BnwoPX+g3onIKGJi8k2++ipKpPjG0+wgjhrVFA4rLwbYbSASCRIRRWoCgFtImhqESvegG4SynVA2 RnJLWtDrhdx6IfCKJ5Q8CT1MUDu2JsngwP0nWncurUWfOidMQTP57g0BMiYseOcvLV39x9KNcj/Z dGFEVYKs3bq1TMrc1HcNzaGnSDu1aFrVy4K3tzEjDQJ/8YGEh6bv7SDMgqRER8iCSIhFEQfDszPk J3XPsQ4Rw7R+KAc7p+Cubv7RoiySN/Ap4E/WBuUsndAykhUDVEAxMRgSAGHgouD7z9kGESQHdAc+ QgWQwiGdNETOthRIqe5DIBqBIARYgwgrICQIkLrPMlhQp0hIB7P3wIOkBTYAxCEEJEhAEkM0S9AG 0YPpVZH1xkN3P7uYT1/+3aUgQX0AHRsQEOJXgB0pZQNHN6v9dSgatJqKaKKWBSQoGxKDYlkQpEsE oJQbEoNEpWKUsKVgNIwoCJSwoJTChBklCJSDJQEoNEoNiUGxLBLEsEolglBKDRKDYlBolBolJGQp EoNiUGiUjLBKRlBKRlEoNEsiFEoNEoNEpI5jinicoL+oSwnAj+wuL+MAJFP6HN2dapLiGEFq0oMp UCQe1KVApOEjec6JpRJpqxFRRisRQQZKDQVSFiVGFJFKCSMGVgJWQqASwJEJu2DBZJgEBCpNIUtE lUuAIWAQuCh6NpXlB9wThnxt8JLI9AnXtr0vbCBkGlxZkhIQKAhAUGK+/P4V+quB22xGfl/rgvFg wSkhhH6djbQh7hWi7KpAixIx/VysURBJp7cD9cP8l1QPIpxF2mcGzVYSiJMo0nNqnapvJqazU/t2 RipJ9+JbXfWcGewukiGpRpqumqAhNrso74AZCglxypGq7jJI4UKYEyvbG/5cWMd+FYHagWjlQSgd dNpVUKsKHbRlWA4uK/cISxxrSPDaPELznVsSr1lF7uvU3uzbuz9g7BJyGvNWcPqX0d0nuXqCPMR8 z1zfmapIQcIFlh1l1dGszLsLm3LVnbAZu3aOyA2EEQXYDYoMwQBqsWWEBSpHdIw0bSZJ1mOxa8D6 vsnNARmGvZVyblMGujhJSga7GhYQQUUg4ZzihrAnUaU4TID6NAQXdo0sWkWBdIPfFSNmq52P8ZyG jxkENamg1rvMUWNpAo4w2GYKoKogJMd5DZbMDRJERxoNJZXpSsqtAOpbAqHyVBVw8LaCJ3KgQdir XhItjglFJBODWQHYwmxIIag5QokgovwKejiqGgOgnvVslPUzSaQDsQBiLlz7gaT9Pd3FC3FlcK1B 2cqgszydeJSpt73o71vv8JxDXEoYazHZZZOpMDCYSapvA4wMjbJZgvFbZaVjw2S0EtJVGEYHJEiQ AvfVHHqlRUqTk4s/FD3oj+9/UGURA/rI4D/L+pHbnmUcSvKFGac2hJZNm6wG7K0l6whuRKUmWmTR pI3SbAgUAQzRMgvp9iMkP92NIiEWWoUnCEoiC0N0yzpCBpho/OraVrWhyqa+D2YiqB7t4NcQhxHQ I85fB3KLnE/rqPCI64TZCFmNuS9+V2ZshmS6A49OaOGbTTMB0oBhgCIM1cigrOZd5A1EMIGePEF7 PlZCXDJIjQhvgHjA1Q9mHr3IDgOHzllHDZRAM93Flh88p0fh+0zLaVo39hSdhA6+jrIH/BSEQgwU hFEIKBgOchxni5k0ghzEREFh4hKZ/GkpUzvO0iO3pSCVObUEkicEQzRdE45U1kMN+kSaLmE2JePM NnnJfaCHNjQCiaLHFfE26GTPY12SiJIJGRN6is5TU8hmQI3D9EAC0JADdtgUGIiiHkBRO0H91jis oXAcsds5YdgEDgDhFOUQMZ2eIncJk9PHXqta1rdc52+Gzv59Rpk+eE7twf32HxB1LGlLbUGMaWEO oCLpIhUQghDUU+EWw4kKXk8lkp8Reot1RwhACWbU8pAvDnpVOaugTYYbHJ/0RzO9j6BnI7vN/uRE VgRRb6y8rRH7YOVe2IneREiLGohURJCERKCFRBAeqkBpVkWSReWDUSM6S/EIoh3cOBk5FcvmvSti IBG5YDRcEzRVIm7CQ3wYWYAW1lNLTDBiwjJkiIKIgMBIiQSCJIce8jAoahBSS7kMQVIbpBn55hs0 zz4ECwllQWHpBRiM9B3XHBC/P7TznoNmhA/8+qnrPYLQ18nQoHX70LWncQ0fAUPiawSkgtDekxnB uTTiiZ7f884zsi3nzHHsiIC2AgECIBTCScaZjYPXXHLaDYWAMlNp3wmusTAH7CiBWRM6IDAL6Y1M Svlo+Nwy7DFttkl64ByPxCdUN21H3Q90GDDZgahzlDTKahzlLQnaz0QIJo+X2AMMO799Jt1D32fF 3vu7fTNy72YpscmJKWlbd02cb168gDNyj24p5jjToIGWUHk6VRQPzE+K+dCPA9wnkQiOLlm438AL gwRJ6+pNh0dH9n+7au19hvDqE+/gea3XVdD4HtvgYGNgC2I9F6kiXhIXx4bqBcOyZ7h5gmi+iFr5 Z3I0R4NDOLRkwrmaJ0NxPv4HByUaHBI4hytBAarl5NYyxxdYQRGyCwGaqrIqDky0kqQmhNScQFWi zO80XiQzg0ZUSHFMC8AwUdCl4irTFpIREFQMYQQQzPGLhxrfWZcr34XCsgcibDCzqwgdeLA44C4t gwTAjFUNyJRAUKFBKTIABo7iERGOeMjzVih55pMPESsuSoQL0LYhrtFC9Oq3546tEYKEEE8I0ih3 BHXa6ACGwKObqJcvUt0LaFdBrNgD8zJQHaACGyRYVaFCwmFA1Whw50ZddIRhwei2QCH3Ahw+b4uF 6c77MudWE66nAIcFGLhe/czIO5gJvo5IzTsqtozQ7RNZVXUtM3uTE2sx2RmTejDWYaFTQxGSlJhg jDImykqLoNBk50Y8SmzepeKYRgUGqpWrNQwB1KdQUQMqUsJlY68a2XMwp6iKIMRLkDPvBt2rQYaN MGpAJZQs0b605SYvNgU4y5WcTd2OhLN1wQJEDC+jC8wwRpEpoWiUg/5n7xPaP3g+TWaqEnV/crsB 4dKH9UOIThXDx/Hk4c1GHEHFIXIRYkmIG3zVShMdvIQOVCBZDnuAgbM9RAnSTpIcSXrOTRkRFVVU f1yiUImrsuULQhPRETtSipeIgohNnBhMJSBmQoFIcSHASk0bCISiBJIsxUVjZdaGFEjHTnUVxQii taFQbHm0c6BQ6QugVhriGqLEBvm+1tICwhAgxUIgwWK3imDBsEVHCK0JEYRTX1+gtoc2xjOsX/iz zEpxCecODvUYuMlXmgpKVWWwqKCLBYiA0nikAw8QgaChOAQNRCYShxMJ4AGB8vSxxZSXi6SzLBym N1vJL0EGK0JguABwPImSMgCN4QJNbDAQ98LpktZvDJHLvNMNw0CFNrEBCA02AoUqewy9M9m9KAOw 3AHQknlHq0vd6SKzxanigMIggDGdZ7fdKkFFegENAulHOLm3hn56qC2kvSFhCdJPrZA+1JFgQnpf tpVtt1QcuZbc3XeZTREM9JaWBLqUzXHfaVmgB8UpkQRRlSshYA29OSRCycXpAh0vuAgWSQ0hs+Up Q7u8dGJkzJ5e2qGma2UxKgSWINQRDgNO4iSmTndOvZi2tbWx3DeyrdkqEnvhIokiIAgwPOnLrphg bhAQDWYWlEa+m0cgU1hZscjil7D6FKVTIcc+gZiXVT4rr59tRB/kgJ61E6IoIhBYKEnuQIfmmixK IlBjrLAcHBs5DUQ4DvYpnICcDMqkqksQniwsRZFVERLaImCVEUghMYBLEAWClAQlIZJZCjSoLgRI RtZvtBCKigQBDSOvjcFIAutNhSnD16Q1QSRZFSMOszvTO7QbFEZEjIDIqqCoCQQEFgJAYoMWDEIR lzssKNfYiUlGGpqDUeTrMMwkIIEN4fzoHJUgKfJ1Cet/ihD2J3RPkvPxGJh1Sx7Y6S6PXs1EUiyP crlQ5ixBTUKqCrWsUR1REAFARSXgID/pl/h9DqerMVoBCgnoD0ApFIpoEHSupiL17fTnLBoaEBIo OhPcBZ4Oq6ZwQhnCgQz4HW/rIQgSNQ5M3iXaQveYXpIDKgFF2HQHYsZF7+qEL0TwXzfkDEBo/PSP LxobOov/bmXsh6IK2nc0HKgBg+ZSJIQkhJ5xBmkc1POAmYVT1nsh6Z6oghvu4qURsh0jgsE8vM5h 4qBigWiqnOn9EgjCCEIoeVii9efAQNowFPQnMqB9Ylrw9z8E+SeBqQOALoh8lcxzYwbBZo4VPRFf 8ooXMXRwcMcHnR5IHPR/aOdTv2hvFrcqQXynxPAoLB2pDRB6vwPjcP8ggJAMKvWYMxTpEYGBwCDT fwE9qFu46s1gM8GkLcB52EiW6xIHQjB7862GghAjAjgqKBQ1BDI6sBASInyMrGiOMLQC0BDPSxiS Rk4jqLEs01FoQ2pT64HRnzht+qhFEP+Xxi/whASs6aD2JFEFVf7aEIg937Qfhe6N0CIH+IPH4hxh hgYBAgcN/xH0qphs4g+9DmA/MRxR53DISiIhEj1bfmMVcwdv2R/IfNDZDx/ywgj+0efLmIhEDhtC l3CPb/cb+J6r+NFVAlHuJrEDnA1fEToQ1O/xjUWy0ZDqW9wamdZqduFnBZa2vcCmEUid/QJCXFdX ZzsHZonaf2nngzvbJf9CCZEREKeiHpEDeIRnD+tGyH58MBgjJGSQWCdn8IlA9FCxI1srAwXApaSh UsBC2acn2TcnB8CHgDRxCecG/WdZgbOEM8YSmMl7gwhLgXiFv5ShKSj25GUojzBOD/JB+8btIPSD lBnEGxuRJKEHgPV80RHofLOwP1c9SgScB2txHA0lyt4j9tLzxENUVsMKhIcEUxOt1PtuAGdHHtVT LTEfWBs4s3piPggPF5ftKEpAMQcD8eCvqJ6SPmIH6ywU/vxKCRyJNyYD5zzevzdAkcRNiqcx4m/6 nnRCia0oq0z07Qcn0SQDQXjg+yAT8JOp70LCewHyPUh6TVu/MdZzbRd5Ft+zUIlL7yeM9QpxHFCS WwjmDaJ4cokHy+cDzCejw4DQHakEiqjGEiqLEZcvuJ8wT5IH9/5APDUbxE786aopCAZkeNHhPaKb R4PtmozHrnIsBRDoRhzp3e8eDE4/LeT5kOjxlFpXd+TD8LCwHWeGrrCtrdebp126owIPMeian0eI cAoBdUNgd3WchfOQAxFOIRTIuEidFtolyjER6JOeEx3JCQCiED8PyEuPvQ9A9InaO3tE9CIPq+QG zh0vACHBuH61JkHtRiMYTQ+iH8/pj2I0+zabWBHaNxTlUdqPPmO9UDkUOUTxFNu1D/QiwkYxEgKI owRCKgixEEZERCKu5RyrlEsWV2iaELGkDdrxeJVPuRz+dDODgcHLEmgzomVA95z6NfAJ1P2nYD1g WRTp99KqG4IiPMHtE8sE8gN+fs7g+KNEPsboepB9A/DaBHuQ/FDQj3BO4eAfoCQgRCChIAJ6GgkC MiwGMIMiggpPrSSYCkkQgfsf84wf/zgqiIQIh73ETNBYDaIED/QM579mxhAExKZYUjNhQkKcOCES bqh+/nz5lCtVW90Ppmj7cwHfQz1SjQYkzICF1k6HNAKfqV//xdyRThQkCvUATwA=