diff --git a/.bzrignore b/.bzrignore index 095c04b36eeef000183e08348613cd06b4007da1..57fcbdd8f737cd5f4ea089cfc36c187cbac9e08f 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1770,3 +1770,4 @@ vio/viotest-sslconnect.cpp vio/viotest.cpp zlib/*.ds? zlib/*.vcproj +libmysqld/event_scheduler.cc diff --git a/client/get_password.c b/client/get_password.c index 1b7b4e65a9ff25b287784814fc68eadcadfafae5..b643b760718c953c5430f2a67599cdd4bff2189d 100644 --- a/client/get_password.c +++ b/client/get_password.c @@ -64,7 +64,7 @@ /* were just going to fake it here and get input from the keyboard */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { char to[80]; char *pos=to,*end=to+sizeof(to)-1; @@ -150,7 +150,7 @@ static void get_password(char *to,uint length,int fd,bool echo) #endif /* ! HAVE_GETPASS */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { #ifdef HAVE_GETPASS char *passbuff; diff --git a/configure.in b/configure.in index 64886892e2efa1ffe86d6d84298b27b8b85572df..0f0649fd3bcc53227b94ab08556cb57e144baa16 100644 --- a/configure.in +++ b/configure.in @@ -7,7 +7,7 @@ AC_INIT(sql/mysqld.cc) AC_CANONICAL_SYSTEM # The Docs Makefile.am parses this line! # remember to also change ndb version below and update version.c in ndb -AM_INIT_AUTOMAKE(mysql, 5.1.11-beta) +AM_INIT_AUTOMAKE(mysql, 5.1.12-beta) AM_CONFIG_HEADER(config.h) PROTOCOL_VERSION=10 diff --git a/extra/yassl/examples/echoserver/echoserver.cpp b/extra/yassl/examples/echoserver/echoserver.cpp index 3243cc21a7cfa532ccfae039202bebbe75a6ab67..8e23ead20aba31b6d469eeccb64423a6ae7695c8 100644 --- a/extra/yassl/examples/echoserver/echoserver.cpp +++ b/extra/yassl/examples/echoserver/echoserver.cpp @@ -65,7 +65,8 @@ THREAD_RETURN YASSL_API echoserver_test(void* args) while (!shutdown) { sockaddr_in client; socklen_t client_len = sizeof(client); - int clientfd = accept(sockfd, (sockaddr*)&client, &client_len); + int clientfd = accept(sockfd, (sockaddr*)&client, + (ACCEPT_THIRD_T)&client_len); if (clientfd == -1) err_sys("tcp accept failed"); SSL* ssl = SSL_new(ctx); diff --git a/extra/yassl/include/openssl/ssl.h b/extra/yassl/include/openssl/ssl.h index a7eca9138a29fba8e54cf964a062033cb8cc04b2..23e48d2011f287275b5c304d88b1a6af824aacf0 100644 --- a/extra/yassl/include/openssl/ssl.h +++ b/extra/yassl/include/openssl/ssl.h @@ -273,6 +273,7 @@ int SSL_pending(SSL*); enum { /* ssl Constants */ + SSL_WOULD_BLOCK = -8, SSL_BAD_STAT = -7, SSL_BAD_PATH = -6, SSL_BAD_FILETYPE = -5, @@ -494,7 +495,7 @@ ASN1_TIME* X509_get_notAfter(X509* x); typedef struct MD4_CTX { - void* ptr; + int buffer[32]; /* big enough to hold, check size in Init */ } MD4_CTX; void MD4_Init(MD4_CTX*); diff --git a/extra/yassl/include/socket_wrapper.hpp b/extra/yassl/include/socket_wrapper.hpp index d2258a937237df6f7d6f2a2e2920b4ede8b19f87..16db142b3a2d92ac3ee918b4c5c7ca513af695ea 100644 --- a/extra/yassl/include/socket_wrapper.hpp +++ b/extra/yassl/include/socket_wrapper.hpp @@ -66,6 +66,7 @@ typedef unsigned char byte; // Wraps Windows Sockets and BSD Sockets class Socket { socket_t socket_; // underlying socket descriptor + bool wouldBlock_; // for non-blocking data public: explicit Socket(socket_t s = INVALID_SOCKET); ~Socket(); @@ -75,9 +76,10 @@ public: socket_t get_fd() const; uint send(const byte* buf, unsigned int len, int flags = 0) const; - uint receive(byte* buf, unsigned int len, int flags = 0) const; + uint receive(byte* buf, unsigned int len, int flags = 0); - bool wait() const; + bool wait(); + bool WouldBlock() const; void closeSocket(); void shutDown(int how = SD_SEND); diff --git a/extra/yassl/mySTL/stdexcept.hpp b/extra/yassl/mySTL/stdexcept.hpp index 33ea43bf0e068e81609a7c2a5b244f57ef594c39..b50dd35edae495493c5e83a72fdf041700ba0186 100644 --- a/extra/yassl/mySTL/stdexcept.hpp +++ b/extra/yassl/mySTL/stdexcept.hpp @@ -46,8 +46,10 @@ public: // for compiler generated call, never used static void operator delete(void*) { assert(0); } private: +#if defined(__hpux) // don't allow dynamic creation of exceptions static void* operator new(size_t); +#endif }; diff --git a/extra/yassl/src/handshake.cpp b/extra/yassl/src/handshake.cpp index 2603365e41ae59bb5c6e428c7cf44356a5fd388e..2b099af930cf4bc41e90a709175066b859acb5b3 100644 --- a/extra/yassl/src/handshake.cpp +++ b/extra/yassl/src/handshake.cpp @@ -656,7 +656,7 @@ mySTL::auto_ptr<input_buffer> DoProcessReply(SSL& ssl, mySTL::auto_ptr<input_buffer> buffered) { // wait for input if blocking - if (!ssl.getSocket().wait()) { + if (!ssl.useSocket().wait()) { ssl.SetError(receive_error); buffered.reset(0); return buffered; @@ -673,7 +673,7 @@ DoProcessReply(SSL& ssl, mySTL::auto_ptr<input_buffer> buffered) } // add new data - uint read = ssl.getSocket().receive(buffer.get_buffer() + buffSz, ready); + uint read = ssl.useSocket().receive(buffer.get_buffer() + buffSz, ready); buffer.add_size(read); uint offset = 0; const MessageFactory& mf = ssl.getFactory().getMessage(); @@ -858,6 +858,9 @@ void sendFinished(SSL& ssl, ConnectionEnd side, BufferOutput buffer) // send data int sendData(SSL& ssl, const void* buffer, int sz) { + if (ssl.GetError() == YasslError(SSL_ERROR_WANT_READ)) + ssl.SetError(no_error); + ssl.verfiyHandShakeComplete(); if (ssl.GetError()) return 0; int sent = 0; @@ -893,6 +896,9 @@ int sendAlert(SSL& ssl, const Alert& alert) // process input data int receiveData(SSL& ssl, Data& data) { + if (ssl.GetError() == YasslError(SSL_ERROR_WANT_READ)) + ssl.SetError(no_error); + ssl.verfiyHandShakeComplete(); if (ssl.GetError()) return 0; @@ -902,6 +908,11 @@ int receiveData(SSL& ssl, Data& data) ssl.useLog().ShowData(data.get_length()); if (ssl.GetError()) return 0; + + if (data.get_length() == 0 && ssl.getSocket().WouldBlock()) { + ssl.SetError(YasslError(SSL_ERROR_WANT_READ)); + return SSL_WOULD_BLOCK; + } return data.get_length(); } diff --git a/extra/yassl/src/socket_wrapper.cpp b/extra/yassl/src/socket_wrapper.cpp index c6611803421720e0c5b41a629b04a5dc802d7176..803f4b01249749ad51551cf692e985125092dad4 100644 --- a/extra/yassl/src/socket_wrapper.cpp +++ b/extra/yassl/src/socket_wrapper.cpp @@ -58,7 +58,7 @@ namespace yaSSL { Socket::Socket(socket_t s) - : socket_(s) + : socket_(s), wouldBlock_(false) {} @@ -123,17 +123,21 @@ uint Socket::send(const byte* buf, unsigned int sz, int flags) const } -uint Socket::receive(byte* buf, unsigned int sz, int flags) const +uint Socket::receive(byte* buf, unsigned int sz, int flags) { assert(socket_ != INVALID_SOCKET); + wouldBlock_ = false; + int recvd = ::recv(socket_, reinterpret_cast<char *>(buf), sz, flags); // idea to seperate error from would block by arnetheduck@gmail.com if (recvd == -1) { if (get_lastError() == SOCKET_EWOULDBLOCK || - get_lastError() == SOCKET_EAGAIN) + get_lastError() == SOCKET_EAGAIN) { + wouldBlock_ = true; return 0; } + } else if (recvd == 0) return static_cast<uint>(-1); @@ -142,7 +146,7 @@ uint Socket::receive(byte* buf, unsigned int sz, int flags) const // wait if blocking for input, return false for error -bool Socket::wait() const +bool Socket::wait() { byte b; return receive(&b, 1, MSG_PEEK) != static_cast<uint>(-1); @@ -166,6 +170,12 @@ int Socket::get_lastError() } +bool Socket::WouldBlock() const +{ + return wouldBlock_; +} + + void Socket::set_lastError(int errorCode) { #ifdef _WIN32 diff --git a/extra/yassl/src/ssl.cpp b/extra/yassl/src/ssl.cpp index 66196514a876284f1fd6698e7795fd4abc9632ea..747305730dfb232dbf14bb1f7c16b0a9b20b504d 100644 --- a/extra/yassl/src/ssl.cpp +++ b/extra/yassl/src/ssl.cpp @@ -37,6 +37,7 @@ #include "handshake.hpp" #include "yassl_int.hpp" #include "md5.hpp" // for TaoCrypt MD5 size assert +#include "md4.hpp" // for TaoCrypt MD4 size assert #include <stdio.h> #ifdef _WIN32 @@ -1131,17 +1132,26 @@ void* X509_get_ext_d2i(X509* x, int nid, int* crit, int* idx) void MD4_Init(MD4_CTX* md4) { - assert(0); // not yet supported, build compat. only + // make sure we have a big enough buffer + typedef char ok[sizeof(md4->buffer) >= sizeof(TaoCrypt::MD4) ? 1 : -1]; + (void) sizeof(ok); + + // using TaoCrypt since no dynamic memory allocated + // and no destructor will be called + new (reinterpret_cast<yassl_pointer>(md4->buffer)) TaoCrypt::MD4(); } void MD4_Update(MD4_CTX* md4, const void* data, unsigned long sz) { + reinterpret_cast<TaoCrypt::MD4*>(md4->buffer)->Update( + static_cast<const byte*>(data), static_cast<unsigned int>(sz)); } void MD4_Final(unsigned char* hash, MD4_CTX* md4) { + reinterpret_cast<TaoCrypt::MD4*>(md4->buffer)->Final(hash); } diff --git a/extra/yassl/src/template_instnt.cpp b/extra/yassl/src/template_instnt.cpp index 43b80d59a4d7b88d44e7ba937cf8d2637e9129d5..134deb00c756ea572baacb9dc6eda3ea661b435b 100644 --- a/extra/yassl/src/template_instnt.cpp +++ b/extra/yassl/src/template_instnt.cpp @@ -31,6 +31,7 @@ #include "hmac.hpp" #include "md5.hpp" #include "sha.hpp" +#include "ripemd.hpp" #include "openssl/ssl.h" #ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION diff --git a/extra/yassl/src/timer.cpp b/extra/yassl/src/timer.cpp index 4fe0d3aa4f9c33138cd7d137080da6f827a1814e..8b7d2d17a84f2e81550b3fe5abb44b0ca54445e6 100644 --- a/extra/yassl/src/timer.cpp +++ b/extra/yassl/src/timer.cpp @@ -26,13 +26,17 @@ #include "runtime.hpp" #include "timer.hpp" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#else +#include <sys/time.h> +#endif + namespace yaSSL { #ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN - #include <windows.h> - timer_d timer() { static bool init(false); @@ -57,8 +61,6 @@ namespace yaSSL { #else // _WIN32 - #include <sys/time.h> - timer_d timer() { struct timeval tv; diff --git a/extra/yassl/src/yassl_error.cpp b/extra/yassl/src/yassl_error.cpp index 59113d7438c4cb3ad8877432454c8fc35ba9ed66..1973c54d781a7a3baadc67b36aa7951d3a0ba5ba 100644 --- a/extra/yassl/src/yassl_error.cpp +++ b/extra/yassl/src/yassl_error.cpp @@ -26,6 +26,7 @@ #include "runtime.hpp" #include "yassl_error.hpp" #include "error.hpp" // TaoCrypt error numbers +#include "openssl/ssl.h" // SSL_ERROR_WANT_READ namespace yaSSL { @@ -117,6 +118,11 @@ void SetErrorString(YasslError error, char* buffer) strncpy(buffer, "unable to proccess cerificate", max); break; + // openssl errors + case SSL_ERROR_WANT_READ : + strncpy(buffer, "the read operation would block", max); + break; + // TaoCrypt errors case NO_ERROR : strncpy(buffer, "not in error state", max); diff --git a/extra/yassl/src/yassl_int.cpp b/extra/yassl/src/yassl_int.cpp index f7fb1abfa3f97b2906837d90e55fa157c907374b..a715d32f282048aa135fb66a35a573ea48067edf 100644 --- a/extra/yassl/src/yassl_int.cpp +++ b/extra/yassl/src/yassl_int.cpp @@ -1415,15 +1415,6 @@ BulkCipher* CryptProvider::NewDesEde() } -extern "C" void yaSSL_CleanUp() -{ - TaoCrypt::CleanUp(); - ysDelete(cryptProviderInstance); - ysDelete(sslFactoryInstance); - ysDelete(sessionsInstance); -} - - typedef Mutex::Lock Lock; @@ -2109,9 +2100,18 @@ ASN1_STRING* StringHolder::GetString() } - } // namespace + +extern "C" void yaSSL_CleanUp() +{ + TaoCrypt::CleanUp(); + ysDelete(yaSSL::cryptProviderInstance); + ysDelete(yaSSL::sslFactoryInstance); + ysDelete(yaSSL::sessionsInstance); +} + + #ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION namespace mySTL { template yaSSL::yassl_int_cpp_local1::SumData for_each<mySTL::list<yaSSL::input_buffer*>::iterator, yaSSL::yassl_int_cpp_local1::SumData>(mySTL::list<yaSSL::input_buffer*>::iterator, mySTL::list<yaSSL::input_buffer*>::iterator, yaSSL::yassl_int_cpp_local1::SumData); diff --git a/extra/yassl/taocrypt/CMakeLists.txt b/extra/yassl/taocrypt/CMakeLists.txt index 3ad9195b3727d1738def0cc4a0273bb9e8e2710a..0af0a242e5d3e3e8b4615e32d30f6af89a153f0e 100644 --- a/extra/yassl/taocrypt/CMakeLists.txt +++ b/extra/yassl/taocrypt/CMakeLists.txt @@ -2,7 +2,7 @@ INCLUDE_DIRECTORIES(../mySTL include) ADD_LIBRARY(taocrypt src/aes.cpp src/aestables.cpp src/algebra.cpp src/arc4.cpp src/asn.cpp src/coding.cpp src/des.cpp src/dh.cpp src/dsa.cpp src/file.cpp src/hash.cpp src/integer.cpp src/md2.cpp - src/md5.cpp src/misc.cpp src/random.cpp src/ripemd.cpp src/rsa.cpp src/sha.cpp + src/md4.cpp src/md5.cpp src/misc.cpp src/random.cpp src/ripemd.cpp src/rsa.cpp src/sha.cpp include/aes.hpp include/algebra.hpp include/arc4.hpp include/asn.hpp include/block.hpp include/coding.hpp include/des.hpp include/dh.hpp include/dsa.hpp include/dsa.hpp include/error.hpp include/file.hpp include/hash.hpp include/hmac.hpp include/integer.hpp diff --git a/extra/yassl/taocrypt/include/block.hpp b/extra/yassl/taocrypt/include/block.hpp index 4c262e1a5401cf35931c850258e4376a63e284ab..76836615ce63c33a46324ecd50cb32485eaa9464 100644 --- a/extra/yassl/taocrypt/include/block.hpp +++ b/extra/yassl/taocrypt/include/block.hpp @@ -96,7 +96,7 @@ public: pointer allocate(size_type n, const void* = 0) { - CheckSize(n); + this->CheckSize(n); if (n == 0) return 0; return NEW_TC T[n]; diff --git a/extra/yassl/taocrypt/include/md4.hpp b/extra/yassl/taocrypt/include/md4.hpp new file mode 100644 index 0000000000000000000000000000000000000000..aac930d74988daee24c476a3fc3eaed0b44b62a8 --- /dev/null +++ b/extra/yassl/taocrypt/include/md4.hpp @@ -0,0 +1,65 @@ +/* md4.hpp + * + * Copyright (C) 2003 Sawtooth Consulting Ltd. + * + * This file is part of yaSSL. + * + * yaSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * yaSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* md4.hpp provides MD4 digest support + * WANRING: MD4 is considered insecure, only use if you have to, e.g., yaSSL + * libcurl supports needs this for NTLM authentication +*/ + +#ifndef TAO_CRYPT_MD4_HPP +#define TAO_CRYPT_MD4_HPP + +#include "hash.hpp" + +namespace TaoCrypt { + + +// MD4 digest +class MD4 : public HASHwithTransform { +public: + enum { BLOCK_SIZE = 64, DIGEST_SIZE = 16, PAD_SIZE = 56, + TAO_BYTE_ORDER = LittleEndianOrder }; // in Bytes + MD4() : HASHwithTransform(DIGEST_SIZE / sizeof(word32), BLOCK_SIZE) + { Init(); } + ByteOrder getByteOrder() const { return ByteOrder(TAO_BYTE_ORDER); } + word32 getBlockSize() const { return BLOCK_SIZE; } + word32 getDigestSize() const { return DIGEST_SIZE; } + word32 getPadSize() const { return PAD_SIZE; } + + MD4(const MD4&); + MD4& operator= (const MD4&); + + void Init(); + void Swap(MD4&); +private: + void Transform(); +}; + +inline void swap(MD4& a, MD4& b) +{ + a.Swap(b); +} + + +} // namespace + +#endif // TAO_CRYPT_MD4_HPP + diff --git a/extra/yassl/taocrypt/include/runtime.hpp b/extra/yassl/taocrypt/include/runtime.hpp index 09ca7524ef356a288f9e1ddfbaa2fb9456aa5630..3a5cf62865af0fd9edecf86ebb531ad18157e96c 100644 --- a/extra/yassl/taocrypt/include/runtime.hpp +++ b/extra/yassl/taocrypt/include/runtime.hpp @@ -28,10 +28,6 @@ #ifndef yaSSL_NEW_HPP #define yaSSL_NEW_HPP -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #ifdef __sun diff --git a/extra/yassl/taocrypt/src/Makefile.am b/extra/yassl/taocrypt/src/Makefile.am index d3e72346110c4b853b91365030290bcf084a02f7..1110ed335b8e9bf2a0ab31a3c050389baf6c6b83 100644 --- a/extra/yassl/taocrypt/src/Makefile.am +++ b/extra/yassl/taocrypt/src/Makefile.am @@ -4,7 +4,7 @@ noinst_LTLIBRARIES = libtaocrypt.la libtaocrypt_la_SOURCES = aes.cpp aestables.cpp algebra.cpp arc4.cpp \ asn.cpp bftables.cpp blowfish.cpp coding.cpp des.cpp dh.cpp \ - dsa.cpp file.cpp hash.cpp integer.cpp md2.cpp md5.cpp misc.cpp \ + dsa.cpp file.cpp hash.cpp integer.cpp md2.cpp md4.cpp md5.cpp misc.cpp \ random.cpp ripemd.cpp rsa.cpp sha.cpp template_instnt.cpp \ tftables.cpp twofish.cpp diff --git a/extra/yassl/taocrypt/src/md4.cpp b/extra/yassl/taocrypt/src/md4.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dfc2b079141411d7bd998e5ab1784399c5818b19 --- /dev/null +++ b/extra/yassl/taocrypt/src/md4.cpp @@ -0,0 +1,154 @@ +/* md4.cpp + * + * Copyright (C) 2003 Sawtooth Consulting Ltd. + * + * This file is part of yaSSL. + * + * yaSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * yaSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + + +/* based on Wei Dai's md4.cpp from CryptoPP */ + +#include "runtime.hpp" +#include "md4.hpp" +#include "algorithm.hpp" // mySTL::swap + + + +namespace TaoCrypt { + +void MD4::Init() +{ + digest_[0] = 0x67452301L; + digest_[1] = 0xefcdab89L; + digest_[2] = 0x98badcfeL; + digest_[3] = 0x10325476L; + + buffLen_ = 0; + loLen_ = 0; + hiLen_ = 0; +} + + +MD4::MD4(const MD4& that) : HASHwithTransform(DIGEST_SIZE / sizeof(word32), + BLOCK_SIZE) +{ + buffLen_ = that.buffLen_; + loLen_ = that.loLen_; + hiLen_ = that.hiLen_; + + memcpy(digest_, that.digest_, DIGEST_SIZE); + memcpy(buffer_, that.buffer_, BLOCK_SIZE); +} + +MD4& MD4::operator= (const MD4& that) +{ + MD4 tmp(that); + Swap(tmp); + + return *this; +} + + +void MD4::Swap(MD4& other) +{ + mySTL::swap(loLen_, other.loLen_); + mySTL::swap(hiLen_, other.hiLen_); + mySTL::swap(buffLen_, other.buffLen_); + + memcpy(digest_, other.digest_, DIGEST_SIZE); + memcpy(buffer_, other.buffer_, BLOCK_SIZE); +} + + +void MD4::Transform() +{ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + + word32 A, B, C, D; + + A = digest_[0]; + B = digest_[1]; + C = digest_[2]; + D = digest_[3]; + +#define function(a,b,c,d,k,s) a=rotlFixed(a+F(b,c,d)+buffer_[k],s); + function(A,B,C,D, 0, 3); + function(D,A,B,C, 1, 7); + function(C,D,A,B, 2,11); + function(B,C,D,A, 3,19); + function(A,B,C,D, 4, 3); + function(D,A,B,C, 5, 7); + function(C,D,A,B, 6,11); + function(B,C,D,A, 7,19); + function(A,B,C,D, 8, 3); + function(D,A,B,C, 9, 7); + function(C,D,A,B,10,11); + function(B,C,D,A,11,19); + function(A,B,C,D,12, 3); + function(D,A,B,C,13, 7); + function(C,D,A,B,14,11); + function(B,C,D,A,15,19); + +#undef function +#define function(a,b,c,d,k,s) a=rotlFixed(a+G(b,c,d)+buffer_[k]+0x5a827999,s); + function(A,B,C,D, 0, 3); + function(D,A,B,C, 4, 5); + function(C,D,A,B, 8, 9); + function(B,C,D,A,12,13); + function(A,B,C,D, 1, 3); + function(D,A,B,C, 5, 5); + function(C,D,A,B, 9, 9); + function(B,C,D,A,13,13); + function(A,B,C,D, 2, 3); + function(D,A,B,C, 6, 5); + function(C,D,A,B,10, 9); + function(B,C,D,A,14,13); + function(A,B,C,D, 3, 3); + function(D,A,B,C, 7, 5); + function(C,D,A,B,11, 9); + function(B,C,D,A,15,13); + +#undef function +#define function(a,b,c,d,k,s) a=rotlFixed(a+H(b,c,d)+buffer_[k]+0x6ed9eba1,s); + function(A,B,C,D, 0, 3); + function(D,A,B,C, 8, 9); + function(C,D,A,B, 4,11); + function(B,C,D,A,12,15); + function(A,B,C,D, 2, 3); + function(D,A,B,C,10, 9); + function(C,D,A,B, 6,11); + function(B,C,D,A,14,15); + function(A,B,C,D, 1, 3); + function(D,A,B,C, 9, 9); + function(C,D,A,B, 5,11); + function(B,C,D,A,13,15); + function(A,B,C,D, 3, 3); + function(D,A,B,C,11, 9); + function(C,D,A,B, 7,11); + function(B,C,D,A,15,15); + + digest_[0] += A; + digest_[1] += B; + digest_[2] += C; + digest_[3] += D; +} + + +} // namespace + diff --git a/extra/yassl/taocrypt/src/template_instnt.cpp b/extra/yassl/taocrypt/src/template_instnt.cpp index 5efd2d32a10387ff2bed5abb1aa06d87ea40cfed..12bcd8238f293318ea5bbd7616f00cc588bcb6b0 100644 --- a/extra/yassl/taocrypt/src/template_instnt.cpp +++ b/extra/yassl/taocrypt/src/template_instnt.cpp @@ -30,11 +30,11 @@ #include "sha.hpp" #include "md5.hpp" #include "hmac.hpp" +#include "ripemd.hpp" #include "pwdbased.hpp" #include "algebra.hpp" #include "vector.hpp" #include "hash.hpp" -#include "ripemd.hpp" #ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION namespace TaoCrypt { diff --git a/extra/yassl/taocrypt/taocrypt.dsp b/extra/yassl/taocrypt/taocrypt.dsp index b741cef009616cb0844e94778b874440153fb143..19edf7b2f22dbf6288a1f257986d6d447f722ae7 100644 --- a/extra/yassl/taocrypt/taocrypt.dsp +++ b/extra/yassl/taocrypt/taocrypt.dsp @@ -146,6 +146,10 @@ SOURCE=.\src\md2.cpp # End Source File # Begin Source File +SOURCE=.\src\md4.cpp +# End Source File +# Begin Source File + SOURCE=.\src\md5.cpp # End Source File # Begin Source File @@ -246,6 +250,10 @@ SOURCE=.\include\md2.hpp # End Source File # Begin Source File +SOURCE=.\include\md4.hpp +# End Source File +# Begin Source File + SOURCE=.\include\md5.hpp # End Source File # Begin Source File diff --git a/extra/yassl/taocrypt/test/test.cpp b/extra/yassl/taocrypt/test/test.cpp index b8618b18d473571a9674a60cee92bd4fa95eeace..28ef73dfac8f20b5e658da4a9f982ee8c4289a3b 100644 --- a/extra/yassl/taocrypt/test/test.cpp +++ b/extra/yassl/taocrypt/test/test.cpp @@ -8,6 +8,7 @@ #include "sha.hpp" #include "md5.hpp" #include "md2.hpp" +#include "md4.hpp" #include "ripemd.hpp" #include "hmac.hpp" #include "arc4.hpp" @@ -30,6 +31,7 @@ using TaoCrypt::word32; using TaoCrypt::SHA; using TaoCrypt::MD5; using TaoCrypt::MD2; +using TaoCrypt::MD4; using TaoCrypt::RIPEMD160; using TaoCrypt::HMAC; using TaoCrypt::ARC4; @@ -89,6 +91,7 @@ void file_test(int, char**); int sha_test(); int md5_test(); int md2_test(); +int md4_test(); int ripemd_test(); int hmac_test(); int arc4_test(); @@ -165,6 +168,11 @@ void taocrypt_test(void* args) else printf( "MD2 test passed!\n"); + if ( (ret = md4_test()) ) + err_sys("MD4 test failed!\n", ret); + else + printf( "MD4 test passed!\n"); + if ( (ret = ripemd_test()) ) err_sys("RIPEMD test failed!\n", ret); else @@ -348,6 +356,51 @@ int md5_test() } +int md4_test() +{ + MD4 md4; + byte hash[MD4::DIGEST_SIZE]; + + testVector test_md4[] = + { + testVector("", + "\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31\xb7\x3c\x59\xd7\xe0\xc0\x89" + "\xc0"), + testVector("a", + "\xbd\xe5\x2c\xb3\x1d\xe3\x3e\x46\x24\x5e\x05\xfb\xdb\xd6\xfb" + "\x24"), + testVector("abc", + "\xa4\x48\x01\x7a\xaf\x21\xd8\x52\x5f\xc1\x0a\xe8\x7a\xa6\x72" + "\x9d"), + testVector("message digest", + "\xd9\x13\x0a\x81\x64\x54\x9f\xe8\x18\x87\x48\x06\xe1\xc7\x01" + "\x4b"), + testVector("abcdefghijklmnopqrstuvwxyz", + "\xd7\x9e\x1c\x30\x8a\xa5\xbb\xcd\xee\xa8\xed\x63\xdf\x41\x2d" + "\xa9"), + testVector("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345" + "6789", + "\x04\x3f\x85\x82\xf2\x41\xdb\x35\x1c\xe6\x27\xe1\x53\xe7\xf0" + "\xe4"), + testVector("1234567890123456789012345678901234567890123456789012345678" + "9012345678901234567890", + "\xe3\x3b\x4d\xdc\x9c\x38\xf2\x19\x9c\x3e\x7b\x16\x4f\xcc\x05" + "\x36") + }; + + int times( sizeof(test_md4) / sizeof(testVector) ); + for (int i = 0; i < times; ++i) { + md4.Update(test_md4[i].input_, test_md4[i].inLen_); + md4.Final(hash); + + if (memcmp(hash, test_md4[i].output_, MD4::DIGEST_SIZE) != 0) + return -5 - i; + } + + return 0; +} + + int md2_test() { MD2 md5; diff --git a/extra/yassl/testsuite/test.hpp b/extra/yassl/testsuite/test.hpp index 259975fba0b3f87472c512fac5710a1caf0bdaac..c80e3ad23da536d37f0afda76d658f6cb98469e4 100644 --- a/extra/yassl/testsuite/test.hpp +++ b/extra/yassl/testsuite/test.hpp @@ -33,10 +33,16 @@ // HPUX doesn't use socklent_t for third parameter to accept -#if !defined(__hpux__) +#if !defined(__hpux) typedef socklen_t* ACCEPT_THIRD_T; #else typedef int* ACCEPT_THIRD_T; + +// HPUX does not define _POSIX_THREADS as it's not _fully_ implemented +#ifndef _POSIX_THREADS +#define _POSIX_THREADS +#endif + #endif diff --git a/include/m_string.h b/include/m_string.h index ce34c303c22099c44df04712c27c223c22960c7c..9f7ec220f2c5b211ab9e82746272bb4202ed6a13 100644 --- a/include/m_string.h +++ b/include/m_string.h @@ -240,4 +240,22 @@ extern int my_snprintf(char* to, size_t n, const char* fmt, ...); #if defined(__cplusplus) } #endif + +/* + LEX_STRING -- a pair of a C-string and its length. + + NOTE: this exactly form of declaration is required for some C-compilers + (for one, Sun C 5.7 2005/01/07). Unfortunatelt with such declaration + LEX_STRING can not be forward declared. +*/ + +typedef struct +{ + char *str; + uint length; +} LEX_STRING; + +#define STRING_WITH_LEN(X) (X), ((uint) (sizeof(X) - 1)) +#define C_STRING_WITH_SIZE(X) ((char *) (X)), ((uint) (sizeof(X) - 1)) + #endif diff --git a/include/my_sys.h b/include/my_sys.h index 3a4895f5dc5b0decec32c42996b53a9c4faba2a2..5024505a8219ec5a4c8c3b242a8da1b98ad52145 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -77,6 +77,10 @@ extern int NEAR my_errno; /* Last error in mysys */ #define MY_GIVE_INFO 2 /* Give time info about process*/ #define MY_DONT_FREE_DBUG 4 /* Do not call DBUG_END() in my_end() */ +#define MY_REMOVE_NONE 0 /* Params for modify_defaults_file */ +#define MY_REMOVE_OPTION 1 +#define MY_REMOVE_SECTION 2 + #define ME_HIGHBYTE 8 /* Shift for colours */ #define ME_NOCUR 1 /* Don't use curses message */ #define ME_OLDWIN 2 /* Use old window */ diff --git a/include/mysql_com.h b/include/mysql_com.h index 2af0fb869060c769fbc6c3029f629ac14ea13317..bff5fcc47d24a5a186d264588911a8f1b2a1bea7 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -423,17 +423,11 @@ char *octet2hex(char *to, const char *str, unsigned int len); /* end of password.c */ -char *get_tty_password(char *opt_message); +char *get_tty_password(const char *opt_message); const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); /* Some other useful functions */ -my_bool my_init(void); -extern int modify_defaults_file(const char *file_location, const char *option, - const char *option_value, - const char *section_name, int remove_option); -int load_defaults(const char *conf_file, const char **groups, - int *argc, char ***argv); my_bool my_thread_init(void); void my_thread_end(void); diff --git a/libmysql/get_password.c b/libmysql/get_password.c index a48cb6d7a6ec3e4455ad2e959fc63eb0878447b3..4c251677a6615aa78e13a7e763c998324e7cf34c 100644 --- a/libmysql/get_password.c +++ b/libmysql/get_password.c @@ -75,7 +75,7 @@ #define _cputs(A) putstring(A) #endif -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { char to[80]; char *pos=to,*end=to+sizeof(to)-1; @@ -159,7 +159,7 @@ static void get_password(char *to,uint length,int fd,bool echo) #endif /* ! HAVE_GETPASS */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { #ifdef HAVE_GETPASS char *passbuff; diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index 8d1591282db13f7d637cdd87d838fb4215a5565b..a3af2d43bd5283b9a4ff6a1f5484ff10abdf8bd0 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -68,7 +68,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ - event_executor.cc event.cc event_timed.cc \ + event_scheduler.cc event.cc event_timed.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ sql_tablespace.cc \ rpl_injector.cc my_user.c partition_info.cc diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 0591c24e914c6bf6a0789ad5ab5b7d471c767ab8..8923c032dffa0c816bbc44c6aa376b6e56e9a74f 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -1220,9 +1220,12 @@ sub environment_setup () { $ENV{'NDBCLUSTER_PORT_SLAVE'}=$opt_ndbcluster_port_slave; $ENV{'NDB_STATUS_OK'}= "YES"; + $ENV{'IM_EXE'}= $exe_im; $ENV{'IM_PATH_PID'}= $instance_manager->{path_pid}; $ENV{'IM_PATH_ANGEL_PID'}= $instance_manager->{path_angel_pid}; $ENV{'IM_PORT'}= $instance_manager->{port}; + $ENV{'IM_DEFAULTS_PATH'}= $instance_manager->{defaults_file}; + $ENV{'IM_PASSWORD_PATH'}= $instance_manager->{password_file}; $ENV{'IM_MYSQLD1_SOCK'}= $instance_manager->{instances}->[0]->{path_sock}; $ENV{'IM_MYSQLD1_PORT'}= $instance_manager->{instances}->[0]->{port}; diff --git a/mysql-test/r/events.result b/mysql-test/r/events.result index 01d206be7cb570de5a2542ae485b1c656135ff68..d02a2af3c9fa790dcf4db47b351648a195ebe990 100644 --- a/mysql-test/r/events.result +++ b/mysql-test/r/events.result @@ -17,13 +17,13 @@ db_x SHOW TABLES FROM db_x; Tables_in_db_x x_table -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; DROP EVENT e_x1; DROP EVENT e_x2; DROP DATABASE db_x; DROP USER pauline@localhost; USE events_test; -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; drop event if exists event1; Warnings: Note 1305 Event event1 does not exist @@ -100,7 +100,7 @@ a 800219 drop event non_qualif_ev; drop table non_qualif; -set global event_scheduler = 0; +set global event_scheduler = 2; create table t_event3 (a int, b float); drop event if exists event3; Warnings: @@ -324,18 +324,18 @@ events_test one_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test three_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test two_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED "This should show us only 3 events:"; -SHOW FULL EVENTS; +SHOW EVENTS; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status events_test one_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test three_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test two_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED "This should show us only 2 events:"; -SHOW FULL EVENTS LIKE 't%event'; +SHOW EVENTS LIKE 't%event'; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status events_test three_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test two_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED "This should show us no events:"; -SHOW FULL EVENTS FROM test LIKE '%'; +SHOW EVENTS FROM test LIKE '%'; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status DROP DATABASE events_test2; "should see 1 event:"; @@ -343,11 +343,8 @@ SHOW EVENTS; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status events_test one_event root@localhost RECURRING NULL 10 SECOND # # ENABLED "we should see 4 events now:"; -SHOW FULL EVENTS; +SHOW EVENTS; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status -events_test one_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED -events_test three_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED -events_test two_event ev_test@localhost RECURRING NULL 20 SECOND # # ENABLED events_test one_event root@localhost RECURRING NULL 10 SECOND # # ENABLED SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT from information_schema.events; EVENT_CATALOG EVENT_SCHEMA EVENT_NAME DEFINER EVENT_BODY EVENT_TYPE EXECUTE_AT INTERVAL_VALUE INTERVAL_FIELD STATUS ON_COMPLETION EVENT_COMMENT @@ -373,12 +370,12 @@ ERROR HY000: Incorrect AT value: 'definitely not a datetime' set names utf8; create event задачка on schedule every 123 minute starts now() ends now() + interval 1 month do select 1; drop event задачка; -set event_scheduler=0; +set event_scheduler=2; ERROR HY000: Variable 'event_scheduler' is a GLOBAL variable and should be set with SET GLOBAL -set global event_scheduler=2; -ERROR 42000: Variable 'event_scheduler' can't be set to the value of '2' +set global event_scheduler=3; +ERROR 42000: Variable 'event_scheduler' can't be set to the value of '3' "DISABLE the scheduler. Testing that it does not work when the variable is 0" -set global event_scheduler=0; +set global event_scheduler=2; select definer, name, db from mysql.event; definer name db select get_lock("test_lock1", 20); @@ -389,9 +386,10 @@ create event закачка on schedule every 10 hour do select get_lock("test_l select definer, name, db from mysql.event; definer name db root@localhost закачка events_test -"Should be 0 processes" +"Should be only 1 process" select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info +event_scheduler localhost NULL Connect Suspended NULL select release_lock("test_lock1"); release_lock("test_lock1") 1 @@ -409,11 +407,12 @@ get_lock("test_lock2", 20) create event закачка on schedule every 10 hour do select get_lock("test_lock2", 20); "Let some time pass to the event starts" "Should have only 2 processes: the scheduler and the locked event" -select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; +select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info event_scheduler localhost NULL Connect Sleeping NULL root localhost events_test Connect User lock select get_lock("test_lock2", 20) "Release the mutex, the event worker should finish." +"Release the mutex, the event worker should finish." select release_lock("test_lock2"); release_lock("test_lock2") 1 @@ -423,21 +422,17 @@ select get_lock("test_lock2_1", 20); get_lock("test_lock2_1", 20) 1 create event закачка21 on schedule every 10 hour do select get_lock("test_lock2_1", 20); -"Should see 1 process, locked on get_lock(" -"Shutting down the scheduler, it should wait for the running event" -set global event_scheduler=0; -"Should have only 2 processes: the scheduler and the locked event" -select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; +"Should have only 3 processes: the scheduler, our conn and the locked event" +select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info event_scheduler localhost NULL Connect Sleeping NULL root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) -"Release the lock so the child process should finish. Hence the scheduler also" -select release_lock("test_lock2_1"); -release_lock("test_lock2_1") -1 -"Should see 0 processes now:" -select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; +set global event_scheduler=2; +"Should have only our process now:" +select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info +event_scheduler localhost NULL Connect Suspended NULL +root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) drop event закачка21; create table t_16 (s1 int); create trigger t_16_bi before insert on t_16 for each row create event e_16 on schedule every 1 second do set @a=5; @@ -457,6 +452,9 @@ select 2; select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; event_schema event_name definer event_body events_test white_space root@localhost select 2 +select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; +event_schema event_name definer event_body +events_test white_space root@localhost select 2 drop event white_space; create event white_space on schedule every 10 hour disable do select 3; select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index ef1ccfadecb35b36fed68a8a116710374d86fc53..bc89c692f9ab682e9e9503c0807ee34d05ec6aa8 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -35,7 +35,7 @@ create event e_55 on schedule every 10 hour starts 99990101000000 do drop table ERROR HY000: Incorrect STARTS value: '99990101000000' create event e_55 on schedule every 10 minute ends 99990101000000 do drop table t; ERROR HY000: ENDS is either invalid or before STARTS -set global event_scheduler=0; +set global event_scheduler=2; "Wait a bit to settle down" delete from mysql.event; set global event_scheduler= 1; @@ -57,7 +57,7 @@ root localhost events_test Connect User lock select get_lock('test_bug16407', 60 select release_lock('test_bug16407'); release_lock('test_bug16407') 1 -set global event_scheduler= 0; +set global event_scheduler= 2; select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; event_schema event_name sql_mode events_test e_16407 REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI @@ -115,7 +115,7 @@ release_lock('ee_16407_2') select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info event_scheduler localhost NULL Connect Sleeping NULL -set global event_scheduler= 0; +set global event_scheduler= 2; select * from events_smode_test order by ev_name, a; ev_name a ee_16407_3 1980-02-19 @@ -175,7 +175,7 @@ drop event ee_16407_5; drop event ee_16407_6; drop procedure ee_16407_5_pendant; drop procedure ee_16407_6_pendant; -set global event_scheduler= 0; +set global event_scheduler= 2; drop table events_smode_test; set sql_mode=@old_sql_mode; drop database events_test; diff --git a/mysql-test/r/events_logs_tests.result b/mysql-test/r/events_logs_tests.result index ab1666fefb98cb1be939768929c3d93d36e26766..911bc8b2d600a7d829f459b856a065f929fef63a 100644 --- a/mysql-test/r/events_logs_tests.result +++ b/mysql-test/r/events_logs_tests.result @@ -8,7 +8,7 @@ BEGIN SELECT user_host, argument FROM mysql.general_log WHERE argument LIKE '%alabala%'; END| "Check General Query Log" -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; create event log_general on schedule every 1 minute do SELect 'alabala', sleep(3) from dual; TRUNCATE mysql.general_log; "1 row, the current statement!" @@ -22,7 +22,7 @@ user_host argument root[root] @ localhost [localhost] SELect 'alabala', sleep(3) from dual DROP PROCEDURE select_general_log; DROP EVENT log_general; -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; "Check slow query log" "Save the values" SET @old_global_long_query_time:=(select get_value()); @@ -36,14 +36,14 @@ SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; user_host query_time db sql_text "Set new values" SET GLOBAL long_query_time=4; -SET SESSION long_query_time=2; +SET SESSION long_query_time=1; "Check that logging is working" -SELECT SLEEP(3); -SLEEP(3) +SELECT SLEEP(2); +SLEEP(2) 0 SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; user_host query_time db sql_text -root[root] @ localhost [] SLEEPVAL events_test SELECT SLEEP(3) +root[root] @ localhost [] SLEEPVAL events_test SELECT SLEEP(2) TRUNCATE mysql.slow_log; CREATE TABLE slow_event_test (slo_val tinyint, val tinyint); "This won't go to the slow log" @@ -54,7 +54,7 @@ SET GLOBAL event_scheduler=1; "Sleep some more time than the actual event run will take" SHOW VARIABLES LIKE 'event_scheduler'; Variable_name Value -event_scheduler ON +event_scheduler 1 "Check our table. Should see 1 row" SELECT * FROM slow_event_test; slo_val val @@ -64,18 +64,19 @@ SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; user_host query_time db sql_text "This should go to the slow log" SET SESSION long_query_time=10; +SET GLOBAL long_query_time=1; DROP EVENT long_event; -CREATE EVENT long_event2 ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(5); +CREATE EVENT long_event2 ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(2); "Sleep some more time than the actual event run will take" "Check our table. Should see 2 rows" SELECT * FROM slow_event_test; slo_val val 4 0 -4 0 -"Check slow log. Should see 1 row because 5 is over the threshold of 4 for GLOBAL, though under SESSION which is 10" +1 0 +"Check slow log. Should see 1 row because 4 is over the threshold of 3 for GLOBAL, though under SESSION which is 10" SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; user_host query_time db sql_text -root[root] @ localhost [localhost] SLEEPVAL events_test INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(5) +root[root] @ localhost [localhost] SLEEPVAL events_test INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(2) DROP EVENT long_event2; SET GLOBAL long_query_time =@old_global_long_query_time; SET SESSION long_query_time =@old_session_long_query_time; diff --git a/mysql-test/r/events_microsec.result b/mysql-test/r/events_microsec.result index ed15b066b9362f794111228d7eaafd97e7403afd..b96bd55151150ccdce58c24629ce291ec7cfbe45 100644 --- a/mysql-test/r/events_microsec.result +++ b/mysql-test/r/events_microsec.result @@ -10,50 +10,4 @@ CREATE EVENT micro_test ON SCHEDULE EVERY 100 MINUTE_MICROSECOND DO SELECT 1; ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' CREATE EVENT micro_test ON SCHEDULE EVERY 100 SECOND_MICROSECOND DO SELECT 1; ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -"Now create normal event and change it on SQL level" -CREATE EVENT micro_test2 ON SCHEDULE EVERY 1 MONTH DO SELECT 1; -UPDATE mysql.event SET interval_field='MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; -SHOW CREATE EVENT micro_test2; -ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -SET GLOBAL event_scheduler=0; -"Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -Variable_name Value -event_scheduler OFF -UPDATE mysql.event SET interval_field='DAY_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; -SHOW CREATE EVENT micro_test2; -ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -SET GLOBAL event_scheduler=0; -"Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -Variable_name Value -event_scheduler OFF -UPDATE mysql.event SET interval_field='SECOND_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; -SHOW CREATE EVENT micro_test2; -ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -SET GLOBAL event_scheduler=0; -"Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -Variable_name Value -event_scheduler OFF -UPDATE mysql.event SET interval_field='HOUR_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; -SHOW CREATE EVENT micro_test2; -ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -SET GLOBAL event_scheduler=0; -"Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -Variable_name Value -event_scheduler OFF -UPDATE mysql.event SET interval_field='MINUTE_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; -SHOW CREATE EVENT micro_test2; -ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' -SET GLOBAL event_scheduler=0; -"Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -Variable_name Value -event_scheduler OFF -SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER='event_scheduler'; -COUNT(*) -0 -DROP EVENT micro_test2; drop database events_test; diff --git a/mysql-test/r/events_scheduling.result b/mysql-test/r/events_scheduling.result index 8b1f29d320fe522d735da8ee315c2e5be60282be..aec2053f0e721aedf1b76871568492dfb9a35508 100644 --- a/mysql-test/r/events_scheduling.result +++ b/mysql-test/r/events_scheduling.result @@ -14,7 +14,7 @@ ENDS NOW() + INTERVAL 6 SECOND ON COMPLETION PRESERVE DO INSERT INTO table_2 VALUES(1); CREATE EVENT only_one_time ON SCHEDULE EVERY 2 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_3 VALUES(1); -CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_4 VALUES(1); +CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND ON COMPLETION PRESERVE DO INSERT INTO table_4 VALUES(1); SELECT IF(SUM(a) >= 4, 'OK', 'ERROR') FROM table_1; IF(SUM(a) >= 4, 'OK', 'ERROR') OK @@ -38,9 +38,12 @@ DROP EVENT start_n_end; "Already dropped because ended. Therefore an error." DROP EVENT only_one_time; ERROR HY000: Unknown event 'only_one_time' -"Already dropped because ended. Therefore an error." +"Should be preserved" +SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS; +EVENT_NAME STATUS +E19170 ENABLED +two_time DISABLED DROP EVENT two_time; -ERROR HY000: Unknown event 'two_time' DROP TABLE table_1; DROP TABLE table_2; DROP TABLE table_3; diff --git a/mysql-test/r/events_stress.result b/mysql-test/r/events_stress.result index 9f95cfad75d8d21849469358f116e930b1c95f3b..ead618e8136785847907263f01241fd32dc5e7d3 100644 --- a/mysql-test/r/events_stress.result +++ b/mysql-test/r/events_stress.result @@ -1,46 +1,61 @@ CREATE DATABASE IF NOT EXISTS events_test; -CREATE DATABASE events_test2; -USE events_test2; +CREATE DATABASE events_conn1_test2; +CREATE TABLE events_test.fill_it(test_name varchar(20), occur datetime); +CREATE USER event_user2@localhost; +CREATE DATABASE events_conn2_db; +GRANT ALL ON *.* TO event_user2@localhost; +CREATE USER event_user3@localhost; +CREATE DATABASE events_conn3_db; +GRANT ALL ON *.* TO event_user3@localhost; +"In the second connection we create some events which won't be dropped till the end" +"In the second connection we create some events which won't be dropped till the end" +USE events_conn1_test2; CREATE EVENT ev_drop1 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; CREATE EVENT ev_drop2 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; CREATE EVENT ev_drop3 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; USE events_test; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS; +COUNT(*) +103 +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; COUNT(*) 3 -DROP DATABASE events_test2; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +DROP DATABASE events_conn1_test2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; COUNT(*) 0 "Now testing stability - dropping db -> events while they are running" -CREATE DATABASE events_test2; -USE events_test2; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +CREATE DATABASE events_conn1_test2; +USE events_conn1_test2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; COUNT(*) -1000 +50 SET GLOBAL event_scheduler=1; -DROP DATABASE events_test2; -SET GLOBAL event_scheduler=0; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +DROP DATABASE events_conn1_test2; +SET GLOBAL event_scheduler=2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; COUNT(*) 0 -CREATE DATABASE events_test3; -USE events_test3; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test3'; +CREATE DATABASE events_conn1_test3; +USE events_conn1_test3; +SET GLOBAL event_scheduler=1; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test3'; COUNT(*) -950 -CREATE DATABASE events_test4; -USE events_test4; -CREATE DATABASE events_test2; -USE events_test2; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +50 +CREATE DATABASE events_conn1_test4; +USE events_conn1_test4; +CREATE DATABASE events_conn1_test2; +USE events_conn1_test2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; COUNT(*) -1050 -DROP DATABASE events_test2; -SET GLOBAL event_scheduler=0; -DROP DATABASE events_test3; -SET GLOBAL event_scheduler=1; -DROP DATABASE events_test4; +50 +DROP DATABASE events_conn2_db; +DROP DATABASE events_conn3_db; +DROP DATABASE events_conn1_test2; +DROP DATABASE events_conn1_test3; +SET GLOBAL event_scheduler=2; +DROP DATABASE events_conn1_test4; SET GLOBAL event_scheduler=1; USE events_test; +DROP TABLE fill_it; DROP DATABASE events_test; diff --git a/mysql-test/r/im_cmd_line.result b/mysql-test/r/im_cmd_line.result new file mode 100644 index 0000000000000000000000000000000000000000..5b289549a3fb4fe8ae6da0a4566f37add88b8997 --- /dev/null +++ b/mysql-test/r/im_cmd_line.result @@ -0,0 +1,40 @@ +--> Listing users... +im_admin + +==> Adding user 'testuser'... + +--> IM password file: +testuser:*0D3CED9BEC10A777AEC23CCC353A8C08A633045E +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +--> EOF + +--> Printing out line for 'testuser'... +testuser:*0D3CED9BEC10A777AEC23CCC353A8C08A633045E + +--> Listing users... +im_admin +testuser + +==> Changing the password of 'testuser'... + +--> IM password file: +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +testuser:*39C549BDECFBA8AFC3CE6B948C9359A0ECE08DE2 +--> EOF + +--> Printing out line for 'testuser'... +testuser:*39C549BDECFBA8AFC3CE6B948C9359A0ECE08DE2 + +--> Listing users... +testuser +im_admin + +==> Dropping user 'testuser'... + +--> IM password file: +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +--> EOF + +--> Listing users... +im_admin + diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index d0a76b450feca44c532960056f625ac1992a8489..29c9ea2047d9a8cf00e0298bfa4cf8ba42d8e56e 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -1,5 +1,5 @@ SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline Killing the process... diff --git a/mysql-test/r/im_instance_conf.result b/mysql-test/r/im_instance_conf.result new file mode 100644 index 0000000000000000000000000000000000000000..efda9439f383c65e17f32423821603e0866f3437 --- /dev/null +++ b/mysql-test/r/im_instance_conf.result @@ -0,0 +1,196 @@ +-------------------------------------------------------------------- +server_id = 1 +server_id = 2 +-------------------------------------------------------------------- +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline + +---> connection: mysql1_con +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 + +---> connection: default +CREATE INSTANCE mysqld3; +SHOW INSTANCES; +instance_name state +mysqld3 offline +mysqld2 offline +mysqld1 online +-------------------------------------------------------------------- +server_id = 1 +server_id = 2 +-------------------------------------------------------------------- +CREATE INSTANCE mysqld1; +ERROR HY000: Instance already exists +CREATE INSTANCE mysqld2; +ERROR HY000: Instance already exists +CREATE INSTANCE mysqld3; +ERROR HY000: Instance already exists +-------------------------------------------------------------------- +nonguarded +-------------------------------------------------------------------- +CREATE INSTANCE mysqld4 nonguarded; +SHOW INSTANCES; +instance_name state +mysqld3 offline +mysqld4 offline +mysqld1 online +mysqld2 offline +-------------------------------------------------------------------- +nonguarded +nonguarded +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld5 test-A = 000, test-B = test; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld4 offline +mysqld5 offline +mysqld2 offline +mysqld3 offline +-------------------------------------------------------------------- +test-A=000 +-------------------------------------------------------------------- +test-B=test +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld6 test-C1 = 10 , test-C2 = 02 ; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +-------------------------------------------------------------------- +test-C1=10 +-------------------------------------------------------------------- +test-C2=02 +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld7 test-D = test-D-value ; +ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +CREATE INSTANCE mysqld8 test-E 0 ; +ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +CREATE INSTANCE mysqld8 test-F = ; +ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld9 test-1=" hello world ", test-2=' '; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +CREATE INSTANCE mysqld9a test-3='\b\babc\sdef'; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld9a offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +CREATE INSTANCE mysqld9b test-4='abc\tdef', test-5='abc\ndef'; +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld9a offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +CREATE INSTANCE mysqld9c test-6="abc\rdef", test-7="abc\\def"; +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld6 offline +mysqld5 offline +mysqld9c offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +mysqld9a offline +CREATE INSTANCE mysqld10 test-bad=' \ '; +ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld6 offline +mysqld5 offline +mysqld9c offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +mysqld9a offline +-------------------------------------------------------------------- +test-1= hello world +-------------------------------------------------------------------- +test-2= +-------------------------------------------------------------------- +test-3=abc def +-------------------------------------------------------------------- +test-4=abc def +-------------------------------------------------------------------- +test-5=abc +-------------------------------------------------------------------- +test-6=abc def +-------------------------------------------------------------------- +test-7=abc\def +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE qqq1; +ERROR HY000: Malformed instance name. diff --git a/mysql-test/r/im_life_cycle.result b/mysql-test/r/im_life_cycle.result index e208ccb9f0052194acc3bbdcf8bbf0ee671e5fca..876fbb38eee0a69d62c73664110dbb057994abbd 100644 --- a/mysql-test/r/im_life_cycle.result +++ b/mysql-test/r/im_life_cycle.result @@ -1,69 +1,93 @@ + +-------------------------------------------------------------------- +-- 1.1.1. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline -SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION -SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 offline VERSION_NUMBER VERSION + +-------------------------------------------------------------------- +-- 1.1.2. +-------------------------------------------------------------------- START INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 online -SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION -SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 online VERSION_NUMBER VERSION SHOW VARIABLES LIKE 'port'; Variable_name Value -port IM_MYSQLD1_PORT +port IM_MYSQLD2_PORT + +-------------------------------------------------------------------- +-- 1.1.3. +-------------------------------------------------------------------- STOP INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION +instance_name state version_number version mysqld_compatible +mysqld1 online VERSION_NUMBER VERSION no SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 offline VERSION_NUMBER VERSION +instance_name state version_number version mysqld_compatible +mysqld2 offline VERSION_NUMBER VERSION no + +-------------------------------------------------------------------- +-- 1.1.4. +-------------------------------------------------------------------- START INSTANCE mysqld3; ERROR HY000: Bad instance name. Check that the instance with such a name exists START INSTANCE mysqld1; ERROR HY000: The instance is already started + +-------------------------------------------------------------------- +-- 1.1.5. +-------------------------------------------------------------------- STOP INSTANCE mysqld3; ERROR HY000: Bad instance name. Check that the instance with such a name exists + +-------------------------------------------------------------------- +-- 1.1.6. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline Killing the process... Sleeping... Success: the process was restarted. + +-------------------------------------------------------------------- +-- 1.1.7. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline START INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 online Killing the process... Sleeping... Success: the process was killed. SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline + +-------------------------------------------------------------------- +-- 1.1.8. +-------------------------------------------------------------------- SHOW INSTANCE STATUS; ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use + +-------------------------------------------------------------------- +-- BUG#12813 +-------------------------------------------------------------------- START INSTANCE mysqld1,mysqld2,mysqld3; ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use STOP INSTANCE mysqld1,mysqld2,mysqld3; diff --git a/mysql-test/r/im_options.result b/mysql-test/r/im_options.result new file mode 100644 index 0000000000000000000000000000000000000000..8039333b7d9580cc71314767b667e6c0f29c1e48 --- /dev/null +++ b/mysql-test/r/im_options.result @@ -0,0 +1,150 @@ +-------------------------------------------------------------------- +server_id = 1 +server_id = 2 +-------------------------------------------------------------------- +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 +SHOW INSTANCES; +instance_name state +mysqld1 starting +mysqld2 offline +UNSET mysqld1.server_id; +ERROR HY000: The instance is active. Stop the instance first +SET mysqld1.server_id = 11; +ERROR HY000: The instance is active. Stop the instance first +CREATE INSTANCE mysqld3 datadir = '/'; +START INSTANCE mysqld3; +UNSET mysqld3.server_id; +ERROR HY000: The instance is active. Stop the instance first +SET mysqld3.server_id = 11; +ERROR HY000: The instance is active. Stop the instance first +STOP INSTANCE mysqld3; +SHOW INSTANCE STATUS mysqld3; +instance_name state version_number version mysqld_compatible +mysqld3 offline VERSION_NUMBER VERSION no +UNSET mysqld2.server_id; +UNSET mysqld2.server_id; +SHOW INSTANCE OPTIONS mysqld2; +option_name value +instance_name option_value +socket option_value +pid-file option_value +port option_value +datadir option_value +log option_value +log-error option_value +log-slow-queries option_value +language option_value +character-sets-dir option_value +basedir option_value +skip-stack-trace option_value +skip-innodb option_value +skip-bdb option_value +skip-ndbcluster option_value +nonguarded option_value +log-output option_value +SET mysqld2.server_id = 2; +SET mysqld2.server_id = 2; +SHOW INSTANCE OPTIONS mysqld2; +option_name value +instance_name option_value +socket option_value +pid-file option_value +port option_value +datadir option_value +log option_value +log-error option_value +log-slow-queries option_value +language option_value +character-sets-dir option_value +basedir option_value +skip-stack-trace option_value +skip-innodb option_value +skip-bdb option_value +skip-ndbcluster option_value +nonguarded option_value +log-output option_value +server_id option_value +UNSET mysqld2.server_id = 11; +ERROR 42000: You have an error in your command syntax. Check the manual that corresponds to your MySQL Instance Manager version for the right syntax to use +SET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc = 0010, mysqld3.ddd = 0020; +-------------------------------------------------------------------- +aaa +-------------------------------------------------------------------- +bbb +-------------------------------------------------------------------- +ccc=0010 +-------------------------------------------------------------------- +ddd=0020 +-------------------------------------------------------------------- +UNSET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc, mysqld3.ddd; +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +SET mysqld2.aaa, mysqld3.bbb, mysqld.ccc = 0010; +ERROR HY000: Bad instance name. Check that the instance with such a name exists +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +SET mysqld2.aaa, mysqld3.bbb, mysqld1.ccc = 0010; +ERROR HY000: The instance is active. Stop the instance first +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +UNSET mysqld2.server_id, mysqld3.server_id, mysqld.ccc; +ERROR HY000: Bad instance name. Check that the instance with such a name exists +-------------------------------------------------------------------- +server_id = 1 +server_id=2 +-------------------------------------------------------------------- +UNSET mysqld2.server_id, mysqld3.server_id, mysqld1.ccc; +ERROR HY000: The instance is active. Stop the instance first +-------------------------------------------------------------------- +server_id = 1 +server_id=2 +-------------------------------------------------------------------- +DROP INSTANCE mysqld3; +SET mysqld2.server_id=222; +SET mysqld2.server_id = 222; +SET mysqld2.server_id = 222 ; +SET mysqld2 . server_id = 222 ; +SET mysqld2 . server_id = 222 , mysqld2 . aaa , mysqld2 . bbb ; +-------------------------------------------------------------------- +server_id = 1 +server_id=222 +-------------------------------------------------------------------- +aaa +-------------------------------------------------------------------- +bbb +-------------------------------------------------------------------- +UNSET mysqld2 . aaa , mysqld2 . bbb ; +-------------------------------------------------------------------- +server_id = 1 +server_id=222 +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +server_id = 1 +server_id=222 +-------------------------------------------------------------------- +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +FLUSH INSTANCES; +ERROR HY000: At least one instance is active. Stop all instances first +STOP INSTANCE mysqld1; +SHOW INSTANCES; +instance_name state +mysqld1 offline +mysqld2 offline +FLUSH INSTANCES; diff --git a/mysql-test/r/im_options_set.result b/mysql-test/r/im_options_set.result deleted file mode 100644 index 5e6c740624ee73b1697fc7975e362d4b15a74d73..0000000000000000000000000000000000000000 --- a/mysql-test/r/im_options_set.result +++ /dev/null @@ -1,20 +0,0 @@ -server_id = 1 -server_id = 2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -SET mysqld1.server_id = 11; -server_id =11 -server_id = 2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -SET mysqld2.server_id = 12; -server_id =11 -server_id =12 -FLUSH INSTANCES; -server_id =11 -server_id =12 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 diff --git a/mysql-test/r/im_options_unset.result b/mysql-test/r/im_options_unset.result deleted file mode 100644 index bf54025edb7ef5a7dcf61e17c89d37d8d774e2c5..0000000000000000000000000000000000000000 --- a/mysql-test/r/im_options_unset.result +++ /dev/null @@ -1,15 +0,0 @@ -server_id = 1 -server_id = 2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -UNSET mysqld1.server_id; -server_id = 2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -UNSET mysqld2.server_id; -FLUSH INSTANCES; -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 diff --git a/mysql-test/r/im_utils.result b/mysql-test/r/im_utils.result index 504b2efe4afd8e16739d9312de0d67dec8e96120..ae8e03bf8ea306263d23b5fcc35abb454a094633 100644 --- a/mysql-test/r/im_utils.result +++ b/mysql-test/r/im_utils.result @@ -1,11 +1,10 @@ SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline SHOW INSTANCE OPTIONS mysqld1; option_name value instance_name VALUE -mysqld-path VALUE socket VALUE pid-file VALUE port VALUE @@ -25,8 +24,6 @@ log-output VALUE SHOW INSTANCE OPTIONS mysqld2; option_name value instance_name VALUE -mysqld-path VALUE -nonguarded VALUE socket VALUE pid-file VALUE port VALUE @@ -42,6 +39,7 @@ skip-stack-trace VALUE skip-innodb VALUE skip-bdb VALUE skip-ndbcluster VALUE +nonguarded VALUE log-output VALUE START INSTANCE mysqld2; STOP INSTANCE mysqld2; diff --git a/mysql-test/r/log_tables.result b/mysql-test/r/log_tables.result index 2164c18823fe240b434be126a1a5e3002309bb6f..0b43024cb0ff4b5fa9454148f0f1cd6130086272 100644 --- a/mysql-test/r/log_tables.result +++ b/mysql-test/r/log_tables.result @@ -2,14 +2,14 @@ use mysql; truncate table general_log; select * from general_log; event_time user_host thread_id server_id command_type argument -TIMESTAMP root[root] @ localhost [] 1 1 Query select * from general_log +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query select * from general_log truncate table slow_log; select * from slow_log; start_time user_host query_time lock_time rows_sent rows_examined db last_insert_id insert_id server_id sql_text truncate table general_log; select * from general_log where argument like '%general_log%'; event_time user_host thread_id server_id command_type argument -TIMESTAMP root[root] @ localhost [] 1 1 Query select * from general_log where argument like '%general_log%' +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query select * from general_log where argument like '%general_log%' create table join_test (verbose_comment varchar (80), command_type varchar(64)); insert into join_test values ("User performed a usual SQL query", "Query"); insert into join_test values ("New DB connection was registered", "Connect"); @@ -59,10 +59,10 @@ create table bug16905 (s char(15) character set utf8 default 'пуÑто'); insert into bug16905 values ('новое'); select * from mysql.general_log; event_time user_host thread_id server_id command_type argument -TIMESTAMP root[root] @ localhost [] 2 1 Query set names utf8 -TIMESTAMP root[root] @ localhost [] 2 1 Query create table bug16905 (s char(15) character set utf8 default 'пуÑто') -TIMESTAMP root[root] @ localhost [] 2 1 Query insert into bug16905 values ('новое') -TIMESTAMP root[root] @ localhost [] 2 1 Query select * from mysql.general_log +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query set names utf8 +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query create table bug16905 (s char(15) character set utf8 default 'пуÑто') +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query insert into bug16905 values ('новое') +TIMESTAMP root[root] @ localhost [] THREAD_ID 1 Query select * from mysql.general_log drop table bug16905; truncate table mysql.slow_log; set session long_query_time=1; diff --git a/mysql-test/r/not_embedded_server.result b/mysql-test/r/not_embedded_server.result index e471b5a3afaf79923099be5edd0d186fae76c108..7cbe91b3753cf447f0bd6b703ad290faf9dc6e87 100644 --- a/mysql-test/r/not_embedded_server.result +++ b/mysql-test/r/not_embedded_server.result @@ -1,5 +1,6 @@ prepare stmt1 from ' show full processlist '; execute stmt1; Id User Host db Command Time State Info +number event_scheduler localhost NULL Connect time Suspended NULL number root localhost test Query time NULL show full processlist deallocate prepare stmt1; diff --git a/mysql-test/r/ps_1general.result b/mysql-test/r/ps_1general.result index d0b773dfe3479a04fffc5ecdd0ebf3a99887599d..1a1d64324119203a8e22f10135ad7ca26375b5f1 100644 --- a/mysql-test/r/ps_1general.result +++ b/mysql-test/r/ps_1general.result @@ -299,7 +299,7 @@ t9 MyISAM 10 Dynamic 2 216 432 # 2048 0 NULL # # # latin1_swedish_ci NULL prepare stmt4 from ' show status like ''Threads_running'' '; execute stmt4; Variable_name Value -Threads_running 1 +Threads_running 2 prepare stmt4 from ' show variables like ''sql_mode'' '; execute stmt4; Variable_name Value diff --git a/mysql-test/r/skip_name_resolve.result b/mysql-test/r/skip_name_resolve.result index 8ef52e752385d0605bbe64ef71f9205fed445c53..855876825addade7e64397b1d7babfc9a32927ca 100644 --- a/mysql-test/r/skip_name_resolve.result +++ b/mysql-test/r/skip_name_resolve.result @@ -10,5 +10,6 @@ user() # show processlist; Id User Host db Command Time State Info +<id> event_scheduler <host> NULL <command> <time> <state> <info> <id> root <host> test <command> <time> <state> <info> <id> root <host> test <command> <time> <state> <info> diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index c516d7a643f06f739f868e0d311363e2b3fc4b70..3cba437e0a63e4d0be883c87ed7c870de79a8912 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -34,6 +34,7 @@ lock tables t2 write; call bug9486(); show processlist; Id User Host db Command Time State Info +# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Sleep # NULL # root localhost test Query # Locked update t1, t2 set val= 1 where id1=id2 # root localhost test Query # NULL show processlist diff --git a/mysql-test/r/sp_notembedded.result b/mysql-test/r/sp_notembedded.result index c8cafe5ace148c53194980519771ca2c81b5f110..c5d60446e0aabaf79e7ad41c248e9f6fbdcf295c 100644 --- a/mysql-test/r/sp_notembedded.result +++ b/mysql-test/r/sp_notembedded.result @@ -18,9 +18,11 @@ show processlist; end| call bug4902_2()| Id User Host db Command Time State Info +# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Query # NULL show processlist call bug4902_2()| Id User Host db Command Time State Info +# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Query # NULL show processlist drop procedure bug4902_2| drop function if exists bug5278| diff --git a/mysql-test/r/status.result b/mysql-test/r/status.result index ca21b333a6a0f29cff1267bea4469bf8d6c09f45..e83ade78cf63603eac98a507ab9357070419b2e2 100644 --- a/mysql-test/r/status.result +++ b/mysql-test/r/status.result @@ -26,20 +26,20 @@ Last_query_cost 0.000000 FLUSH STATUS; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 1 +Max_used_connections 2 SET @save_thread_cache_size=@@thread_cache_size; SET GLOBAL thread_cache_size=3; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 3 +Max_used_connections 4 FLUSH STATUS; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 2 -SHOW STATUS LIKE 'max_used_connections'; -Variable_name Value Max_used_connections 3 SHOW STATUS LIKE 'max_used_connections'; Variable_name Value Max_used_connections 4 +SHOW STATUS LIKE 'max_used_connections'; +Variable_name Value +Max_used_connections 5 SET GLOBAL thread_cache_size=@save_thread_cache_size; diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index 562006c76875c95ecc1e7728cca186c471b06826..d6083ab8bfe51355ba3c597b79396f5567ff9c00 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -9,11 +9,10 @@ # Do not use any TAB characters for whitespace. # ############################################################################## -events_bugs : BUG#17619 2006-02-21 andrey Race conditions -events_stress : BUG#17619 2006-02-21 andrey Race conditions -events : BUG#17619 2006-02-21 andrey Race conditions -events_scheduling : BUG#19170 2006-04-26 andrey Test case of 19170 fails on some platforms. Has to be checked. -events_logs_tests : BUG#17619 2006-05-16 andrey Test case problems +#events_bugs : BUG#17619 2006-02-21 andrey Race conditions +#events_stress : BUG#17619 2006-02-21 andrey Race conditions +#events : BUG#17619 2006-02-21 andrey Race conditions +#events_scheduling : BUG#19170 2006-04-26 andrey Test case of 19170 fails on some platforms. Has to be checked. ndb_autodiscover : BUG#18952 2006-02-16 jmiller Needs to be fixed w.r.t binlog ndb_autodiscover2 : BUG#18952 2006-02-16 jmiller Needs to be fixed w.r.t binlog #ndb_binlog_discover : BUG#19395 2006-04-28 tomas/knielsen mysqld does not always detect cluster shutdown diff --git a/mysql-test/t/events.test b/mysql-test/t/events.test index fbcd4924d561a6e9deba94c15a2edeee5f9fd9cf..819d64ccf14de2e56e37a1215d2af74877790527 100644 --- a/mysql-test/t/events.test +++ b/mysql-test/t/events.test @@ -15,11 +15,10 @@ CREATE EVENT e_x2 ON SCHEDULE EVERY 1 SECOND DO DROP TABLE x_table; connection default; SHOW DATABASES LIKE 'db_x'; SET GLOBAL event_scheduler=1; ---sleep 2 +--sleep 1.5 SHOW DATABASES LIKE 'db_x'; SHOW TABLES FROM db_x; -SET GLOBAL event_scheduler=0; ---sleep 1 +SET GLOBAL event_scheduler=2; connection priv_conn; DROP EVENT e_x1; DROP EVENT e_x2; @@ -31,8 +30,7 @@ USE events_test; # # END: BUG #17289 Events: missing privilege check for drop database # -SET GLOBAL event_scheduler=0; ---sleep 1 +SET GLOBAL event_scheduler=2; drop event if exists event1; create event event1 on schedule every 15 minute starts now() ends date_add(now(), interval 5 hour) DO begin end; alter event event1 rename to event2 enable; @@ -92,11 +90,11 @@ drop event e_43; --echo "Let's check whether we can use non-qualified names" create table non_qualif(a int); create event non_qualif_ev on schedule every 10 minute do insert into non_qualif values (800219); ---sleep 2 +--sleep 1 select * from non_qualif; drop event non_qualif_ev; drop table non_qualif; -set global event_scheduler = 0; +set global event_scheduler = 2; create table t_event3 (a int, b float); drop event if exists event3; @@ -281,15 +279,15 @@ SHOW EVENTS; --echo "This should show us only 3 events:"; --replace_column 8 # 9 # -SHOW FULL EVENTS; +SHOW EVENTS; --echo "This should show us only 2 events:"; --replace_column 8 # 9 # -SHOW FULL EVENTS LIKE 't%event'; +SHOW EVENTS LIKE 't%event'; --echo "This should show us no events:"; --replace_column 8 # 9 # -SHOW FULL EVENTS FROM test LIKE '%'; +SHOW EVENTS FROM test LIKE '%'; #ok, we are back connection default; DROP DATABASE events_test2; @@ -300,7 +298,7 @@ SHOW EVENTS; --echo "we should see 4 events now:"; --replace_column 8 # 9 # -SHOW FULL EVENTS; +SHOW EVENTS; SELECT EVENT_CATALOG, EVENT_SCHEMA, EVENT_NAME, DEFINER, EVENT_BODY, EVENT_TYPE, EXECUTE_AT, INTERVAL_VALUE, INTERVAL_FIELD, STATUS,ON_COMPLETION, EVENT_COMMENT from information_schema.events; connection ev_con1; @@ -330,21 +328,21 @@ create event задачка on schedule every 123 minute starts now() ends now() drop event задачка; # event_scheduler is a global var ---error 1229 -set event_scheduler=0; -# event_scheduler could be only either 0 or 1 ---error 1231 -set global event_scheduler=2; +--error ER_GLOBAL_VARIABLE +set event_scheduler=2; +# event_scheduler could be only either 1 or 2 +--error ER_WRONG_VALUE_FOR_VAR +set global event_scheduler=3; --echo "DISABLE the scheduler. Testing that it does not work when the variable is 0" -set global event_scheduler=0; +set global event_scheduler=2; select definer, name, db from mysql.event; select get_lock("test_lock1", 20); create event закачка on schedule every 10 hour do select get_lock("test_lock1", 20); --echo "Should return 1 row" select definer, name, db from mysql.event; ---echo "Should be 0 processes" +--echo "Should be only 1 process" select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock("test_lock1"); drop event закачка; @@ -362,7 +360,7 @@ create event закачка on schedule every 10 hour do select get_lock("test_l --echo "Let some time pass to the event starts" --sleep 2 --echo "Should have only 2 processes: the scheduler and the locked event" -select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; +select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info;--echo "Release the mutex, the event worker should finish." --echo "Release the mutex, the event worker should finish." select release_lock("test_lock2"); drop event закачка; @@ -379,18 +377,11 @@ set global event_scheduler=1; select get_lock("test_lock2_1", 20); create event закачка21 on schedule every 10 hour do select get_lock("test_lock2_1", 20); --sleep 1 ---echo "Should see 1 process, locked on get_lock(" -#select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; ---echo "Shutting down the scheduler, it should wait for the running event" -set global event_scheduler=0; ---sleep 1 ---echo "Should have only 2 processes: the scheduler and the locked event" +--echo "Should have only 3 processes: the scheduler, our conn and the locked event" +select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; +set global event_scheduler=2; +--echo "Should have only our process now:" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; ---echo "Release the lock so the child process should finish. Hence the scheduler also" -select release_lock("test_lock2_1"); ---sleep 1 ---echo "Should see 0 processes now:" -select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; drop event закачка21; #### @@ -418,6 +409,7 @@ create event white_space on schedule every 10 hour disable do select 2; select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; +select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; drop event white_space; create event white_space on schedule every 10 hour disable do select 3; select event_schema, event_name, definer, event_body from information_schema.events where event_name='white_space'; @@ -426,7 +418,7 @@ drop event white_space; # END: BUG #17453: Creating Event crash the server # -##set global event_scheduler=1; +# # Bug#17403 "Events: packets out of order with show create event" # create event e1 on schedule every 1 year do set @a = 5; @@ -440,7 +432,7 @@ drop event e1; ##select get_lock("test_lock3", 20); ##create event закачка on schedule every 10 hour do select get_lock("test_lock3", 20); ##select sleep(2); -##show processlist; +##select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; ##drop event закачка; ##select release_lock("test_lock3"); @@ -450,14 +442,14 @@ drop event e1; ##select get_lock("test_lock4", 20); ##create event закачка4 on schedule every 1 second do select get_lock("test_lock4", 20); ##select sleep(3); -##--replace_column 1 # 6 # +##select /*6*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; ##drop event закачка4; ##select release_lock("test_lock4"); -##set global event_scheduler=0; +##set global event_scheduler=2; ##select sleep(2); ##--replace_column 1 # 6 # +##show processlist; ##select count(*) from mysql.event; drop database events_test; - diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index 3f339ff039891a561dca2b86fb956c611e4f2291..e3b79a6bd13e98435287d8534c37b8ea22d9b369 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -30,13 +30,13 @@ set @a=3; CREATE PROCEDURE p_16 () CREATE EVENT e_16 ON SCHEDULE EVERY @a SECOND DO SET @a=5; call p_16(); --echo "Here we used to crash!" ---error 1516 +--error ER_EVENT_ALREADY_EXISTS call p_16(); ---error 1516 +--error ER_EVENT_ALREADY_EXISTS call p_16(); DROP EVENT e_16; CALL p_16(); ---error 1516 +--error ER_EVENT_ALREADY_EXISTS CALL p_16(); DROP PROCEDURE p_16; DROP EVENT e_16; @@ -47,9 +47,9 @@ DROP EVENT e_16; # # Start - 16396: Events: Distant-future dates become past dates # ---error 1504 +--error ER_WRONG_VALUE create event e_55 on schedule at 99990101000000 do drop table t; ---error 1504 +--error ER_WRONG_VALUE create event e_55 on schedule every 10 hour starts 99990101000000 do drop table t; --error ER_EVENT_ENDS_BEFORE_STARTS create event e_55 on schedule every 10 minute ends 99990101000000 do drop table t; @@ -60,7 +60,7 @@ create event e_55 on schedule every 10 minute ends 99990101000000 do drop table # # Start - 16407: Events: Changes in sql_mode won't be taken into account # -set global event_scheduler=0; +set global event_scheduler=2; --echo "Wait a bit to settle down" --sleep 1 delete from mysql.event; @@ -79,7 +79,7 @@ delimiter ;| --echo "Now if everything is fine the event has compiled and is locked select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('test_bug16407'); -set global event_scheduler= 0; +set global event_scheduler= 2; select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; --echo "Let's check whether we change the sql_mode on ALTER EVENT" set sql_mode='traditional'; @@ -121,9 +121,9 @@ set global event_scheduler= 1; --sleep 1 select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('ee_16407_2'); ---sleep 3 +--sleep 2 select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; -set global event_scheduler= 0; +set global event_scheduler= 2; select * from events_smode_test order by ev_name, a; --echo "OK, last check before we drop them" select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; @@ -156,7 +156,7 @@ set global event_scheduler= 1; --echo "Should have 2 locked processes" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('ee_16407_5'); ---sleep 3 +--sleep 2 --echo "Should have 0 processes locked" select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select * from events_smode_test order by ev_name, a; @@ -166,7 +166,7 @@ drop event ee_16407_5; drop event ee_16407_6; drop procedure ee_16407_5_pendant; drop procedure ee_16407_6_pendant; -set global event_scheduler= 0; +set global event_scheduler= 2; drop table events_smode_test; set sql_mode=@old_sql_mode; # diff --git a/mysql-test/t/events_logs_tests.test b/mysql-test/t/events_logs_tests.test index 21adc17d5b8708880502d3fa9448d6b2a89d252f..a468685ddc638eab4b52708adcd62199e79fbf93 100644 --- a/mysql-test/t/events_logs_tests.test +++ b/mysql-test/t/events_logs_tests.test @@ -10,7 +10,7 @@ BEGIN END| delimiter ;| --echo "Check General Query Log" -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; create event log_general on schedule every 1 minute do SELect 'alabala', sleep(3) from dual; TRUNCATE mysql.general_log; --echo "1 row, the current statement!" @@ -22,7 +22,7 @@ SET GLOBAL event_scheduler=1; call select_general_log(); DROP PROCEDURE select_general_log; DROP EVENT log_general; -SET GLOBAL event_scheduler=0; +SET GLOBAL event_scheduler=2; --sleep 1 --echo "Check slow query log" @@ -53,10 +53,10 @@ TRUNCATE mysql.slow_log; SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; --echo "Set new values" SET GLOBAL long_query_time=4; -SET SESSION long_query_time=2; +SET SESSION long_query_time=1; --echo "Check that logging is working" -SELECT SLEEP(3); ---replace_regex /00:00:0[3-5]/SLEEPVAL/ +SELECT SLEEP(2); +--replace_regex /00:00:0[2-4]/SLEEPVAL/ SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; TRUNCATE mysql.slow_log; CREATE TABLE slow_event_test (slo_val tinyint, val tinyint); @@ -73,14 +73,15 @@ SELECT * FROM slow_event_test; SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; --echo "This should go to the slow log" SET SESSION long_query_time=10; +SET GLOBAL long_query_time=1; DROP EVENT long_event; -CREATE EVENT long_event2 ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(5); +CREATE EVENT long_event2 ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(2); --echo "Sleep some more time than the actual event run will take" ---sleep 7 +--sleep 3 --echo "Check our table. Should see 2 rows" SELECT * FROM slow_event_test; ---echo "Check slow log. Should see 1 row because 5 is over the threshold of 4 for GLOBAL, though under SESSION which is 10" ---replace_regex /00:00:0[5-7]/SLEEPVAL/ +--echo "Check slow log. Should see 1 row because 4 is over the threshold of 3 for GLOBAL, though under SESSION which is 10" +--replace_regex /00:00:0[2-4]/SLEEPVAL/ SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; DROP EVENT long_event2; SET GLOBAL long_query_time =@old_global_long_query_time; diff --git a/mysql-test/t/events_microsec.test b/mysql-test/t/events_microsec.test index 34855fdadff75bc8517090fd1f9b5b416e5c860a..e01120a0756b0743887e98bb4c013be9d3735151 100644 --- a/mysql-test/t/events_microsec.test +++ b/mysql-test/t/events_microsec.test @@ -1,55 +1,15 @@ create database if not exists events_test; use events_test; ---error 1235 +--error ER_NOT_SUPPORTED_YET CREATE EVENT micro_test ON SCHEDULE EVERY 100 MICROSECOND DO SELECT 1; ---error 1235 +--error ER_NOT_SUPPORTED_YET CREATE EVENT micro_test ON SCHEDULE EVERY 100 DAY_MICROSECOND DO SELECT 1; ---error 1235 +--error ER_NOT_SUPPORTED_YET CREATE EVENT micro_test ON SCHEDULE EVERY 100 HOUR_MICROSECOND DO SELECT 1; ---error 1235 +--error ER_NOT_SUPPORTED_YET CREATE EVENT micro_test ON SCHEDULE EVERY 100 MINUTE_MICROSECOND DO SELECT 1; ---error 1235 +--error ER_NOT_SUPPORTED_YET CREATE EVENT micro_test ON SCHEDULE EVERY 100 SECOND_MICROSECOND DO SELECT 1; ---echo "Now create normal event and change it on SQL level" -CREATE EVENT micro_test2 ON SCHEDULE EVERY 1 MONTH DO SELECT 1; -UPDATE mysql.event SET interval_field='MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; ---error 1235 -SHOW CREATE EVENT micro_test2; -SET GLOBAL event_scheduler=0; ---sleep 1 ---echo "Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -UPDATE mysql.event SET interval_field='DAY_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; ---error 1235 -SHOW CREATE EVENT micro_test2; -SET GLOBAL event_scheduler=0; ---sleep 1 ---echo "Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -UPDATE mysql.event SET interval_field='SECOND_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; ---error 1235 -SHOW CREATE EVENT micro_test2; -SET GLOBAL event_scheduler=0; ---sleep 1 ---echo "Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -UPDATE mysql.event SET interval_field='HOUR_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; ---error 1235 -SHOW CREATE EVENT micro_test2; -SET GLOBAL event_scheduler=0; ---sleep 1 ---echo "Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -UPDATE mysql.event SET interval_field='MINUTE_MICROSECOND' WHERE db=database() AND definer=user() AND name='micro_test2'; ---error 1235 -SHOW CREATE EVENT micro_test2; -SET GLOBAL event_scheduler=0; ---sleep 1 ---echo "Should not be running:" -SHOW VARIABLES like 'event_scheduler'; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER='event_scheduler'; -DROP EVENT micro_test2; - drop database events_test; diff --git a/mysql-test/t/events_scheduling.test b/mysql-test/t/events_scheduling.test index ae3cc7d5fac06abf0105503924219d308e4250db..a73d25cd8eef972382f92ca50336dff5cf902137 100644 --- a/mysql-test/t/events_scheduling.test +++ b/mysql-test/t/events_scheduling.test @@ -15,7 +15,7 @@ CREATE EVENT start_n_end DO INSERT INTO table_2 VALUES(1); --sleep 5 CREATE EVENT only_one_time ON SCHEDULE EVERY 2 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_3 VALUES(1); -CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_4 VALUES(1); +CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND ON COMPLETION PRESERVE DO INSERT INTO table_4 VALUES(1); --sleep 5 SELECT IF(SUM(a) >= 4, 'OK', 'ERROR') FROM table_1; SELECT IF(SUM(a) >= 5, 'OK', 'ERROR') FROM table_2; @@ -28,8 +28,8 @@ DROP EVENT start_n_end; --echo "Already dropped because ended. Therefore an error." --error ER_EVENT_DOES_NOT_EXIST DROP EVENT only_one_time; ---echo "Already dropped because ended. Therefore an error." ---error ER_EVENT_DOES_NOT_EXIST +--echo "Should be preserved" +SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS; DROP EVENT two_time; DROP TABLE table_1; DROP TABLE table_2; diff --git a/mysql-test/t/events_stress.test b/mysql-test/t/events_stress.test index f6eed79425c848d106ff897454751602c443b2c5..8d0034c232e418253584f2a0804a1cdd14d22f46 100644 --- a/mysql-test/t/events_stress.test +++ b/mysql-test/t/events_stress.test @@ -2,78 +2,124 @@ CREATE DATABASE IF NOT EXISTS events_test; # # DROP DATABASE test start (bug #16406) # -CREATE DATABASE events_test2; -USE events_test2; +CREATE DATABASE events_conn1_test2; +CREATE TABLE events_test.fill_it(test_name varchar(20), occur datetime); +CREATE USER event_user2@localhost; +CREATE DATABASE events_conn2_db; +GRANT ALL ON *.* TO event_user2@localhost; +CREATE USER event_user3@localhost; +CREATE DATABASE events_conn3_db; +GRANT ALL ON *.* TO event_user3@localhost; +connect (conn2,localhost,event_user2,,events_conn2_db); +--echo "In the second connection we create some events which won't be dropped till the end" +--disable_query_log +let $1= 50; +while ($1) +{ + eval CREATE EVENT conn2_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn2_ev$1", NOW()); + dec $1; +} +--enable_query_log +connect (conn3,localhost,event_user3,,events_conn3_db); +--echo "In the second connection we create some events which won't be dropped till the end" +--disable_query_log +let $1= 50; +while ($1) +{ + eval CREATE EVENT conn3_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn3_ev$1", NOW()); + dec $1; +} +--enable_query_log +connection default; +USE events_conn1_test2; CREATE EVENT ev_drop1 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; CREATE EVENT ev_drop2 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; CREATE EVENT ev_drop3 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1; USE events_test; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; -DROP DATABASE events_test2; -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; +DROP DATABASE events_conn1_test2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; --echo "Now testing stability - dropping db -> events while they are running" -CREATE DATABASE events_test2; -USE events_test2; +CREATE DATABASE events_conn1_test2; +USE events_conn1_test2; --disable_query_log -let $1= 1000; +let $1= 50; while ($1) { - eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1; + eval CREATE EVENT conn1_round1_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round1_ev$1", NOW()); dec $1; } --enable_query_log -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; SET GLOBAL event_scheduler=1; ---sleep 4 -DROP DATABASE events_test2; +--sleep 6 +DROP DATABASE events_conn1_test2; -SET GLOBAL event_scheduler=0; ---sleep 2 -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; -CREATE DATABASE events_test3; -USE events_test3; +SET GLOBAL event_scheduler=2; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; +CREATE DATABASE events_conn1_test3; +USE events_conn1_test3; --disable_query_log -let $1= 950; +let $1= 50; while ($1) { - eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1; + eval CREATE EVENT conn1_round2_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round2_ev$1", NOW()); dec $1; } --enable_query_log -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test3'; ---sleep 3 -CREATE DATABASE events_test4; -USE events_test4; +SET GLOBAL event_scheduler=1; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test3'; +CREATE DATABASE events_conn1_test4; +USE events_conn1_test4; --disable_query_log -let $1= 860; +let $1= 50; while ($1) { - eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1; + eval CREATE EVENT conn1_round3_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round3_ev$1", NOW()); dec $1; } --enable_query_log - -CREATE DATABASE events_test2; -USE events_test2; +CREATE DATABASE events_conn1_test2; +USE events_conn1_test2; --disable_query_log -let $1= 1050; +let $1= 50; while ($1) { - eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1; + eval CREATE EVENT ev_round4_drop$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round4_ev$1", NOW()); dec $1; } --enable_query_log -SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2'; +SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; --sleep 6 -DROP DATABASE events_test2; -SET GLOBAL event_scheduler=0; -DROP DATABASE events_test3; -SET GLOBAL event_scheduler=1; -DROP DATABASE events_test4; +connection conn2; +--send +DROP DATABASE events_conn2_db; +connection conn3; +--send +DROP DATABASE events_conn3_db; +connection default; +--send +DROP DATABASE events_conn1_test2; +DROP DATABASE events_conn1_test3; +SET GLOBAL event_scheduler=2; +DROP DATABASE events_conn1_test4; SET GLOBAL event_scheduler=1; +connection conn2; +reap; +disconnect conn2; +connection conn3; +reap; +disconnect conn3; +connection default; USE events_test; +DROP TABLE fill_it; +--disable_query_log +DROP USER event_user2@localhost; +DROP USER event_user3@localhost; +--enable_query_log # # DROP DATABASE test end (bug #16406) # diff --git a/mysql-test/t/im_cmd_line.imtest b/mysql-test/t/im_cmd_line.imtest new file mode 100644 index 0000000000000000000000000000000000000000..00e8351535e00dd42044c275ae673ebb0c850970 --- /dev/null +++ b/mysql-test/t/im_cmd_line.imtest @@ -0,0 +1,68 @@ +########################################################################### +# +# Tests for user-management command-line options. +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### + +# List users so we are sure about starting conditions. + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Add a new user. + +--echo ==> Adding user 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --add-user --username=testuser --password=abc 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Printing out line for 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --passwd --username=testuser --password=abc | tail -1 +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Edit user's attributes. + +--echo ==> Changing the password of 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --edit-user --username=testuser --password=xyz 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Printing out line for 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --passwd --username=testuser --password=xyz | tail -1 +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Drop user. + +--echo ==> Dropping user 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --drop-user --username=testuser 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo diff --git a/mysql-test/t/im_daemon_life_cycle-im.opt b/mysql-test/t/im_daemon_life_cycle-im.opt index 21c01191e4c8e52240ba916d9b02c7b3aa20ffd9..3a45c7a41f7af62efb101ae41a54c559af49fe19 100644 --- a/mysql-test/t/im_daemon_life_cycle-im.opt +++ b/mysql-test/t/im_daemon_life_cycle-im.opt @@ -1,2 +1,3 @@ --run-as-service --log=$MYSQLTEST_VARDIR/log/im.log +--monitoring-interval=1 diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index 87388d7c1e6620503bf7d0853e2d14cad508bbbb..d173ce2a6e2c64b23005667d26db81fdd54f4ee7 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -10,6 +10,9 @@ ########################################################################### +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted diff --git a/mysql-test/t/im_instance_conf-im.opt b/mysql-test/t/im_instance_conf-im.opt new file mode 100644 index 0000000000000000000000000000000000000000..34b74ce0c958eadb9ee408f2928ec95f2b2d01e8 --- /dev/null +++ b/mysql-test/t/im_instance_conf-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_instance_conf.imtest b/mysql-test/t/im_instance_conf.imtest new file mode 100644 index 0000000000000000000000000000000000000000..17703fdd303df74ab01fba8635da3150fe227fa6 --- /dev/null +++ b/mysql-test/t/im_instance_conf.imtest @@ -0,0 +1,228 @@ +########################################################################### +# +# This test suite checks the following statements: +# - CREATE INSTANCE <instance_name> [option1[=option1_value], ...]; +# - DROP INSTANCE <instance_name>; +# +# For CREATE INSTANCE we check that: +# - CREATE INSTANCE succeeds for non-existing instance; +# - CREATE INSTANCE fails for existing instance; +# - CREATE INSTANCE can get additional options with and w/o values; +# - CREATE INSTANCE parses options and handles grammar errors correctly. +# Check that strings with spaces are handled correctly, unknown (for +# mysqld) options should also be handled; +# - CREATE INSTANCE updates both config file and internal configuration cache; +# - CREATE INSTANCE allows to create instances only with properly formed +# names (mysqld*); +# +# For DROP INSTANCE we check that: +# - DROP INSTANCE succeeds for existing instance; +# - DROP INSTANCE fails for non-existing instance; +# - DROP INSTANCE fails for active instance. +# - DROP INSTANCE updates both config file and internal configuration cache; +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### +# +# Check starting conditions. +# +########################################################################### + +# Check that the configuration file contains only instances that we expect. + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# Check that mysqld1 is reported as running. + +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + +SHOW INSTANCES; + +# Check that the expected mysqld instance is actually run (check that we can +# connect and execute something). + +--echo +--echo ---> connection: mysql1_con +--connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--disconnect mysql1_con + +--echo +--echo ---> connection: default +--connection default + +########################################################################### +# +# CREATE INSTANCE tests. +# +########################################################################### + +# Check that CREATE INSTANCE succeeds for non-existing instance and also check +# that both config file and internal configuration cache have been updated. + +CREATE INSTANCE mysqld3; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# Check that CREATE INSTANCE fails for existing instance. Let's all three +# existing instances (running one, stopped one and just created one). Just in +# case... + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld1; + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld2; + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld3; + +# Check that CREATE INSTANCE can get additional options with and w/o values. +# Ensure that config file is updated properly. + +# - without values; + +--echo -------------------------------------------------------------------- +--exec grep nonguarded $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld4 nonguarded; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep nonguarded $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# - with value; + +--echo -------------------------------------------------------------------- +--exec grep test-A $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-B $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld5 test-A = 000, test-B = test; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-A $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-B $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# Check that CREATE INSTANCE parses options and handles grammar errors +# correctly. Check that strings with spaces are handled correctly, +# unknown (for mysqld) options should also be handled. + +# - check handling of extra spaces; + +--echo -------------------------------------------------------------------- +--exec grep test-C $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld6 test-C1 = 10 , test-C2 = 02 ; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-C1 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-C2 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# - check handling of grammar error; + +--echo -------------------------------------------------------------------- +--exec grep test-D $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-E $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld7 test-D = test-D-value ; +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld8 test-E 0 ; +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld8 test-F = ; +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-D $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-E $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check parsing of string option values + +--echo -------------------------------------------------------------------- +--exec grep test-1 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-2 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-3 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-4 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld9 test-1=" hello world ", test-2=' '; +SHOW INSTANCES; + +CREATE INSTANCE mysqld9a test-3='\b\babc\sdef'; +# test-3='abc def' +SHOW INSTANCES; + +CREATE INSTANCE mysqld9b test-4='abc\tdef', test-5='abc\ndef'; +SHOW INSTANCES; + +CREATE INSTANCE mysqld9c test-6="abc\rdef", test-7="abc\\def"; +# test-6=abc +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld10 test-bad=' \ '; +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-1 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-2 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-3 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-4 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-5 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-6 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-7 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-bad $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + + +# Check that CREATE INSTANCE allows to create instances only with properly +# formed names (mysqld*). + +--error 3014 # ER_MALFORMED_INSTANCE_NAME +CREATE INSTANCE qqq1; + diff --git a/mysql-test/t/im_life_cycle-im.opt b/mysql-test/t/im_life_cycle-im.opt new file mode 100644 index 0000000000000000000000000000000000000000..34b74ce0c958eadb9ee408f2928ec95f2b2d01e8 --- /dev/null +++ b/mysql-test/t/im_life_cycle-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_life_cycle.imtest b/mysql-test/t/im_life_cycle.imtest index 246843a022bd6deed0ba32a49eaa0a10275f11db..d71cdc86624a3590a7e70807d97236887db4baae 100644 --- a/mysql-test/t/im_life_cycle.imtest +++ b/mysql-test/t/im_life_cycle.imtest @@ -17,11 +17,15 @@ # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.1. +--echo -------------------------------------------------------------------- + +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld1; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld2; ########################################################################### # @@ -33,20 +37,22 @@ SHOW INSTANCE STATUS mysqld2; # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.2. +--echo -------------------------------------------------------------------- + START INSTANCE mysqld2; -# FIXME +# FIXME: START INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to start instance. SHOW INSTANCES; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld1; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld2; ---connect (mysql_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) +--connect (mysql_con,localhost,root,,mysql,$IM_MYSQLD2_PORT,$IM_MYSQLD2_SOCK) --connection mysql_con ---replace_result $IM_MYSQLD1_PORT IM_MYSQLD1_PORT +--replace_result $IM_MYSQLD2_PORT IM_MYSQLD2_PORT SHOW VARIABLES LIKE 'port'; --connection default @@ -61,9 +67,15 @@ SHOW VARIABLES LIKE 'port'; # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.3. +--echo -------------------------------------------------------------------- + STOP INSTANCE mysqld2; -# FIXME +# FIXME: STOP INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to stop instance. SHOW INSTANCES; --replace_column 3 VERSION_NUMBER 4 VERSION @@ -81,16 +93,17 @@ SHOW INSTANCE STATUS mysqld2; # ########################################################################### ---error 3000 +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.4. +--echo -------------------------------------------------------------------- + +--error 3000 # ER_BAD_INSTANCE_NAME START INSTANCE mysqld3; ---error 3002 +--error 3002 # ER_INSTANCE_ALREADY_STARTED START INSTANCE mysqld1; -# FIXME TODO -# BUG#12813: START/STOP INSTANCE commands accept a list as argument -# START INSTANCE mysqld1, mysqld2; - ########################################################################### # # 1.1.5. Check that Instance Manager reports correct errors for 'STOP INSTANCE' @@ -101,39 +114,54 @@ START INSTANCE mysqld1; # ########################################################################### ---error 3000 +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.5. +--echo -------------------------------------------------------------------- + +--error 3000 # ER_BAD_INSTANCE_NAME STOP INSTANCE mysqld3; # TODO: IM should be fixed. # BUG#12673: Instance Manager allows to stop the instance many times -# --error 3002 +# --error 3002 # ER_INSTANCE_ALREADY_STARTED # STOP INSTANCE mysqld2; -# FIXME TODO -# BUG#12813: START/STOP INSTANCE commands accept a list as argument -# STOP INSTANCE mysqld1, mysqld2; - ########################################################################### # # 1.1.6. Check that Instance Manager is able to restart guarded instances. # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.6. +--echo -------------------------------------------------------------------- + SHOW INSTANCES; --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD1_PATH_PID restarted +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + ########################################################################### # # 1.1.7. Check that Instance Manager does not restart non-guarded instance. # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.7. +--echo -------------------------------------------------------------------- + SHOW INSTANCES; START INSTANCE mysqld2; -# FIXME +# FIXME: START INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to start instance. SHOW INSTANCES; @@ -147,7 +175,13 @@ SHOW INSTANCES; # incomplete SHOW INSTANCE STATUS command. # ########################################################################### ---error 1149 + +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.8. +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR SHOW INSTANCE STATUS; # @@ -159,8 +193,13 @@ SHOW INSTANCE STATUS; # a list as argument. # ---error 1149 +--echo +--echo -------------------------------------------------------------------- +--echo -- BUG#12813 +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR START INSTANCE mysqld1,mysqld2,mysqld3; ---error 1149 +--error ER_SYNTAX_ERROR STOP INSTANCE mysqld1,mysqld2,mysqld3; diff --git a/mysql-test/t/im_options.imtest b/mysql-test/t/im_options.imtest new file mode 100644 index 0000000000000000000000000000000000000000..cd905416cda42b355f41e03b3b89555dc65778d5 --- /dev/null +++ b/mysql-test/t/im_options.imtest @@ -0,0 +1,268 @@ +########################################################################### +# +# This test suite checks the following statements: +# - SET <instance id>.<option name> = <option value>; +# - UNSET <instance id>.<option name> = <option value>; +# - FLUSH INSTANCES; +# +# For SET/UNSET we check that: +# - SET ignores spaces correctly; +# - UNSET does not allow option-value part (= <option value>); +# - SET/UNSET can be applied several times w/o error; +# - SET/UNSET is allowed only for stopped instances; +# - SET/UNSET updates both the configuration cache in IM and +# the configuration file; +# +# For FLUSH INSTANCES we check that: +# - FLUSH INSTANCES is allowed only when all instances are stopped; +# +# According to the IM implementation details, we should play at least with the +# following options: +# - server_id +# - port +# - nonguarded + +# Let's test SET statement on the option 'server_id'. It's expected that +# originally the instances have the following server ids and states: +# - mysqld1: server_id: 1; running (online) +# - mysqld2: server_id: 2; stopped (offline) +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### +# +# Check starting conditions. +# +########################################################################### + +# - check the configuration file; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# - check the running instances. + +--connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) + +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--connection default + +# - check the internal cache. + +SHOW INSTANCES; + +########################################################################### +# +# Check that SET/UNSET is allowed only for stopped instances. +# +########################################################################### + +# - check that SET/UNSET is denied for running instances; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld1.server_id; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld1.server_id = 11; + +# - check that SET/UNSET is denied for active instances: +# - create dummy misconfigured instance; +# - start it; +# - try to set/unset options; + +CREATE INSTANCE mysqld3 datadir = '/'; +START INSTANCE mysqld3; + +# FIXME: START INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + +# NOTE: We can not analyze state of the instance here -- it can be Failed or +# Starting because Instance Manager is trying to start the misconfigured +# instance several times. + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld3.server_id; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld3.server_id = 11; + +STOP INSTANCE mysqld3; + +# FIXME: STOP INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to stop instance. + +--replace_column 3 VERSION_NUMBER 4 VERSION +SHOW INSTANCE STATUS mysqld3; + +# - check that SET/UNSET succeed for stopped instances; +# - check that SET/UNSET can be applied multiple times; + +UNSET mysqld2.server_id; +UNSET mysqld2.server_id; + +--replace_column 2 option_value +SHOW INSTANCE OPTIONS mysqld2; + +SET mysqld2.server_id = 2; +SET mysqld2.server_id = 2; + +--replace_column 2 option_value +SHOW INSTANCE OPTIONS mysqld2; + +# - check that UNSET does not allow option-value part (= <option value>); + +--error ER_SYNTAX_ERROR +UNSET mysqld2.server_id = 11; + +# - check that SET/UNSET working properly with multiple options; + +SET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc = 0010, mysqld3.ddd = 0020; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep ddd $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +UNSET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc, mysqld3.ddd; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ddd $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check that if some instance name is invalid or the active is active, +# whole SET-statement will not be executed; + +--error 3000 # ER_BAD_INSTANCE_NAME +SET mysqld2.aaa, mysqld3.bbb, mysqld.ccc = 0010; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld2.aaa, mysqld3.bbb, mysqld1.ccc = 0010; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check that if some instance name is invalid or the active is active, +# whole UNSET-statement will not be executed; + +--error 3000 # ER_BAD_INSTANCE_NAME +UNSET mysqld2.server_id, mysqld3.server_id, mysqld.ccc; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld2.server_id, mysqld3.server_id, mysqld1.ccc; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +DROP INSTANCE mysqld3; + +# - check that spaces are handled correctly; + +SET mysqld2.server_id=222; +SET mysqld2.server_id = 222; +SET mysqld2.server_id = 222 ; +SET mysqld2 . server_id = 222 ; +SET mysqld2 . server_id = 222 , mysqld2 . aaa , mysqld2 . bbb ; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +UNSET mysqld2 . aaa , mysqld2 . bbb ; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +########################################################################### +# +# Check that SET/UNSET updates both the configuration cache in IM and +# the configuration file. +# +########################################################################### + +# - check that the configuration file has been updated (i.e. contains +# server_id=SERVER_ID for mysqld2); + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# - (for mysqld1) check that the running instance has not been affected: +# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' +# returns zero; + +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--connection default + +# - check that internal cache of Instance Manager has been affected; +# TODO: we should check only server_id option here. + +# SHOW INSTANCE OPTIONS mysqld2; + +########################################################################### +# +# Check that FLUSH INSTANCES is allowed only when all instances are stopped. +# +########################################################################### + +SHOW INSTANCES; + +--error 3016 # ER_THERE_IS_ACTIVE_INSTACE +FLUSH INSTANCES; + +STOP INSTANCE mysqld1; +# FIXME: STOP INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to stop instance. + +SHOW INSTANCES; + +FLUSH INSTANCES; diff --git a/mysql-test/t/im_options_set.imtest b/mysql-test/t/im_options_set.imtest deleted file mode 100644 index a9b64861f9960b51e614e895491b944f95cf12eb..0000000000000000000000000000000000000000 --- a/mysql-test/t/im_options_set.imtest +++ /dev/null @@ -1,142 +0,0 @@ -########################################################################### -# -# This file contains test for (3) test suite. -# -# Consult WL#2789 for more information. -# -########################################################################### - -# -# Check the options-management commands: -# - SET; -# - FLUSH INSTANCES; -# -# Let's test the commands on the option 'server_id'. It's expected that -# originally the instances have the following server ids: -# - mysqld1: 1 -# - mysqld2: 2 -# -# 1. SET <instance_id>.server_id= SERVER_ID); where SERVER_ID is 11 or 12. -# 1.1. check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); -# 1.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero; -# 1.3. check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# -# 2. FLUSH INSTANCES; -# 2.1. check that the configuration file has not been updated; -# 2.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero value; -# 2.3. check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> contains 'server_id=SERVER_ID' line). -# -# 3. Restore options. -# - -########################################################################### - ---source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 1. SET <instance_id>.server_id= SERVER_ID); where SERVER_ID is 11 or 12. -# -########################################################################### - -# * mysqld1 - -SET mysqld1.server_id = 11; - -# - check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; - -# * mysqld2 - -SET mysqld2.server_id = 12; - -# - check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 2. FLUSH INSTANCES; -# -########################################################################### - -FLUSH INSTANCES; - -# - check that the configuration file has not been updated; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> contains 'server_id=' line). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; diff --git a/mysql-test/t/im_options_unset.imtest b/mysql-test/t/im_options_unset.imtest deleted file mode 100644 index 40629805d455ade1eb668025165e7e14af6a42d2..0000000000000000000000000000000000000000 --- a/mysql-test/t/im_options_unset.imtest +++ /dev/null @@ -1,150 +0,0 @@ -########################################################################### -# -# This file contains test for (3) test suite. -# -# Consult WL#2789 for more information. -# -########################################################################### - -# -# Check the options-management commands: -# - UNSET; -# - FLUSH INSTANCES; -# -# Let's test the commands on the option 'server_id'. It's expected that -# originally the instances have the following server ids: -# - mysqld1: 1 -# - mysqld2: 2 -# -# The test case: -# -# 1. UNSET <instance_id>.server_id; -# -# Do the step for both instances. -# -# 1.1. check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); -# 1.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; -# 1.3. check that internal cache of Instance Manager is not affected (i.e. -# SHOW INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# -# 2. FLUSH INSTANCES; -# -# Do the step for both instances. -# -# 2.1. check that the configuration file has not been updated (i.e. does not -# contain 'server_id=' for the instance); -# 2.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; -# 2.3. check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> does not contain 'server_id=' line). -# - -########################################################################### - ---source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 1. UNSET <instance_id>.server_id; -# -########################################################################### - -# * mysqld1 - -UNSET mysqld1.server_id; - -# - check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check that the running instance has not been affected: connect to the -# instance and check that 'SHOW VARIABLES LIKE 'server_id'' returns non-zero -# value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager is not affected (i.e. SHOW -# INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; - -# * mysqld2 - -UNSET mysqld2.server_id; - -# - check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf || true; - -# - check that internal cache of Instance Manager is not affected (i.e. SHOW -# INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 2. FLUSH INSTANCES; -# -########################################################################### - -FLUSH INSTANCES; - -# - check that the configuration file has not been updated (i.e. does not -# contain 'server_id=' for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf || true; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> does not contain 'server_id=' line). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; diff --git a/mysql-test/t/im_utils-im.opt b/mysql-test/t/im_utils-im.opt new file mode 100644 index 0000000000000000000000000000000000000000..34b74ce0c958eadb9ee408f2928ec95f2b2d01e8 --- /dev/null +++ b/mysql-test/t/im_utils-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_utils.imtest b/mysql-test/t/im_utils.imtest index dc6fb93c4ffb54de9260cfe9f65a9e3a8b02477b..8e8d475cfeeb39a0ed16a856abae914ed9236cc5 100644 --- a/mysql-test/t/im_utils.imtest +++ b/mysql-test/t/im_utils.imtest @@ -17,6 +17,9 @@ # - the second instance is offline; # +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; # @@ -41,8 +44,9 @@ SHOW INSTANCE OPTIONS mysqld2; START INSTANCE mysqld2; -# FIXME --- sleep 3 +# FIXME: START INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to start instance. STOP INSTANCE mysqld2; diff --git a/mysql-test/t/log_tables.test b/mysql-test/t/log_tables.test index 048da802d02d3e559215b66423ef89737c73b292..ef172f6b138e45765e924d922535aca276c4d803 100644 --- a/mysql-test/t/log_tables.test +++ b/mysql-test/t/log_tables.test @@ -16,7 +16,7 @@ use mysql; # truncate table general_log; ---replace_column 1 TIMESTAMP +--replace_column 1 TIMESTAMP 3 THREAD_ID select * from general_log; truncate table slow_log; --replace_column 1 TIMESTAMP @@ -31,7 +31,7 @@ select * from slow_log; # truncate table general_log; ---replace_column 1 TIMESTAMP +--replace_column 1 TIMESTAMP 3 THREAD_ID select * from general_log where argument like '%general_log%'; @@ -156,7 +156,7 @@ truncate table mysql.general_log; set names utf8; create table bug16905 (s char(15) character set utf8 default 'пуÑто'); insert into bug16905 values ('новое'); ---replace_column 1 TIMESTAMP +--replace_column 1 TIMESTAMP 3 THREAD_ID select * from mysql.general_log; drop table bug16905; diff --git a/mysys/default.c b/mysys/default.c index 580bcc19eca8e545809ed5f484f35d7e82fb93ab..3a80d7b37b9de3773e9051efcc9475d213c9b719 100644 --- a/mysys/default.c +++ b/mysys/default.c @@ -244,7 +244,8 @@ int my_search_option_files(const char *conf_file, int *argc, char ***argv, handle_option_ctx structure. group_name The name of the group the option belongs to. option The very option to be processed. It is already - prepared to be used in argv (has -- prefix) + prepared to be used in argv (has -- prefix). If it + is NULL, we are handling a new group (section). DESCRIPTION This handler checks whether a group is one of the listed and adds an option @@ -263,6 +264,9 @@ static int handle_default_option(void *in_ctx, const char *group_name, char *tmp; struct handle_option_ctx *ctx= (struct handle_option_ctx *) in_ctx; + if (!option) + return 0; + if (find_type((char *)group_name, ctx->group, 3)) { if (!(tmp= alloc_root(ctx->alloc, (uint) strlen(option) + 1))) @@ -719,6 +723,10 @@ static int search_default_file_with_ext(Process_option_func opt_handler, end[0]=0; strnmov(curr_gr, ptr, min((uint) (end-ptr)+1, 4096)); + + /* signal that a new group is found */ + opt_handler(handler_ctx, curr_gr, NULL); + continue; } if (!found_group) diff --git a/mysys/default_modify.c b/mysys/default_modify.c index 0f58b8a930c7b00a1961edf561a775aaab298697..8dbcac699eace1c75d15bb364bbe603109bcb4f5 100644 --- a/mysys/default_modify.c +++ b/mysys/default_modify.c @@ -40,11 +40,13 @@ static char *add_option(char *dst, const char *option_value, SYNOPSYS modify_defaults_file() file_location The location of configuration file to edit - option option to look for - option value The value of the option we would like to set - section_name the name of the section - remove_option This is true if we want to remove the option. - False otherwise. + option The name of the option to look for (can be NULL) + option value The value of the option we would like to set (can be NULL) + section_name The name of the section (must be NOT NULL) + remove_option This defines what we want to remove: + - MY_REMOVE_NONE -- nothing to remove; + - MY_REMOVE_OPTION -- remove the specified option; + - MY_REMOVE_SECTION -- remove the specified section; IMPLEMENTATION We open the option file first, then read the file line-by-line, looking for the section we need. At the same time we put these lines @@ -67,7 +69,9 @@ int modify_defaults_file(const char *file_location, const char *option, FILE *cnf_file; MY_STAT file_stat; char linebuff[BUFF_SIZE], *src_ptr, *dst_ptr, *file_buffer; - uint opt_len, optval_len, sect_len, nr_newlines= 0, buffer_size; + uint opt_len= 0; + uint optval_len= 0; + uint sect_len, nr_newlines= 0, buffer_size; my_bool in_section= FALSE, opt_applied= 0; uint reserve_extended; uint new_opt_len; @@ -81,8 +85,11 @@ int modify_defaults_file(const char *file_location, const char *option, if (my_fstat(fileno(cnf_file), &file_stat, MYF(0))) goto malloc_err; - opt_len= (uint) strlen(option); - optval_len= (uint) strlen(option_value); + if (option && option_value) + { + opt_len= (uint) strlen(option); + optval_len= (uint) strlen(option_value); + } new_opt_len= opt_len + 1 + optval_len + NEWLINE_LEN; @@ -119,8 +126,8 @@ int modify_defaults_file(const char *file_location, const char *option, continue; } - /* correct the option */ - if (in_section && !strncmp(src_ptr, option, opt_len) && + /* correct the option (if requested) */ + if (option && in_section && !strncmp(src_ptr, option, opt_len) && (*(src_ptr + opt_len) == '=' || my_isspace(&my_charset_latin1, *(src_ptr + opt_len)) || *(src_ptr + opt_len) == '\0')) @@ -143,7 +150,12 @@ int modify_defaults_file(const char *file_location, const char *option, } else { - /* If going to new group and we have option to apply, do it now */ + /* + If we are going to the new group and have an option to apply, do + it now. If we are removing a single option or the whole section + this will only trigger opt_applied flag. + */ + if (in_section && !opt_applied && *src_ptr == '[') { dst_ptr= add_option(dst_ptr, option_value, option, remove_option); @@ -153,7 +165,10 @@ int modify_defaults_file(const char *file_location, const char *option, for (; nr_newlines; nr_newlines--) dst_ptr= strmov(dst_ptr, NEWLINE); - dst_ptr= strmov(dst_ptr, linebuff); + + /* Skip the section if MY_REMOVE_SECTION was given */ + if (!in_section || remove_option != MY_REMOVE_SECTION) + dst_ptr= strmov(dst_ptr, linebuff); } /* Look for a section */ if (*src_ptr == '[') @@ -167,18 +182,31 @@ int modify_defaults_file(const char *file_location, const char *option, {} if (*src_ptr != ']') + { + in_section= FALSE; continue; /* Missing closing parenthesis. Assume this was no group */ + } + + if (remove_option == MY_REMOVE_SECTION) + dst_ptr= dst_ptr - strlen(linebuff); + in_section= TRUE; } else in_section= FALSE; /* mark that this section is of no interest to us */ } } - /* File ended. */ - if (!opt_applied && !remove_option && in_section) + + /* + File ended. Apply an option or set opt_applied flag (in case of + MY_REMOVE_SECTION) so that the changes are saved. Do not do anything + if we are removing non-existent option. + */ + + if (!opt_applied && in_section && (remove_option != MY_REMOVE_OPTION)) { /* New option still remains to apply at the end */ - if (*(dst_ptr - 1) != '\n') + if (!remove_option && *(dst_ptr - 1) != '\n') dst_ptr= strmov(dst_ptr, NEWLINE); dst_ptr= add_option(dst_ptr, option_value, option, remove_option); opt_applied= 1; diff --git a/server-tools/instance-manager/CMakeLists.txt b/server-tools/instance-manager/CMakeLists.txt index c20b9c7f9dfeee7a371abc1176a52fb7e441ed79..1983d459ce28cdf2cac4162a9ebc2bc0ad28583e 100644 --- a/server-tools/instance-manager/CMakeLists.txt +++ b/server-tools/instance-manager/CMakeLists.txt @@ -9,6 +9,7 @@ ADD_EXECUTABLE(mysqlmanager buffer.cc command.cc commands.cc guardian.cc instanc instance_options.cc listener.cc log.cc manager.cc messages.cc mysql_connection.cc mysqlmanager.cc options.cc parse.cc parse_output.cc priv.cc protocol.cc thread_registry.cc user_map.cc imservice.cpp windowsservice.cpp + user_management_commands.cc ../../sql/net_serv.cc ../../sql-common/pack.c ../../sql/password.c ../../sql/sql_state.c ../../sql-common/client.c ../../libmysql/get_password.c ../../libmysql/errmsg.c) diff --git a/server-tools/instance-manager/IMService.cpp b/server-tools/instance-manager/IMService.cpp index b7ea8e7eb8132daac8016368cdfdf8a188e1561f..f9ea7ee471d6caa05633d057d3557f6323b4a1e3 100644 --- a/server-tools/instance-manager/IMService.cpp +++ b/server-tools/instance-manager/IMService.cpp @@ -20,7 +20,7 @@ IMService::~IMService(void) void IMService::Stop() { ReportStatus(SERVICE_STOP_PENDING); - + // stop the IM work raise(SIGTERM); } @@ -30,15 +30,14 @@ void IMService::Run(DWORD argc, LPTSTR *argv) // report to the SCM that we're about to start ReportStatus((DWORD)SERVICE_START_PENDING); - Options o; - o.load(argc, argv); - + Options::load(argc, argv); + // init goes here ReportStatus((DWORD)SERVICE_RUNNING); // wait for main loop to terminate - manager(o); - o.cleanup(); + manager(); + Options::cleanup(); } void IMService::Log(const char *msg) @@ -46,13 +45,13 @@ void IMService::Log(const char *msg) log_info(msg); } -int HandleServiceOptions(Options options) +int HandleServiceOptions() { int ret_val= 0; IMService winService; - if (options.install_as_service) + if (Options::Service::install_as_service) { if (winService.IsInstalled()) log_info("Service is already installed"); @@ -64,7 +63,7 @@ int HandleServiceOptions(Options options) ret_val= 1; } } - else if (options.remove_service) + else if (Options::Service::remove_service) { if (! winService.IsInstalled()) log_info("Service is not installed"); @@ -77,6 +76,19 @@ int HandleServiceOptions(Options options) } } else - ret_val= !winService.Init(); + { + log_info("Initializing Instance Manager service..."); + + if (!winService.Init()) + { + log_info("Service failed to initialize."); + fprintf(stderr, + "The service should be started by Windows Service Manager.\n" + "The MySQL Manager should be started with '--standalone'\n" + "to run from command line."); + ret_val= 1; + } + } + return ret_val; } diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h index cad38bebdafbed4632b624a71a3374863ad1a4d7..94d59c2af317a7e5e279a25af0fd093a77ad74e9 100644 --- a/server-tools/instance-manager/IMService.h +++ b/server-tools/instance-manager/IMService.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #pragma once #include "windowsservice.h" @@ -12,3 +30,5 @@ class IMService : public WindowsService void Stop(); void Run(DWORD argc, LPTSTR *argv); }; + +extern int HandleServiceOptions(); diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am index 6ab5c3d1bfc41b33b68da53eec4d0ef62aea48cc..4139bf2eb105fbbe5d143e45bcb80f009882870e 100644 --- a/server-tools/instance-manager/Makefile.am +++ b/server-tools/instance-manager/Makefile.am @@ -76,7 +76,10 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ guardian.cc guardian.h \ parse_output.cc parse_output.h \ mysql_manager_error.h \ - portability.h + portability.h \ + exit_codes.h \ + user_management_commands.h \ + user_management_commands.cc mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ liboptions.la \ diff --git a/server-tools/instance-manager/WindowsService.cpp b/server-tools/instance-manager/WindowsService.cpp index 192045b7a4c5a056a562ed4ce048bf2e697045b3..8a36a2f2fdd965a08f2f724d2043b0d9412931b3 100644 --- a/server-tools/instance-manager/WindowsService.cpp +++ b/server-tools/instance-manager/WindowsService.cpp @@ -7,9 +7,9 @@ static WindowsService *gService; WindowsService::WindowsService(void) : statusCheckpoint(0), serviceName(NULL), - inited(false), + inited(FALSE), dwAcceptedControls(SERVICE_ACCEPT_STOP), - debugging(false) + debugging(FALSE) { gService= this; status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; @@ -22,11 +22,12 @@ WindowsService::~WindowsService(void) BOOL WindowsService::Install() { - bool ret_val= false; + bool ret_val= FALSE; SC_HANDLE newService; SC_HANDLE scm; - if (IsInstalled()) return true; + if (IsInstalled()) + return TRUE; // determine the name of the currently executing file char szFilePath[_MAX_PATH]; @@ -34,7 +35,7 @@ BOOL WindowsService::Install() // open a connection to the SCM if (!(scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE))) - return false; + return FALSE; newService= CreateService(scm, serviceName, displayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, @@ -45,7 +46,7 @@ BOOL WindowsService::Install() if (newService) { CloseServiceHandle(newService); - ret_val= true; + ret_val= TRUE; } CloseServiceHandle(scm); @@ -56,34 +57,35 @@ BOOL WindowsService::Init() { assert(serviceName != NULL); - if (inited) return true; + if (inited) + return TRUE; SERVICE_TABLE_ENTRY stb[] = { { (LPSTR)serviceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, { NULL, NULL } }; - inited= true; + inited= TRUE; return StartServiceCtrlDispatcher(stb); //register with the Service Manager } BOOL WindowsService::Remove() { - bool ret_val= false; + bool ret_val= FALSE; - if (! IsInstalled()) - return true; + if (!IsInstalled()) + return TRUE; // open a connection to the SCM SC_HANDLE scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE); - if (! scm) - return false; + if (!scm) + return FALSE; SC_HANDLE service= OpenService(scm, serviceName, DELETE); if (service) { if (DeleteService(service)) - ret_val= true; + ret_val= TRUE; DWORD dw= ::GetLastError(); CloseServiceHandle(service); } @@ -116,7 +118,8 @@ void WindowsService::SetAcceptedControls(DWORD acceptedControls) BOOL WindowsService::ReportStatus(DWORD currentState, DWORD waitHint, DWORD dwError) { - if(debugging) return TRUE; + if (debugging) + return TRUE; if(currentState == SERVICE_START_PENDING) status.dwControlsAccepted= 0; diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h index 1a034ce13518da4f6bf760e4cc2227ad4c23e91b..3af7cdf39a7bacd1ce4b3e22584376f180661149 100644 --- a/server-tools/instance-manager/WindowsService.h +++ b/server-tools/instance-manager/WindowsService.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #pragma once class WindowsService diff --git a/server-tools/instance-manager/command.h b/server-tools/instance-manager/command.h index b84cc6a8e9e918dbf9086e83f3f55d60b2d2a6c5..f31ea4048670de6be4126a81c83ee351262654fc 100644 --- a/server-tools/instance-manager/command.h +++ b/server-tools/instance-manager/command.h @@ -22,10 +22,12 @@ #pragma interface #endif -/* Class responsible for allocation of im commands. */ +/* Class responsible for allocation of IM commands. */ class Instance_map; +struct st_net; + /* Command - entry point for any command. GangOf4: 'Command' design pattern @@ -37,8 +39,18 @@ class Command Command(Instance_map *instance_map_arg= 0); virtual ~Command(); - /* method of executing: */ - virtual int execute(struct st_net *net, ulong connection_id) = 0; + /* + This operation incapsulates behaviour of the command. + + SYNOPSYS + net The network connection to the client. + connection_id Client connection ID + + RETURN + 0 On success + non 0 On error. Client error code is returned. + */ + virtual int execute(st_net *net, ulong connection_id) = 0; protected: Instance_map *instance_map; diff --git a/server-tools/instance-manager/commands.cc b/server-tools/instance-manager/commands.cc index 7b999f615038abfc36c5d7ecbd218d5915a7a358..07e1e9a18f3a8254c3ccf5324368bb7a56cdc041 100644 --- a/server-tools/instance-manager/commands.cc +++ b/server-tools/instance-manager/commands.cc @@ -14,36 +14,53 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + #include "commands.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql.h> +#include <my_dir.h> + +#include "buffer.h" +#include "guardian.h" #include "instance_map.h" +#include "log.h" +#include "manager.h" #include "messages.h" #include "mysqld_error.h" #include "mysql_manager_error.h" -#include "protocol.h" -#include "buffer.h" #include "options.h" +#include "priv.h" +#include "protocol.h" -#include <m_string.h> -#include <m_ctype.h> -#include <mysql.h> -#include <my_dir.h> + +/* + modify_defaults_to_im_error -- a map of error codes of + mysys::modify_defaults_file() into Instance Manager error codes. +*/ + +static const int modify_defaults_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, + ER_ACCESS_OPTION_FILE }; /* - Add a string to a buffer + Add a string to a buffer. SYNOPSYS put_to_buff() buff buffer to add the string str string to add - uint offset in the buff to add a string + position offset in the buff to add a string DESCRIPTION Function to add a string to the buffer. It is different from - store_to_protocol_packet, which is used in the protocol.cc. The last - one also stores the length of the string in a special way. + store_to_protocol_packet, which is used in the protocol.cc. + The last one also stores the length of the string in a special way. This is required for MySQL client/server protocol support only. RETURN @@ -51,7 +68,6 @@ 1 - error occured */ - static inline int put_to_buff(Buffer *buff, const char *str, uint *position) { uint len= strlen(str); @@ -88,749 +104,1615 @@ static int parse_version_number(const char *version_str, char *version, } -/* implementation for Show_instances: */ +/************************************************************************** + Implementation of Instance_name. +**************************************************************************/ +Instance_name::Instance_name(const LEX_STRING *name) +{ + str.str= str_buffer; + str.length= name->length; -/* - The method sends a list of instances in the instance map to the client. + if (str.length > MAX_INSTANCE_NAME_SIZE - 1) + str.length= MAX_INSTANCE_NAME_SIZE - 1; - SYNOPSYS - Show_instances::execute() - net The network connection to the client. - connection_id Client connection ID + strmake(str.str, name->str, str.length); +} - RETURN - 0 - ok - 1 - error occured +/************************************************************************** + Implementation of Show_instances. +**************************************************************************/ + +/* + Implementation of SHOW INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instances::execute(struct st_net *net, ulong connection_id) +int Show_instances::execute(st_net *net, ulong connection_id) { - Buffer send_buff; /* buffer for packets */ - LIST name, status; - NAME_WITH_LENGTH name_field, status_field; + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net))) + return err_code; + + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instances::write_header(st_net *net) +{ + LIST name, state; + LEX_STRING name_field, state_field; LIST *field_list; - uint position=0; - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - field_list= list_add(NULL, &status); + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + field_list= list_add(NULL, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + + +int Show_instances::write_data(st_net *net) +{ + my_bool err_status= FALSE; + + Instance *instance; + Instance_map::Iterator iterator(instance_map); + + instance_map->guardian->lock(); + instance_map->lock(); + while ((instance= iterator.next())) { - Instance *instance; - Instance_map::Iterator iterator(instance_map); + Buffer send_buf; /* buffer for packets */ + uint pos= 0; + + const char *instance_name= instance->options.instance_name.str; + const char *state_name= instance_map->get_instance_state_name(instance); - instance_map->lock(); - while ((instance= iterator.next())) + if (store_to_protocol_packet(&send_buf, instance_name, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + my_net_write(net, send_buf.buffer, pos)) { - position= 0; - store_to_protocol_packet(&send_buff, instance->options.instance_name, - &position); - if (instance->is_running()) - store_to_protocol_packet(&send_buff, (char*) "online", &position); - else - store_to_protocol_packet(&send_buff, (char*) "offline", &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + err_status= TRUE; + break; } - instance_map->unlock(); } - if (send_eof(net)) - goto err; - if (net_flush(net)) - goto err; - return 0; -err: - return ER_OUT_OF_RESOURCES; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return err_status ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Flush_instances: */ +/************************************************************************** + Implementation of Flush_instances. +**************************************************************************/ + +/* + Implementation of FLUSH INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation + ER_THERE_IS_ACTIVE_INSTACE If there is an active instance +*/ -int Flush_instances::execute(struct st_net *net, ulong connection_id) +int Flush_instances::execute(st_net *net, ulong connection_id) { - if (instance_map->flush_instances() || - net_send_ok(net, connection_id, NULL)) + instance_map->guardian->lock(); + instance_map->lock(); + + if (instance_map->is_there_active_instance()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); + return ER_THERE_IS_ACTIVE_INSTACE; + } + + if (instance_map->flush_instances()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); return ER_OUT_OF_RESOURCES; + } - return 0; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return net_send_ok(net, connection_id, NULL) ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Show_instance_status: */ +/************************************************************************** + Implementation of Abstract_instance_cmd. +**************************************************************************/ -Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Abstract_instance_cmd::Abstract_instance_cmd( + Instance_map *instance_map_arg, const LEX_STRING *instance_name_arg) + :Command(instance_map_arg), + instance_name(instance_name_arg) { - Instance *instance; + /* + MT-NOTE: we can not make a search for Instance object here, + because it can dissappear after releasing the lock. + */ +} + + +int Abstract_instance_cmd::execute(st_net *net, ulong connection_id) +{ + int err_code; + + instance_map->lock(); + + { + Instance *instance= instance_map->find(get_instance_name()); + + if (!instance) + { + instance_map->unlock(); + return ER_BAD_INSTANCE_NAME; + } + + err_code= execute_impl(net, instance); + } + + instance_map->unlock(); - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + if (!err_code) + err_code= send_ok_response(net, connection_id); + + return err_code; } -/* - The method sends a table with a status of requested instance to the client. +/************************************************************************** + Implementation of Show_instance_status. +**************************************************************************/ - SYNOPSYS - Show_instance_status::do_command() - net The network connection to the client. - instance_name The name of the instance. +Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} - RETURN - 0 - ok - 1 - error occured + +/* + Implementation of SHOW INSTANCE STATUS statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ +int Show_instance_status::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} -int Show_instance_status::execute(struct st_net *net, - ulong connection_id) + +int Show_instance_status::send_ok_response(st_net *net, ulong connection_id) { - enum { MAX_VERSION_LENGTH= 40 }; - Buffer send_buff; /* buffer for packets */ - LIST name, status, version, version_number; + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_status::write_header(st_net *net) +{ + LIST name, state, version, version_number, mysqld_compatible; LIST *field_list; - NAME_WITH_LENGTH name_field, status_field, version_field, - version_number_field; - uint position=0; + LEX_STRING name_field, state_field, version_field, + version_number_field, mysqld_compatible_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - version_field.name= (char*) "version"; + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + version_field.str= (char *) "version"; version_field.length= MAX_VERSION_LENGTH; version.data= &version_field; - version_number_field.name= (char*) "version_number"; + + version_number_field.str= (char *) "version_number"; version_number_field.length= MAX_VERSION_LENGTH; version_number.data= &version_number_field; - field_list= list_add(NULL, &version); + + mysqld_compatible_field.str= (char *) "mysqld_compatible"; + mysqld_compatible_field.length= DEFAULT_FIELD_LENGTH; + mysqld_compatible.data= &mysqld_compatible_field; + + field_list= list_add(NULL, &mysqld_compatible); + field_list= list_add(field_list, &version); field_list= list_add(field_list, &version_number); - field_list= list_add(field_list, &status); + field_list= list_add(field_list, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - { - Instance *instance; - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) - goto err; - if (instance->is_running()) - store_to_protocol_packet(&send_buff, (char*) "online", &position); - else - store_to_protocol_packet(&send_buff, (char*) "offline", &position); +int Show_instance_status::write_data(st_net *net, Instance *instance) +{ + Buffer send_buf; /* buffer for packets */ + char version_num_buf[MAX_VERSION_LENGTH]; + uint pos= 0; - if (instance->options.mysqld_version) - { - char parsed_version[MAX_VERSION_LENGTH]; + const char *state_name; + const char *version_tag= "unknown"; + const char *version_num= "unknown"; + const char *mysqld_compatible_status; - parse_version_number(instance->options.mysqld_version, parsed_version, - sizeof(parsed_version)); - store_to_protocol_packet(&send_buff, parsed_version, &position); + instance_map->guardian->lock(); + state_name= instance_map->get_instance_state_name(instance); + mysqld_compatible_status= instance->is_mysqld_compatible() ? "yes" : "no"; + instance_map->guardian->unlock(); - store_to_protocol_packet(&send_buff, instance->options.mysqld_version, - &position); - } - else - { - store_to_protocol_packet(&send_buff, (char*) "unknown", &position); - store_to_protocol_packet(&send_buff, (char*) "unknown", &position); - } + if (instance->options.mysqld_version) + { + if (parse_version_number(instance->options.mysqld_version, version_num_buf, + sizeof(version_num_buf))) + return ER_OUT_OF_RESOURCES; - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + version_num= version_num_buf; + version_tag= instance->options.mysqld_version; } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buf, get_instance_name()->str, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + store_to_protocol_packet(&send_buf, version_num, &pos) || + store_to_protocol_packet(&send_buf, version_tag, &pos) || + store_to_protocol_packet(&send_buf, mysqld_compatible_status, &pos) || + my_net_write(net, send_buf.buffer, (uint) pos)) + { + return ER_OUT_OF_RESOURCES; + } return 0; +} + + +/************************************************************************** + Implementation of Show_instance_options. +**************************************************************************/ -err: - return ER_OUT_OF_RESOURCES; +Show_instance_options::Show_instance_options( + Instance_map *instance_map_arg, const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ } -/* Implementation for Show_instance_options */ +/* + Implementation of SHOW INSTANCE OPTIONS statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ -Show_instance_options::Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len): - Command(instance_map_arg) +int Show_instance_options::execute_impl(st_net *net, Instance *instance) { - Instance *instance; + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} + + +int Show_instance_options::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + return 0; } -int Show_instance_options::execute(struct st_net *net, ulong connection_id) +int Show_instance_options::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name, option; LIST *field_list; - NAME_WITH_LENGTH name_field, option_field; - uint position=0; + LEX_STRING name_field, option_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "option_name"; + name_field.str= (char *) "option_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - option_field.name= (char*) "value"; + + option_field.str= (char *) "value"; option_field.length= DEFAULT_FIELD_LENGTH; option.data= &option_field; + field_list= list_add(NULL, &option); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + + +int Show_instance_options::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + uint pos= 0; + if (store_to_protocol_packet(&send_buff, "instance_name", &pos) || + store_to_protocol_packet(&send_buff, get_instance_name()->str, &pos) || + my_net_write(net, send_buff.buffer, pos)) { - Instance *instance; - - if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) - goto err; - store_to_protocol_packet(&send_buff, (char*) "instance_name", &position); - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - if ((instance->options.mysqld_path)) - { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "mysqld-path", &position); - store_to_protocol_packet(&send_buff, - (char*) instance->options.mysqld_path, - &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + return ER_OUT_OF_RESOURCES; + } - if ((instance->options.nonguarded)) - { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "nonguarded", &position); - store_to_protocol_packet(&send_buff, "", &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + /* Loop through the options. */ - /* loop through the options stored in DYNAMIC_ARRAY */ - for (uint i= 0; i < instance->options.options_array.elements; i++) - { - char *tmp_option, *option_value; - get_dynamic(&(instance->options.options_array), (gptr) &tmp_option, i); - option_value= strchr(tmp_option, '='); - /* split the option string into two parts if it has a value */ + for (int i= 0; i < instance->options.get_num_options(); i++) + { + Named_value option= instance->options.get_option(i); + const char *option_value= option.get_value()[0] ? option.get_value() : ""; - position= 0; - if (option_value != NULL) - { - *option_value= 0; - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, option_value + 1, &position); - /* join name and the value into the same option again */ - *option_value= '='; - } - else - { - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, "", &position); - } + pos= 0; - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + if (store_to_protocol_packet(&send_buff, option.get_name(), &pos) || + store_to_protocol_packet(&send_buff, option_value, &pos) || + my_net_write(net, send_buff.buffer, pos)) + { + return ER_OUT_OF_RESOURCES; } } - if (send_eof(net) || net_flush(net)) - goto err; - return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* Implementation for Start_instance */ +/************************************************************************** + Implementation of Start_instance. +**************************************************************************/ Start_instance::Start_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) { - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; } -int Start_instance::execute(struct st_net *net, ulong connection_id) +/* + Implementation of START INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Start_instance::execute_impl(st_net *net, Instance *instance) { - uint err_code; - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ - else - { - if ((err_code= instance->start())) - return err_code; + int err_code; - if (!(instance->options.nonguarded)) - instance_map->guardian->guard(instance); + if ((err_code= instance->start())) + return err_code; - net_send_ok(net, connection_id, "Instance started"); - return 0; - } + if (!(instance->options.nonguarded)) + instance_map->guardian->guard(instance); + + return 0; } -/* implementation for Show_instance_log: */ +int Start_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance started")) + return ER_OUT_OF_RESOURCES; -Show_instance_log::Show_instance_log(Instance_map *instance_map_arg, - const char *name, uint len, - Log_type log_type_arg, - const char *size_arg, - const char *offset_arg) - :Command(instance_map_arg) + return 0; +} + + +/************************************************************************** + Implementation of Stop_instance. +**************************************************************************/ + +Stop_instance::Stop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) { - Instance *instance; +} + + +/* + Implementation of STOP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Stop_instance::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + return 0; +} + - if (offset_arg != NULL) - offset= atoi(offset_arg); - else - offset= 0; - size= atoi(size_arg); - log_type= log_type_arg; +int Stop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + return 0; } +/************************************************************************** + Implementation for Create_instance. +**************************************************************************/ + +Create_instance::Create_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Command(instance_map_arg), + instance_name(instance_name_arg) +{ +} + /* - Open the logfile, read requested part of the log and send the info - to the client. + This operation initializes Create_instance object. SYNOPSYS - Show_instance_log::execute() - net The network connection to the client. - connection_id Client connection ID + text [IN/OUT] a pointer to the text containing instance options. - DESCRIPTION + RETURN + FALSE On success. + TRUE On error. +*/ + +bool Create_instance::init(const char **text) +{ + return options.init() || parse_args(text); +} + + +/* + This operation parses CREATE INSTANCE options. - Send a table with the content of the log requested. The function also - deals with errro handling, to be verbose. + SYNOPSYS + text [IN/OUT] a pointer to the text containing instance options. RETURN - ER_OFFSET_ERROR We were requested to read negative number of bytes - from the log - ER_NO_SUCH_LOG The kind log being read is not enabled in the instance - ER_GUESS_LOGFILE IM wasn't able to figure out the log placement, while - it is enabled. Probably user should specify the path - to the logfile explicitly. - ER_OPEN_LOGFILE Cannot open the logfile - ER_READ_FILE Cannot read the logfile - ER_OUT_OF_RESOURCES We weren't able to allocate some resources + FALSE On success. + TRUE On syntax error. */ -int Show_instance_log::execute(struct st_net *net, ulong connection_id) +bool Create_instance::parse_args(const char **text) { - Buffer send_buff; /* buffer for packets */ - LIST name; - LIST *field_list; - NAME_WITH_LENGTH name_field; - uint position= 0; + uint len; - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Log"; - name_field.length= DEFAULT_FIELD_LENGTH; - name.data= &name_field; - field_list= list_add(NULL, &name); + /* Check if we have something (and trim leading spaces). */ - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + get_word(text, &len, NONSPACE); - /* cannot read negative number of bytes */ - if (offset > size) - return ER_OFFSET_ERROR; + if (len == 0) + return FALSE; /* OK: no option. */ - send_fields(net, field_list); + /* Main parsing loop. */ + while (TRUE) { - Instance *instance; - const char *logpath; - File fd; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for option name. */ - if ((instance= instance_map->find(instance_name, - strlen(instance_name))) == NULL) - goto err; + get_word(text, &option_name.length, OPTION_NAME); - logpath= instance->options.logs[log_type]; + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ - /* Instance has no such log */ - if (logpath == NULL) - return ER_NO_SUCH_LOG; + option_name.str= (char *) *text; + *text+= option_name.length; - if (*logpath == '\0') - return ER_GUESS_LOGFILE; + /* Looking for equal sign. */ + skip_spaces(text); - if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) >= 0) + if (**text == '=') { - size_t buff_size; - int read_len; - /* calculate buffer size */ - MY_STAT file_stat; - Buffer read_buff; + ++(*text); /* Skip an equal sign. */ - /* my_fstat doesn't use the flag parameter */ - if (my_fstat(fd, &file_stat, MYF(0))) - goto err; + /* Looking for option value. */ - buff_size= (size - offset); + skip_spaces(text); - read_buff.reserve(0, buff_size); + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ - /* read in one chunk */ - read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); + if (**text != '\'' && **text != '"') + { + /* Option value is a simple token. */ - if ((read_len= my_read(fd, (byte*) read_buff.buffer, - buff_size, MYF(0))) < 0) - return ER_READ_FILE; - store_to_protocol_packet(&send_buff, read_buff.buffer, - &position, read_len); - close(fd); - } - else - return ER_OPEN_LOGFILE; + LEX_STRING option_value; - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + get_word(text, &option_value.length, ALPHANUM); - if (send_eof(net) || net_flush(net)) - goto err; + if (option_value.length == 0) + return TRUE; /* internal parser error. */ - return 0; + option_value.str= (char *) *text; + *text+= option_value.length; -err: - return ER_OUT_OF_RESOURCES; -} + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ + } + else + { + /* Option value is a string. */ + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ -/* implementation for Show_instance_log_files: */ + *text+= len; + } + } -Show_instance_log_files::Show_instance_log_files - (Instance_map *instance_map_arg, const char *name, uint len) - :Command(instance_map_arg) -{ - Instance *instance; + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_value_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); + + if (options.add_element(&option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); + } } /* - The method sends a table with a list of log files - used by the instance. + Implementation of CREATE INSTANCE statement. - SYNOPSYS - Show_instance_log_files::execute() - net The network connection to the client. - connection_id The ID of the client connection + Possible error codes: + ER_MALFORMED_INSTANCE_NAME Instance name is malformed + ER_CREATE_EXISTING_INSTANCE There is an instance with the given name + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ - RETURN - ER_BAD_INSTANCE_NAME The instance name specified is not valid - ER_OUT_OF_RESOURCES some error occured - 0 - ok +int Create_instance::execute(st_net *net, ulong connection_id) +{ + int err_code; + + /* Check that the name is valid and there is no instance with such name. */ + + if (!Instance::is_name_valid(get_instance_name())) + return ER_MALFORMED_INSTANCE_NAME; + + /* + NOTE: In order to prevent race condition, we should perform all operations + on under acquired lock. + */ + + instance_map->lock(); + + if (instance_map->find(get_instance_name())) + { + instance_map->unlock(); + return ER_CREATE_EXISTING_INSTANCE; + } + + if ((err_code= instance_map->create_instance(get_instance_name(), &options))) + { + instance_map->unlock(); + return err_code; + } + + if ((err_code= create_instance_in_file(get_instance_name(), &options))) + { + Instance *instance= instance_map->find(get_instance_name()); + + if (instance) + instance_map->remove_instance(instance); /* instance is deleted here. */ + + instance_map->unlock(); + return err_code; + } + + /* That's all. */ + + instance_map->unlock(); + + /* Send the result. */ + + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation for Drop_instance. +**************************************************************************/ + +Drop_instance::Drop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} + + +/* + Implementation of DROP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_DROP_ACTIVE_INSTANCE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instance_log_files::execute(struct st_net *net, ulong connection_id) +int Drop_instance::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + /* Check that the instance is offline. */ + + if (instance_map->guardian->is_active(instance)) + return ER_DROP_ACTIVE_INSTANCE; + + err_code= modify_defaults_file(Options::Main::config_file, NULL, NULL, + get_instance_name()->str, MY_REMOVE_SECTION); + DBUG_ASSERT(err_code >= 0 && err_code <= 2); + + if (err_code) + { + log_error("Can not remove instance '%s' from defaults file (%s). " + "Original error code: %d.", + (const char *) get_instance_name()->str, + (const char *) Options::Main::config_file, + (int) err_code); + } + + if (err_code) + return modify_defaults_to_im_error[err_code]; + + /* Remove instance from the instance map hash and Guardian's list. */ + + if (!instance->options.nonguarded) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + instance_map->remove_instance(instance); + + return 0; +} + + +int Drop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance dropped")) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation for Show_instance_log. +**************************************************************************/ + +Show_instance_log::Show_instance_log(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg, + Log_type log_type_arg, + uint size_arg, uint offset_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg), + log_type(log_type_arg), + size(size_arg), + offset(offset_arg) +{ +} + + +/* + Implementation of SHOW INSTANCE LOG statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OFFSET_ERROR We were requested to read negative number of + bytes from the log + ER_NO_SUCH_LOG The specified type of log is not available for + the given instance + ER_GUESS_LOGFILE IM wasn't able to figure out the log + placement, while it is enabled. Probably user + should specify the path to the logfile + explicitly. + ER_OPEN_LOGFILE Cannot open the logfile + ER_READ_FILE Cannot read the logfile + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Show_instance_log::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= check_params(instance))) + return err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} + + +int Show_instance_log::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_log::check_params(Instance *instance) +{ + const char *logpath= instance->options.logs[log_type]; + + /* Cannot read negative number of bytes. */ + + if (offset > size) + return ER_OFFSET_ERROR; + + /* Instance has no such log. */ + + if (logpath == NULL) + return ER_NO_SUCH_LOG; + + if (*logpath == '\0') + return ER_GUESS_LOGFILE; + + return 0; +} + + +int Show_instance_log::write_header(st_net *net) +{ + LIST name; + LIST *field_list; + LEX_STRING name_field; + + /* Create list of the fields to be passed to send_fields(). */ + + name_field.str= (char *) "Log"; + name_field.length= DEFAULT_FIELD_LENGTH; + + name.data= &name_field; + + field_list= list_add(NULL, &name); + + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + + +int Show_instance_log::write_data(st_net *net, Instance *instance) { Buffer send_buff; /* buffer for packets */ + uint pos= 0; + + const char *logpath= instance->options.logs[log_type]; + File fd; + + size_t buff_size; + int read_len; + + MY_STAT file_stat; + Buffer read_buff; + + if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) <= 0) + return ER_OPEN_LOGFILE; + + /* my_fstat doesn't use the flag parameter */ + if (my_fstat(fd, &file_stat, MYF(0))) + { + close(fd); + return ER_OUT_OF_RESOURCES; + } + + /* calculate buffer size */ + buff_size= (size - offset); + + read_buff.reserve(0, buff_size); + + /* read in one chunk */ + read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); + + if ((read_len= my_read(fd, (byte*) read_buff.buffer, + buff_size, MYF(0))) < 0) + { + close(fd); + return ER_READ_FILE; + } + + close(fd); + + if (store_to_protocol_packet(&send_buff, read_buff.buffer, &pos, read_len) || + my_net_write(net, send_buff.buffer, pos)) + { + return ER_OUT_OF_RESOURCES; + } + + return 0; +} + + +/************************************************************************** + Implementation of Show_instance_log_files. +**************************************************************************/ + +Show_instance_log_files::Show_instance_log_files + (Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} + + +/* + Implementation of SHOW INSTANCE LOG FILES statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Show_instance_log_files::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} + + +int Show_instance_log_files::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_log_files::write_header(st_net *net) +{ LIST name, path, size; LIST *field_list; - NAME_WITH_LENGTH name_field, path_field, size_field; - uint position= 0; + LEX_STRING name_field, path_field, size_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Logfile"; + name_field.str= (char *) "Logfile"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - path_field.name= (char*) "Path"; + + path_field.str= (char *) "Path"; path_field.length= DEFAULT_FIELD_LENGTH; path.data= &path_field; - size_field.name= (char*) "File size"; + + size_field.str= (char *) "File size"; size_field.length= DEFAULT_FIELD_LENGTH; size.data= &size_field; + field_list= list_add(NULL, &size); field_list= list_add(field_list, &path); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - Instance *instance; - if ((instance= instance_map-> - find(instance_name, strlen(instance_name))) == NULL) - goto err; +int Show_instance_log_files::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + /* + We have alike structure in instance_options.cc. We use such to be able + to loop through the options, which we need to handle in some common way. + */ + struct log_files_st + { + const char *name; + const char *value; + } logs[]= { + {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, + {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, + {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, + {NULL, NULL} + }; + struct log_files_st *log_files; + + for (log_files= logs; log_files->name; log_files++) + { + if (!log_files->value) + continue; + + struct stat file_stat; /* - We have alike structure in instance_options.cc. We use such to be able - to loop through the options, which we need to handle in some common way. + Save some more space for the log file names. In fact all + we need is strlen("GENERAL_LOG") + 1 */ - struct log_files_st - { - const char *name; - const char *value; - } logs[]= - { - {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, - {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, - {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, - {NULL, NULL} - }; - struct log_files_st *log_files; - - for (log_files= logs; log_files->name; log_files++) + enum { LOG_NAME_BUFFER_SIZE= 20 }; + char buff[LOG_NAME_BUFFER_SIZE]; + + uint pos= 0; + + const char *log_path= ""; + const char *log_size= "0"; + + if (!stat(log_files->value, &file_stat) && + MY_S_ISREG(file_stat.st_mode)) { - if (log_files->value != NULL) - { - struct stat file_stat; - /* - Save some more space for the log file names. In fact all - we need is srtlen("GENERAL_LOG") + 1 - */ - enum { LOG_NAME_BUFFER_SIZE= 20 }; - char buff[LOG_NAME_BUFFER_SIZE]; - - position= 0; - /* store the type of the log in the send buffer */ - store_to_protocol_packet(&send_buff, log_files->name, &position); - if (stat(log_files->value, &file_stat)) - { - store_to_protocol_packet(&send_buff, "", &position); - store_to_protocol_packet(&send_buff, (char*) "0", &position); - } - else if (MY_S_ISREG(file_stat.st_mode)) - { - store_to_protocol_packet(&send_buff, - (char*) log_files->value, - &position); - int10_to_str(file_stat.st_size, buff, 10); - store_to_protocol_packet(&send_buff, (char*) buff, &position); - } - - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + int10_to_str(file_stat.st_size, buff, 10); + + log_path= log_files->value; + log_size= buff; } - } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buff, log_files->name, &pos) || + store_to_protocol_packet(&send_buff, log_path, &pos) || + store_to_protocol_packet(&send_buff, log_size, &pos) || + my_net_write(net, send_buff.buffer, pos)) + return ER_OUT_OF_RESOURCES; + } return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* implementation for SET instance_name.option=option_value: */ +/************************************************************************** + Implementation of Abstract_option_cmd. +**************************************************************************/ -Set_option::Set_option(Instance_map *instance_map_arg, - const char *name, uint len, - const char *option_arg, uint option_len_arg, - const char *option_value_arg, uint option_value_len_arg) - :Command(instance_map_arg) +/* + Instance_options_list -- a data class representing a list of options for + some instance. +*/ + +class Instance_options_list { - Instance *instance; +public: + Instance_options_list(const LEX_STRING *instance_name_arg); - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - { - instance_name= instance->options.instance_name; +public: + bool init(); - /* add prefix for add_option */ - if ((option_len_arg < MAX_OPTION_LEN - 1) || - (option_value_len_arg < MAX_OPTION_LEN - 1)) - { - strmake(option, option_arg, option_len_arg); - strmake(option_value, option_value_arg, option_value_len_arg); - } - else - { - option[0]= 0; - option_value[0]= 0; - } - instance_name_len= len; - } - else + const LEX_STRING *get_instance_name() const { - instance_name= NULL; - instance_name_len= 0; + return instance_name.get_str(); } + +public: + /* + This member is set and used only in Abstract_option_cmd::execute_impl(). + Normally it is not used (and should not). + + The problem is that construction and execution of commands are made not + in one transaction (not under one lock session). So, we can not initialize + instance in constructor and use it in execution. + */ + Instance *instance; + + Named_value_arr options; + +private: + Instance_name instance_name; +}; + + +/**************************************************************************/ + +Instance_options_list::Instance_options_list( + const LEX_STRING *instance_name_arg) + :instance(NULL), + instance_name(instance_name_arg) +{ } -/* - The method sends a table with a list of log files - used by the instance. +bool Instance_options_list::init() +{ + return options.init(); +} - SYNOPSYS - Set_option::correct_file() - skip Skip the option, being searched while writing the result file. - That is, to delete it. - DESCRIPTION +/**************************************************************************/ + +C_MODE_START + +static byte* get_item_key(const byte* item, uint* len, + my_bool __attribute__((unused)) t) +{ + const Instance_options_list *lst= (const Instance_options_list *) item; + *len= lst->get_instance_name()->length; + return (byte *) lst->get_instance_name()->str; +} + +static void delete_item(void *item) +{ + delete (Instance_options_list *) item; +} + +C_MODE_END + + +/**************************************************************************/ + +Abstract_option_cmd::Abstract_option_cmd(Instance_map *instance_map_arg) + :Command(instance_map_arg), + initialized(FALSE) +{ +} + + +Abstract_option_cmd::~Abstract_option_cmd() +{ + if (initialized) + hash_free(&instance_options_map); +} + +bool Abstract_option_cmd::add_option(const LEX_STRING *instance_name, + Named_value *option) +{ + Instance_options_list *lst= get_instance_options_list(instance_name); + + if (!lst) + return TRUE; + + lst->options.add_element(option); + + return FALSE; +} + + +bool Abstract_option_cmd::init(const char **text) +{ + static const int INITIAL_HASH_SIZE= 16; + + if (hash_init(&instance_options_map, default_charset_info, + INITIAL_HASH_SIZE, 0, 0, get_item_key, delete_item, 0)) + return TRUE; + + if (parse_args(text)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +/* Correct the option file. The "skip" option is used to remove the found option. + SYNOPSYS + Abstract_option_cmd::correct_file() + skip Skip the option, being searched while writing the result file. + That is, to delete it. + RETURN - ER_OUT_OF_RESOURCES out of resources + 0 Success + ER_OUT_OF_RESOURCES Not enough resources to complete the operation ER_ACCESS_OPTION_FILE Cannot access the option file - 0 - ok */ -int Set_option::correct_file(int skip) +int Abstract_option_cmd::correct_file(Instance *instance, Named_value *option, + bool skip) { - static const int mysys_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, - ER_ACCESS_OPTION_FILE }; - int error; + int err_code= modify_defaults_file(Options::Main::config_file, + option->get_name(), + option->get_value(), + instance->get_name()->str, + skip); + + DBUG_ASSERT(err_code >= 0 && err_code <= 2); - error= modify_defaults_file(Options::config_file, option, - option_value, instance_name, skip); - DBUG_ASSERT(error >= 0 && error <= 2); + if (err_code) + { + log_error("Can not modify option (%s) in defaults file (%s). " + "Original error code: %d.", + (const char *) option->get_name(), + (const char *) Options::Main::config_file, + (int) err_code); + } - return mysys_to_im_error[error]; + return modify_defaults_to_im_error[err_code]; } /* - The method sets an option in the the default config file (/etc/my.cnf). - - SYNOPSYS - Set_option::do_command() - net The network connection to the client. - - RETURN - 0 - ok - 1 - error occured + Implementation of SET statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_INCOMPATIBLE_OPTION The specified option can not be set for + mysqld-compatible instance + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Set_option::do_command(struct st_net *net) +int Abstract_option_cmd::execute(st_net *net, ulong connection_id) { - int error; + int err_code; - /* we must hold the instance_map mutex while changing config file */ instance_map->lock(); - error= correct_file(FALSE); + + err_code= execute_impl(net, connection_id); + instance_map->unlock(); - return error; + return err_code; } -int Set_option::execute(struct st_net *net, ulong connection_id) +Instance_options_list * +Abstract_option_cmd::get_instance_options_list(const LEX_STRING *instance_name) { - if (instance_name != NULL) + Instance_options_list *lst= + (Instance_options_list *) hash_search(&instance_options_map, + (byte *) instance_name->str, + instance_name->length); + + if (!lst) { - int val; + lst= new Instance_options_list(instance_name); - val= do_command(net); + if (!lst) + return NULL; + + if (lst->init() || my_hash_insert(&instance_options_map, (byte *) lst)) + { + delete lst; + return NULL; + } + } + + return lst; +} + + +int Abstract_option_cmd::execute_impl(st_net *net, ulong connection_id) +{ + int err_code; - if (val == 0) - net_send_ok(net, connection_id, NULL); + /* Check that all the specified instances exist and are offline. */ - return val; + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + lst->instance= instance_map->find(lst->get_instance_name()); + + if (!lst->instance) + return ER_BAD_INSTANCE_NAME; + + if (instance_map->guardian->is_active(lst->instance)) + return ER_INSTANCE_IS_ACTIVE; + } + + /* Perform command-specific (SET/UNSET) actions. */ + + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + for (int j= 0; j < lst->options.get_size(); ++j) + { + Named_value option= lst->options.get_element(j); + err_code= process_option(lst->instance, &option); + + if (err_code) + break; + } + + if (err_code) + break; } - return ER_BAD_INSTANCE_NAME; + if (err_code == 0) + net_send_ok(net, connection_id, NULL); + + return err_code; } -/* the only function from Unset_option we need to Implement */ +/************************************************************************** + Implementation of Set_option. +**************************************************************************/ -int Unset_option::do_command(struct st_net *net) +Set_option::Set_option(Instance_map *instance_map_arg) + :Abstract_option_cmd(instance_map_arg) { - return correct_file(TRUE); } -/* Implementation for Stop_instance: */ +/* + This operation parses SET options. -Stop_instance::Stop_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) + SYNOPSYS + text [IN/OUT] a pointer to the text containing options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Set_option::parse_args(const char **text) { - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; + uint len; + + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + /* Looking for equal sign. */ + + skip_spaces(text); + + if (**text == '=') + { + ++(*text); /* Skip an equal sign. */ + + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ + + if (**text != '\'' && **text != '"') + { + /* Option value is a simple token. */ + + LEX_STRING option_value; + + get_word(text, &option_value.length, ALPHANUM); + + if (option_value.length == 0) + return TRUE; /* internal parser error. */ + + option_value.str= (char *) *text; + *text+= option_value.length; + + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ + } + else + { + /* Option value is a string. */ + + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ + + *text+= len; + } + } + + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_name_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } } -int Stop_instance::execute(struct st_net *net, ulong connection_id) +int Set_option::process_option(Instance *instance, Named_value *option) { - uint err_code; + /* Check that the option is valid. */ - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option->get_name())) + { + log_error("Error: IM-option (%s) can not be used " + "in the configuration of mysqld-compatible instance (%s).", + (const char *) option->get_name(), + (const char *) instance->get_name()->str); + return ER_INCOMPATIBLE_OPTION; + } - if (!(instance->options.nonguarded)) - instance_map->guardian->stop_guard(instance); + /* Update the configuration file. */ - if ((err_code= instance->stop())) + int err_code= correct_file(instance, option, FALSE); + + if (err_code) return err_code; - net_send_ok(net, connection_id, NULL); + /* Update the internal cache. */ + + if (instance->options.set_option(option)) + return ER_OUT_OF_RESOURCES; + return 0; } -int Syntax_error::execute(struct st_net *net, ulong connection_id) +/************************************************************************** + Implementation of Unset_option. +**************************************************************************/ + +Unset_option::Unset_option(Instance_map *instance_map_arg) + :Abstract_option_cmd(instance_map_arg) +{ +} + + +/* + This operation parses UNSET options. + + SYNOPSYS + text [IN/OUT] a pointer to the text containing options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Unset_option::parse_args(const char **text) +{ + uint len; + + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); /* Skip a dot. */ + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + return TRUE; /* out of memory during parsing. */ + + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } +} + + +/* + Implementation of UNSET statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance name specified is not valid + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Unset_option::process_option(Instance *instance, Named_value *option) +{ + /* Update the configuration file. */ + + int err_code= correct_file(instance, option, TRUE); + + if (err_code) + return err_code; + + /* Update the internal cache. */ + + instance->options.unset_option(option->get_name()); + + return 0; +} + + +/************************************************************************** + Implementation of Syntax_error. +**************************************************************************/ + +int Syntax_error::execute(st_net *net, ulong connection_id) { return ER_SYNTAX_ERROR; } diff --git a/server-tools/instance-manager/commands.h b/server-tools/instance-manager/commands.h index bfd38d348890d38427e4bf6f6b6c713746716c1e..9a9911f2358deefc0ef7a79d264819a65b8a05d9 100644 --- a/server-tools/instance-manager/commands.h +++ b/server-tools/instance-manager/commands.h @@ -16,10 +16,20 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include <hash.h> + #include "command.h" #include "instance.h" #include "parse.h" +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + + /* Print all instances of this instance manager. Grammar: SHOW ISTANCES @@ -31,12 +41,16 @@ class Show_instances : public Command Show_instances(Instance_map *instance_map_arg): Command(instance_map_arg) {} - int execute(struct st_net *net, ulong connection_id); + int execute(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net); }; /* - Reread configuration file and refresh instance map. + Reread configuration file and refresh internal cache. Grammar: FLUSH INSTANCES */ @@ -46,7 +60,43 @@ class Flush_instances : public Command Flush_instances(Instance_map *instance_map_arg): Command(instance_map_arg) {} - int execute(struct st_net *net, ulong connection_id); + int execute(st_net *net, ulong connection_id); +}; + + +/* + Abstract class for Instance-specific commands. +*/ + +class Abstract_instance_cmd : public Command +{ +public: + Abstract_instance_cmd(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +public: + virtual int execute(st_net *net, ulong connection_id); + +protected: + /* MT-NOTE: this operation is called under acquired Instance_map's lock. */ + virtual int execute_impl(st_net *net, Instance *instance) = 0; + + /* + This operation is invoked on successful return of execute_impl() and is + intended to send closing data. + + MT-NOTE: this operation is called under released Instance_map's lock. + */ + virtual int send_ok_response(st_net *net, ulong connection_id) = 0; + +protected: + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + Instance_name instance_name; }; @@ -55,31 +105,40 @@ class Flush_instances : public Command Grammar: SHOW ISTANCE STATUS <instance_name> */ -class Show_instance_status : public Command +class Show_instance_status : public Abstract_instance_cmd { public: - Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Print options if chosen instance. + Print options of chosen instance. Grammar: SHOW INSTANCE OPTIONS <instance_name> */ -class Show_instance_options : public Command +class Show_instance_options : public Abstract_instance_cmd { public: - Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len); + const LEX_STRING *instance_name_arg); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; @@ -88,14 +147,15 @@ class Show_instance_options : public Command Grammar: START INSTANCE <instance_name> */ -class Start_instance : public Command +class Start_instance : public Abstract_instance_cmd { public: - Start_instance(Instance_map *instance_map_arg, const char *name, uint len); + Start_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; - Instance *instance; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; @@ -104,33 +164,95 @@ class Start_instance : public Command Grammar: STOP INSTANCE <instance_name> */ -class Stop_instance : public Command +class Stop_instance : public Abstract_instance_cmd { public: - Stop_instance(Instance_map *instance_map_arg, const char *name, uint len); + Stop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); - Instance *instance; - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; /* - Print requested part of the log + Create an instance. + Grammar: CREATE INSTANCE <instance_name> [<options>] +*/ + +class Create_instance : public Command +{ +public: + Create_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +public: + bool init(const char **text); + +protected: + virtual int execute(st_net *net, ulong connection_id); + + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + bool parse_args(const char **text); + +private: + Instance_name instance_name; + + Named_value_arr options; +}; + + +/* + Drop an instance. + Grammar: DROP INSTANCE <instance_name> + + Operation is permitted only if the instance is stopped. On successful + completion the instance section is removed from config file and the instance + is removed from the instance map. +*/ + +class Drop_instance : public Abstract_instance_cmd +{ +public: + Drop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); +}; + + +/* + Print requested part of the log. Grammar: - SHOW <instance_name> log {ERROR | SLOW | GENERAL} size[, offset_from_end] + SHOW <instance_name> LOG {ERROR | SLOW | GENERAL} size[, offset_from_end] */ -class Show_instance_log : public Command +class Show_instance_log : public Abstract_instance_cmd { public: + Show_instance_log(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg, + Log_type log_type_arg, uint size_arg, uint offset_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int check_params(Instance *instance); + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); - Show_instance_log(Instance_map *instance_map_arg, const char *name, - uint len, Log_type log_type_arg, const char *size_arg, - const char *offset_arg); - int execute(struct st_net *net, ulong connection_id); +private: Log_type log_type; - const char *instance_name; uint size; uint offset; }; @@ -141,75 +263,112 @@ class Show_instance_log : public Command Grammar: SHOW <instance_name> LOG FILES */ -class Show_instance_log_files : public Command +class Show_instance_log_files : public Abstract_instance_cmd { public: - Show_instance_log_files(Instance_map *instance_map_arg, - const char *name, uint len); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; - const char *option; + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Syntax error command. This command is issued if parser reported a syntax - error. We need it to distinguish the parse error and the situation when - parser internal error occured. E.g. parsing failed because we hadn't had - enought memory. In the latter case parse_command() should return an error. + Abstract class for option-management commands. */ -class Syntax_error : public Command +class Instance_options_list; + +class Abstract_option_cmd : public Command { public: - int execute(struct st_net *net, ulong connection_id); + ~Abstract_option_cmd(); + +public: + bool add_option(const LEX_STRING *instance_name, Named_value *option); + +public: + bool init(const char **text); + + virtual int execute(st_net *net, ulong connection_id); + +protected: + Abstract_option_cmd(Instance_map *instance_map_arg); + + int correct_file(Instance *instance, Named_value *option, bool skip); + +protected: + virtual bool parse_args(const char **text) = 0; + virtual int process_option(Instance *instance, Named_value *option) = 0; + +private: + Instance_options_list * + get_instance_options_list(const LEX_STRING *instance_name); + + int execute_impl(st_net *net, ulong connection_id); + +private: + HASH instance_options_map; + bool initialized; }; + /* Set an option for the instance. - Grammar: SET instance_name.option=option_value + Grammar: SET instance_name.option[=option_value][, ...] */ -class Set_option : public Command +class Set_option : public Abstract_option_cmd { public: - Set_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len); - /* - the following function is virtual to let Unset_option to use - */ - virtual int do_command(struct st_net *net); - int execute(struct st_net *net, ulong connection_id); + Set_option(Instance_map *instance_map_arg); + protected: - int correct_file(int skip); -public: - const char *instance_name; - uint instance_name_len; - /* buffer for the option */ - enum { MAX_OPTION_LEN= 1024 }; - char option[MAX_OPTION_LEN]; - char option_value[MAX_OPTION_LEN]; + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; /* - Remove option of the instance from config file - Grammar: UNSET instance_name.option + Remove option of the instance. + Grammar: UNSET instance_name.option[, ...] */ -class Unset_option: public Set_option +class Unset_option: public Abstract_option_cmd { public: - Unset_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len): - Set_option(instance_map_arg, name, len, option_arg, option_len, - option_value_arg, option_value_len) - {} - int do_command(struct st_net *net); + Unset_option(Instance_map *instance_map_arg); + +protected: + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; +/* + Syntax error command. + + This command is issued if parser reported a syntax error. We need it to + distinguish between syntax error and internal parser error. E.g. parsing + failed because we hadn't had enought memory. In the latter case the parser + just returns NULL. +*/ + +class Syntax_error : public Command +{ +public: + /* This is just to avoid compiler warning. */ + Syntax_error() :Command(NULL) + {} + +public: + int execute(st_net *net, ulong connection_id); +}; + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H */ diff --git a/server-tools/instance-manager/exit_codes.h b/server-tools/instance-manager/exit_codes.h new file mode 100644 index 0000000000000000000000000000000000000000..560ce30b7aa09e4cb09575b7650762e2efcecee4 --- /dev/null +++ b/server-tools/instance-manager/exit_codes.h @@ -0,0 +1,41 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H + +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + This file contains a list of exit codes, which are used when Instance + Manager is working in user-management mode. +*/ + +const int ERR_OK = 0; + +const int ERR_OUT_OF_MEMORY = 1; +const int ERR_INVALID_USAGE = 2; +const int ERR_INTERNAL_ERROR = 3; +const int ERR_IO_ERROR = 4; +const int ERR_PASSWORD_FILE_CORRUPTED = 5; +const int ERR_PASSWORD_FILE_DOES_NOT_EXIST = 6; + +const int ERR_CAN_NOT_READ_USER_NAME = 10; +const int ERR_CAN_NOT_READ_PASSWORD = 11; +const int ERR_USER_ALREADY_EXISTS = 12; +const int ERR_USER_NOT_FOUND = 13; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H diff --git a/server-tools/instance-manager/guardian.cc b/server-tools/instance-manager/guardian.cc index 3be672cd71c423ebe8fc8ea0b929ca1530961519..7a9ff3e8367d256f1cf84a1d2816d0f4e558ae83 100644 --- a/server-tools/instance-manager/guardian.cc +++ b/server-tools/instance-manager/guardian.cc @@ -21,16 +21,14 @@ #include "guardian.h" -#include "instance_map.h" -#include "instance.h" -#include "mysql_manager_error.h" -#include "log.h" -#include "portability.h" - #include <string.h> #include <sys/types.h> #include <signal.h> +#include "instance.h" +#include "instance_map.h" +#include "log.h" +#include "mysql_manager_error.h" pthread_handler_t guardian(void *arg) @@ -40,6 +38,37 @@ pthread_handler_t guardian(void *arg) return 0; } + +const char * +Guardian_thread::get_instance_state_name(enum_instance_state state) +{ + switch (state) { + case NOT_STARTED: + return "offline"; + + case STARTING: + return "starting"; + + case STARTED: + return "online"; + + case JUST_CRASHED: + return "failed"; + + case CRASHED: + return "crashed"; + + case CRASHED_AND_ABANDONED: + return "abandoned"; + + case STOPPING: + return "stopping"; + } + + return NULL; /* just to ignore compiler warning. */ +} + + Guardian_thread::Guardian_thread(Thread_registry &thread_registry_arg, Instance_map *instance_map_arg, uint monitoring_interval_arg) : @@ -89,10 +118,17 @@ void Guardian_thread::process_instance(Instance *instance, if (current_node->state == STOPPING) { /* this brach is executed during shutdown */ - if (instance->options.shutdown_delay_val) + if (instance->options.shutdown_delay) + { + /* + NOTE: it is important to check shutdown_delay here, but use + shutdown_delay_val. The idea is that if the option is unset, + shutdown_delay will be NULL, but shutdown_delay_val will not be reset. + */ waitchild= instance->options.shutdown_delay_val; + } - /* this returns true if and only if an instance was stopped for sure */ + /* this returns TRUE if and only if an instance was stopped for sure */ if (instance->is_crashed()) *guarded_instances= list_delete(*guarded_instances, node); else if ( (uint) (current_time - current_node->last_checked) > waitchild) @@ -159,7 +195,11 @@ void Guardian_thread::process_instance(Instance *instance, instance->options.instance_name); } else + { + log_info("guardian: cannot start instance %s. Abandoning attempts " + "to (re)start it", instance->options.instance_name); current_node->state= CRASHED_AND_ABANDONED; + } } break; case CRASHED_AND_ABANDONED: @@ -242,7 +282,9 @@ int Guardian_thread::is_stopped() SYNOPSYS Guardian_thread::init() - NOTE: One should always lock guardian before calling this routine. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian_thread; + - Instance_map; RETURN 0 - ok @@ -261,12 +303,11 @@ int Guardian_thread::init() while ((instance= iterator.next())) { - if (!(instance->options.nonguarded)) - if (guard(instance, TRUE)) /* do not lock guardian */ - { - instance_map->unlock(); - return 1; - } + if (instance->options.nonguarded) + continue; + + if (guard(instance, TRUE)) /* do not lock guardian */ + return 1; } return 0; @@ -334,24 +375,14 @@ int Guardian_thread::stop_guard(Instance *instance) LIST *node; pthread_mutex_lock(&LOCK_guardian); - node= guarded_instances; - while (node != NULL) - { - /* - We compare only pointers, as we always use pointers from the - instance_map's MEM_ROOT. - */ - if (((GUARD_NODE *) node->data)->instance == instance) - { - guarded_instances= list_delete(guarded_instances, node); - pthread_mutex_unlock(&LOCK_guardian); - return 0; - } - else - node= node->next; - } + node= find_instance_node(instance); + + if (node != NULL) + guarded_instances= list_delete(guarded_instances, node); + pthread_mutex_unlock(&LOCK_guardian); + /* if there is nothing to delete it is also fine */ return 0; } @@ -420,7 +451,7 @@ int Guardian_thread::stop_instances(bool stop_instances_arg) void Guardian_thread::lock() { - pthread_mutex_lock(&LOCK_guardian); + pthread_mutex_lock(&LOCK_guardian); } @@ -428,3 +459,41 @@ void Guardian_thread::unlock() { pthread_mutex_unlock(&LOCK_guardian); } + + +LIST *Guardian_thread::find_instance_node(Instance *instance) +{ + LIST *node= guarded_instances; + + while (node != NULL) + { + /* + We compare only pointers, as we always use pointers from the + instance_map's MEM_ROOT. + */ + if (((GUARD_NODE *) node->data)->instance == instance) + return node; + + node= node->next; + } + + return NULL; +} + + +bool Guardian_thread::is_active(Instance *instance) +{ + bool guarded; + + lock(); + + guarded= find_instance_node(instance) != NULL; + + /* is_running() can take a long time, so let's unlock mutex first. */ + unlock(); + + if (guarded) + return true; + + return instance->is_running(); +} diff --git a/server-tools/instance-manager/guardian.h b/server-tools/instance-manager/guardian.h index 16b4c373c91b7f5ee1d801482592054b30220f07..6d3a2b222d77ea08e1a3a3d2ee8a47f194971f27 100644 --- a/server-tools/instance-manager/guardian.h +++ b/server-tools/instance-manager/guardian.h @@ -17,11 +17,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> -#include "thread_registry.h" - #include <my_sys.h> #include <my_list.h> +#include "thread_registry.h" + #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif @@ -79,6 +79,8 @@ class Guardian_thread: public Guardian_thread_args time_t last_checked; }; + /* Return client state name. */ + static const char *get_instance_state_name(enum_instance_state state); Guardian_thread(Thread_registry &thread_registry_arg, Instance_map *instance_map_arg, @@ -94,11 +96,28 @@ class Guardian_thread: public Guardian_thread_args int guard(Instance *instance, bool nolock= FALSE); /* Stop instance protection */ int stop_guard(Instance *instance); - /* Returns true if guardian thread is stopped */ + /* Returns TRUE if guardian thread is stopped */ int is_stopped(); void lock(); void unlock(); + /* + Return an internal list node for the given instance if the instance is + managed by Guardian. Otherwise, return NULL. + + MT-NOTE: must be called under acquired lock. + */ + LIST *find_instance_node(Instance *instance); + + /* The operation is used to check if the instance is active or not. */ + bool is_active(Instance *instance); + + /* + Return state of the given instance list node. The pointer must specify + a valid list node. + */ + inline enum_instance_state get_instance_state(LIST *instance_node); + public: pthread_cond_t COND_guardian; @@ -108,6 +127,7 @@ class Guardian_thread: public Guardian_thread_args /* check instance state and act accordingly */ void process_instance(Instance *instance, GUARD_NODE *current_node, LIST **guarded_instances, LIST *elem); + int stopped; private: @@ -115,9 +135,15 @@ class Guardian_thread: public Guardian_thread_args Thread_info thread_info; LIST *guarded_instances; MEM_ROOT alloc; - enum { MEM_ROOT_BLOCK_SIZE= 512 }; /* this variable is set to TRUE when we want to stop Guardian thread */ bool shutdown_requested; }; + +inline Guardian_thread::enum_instance_state +Guardian_thread::get_instance_state(LIST *instance_node) +{ + return ((GUARD_NODE *) instance_node->data)->state; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */ diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc index 39381b457aba79d4d06a4bbafc2dcb84cd7aae85..456052bf7e56de5b5be54a6cfc6e6847c9e51172 100644 --- a/server-tools/instance-manager/instance.cc +++ b/server-tools/instance-manager/instance.cc @@ -20,18 +20,27 @@ #include "instance.h" -#include "mysql_manager_error.h" -#include "log.h" -#include "instance_map.h" -#include "priv.h" -#include "portability.h" +#include <my_global.h> +#include <mysql.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif -#include <my_sys.h> -#include <signal.h> -#include <m_string.h> -#include <mysql.h> + +#include "guardian.h" +#include "instance_map.h" +#include "log.h" +#include "mysql_manager_error.h" +#include "portability.h" +#include "priv.h" + + +const LEX_STRING +Instance::DFLT_INSTANCE_NAME= { C_STRING_WITH_SIZE("mysqld") }; + +static const char * const INSTANCE_NAME_PREFIX= Instance::DFLT_INSTANCE_NAME.str; +static const int INSTANCE_NAME_PREFIX_LEN= Instance::DFLT_INSTANCE_NAME.length; static void start_and_monitor_instance(Instance_options *old_instance_options, @@ -152,7 +161,7 @@ static int start_process(Instance_options *instance_options, switch (*pi) { case 0: /* never happens on QNX */ - execv(instance_options->mysqld_path, instance_options->argv); + execv(instance_options->mysqld_path.str, instance_options->argv); /* exec never returns */ exit(1); case -1: @@ -180,7 +189,7 @@ static int start_process(Instance_options *instance_options, char *cmdline= new char[cmdlen]; if (cmdline == NULL) return 1; - + cmdline[0]= 0; for (int i= 0; instance_options->argv[i] != 0; i++) { @@ -232,9 +241,7 @@ static int start_process(Instance_options *instance_options, static void start_and_monitor_instance(Instance_options *old_instance_options, Instance_map *instance_map) { - enum { MAX_INSTANCE_NAME_LEN= 512 }; - char instance_name_buff[MAX_INSTANCE_NAME_LEN]; - uint instance_name_len; + Instance_name instance_name(&old_instance_options->instance_name); Instance *current_instance; My_process_info process_info; @@ -248,11 +255,8 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, Save the instance name in the case if Instance object we are using is destroyed. (E.g. by "FLUSH INSTANCES") */ - strmake(instance_name_buff, old_instance_options->instance_name, - MAX_INSTANCE_NAME_LEN - 1); - instance_name_len= old_instance_options->instance_name_len; - log_info("starting instance %s", instance_name_buff); + log_info("starting instance %s", (const char *) instance_name.get_c_str()); if (start_process(old_instance_options, &process_info)) { @@ -266,15 +270,36 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, /* don't check for return value */ wait_process(&process_info); - current_instance= instance_map->find(instance_name_buff, instance_name_len); + instance_map->lock(); + + current_instance= instance_map->find(instance_name.get_str()); if (current_instance) current_instance->set_crash_flag_n_wake_all(); + instance_map->unlock(); + return; } +bool Instance::is_name_valid(const LEX_STRING *name) +{ + const char *name_suffix= name->str + INSTANCE_NAME_PREFIX_LEN; + + if (strncmp(name->str, INSTANCE_NAME_PREFIX, INSTANCE_NAME_PREFIX_LEN) != 0) + return FALSE; + + return *name_suffix == 0 || my_isdigit(default_charset_info, *name_suffix); +} + + +bool Instance::is_mysqld_compatible_name(const LEX_STRING *name) +{ + return strcmp(name->str, INSTANCE_NAME_PREFIX) == 0; +} + + Instance_map *Instance::get_map() { return instance_map; @@ -309,11 +334,11 @@ int Instance::start() { /* clear crash flag */ pthread_mutex_lock(&LOCK_instance); - crashed= 0; + crashed= FALSE; pthread_mutex_unlock(&LOCK_instance); - if (!is_running()) + if (configured && !is_running()) { remove_pid(); @@ -339,8 +364,8 @@ int Instance::start() return 0; } - /* the instance is started already */ - return ER_INSTANCE_ALREADY_STARTED; + /* The instance is started already or misconfigured. */ + return configured ? ER_INSTANCE_ALREADY_STARTED : ER_INSTANCE_MISCONFIGURED; } /* @@ -363,7 +388,7 @@ void Instance::set_crash_flag_n_wake_all() { /* set instance state to crashed */ pthread_mutex_lock(&LOCK_instance); - crashed= 1; + crashed= TRUE; pthread_mutex_unlock(&LOCK_instance); /* @@ -378,7 +403,7 @@ void Instance::set_crash_flag_n_wake_all() -Instance::Instance(): crashed(0) +Instance::Instance(): crashed(FALSE), configured(FALSE) { pthread_mutex_init(&LOCK_instance, 0); pthread_cond_init(&COND_instance_stopped, 0); @@ -392,9 +417,9 @@ Instance::~Instance() } -int Instance::is_crashed() +bool Instance::is_crashed() { - int val; + bool val; pthread_mutex_lock(&LOCK_instance); val= crashed; pthread_mutex_unlock(&LOCK_instance); @@ -413,10 +438,17 @@ bool Instance::is_running() bool return_val; if (options.mysqld_port) + { + /* + NOTE: it is important to check mysqld_port here, but use + mysqld_port_val. The idea is that if the option is unset, mysqld_port + will be NULL, but mysqld_port_val will not be reset. + */ port= options.mysqld_port_val; + } if (options.mysqld_socket) - socket= strchr(options.mysqld_socket, '=') + 1; + socket= options.mysqld_socket; /* no port was specified => instance falled back to default value */ if (!options.mysqld_port && !options.mysqld_socket) @@ -469,8 +501,15 @@ int Instance::stop() struct timespec timeout; uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; - if (options.shutdown_delay_val) + if (options.shutdown_delay) + { + /* + NOTE: it is important to check shutdown_delay here, but use + shutdown_delay_val. The idea is that if the option is unset, + shutdown_delay will be NULL, but shutdown_delay_val will not be reset. + */ waitchild= options.shutdown_delay_val; + } kill_instance(SIGTERM); /* sleep on condition to wait for SIGCHLD */ @@ -588,20 +627,33 @@ void Instance::kill_instance(int signum) } /* - We execute this function to initialize instance parameters. - Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. + Initialize instance parameters. + + SYNOPSYS + Instance::init() + name_arg name of the instance + + RETURN: + 0 ok + !0 error */ -int Instance::init(const char *name_arg) +int Instance::init(const LEX_STRING *name_arg) { + mysqld_compatible= is_mysqld_compatible_name(name_arg); + return options.init(name_arg); } int Instance::complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, - uint instance_type) + const char *mysqld_path) { instance_map= instance_map_arg; - return options.complete_initialization(mysqld_path, instance_type); + configured= !options.complete_initialization(mysqld_path); + return 0; + /* + TODO: return actual status (from + Instance_options::complete_initialization()) here. + */ } diff --git a/server-tools/instance-manager/instance.h b/server-tools/instance-manager/instance.h index adb669916853346e61c860902fd051e694b18a39..1f06cabebf72089ca9e456a0b138c6d5b4bd93f0 100644 --- a/server-tools/instance-manager/instance.h +++ b/server-tools/instance-manager/instance.h @@ -17,7 +17,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> +#include <m_string.h> + #include "instance_options.h" +#include "priv.h" #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface @@ -25,31 +28,120 @@ class Instance_map; + +/* + Instance_name -- the class represents instance name -- a string of length + less than MAX_INSTANCE_NAME_SIZE. + + Generally, this is just a string with self-memory-management and should be + eliminated in the future. +*/ + +class Instance_name +{ +public: + Instance_name(const LEX_STRING *name); + +public: + inline const LEX_STRING *get_str() const + { + return &str; + } + + inline const char *get_c_str() const + { + return str.str; + } + + inline uint get_length() const + { + return str.length; + } + +private: + LEX_STRING str; + char str_buffer[MAX_INSTANCE_NAME_SIZE]; +}; + + class Instance { +public: + /* + The following two constants defines name of the default mysqld-instance + ("mysqld"). + */ + static const LEX_STRING DFLT_INSTANCE_NAME; + +public: + /* + The operation is intended to check whether string is a well-formed + instance name or not. + */ + static bool is_name_valid(const LEX_STRING *name); + + /* + The operation is intended to check if the given instance name is + mysqld-compatible or not. + */ + static bool is_mysqld_compatible_name(const LEX_STRING *name); + public: Instance(); ~Instance(); - int init(const char *name); + int init(const LEX_STRING *name_arg); int complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, uint instance_type); + const char *mysqld_path); bool is_running(); int start(); int stop(); /* send a signal to the instance */ void kill_instance(int signo); - int is_crashed(); + bool is_crashed(); void set_crash_flag_n_wake_all(); Instance_map *get_map(); + /* + The operation is intended to check if the instance is mysqld-compatible + or not. + */ + inline bool is_mysqld_compatible() const; + + /* + The operation is intended to check if the instance is configured properly + or not. Misconfigured instances are not managed. + */ + inline bool is_configured() const; + + inline const LEX_STRING *get_name() const; + public: enum { DEFAULT_SHUTDOWN_DELAY= 35 }; Instance_options options; private: - int crashed; + /* This attributes is a flag, specifies if the instance has been crashed. */ + bool crashed; + + /* + This attribute specifies if the instance is configured properly or not. + Misconfigured instances are not managed. + */ + bool configured; + + /* + This attribute specifies whether the instance is mysqld-compatible or not. + Mysqld-compatible instances can contain only mysqld-specific options. + At the moment an instance is mysqld-compatible if its name is "mysqld". + + The idea is that [mysqld] section should contain only mysqld-specific + options (no Instance Manager-specific options) to be readable by mysqld + program. + */ + bool mysqld_compatible; + /* Mutex protecting the instance. Currently we use it to avoid the double start of the instance. This happens when the instance is starting @@ -66,4 +158,22 @@ class Instance void remove_pid(); }; + +inline bool Instance::is_mysqld_compatible() const +{ + return mysqld_compatible; +} + + +inline bool Instance::is_configured() const +{ + return configured; +} + + +inline const LEX_STRING *Instance::get_name() const +{ + return &options.instance_name; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H */ diff --git a/server-tools/instance-manager/instance_map.cc b/server-tools/instance-manager/instance_map.cc index 543c9c31bfa2b2797f2c35def680623f24a7deea..c9608fa7c140ad1ba473ece0fbae48785e196576 100644 --- a/server-tools/instance-manager/instance_map.cc +++ b/server-tools/instance-manager/instance_map.cc @@ -20,14 +20,19 @@ #include "instance_map.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql_com.h> + #include "buffer.h" +#include "guardian.h" #include "instance.h" #include "log.h" +#include "manager.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" #include "options.h" - -#include <m_ctype.h> -#include <mysql_com.h> -#include <m_string.h> +#include "priv.h" /* Note: As we are going to suppost different types of connections, @@ -45,8 +50,8 @@ static byte* get_instance_key(const byte* u, uint* len, my_bool __attribute__((unused)) t) { const Instance *instance= (const Instance *) u; - *len= instance->options.instance_name_len; - return (byte *) instance->options.instance_name; + *len= instance->options.instance_name.length; + return (byte *) instance->options.instance_name.str; } static void delete_instance(void *u) @@ -79,15 +84,59 @@ static void delete_instance(void *u) static int process_option(void *ctx, const char *group, const char *option) { - Instance_map *map= NULL; + Instance_map *map= (Instance_map*) ctx; + LEX_STRING group_str; - map = (Instance_map*) ctx; - return map->process_one_option(group, option); + group_str.str= (char *) group; + group_str.length= strlen(group); + + return map->process_one_option(&group_str, option); } C_MODE_END +/* + Parse option string. + + SYNOPSIS + parse_option() + option_str [IN] option string (e.g. "--name=value") + option_name_buf [OUT] parsed name of the option. + Must be of (MAX_OPTION_LEN + 1) size. + option_value_buf [OUT] parsed value of the option. + Must be of (MAX_OPTION_LEN + 1) size. + + DESCRIPTION + This is an auxiliary function and should not be used externally. It is + intended to parse whole option string into option name and option value. +*/ + +static void parse_option(const char *option_str, + char *option_name_buf, + char *option_value_buf) +{ + const char *eq_pos; + const char *ptr= option_str; + + while (*ptr == '-') + ++ptr; + + strmake(option_name_buf, ptr, MAX_OPTION_LEN + 1); + + eq_pos= strchr(ptr, '='); + if (eq_pos) + { + option_name_buf[eq_pos - ptr]= 0; + strmake(option_value_buf, eq_pos + 1, MAX_OPTION_LEN + 1); + } + else + { + option_value_buf[0]= 0; + } +} + + /* Process one option from the configuration file. @@ -103,34 +152,64 @@ C_MODE_END of the instance map object. */ -int Instance_map::process_one_option(const char *group, const char *option) +int Instance_map::process_one_option(const LEX_STRING *group, + const char *option) { Instance *instance= NULL; - static const char prefix[]= { 'm', 'y', 's', 'q', 'l', 'd' }; - if (strncmp(group, prefix, sizeof prefix) == 0 && - ((my_isdigit(default_charset_info, group[sizeof prefix])) - || group[sizeof(prefix)] == '\0')) + if (!Instance::is_name_valid(group)) + { + /* + Current section name is not a valid instance name. + We should skip it w/o error. + */ + return 0; + } + + if (!(instance= (Instance *) hash_search(&hash, (byte *) group->str, + group->length))) + { + if (!(instance= new Instance())) + return 1; + + if (instance->init(group) || add_instance(instance)) { - if (!(instance= (Instance *) hash_search(&hash, (byte *) group, - strlen(group)))) - { - if (!(instance= new Instance)) - goto err; - if (instance->init(group) || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - } - - if (instance->options.add_option(option)) - goto err; /* the instance'll be deleted when we destroy the map */ + delete instance; + return 1; } - return 0; + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) group->str); -err_instance: - delete instance; -err: - return 1; + log_info("mysqld instance '%s' has been added successfully.", + (const char *) group->str); + } + + if (option) + { + char option_name[MAX_OPTION_LEN + 1]; + char option_value[MAX_OPTION_LEN + 1]; + + parse_option(option, option_name, option_value); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option_name)) + { + log_info("Warning: configuration of mysqld-compatible instance '%s' " + "contains IM-specific option '%s'. " + "This breaks backward compatibility for the configuration file.", + (const char *) group->str, + (const char *) option_name); + } + + Named_value option(option_name, option_value); + + if (instance->options.set_option(&option)) + return 1; /* the instance'll be deleted when we destroy the map */ + } + + return 0; } @@ -181,7 +260,7 @@ void Instance_map::unlock() - pass on the new map to the guardian thread: it will start all instances that are marked `guarded' and not yet started. Note, as the check whether an instance is started is currently - very simple (returns true if there is a MySQL server running + very simple (returns TRUE if there is a MySQL server running at the given port), this function has some peculiar side-effects: * if the port number of a running instance was changed, the @@ -194,9 +273,9 @@ void Instance_map::unlock() In order to avoid such side effects one should never call FLUSH INSTANCES without prior stop of all running instances. - TODO - FLUSH INSTANCES should return an error if it's called - while there is a running instance. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian_thread; + - Instance_map; */ int Instance_map::flush_instances() @@ -209,67 +288,169 @@ int Instance_map::flush_instances() guardian (2) reload the instance map (3) reinitialize the guardian with new instances. */ - guardian->lock(); - pthread_mutex_lock(&LOCK_instance_map); hash_free(&hash); hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_instance_key, delete_instance, 0); + rc= load(); guardian->init(); - pthread_mutex_unlock(&LOCK_instance_map); - guardian->unlock(); return rc; } -Instance * -Instance_map::find(const char *name, uint name_len) +bool Instance_map::is_there_active_instance() { Instance *instance; - pthread_mutex_lock(&LOCK_instance_map); - instance= (Instance *) hash_search(&hash, (byte *) name, name_len); - pthread_mutex_unlock(&LOCK_instance_map); - return instance; + Iterator iterator(this); + + while ((instance= iterator.next())) + { + if (guardian->find_instance_node(instance) != NULL || + instance->is_running()) + { + return TRUE; + } + } + + return FALSE; } -int Instance_map::complete_initialization() +int Instance_map::add_instance(Instance *instance) { - Instance *instance; - uint i= 0; + return my_hash_insert(&hash, (byte *) instance); +} - if (hash.records == 0) /* no instances found */ - { - if ((instance= new Instance) == 0) - goto err; +int Instance_map::remove_instance(Instance *instance) +{ + return hash_delete(&hash, (byte *) instance); +} - if (instance->init("mysqld") || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - /* - After an instance have been added to the instance_map, - hash_free should handle it's deletion => goto err, not - err_instance. - */ - if (instance->complete_initialization(this, mysqld_path, - DEFAULT_SINGLE_INSTANCE)) - goto err; +int Instance_map::create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + Instance *instance= new Instance(); + + if (!instance) + { + log_error("Error: can not initialize (name: '%s').", + (const char *) instance_name->str); + return ER_OUT_OF_RESOURCES; } - else - while (i < hash.records) + + if (instance->init(instance_name)) + { + log_error("Error: can not initialize (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + Named_value option= options->get_element(i); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option.get_name())) { - instance= (Instance *) hash_element(&hash, i); - if (instance->complete_initialization(this, mysqld_path, USUAL_INSTANCE)) - goto err; - i++; + log_error("Error: IM-option (%s) can not be used " + "in configuration of mysqld-compatible instance (%s).", + (const char *) option.get_name(), + (const char *) instance_name->str); + delete instance; + return ER_INCOMPATIBLE_OPTION; } + instance->options.set_option(&option); + } + + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) instance_name->str); + + if (instance->complete_initialization(this, mysqld_path)) + { + log_error("Error: can not complete initialization of instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + /* TODO: return more appropriate error code in this case. */ + } + + if (add_instance(instance)) + { + log_error("Error: can not register instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + return 0; -err_instance: - delete instance; -err: - return 1; +} + + +Instance * Instance_map::find(const LEX_STRING *name) +{ + return (Instance *) hash_search(&hash, (byte *) name->str, name->length); +} + + +bool Instance_map::complete_initialization() +{ + bool mysqld_found; + + /* Complete initialization of all registered instances. */ + + for (uint i= 0; i < hash.records; ++i) + { + Instance *instance= (Instance *) hash_element(&hash, i); + + if (instance->complete_initialization(this, mysqld_path)) + return TRUE; + } + + /* That's all if we are runnning in an ordinary mode. */ + + if (!Options::Main::mysqld_safe_compatible) + return FALSE; + + /* In mysqld-compatible mode we must ensure that there 'mysqld' instance. */ + + mysqld_found= find(&Instance::DFLT_INSTANCE_NAME) != NULL; + + if (mysqld_found) + return FALSE; + + if (create_instance(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + log_error("Error: could not create default instance."); + return TRUE; + } + + switch (create_instance_in_file(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + case 0: + case ER_CONF_FILE_DOES_NOT_EXIST: + /* + Continue if the instance has been added to the config file + successfully, or the config file just does not exist. + */ + break; + + default: + log_error("Error: could not add default instance to the config file."); + + Instance *instance= find(&Instance::DFLT_INSTANCE_NAME); + + if (instance) + remove_instance(instance); /* instance is deleted here. */ + + return TRUE; + } + + return FALSE; } @@ -297,10 +478,10 @@ int Instance_map::load() name and start looking for files named "my.cnf.cnf" in all default dirs. Which is not what we want. */ - if (Options::is_forced_default_file) + if (Options::Main::is_forced_default_file) { snprintf(defaults_file_arg, FN_REFLEN, "--defaults-file=%s", - Options::config_file); + Options::Main::config_file); argv_options[1]= defaults_file_arg; argv_options[2]= '\0'; @@ -314,15 +495,12 @@ int Instance_map::load() If the routine failed, we'll simply fallback to defaults in complete_initialization(). */ - if (my_search_option_files(Options::config_file, &argc, + if (my_search_option_files(Options::Main::config_file, &argc, (char ***) &argv, &args_used, process_option, (void*) this)) log_info("Falling back to compiled-in defaults"); - if (complete_initialization()) - return 1; - - return 0; + return complete_initialization(); } @@ -343,3 +521,105 @@ Instance *Instance_map::Iterator::next() return NULL; } + +const char *Instance_map::get_instance_state_name(Instance *instance) +{ + LIST *instance_node; + + if (!instance->is_configured()) + return "misconfigured"; + + if ((instance_node= guardian->find_instance_node(instance)) != NULL) + { + /* The instance is managed by Guardian: we can report precise state. */ + + return Guardian_thread::get_instance_state_name( + guardian->get_instance_state(instance_node)); + } + + /* The instance is not managed by Guardian: we can report status only. */ + + return instance->is_running() ? "online" : "offline"; +} + + +/* + Create a new configuration section for mysqld-instance in the config file. + + SYNOPSYS + create_instance_in_file() + instance_name mysqld-instance name + options options for the new mysqld-instance + + RETURN + 0 On success + ER_CONF_FILE_DOES_NOT_EXIST If config file does not exist + ER_ACCESS_OPTION_FILE If config file is not writable or some I/O + error ocurred during writing configuration +*/ + +int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + File cnf_file; + + if (my_access(Options::Main::config_file, W_OK)) + { + log_error("Error: configuration file (%s) does not exist.", + (const char *) Options::Main::config_file); + return ER_CONF_FILE_DOES_NOT_EXIST; + } + + cnf_file= my_open(Options::Main::config_file, O_WRONLY | O_APPEND, MYF(0)); + + if (cnf_file <= 0) + { + log_error("Error: can not open configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + return ER_ACCESS_OPTION_FILE; + } + + if (my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"[", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)instance_name->str, instance_name->length, + MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"]", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Error: can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + char option_str[MAX_OPTION_STR_LEN]; + char *ptr; + int option_str_len; + Named_value option= options->get_element(i); + + ptr= strxnmov(option_str, MAX_OPTION_LEN + 1, option.get_name(), NullS); + + if (option.get_value()[0]) + ptr= strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + option_str_len= ptr - option_str; + + if (my_write(cnf_file, (byte*)option_str, option_str_len, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Error: can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + } + + my_close(cnf_file, MYF(0)); + + return 0; +} diff --git a/server-tools/instance-manager/instance_map.h b/server-tools/instance-manager/instance_map.h index d3de42f4d80a861acaf69d2132638a9721f880e7..8e6d2360652b5d4fcb6e91ba62242e987e583939 100644 --- a/server-tools/instance-manager/instance_map.h +++ b/server-tools/instance-manager/instance_map.h @@ -17,21 +17,24 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> - -#include "protocol.h" -#include "guardian.h" - #include <my_sys.h> +#include <m_string.h> #include <hash.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +class Guardian_thread; class Instance; +class Named_value_arr; + extern int load_all_groups(char ***groups, const char *filename); extern void free_groups(char **groups); +extern int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options); + /* Instance_map - stores all existing instances @@ -56,22 +59,64 @@ class Instance_map }; friend class Iterator; public: - /* returns a pointer to the instance or NULL, if there is no such instance */ - Instance *find(const char *name, uint name_len); + /* + Return a pointer to the instance or NULL, if there is no such instance. + MT-NOTE: must be called under acquired lock. + */ + Instance *find(const LEX_STRING *name); + /* Clear the configuration cache and reload the configuration file. */ int flush_instances(); + + /* The operation is used to check if there is an active instance or not. */ + bool is_there_active_instance(); + void lock(); void unlock(); + int init(); + /* Process a given option and assign it to appropricate instance. This is required for the option handler, passed to my_search_option_files(). */ - int process_one_option(const char *group, const char *option); + int process_one_option(const LEX_STRING *group, const char *option); + + /* + Add an instance into the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int add_instance(Instance *instance); + + /* + Remove instance from the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int remove_instance(Instance *instance); + + /* + Create a new instance and register it in the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options); Instance_map(const char *default_mysqld_path_arg); ~Instance_map(); + /* + Retrieve client state name of the given instance. + + MT-NOTE: the options must be called under acquired locks of the following + objects: + - Instance_map; + - Guardian_thread; + */ + const char *get_instance_state_name(Instance *instance); + public: const char *mysqld_path; Guardian_thread *guardian; @@ -80,7 +125,7 @@ class Instance_map /* loads options from config files */ int load(); /* inits instances argv's after all options have been loaded */ - int complete_initialization(); + bool complete_initialization(); private: enum { START_HASH_SIZE = 16 }; pthread_mutex_t LOCK_instance_map; diff --git a/server-tools/instance-manager/instance_options.cc b/server-tools/instance-manager/instance_options.cc index 8bbd362a15b9df527b2322ca97e2947ff60b6ce5..b05e40734b7bc527dd429cb9e85e49d1bec34eb0 100644 --- a/server-tools/instance-manager/instance_options.cc +++ b/server-tools/instance-manager/instance_options.cc @@ -20,28 +20,24 @@ #include "instance_options.h" -#include "parse_output.h" -#include "buffer.h" -#include "log.h" - +#include <my_global.h> #include <my_sys.h> -#include <signal.h> #include <m_string.h> -#ifdef __WIN__ -#define NEWLINE_LEN 2 -#else -#define NEWLINE_LEN 1 -#endif +#include <signal.h> + +#include "buffer.h" +#include "instance.h" +#include "log.h" +#include "parse_output.h" +#include "priv.h" /* Create "mysqld ..." command in the buffer */ static inline int create_mysqld_command(Buffer *buf, - const char *mysqld_path_str, - uint mysqld_path_len, - const char *option, - uint option_len) + const LEX_STRING *mysqld_path, + const LEX_STRING *option) { int position= 0; @@ -50,13 +46,13 @@ static inline int create_mysqld_command(Buffer *buf, #ifdef __WIN__ buf->append(position++, "\"", 1); #endif - buf->append(position, mysqld_path_str, mysqld_path_len); - position+= mysqld_path_len; + buf->append(position, mysqld_path->str, mysqld_path->length); + position+= mysqld_path->length; #ifdef __WIN__ buf->append(position++, "\"", 1); #endif /* here the '\0' character is copied from the option string */ - buf->append(position, option, option_len); + buf->append(position, option->str, option->length + 1); return buf->is_error(); } @@ -64,6 +60,42 @@ static inline int create_mysqld_command(Buffer *buf, } +bool Instance_options::is_option_im_specific(const char *option_name) +{ + static const char *IM_SPECIFIC_OPTIONS[] = + { + "nonguarded", + "mysqld-path", + "shutdown-delay", + NULL + }; + + for (int i= 0; IM_SPECIFIC_OPTIONS[i]; ++i) + { + if (!strcmp(option_name, IM_SPECIFIC_OPTIONS[i])) + return TRUE; + } + + return FALSE; +} + + +Instance_options::Instance_options() + :mysqld_version(NULL), mysqld_socket(NULL), mysqld_datadir(NULL), + mysqld_pid_file(NULL), mysqld_port(NULL), mysqld_port_val(0), + nonguarded(NULL), shutdown_delay(NULL), shutdown_delay_val(0), + filled_default_options(0) +{ + mysqld_path.str= NULL; + mysqld_path.length= 0; + + mysqld_real_path.str= NULL; + mysqld_real_path.length= 0; + + memset(logs, 0, sizeof(logs)); +} + + /* Get compiled-in value of default_option @@ -87,13 +119,13 @@ int Instance_options::get_default_option(char *result, size_t result_len, const char *option_name) { int rc= 1; - char verbose_option[]= " --no-defaults --verbose --help"; + LEX_STRING verbose_option= + { C_STRING_WITH_SIZE(" --no-defaults --verbose --help") }; - /* reserve space fot the path + option + final '\0' */ - Buffer cmd(mysqld_path_len + sizeof(verbose_option)); + /* reserve space for the path + option + final '\0' */ + Buffer cmd(mysqld_path.length + verbose_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - verbose_option, sizeof(verbose_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &verbose_option)) goto err; /* +2 eats first "--" from the option string (E.g. "--datadir") */ @@ -121,21 +153,19 @@ int Instance_options::get_default_option(char *result, size_t result_len, int Instance_options::fill_instance_version() { - enum { MAX_VERSION_STRING_LENGTH= 160 }; - char result[MAX_VERSION_STRING_LENGTH]; - char version_option[]= " --no-defaults --version"; + char result[MAX_VERSION_LENGTH]; + LEX_STRING version_option= + { C_STRING_WITH_SIZE(" --no-defaults --version") }; int rc= 1; - Buffer cmd(mysqld_path_len + sizeof(version_option)); + Buffer cmd(mysqld_path.length + version_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - version_option, sizeof(version_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &version_option)) goto err; - bzero(result, MAX_VERSION_STRING_LENGTH); + bzero(result, MAX_VERSION_LENGTH); - rc= parse_output_and_get_value(cmd.buffer, "Ver", - result, MAX_VERSION_STRING_LENGTH, - GET_LINE); + rc= parse_output_and_get_value(cmd.buffer, "Ver", result, + MAX_VERSION_LENGTH, GET_LINE); if (*result != '\0') { @@ -146,6 +176,7 @@ int Instance_options::fill_instance_version() start= result; while (my_isspace(default_charset_info, *start)) ++start; + mysqld_version= strdup_root(&alloc, start); } err: @@ -178,12 +209,12 @@ int Instance_options::fill_instance_version() int Instance_options::fill_mysqld_real_path() { char result[FN_REFLEN]; - char help_option[]= " --no-defaults --help"; + LEX_STRING help_option= + { C_STRING_WITH_SIZE(" --no-defaults --help") }; int rc= 1; - Buffer cmd(mysqld_path_len + sizeof(help_option)); + Buffer cmd(mysqld_path.length + help_option.length); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - help_option, sizeof(help_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &help_option)) goto err; bzero(result, FN_REFLEN); @@ -198,7 +229,8 @@ int Instance_options::fill_mysqld_real_path() /* chop the path of at [OPTIONS] */ if ((options_str= strstr(result, "[OPTIONS]"))) *options_str= '\0'; - mysqld_real_path= strdup_root(&alloc, result); + mysqld_real_path.str= strdup_root(&alloc, result); + mysqld_real_path.length= strlen(mysqld_real_path.str); } err: if (rc) @@ -255,8 +287,7 @@ int Instance_options::fill_log_options() else { /* below is safe, as --datadir always has a value */ - strmake(datadir, - strchr(mysqld_datadir, '=') + 1, MAX_LOG_OPTION_LENGTH - 1); + strmake(datadir, mysqld_datadir, MAX_LOG_OPTION_LENGTH - 1); } if (gethostname(hostname,sizeof(hostname)-1) < 0) @@ -342,7 +373,6 @@ int Instance_options::fill_log_options() int Instance_options::get_pid_filename(char *result) { - const char *pid_file= mysqld_pid_file; char datadir[MAX_PATH_LEN]; if (mysqld_datadir == NULL) @@ -352,14 +382,10 @@ int Instance_options::get_pid_filename(char *result) return 1; } else - strxnmov(datadir, MAX_PATH_LEN - 1, strchr(mysqld_datadir, '=') + 1, - "/", NullS); - - DBUG_ASSERT(mysqld_pid_file); - pid_file= strchr(pid_file, '=') + 1; + strxnmov(datadir, MAX_PATH_LEN - 1, mysqld_datadir, "/", NullS); /* get the full path to the pidfile */ - my_load_path(result, pid_file, datadir); + my_load_path(result, mysqld_pid_file, datadir); return 0; } @@ -388,23 +414,23 @@ pid_t Instance_options::get_pid() } -int Instance_options::complete_initialization(const char *default_path, - uint instance_type) +int Instance_options::complete_initialization(const char *default_path) { + int arg_idx; const char *tmp; char *end; - if (!mysqld_path && !(mysqld_path= strdup_root(&alloc, default_path))) + if (!mysqld_path.str && !(mysqld_path.str= strdup_root(&alloc, default_path))) goto err; // it's safe to cast this to char* since this is a buffer we are allocating - end= convert_dirname((char*)mysqld_path, mysqld_path, NullS); + end= convert_dirname((char*)mysqld_path.str, mysqld_path.str, NullS); end[-1]= 0; - mysqld_path_len= strlen(mysqld_path); + mysqld_path.length= strlen(mysqld_path.str); if (mysqld_port) - mysqld_port_val= atoi(strchr(mysqld_port, '=') + 1); + mysqld_port_val= atoi(mysqld_port); if (shutdown_delay) shutdown_delay_val= atoi(shutdown_delay); @@ -412,7 +438,7 @@ int Instance_options::complete_initialization(const char *default_path, if (!(tmp= strdup_root(&alloc, "--no-defaults"))) goto err; - if (!(mysqld_pid_file)) + if (!mysqld_pid_file) { char pidfilename[MAX_PATH_LEN]; char hostname[MAX_PATH_LEN]; @@ -421,26 +447,27 @@ int Instance_options::complete_initialization(const char *default_path, If we created only one istance [mysqld], because no config. files were found, we would like to model mysqld pid file values. */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", hostname, - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, hostname, ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - "-", hostname, ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, "-", + hostname, ".pid", NullS); } else { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", "mysql", - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, "mysql", ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, ".pid", + NullS); } - add_option(pidfilename); + Named_value option((char *) "pid-file", pidfilename); + + set_option(&option); } if (get_pid_filename(pid_file_with_path)) @@ -448,20 +475,37 @@ int Instance_options::complete_initialization(const char *default_path, /* we need to reserve space for the final zero + possible default options */ if (!(argv= (char**) - alloc_root(&alloc, (options_array.elements + 1 + alloc_root(&alloc, (get_num_options() + 1 + MAX_NUMBER_OF_DEFAULT_OPTIONS) * sizeof(char*)))) goto err; + filled_default_options= 0; /* the path must be first in the argv */ - if (add_to_argv(mysqld_path)) + if (add_to_argv(mysqld_path.str)) goto err; if (add_to_argv(tmp)) goto err; - memcpy((gptr) (argv + filled_default_options), options_array.buffer, - options_array.elements*sizeof(char*)); - argv[filled_default_options + options_array.elements]= 0; + arg_idx= filled_default_options; + for (int opt_idx= 0; opt_idx < get_num_options(); ++opt_idx) + { + char option_str[MAX_OPTION_STR_LEN]; + Named_value option= get_option(opt_idx); + + if (is_option_im_specific(option.get_name())) + continue; + + char *ptr= strxnmov(option_str, MAX_OPTION_LEN + 3, "--", option.get_name(), + NullS); + + if (option.get_value()[0]) + strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + argv[arg_idx++]= strdup_root(&alloc, option_str); + } + + argv[arg_idx]= 0; if (fill_log_options() || fill_mysqld_real_path() || fill_instance_version()) goto err; @@ -473,75 +517,91 @@ int Instance_options::complete_initialization(const char *default_path, } -/* - Assigns given value to the appropriate option from the class. +bool Instance_options::set_option(Named_value *option) +{ + bool err_status; + int idx= find_option(option->get_name()); + char *option_name_str; + char *option_value_str; - SYNOPSYS - add_option() - option string with the option prefixed by -- + if (!(option_name_str= Named_value::alloc_str(option->get_name()))) + return TRUE; - DESCRIPTION + if (!(option_value_str= Named_value::alloc_str(option->get_value()))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } - The method is called from the option handling routine. + Named_value option_copy(option_name_str, option_value_str); - RETURN - 0 - ok - 1 - error occured -*/ + if (idx < 0) + err_status= options.add_element(&option_copy); + else + err_status= options.replace_element(idx, &option_copy); + + if (!err_status) + update_var(option_copy.get_name(), option_copy.get_value()); + else + option_copy.free(); + + return err_status; +} -int Instance_options::add_option(const char* option) + +void Instance_options::unset_option(const char *option_name) { - char *tmp; - enum { SAVE_VALUE= 1, SAVE_WHOLE, SAVE_WHOLE_AND_ADD }; - struct selected_options_st + int idx= find_option(option_name); + + if (idx < 0) + return; /* the option has not been set. */ + + options.remove_element(idx); + + update_var(option_name, NULL); +} + + +void Instance_options::update_var(const char *option_name, + const char *option_value) +{ + struct options_st { const char *name; - uint length; - const char **value; - uint type; - } options[]= + uint name_len; + const char **var; + } options_def[]= { - {"--socket=", 9, &mysqld_socket, SAVE_WHOLE_AND_ADD}, - {"--port=", 7, &mysqld_port, SAVE_WHOLE_AND_ADD}, - {"--datadir=", 10, &mysqld_datadir, SAVE_WHOLE_AND_ADD}, - {"--bind-address=", 15, &mysqld_bind_address, SAVE_WHOLE_AND_ADD}, - {"--pid-file=", 11, &mysqld_pid_file, SAVE_WHOLE_AND_ADD}, - {"--mysqld-path=", 14, &mysqld_path, SAVE_VALUE}, - {"--nonguarded", 9, &nonguarded, SAVE_WHOLE}, - {"--shutdown_delay", 9, &shutdown_delay, SAVE_VALUE}, - {NULL, 0, NULL, 0} + {"socket", 6, &mysqld_socket}, + {"port", 4, &mysqld_port}, + {"datadir", 7, &mysqld_datadir}, + {"pid-file", 8, &mysqld_pid_file}, + {"nonguarded", 10, &nonguarded}, + {"mysqld-path", 11, (const char **) &mysqld_path.str}, + {"shutdown-delay", 14, &shutdown_delay}, + {NULL, 0, NULL} }; - struct selected_options_st *selected_options; - if (!(tmp= strdup_root(&alloc, option))) - goto err; + for (options_st *opt= options_def; opt->name; ++opt) + { + if (!strncmp(opt->name, option_name, opt->name_len)) + { + *(opt->var)= option_value; + break; + } + } +} - for (selected_options= options; selected_options->name; selected_options++) - { - if (strncmp(tmp, selected_options->name, selected_options->length) == 0) - switch (selected_options->type) { - case SAVE_WHOLE_AND_ADD: - *(selected_options->value)= tmp; - insert_dynamic(&options_array,(gptr) &tmp); - return 0; - case SAVE_VALUE: - *(selected_options->value)= strchr(tmp, '=') + 1; - return 0; - case SAVE_WHOLE: - *(selected_options->value)= tmp; - return 0; - default: - break; - } - } - - /* if we haven't returned earlier we should just save the option */ - insert_dynamic(&options_array,(gptr) &tmp); - return 0; +int Instance_options::find_option(const char *option_name) +{ + for (int i= 0; i < get_num_options(); i++) + { + if (!strcmp(get_option(i).get_name(), option_name)) + return i; + } -err: - return 1; + return -1; } @@ -559,7 +619,10 @@ int Instance_options::add_to_argv(const char* option) void Instance_options::print_argv() { int i; - printf("printing out an instance %s argv:\n", instance_name); + + printf("printing out an instance %s argv:\n", + (const char *) instance_name.str); + for (i=0; argv[i] != NULL; i++) printf("argv: %s\n", argv[i]); } @@ -570,17 +633,17 @@ void Instance_options::print_argv() Return value: 0 - ok. 1 - unable to allocate memory. */ -int Instance_options::init(const char *instance_name_arg) +int Instance_options::init(const LEX_STRING *instance_name_arg) { - instance_name_len= strlen(instance_name_arg); + instance_name.length= instance_name_arg->length; init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); - if (my_init_dynamic_array(&options_array, sizeof(char*), 0, 32)) + if (options.init()) goto err; - if (!(instance_name= strmake_root(&alloc, (char*) instance_name_arg, - instance_name_len))) + if (!(instance_name.str= strmake_root(&alloc, instance_name_arg->str, + instance_name_arg->length))) goto err; return 0; @@ -593,6 +656,4 @@ int Instance_options::init(const char *instance_name_arg) Instance_options::~Instance_options() { free_root(&alloc, MYF(0)); - delete_dynamic(&options_array); } - diff --git a/server-tools/instance-manager/instance_options.h b/server-tools/instance-manager/instance_options.h index b316dbf00fca765d468336df3b9f3c2c01eb66dd..c3b0a16a40dd27d79c5accaf77ce15db3c4c60a3 100644 --- a/server-tools/instance-manager/instance_options.h +++ b/server-tools/instance-manager/instance_options.h @@ -18,8 +18,9 @@ #include <my_global.h> #include <my_sys.h> + #include "parse.h" -#include "portability.h" +#include "portability.h" /* for pid_t on Win32 */ #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface @@ -35,25 +36,26 @@ don't have to synchronize between threads. */ -#define USUAL_INSTANCE 0 -#define DEFAULT_SINGLE_INSTANCE 1 - class Instance_options { public: - Instance_options() : - mysqld_version(0), mysqld_socket(0), mysqld_datadir(0), - mysqld_bind_address(0), mysqld_pid_file(0), mysqld_port(0), - mysqld_port_val(0), mysqld_path(0), mysqld_real_path(0), - nonguarded(0), shutdown_delay(0), - shutdown_delay_val(0), filled_default_options(0) - {} + /* The operation is used to check if the option is IM-specific or not. */ + static bool is_option_im_specific(const char *option_name); + +public: + Instance_options(); ~Instance_options(); /* fills in argv */ - int complete_initialization(const char *default_path, uint instance_type); + int complete_initialization(const char *default_path); - int add_option(const char* option); - int init(const char *instance_name_arg); + bool set_option(Named_value *option); + void unset_option(const char *option_name); + + inline int get_num_options() const; + inline Named_value get_option(int idx) const; + +public: + int init(const LEX_STRING *instance_name_arg); pid_t get_pid(); int get_pid_filename(char *result); int unlink_pidfile(); @@ -66,7 +68,6 @@ class Instance_options */ enum { MAX_PATH_LEN= 512 }; enum { MAX_NUMBER_OF_DEFAULT_OPTIONS= 2 }; - enum { MEM_ROOT_BLOCK_SIZE= 512 }; char pid_file_with_path[MAX_PATH_LEN]; char **argv; /* @@ -77,23 +78,18 @@ class Instance_options /* We need the some options, so we store them as a separate pointers */ const char *mysqld_socket; const char *mysqld_datadir; - const char *mysqld_bind_address; const char *mysqld_pid_file; const char *mysqld_port; uint mysqld_port_val; - const char *instance_name; - uint instance_name_len; - const char *mysqld_path; - uint mysqld_path_len; - const char *mysqld_real_path; + LEX_STRING instance_name; + LEX_STRING mysqld_path; + LEX_STRING mysqld_real_path; const char *nonguarded; const char *shutdown_delay; uint shutdown_delay_val; /* log enums are defined in parse.h */ char *logs[3]; - /* this value is computed and cashed here */ - DYNAMIC_ARRAY options_array; private: int fill_log_options(); int fill_instance_version(); @@ -101,9 +97,27 @@ class Instance_options int add_to_argv(const char *option); int get_default_option(char *result, size_t result_len, const char *option_name); + + void update_var(const char *option_name, const char *option_value); + int find_option(const char *option_name); + private: uint filled_default_options; MEM_ROOT alloc; + + Named_value_arr options; }; + +inline int Instance_options::get_num_options() const +{ + return options.get_size(); +} + + +inline Named_value Instance_options::get_option(int idx) const +{ + return options.get_element(idx); +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H */ diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc index 500b25bec033c7bef7763b0ea8408b18c2df47da..da9338e28c5d86bf84ab00a800abc4b0e92f9c98 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -19,21 +19,23 @@ #endif #include "listener.h" -#include "priv.h" -#include <m_string.h> + +#include <my_global.h> #include <mysql.h> #include <violite.h> + +#include <sys/stat.h> #ifndef __WIN__ #include <sys/un.h> #endif -#include <sys/stat.h> -#include "thread_registry.h" -#include "options.h" #include "instance_map.h" #include "log.h" #include "mysql_connection.h" +#include "options.h" #include "portability.h" +#include "priv.h" +#include "thread_registry.h" /* @@ -62,8 +64,7 @@ class Listener_thread: public Listener_thread_args Listener_thread::Listener_thread(const Listener_thread_args &args) : - Listener_thread_args(args.thread_registry, args.options, args.user_map, - args.instance_map) + Listener_thread_args(args.thread_registry, args.user_map, args.instance_map) ,total_connection_count(0) ,thread_info(pthread_self()) ,num_sockets(0) @@ -234,14 +235,16 @@ int Listener_thread::create_tcp_socket() bzero(&ip_socket_address, sizeof(ip_socket_address)); ulong im_bind_addr; - if (options.bind_address != 0) + if (Options::Main::bind_address != 0) { - if ((im_bind_addr= (ulong) inet_addr(options.bind_address)) == INADDR_NONE) + im_bind_addr= (ulong) inet_addr(Options::Main::bind_address); + + if (im_bind_addr == INADDR_NONE) im_bind_addr= htonl(INADDR_ANY); } else im_bind_addr= htonl(INADDR_ANY); - uint im_port= options.port_number; + uint im_port= Options::Main::port_number; ip_socket_address.sin_family= AF_INET; ip_socket_address.sin_addr.s_addr= im_bind_addr; @@ -295,7 +298,7 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) bzero(&unix_socket_address, sizeof(unix_socket_address)); unix_socket_address.sun_family= AF_UNIX; - strmake(unix_socket_address.sun_path, options.socket_file_name, + strmake(unix_socket_address.sun_path, Options::Main::socket_file_name, sizeof(unix_socket_address.sun_path)); unlink(unix_socket_address.sun_path); // in case we have stale socket file diff --git a/server-tools/instance-manager/listener.h b/server-tools/instance-manager/listener.h index 28ccbf91731025de1d9c0955f419579b7f5fd6a8..c28ab0649d746628e6a3dc3ad9ab2ed3041d465e 100644 --- a/server-tools/instance-manager/listener.h +++ b/server-tools/instance-manager/listener.h @@ -27,23 +27,19 @@ pthread_handler_t listener(void *arg); class Thread_registry; -struct Options; class User_map; class Instance_map; struct Listener_thread_args { Thread_registry &thread_registry; - const Options &options; const User_map &user_map; Instance_map &instance_map; Listener_thread_args(Thread_registry &thread_registry_arg, - const Options &options_arg, const User_map &user_map_arg, Instance_map &instance_map_arg) : thread_registry(thread_registry_arg) - ,options(options_arg) ,user_map(user_map_arg) ,instance_map(instance_map_arg) {} diff --git a/server-tools/instance-manager/log.cc b/server-tools/instance-manager/log.cc index 3f54bc0649a9132dff10543fd4e4d6702d999de2..a88344f0b9135a608a930ee1e349d129ec175dcc 100644 --- a/server-tools/instance-manager/log.cc +++ b/server-tools/instance-manager/log.cc @@ -14,14 +14,16 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> - #include "log.h" -#include "portability.h" -#include <stdarg.h> + +#include <my_global.h> #include <m_string.h> #include <my_sys.h> +#include <stdarg.h> + +#include "portability.h" /* for vsnprintf() on Windows. */ + /* TODO: - add flexible header support @@ -71,7 +73,7 @@ static inline void log(FILE *file, const char *format, va_list args) { int size= sizeof(buff_stack) * 2; buff_msg= (char*) my_malloc(size, MYF(0)); - while (true) + while (TRUE) { if (buff_msg == 0) { diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc index 90d9d04cd36014779d3009af979830cc83241cb9..e2bb09cb9fa2332131c6d7eacc3bbd2fc76592d6 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -14,39 +14,55 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "manager.h" -#include "priv.h" -#include "thread_registry.h" -#include "listener.h" -#include "instance_map.h" -#include "options.h" -#include "user_map.h" -#include "log.h" -#include "guardian.h" - -#include <my_sys.h> +#include <my_global.h> #include <m_string.h> -#include <signal.h> +#include <my_sys.h> #include <thr_alarm.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif +#include "exit_codes.h" +#include "guardian.h" +#include "instance_map.h" +#include "listener.h" +#include "log.h" +#include "options.h" +#include "priv.h" +#include "thread_registry.h" +#include "user_map.h" + int create_pid_file(const char *pid_file_name, int pid) { - if (FILE *pid_file= my_fopen(pid_file_name, - O_WRONLY | O_CREAT | O_BINARY, MYF(0))) + FILE *pid_file; + + if (!(pid_file= my_fopen(pid_file_name, O_WRONLY | O_CREAT | O_BINARY, + MYF(0)))) + { + log_error("Error: can not create pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; + } + + if (fprintf(pid_file, "%d\n", (int) pid) <= 0) { - fprintf(pid_file, "%d\n", (int) pid); - my_fclose(pid_file, MYF(0)); - return 0; + log_error("Error: can not write to pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; } - log_error("can't create pid file %s: errno=%d, %s", - pid_file_name, errno, strerror(errno)); - return 1; + + my_fclose(pid_file, MYF(0)); + + return 0; } #ifndef __WIN__ @@ -82,14 +98,14 @@ bool have_signal; void onsignal(int signo) { - have_signal= true; + have_signal= TRUE; } void set_signals(sigset_t *set) { signal(SIGINT, onsignal); signal(SIGTERM, onsignal); - have_signal= false; + have_signal= FALSE; } int my_sigwait(const sigset_t *set, int *sig) @@ -109,10 +125,16 @@ int my_sigwait(const sigset_t *set, int *sig) listener thread, write pid file and enter into signal handling. See also comments in mysqlmanager.cc to picture general Instance Manager architecture. + + TODO: how about returning error status. */ -void manager(const Options &options) +void manager() { + int err_code; + const char *err_msg; + bool shutdown_complete= FALSE; + Thread_registry thread_registry; /* All objects created in the manager() function live as long as @@ -121,31 +143,60 @@ void manager(const Options &options) */ User_map user_map; - Instance_map instance_map(options.default_mysqld_path); + Instance_map instance_map(Options::Main::default_mysqld_path); Guardian_thread guardian_thread(thread_registry, &instance_map, - options.monitoring_interval); + Options::Main::monitoring_interval); - Listener_thread_args listener_args(thread_registry, options, user_map, - instance_map); + Listener_thread_args listener_args(thread_registry, user_map, instance_map); manager_pid= getpid(); instance_map.guardian= &guardian_thread; - if (instance_map.init() || user_map.init()) + /* Initialize instance map. */ + + if (instance_map.init()) + { + log_error("Error: can not initialize instance list: out of memory."); return; + } - if (user_map.load(options.password_file_name)) + /* Initialize user map and load password file. */ + + if (user_map.init()) + { + log_error("Error: can not initialize user list: out of memory."); return; + } + + if ((err_code= user_map.load(Options::Main::password_file_name, &err_msg))) + { + if (err_code == ERR_PASSWORD_FILE_DOES_NOT_EXIST && + Options::Main::mysqld_safe_compatible) + { + /* + The password file does not exist, but we are running in + mysqld_safe-compatible mode. Continue, but complain in log. + */ + + log_error("Warning: password file does not exist, " + "nobody will be able to connect to Instance Manager."); + } + else + { + log_error("Error: %s.", (const char *) err_msg); + return; + } + } /* write Instance Manager pid file */ log_info("IM pid file: '%s'; PID: %d.", - (const char *) options.pid_file_name, + (const char *) Options::Main::pid_file_name, (int) manager_pid); - if (create_pid_file(options.pid_file_name, manager_pid)) - return; + if (create_pid_file(Options::Main::pid_file_name, manager_pid)) + return; /* necessary logging has been already done. */ sigset_t mask; set_signals(&mask); @@ -198,17 +249,23 @@ void manager(const Options &options) To work nicely with LinuxThreads, the signal thread is the first thread in the process. */ - int signo; - bool shutdown_complete; - - shutdown_complete= FALSE; - if (instance_map.flush_instances()) { - log_error("Cannot init instances repository. This might be caused by " - "the wrong config file options. For instance, missing mysqld " - "binary. Aborting."); - return; + instance_map.guardian->lock(); + instance_map.lock(); + + int flush_instances_status= instance_map.flush_instances(); + + instance_map.unlock(); + instance_map.guardian->unlock(); + + if (flush_instances_status) + { + log_error("Cannot init instances repository. This might be caused by " + "the wrong config file options. For instance, missing mysqld " + "binary. Aborting."); + return; + } } /* @@ -219,6 +276,7 @@ void manager(const Options &options) while (!shutdown_complete) { + int signo; int status= 0; if ((status= my_sigwait(&mask, &signo)) != 0) @@ -245,7 +303,7 @@ void manager(const Options &options) { if (!guardian_thread.is_stopped()) { - bool stop_instances= true; + bool stop_instances= TRUE; guardian_thread.request_shutdown(stop_instances); pthread_cond_signal(&guardian_thread.COND_guardian); } @@ -259,7 +317,7 @@ void manager(const Options &options) err: /* delete the pid file */ - my_delete(options.pid_file_name, MYF(0)); + my_delete(Options::Main::pid_file_name, MYF(0)); #ifndef __WIN__ /* free alarm structures */ @@ -267,4 +325,3 @@ void manager(const Options &options) /* don't pthread_exit to kill all threads who did not shut down in time */ #endif } - diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h index 3ddf292132efad22f95b3ce02d01e538bfcb13a0..7aa4b3e1a962c0d3af71e9e75f1eccf273727567 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -16,9 +16,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -struct Options; - -void manager(const Options &options); +void manager(); int create_pid_file(const char *pid_file_name, int pid); diff --git a/server-tools/instance-manager/messages.cc b/server-tools/instance-manager/messages.cc index a9b00b9e01f10ddac35adf0786440976c1678c7a..af35af1e7b0af156f5ad65b636a3994371c8f83a 100644 --- a/server-tools/instance-manager/messages.cc +++ b/server-tools/instance-manager/messages.cc @@ -14,15 +14,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "messages.h" +#include <my_global.h> +#include <mysql_com.h> + #include "mysqld_error.h" #include "mysql_manager_error.h" -#include <mysql_com.h> -#include <assert.h> - static const char *mysqld_error_message(unsigned sql_errno) { @@ -70,6 +69,23 @@ static const char *mysqld_error_message(unsigned sql_errno) "in the instance options"; case ER_ACCESS_OPTION_FILE: return "Cannot open the option file to edit. Check permissions"; + case ER_DROP_ACTIVE_INSTANCE: + return "Cannot drop an active instance. You should stop it first"; + case ER_CREATE_EXISTING_INSTANCE: + return "Instance already exists"; + case ER_INSTANCE_MISCONFIGURED: + return "Instance is misconfigured. Cannot start it"; + case ER_MALFORMED_INSTANCE_NAME: + return "Malformed instance name."; + case ER_INSTANCE_IS_ACTIVE: + return "The instance is active. Stop the instance first"; + case ER_THERE_IS_ACTIVE_INSTACE: + return "At least one instance is active. Stop all instances first"; + case ER_INCOMPATIBLE_OPTION: + return "Instance Manager-specific options are prohibited from being used " + "in the configuration of mysqld-compatible instances"; + case ER_CONF_FILE_DOES_NOT_EXIST: + return "Configuration file does not exist"; default: DBUG_ASSERT(0); return 0; diff --git a/server-tools/instance-manager/mysql_connection.cc b/server-tools/instance-manager/mysql_connection.cc index dcd1807701fd9e71b157dbafd5f1b114a630bf26..17cda3af7043e508fb66aa98f87948a8761c6c20 100644 --- a/server-tools/instance-manager/mysql_connection.cc +++ b/server-tools/instance-manager/mysql_connection.cc @@ -20,22 +20,24 @@ #include "mysql_connection.h" -#include "priv.h" -#include "mysql_manager_error.h" -#include "mysqld_error.h" -#include "thread_registry.h" +#include <m_string.h> +#include <m_string.h> +#include <my_global.h> +#include <mysql_com.h> +#include <mysql.h> +#include <my_sys.h> +#include <violite.h> + +#include "command.h" #include "log.h" -#include "user_map.h" -#include "protocol.h" #include "messages.h" -#include "command.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" #include "parse.h" - -#include <mysql.h> -#include <violite.h> -#include <mysql_com.h> -#include <m_string.h> -#include <my_sys.h> +#include "priv.h" +#include "protocol.h" +#include "thread_registry.h" +#include "user_map.h" Mysql_connection_thread_args::Mysql_connection_thread_args( @@ -56,7 +58,7 @@ Mysql_connection_thread_args::Mysql_connection_thread_args( See also comments in mysqlmanager.cc to picture general Instance Manager architecture. We use conventional technique to work with classes without exceptions: - class acquires all vital resource in init(); Thus if init() succeed, + class acquires all vital resource in init(); Thus if init() succeed, a user must call cleanup(). All other methods are valid only between init() and cleanup(). */ @@ -190,8 +192,6 @@ void Mysql_connection_thread::run() int Mysql_connection_thread::check_connection() { ulong pkt_len=0; // to hold client reply length - /* maximum size of the version string */ - enum { MAX_VERSION_LENGTH= 80 }; /* buffer for the first packet */ /* packet contains: */ char buff[MAX_VERSION_LENGTH + 1 + // server version, 0-ended @@ -202,8 +202,8 @@ int Mysql_connection_thread::check_connection() char *pos= buff; ulong server_flags; - memcpy(pos, mysqlmanager_version, mysqlmanager_version_length + 1); - pos+= mysqlmanager_version_length + 1; + memcpy(pos, mysqlmanager_version.str, mysqlmanager_version.length + 1); + pos+= mysqlmanager_version.length + 1; int4store((uchar*) pos, connection_id); pos+= 4; @@ -271,12 +271,14 @@ int Mysql_connection_thread::check_connection() const char *user= pos; const char *password= strend(user)+1; ulong password_len= *password++; + LEX_STRING user_name= { (char *) user, password - user - 2 }; + if (password_len != SCRAMBLE_LENGTH) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; } - if (user_map.authenticate(user, password-user-2, password, scramble)) + if (user_map.authenticate(&user_name, password, scramble)) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; @@ -312,7 +314,7 @@ int Mysql_connection_thread::do_command() packet= (char*) net.read_pos; enum enum_server_command command= (enum enum_server_command) (uchar) *packet; - log_info("connection %d: packet_length=%d, command=%d", + log_info("connection %d: packet_length=%d, command=%d", connection_id, packet_length, command); return dispatch_command(command, packet + 1, packet_length - 1); } @@ -336,7 +338,7 @@ int Mysql_connection_thread::dispatch_command(enum enum_server_command command, if (Command *command= parse_command(&instance_map, packet)) { int res= 0; - log_info("query for connection %d successefully parsed",connection_id); + log_info("query for connection %d successfully parsed",connection_id); res= command->execute(&net, connection_id); delete command; if (!res) @@ -380,5 +382,5 @@ pthread_handler_t mysql_connection(void *arg) } /* - vim: fdm=marker + vim: fdm=marker */ diff --git a/server-tools/instance-manager/mysql_manager_error.h b/server-tools/instance-manager/mysql_manager_error.h index ff782923a8e04c37e71e6a1ba9fc0275eb950e5f..373c7d798a014c410de53b5ae213cc15aceb4dd3 100644 --- a/server-tools/instance-manager/mysql_manager_error.h +++ b/server-tools/instance-manager/mysql_manager_error.h @@ -29,5 +29,13 @@ #define ER_ACCESS_OPTION_FILE 3008 #define ER_OFFSET_ERROR 3009 #define ER_READ_FILE 3010 +#define ER_DROP_ACTIVE_INSTANCE 3011 +#define ER_CREATE_EXISTING_INSTANCE 3012 +#define ER_INSTANCE_MISCONFIGURED 3013 +#define ER_MALFORMED_INSTANCE_NAME 3014 +#define ER_INSTANCE_IS_ACTIVE 3015 +#define ER_THERE_IS_ACTIVE_INSTACE 3016 +#define ER_INCOMPATIBLE_OPTION 3017 +#define ER_CONF_FILE_DOES_NOT_EXIST 3018 #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_MANAGER_ERROR_H */ diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index ef714099de744b7bc7d6149eb8bc21fa38524d26..177b761b419cd8e9ff75beef41112ceeb4e9a070 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -15,25 +15,30 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> -#include "manager.h" - -#include "options.h" -#include "log.h" - #include <my_sys.h> + #include <string.h> #include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> + #ifndef __WIN__ #include <pwd.h> #include <grp.h> #include <sys/wait.h> #endif -#include <sys/types.h> -#include <sys/stat.h> + +#include "log.h" +#include "manager.h" +#include "options.h" +#include "user_management_commands.h" + #ifdef __WIN__ -#include "windowsservice.h" +#include "IMService.h" +#include "WindowsService.h" #endif + /* Few notes about Instance Manager architecture: Instance Manager consisits of two processes: the angel process, and the @@ -59,13 +64,12 @@ */ static void init_environment(char *progname); + #ifndef __WIN__ static void daemonize(const char *log_file_name); -static void angel(const Options &options); +static void angel(); static struct passwd *check_user(const char *user); static int set_user(const char *user, struct passwd *user_info); -#else -int HandleServiceOptions(Options options); #endif @@ -81,41 +85,61 @@ int main(int argc, char *argv[]) { int return_value= 1; init_environment(argv[0]); - Options options; - if (options.load(argc, argv)) - goto err; + if ((return_value= Options::load(argc, argv))) + goto main_end; + + if (Options::User_management::cmd) + { + return_value= Options::User_management::cmd->execute(); + + goto main_end; + } #ifndef __WIN__ + struct passwd *user_info; - if ((user_info= check_user(options.user))) + if ((user_info= check_user(Options::Daemon::user))) { - if (set_user(options.user, user_info)) - goto err; + if (set_user(Options::Daemon::user, user_info)) + { + return_value= 1; + goto main_end; + } } - if (options.run_as_service) + if (Options::Daemon::run_as_service) { /* forks, and returns only in child */ - daemonize(options.log_file_name); + daemonize(Options::Daemon::log_file_name); /* forks again, and returns only in child: parent becomes angel */ - angel(options); + angel(); } + + manager(); + #else - if (!options.stand_alone) + + if (!Options::Service::stand_alone) { - if (HandleServiceOptions(options)) - goto err; + if (HandleServiceOptions()) + { + return_value= 1; + goto main_end; + } } else + { + manager(); + } + #endif - manager(options); return_value= 0; -err: - options.cleanup(); +main_end: + Options::cleanup(); my_end(0); return return_value; } @@ -200,7 +224,7 @@ static void init_environment(char *progname) MY_INIT(progname); log_init(); umask(0117); - srand(time(0)); + srand((unsigned int) time(0)); } @@ -298,7 +322,7 @@ void terminate(int signo) Angel process will exit silently if mysqlmanager exits normally. */ -static void angel(const Options &options) +static void angel() { /* install signal handlers */ sigset_t zeromask; // to sigsuspend in parent @@ -341,10 +365,10 @@ static void angel(const Options &options) pid= getpid(); /* Get our pid. */ log_info("Angel pid file: '%s'; PID: %d.", - (const char *) options.angel_pid_file_name, + (const char *) Options::Daemon::angel_pid_file_name, (int) pid); - create_pid_file(Options::angel_pid_file_name, pid); + create_pid_file(Options::Daemon::angel_pid_file_name, pid); while (child_status == CHILD_OK && is_terminated == 0) sigsuspend(&zeromask); diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index 8de592f59b6b35b6b703f37c4dca8345737a93b5..31ce5b001901f5ea5cac76a42809ad93a67b4994 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -20,46 +20,89 @@ #include "options.h" -#include "priv.h" -#include "portability.h" +#include <my_global.h> #include <my_sys.h> #include <my_getopt.h> -#include <m_string.h> #include <mysql_com.h> +#include "exit_codes.h" +#include "log.h" +#include "portability.h" +#include "priv.h" +#include "user_management_commands.h" + #define QUOTE2(x) #x #define QUOTE(x) QUOTE2(x) #ifdef __WIN__ -char Options::install_as_service; -char Options::remove_service; -char Options::stand_alone; -char windows_config_file[FN_REFLEN]; -char default_password_file_name[FN_REFLEN]; -char default_log_file_name[FN_REFLEN]; -const char *Options::config_file= windows_config_file; -#else -char Options::run_as_service; -const char *Options::user= 0; /* No default value */ -const char *default_password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); -const char *default_log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); -const char *Options::config_file= QUOTE(DEFAULT_CONFIG_FILE); -const char *Options::angel_pid_file_name= NULL; + +/* Define holders for default values. */ + +static char win_dflt_config_file_name[FN_REFLEN]; +static char win_dflt_password_file_name[FN_REFLEN]; +static char win_dflt_pid_file_name[FN_REFLEN]; +static char win_dflt_socket_file_name[FN_REFLEN]; + +static char win_dflt_mysqld_path[FN_REFLEN]; + +/* Define and initialize Windows-specific options. */ + +my_bool Options::Service::install_as_service; +my_bool Options::Service::remove_service; +my_bool Options::Service::stand_alone; + +const char *Options::Main::config_file= win_dflt_config_file_name; +const char *Options::Main::password_file_name= win_dflt_password_file_name; +const char *Options::Main::pid_file_name= win_dflt_pid_file_name; +const char *Options::Main::socket_file_name= win_dflt_socket_file_name; + +const char *Options::Main::default_mysqld_path= win_dflt_mysqld_path; + +static int setup_windows_defaults(); + +#else /* UNIX */ + +/* Define and initialize UNIX-specific options. */ + +my_bool Options::Daemon::run_as_service= FALSE; +const char *Options::Daemon::log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); +const char *Options::Daemon::user= NULL; /* No default value */ +const char *Options::Daemon::angel_pid_file_name= NULL; + +const char *Options::Main::config_file= QUOTE(DEFAULT_CONFIG_FILE); +const char * +Options::Main::password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); +const char *Options::Main::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); +const char *Options::Main::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME); + +const char *Options::Main::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); + #endif -const char *Options::log_file_name= default_log_file_name; -const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); -const char *Options::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME); -const char *Options::password_file_name= default_password_file_name; -const char *Options::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); -const char *Options::bind_address= 0; /* No default value */ -uint Options::monitoring_interval= DEFAULT_MONITORING_INTERVAL; -uint Options::port_number= DEFAULT_PORT; -/* just to declare */ + +/* Remember if the config file was forced. */ + +bool Options::Main::is_forced_default_file= FALSE; + +/* Define and initialize common options. */ + +const char *Options::Main::bind_address= NULL; /* No default value */ +uint Options::Main::monitoring_interval= DEFAULT_MONITORING_INTERVAL; +uint Options::Main::port_number= DEFAULT_PORT; +my_bool Options::Main::mysqld_safe_compatible= FALSE; + +/* Options::User_management */ + +char *Options::User_management::user_name= NULL; +char *Options::User_management::password= NULL; + +User_management_cmd *Options::User_management::cmd= NULL; + +/* Private members. */ + char **Options::saved_argv= NULL; -/* Remember if the config file was forced */ -bool Options::is_forced_default_file= 0; + #ifndef DBUG_OFF -const char *Options::default_dbug_option= "d:t:i:O,im.trace"; +const char *Options::Debug::config_str= "d:t:i:O,im.trace"; #endif static const char * const ANGEL_PID_FILE_SUFFIX= ".angel.pid"; @@ -71,24 +114,34 @@ static const int ANGEL_PID_FILE_SUFFIX_LEN= strlen(ANGEL_PID_FILE_SUFFIX); */ enum options { + OPT_PASSWD= 'P', + OPT_USERNAME= 'u', + OPT_PASSWORD= 'p', OPT_LOG= 256, OPT_PID_FILE, OPT_SOCKET, OPT_PASSWORD_FILE, OPT_MYSQLD_PATH, -#ifndef __WIN__ - OPT_RUN_AS_SERVICE, - OPT_USER, - OPT_ANGEL_PID_FILE, -#else +#ifdef __WIN__ OPT_INSTALL_SERVICE, OPT_REMOVE_SERVICE, OPT_STAND_ALONE, +#else + OPT_RUN_AS_SERVICE, + OPT_USER, + OPT_ANGEL_PID_FILE, #endif OPT_MONITORING_INTERVAL, OPT_PORT, OPT_WAIT_TIMEOUT, - OPT_BIND_ADDRESS + OPT_BIND_ADDRESS, + OPT_ADD_USER, + OPT_DROP_USER, + OPT_EDIT_USER, + OPT_CLEAN_PASSWORD_FILE, + OPT_CHECK_PASSWORD_FILE, + OPT_LIST_USERS, + OPT_MYSQLD_SAFE_COMPATIBLE }; static struct my_option my_long_options[] = @@ -96,101 +149,158 @@ static struct my_option my_long_options[] = { "help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "add-user", OPT_ADD_USER, + "Add a user to the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + #ifndef __WIN__ { "angel-pid-file", OPT_ANGEL_PID_FILE, "Pid file for angel process.", - (gptr *) &Options::angel_pid_file_name, - (gptr *) &Options::angel_pid_file_name, + (gptr *) &Options::Daemon::angel_pid_file_name, + (gptr *) &Options::Daemon::angel_pid_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif { "bind-address", OPT_BIND_ADDRESS, "Bind address to use for connection.", - (gptr *) &Options::bind_address, (gptr *) &Options::bind_address, + (gptr *) &Options::Main::bind_address, + (gptr *) &Options::Main::bind_address, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "check-password-file", OPT_CHECK_PASSWORD_FILE, + "Check the password file for consistency", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "clean-password-file", OPT_CLEAN_PASSWORD_FILE, + "Clean the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + #ifndef DBUG_OFF {"debug", '#', "Debug log.", - (gptr*) &Options::default_dbug_option, (gptr*) &Options::default_dbug_option, + (gptr *) &Options::Debug::config_str, + (gptr *) &Options::Debug::config_str, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, #endif { "default-mysqld-path", OPT_MYSQLD_PATH, "Where to look for MySQL" " Server binary.", - (gptr *) &Options::default_mysqld_path, - (gptr *) &Options::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }, + + { "drop-user", OPT_DROP_USER, + "Drop existing user from the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "edit-user", OPT_EDIT_USER, + "Edit existing user in the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + #ifdef __WIN__ { "install", OPT_INSTALL_SERVICE, "Install as system service.", - (gptr *) &Options::install_as_service, (gptr*) &Options::install_as_service, + (gptr *) &Options::Service::install_as_service, + (gptr *) &Options::Service::install_as_service, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, #endif + { "list-users", OPT_LIST_USERS, + "Print out a list of registered users", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifndef __WIN__ { "log", OPT_LOG, "Path to log file. Used only with --run-as-service.", - (gptr *) &Options::log_file_name, (gptr *) &Options::log_file_name, + (gptr *) &Options::Daemon::log_file_name, + (gptr *) &Options::Daemon::log_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif { "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor" " instances in seconds.", - (gptr *) &Options::monitoring_interval, - (gptr *) &Options::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, 0, GET_UINT, REQUIRED_ARG, DEFAULT_MONITORING_INTERVAL, 0, 0, 0, 0, 0 }, - { "passwd", 'P', "Prepare entry for passwd file and exit.", 0, 0, 0, - GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "mysqld-safe-compatible", OPT_MYSQLD_SAFE_COMPATIBLE, + "Start Instance Manager in mysqld_safe compatible manner", + (gptr *) &Options::Main::mysqld_safe_compatible, + (gptr *) &Options::Main::mysqld_safe_compatible, + 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, + + { "passwd", OPT_PASSWD, + "Prepare an entry for the password file and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "password", OPT_PASSWORD, "Password to update the password file", + (gptr *) &Options::User_management::password, + (gptr *) &Options::User_management::password, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - { "password-file", OPT_PASSWORD_FILE, "Look for Instance Manager users" - " and passwords here.", - (gptr *) &Options::password_file_name, - (gptr *) &Options::password_file_name, + { "password-file", OPT_PASSWORD_FILE, + "Look for Instance Manager users and passwords here.", + (gptr *) &Options::Main::password_file_name, + (gptr *) &Options::Main::password_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "pid-file", OPT_PID_FILE, "Pid file to use.", - (gptr *) &Options::pid_file_name, (gptr *) &Options::pid_file_name, + (gptr *) &Options::Main::pid_file_name, + (gptr *) &Options::Main::pid_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "port", OPT_PORT, "Port number to use for connections", - (gptr *) &Options::port_number, (gptr *) &Options::port_number, + (gptr *) &Options::Main::port_number, + (gptr *) &Options::Main::port_number, 0, GET_UINT, REQUIRED_ARG, DEFAULT_PORT, 0, 0, 0, 0, 0 }, #ifdef __WIN__ { "remove", OPT_REMOVE_SERVICE, "Remove system service.", - (gptr *)&Options::remove_service, (gptr*) &Options::remove_service, + (gptr *) &Options::Service::remove_service, + (gptr *) &Options::Service::remove_service, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, #else { "run-as-service", OPT_RUN_AS_SERVICE, - "Daemonize and start angel process.", (gptr *) &Options::run_as_service, + "Daemonize and start angel process.", + (gptr *) &Options::Daemon::run_as_service, 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, #endif { "socket", OPT_SOCKET, "Socket file to use for connection.", - (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, + (gptr *) &Options::Main::socket_file_name, + (gptr *) &Options::Main::socket_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #ifdef __WIN__ { "standalone", OPT_STAND_ALONE, "Run the application in stand alone mode.", - (gptr *)&Options::stand_alone, (gptr*) &Options::stand_alone, + (gptr *) &Options::Service::stand_alone, + (gptr *) &Options::Service::stand_alone, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, #else { "user", OPT_USER, "Username to start mysqlmanager", - (gptr *) &Options::user, - (gptr *) &Options::user, + (gptr *) &Options::Daemon::user, + (gptr *) &Options::Daemon::user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif + { "username", OPT_USERNAME, + "Username to update the password file", + (gptr *) &Options::User_management::user_name, + (gptr *) &Options::User_management::user_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "wait-timeout", OPT_WAIT_TIMEOUT, "The number of seconds IM waits " "for activity on a connection before closing it.", - (gptr *) &net_read_timeout, (gptr *) &net_read_timeout, 0, GET_ULONG, - REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, + (gptr *) &net_read_timeout, + (gptr *) &net_read_timeout, + 0, GET_ULONG, REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 } }; static void version() { - printf("%s Ver %s for %s on %s\n", my_progname, mysqlmanager_version, + printf("%s Ver %s for %s on %s\n", my_progname, + (const char *) mysqlmanager_version.str, SYSTEM_TYPE, MACHINE_TYPE); } @@ -218,37 +328,6 @@ static void usage() } -static void passwd() -{ - char user[1024], *p; - const char *pw1, *pw2; - char pw1msg[]= "Enter password: "; - char pw2msg[]= "Re-type password: "; - char crypted_pw[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; - - fprintf(stderr, "Creating record for new user.\n"); - fprintf(stderr, "Enter user name: "); - if (!fgets(user, sizeof(user), stdin)) - { - fprintf(stderr, "Unable to read user.\n"); - return; - } - if ((p= strchr(user, '\n'))) *p= 0; - - pw1= get_tty_password(pw1msg); - pw2= get_tty_password(pw2msg); - - if (strcmp(pw1, pw2)) - { - fprintf(stderr, "Sorry, passwords do not match.\n"); - return; - } - - make_scrambled_password(crypted_pw, pw1); - printf("%s:%s\n", user, crypted_pw); -} - - C_MODE_START static my_bool @@ -260,16 +339,52 @@ get_one_option(int optid, case 'V': version(); exit(0); - case 'P': - passwd(); - exit(0); + case OPT_PASSWD: + case OPT_ADD_USER: + case OPT_DROP_USER: + case OPT_EDIT_USER: + case OPT_CLEAN_PASSWORD_FILE: + case OPT_CHECK_PASSWORD_FILE: + case OPT_LIST_USERS: + if (Options::User_management::cmd) + { + fprintf(stderr, "Error: only one password-management command " + "can be specified at a time.\n"); + exit(ERR_INVALID_USAGE); + } + + switch (optid) { + case OPT_PASSWD: + Options::User_management::cmd= new Passwd_cmd(); + break; + case OPT_ADD_USER: + Options::User_management::cmd= new Add_user_cmd(); + break; + case OPT_DROP_USER: + Options::User_management::cmd= new Drop_user_cmd(); + break; + case OPT_EDIT_USER: + Options::User_management::cmd= new Edit_user_cmd(); + break; + case OPT_CLEAN_PASSWORD_FILE: + Options::User_management::cmd= new Clean_db_cmd(); + break; + case OPT_CHECK_PASSWORD_FILE: + Options::User_management::cmd= new Check_db_cmd(); + break; + case OPT_LIST_USERS: + Options::User_management::cmd= new List_users_cmd(); + break; + } + + break; case '?': usage(); exit(0); case '#': #ifndef DBUG_OFF - DBUG_SET(argument ? argument : Options::default_dbug_option); - DBUG_SET_INITIAL(argument ? argument : Options::default_dbug_option); + DBUG_SET(argument ? argument : Options::Debug::config_str); + DBUG_SET_INITIAL(argument ? argument : Options::Debug::config_str); #endif break; } @@ -295,8 +410,8 @@ int Options::load(int argc, char **argv) { if (is_prefix(argv[1], "--defaults-file=")) { - Options::config_file= strchr(argv[1], '=') + 1; - Options::is_forced_default_file= 1; + Main::config_file= strchr(argv[1], '=') + 1; + Main::is_forced_default_file= TRUE; } if (is_prefix(argv[1], "--defaults-extra-file=") || is_prefix(argv[1], "--no-defaults")) @@ -305,29 +420,44 @@ int Options::load(int argc, char **argv) fprintf(stderr, "The --defaults-extra-file and --no-defaults options" " are not supported by\n" "Instance Manager. Program aborted.\n"); - goto err; + return ERR_INVALID_USAGE; } } #ifdef __WIN__ if (setup_windows_defaults()) - goto err; + { + fprintf(stderr, "Internal error: could not setup default values.\n"); + return ERR_OUT_OF_MEMORY; + } #endif + /* load_defaults will reset saved_argv with a new allocated list */ saved_argv= argv; /* config-file options are prepended to command-line ones */ - load_defaults(config_file, default_groups, &argc, - &saved_argv); - if ((handle_options(&argc, &saved_argv, my_long_options, - get_one_option)) != 0) - goto err; + log_info("Loading config file '%s'...", + (const char *) Main::config_file); + + load_defaults(Main::config_file, default_groups, &argc, &saved_argv); + + if ((handle_options(&argc, &saved_argv, my_long_options, get_one_option))) + return ERR_INVALID_USAGE; + + if (!User_management::cmd && + (User_management::user_name || User_management::password)) + { + fprintf(stderr, + "--username and/or --password options have been specified, " + "but no password-management command has been given.\n"); + return ERR_INVALID_USAGE; + } #ifndef __WIN__ - if (Options::run_as_service) + if (Options::Daemon::run_as_service) { - if (Options::angel_pid_file_name == NULL) + if (Options::Daemon::angel_pid_file_name == NULL) { /* Calculate angel pid file on the IM pid file basis: replace the @@ -339,10 +469,11 @@ int Options::load(int argc, char **argv) char *base_name_ptr; char *ext_ptr; - angel_pid_file_name= (char *) malloc(strlen(Options::pid_file_name) + - ANGEL_PID_FILE_SUFFIX_LEN); + angel_pid_file_name= + (char *) malloc(strlen(Options::Main::pid_file_name) + + ANGEL_PID_FILE_SUFFIX_LEN); - strcpy(angel_pid_file_name, Options::pid_file_name); + strcpy(angel_pid_file_name, Options::Main::pid_file_name); base_name_ptr= strrchr(angel_pid_file_name, '/'); @@ -355,54 +486,73 @@ int Options::load(int argc, char **argv) strcat(angel_pid_file_name, ANGEL_PID_FILE_SUFFIX); - Options::angel_pid_file_name= angel_pid_file_name; + Options::Daemon::angel_pid_file_name= angel_pid_file_name; } else { - Options::angel_pid_file_name= strdup(Options::angel_pid_file_name); + Options::Daemon::angel_pid_file_name= + strdup(Options::Daemon::angel_pid_file_name); } } #endif return 0; - -err: - return 1; } void Options::cleanup() { - /* free_defaults returns nothing */ - if (Options::saved_argv != NULL) - free_defaults(Options::saved_argv); + if (saved_argv) + free_defaults(saved_argv); + + delete User_management::cmd; #ifndef __WIN__ - if (Options::run_as_service) - free((void *) Options::angel_pid_file_name); + if (Options::Daemon::run_as_service) + free((void *) Options::Daemon::angel_pid_file_name); #endif } #ifdef __WIN__ -int Options::setup_windows_defaults() +static int setup_windows_defaults() { - if (!GetModuleFileName(NULL, default_password_file_name, - sizeof(default_password_file_name))) - return 1; - char *filename= strstr(default_password_file_name, ".exe"); - strcpy(filename, ".passwd"); - - if (!GetModuleFileName(NULL, default_log_file_name, - sizeof(default_log_file_name))) + char module_full_name[FN_REFLEN]; + char dir_name[FN_REFLEN]; + char base_name[FN_REFLEN]; + char im_name[FN_REFLEN]; + char *base_name_ptr; + char *ptr; + + /* Determine dirname and basename. */ + + if (!GetModuleFileName(NULL, module_full_name, sizeof (module_full_name)) || + !GetFullPathName(module_full_name, sizeof (dir_name), dir_name, + &base_name_ptr)) + { return 1; - filename= strstr(default_log_file_name, ".exe"); - strcpy(filename, ".log"); + } + + strmake(base_name, base_name_ptr, FN_REFLEN); + *base_name_ptr= 0; + + strmake(im_name, base_name, FN_REFLEN); + ptr= strrchr(im_name, '.'); + + if (!ptr) + return 1; + + *ptr= 0; + + /* Initialize the defaults. */ + + strxmov(win_dflt_config_file_name, dir_name, DFLT_CONFIG_FILE_NAME, NullS); + strxmov(win_dflt_mysqld_path, dir_name, DFLT_MYSQLD_PATH, NullS); + strxmov(win_dflt_password_file_name, dir_name, im_name, DFLT_PASSWD_FILE_EXT, + NullS); + strxmov(win_dflt_pid_file_name, dir_name, im_name, DFLT_PID_FILE_EXT, NullS); + strxmov(win_dflt_socket_file_name, dir_name, im_name, DFLT_SOCKET_FILE_EXT, + NullS); - if (!GetModuleFileName(NULL, windows_config_file, - sizeof(windows_config_file))) - return 1; - char *slash= strrchr(windows_config_file, '\\'); - strcpy(slash, "\\my.ini"); return 0; } diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h index 00a50a2acdb95a3db80a6edd0484a49381bb246c..5c54ff201b293774e94fe8ba579a6b649ef4c982 100644 --- a/server-tools/instance-manager/options.h +++ b/server-tools/instance-manager/options.h @@ -17,50 +17,87 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - Options - all possible options for the instance manager grouped in one - struct. + Options - all possible command-line options for the Instance Manager grouped + in one struct. */ + #include <my_global.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +class User_management_cmd; + struct Options { -#ifdef __WIN__ - static char install_as_service; - static char remove_service; - static char stand_alone; -#else - static char run_as_service; /* handle_options doesn't support bool */ - static const char *user; - static const char *angel_pid_file_name; -#endif - static bool is_forced_default_file; - static const char *log_file_name; - static const char *pid_file_name; - static const char *socket_file_name; - static const char *password_file_name; - static const char *default_mysqld_path; - /* the option which should be passed to process_default_option_files */ - static uint monitoring_interval; - static uint port_number; - static const char *bind_address; - static const char *config_file; + /* + NOTE: handle_options() expects value of my_bool type for GET_BOOL + accessor (i.e. bool must not be used). + */ - /* argv pointer returned by load_defaults() to be used by free_defaults() */ - static char **saved_argv; + struct User_management + { + static User_management_cmd *cmd; + + static char *user_name; + static char *password; + }; + + struct Main + { + /* this is not an option parsed by handle_options(). */ + static bool is_forced_default_file; + + static const char *pid_file_name; + static const char *socket_file_name; + static const char *password_file_name; + static const char *default_mysqld_path; + static uint monitoring_interval; + static uint port_number; + static const char *bind_address; + static const char *config_file; + static my_bool mysqld_safe_compatible; + }; #ifndef DBUG_OFF - static const char *default_dbug_option; + struct Debug + { + static const char *config_str; + }; #endif - int load(int argc, char **argv); - void cleanup(); -#ifdef __WIN__ - int setup_windows_defaults(); +#ifndef __WIN__ + + struct Daemon + { + static my_bool run_as_service; + static const char *log_file_name; + static const char *user; + static const char *angel_pid_file_name; + }; + +#else + + struct Service + { + static my_bool install_as_service; + static my_bool remove_service; + static my_bool stand_alone; + }; + #endif + +public: + static int load(int argc, char **argv); + static void cleanup(); + +private: + Options(); /* Deny instantiation of this class. */ + +private: + /* argv pointer returned by load_defaults() to be used by free_defaults() */ + static char **saved_argv; }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H diff --git a/server-tools/instance-manager/parse.cc b/server-tools/instance-manager/parse.cc index 14b3db16b45b1d252b8312c0bd98d17b7f5f339c..4e931488fcecc5160c3049033654c587abbf0d20 100644 --- a/server-tools/instance-manager/parse.cc +++ b/server-tools/instance-manager/parse.cc @@ -17,12 +17,12 @@ #include "parse.h" #include "commands.h" -#include <string.h> - enum Token { - TOK_ERROR= 0, /* Encodes the "ERROR" word, it doesn't indicate error. */ + TOK_CREATE= 0, + TOK_DROP, + TOK_ERROR, /* Encodes the "ERROR" word, it doesn't indicate error. */ TOK_FILES, TOK_FLUSH, TOK_GENERAL, @@ -50,6 +50,8 @@ struct tokens_st static struct tokens_st tokens[]= { + {6, "CREATE"}, + {4, "DROP"}, {5, "ERROR"}, {5, "FILES"}, {5, "FLUSH"}, @@ -67,6 +69,37 @@ static struct tokens_st tokens[]= { {5, "UNSET"} }; +/************************************************************************/ + +Named_value_arr::Named_value_arr() : + initialized(FALSE) +{ +} + + +bool Named_value_arr::init() +{ + if (my_init_dynamic_array(&arr, sizeof(Named_value), 0, 32)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +Named_value_arr::~Named_value_arr() +{ + if (!initialized) + return; + + for (int i= 0; i < get_size(); ++i) + get_element(i).free(); + + delete_dynamic(&arr); +} + +/************************************************************************/ /* Returns token no if word corresponds to some token, otherwise returns @@ -104,53 +137,200 @@ Token shift_token(const char **text, uint *word_len) } -int get_text_id(const char **text, uint *word_len, const char **id) +int get_text_id(const char **text, LEX_STRING *token) { - get_word(text, word_len); - if (*word_len == 0) + get_word(text, &token->length); + if (token->length == 0) return 1; - *id= *text; + token->str= (char *) *text; return 0; } +static bool parse_long(const LEX_STRING *token, long *value) +{ + int err_code; + char *end_ptr= token->str + token->length; + + *value= my_strtoll10(token->str, &end_ptr, &err_code); + + return err_code != 0; +} + + +bool parse_option_value(const char *text, uint *text_len, char **value) +{ + char beginning_quote; + const char *text_start_ptr; + char *v; + bool escape_mode= FALSE; + + if (!*text || (*text != '\'' && *text != '"')) + return TRUE; /* syntax error: string expected. */ + + beginning_quote= *text; + + ++text; /* skip the beginning quote. */ + + text_start_ptr= text; + + if (!(v= Named_value::alloc_str(text))) + return TRUE; + + *value= v; + + while (TRUE) + { + if (!*text) + { + Named_value::free_str(value); + return TRUE; /* syntax error: missing terminating ' character. */ + } + + if (*text == '\n' || *text == '\r') + { + Named_value::free_str(value); + return TRUE; /* syntax error: option value should be a single line. */ + } + + if (!escape_mode && *text == beginning_quote) + break; + + if (escape_mode) + { + switch (*text) + { + case 'b': /* \b -- backspace */ + if (v > *value) + --v; + break; + + case 't': /* \t -- tab */ + *v= '\t'; + ++v; + break; + + case 'n': /* \n -- newline */ + *v= '\n'; + ++v; + break; + + case 'r': /* \r -- carriage return */ + *v= '\r'; + ++v; + break; + + case '\\': /* \\ -- back slash */ + *v= '\\'; + ++v; + break; + + case 's': /* \s -- space */ + *v= ' '; + ++v; + break; + + default: /* Unknown escape sequence. Treat as error. */ + Named_value::free_str(value); + return TRUE; + } + + escape_mode= FALSE; + } + else + { + if (*text == '\\') + { + escape_mode= TRUE; + } + else + { + *v= *text; + ++v; + } + } + + ++text; + } + + *v= 0; + + /* "2" below stands for beginning and ending quotes. */ + *text_len= text - text_start_ptr + 2; + + return FALSE; +} + + +void skip_spaces(const char **text) +{ + while (**text && my_isspace(default_charset_info, **text)) + ++(*text); +} + + Command *parse_command(Instance_map *map, const char *text) { uint word_len; - const char *instance_name; - uint instance_name_len; - const char *option; - uint option_len; - const char *option_value; - uint option_value_len; - const char *log_size; + LEX_STRING instance_name; Command *command; const char *saved_text= text; - bool skip= false; - const char *tmp; Token tok1= shift_token(&text, &word_len); switch (tok1) { case TOK_START: // fallthrough case TOK_STOP: + case TOK_CREATE: + case TOK_DROP: if (shift_token(&text, &word_len) != TOK_INSTANCE) goto syntax_error; get_word(&text, &word_len); if (word_len == 0) goto syntax_error; - instance_name= text; - instance_name_len= word_len; + instance_name.str= (char *) text; + instance_name.length= word_len; text+= word_len; - /* it should be the end of command */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; - if (tok1 == TOK_START) - command= new Start_instance(map, instance_name, instance_name_len); + if (tok1 == TOK_CREATE) + { + Create_instance *cmd= new Create_instance(map, &instance_name); + + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ + + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } + + command= cmd; + } else - command= new Stop_instance(map, instance_name, instance_name_len); + { + /* it should be the end of command */ + get_word(&text, &word_len, NONSPACE); + if (word_len) + goto syntax_error; + } + + switch (tok1) { + case TOK_START: + command= new Start_instance(map, &instance_name); + break; + case TOK_STOP: + command= new Stop_instance(map, &instance_name); + break; + case TOK_CREATE: + ; /* command already initialized. */ + break; + case TOK_DROP: + command= new Drop_instance(map, &instance_name); + break; + default: /* this is impossible, but nevertheless... */ + DBUG_ASSERT(0); + } break; case TOK_FLUSH: if (shift_token(&text, &word_len) != TOK_INSTANCES) @@ -163,53 +343,28 @@ Command *parse_command(Instance_map *map, const char *text) command= new Flush_instances(map); break; case TOK_UNSET: - skip= true; case TOK_SET: + { + Abstract_option_cmd *cmd; - if (get_text_id(&text, &instance_name_len, &instance_name)) - goto syntax_error; - text+= instance_name_len; - - /* the next token should be a dot */ - get_word(&text, &word_len); - if (*text != '.') - goto syntax_error; - text++; + if (tok1 == TOK_SET) + cmd= new Set_option(map); + else + cmd= new Unset_option(map); - get_word(&text, &option_len, NONSPACE); - option= text; - if ((tmp= strchr(text, '=')) != NULL) - option_len= tmp - text; - text+= option_len; + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ - get_word(&text, &word_len); - if (*text == '=') - { - text++; /* skip '=' */ - get_word(&text, &option_value_len, NONSPACE); - option_value= text; - text+= option_value_len; - } - else - { - option_value= ""; - option_value_len= 0; - } + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } - /* should be empty */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; + command= cmd; - if (skip) - command= new Unset_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - else - command= new Set_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - break; + break; + } case TOK_SHOW: switch (shift_token(&text, &word_len)) { case TOK_INSTANCES: @@ -222,30 +377,35 @@ Command *parse_command(Instance_map *map, const char *text) switch (Token tok2= shift_token(&text, &word_len)) { case TOK_OPTIONS: case TOK_STATUS: - if (get_text_id(&text, &instance_name_len, &instance_name)) + if (get_text_id(&text, &instance_name)) goto syntax_error; - text+= instance_name_len; + text+= instance_name.length; /* check that this is the end of the command */ get_word(&text, &word_len, NONSPACE); if (word_len) goto syntax_error; if (tok2 == TOK_STATUS) - command= new Show_instance_status(map, instance_name, - instance_name_len); + command= new Show_instance_status(map, &instance_name); else - command= new Show_instance_options(map, instance_name, - instance_name_len); + command= new Show_instance_options(map, &instance_name); break; default: goto syntax_error; } break; default: - instance_name= text - word_len; - instance_name_len= word_len; - if (instance_name_len) + instance_name.str= (char *) text - word_len; + instance_name.length= word_len; + if (instance_name.length) { Log_type log_type; + + long log_size; + LEX_STRING log_size_str; + + long log_offset= 0; + LEX_STRING log_offset_str= { NULL, 0 }; + switch (shift_token(&text, &word_len)) { case TOK_LOG: switch (Token tok3= shift_token(&text, &word_len)) { @@ -254,8 +414,7 @@ Command *parse_command(Instance_map *map, const char *text) /* check that this is the end of the command */ if (word_len) goto syntax_error; - command= new Show_instance_log_files(map, instance_name, - instance_name_len); + command= new Show_instance_log_files(map, &instance_name); break; case TOK_ERROR: case TOK_GENERAL: @@ -275,12 +434,14 @@ Command *parse_command(Instance_map *map, const char *text) goto syntax_error; } /* get the size of the log we want to retrieve */ - if (get_text_id(&text, &word_len, &log_size)) + if (get_text_id(&text, &log_size_str)) goto syntax_error; - text+= word_len; + text+= log_size_str.length; + /* this parameter is required */ - if (!word_len) + if (!log_size_str.length) goto syntax_error; + /* the next token should be comma, or nothing */ get_word(&text, &word_len); switch (*text) { @@ -290,23 +451,41 @@ Command *parse_command(Instance_map *map, const char *text) get_word(&text, &word_len); if (!word_len) goto syntax_error; + log_offset_str.str= (char *) text; + log_offset_str.length= word_len; text+= word_len; - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, text); get_word(&text, &word_len, NONSPACE); /* check that this is the end of the command */ if (word_len) goto syntax_error; break; case '\0': - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, NULL); break; /* this is ok */ default: + goto syntax_error; + } + + /* Parse size parameter. */ + + if (parse_long(&log_size_str, &log_size)) + goto syntax_error; + + if (log_size <= 0) goto syntax_error; + + /* Parse offset parameter (if specified). */ + + if (log_offset_str.length) + { + if (parse_long(&log_offset_str, &log_offset)) + goto syntax_error; + + if (log_offset <= 0) + goto syntax_error; } + + command= new Show_instance_log(map, &instance_name, + log_type, log_size, log_offset); break; default: goto syntax_error; diff --git a/server-tools/instance-manager/parse.h b/server-tools/instance-manager/parse.h index 3da53e3a61e15d9ab118cf6360cb07f1d40a66f6..ae29c7eb64abcabffd8867335a4043790f6d9b26 100644 --- a/server-tools/instance-manager/parse.h +++ b/server-tools/instance-manager/parse.h @@ -18,6 +18,7 @@ #include <my_global.h> #include <my_sys.h> +#include <m_string.h> class Command; class Instance_map; @@ -29,10 +30,148 @@ enum Log_type IM_LOG_SLOW }; -Command *parse_command(Instance_map *instance_map, const char *text); +Command *parse_command(Instance_map *map, const char *text); + +bool parse_option_value(const char *text, uint *text_len, char **value); + +void skip_spaces(const char **text); /* define kinds of the word seek method */ -enum { ALPHANUM= 1, NONSPACE }; +enum enum_seek_method { ALPHANUM= 1, NONSPACE, OPTION_NAME }; + +/************************************************************************/ + +class Named_value +{ +public: + /* + The purpose of these methods is just to have one method for + allocating/deallocating memory for strings for Named_value. + */ + + static inline char *alloc_str(const LEX_STRING *str); + static inline char *alloc_str(const char *str); + static inline void free_str(char **str); + +public: + inline Named_value(); + inline Named_value(char *name_arg, char *value_arg); + + inline char *get_name(); + inline char *get_value(); + + inline void free(); + +private: + char *name; + char *value; +}; + +inline char *Named_value::alloc_str(const LEX_STRING *str) +{ + return my_strndup((const byte *) str->str, str->length, MYF(0)); +} + +inline char *Named_value::alloc_str(const char *str) +{ + return my_strdup(str, MYF(0)); +} + +inline void Named_value::free_str(char **str) +{ + my_free(*str, MYF(MY_ALLOW_ZERO_PTR)); + *str= NULL; +} + +inline Named_value::Named_value() + :name(NULL), value(NULL) +{ } + +inline Named_value::Named_value(char *name_arg, char *value_arg) + :name(name_arg), value(value_arg) +{ } + +inline char *Named_value::get_name() +{ + return name; +} + +inline char *Named_value::get_value() +{ + return value; +} + +void Named_value::free() +{ + free_str(&name); + free_str(&value); +} + +/************************************************************************/ + +class Named_value_arr +{ +public: + Named_value_arr(); + ~Named_value_arr(); + + bool init(); + + inline int get_size() const; + inline Named_value get_element(int idx) const; + inline void remove_element(int idx); + inline bool add_element(Named_value *option); + inline bool replace_element(int idx, Named_value *option); + +private: + bool initialized; + DYNAMIC_ARRAY arr; +}; + + +inline int Named_value_arr::get_size() const +{ + return arr.elements; +} + + +inline Named_value Named_value_arr::get_element(int idx) const +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + Named_value option; + get_dynamic((DYNAMIC_ARRAY *) &arr, (gptr) &option, idx); + + return option; +} + + +inline void Named_value_arr::remove_element(int idx) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + delete_dynamic_element(&arr, idx); +} + + +inline bool Named_value_arr::add_element(Named_value *option) +{ + return insert_dynamic(&arr, (gptr) option); +} + + +inline bool Named_value_arr::replace_element(int idx, Named_value *option) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + return set_dynamic(&arr, (gptr) option, idx); +} + +/************************************************************************/ /* tries to find next word in the text @@ -41,7 +180,7 @@ enum { ALPHANUM= 1, NONSPACE }; */ inline void get_word(const char **text, uint *word_len, - int seek_method= ALPHANUM) + enum_seek_method seek_method= ALPHANUM) { const char *word_end; @@ -51,13 +190,23 @@ inline void get_word(const char **text, uint *word_len, word_end= *text; - if (seek_method == ALPHANUM) + switch (seek_method) { + case ALPHANUM: while (my_isalnum(default_charset_info, *word_end)) ++word_end; - else + break; + case NONSPACE: while (!my_isspace(default_charset_info, *word_end) && (*word_end != '\0')) ++word_end; + break; + case OPTION_NAME: + while (my_isalnum(default_charset_info, *word_end) || + *word_end == '-' || + *word_end == '_') + ++word_end; + break; + } *word_len= word_end - *text; } diff --git a/server-tools/instance-manager/parse_output.cc b/server-tools/instance-manager/parse_output.cc index 64bb6a6485f7b9f59ba8b4ea9ecd108158131c18..643a50625a1ed4e39897af7fbf403bb403b81448 100644 --- a/server-tools/instance-manager/parse_output.cc +++ b/server-tools/instance-manager/parse_output.cc @@ -14,13 +14,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include "parse.h" #include "parse_output.h" -#include <stdio.h> +#include <my_global.h> #include <my_sys.h> #include <m_string.h> + +#include <stdio.h> + +#include "parse.h" #include "portability.h" diff --git a/server-tools/instance-manager/parse_output.h b/server-tools/instance-manager/parse_output.h index 6a84fabbf17830debf87aba43e834e1bcc4f04a1..b86363a4452e2298af820322c9e8631cfffa3854 100644 --- a/server-tools/instance-manager/parse_output.h +++ b/server-tools/instance-manager/parse_output.h @@ -16,6 +16,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> + #define GET_VALUE 1 #define GET_LINE 2 diff --git a/server-tools/instance-manager/portability.h b/server-tools/instance-manager/portability.h index 1a3be5705e35bb937154089fd8c0747d32c2d5e4..a76cff588935318375bde7b02fa9715b4db2f5d1 100644 --- a/server-tools/instance-manager/portability.h +++ b/server-tools/instance-manager/portability.h @@ -16,11 +16,25 @@ /*TODO: fix this */ #define PROTOCOL_VERSION 10 +#define DFLT_CONFIG_FILE_NAME "my.ini" +#define DFLT_MYSQLD_PATH "mysqld" +#define DFLT_PASSWD_FILE_EXT ".passwd" +#define DFLT_PID_FILE_EXT ".pid" +#define DFLT_SOCKET_FILE_EXT ".sock" + typedef int pid_t; #undef popen #define popen(A,B) _popen(A,B) +#define NEWLINE "\r\n" +#define NEWLINE_LEN 2 + +#else /* ! __WIN__ */ + +#define NEWLINE "\n" +#define NEWLINE_LEN 1 + #endif /* __WIN__ */ #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PORTABILITY_H */ diff --git a/server-tools/instance-manager/priv.cc b/server-tools/instance-manager/priv.cc index d2d6a3f636c2ed015c10b6235d761fb4881f5bf2..d3cc52ec6386793fb44d45bf9eee5f3c001438db 100644 --- a/server-tools/instance-manager/priv.cc +++ b/server-tools/instance-manager/priv.cc @@ -14,10 +14,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "priv.h" + #include <my_global.h> #include <mysql_com.h> -#include "priv.h" -#include "portability.h" #if defined(__ia64__) || defined(__ia64) /* @@ -43,9 +43,7 @@ bool linuxthreads; The following string must be less then 80 characters, as mysql_connection.cc relies on it */ -const char mysqlmanager_version[] = "0.2-alpha"; - -const int mysqlmanager_version_length= sizeof(mysqlmanager_version) - 1; +const LEX_STRING mysqlmanager_version= { C_STRING_WITH_SIZE("1.0-beta") }; const unsigned char protocol_version= PROTOCOL_VERSION; diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h index 52d7aa1d23d2db754952a344c17d9a0725b599fc..0b393c17ac2f8d419ce97576159c30e529c5294d 100644 --- a/server-tools/instance-manager/priv.h +++ b/server-tools/instance-manager/priv.h @@ -16,13 +16,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> +#include <m_string.h> +#include <my_pthread.h> + #include <sys/types.h> -#ifdef __WIN__ -#include "portability.h" -#else + +#ifndef __WIN__ #include <unistd.h> #endif -#include "my_pthread.h" + +#include "portability.h" /* IM-wide platform-independent defines */ #define SERVER_DEFAULT_PORT 3306 @@ -31,6 +35,21 @@ /* three-week timeout should be enough */ #define LONG_TIMEOUT ((ulong) 3600L*24L*21L) +const int MEM_ROOT_BLOCK_SIZE= 512; + +/* The maximal length of option name and option value. */ +const int MAX_OPTION_LEN= 1024; + +/* + The maximal length of whole option string: + --<option name>=<option value> +*/ +const int MAX_OPTION_STR_LEN= 2 + MAX_OPTION_LEN + 1 + MAX_OPTION_LEN + 1; + +const int MAX_VERSION_LENGTH= 160; + +const int MAX_INSTANCE_NAME_SIZE= FN_REFLEN; + /* the pid of the manager process (of the signal thread on the LinuxThreads) */ extern pid_t manager_pid; @@ -42,8 +61,7 @@ extern pid_t manager_pid; extern bool linuxthreads; #endif -extern const char mysqlmanager_version[]; -extern const int mysqlmanager_version_length; +extern const LEX_STRING mysqlmanager_version; /* MySQL client-server protocol version: substituted from configure */ extern const unsigned char protocol_version; diff --git a/server-tools/instance-manager/protocol.cc b/server-tools/instance-manager/protocol.cc index 73e07f993ae26513e354806fe6f9f6d338877a8c..4a8c4d0b88d6013e9f69b3e67f0a6b0cb88c2d78 100644 --- a/server-tools/instance-manager/protocol.cc +++ b/server-tools/instance-manager/protocol.cc @@ -163,7 +163,7 @@ int send_fields(struct st_net *net, LIST *fields) Buffer send_buff; char small_buff[4]; uint position= 0; - NAME_WITH_LENGTH *field; + LEX_STRING *field; /* send the number of fileds */ net_store_length(small_buff, (uint) list_length(fields)); @@ -173,7 +173,7 @@ int send_fields(struct st_net *net, LIST *fields) while (tmp) { position= 0; - field= (NAME_WITH_LENGTH *) tmp->data; + field= (LEX_STRING *) tmp->data; store_to_protocol_packet(&send_buff, (char*) "", &position); /* catalog name */ @@ -184,9 +184,9 @@ int send_fields(struct st_net *net, LIST *fields) store_to_protocol_packet(&send_buff, (char*) "", &position); /* table name alias */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name */ + field->str, &position); /* column name */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name alias */ + field->str, &position); /* column name alias */ send_buff.reserve(position, 12); if (send_buff.is_error()) goto err; diff --git a/server-tools/instance-manager/protocol.h b/server-tools/instance-manager/protocol.h index f38eac6b079b4840f4a80607c732a2dc3c8b3c14..2c84ad394b4fe7467b26c862ca550eabff5d0c95 100644 --- a/server-tools/instance-manager/protocol.h +++ b/server-tools/instance-manager/protocol.h @@ -20,11 +20,6 @@ #include <my_list.h> -typedef struct field { - char *name; - uint length; -} NAME_WITH_LENGTH; - /* default field length to be used in various field-realted functions */ enum { DEFAULT_FIELD_LENGTH= 20 }; diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc index 0091d713a9101605fcbf217c86ac7b2d376bd46e..a424860548d66c2a7bc2c3889cab8270c891ec6d 100644 --- a/server-tools/instance-manager/thread_registry.cc +++ b/server-tools/instance-manager/thread_registry.cc @@ -20,11 +20,12 @@ #include "thread_registry.h" -#include "log.h" +#include <my_global.h> +#include <thr_alarm.h> -#include <assert.h> #include <signal.h> -#include <thr_alarm.h> + +#include "log.h" #ifndef __WIN__ @@ -52,7 +53,7 @@ Thread_info::Thread_info(pthread_t thread_id_arg) : */ Thread_registry::Thread_registry() : - shutdown_in_progress(false) + shutdown_in_progress(FALSE) ,sigwait_thread_pid(pthread_self()) { pthread_mutex_init(&LOCK_thread_registry, 0); @@ -186,7 +187,7 @@ void Thread_registry::deliver_shutdown() set_timespec(shutdown_time, 1); pthread_mutex_lock(&LOCK_thread_registry); - shutdown_in_progress= true; + shutdown_in_progress= TRUE; #ifndef __WIN__ /* to stop reading from the network we need to flush alarm queue */ diff --git a/server-tools/instance-manager/user_management_commands.cc b/server-tools/instance-manager/user_management_commands.cc new file mode 100644 index 0000000000000000000000000000000000000000..03a3f9814e3fbdd242c427a2189f15aad28f2bb9 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.cc @@ -0,0 +1,406 @@ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "user_management_commands.h" + +#include "exit_codes.h" +#include "options.h" +#include "user_map.h" + +/************************************************************************* + Module-specific (internal) functions. +*************************************************************************/ + +/* + The function returns user name. The user name is retrieved from command-line + options (if specified) or from console. + + NOTE + This function must not be used in user-management command implementations. + Use get_user_name() instead. + + SYNOPSYS + get_user_name_impl() + + RETURN + NULL on error + valid pointer on success +*/ + +static char *get_user_name_impl() +{ + static char user_name_buf[1024]; + char *ptr; + + if (Options::User_management::user_name) + return Options::User_management::user_name; + + printf("Enter user name: "); + fflush(stdout); + + if (!fgets(user_name_buf, sizeof (user_name_buf), stdin)) + return NULL; + + if ((ptr= strchr(user_name_buf, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_name_buf, '\r'))) + *ptr= 0; + + return user_name_buf; +} + + +/* + The function is intended to provide user name for user-management + operations. It also checks that length of the specified user name is correct + (not empty, not exceeds USERNAME_LENGTH). Report to stderr if something is + wrong. + + SYNOPSYS + get_user_name() + user_name [OUT] on success contains user name + + RETURN + TRUE on error + FALSE on success +*/ + +static bool get_user_name(LEX_STRING *user_name) +{ + char *user_name_str= get_user_name_impl(); + + if (!user_name_str) + { + fprintf(stderr, "Error: unable to read user name from stdin.\n"); + return TRUE; + } + + user_name->str= user_name_str; + user_name->length= strlen(user_name->str); + + if (user_name->length == 0) + { + fprintf(stderr, "Error: user name can not be empty.\n"); + return TRUE; + } + + if (user_name->length > USERNAME_LENGTH) + { + fprintf(stderr, "Error: user name must not exceed %d characters.\n", + (int) USERNAME_LENGTH); + return TRUE; + } + + return FALSE; +} + + +/* + The function is intended to provide password for user-management operations. + The password is retrieved from command-line options (if specified) or from + console. + + SYNOPSYS + get_password() + + RETURN + NULL on error + valid pointer on success +*/ + +static const char *get_password() +{ + if (Options::User_management::password) + return Options::User_management::password; + + const char *passwd1= get_tty_password("Enter password: "); + const char *passwd2= get_tty_password("Re-type password: "); + + if (strcmp(passwd1, passwd2)) + { + fprintf(stderr, "Error: passwords do not match.\n"); + return 0; + } + + return passwd1; +} + + +/* + Load password file into user map. + + SYNOPSYS + load_password_file() + user_map target user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int load_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if (user_map->init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + if ((err_code= user_map->load(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + + +/* + Save user map into password file. + + SYNOPSYS + save_password_file() + user_map user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int save_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if ((err_code= user_map->save(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + +/************************************************************************* + Passwd_cmd +*************************************************************************/ + +int Passwd_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + printf("Creating record for new user.\n"); + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + { + User user(&user_name, password); + + printf("%s:%s\n", + (const char *) user.user, + (const char *) user.scrambled_password); + } + + return ERR_OK; +} + + +/************************************************************************* + Add_user_cmd +*************************************************************************/ + +int Add_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + User_map user_map; + User *new_user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Check that the user does not exist. */ + + if (user_map.find_user(&user_name)) + { + fprintf(stderr, "Error: user '%s' already exists.\n", + (const char *) user_name.str); + return ERR_USER_ALREADY_EXISTS; + } + + /* Add the user. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + if (!(new_user= new User(&user_name, password))) + return ERR_OUT_OF_MEMORY; + + if (user_map.add_user(new_user)) + { + delete new_user; + return ERR_OUT_OF_MEMORY; + } + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Drop_user_cmd +*************************************************************************/ + +int Drop_user_cmd::execute() +{ + LEX_STRING user_name; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Remove the user (ignore possible errors). */ + + user_map.remove_user(user); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Edit_user_cmd +*************************************************************************/ + +int Edit_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Modify user's password. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + user->set_password(password); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Clean_db_cmd +*************************************************************************/ + +int Clean_db_cmd::execute() +{ + User_map user_map; + + if (user_map.init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + return save_password_file(&user_map); +} + + +/************************************************************************* + Check_db_cmd +*************************************************************************/ + +int Check_db_cmd::execute() +{ + User_map user_map; + + return load_password_file(&user_map); +} + + +/************************************************************************* + List_users_cmd +*************************************************************************/ + +int List_users_cmd::execute() +{ + User_map user_map; + + int err_code; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map))) + return err_code; + + /* Print out registered users. */ + + { + User_map::Iterator it(&user_map); + User *user; + + while ((user= it.next())) + fprintf(stderr, "%s\n", (const char *) user->user); + } + + return ERR_OK; +} diff --git a/server-tools/instance-manager/user_management_commands.h b/server-tools/instance-manager/user_management_commands.h new file mode 100644 index 0000000000000000000000000000000000000000..4bf3546f0a6749ef445ff600409b8da0731396fe --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.h @@ -0,0 +1,167 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H + +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + This header contains declarations of classes inteded to support + user-management commands (such as add user, get list of users, etc). + + The general idea is to have one interface (pure abstract class) for such a + command. Each concrete user-management command is implemented in concrete + class, derived from the common interface. +*/ + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/************************************************************************* + User_management_cmd -- base class for User-management commands. +*************************************************************************/ + +class User_management_cmd +{ +public: + User_management_cmd() + { } + + virtual ~User_management_cmd() + { } + +public: + /* + Executes user-management command. + + SYNOPSYS + execute() + + RETURN + See exit_codes.h for possible values. + */ + + virtual int execute() = 0; +}; + + +/************************************************************************* + Passwd_cmd: support for --passwd command-line option. +*************************************************************************/ + +class Passwd_cmd : public User_management_cmd +{ +public: + Passwd_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Add_user_cmd: support for --add-user command-line option. +*************************************************************************/ + +class Add_user_cmd : public User_management_cmd +{ +public: + Add_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Drop_user_cmd: support for --drop-user command-line option. +*************************************************************************/ + +class Drop_user_cmd : public User_management_cmd +{ +public: + Drop_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Edit_user_cmd: support for --edit-user command-line option. +*************************************************************************/ + +class Edit_user_cmd : public User_management_cmd +{ +public: + Edit_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Clean_db_cmd: support for --clean-db command-line option. +*************************************************************************/ + +class Clean_db_cmd : public User_management_cmd +{ +public: + Clean_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Check_db_cmd: support for --check-db command-line option. +*************************************************************************/ + +class Check_db_cmd : public User_management_cmd +{ +public: + Check_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + List_users_cmd: support for --list-users command-line option. +*************************************************************************/ + +class List_users_cmd : public User_management_cmd +{ +public: + List_users_cmd() + { } + +public: + virtual int execute(); +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H diff --git a/server-tools/instance-manager/user_map.cc b/server-tools/instance-manager/user_map.cc index 9cb153071311805ebc654d433bc1969d5208c71e..e8128cf015be105d5b729b386d46d614da9151f0 100644 --- a/server-tools/instance-manager/user_map.cc +++ b/server-tools/instance-manager/user_map.cc @@ -19,32 +19,32 @@ #endif #include "user_map.h" - -#include <mysql_com.h> -#include <m_string.h> - +#include "exit_codes.h" #include "log.h" +#include "portability.h" -struct User +User::User(const LEX_STRING *user_name_arg, const char *password) { - char user[USERNAME_LENGTH + 1]; - uint8 user_length; - uint8 salt[SCRAMBLE_LENGTH]; - int init(const char *line); -}; + user_length= strmake(user, user_name_arg->str, USERNAME_LENGTH + 1) - user; + set_password(password); +} int User::init(const char *line) { const char *name_begin, *name_end, *password; - int line_ending_len= 1; + int password_length; if (line[0] == '\'' || line[0] == '"') { name_begin= line + 1; name_end= strchr(name_begin, line[0]); if (name_end == 0 || name_end[1] != ':') - goto err; + { + log_info("Error: invalid format (unmatched quote) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 2; } else @@ -52,33 +52,47 @@ int User::init(const char *line) name_begin= line; name_end= strchr(name_begin, ':'); if (name_end == 0) - goto err; + { + log_info("Error: invalid format (no delimiter) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 1; } + user_length= name_end - name_begin; if (user_length > USERNAME_LENGTH) - goto err; - - /* - assume that newline characater is present - we support reading password files that end in \n or \r\n on - either platform. - */ - if (password[strlen(password)-2] == '\r') - line_ending_len= 2; - if (strlen(password) != (uint) (SCRAMBLED_PASSWORD_CHAR_LENGTH + - line_ending_len)) - goto err; + { + log_info("Error: user name is too long (%d). Max length: %d. " + "User line: '%s'.", + (int) user_length, + (int) USERNAME_LENGTH, + (const char *) line); + return 1; + } + + password_length= strlen(password); + if (password_length > SCRAMBLED_PASSWORD_CHAR_LENGTH) + { + log_info("Error: password is too long (%d). Max length: %d. ", + "User line: '%s'.", + (int) password_length, + (int) SCRAMBLED_PASSWORD_CHAR_LENGTH, + (const char *) line); + return 1; + } memcpy(user, name_begin, user_length); user[user_length]= 0; + + memcpy(scrambled_password, password, password_length); + scrambled_password[password_length]= 0; + get_salt_from_password(salt, password); - log_info("loaded user %s", user); + + log_info("loaded user '%s'.", user); return 0; -err: - log_error("error parsing user and password at line %s", line); - return 1; } @@ -101,30 +115,70 @@ static void delete_user(void *u) C_MODE_END +void User_map::Iterator::reset() +{ + cur_idx= 0; +} + + +User *User_map::Iterator::next() +{ + if (cur_idx < user_map->hash.records) + return (User *) hash_element(&user_map->hash, cur_idx++); + + return NULL; +} + + int User_map::init() { enum { START_HASH_SIZE= 16 }; if (hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_user_key, delete_user, 0)) return 1; + + initialized= TRUE; + return 0; } +User_map::User_map() + :initialized(FALSE) +{ +} + + User_map::~User_map() { - hash_free(&hash); + if (initialized) + hash_free(&hash); } /* - Load all users from the password file. Must be called once right after - construction. - In case of failure, puts error message to the log file and returns 1 + Load password database. + + SYNOPSYS + load() + password_file_name [IN] password file path + err_msg [OUT] error message + + DESCRIPTION + Load all users from the password file. Must be called once right after + construction. In case of failure, puts error message to the log file and + returns specific error code. + + RETURN + 0 on success + !0 on error */ -int User_map::load(const char *password_file_name) +int User_map::load(const char *password_file_name, const char **err_msg) { + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + FILE *file; char line[USERNAME_LENGTH + SCRAMBLED_PASSWORD_CHAR_LENGTH + 2 + /* for possible quotes */ @@ -134,33 +188,172 @@ int User_map::load(const char *password_file_name) User *user; int rc= 1; + if (my_access(password_file_name, F_OK) != 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) does not exist", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_DOES_NOT_EXIST; + } + if ((file= my_fopen(password_file_name, O_RDONLY | O_BINARY, MYF(0))) == 0) { - /* Probably the password file wasn't specified. Try to leave without it */ - log_info("[WARNING] can't open password file %s: errno=%d, %s", password_file_name, - errno, strerror(errno)); - return 0; + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; } + log_info("loading the password database..."); + while (fgets(line, sizeof(line), file)) { + char *user_line= line; + + /* + We need to skip EOL-symbols also from the beginning of the line, because + if the previous line was ended by \n\r sequence, we get \r in our line. + */ + + while (user_line[0] == '\r' || user_line[0] == '\n') + ++user_line; + + /* Skip EOL-symbols in the end of the line. */ + + { + char *ptr; + + if ((ptr= strchr(user_line, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_line, '\r'))) + *ptr= 0; + } + /* skip comments and empty lines */ - if (line[0] == '#' || line[0] == '\n' && - (line[1] == '\0' || line[1] == '\r')) + if (!user_line[0] || user_line[0] == '#') continue; + if ((user= new User) == 0) - goto done; - if (user->init(line) || my_hash_insert(&hash, (byte *) user)) - goto err_init_user; + { + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } + + if (user->init(user_line)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) corrupted", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_CORRUPTED; + } + + if (my_hash_insert(&hash, (byte *) user)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } } - if (feof(file)) - rc= 0; - goto done; -err_init_user: - delete user; -done: + + log_info("the password database loaded successfully."); + my_fclose(file, MYF(0)); - return rc; + + if (err_msg) + *err_msg= NULL; + + return ERR_OK; +} + + +int User_map::save(const char *password_file_name, const char **err_msg) +{ + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + + FILE *file; + + if ((file= my_fopen(password_file_name, O_WRONLY | O_TRUNC | O_BINARY, + MYF(0))) == 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s) for writing: %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; + } + + { + User_map::Iterator it(this); + User *user; + + while ((user= it.next())) + { + if (fprintf(file, "%s:%s\n", (const char *) user->user, + (const char *) user->scrambled_password) < 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not write to password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + my_fclose(file, MYF(0)); + + return ERR_IO_ERROR; + } + } + } + + my_fclose(file, MYF(0)); + + return ERR_OK; } @@ -172,13 +365,33 @@ int User_map::load(const char *password_file_name) 2 - user not found */ -int User_map::authenticate(const char *user_name, uint length, +int User_map::authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const { - const User *user= (const User *) hash_search((HASH *) &hash, - (byte *) user_name, length); - if (user) - return check_scramble(scrambled_password, scramble, user->salt); - return 2; + const User *user= find_user(user_name); + return user ? check_scramble(scrambled_password, scramble, user->salt) : 2; +} + + +User *User_map::find_user(const LEX_STRING *user_name) +{ + return (User*) hash_search(&hash, (byte*) user_name->str, user_name->length); +} + +const User *User_map::find_user(const LEX_STRING *user_name) const +{ + return const_cast<User_map *> (this)->find_user(user_name); +} + + +bool User_map::add_user(User *user) +{ + return my_hash_insert(&hash, (byte*) user) == 0 ? FALSE : TRUE; +} + + +bool User_map::remove_user(User *user) +{ + return hash_delete(&hash, (byte*) user) == 0 ? FALSE : TRUE; } diff --git a/server-tools/instance-manager/user_map.h b/server-tools/instance-manager/user_map.h index 4134017dd9b44845ec90c41c38ffa0cf07ca99f3..de207c11e658793fb87f2bea598c22cd80fca2d5 100644 --- a/server-tools/instance-manager/user_map.h +++ b/server-tools/instance-manager/user_map.h @@ -18,14 +18,35 @@ #include <my_global.h> - #include <my_sys.h> +#include <mysql_com.h> +#include <m_string.h> #include <hash.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +struct User +{ + User() + {} + + User(const LEX_STRING *user_name_arg, const char *password); + + int init(const char *line); + + inline void set_password(const char *password) + { + make_scrambled_password(scrambled_password, password); + } + + char user[USERNAME_LENGTH + 1]; + char scrambled_password[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; + uint8 user_length; + uint8 salt[SCRAMBLE_LENGTH]; +}; + /* User_map -- all users and passwords */ @@ -33,15 +54,51 @@ class User_map { public: + /* User_map iterator */ + + class Iterator + { + public: + Iterator(User_map *user_map_arg) : + cur_idx(0), user_map(user_map_arg) + { } + + public: + void reset(); + + User *next(); + + private: + User_map *user_map; + uint cur_idx; + }; + +public: + User_map(); ~User_map(); int init(); - int load(const char *password_file_name); - int authenticate(const char *user_name, uint length, + int load(const char *password_file_name, const char **err_msg); + int save(const char *password_file_name, const char **err_msg); + int authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const; + + const User *find_user(const LEX_STRING *user_name) const; + User *find_user(const LEX_STRING *user_name); + + bool add_user(User *user); + bool remove_user(User *user); + +private: + User_map(const User_map &); + User_map &operator =(const User_map &); + private: HASH hash; + bool initialized; + + friend class Iterator; }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 05b1efdbe5193f0aa21971f5c63f8d201578deaa..2b44fbdcc793175f87bebe481748a598c3807ac5 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -51,7 +51,7 @@ ADD_EXECUTABLE(mysqld ../sql-common/client.c derror.cc des_key_file.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc time.cc tztime.cc uniques.cc unireg.cc item_xmlfunc.cc - rpl_tblmap.cc sql_binlog.cc event_executor.cc event_timed.cc + rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_timed.cc sql_tablespace.cc event.cc ../sql-common/my_user.c partition_info.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc diff --git a/sql/Makefile.am b/sql/Makefile.am index deaf2427aebe85d4b3171e071a66de9534f436c9..eec7209bf508ef018cd9bd5b8a8a7d36c0d8cc74 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -66,7 +66,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ parse_file.h sql_view.h sql_trigger.h \ sql_array.h sql_cursor.h event.h event_priv.h \ sql_plugin.h authors.h sql_partition.h \ - partition_info.h partition_element.h + partition_info.h partition_element.h event_scheduler.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \ @@ -103,7 +103,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ tztime.cc my_time.c my_user.c my_decimal.cc\ sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \ sp_cache.cc parse_file.cc sql_trigger.cc \ - event_executor.cc event.cc event_timed.cc \ + event_scheduler.cc event.cc event_timed.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc diff --git a/sql/event.cc b/sql/event.cc index 4a3c6aad30c16ec524d3d47866e8b13806dae480..7c3f17304aaeaef1ba9c482d3d286478875dc739 100644 --- a/sql/event.cc +++ b/sql/event.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2005 MySQL AB +/* Copyright (C) 2004-2006 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,13 +16,12 @@ #include "event_priv.h" #include "event.h" +#include "event_scheduler.h" #include "sp.h" +#include "sp_head.h" /* TODO list : - - The default value of created/modified should not be 0000-00-00 because of - STRICT mode restricions. - - CREATE EVENT should not go into binary log! Does it now? The SQL statements issued by the EVENT are replicated. I have an idea how to solve the problem at failover. So the status field @@ -38,23 +37,8 @@ ENABLED to DISABLED status change and this is safe for replicating. As well an event may be deleted which is also safe for RBR. - - Maybe move all allocations during parsing to evex_mem_root thus saving - double parsing in evex_create_event! - - - If the server is killed (stopping) try to kill executing events? - - - What happens if one renames an event in the DB while it is in memory? - Or even deleting it? - - - Consider using conditional variable when doing shutdown instead of - waiting till all worker threads end. - - - Make Event_timed::get_show_create_event() work - - Add logging to file - - Move comparison code to class Event_timed - Warning: - For now parallel execution is not possible because the same sp_head cannot be executed few times!!! There is still no lock attached to particular event. @@ -62,12 +46,26 @@ */ -QUEUE EVEX_EQ_NAME; MEM_ROOT evex_mem_root; time_t mysql_event_last_create_time= 0L; -static TABLE_FIELD_W_TYPE event_table_fields[EVEX_FIELD_COUNT] = { +const char *event_scheduler_state_names[]= + { "OFF", "0", "ON", "1", "SUSPEND", "2", NullS }; + +TYPELIB Events::opt_typelib= +{ + array_elements(event_scheduler_state_names)-1, + "", + event_scheduler_state_names, + NULL +}; + + +ulong Events::opt_event_scheduler= 2; + +static +TABLE_FIELD_W_TYPE event_table_fields[Events::FIELD_COUNT] = { { {(char *) STRING_WITH_LEN("db")}, {(char *) STRING_WITH_LEN("char(64)")}, @@ -186,41 +184,17 @@ LEX_STRING interval_type_to_name[] = { }; - -/* - Inits the scheduler queue - prioritized queue from mysys/queue.c - - Synopsis - evex_queue_init() - - queue - pointer the the memory to be initialized as queue. has to be - allocated from the caller - - Notes - During initialization the queue is sized for 30 events, and when is full - will auto extent with 30. -*/ - -void -evex_queue_init(EVEX_QUEUE_TYPE *queue) -{ - if (init_queue_ex(queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, - event_timed_compare_q, NULL, 30 /*auto_extent*/)) - sql_print_error("Insufficient memory to initialize executing queue."); -} - - /* Compares 2 LEX strings regarding case. - Synopsis + SYNOPSIS my_time_compare() s - first LEX_STRING t - second LEX_STRING cs - charset - RETURNS: + RETURN VALUE -1 - s < t 0 - s == t 1 - s > t @@ -239,32 +213,26 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) /* Compares 2 TIME structures - Synopsis + SYNOPSIS my_time_compare() a - first TIME b - second time - RETURNS: + RETURN VALUE -1 - a < b 0 - a == b 1 - a > b - Notes + NOTES TIME.second_part is not considered during comparison */ int my_time_compare(TIME *a, TIME *b) { - -#ifdef ENABLE_WHEN_WE_HAVE_MILLISECOND_IN_TIMESTAMPS - my_ulonglong a_t= TIME_to_ulonglong_datetime(a)*100L + a->second_part; - my_ulonglong b_t= TIME_to_ulonglong_datetime(b)*100L + b->second_part; -#else my_ulonglong a_t= TIME_to_ulonglong_datetime(a); my_ulonglong b_t= TIME_to_ulonglong_datetime(b); -#endif if (a_t > b_t) return 1; @@ -275,37 +243,12 @@ my_time_compare(TIME *a, TIME *b) } -/* - Compares the execute_at members of 2 Event_timed instances - - Synopsis - event_timed_compare() - - a - first Event_timed object - b - second Event_timed object - - RETURNS: - -1 - a->execute_at < b->execute_at - 0 - a->execute_at == b->execute_at - 1 - a->execute_at > b->execute_at - - Notes - execute_at.second_part is not considered during comparison -*/ - -int -event_timed_compare(Event_timed *a, Event_timed *b) -{ - return my_time_compare(&a->execute_at, &b->execute_at); -} - - /* Compares the execute_at members of 2 Event_timed instances. Used as callback for the prioritized queue when shifting elements inside. - Synopsis + SYNOPSIS event_timed_compare() vptr - not used (set it to NULL) @@ -324,7 +267,8 @@ event_timed_compare(Event_timed *a, Event_timed *b) int event_timed_compare_q(void *vptr, byte* a, byte *b) { - return event_timed_compare((Event_timed *)a, (Event_timed *)b); + return my_time_compare(&((Event_timed *)a)->execute_at, + &((Event_timed *)b)->execute_at); } @@ -335,8 +279,8 @@ event_timed_compare_q(void *vptr, byte* a, byte *b) YEAR_MONTH - expression is in months DAY_MINUTE - expression is in minutes - Synopsis - event_reconstruct_interval_expression() + SYNOPSIS + Events::reconstruct_interval_expression() buf - preallocated String buffer to add the value to interval - the interval type (for instance YEAR_MONTH) expression - the value in the lowest entity @@ -347,9 +291,9 @@ event_timed_compare_q(void *vptr, byte* a, byte *b) */ int -event_reconstruct_interval_expression(String *buf, - interval_type interval, - longlong expression) +Events::reconstruct_interval_expression(String *buf, + interval_type interval, + longlong expression) { ulonglong expr= expression; char tmp_buff[128], *end; @@ -466,19 +410,20 @@ event_reconstruct_interval_expression(String *buf, Open mysql.event table for read SYNOPSIS - evex_open_event_table_for_read() + Events::open_event_table() thd Thread context lock_type How to lock the table table The table pointer - RETURN + RETURN VALUE 1 Cannot lock table 2 The table is corrupted - different number of fields 0 OK */ int -evex_open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table) +Events::open_event_table(THD *thd, enum thr_lock_type lock_type, + TABLE **table) { TABLE_LIST tables; DBUG_ENTER("open_proc_table"); @@ -491,7 +436,8 @@ evex_open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table) if (simple_open_n_lock_tables(thd, &tables)) DBUG_RETURN(1); - if (table_check_intact(tables.table, EVEX_FIELD_COUNT, event_table_fields, + if (table_check_intact(tables.table, Events::FIELD_COUNT, + event_table_fields, &mysql_event_last_create_time, ER_CANNOT_LOAD_FROM_TABLE)) { @@ -558,50 +504,56 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, 'db' and 'name' and the first key is the primary key over the same fields. */ - if (dbname.length > table->field[EVEX_FIELD_DB]->field_length || - ev_name.length > table->field[EVEX_FIELD_NAME]->field_length || - user_name.length > table->field[EVEX_FIELD_DEFINER]->field_length) + if (dbname.length > table->field[Events::FIELD_DB]->field_length || + ev_name.length > table->field[Events::FIELD_NAME]->field_length || + user_name.length > table->field[Events::FIELD_DEFINER]->field_length) DBUG_RETURN(EVEX_KEY_NOT_FOUND); - table->field[EVEX_FIELD_DB]->store(dbname.str, dbname.length, &my_charset_bin); - table->field[EVEX_FIELD_NAME]->store(ev_name.str, ev_name.length, - &my_charset_bin); - table->field[EVEX_FIELD_DEFINER]->store(user_name.str, user_name.length, - &my_charset_bin); + table->field[Events::FIELD_DB]->store(dbname.str, dbname.length, + &my_charset_bin); + table->field[Events::FIELD_NAME]->store(ev_name.str, ev_name.length, + &my_charset_bin); + table->field[Events::FIELD_DEFINER]->store(user_name.str, + user_name.length, + &my_charset_bin); key_copy(key, table->record[0], table->key_info, table->key_info->key_length); if (table->file->index_read_idx(table->record[0], 0, key, table->key_info->key_length,HA_READ_KEY_EXACT)) + { + DBUG_PRINT("info", ("Row not fonud")); DBUG_RETURN(EVEX_KEY_NOT_FOUND); + } + DBUG_PRINT("info", ("Row found!")); DBUG_RETURN(0); } /* - Puts some data common to CREATE and ALTER EVENT into a row. + Puts some data common to CREATE and ALTER EVENT into a row. - SYNOPSIS - evex_fill_row() - thd THD - table the row to fill out - et Event's data + SYNOPSIS + evex_fill_row() + thd THD + table the row to fill out + et Event's data - Returns - 0 - ok - EVEX_GENERAL_ERROR - bad data - EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - bad data + EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? - DESCRIPTION - Used both when an event is created and when it is altered. + DESCRIPTION + Used both when an event is created and when it is altered. */ static int evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) { - enum evex_table_field field_num; + enum Events::enum_table_field field_num; DBUG_ENTER("evex_fill_row"); @@ -609,19 +561,20 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) DBUG_PRINT("info", ("name =[%s]", et->name.str)); DBUG_PRINT("info", ("body =[%s]", et->body.str)); - if (table->field[field_num= EVEX_FIELD_DB]-> + if (table->field[field_num= Events::FIELD_DB]-> store(et->dbname.str, et->dbname.length, system_charset_info)) goto trunc_err; - if (table->field[field_num= EVEX_FIELD_NAME]-> + if (table->field[field_num= Events::FIELD_NAME]-> store(et->name.str, et->name.length, system_charset_info)) goto trunc_err; - /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull() */ - table->field[EVEX_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, - true); + /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ + table->field[Events::FIELD_ON_COMPLETION]-> + store((longlong)et->on_completion, true); - table->field[EVEX_FIELD_STATUS]->store((longlong)et->status, true); + table->field[Events::FIELD_STATUS]-> + store((longlong)et->status, true); /* Change the SQL_MODE only if body was present in an ALTER EVENT and of course @@ -629,53 +582,54 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) */ if (et->body.str) { - table->field[EVEX_FIELD_SQL_MODE]->store((longlong)thd->variables.sql_mode, - true); + table->field[Events::FIELD_SQL_MODE]-> + store((longlong)thd->variables.sql_mode, true); - if (table->field[field_num= EVEX_FIELD_BODY]-> + if (table->field[field_num= Events::FIELD_BODY]-> store(et->body.str, et->body.length, system_charset_info)) goto trunc_err; } if (et->expression) { - table->field[EVEX_FIELD_INTERVAL_EXPR]->set_notnull(); - table->field[EVEX_FIELD_INTERVAL_EXPR]->store((longlong)et->expression,true); + table->field[Events::FIELD_INTERVAL_EXPR]->set_notnull(); + table->field[Events::FIELD_INTERVAL_EXPR]-> + store((longlong)et->expression, true); - table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->set_notnull(); + table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_notnull(); /* In the enum (C) intervals start from 0 but in mysql enum valid values start from 1. Thus +1 offset is needed! */ - table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->store((longlong)et->interval+1, - true); + table->field[Events::FIELD_TRANSIENT_INTERVAL]-> + store((longlong)et->interval+1, true); - table->field[EVEX_FIELD_EXECUTE_AT]->set_null(); + table->field[Events::FIELD_EXECUTE_AT]->set_null(); if (!et->starts_null) { - table->field[EVEX_FIELD_STARTS]->set_notnull(); - table->field[EVEX_FIELD_STARTS]-> + table->field[Events::FIELD_STARTS]->set_notnull(); + table->field[Events::FIELD_STARTS]-> store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); } if (!et->ends_null) { - table->field[EVEX_FIELD_ENDS]->set_notnull(); - table->field[EVEX_FIELD_ENDS]-> + table->field[Events::FIELD_ENDS]->set_notnull(); + table->field[Events::FIELD_ENDS]-> store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); } } else if (et->execute_at.year) { - table->field[EVEX_FIELD_INTERVAL_EXPR]->set_null(); - table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->set_null(); - table->field[EVEX_FIELD_STARTS]->set_null(); - table->field[EVEX_FIELD_ENDS]->set_null(); + table->field[Events::FIELD_INTERVAL_EXPR]->set_null(); + table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_null(); + table->field[Events::FIELD_STARTS]->set_null(); + table->field[Events::FIELD_ENDS]->set_null(); - table->field[EVEX_FIELD_EXECUTE_AT]->set_notnull(); - table->field[EVEX_FIELD_EXECUTE_AT]->store_time(&et->execute_at, - MYSQL_TIMESTAMP_DATETIME); + table->field[Events::FIELD_EXECUTE_AT]->set_notnull(); + table->field[Events::FIELD_EXECUTE_AT]-> + store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); } else { @@ -686,13 +640,12 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) */ } - ((Field_timestamp *)table->field[EVEX_FIELD_MODIFIED])->set_time(); + ((Field_timestamp *)table->field[Events::FIELD_MODIFIED])->set_time(); if (et->comment.str) { - if (table->field[field_num= EVEX_FIELD_COMMENT]->store(et->comment.str, - et->comment.length, - system_charset_info)) + if (table->field[field_num= Events::FIELD_COMMENT]-> + store(et->comment.str, et->comment.length, system_charset_info)) goto trunc_err; } @@ -704,28 +657,30 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) /* - Creates an event in mysql.event - - SYNOPSIS - db_create_event() - thd THD - et Event_timed object containing information for the event - create_if_not - if an warning should be generated in case event exists - rows_affected - how many rows were affected - - Return value - 0 - OK - EVEX_GENERAL_ERROR - Failure - DESCRIPTION - Creates an event. Relies on evex_fill_row which is shared with - db_update_event. The name of the event is inside "et". + Creates an event in mysql.event + + SYNOPSIS + db_create_event() + thd THD + et Event_timed object containing information for the event + create_if_not If an warning should be generated in case event exists + rows_affected How many rows were affected + + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - Failure + + DESCRIPTION + Creates an event. Relies on evex_fill_row which is shared with + db_update_event. The name of the event is inside "et". */ -static int +int db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, uint *rows_affected) { int ret= 0; + CHARSET_INFO *scs= system_charset_info; TABLE *table; char olddb[128]; bool dbchanged= false; @@ -734,7 +689,7 @@ db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, *rows_affected= 0; DBUG_PRINT("info", ("open mysql.event for update")); - if (evex_open_event_table(thd, TL_WRITE, &table)) + if (Events::open_event_table(thd, TL_WRITE, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); goto err; @@ -778,7 +733,7 @@ db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, goto err; } - if (et->body.length > table->field[EVEX_FIELD_BODY]->field_length) + if (et->body.length > table->field[Events::FIELD_BODY]->field_length) { my_error(ER_TOO_LONG_BODY, MYF(0), et->name.str); goto err; @@ -791,15 +746,14 @@ db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, goto err; } - if ((ret=table->field[EVEX_FIELD_DEFINER]->store(et->definer.str, - et->definer.length, - system_charset_info))) + if ((ret=table->field[Events::FIELD_DEFINER]-> + store(et->definer.str, et->definer.length, scs))) { my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); goto err; } - ((Field_timestamp *)table->field[EVEX_FIELD_CREATED])->set_time(); + ((Field_timestamp *)table->field[Events::FIELD_CREATED])->set_time(); /* evex_fill_row() calls my_error() in case of error so no need to @@ -819,8 +773,8 @@ db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, { thd->clear_error(); /* Such a statement can always go directly to binlog, no trans cache */ - thd->binlog_query(THD::MYSQL_QUERY_TYPE, - thd->query, thd->query_length, FALSE, FALSE); + thd->binlog_query(THD::MYSQL_QUERY_TYPE, thd->query, thd->query_length, + FALSE, FALSE); } #endif @@ -842,17 +796,21 @@ db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, /* - Used to execute ALTER EVENT. Pendant to evex_update_event(). + Used to execute ALTER EVENT. Pendant to Events::update_event(). - SYNOPSIS - db_update_event() - thd THD - sp_name the name of the event to alter - et event's data + SYNOPSIS + db_update_event() + thd THD + sp_name the name of the event to alter + et event's data + + RETURN VALUE + 0 OK + EVEX_GENERAL_ERROR Error occured (my_error() called) - NOTES - sp_name is passed since this is the name of the event to - alter in case of RENAME TO. + NOTES + sp_name is passed since this is the name of the event to + alter in case of RENAME TO. */ static int @@ -863,12 +821,12 @@ db_update_event(THD *thd, Event_timed *et, sp_name *new_name) DBUG_ENTER("db_update_event"); DBUG_PRINT("enter", ("dbname: %.*s", et->dbname.length, et->dbname.str)); DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - DBUG_PRINT("enter", ("user: %.*s", et->name.length, et->name.str)); + DBUG_PRINT("enter", ("user: %.*s", et->definer.length, et->definer.str)); if (new_name) DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, new_name->m_name.str)); - if (evex_open_event_table(thd, TL_WRITE, &table)) + if (Events::open_event_table(thd, TL_WRITE, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); goto err; @@ -914,9 +872,9 @@ db_update_event(THD *thd, Event_timed *et, sp_name *new_name) if (new_name) { - table->field[EVEX_FIELD_DB]-> + table->field[Events::FIELD_DB]-> store(new_name->m_db.str, new_name->m_db.length, system_charset_info); - table->field[EVEX_FIELD_NAME]-> + table->field[Events::FIELD_NAME]-> store(new_name->m_name.str, new_name->m_name.length, system_charset_info); } @@ -938,33 +896,33 @@ db_update_event(THD *thd, Event_timed *et, sp_name *new_name) /* - Looks for a named event in mysql.event and in case of success returns - an object will data loaded from the table. - - SYNOPSIS - db_find_event() - thd THD - name the name of the event to find - definer who owns the event - ett event's data if event is found - tbl TABLE object to use when not NULL - - NOTES - 1) Use sp_name for look up, return in **ett if found - 2) tbl is not closed at exit - - RETURN - 0 ok In this case *ett is set to the event - # error *ett == 0 + Looks for a named event in mysql.event and in case of success returns + an object will data loaded from the table. + + SYNOPSIS + db_find_event() + thd THD + name the name of the event to find + definer who owns the event + ett event's data if event is found + tbl TABLE object to use when not NULL + + NOTES + 1) Use sp_name for look up, return in **ett if found + 2) tbl is not closed at exit + + RETURN VALUE + 0 ok In this case *ett is set to the event + # error *ett == 0 */ -static int +int db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett, TABLE *tbl, MEM_ROOT *root) { TABLE *table; int ret; - Event_timed *et= 0; + Event_timed *et=NULL; DBUG_ENTER("db_find_event"); DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); @@ -973,7 +931,7 @@ db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett, if (tbl) table= tbl; - else if (evex_open_event_table(thd, TL_READ, &table)) + else if (Events::open_event_table(thd, TL_READ, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); ret= EVEX_GENERAL_ERROR; @@ -1001,7 +959,7 @@ db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett, } done: - if (ret) + if (ret && et) { delete et; et= 0; @@ -1015,179 +973,43 @@ db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett, /* - Looks for a named event in mysql.event and then loads it from - the table, compiles it and insert it into the cache. - - SYNOPSIS - evex_load_and_compile_event() - thd THD - spn the name of the event to alter - definer who is the owner - use_lock whether to obtain a lock on LOCK_event_arrays or not - - RETURN VALUE - 0 - OK - < 0 - error (in this case underlying functions call my_error()). -*/ - -static int -evex_load_and_compile_event(THD * thd, sp_name *spn, LEX_STRING definer, - bool use_lock) -{ - int ret= 0; - MEM_ROOT *tmp_mem_root; - Event_timed *ett; - Open_tables_state backup; - - DBUG_ENTER("db_load_and_compile_event"); - DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); - - tmp_mem_root= thd->mem_root; - thd->mem_root= &evex_mem_root; - - thd->reset_n_backup_open_tables_state(&backup); - /* no need to use my_error() here because db_find_event() has done it */ - ret= db_find_event(thd, spn, &definer, &ett, NULL, NULL); - thd->restore_backup_open_tables_state(&backup); - if (ret) - goto done; - - ett->compute_next_execution_time(); - if (use_lock) - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - - evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) ett); - - /* - There is a copy in the array which we don't need. sphead won't be - destroyed. - */ - - if (use_lock) - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - -done: - if (thd->mem_root != tmp_mem_root) - thd->mem_root= tmp_mem_root; - - DBUG_RETURN(ret); -} - - -/* - Removes from queue in memory the event which is identified by the tupple - (db, name). - - SYNOPSIS - evex_remove_from_cache() - - db - db name - name - event name - use_lock - whether to lock the mutex LOCK_event_arrays or not in case it - has been already locked outside - is_drop - if an event is currently being executed then we can also delete - the Event_timed instance, so we alarm the event that it should - drop itself if this parameter is set to TRUE. It's false on - ALTER EVENT. - - RETURNS - 0 OK (always) -*/ - -static int -evex_remove_from_cache(LEX_STRING *db, LEX_STRING *name, bool use_lock, - bool is_drop) -{ - //ToDo : Add definer to the tuple (db, name) to become triple - uint i; - int ret= 0; - - DBUG_ENTER("evex_remove_from_cache"); - /* - It is possible that 2 (or 1) pass(es) won't find the event in memory. - The reason is that DISABLED events are not cached. - */ - - if (use_lock) - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - - for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i) - { - Event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, Event_timed*); - DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?",db->str,name->str, et->dbname.str, - et->name.str)); - if (!sortcmp_lex_string(*name, et->name, system_charset_info) && - !sortcmp_lex_string(*db, et->dbname, system_charset_info)) - { - if (et->can_spawn_now()) - { - DBUG_PRINT("evex_remove_from_cache", ("not running - free and delete")); - et->free_sp(); - delete et; - } - else - { - DBUG_PRINT("evex_remove_from_cache", - ("running.defer mem free. is_drop=%d", is_drop)); - et->flags|= EVENT_EXEC_NO_MORE; - et->dropped= is_drop; - } - DBUG_PRINT("evex_remove_from_cache", ("delete from queue")); - evex_queue_delete_element(&EVEX_EQ_NAME, i); - /* ok, we have cleaned */ - ret= 0; - goto done; - } - } - -done: - if (use_lock) - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + The function exported to the world for creating of events. - DBUG_RETURN(ret); -} + SYNOPSIS + Events::create_event() + thd THD + et event's data + create_options Options specified when in the query. We are + interested whether there is IF NOT EXISTS + rows_affected How many rows were affected + RETURN VALUE + 0 OK + !0 Error -/* - The function exported to the world for creating of events. - - SYNOPSIS - evex_create_event() - thd THD - et event's data - create_options Options specified when in the query. We are - interested whether there is IF NOT EXISTS - rows_affected How many rows were affected - - NOTES - - in case there is an event with the same name (db) and - IF NOT EXISTS is specified, an warning is put into the W stack. + NOTES + - in case there is an event with the same name (db) and + IF NOT EXISTS is specified, an warning is put into the W stack. */ int -evex_create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected) +Events::create_event(THD *thd, Event_timed *et, uint create_options, + uint *rows_affected) { - int ret = 0; + int ret; - DBUG_ENTER("evex_create_event"); + DBUG_ENTER("Events::create_event"); DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, et->name.str, create_options)); - if ((ret = db_create_event(thd, et, + if (!(ret = db_create_event(thd, et, create_options & HA_LEX_CREATE_IF_NOT_EXISTS, rows_affected))) - goto done; - - VOID(pthread_mutex_lock(&LOCK_evex_running)); - if (evex_is_running && et->status == MYSQL_EVENT_ENABLED) { - sp_name spn(et->dbname, et->name); - ret= evex_load_and_compile_event(thd, &spn, et->definer, true); + Event_scheduler *scheduler= Event_scheduler::get_instance(); + if (scheduler->initialized() && (ret= scheduler->add_event(thd, et, true))) + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - -done: /* No need to close the table, it will be closed in sql_parse::do_command */ DBUG_RETURN(ret); @@ -1195,73 +1017,63 @@ evex_create_event(THD *thd, Event_timed *et, uint create_options, /* - The function exported to the world for alteration of events. - - SYNOPSIS - evex_update_event() - thd THD - et event's data - new_name set in case of RENAME TO. - - NOTES - et contains data about dbname and event name. - new_name is the new name of the event, if not null (this means - that RENAME TO was specified in the query) + The function exported to the world for alteration of events. + + SYNOPSIS + Events::update_event() + thd THD + et event's data + new_name set in case of RENAME TO. + + RETURN VALUE + 0 OK + !0 Error + + NOTES + et contains data about dbname and event name. + new_name is the new name of the event, if not null (this means + that RENAME TO was specified in the query) */ int -evex_update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected) +Events::update_event(THD *thd, Event_timed *et, sp_name *new_name, + uint *rows_affected) { int ret; - bool need_second_pass= true; - DBUG_ENTER("evex_update_event"); + DBUG_ENTER("Events::update_event"); DBUG_PRINT("enter", ("name: %*s", et->name.length, et->name.str)); - /* db_update_event() opens & closes the table to prevent crash later in the code when loading and compiling the new definition. Also on error conditions my_error() is called so no need to handle here */ - if ((ret= db_update_event(thd, et, new_name))) - goto done; - - VOID(pthread_mutex_lock(&LOCK_evex_running)); - if (!evex_is_running) - UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_evex_running, done); - - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - evex_remove_from_cache(&et->dbname, &et->name, false, false); - if (et->status == MYSQL_EVENT_ENABLED) + if (!(ret= db_update_event(thd, et, new_name))) { - if (new_name) - ret= evex_load_and_compile_event(thd, new_name, et->definer, false); - else - { - sp_name spn(et->dbname, et->name); - ret= evex_load_and_compile_event(thd, &spn, et->definer, false); - } - if (ret == EVEX_COMPILE_ERROR) - my_error(ER_EVENT_COMPILE_ERROR, MYF(0)); + Event_scheduler *scheduler= Event_scheduler::get_instance(); + if (scheduler->initialized() && + (ret= scheduler->replace_event(thd, et, + new_name? &new_name->m_db: NULL, + new_name? &new_name->m_name: NULL))) + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - -done: DBUG_RETURN(ret); } /* - Drops an event - - SYNOPSIS - db_drop_event() - thd THD - et event's name - drop_if_exists if set and the event not existing => warning onto the stack - rows_affected affected number of rows is returned heres + Drops an event + + SYNOPSIS + db_drop_event() + thd THD + et event's name + drop_if_exists if set and the event not existing => warning onto the stack + rows_affected affected number of rows is returned heres + + RETURN VALUE + 0 OK + !0 Error (my_error() called) */ int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, @@ -1275,7 +1087,7 @@ int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, ret= EVEX_OPEN_TABLE_FAILED; thd->reset_n_backup_open_tables_state(&backup); - if (evex_open_event_table(thd, TL_WRITE, &table)) + if (Events::open_event_table(thd, TL_WRITE, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); goto done; @@ -1315,58 +1127,54 @@ int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, /* - Drops an event - - SYNOPSIS - evex_drop_event() - thd THD - et event's name - drop_if_exists if set and the event not existing => warning onto the stack - rows_affected affected number of rows is returned heres - + Drops an event + + SYNOPSIS + Events::drop_event() + thd THD + et event's name + drop_if_exists if set and the event not existing => warning onto the stack + rows_affected affected number of rows is returned heres + + RETURN VALUE + 0 OK + !0 Error (reported) */ int -evex_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +Events::drop_event(THD *thd, Event_timed *et, bool drop_if_exists, + uint *rows_affected) { - int ret= 0; - - DBUG_ENTER("evex_drop_event"); - - - VOID(pthread_mutex_lock(&LOCK_evex_running)); - if (evex_is_running) - ret= evex_remove_from_cache(&et->dbname, &et->name, true, true); - VOID(pthread_mutex_unlock(&LOCK_evex_running)); + int ret; - if (ret == 1) - ret= 0; - else if (ret == 0) - ret= db_drop_event(thd, et, drop_if_exists, rows_affected); - else - my_error(ER_UNKNOWN_ERROR, MYF(0)); + DBUG_ENTER("Events::drop_event"); + if (!(ret= db_drop_event(thd, et, drop_if_exists, rows_affected))) + { + Event_scheduler *scheduler= Event_scheduler::get_instance(); + if (scheduler->initialized() && (ret= scheduler->drop_event(thd, et))) + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); + } DBUG_RETURN(ret); } /* - SHOW CREATE EVENT + SHOW CREATE EVENT - SYNOPSIS - evex_show_create_event() - thd THD - spn the name of the event (db, name) - definer the definer of the event + SYNOPSIS + Events::show_create_event() + thd THD + spn the name of the event (db, name) + definer the definer of the event - RETURNS - 0 - OK - 1 - Error during writing to the wire + RETURN VALUE + 0 OK + 1 Error during writing to the wire */ int -evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer) +Events::show_create_event(THD *thd, sp_name *spn, LEX_STRING definer) { int ret; Event_timed *et= NULL; @@ -1379,7 +1187,7 @@ evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer) ret= db_find_event(thd, spn, &definer, &et, NULL, thd->mem_root); thd->restore_backup_open_tables_state(&backup); - if (et) + if (!ret) { Protocol *protocol= thd->protocol; char show_str_buf[768]; @@ -1389,12 +1197,10 @@ evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer) ulong sql_mode_len=0; show_str.length(0); + show_str.set_charset(system_charset_info); if (et->get_create_event(thd, &show_str)) - { - delete et; - DBUG_RETURN(1); - } + goto err; field_list.push_back(new Item_empty_string("Event", NAME_LEN)); @@ -1408,201 +1214,216 @@ evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer) show_str.length())); if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - { - delete et; - DBUG_RETURN(1); - } + goto err; + protocol->prepare_for_resend(); protocol->store(et->name.str, et->name.length, system_charset_info); protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info); - protocol->store(show_str.ptr(), show_str.length(), system_charset_info); + protocol->store(show_str.c_ptr(), show_str.length(), system_charset_info); ret= protocol->write(); send_eof(thd); - delete et; } - + delete et; DBUG_RETURN(ret); +err: + delete et; + DBUG_RETURN(1); } /* - evex_drop_db_events - Drops all events in the selected database + Drops all events from a schema - thd - Thread - db - ASCIIZ the name of the database - - Returns: - 0 - OK - 1 - Failed to delete a specific row - 2 - Got NULL while reading db name from a row - - Note: - The algo is the following - 1. Go through the in-memory cache, if the scheduler is working - and for every event whose dbname matches the database we drop - check whether is currently in execution: - - Event_timed::can_spawn() returns true -> the event is not - being executed in a child thread. The reason not to use - Event_timed::is_running() is that the latter shows only if - it is being executed, which is 99% of the time in the thread - but there are some initiliazations before and after the - anonymous SP is being called. So if we delete in this moment - -=> *boom*, so we have to check whether the thread has been - spawned and can_spawn() is the right method. - - Event_timed::can_spawn() returns false -> being runned ATM - just set the flags so it should drop itself. + SYNOPSIS + Events::drop_schema_events() + thd Thread + db ASCIIZ schema name + + RETURN VALUE + 0 OK + !0 Error */ int -evex_drop_db_events(THD *thd, char *db) +Events::drop_schema_events(THD *thd, char *db) { - TABLE *table; - READ_RECORD read_record_info; int ret= 0; - uint i; LEX_STRING db_lex= {db, strlen(db)}; DBUG_ENTER("evex_drop_db_events"); - DBUG_PRINT("info",("dropping events from %s", db)); + DBUG_PRINT("enter", ("dropping events from %s", db)); - VOID(pthread_mutex_lock(&LOCK_event_arrays)); + Event_scheduler *scheduler= Event_scheduler::get_instance(); + if (scheduler->initialized()) + ret= scheduler->drop_schema_events(thd, &db_lex); + else + ret= db_drop_events_from_table(thd, &db_lex); - if ((ret= evex_open_event_table(thd, TL_WRITE, &table))) - { - sql_print_error("Table mysql.event is damaged."); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - DBUG_RETURN(SP_OPEN_TABLE_FAILED); - } + DBUG_RETURN(ret); +} - DBUG_PRINT("info",("%d elements in the queue", - evex_queue_num_elements(EVEX_EQ_NAME))); - VOID(pthread_mutex_lock(&LOCK_evex_running)); - if (!evex_is_running) - goto skip_memory; - for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i) - { - Event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, Event_timed*); - if (sortcmp_lex_string(et->dbname, db_lex, system_charset_info)) - continue; +/* + Drops all events in the selected database, from mysql.event. - if (et->can_spawn_now_n_lock(thd)) - { - DBUG_PRINT("info",("event %s not running - direct delete", et->name.str)); - if (!(ret= evex_db_find_event_aux(thd, et, table))) - { - DBUG_PRINT("info",("event %s found on disk", et->name.str)); - if ((ret= table->file->ha_delete_row(table->record[0]))) - { - sql_print_error("Error while deleting a row - dropping " - "a database. Skipping the rest."); - my_error(ER_EVENT_DROP_FAILED, MYF(0), et->name.str); - goto end; - } - DBUG_PRINT("info",("deleted event [%s] num [%d]. Time to free mem", - et->name.str, i)); - } - else if (ret == EVEX_KEY_NOT_FOUND) - { - sql_print_error("Expected to find event %s.%s of %s on disk-not there.", - et->dbname.str, et->name.str, et->definer.str); - } - et->free_sp(); - delete et; - et= 0; - /* no need to call et->spawn_unlock because we already cleaned et */ - } - else - { - DBUG_PRINT("info",("event %s is running. setting exec_no_more and dropped", - et->name.str)); - et->flags|= EVENT_EXEC_NO_MORE; - et->dropped= TRUE; - } - DBUG_PRINT("info",("%d elements in the queue", - evex_queue_num_elements(EVEX_EQ_NAME))); - evex_queue_delete_element(&EVEX_EQ_NAME, i);// 0 is top - DBUG_PRINT("info",("%d elements in the queue", - evex_queue_num_elements(EVEX_EQ_NAME))); - /* - decrease so we start at the same position, there will be - less elements in the queue, it will still be ordered so on - next iteration it will be again i the current element or if - no more we finish. - */ - --i; - } + SYNOPSIS + evex_drop_db_events_from_table() + thd Thread + db Schema name -skip_memory: - /* - The reasoning behind having two loops is the following: - If there was only one loop, the table-scan, then for every element which - matches, the queue in memory has to be searched to remove the element. - While if we go first over the queue and remove what's in there we have only - one pass over it and after finishing it, moving to table-scan for the disabled - events. This needs quite less time and means quite less locking on - LOCK_event_arrays. - */ - DBUG_PRINT("info",("Mem-cache checked, now going to db for disabled events")); + RETURN VALUE + 0 OK + !0 Error from ha_delete_row +*/ + +int +db_drop_events_from_table(THD *thd, LEX_STRING *db) +{ + int ret; + TABLE *table; + READ_RECORD read_record_info; + DBUG_ENTER("db_drop_events_from_table"); + DBUG_PRINT("info", ("dropping events from %s", db->str)); + + if ((ret= Events::open_event_table(thd, TL_WRITE, &table))) + { + sql_print_error("Table mysql.event is damaged."); + DBUG_RETURN(ret); + } /* only enabled events are in memory, so we go now and delete the rest */ - init_read_record(&read_record_info, thd, table ,NULL,1,0); + init_read_record(&read_record_info, thd, table, NULL, 1, 0); while (!(read_record_info.read_record(&read_record_info)) && !ret) { - char *et_db; + char *et_db= get_field(thd->mem_root, + table->field[Events::FIELD_DB]); - if ((et_db= get_field(thd->mem_root, table->field[EVEX_FIELD_DB])) == NULL) - { - ret= 2; - break; - } - LEX_STRING et_db_lex= {et_db, strlen(et_db)}; - if (!sortcmp_lex_string(et_db_lex, db_lex, system_charset_info)) + DBUG_PRINT("info", ("Current event %s.%s", et_db, + get_field(thd->mem_root, + table->field[Events::FIELD_NAME]))); + + if (!sortcmp_lex_string(et_db_lex, *db, system_charset_info)) { - Event_timed ett; - char *ptr; - - if ((ptr= get_field(thd->mem_root, table->field[EVEX_FIELD_STATUS])) - == NullS) - { - sql_print_error("Error while loading from mysql.event. " - "Table probably corrupted"); - goto end; - } - /* - When not running nothing is in memory so we have to clean - everything. - We don't delete EVENT_ENABLED events when the scheduler is running - because maybe this is an event which we asked to drop itself when - it is finished and it hasn't finished yet, so we don't touch it. - It will drop itself. The not running ENABLED events has been already - deleted from ha_delete_row() above in the loop over the QUEUE - (in case the executor is running). - 'D' stands for DISABLED, 'E' for ENABLED - it's an enum - */ - if ((evex_is_running && ptr[0] == 'D') || !evex_is_running) - { - DBUG_PRINT("info", ("Dropping %s.%s", et_db, ett.name.str)); - if ((ret= table->file->ha_delete_row(table->record[0]))) - { - my_error(ER_EVENT_DROP_FAILED, MYF(0), ett.name.str); - goto end; - } - } + DBUG_PRINT("info", ("Dropping")); + if ((ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_DROP_FAILED, MYF(0), + get_field(thd->mem_root, + table->field[Events::FIELD_NAME])); } } - DBUG_PRINT("info",("Disk checked for disabled events. Finishing.")); - -end: - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); end_read_record(&read_record_info); - thd->version--; /* Force close to free memory */ close_thread_tables(thd); DBUG_RETURN(ret); } + + + +/* + Inits the scheduler's structures. + + SYNOPSIS + Events::init() + + NOTES + This function is not synchronized. + + RETURN VALUE + 0 OK + 1 Error +*/ + +int +Events::init() +{ + int ret= 0; + DBUG_ENTER("Events::init"); + + /* it should be an assignment! */ + if (opt_event_scheduler) + { + Event_scheduler *scheduler= Event_scheduler::get_instance(); + DBUG_ASSERT(opt_event_scheduler == 1 || opt_event_scheduler == 2); + DBUG_RETURN(scheduler->init() || + (opt_event_scheduler == 1? scheduler->start(): + scheduler->start_suspended())); + } + DBUG_RETURN(0); +} + + +/* + Cleans up scheduler's resources. Called at server shutdown. + + SYNOPSIS + Events::shutdown() + + NOTES + This function is not synchronized. +*/ + +void +Events::shutdown() +{ + DBUG_ENTER("Events::shutdown"); + Event_scheduler *scheduler= Event_scheduler::get_instance(); + if (scheduler->initialized()) + { + scheduler->stop(); + scheduler->destroy(); + } + + DBUG_VOID_RETURN; +} + + +/* + Proxy for Event_scheduler::dump_internal_status + + SYNOPSIS + Events::dump_internal_status() + thd Thread + + RETURN VALUE + 0 OK + !0 Error +*/ + +int +Events::dump_internal_status(THD *thd) +{ + return Event_scheduler::dump_internal_status(thd); +} + + +/* + Inits Events mutexes + + SYNOPSIS + Events::init_mutexes() + thd Thread +*/ + +void +Events::init_mutexes() +{ + Event_scheduler::init_mutexes(); +} + + +/* + Destroys Events mutexes + + SYNOPSIS + Events::destroy_mutexes() +*/ + +void +Events::destroy_mutexes() +{ + Event_scheduler::destroy_mutexes(); +} diff --git a/sql/event.h b/sql/event.h index 27de8b46e32a7793e023d10d3ba5e597a266271d..40ede7b0c5f0cf0294c5e8018516ed249aae0c97 100644 --- a/sql/event.h +++ b/sql/event.h @@ -1,4 +1,6 @@ -/* Copyright (C) 2004-2005 MySQL AB +#ifndef _EVENT_H_ +#define _EVENT_H_ +/* Copyright (C) 2004-2006 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,66 +16,109 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _EVENT_H_ -#define _EVENT_H_ -#include "sp.h" -#include "sp_head.h" - -#define EVEX_OK SP_OK -#define EVEX_KEY_NOT_FOUND SP_KEY_NOT_FOUND -#define EVEX_OPEN_TABLE_FAILED SP_OPEN_TABLE_FAILED -#define EVEX_WRITE_ROW_FAILED SP_WRITE_ROW_FAILED -#define EVEX_DELETE_ROW_FAILED SP_DELETE_ROW_FAILED -#define EVEX_GET_FIELD_FAILED SP_GET_FIELD_FAILED -#define EVEX_PARSE_ERROR SP_PARSE_ERROR -#define EVEX_INTERNAL_ERROR SP_INTERNAL_ERROR -#define EVEX_NO_DB_ERROR SP_NO_DB_ERROR + +#define EVEX_OK 0 +#define EVEX_KEY_NOT_FOUND -1 +#define EVEX_OPEN_TABLE_FAILED -2 +#define EVEX_WRITE_ROW_FAILED -3 +#define EVEX_DELETE_ROW_FAILED -4 +#define EVEX_GET_FIELD_FAILED -5 +#define EVEX_PARSE_ERROR -6 +#define EVEX_INTERNAL_ERROR -7 +#define EVEX_NO_DB_ERROR -8 #define EVEX_COMPILE_ERROR -19 #define EVEX_GENERAL_ERROR -20 -#define EVEX_BAD_IDENTIFIER SP_BAD_IDENTIFIER -#define EVEX_BODY_TOO_LONG SP_BODY_TOO_LONG -#define EVEX_BAD_PARAMS -21 -#define EVEX_NOT_RUNNING -22 -#define EVEX_MICROSECOND_UNSUP -23 +#define EVEX_BAD_IDENTIFIER -21 +#define EVEX_BODY_TOO_LONG -22 +#define EVEX_BAD_PARAMS -23 +#define EVEX_NOT_RUNNING -24 +#define EVEX_MICROSECOND_UNSUP -25 +#define EVEX_CANT_KILL -26 #define EVENT_EXEC_NO_MORE (1L << 0) #define EVENT_NOT_USED (1L << 1) +#define EVENT_FREE_WHEN_FINISHED (1L << 2) -extern ulong opt_event_executor; +class Event_timed; -enum enum_event_on_completion +class Events { - MYSQL_EVENT_ON_COMPLETION_DROP = 1, - MYSQL_EVENT_ON_COMPLETION_PRESERVE -}; +public: + static ulong opt_event_scheduler; + static TYPELIB opt_typelib; -enum enum_event_status -{ - MYSQL_EVENT_ENABLED = 1, - MYSQL_EVENT_DISABLED + enum enum_table_field + { + FIELD_DB = 0, + FIELD_NAME, + FIELD_BODY, + FIELD_DEFINER, + FIELD_EXECUTE_AT, + FIELD_INTERVAL_EXPR, + FIELD_TRANSIENT_INTERVAL, + FIELD_CREATED, + FIELD_MODIFIED, + FIELD_LAST_EXECUTED, + FIELD_STARTS, + FIELD_ENDS, + FIELD_STATUS, + FIELD_ON_COMPLETION, + FIELD_SQL_MODE, + FIELD_COMMENT, + FIELD_COUNT /* a cool trick to count the number of fields :) */ + }; + + static int + create_event(THD *thd, Event_timed *et, uint create_options, + uint *rows_affected); + + static int + update_event(THD *thd, Event_timed *et, sp_name *new_name, + uint *rows_affected); + + static int + drop_event(THD *thd, Event_timed *et, bool drop_if_exists, + uint *rows_affected); + + static int + open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); + + static int + show_create_event(THD *thd, sp_name *spn, LEX_STRING definer); + + static int + reconstruct_interval_expression(String *buf, interval_type interval, + longlong expression); + + static int + drop_schema_events(THD *thd, char *db); + + static int + dump_internal_status(THD *thd); + + static int + init(); + + static void + shutdown(); + + static void + init_mutexes(); + + static void + destroy_mutexes(); + + +private: + /* Prevent use of these */ + Events(const Events &); + void operator=(Events &); }; -enum evex_table_field -{ - EVEX_FIELD_DB = 0, - EVEX_FIELD_NAME, - EVEX_FIELD_BODY, - EVEX_FIELD_DEFINER, - EVEX_FIELD_EXECUTE_AT, - EVEX_FIELD_INTERVAL_EXPR, - EVEX_FIELD_TRANSIENT_INTERVAL, - EVEX_FIELD_CREATED, - EVEX_FIELD_MODIFIED, - EVEX_FIELD_LAST_EXECUTED, - EVEX_FIELD_STARTS, - EVEX_FIELD_ENDS, - EVEX_FIELD_STATUS, - EVEX_FIELD_ON_COMPLETION, - EVEX_FIELD_SQL_MODE, - EVEX_FIELD_COMMENT, - EVEX_FIELD_COUNT /* a cool trick to count the number of fields :) */ -} ; + + +class sp_head; class Event_timed { @@ -82,12 +127,26 @@ class Event_timed my_bool in_spawned_thread; ulong locked_by_thread_id; my_bool running; + ulong thread_id; pthread_mutex_t LOCK_running; + pthread_cond_t COND_finished; bool status_changed; bool last_executed_changed; public: + enum enum_status + { + ENABLED = 1, + DISABLED + }; + + enum enum_on_completion + { + ON_COMPLETION_DROP = 1, + ON_COMPLETION_PRESERVE + }; + TIME last_executed; LEX_STRING dbname; @@ -111,8 +170,8 @@ class Event_timed ulonglong created; ulonglong modified; - enum enum_event_on_completion on_completion; - enum enum_event_status status; + enum enum_on_completion on_completion; + enum enum_status status; sp_head *sphead; ulong sql_mode; const uchar *body_begin; @@ -153,36 +212,15 @@ class Event_timed DBUG_ASSERT(0); } + Event_timed(); - Event_timed():in_spawned_thread(0),locked_by_thread_id(0), - running(0), status_changed(false), - last_executed_changed(false), expression(0), created(0), - modified(0), on_completion(MYSQL_EVENT_ON_COMPLETION_DROP), - status(MYSQL_EVENT_ENABLED), sphead(0), sql_mode(0), - body_begin(0), dropped(false), - free_sphead_on_delete(true), flags(0) - - { - pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST); - init(); - } - - ~Event_timed() - { - deinit_mutexes(); - - if (free_sphead_on_delete) - free_sp(); - } + ~Event_timed(); void init(); - + void - deinit_mutexes() - { - pthread_mutex_destroy(&this->LOCK_running); - } + deinit_mutexes(); int init_definer(THD *thd); @@ -214,12 +252,12 @@ class Event_timed bool compute_next_execution_time(); - void - mark_last_executed(THD *thd); - int drop(THD *thd); + void + mark_last_executed(THD *thd); + bool update_fields(THD *thd); @@ -227,142 +265,32 @@ class Event_timed get_create_event(THD *thd, String *buf); int - execute(THD *thd, MEM_ROOT *mem_root= NULL); + execute(THD *thd, MEM_ROOT *mem_root); int - compile(THD *thd, MEM_ROOT *mem_root= NULL); - - my_bool - is_running() - { - my_bool ret; - - VOID(pthread_mutex_lock(&this->LOCK_running)); - ret= running; - VOID(pthread_mutex_unlock(&this->LOCK_running)); - - return ret; - } - - /* - Checks whether the object is being used in a spawned thread. - This method is for very basic checking. Use ::can_spawn_now_n_lock() - for most of the cases. - */ - - my_bool - can_spawn_now() - { - my_bool ret; - VOID(pthread_mutex_lock(&this->LOCK_running)); - ret= !in_spawned_thread; - VOID(pthread_mutex_unlock(&this->LOCK_running)); - return ret; - } + compile(THD *thd, MEM_ROOT *mem_root); - /* - Checks whether this thread can lock the object for modification -> - preventing being spawned for execution, and locks if possible. - use ::can_spawn_now() only for basic checking because a race - condition may occur between the check and eventual modification (deletion) - of the object. - */ - - my_bool - can_spawn_now_n_lock(THD *thd); - - int - spawn_unlock(THD *thd); + bool + is_running(); int - spawn_now(void * (*thread_func)(void*)); + spawn_now(void * (*thread_func)(void*), void *arg); - void + bool spawn_thread_finish(THD *thd); void - free_sp() - { - delete sphead; - sphead= 0; - } -protected: + free_sp(); + bool - change_security_context(THD *thd, Security_context *s_ctx, - Security_context **backup); + has_equal_db(Event_timed *etn); + + int + kill_thread(THD *thd); void - restore_security_context(THD *thd, Security_context *backup); + set_thread_id(ulong tid) { thread_id= tid; } }; -int -evex_create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected); - -int -evex_update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected); - -int -evex_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); - -int -evex_open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - -int -evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer); - -int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); - -int -event_reconstruct_interval_expression(String *buf, - interval_type interval, - longlong expression); - -int -evex_drop_db_events(THD *thd, char *db); - - -int -init_events(); - -void -shutdown_events(); - - -// auxiliary -int -event_timed_compare(Event_timed **a, Event_timed **b); - - - -/* -CREATE TABLE event ( - db char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '', - name char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '', - body longblob NOT NULL, - definer char(77) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '', - execute_at DATETIME default NULL, - interval_value int(11) default NULL, - interval_field ENUM('YEAR','QUARTER','MONTH','DAY','HOUR','MINUTE','WEEK', - 'SECOND','MICROSECOND', 'YEAR_MONTH','DAY_HOUR', - 'DAY_MINUTE','DAY_SECOND', - 'HOUR_MINUTE','HOUR_SECOND', - 'MINUTE_SECOND','DAY_MICROSECOND', - 'HOUR_MICROSECOND','MINUTE_MICROSECOND', - 'SECOND_MICROSECOND') default NULL, - created TIMESTAMP NOT NULL, - modified TIMESTAMP NOT NULL, - last_executed DATETIME default NULL, - starts DATETIME default NULL, - ends DATETIME default NULL, - status ENUM('ENABLED','DISABLED') NOT NULL default 'ENABLED', - on_completion ENUM('DROP','PRESERVE') NOT NULL default 'DROP', - comment varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '', - PRIMARY KEY (definer,db,name) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT 'Events'; -*/ - #endif /* _EVENT_H_ */ diff --git a/sql/event_executor.cc b/sql/event_executor.cc index 21464dd777bfc9359d7d7b3237af8db183e4f8e1..f236fb47771ae037babf981b1c72c792f82a14e4 100644 --- a/sql/event_executor.cc +++ b/sql/event_executor.cc @@ -13,998 +13,3 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#include "event_priv.h" -#include "event.h" -#include "sp.h" - -#define WAIT_STATUS_READY 0 -#define WAIT_STATUS_EMPTY_QUEUE 1 -#define WAIT_STATUS_NEW_TOP_EVENT 2 -#define WAIT_STATUS_STOP_EXECUTOR 3 - - -/* - Make this define DBUG_FAULTY_THR to be able to put breakpoints inside - code used by the scheduler's thread(s). In this case user connections - are not possible because the scheduler thread code is ran inside the - main thread (no spawning takes place. If you want to debug client - connection then start with --one-thread and make the define - DBUG_FAULTY_THR ! -*/ -#define DBUG_FAULTY_THR2 - -extern ulong thread_created; -extern const char *my_localhost; -extern pthread_attr_t connection_attrib; - -pthread_mutex_t LOCK_event_arrays, // mutex for when working with the queue - LOCK_workers_count, // mutex for when inc/dec uint workers_count - LOCK_evex_running; // mutes for managing bool evex_is_running - -static pthread_mutex_t LOCK_evex_main_thread; // mutex for when working with the queue -bool scheduler_main_thread_running= false; - -bool evex_is_running= false; - -ulonglong evex_main_thread_id= 0; -ulong opt_event_executor; -my_bool event_executor_running_global_var; -static my_bool evex_mutexes_initted= FALSE; -static uint workers_count; - -static int -evex_load_events_from_db(THD *thd); - -bool -evex_print_warnings(THD *thd, Event_timed *et); - -/* - TODO Andrey: Check for command line option whether to start - the main thread or not. -*/ - -pthread_handler_t -event_executor_worker(void *arg); - -pthread_handler_t -event_executor_main(void *arg); - - -/* - Returns the seconds difference of 2 TIME structs - - SYNOPSIS - evex_time_diff() - a - TIME struct 1 - b - TIME struct 2 - - Returns: - the seconds difference -*/ - -static int -evex_time_diff(TIME *a, TIME *b) -{ - return sec_since_epoch_TIME(a) - sec_since_epoch_TIME(b); -} - - -/* - Inits the mutexes used by the scheduler module - - SYNOPSIS - evex_init_mutexes() - - NOTES - The mutexes are : - LOCK_event_arrays - LOCK_workers_count - LOCK_evex_running -*/ - -static void -evex_init_mutexes() -{ - if (evex_mutexes_initted) - return; - - evex_mutexes_initted= TRUE; - pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST); - pthread_mutex_init(&LOCK_evex_main_thread, MY_MUTEX_INIT_FAST); - - event_executor_running_global_var= opt_event_executor; -} - -extern TABLE_FIELD_W_TYPE mysql_db_table_fields[]; -extern time_t mysql_db_table_last_check; - -/* - Opens mysql.db and mysql.user and checks whether - 1. mysql.db has column Event_priv at column 20 (0 based); - 2. mysql.user has column Event_priv at column 29 (0 based); - - Synopsis - evex_check_system_tables() -*/ - -void -evex_check_system_tables() -{ - THD *thd= current_thd; - TABLE_LIST tables; - Open_tables_state backup; - - /* thd is 0x0 during boot of the server. Later it's !=0x0 */ - if (!thd) - return; - - thd->reset_n_backup_open_tables_state(&backup); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "db"; - tables.lock_type= TL_READ; - - if (simple_open_n_lock_tables(thd, &tables)) - sql_print_error("Cannot open mysql.db"); - else - { - table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, mysql_db_table_fields, - &mysql_db_table_last_check,ER_CANNOT_LOAD_FROM_TABLE); - close_thread_tables(thd); - } - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "user"; - tables.lock_type= TL_READ; - - if (simple_open_n_lock_tables(thd, &tables)) - sql_print_error("Cannot open mysql.db"); - else - { - if (tables.table->s->fields < 29 || - strncmp(tables.table->field[29]->field_name, - STRING_WITH_LEN("Event_priv"))) - sql_print_error("mysql.user has no `Event_priv` column at position 29"); - - close_thread_tables(thd); - } - - thd->restore_backup_open_tables_state(&backup); -} - - -/* - Inits the scheduler. Called on server start and every time the scheduler - is started with switching the event_scheduler global variable to TRUE - - SYNOPSIS - init_events() - - NOTES - Inits the mutexes used by the scheduler. Done at server start. -*/ - -int -init_events() -{ - pthread_t th; - DBUG_ENTER("init_events"); - - DBUG_PRINT("info",("Starting events main thread")); - - evex_check_system_tables(); - - evex_init_mutexes(); - - VOID(pthread_mutex_lock(&LOCK_evex_running)); - evex_is_running= false; - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - if (event_executor_running_global_var) - { -#ifndef DBUG_FAULTY_THR - /* TODO Andrey: Change the error code returned! */ - if (pthread_create(&th, &connection_attrib, event_executor_main,(void*)NULL)) - DBUG_RETURN(ER_SLAVE_THREAD); -#else - event_executor_main(NULL); -#endif - } - - DBUG_RETURN(0); -} - - -/* - Cleans up scheduler memory. Called on server shutdown. - - SYNOPSIS - shutdown_events() - - NOTES - Destroys the mutexes. -*/ - -void -shutdown_events() -{ - DBUG_ENTER("shutdown_events"); - - if (evex_mutexes_initted) - { - evex_mutexes_initted= FALSE; - VOID(pthread_mutex_lock(&LOCK_evex_running)); - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - pthread_mutex_destroy(&LOCK_event_arrays); - pthread_mutex_destroy(&LOCK_workers_count); - pthread_mutex_destroy(&LOCK_evex_running); - pthread_mutex_destroy(&LOCK_evex_main_thread); - } - DBUG_VOID_RETURN; -} - - -/* - Inits an scheduler thread handler, both the main and a worker - - SYNOPSIS - init_event_thread() - thd - the THD of the thread. Has to be allocated by the caller. - - NOTES - 1. The host of the thead is my_localhost - 2. thd->net is initted with NULL - no communication. - - Returns - 0 - OK - -1 - Error -*/ - -static int -init_event_thread(THD* thd) -{ - DBUG_ENTER("init_event_thread"); - thd->client_capabilities= 0; - thd->security_ctx->master_access= 0; - thd->security_ctx->db_access= 0; - thd->security_ctx->host_or_ip= (char*)my_localhost; - my_net_init(&thd->net, 0); - thd->net.read_timeout = slave_net_timeout; - thd->slave_thread= 0; - thd->options|= OPTION_AUTO_IS_NULL; - thd->client_capabilities= CLIENT_LOCAL_FILES; - thd->real_id=pthread_self(); - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thd->thread_id= thread_id++; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - if (init_thr_lock() || thd->store_globals()) - { - thd->cleanup(); - delete thd; - DBUG_RETURN(-1); - } - -#if !defined(__WIN__) && !defined(__NETWARE__) - sigset_t set; - VOID(sigemptyset(&set)); // Get mask in use - VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); -#endif - - thd->proc_info= "Initialized"; - thd->version= refresh_version; - thd->set_time(); - DBUG_RETURN(0); -} - - -/* - This function waits till the time next event in the queue should be - executed. - - Returns - WAIT_STATUS_READY There is an event to be executed right now - WAIT_STATUS_EMPTY_QUEUE No events or the last event was dropped. - WAIT_STATUS_NEW_TOP_EVENT New event has entered the queue and scheduled - on top. Restart ticking. - WAIT_STATUS_STOP_EXECUTOR The thread was killed or SET global event_scheduler=0; -*/ - -static int -executor_wait_till_next_event_exec(THD *thd) -{ - Event_timed *et; - TIME time_now; - int t2sleep; - - DBUG_ENTER("executor_wait_till_next_event_exec"); - /* - now let's see how much time to sleep, we know there is at least 1 - element in the queue. - */ - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - if (!evex_queue_num_elements(EVEX_EQ_NAME)) - { - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - DBUG_RETURN(WAIT_STATUS_EMPTY_QUEUE); - } - et= evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*); - DBUG_ASSERT(et); - if (et->status == MYSQL_EVENT_DISABLED) - { - DBUG_PRINT("evex main thread",("Now it is disabled-exec no more")); - if (et->dropped) - et->drop(thd); - delete et; - evex_queue_delete_element(&EVEX_EQ_NAME, 0);// 0 is top, internally 1 - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - sql_print_information("Event found disabled, dropping."); - DBUG_RETURN(1); - } - - DBUG_PRINT("evex main thread",("computing time to sleep till next exec")); - /* set the internal clock of thd */ - thd->end_time(); - my_tz_UTC->gmt_sec_to_TIME(&time_now, thd->query_start()); - t2sleep= evex_time_diff(&et->execute_at, &time_now); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - - t2sleep*=20; - DBUG_PRINT("evex main thread",("unlocked LOCK_event_arrays")); - if (t2sleep > 0) - { - ulonglong modified= et->modified; - /* - We sleep t2sleep seconds but we check every second whether this thread - has been killed, or there is a new candidate - */ - while (t2sleep-- && !thd->killed && event_executor_running_global_var && - evex_queue_num_elements(EVEX_EQ_NAME) && - (evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*) == et && - evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*)->modified == - modified)) - { - DBUG_PRINT("evex main thread",("will sleep a bit more.")); - my_sleep(50000); - } - DBUG_PRINT("info",("saved_modified=%llu current=%llu", modified, - evex_queue_num_elements(EVEX_EQ_NAME)? - evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*)->modified: - (ulonglong)~0)); - } - - int ret= WAIT_STATUS_READY; - if (!evex_queue_num_elements(EVEX_EQ_NAME)) - ret= WAIT_STATUS_EMPTY_QUEUE; - else if (evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*) != et) - ret= WAIT_STATUS_NEW_TOP_EVENT; - if (thd->killed && event_executor_running_global_var) - ret= WAIT_STATUS_STOP_EXECUTOR; - - DBUG_RETURN(ret); -} - - -/* - The main scheduler thread. Inits the priority queue on start and - destroys it on thread shutdown. Forks child threads for every event - execution. Sleeps between thread forking and does not do a busy wait. - - SYNOPSIS - event_executor_main() - arg unused - - NOTES - 1. The host of the thead is my_localhost - 2. thd->net is initted with NULL - no communication. - -*/ - -pthread_handler_t -event_executor_main(void *arg) -{ - THD *thd; /* needs to be first for thread_stack */ - uint i=0, j=0; - my_ulonglong cnt= 0; - - DBUG_ENTER("event_executor_main"); - DBUG_PRINT("event_executor_main", ("EVEX thread started")); - - pthread_mutex_lock(&LOCK_evex_main_thread); - if (!scheduler_main_thread_running) - scheduler_main_thread_running= true; - else - { - DBUG_PRINT("event_executor_main", ("already running. thd_id=%d", - evex_main_thread_id)); - pthread_mutex_unlock(&LOCK_evex_main_thread); - my_thread_end(); - pthread_exit(0); - DBUG_RETURN(0); // Can't return anything here - } - pthread_mutex_unlock(&LOCK_evex_main_thread); - - /* init memory root */ - init_alloc_root(&evex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - - /* needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff*/ - my_thread_init(); - - if (sizeof(my_time_t) != sizeof(time_t)) - { - sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." - "The scheduler will not work correctly. Stopping."); - DBUG_ASSERT(0); - goto err_no_thd; - } - - /* note that contructor of THD uses DBUG_ ! */ - if (!(thd = new THD)) - { - sql_print_error("SCHEDULER: Cannot create THD for the main thread."); - goto err_no_thd; - } - thd->thread_stack = (char*)&thd; // remember where our stack is - - pthread_detach_this_thread(); - - if (init_event_thread(thd)) - goto finish; - - /* - make this thread visible it has no vio -> show processlist won't see it - unless it's marked as system thread - */ - thd->system_thread= 1; - - VOID(pthread_mutex_lock(&LOCK_thread_count)); - threads.append(thd); - thread_count++; - thread_running++; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - DBUG_PRINT("EVEX main thread", ("Initing events_queue")); - - /* - eventually manifest that we are running, not to crashe because of - usage of non-initialized memory structures. - */ - VOID(pthread_mutex_lock(&LOCK_evex_running)); - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - evex_queue_init(&EVEX_EQ_NAME); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - evex_is_running= true; - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - thd->security_ctx->user= my_strdup("event_scheduler", MYF(0)); - - if (evex_load_events_from_db(thd)) - goto finish; - - evex_main_thread_id= thd->thread_id; - - sql_print_information("SCHEDULER: Main thread started"); - while (!thd->killed) - { - TIME time_now; - Event_timed *et; - - cnt++; - DBUG_PRINT("info", ("EVEX External Loop %d thd->k", cnt)); - - thd->proc_info = "Sleeping"; - if (!event_executor_running_global_var) - { - sql_print_information("SCHEDULER: Asked to stop."); - break; - } - - if (!evex_queue_num_elements(EVEX_EQ_NAME)) - { - my_sleep(100000);// sleep 0.1s - continue; - } - -restart_ticking: - switch (executor_wait_till_next_event_exec(thd)) { - case WAIT_STATUS_READY: // time to execute the event on top - DBUG_PRINT("evex main thread",("time to execute an event")); - break; - case WAIT_STATUS_EMPTY_QUEUE: // no more events - DBUG_PRINT("evex main thread",("no more events")); - continue; - break; - case WAIT_STATUS_NEW_TOP_EVENT: // new event on top in the queue - DBUG_PRINT("evex main thread",("restart ticking")); - goto restart_ticking; - case WAIT_STATUS_STOP_EXECUTOR: - sql_print_information("SCHEDULER: Asked to stop."); - goto finish; - break; - default: - DBUG_ASSERT(0); - } - - - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - thd->end_time(); - my_tz_UTC->gmt_sec_to_TIME(&time_now, thd->query_start()); - - if (!evex_queue_num_elements(EVEX_EQ_NAME)) - { - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - DBUG_PRINT("evex main thread",("empty queue")); - continue; - } - et= evex_queue_first_element(&EVEX_EQ_NAME, Event_timed*); - DBUG_PRINT("evex main thread",("got event from the queue")); - - if (!et->execute_at_null && my_time_compare(&time_now,&et->execute_at) == -1) - { - DBUG_PRINT("evex main thread",("still not the time for execution")); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - continue; - } - - DBUG_PRINT("evex main thread",("it's right time")); - if (et->status == MYSQL_EVENT_ENABLED) - { - int fork_ret_code; - - DBUG_PRINT("evex main thread", ("[%10s] this exec at [%llu]", et->name.str, - TIME_to_ulonglong_datetime(&et->execute_at))); - et->mark_last_executed(thd); - if (et->compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing time of %s.%s . " - "Disabling after execution.", - et->dbname.str, et->name.str); - et->status= MYSQL_EVENT_DISABLED; - } - DBUG_PRINT("evex main thread", ("[%10s] next exec at [%llu]", et->name.str, - TIME_to_ulonglong_datetime(&et->execute_at))); - - et->update_fields(thd); -#ifndef DBUG_FAULTY_THR - thread_safe_increment(workers_count, &LOCK_workers_count); - switch ((fork_ret_code= et->spawn_now(event_executor_worker))) { - case EVENT_EXEC_CANT_FORK: - thread_safe_decrement(workers_count, &LOCK_workers_count); - sql_print_error("SCHEDULER: Problem while trying to create a thread"); - UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_event_arrays, finish); - case EVENT_EXEC_ALREADY_EXEC: - thread_safe_decrement(workers_count, &LOCK_workers_count); - sql_print_information("SCHEDULER: %s.%s in execution. Skip this time.", - et->dbname.str, et->name.str); - break; - default: - DBUG_ASSERT(!fork_ret_code); - if (fork_ret_code) - thread_safe_decrement(workers_count, &LOCK_workers_count); - break; - } -#else - event_executor_worker((void *) et); -#endif - /* - 1. For one-time event : year is > 0 and expression is 0 - 2. For recurring, expression is != -=> check execute_at_null in this case - */ - if ((et->execute_at.year && !et->expression) || et->execute_at_null) - et->flags |= EVENT_EXEC_NO_MORE; - - if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == MYSQL_EVENT_DISABLED) - evex_queue_delete_element(&EVEX_EQ_NAME, 0);// 0 is top, internally 1 - else - evex_queue_first_updated(&EVEX_EQ_NAME); - } - DBUG_PRINT("evex main thread",("unlocking")); - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - }/* while */ -finish: - - /* First manifest that this thread does not work and then destroy */ - VOID(pthread_mutex_lock(&LOCK_evex_running)); - evex_is_running= false; - evex_main_thread_id= 0; - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - - /* - TODO: A better will be with a conditional variable - */ - /* - Read workers_count without lock, no need for locking. - In the worst case we have to wait 1sec more. - */ - sql_print_information("SCHEDULER: Stopping. Waiting for worker threads to finish."); - while (1) - { - VOID(pthread_mutex_lock(&LOCK_workers_count)); - if (!workers_count) - { - VOID(pthread_mutex_unlock(&LOCK_workers_count)); - break; - } - VOID(pthread_mutex_unlock(&LOCK_workers_count)); - my_sleep(1000000);// 1s - } - - /* - First we free all objects ... - Lock because a DROP DATABASE could be running in parallel and it locks on these - */ - sql_print_information("SCHEDULER: Emptying the queue."); - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i) - { - Event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, Event_timed*); - et->free_sp(); - delete et; - } - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - /* ... then we can thrash the whole queue at once */ - evex_queue_destroy(&EVEX_EQ_NAME); - - thd->proc_info = "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); // destructor will not free it, because we are weird - THD_CHECK_SENTRY(thd); - - pthread_mutex_lock(&LOCK_thread_count); - thread_count--; - thread_running--; -#ifndef DBUG_FAULTY_THR - THD_CHECK_SENTRY(thd); - delete thd; -#endif - pthread_mutex_unlock(&LOCK_thread_count); - - -err_no_thd: - VOID(pthread_mutex_lock(&LOCK_evex_running)); - evex_is_running= false; - event_executor_running_global_var= false; - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - free_root(&evex_mem_root, MYF(0)); - sql_print_information("SCHEDULER: Stopped."); - -#ifndef DBUG_FAULTY_THR - pthread_mutex_lock(&LOCK_evex_main_thread); - scheduler_main_thread_running= false; - pthread_mutex_unlock(&LOCK_evex_main_thread); - - my_thread_end(); - pthread_exit(0); -#endif - DBUG_RETURN(0); // Can't return anything here -} - - -/* - Function that executes an event in a child thread. Setups the - environment for the event execution and cleans after that. - - SYNOPSIS - event_executor_worker() - arg The Event_timed object to be processed -*/ - -pthread_handler_t -event_executor_worker(void *event_void) -{ - THD *thd; /* needs to be first for thread_stack */ - Event_timed *event = (Event_timed *) event_void; - MEM_ROOT worker_mem_root; - - DBUG_ENTER("event_executor_worker"); - - init_alloc_root(&worker_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - -#ifndef DBUG_FAULTY_THR - my_thread_init(); - - if (!(thd = new THD)) /* note that contructor of THD uses DBUG_ ! */ - { - sql_print_error("SCHEDULER: Cannot create a THD structure in an worker."); - goto err_no_thd; - } - thd->thread_stack = (char*)&thd; // remember where our stack is - thd->mem_root= &worker_mem_root; - - pthread_detach_this_thread(); - - if (init_event_thread(thd)) - goto err; - - thd->init_for_queries(); - - /* make this thread visible it has no vio -> show processlist needs this flag */ - thd->system_thread= 1; - - VOID(pthread_mutex_lock(&LOCK_thread_count)); - threads.append(thd); - thread_count++; - thread_running++; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); -#else - thd= current_thd; -#endif - - thd->enable_slow_log= TRUE; - { - int ret; - sql_print_information("SCHEDULER: Executing event %s.%s of %s [EXPR:%d]", - event->dbname.str, event->name.str, - event->definer.str, (int) event->expression); - - ret= event->execute(thd, &worker_mem_root); - - evex_print_warnings(thd, event); - sql_print_information("SCHEDULER: Executed event %s.%s of %s [EXPR:%d]. " - "RetCode=%d", event->dbname.str, event->name.str, - event->definer.str, (int) event->expression, ret); - if (ret == EVEX_COMPILE_ERROR) - sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of", - event->dbname.str, event->name.str, - event->definer.str); - else if (ret == EVEX_MICROSECOND_UNSUP) - sql_print_information("SCHEDULER: MICROSECOND is not supported"); - } - event->spawn_thread_finish(thd); - - -err: - VOID(pthread_mutex_lock(&LOCK_thread_count)); -#ifndef DBUG_FAULTY_THR - thread_count--; - thread_running--; - /* - Some extra safety, which should not been needed (normally, event deletion - should already have done these assignments (each event which sets these - variables is supposed to set them to 0 before terminating)). - */ - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - thd->proc_info = "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); // destructor will not free it, because we are weird - THD_CHECK_SENTRY(thd); - - VOID(pthread_mutex_lock(&LOCK_thread_count)); - THD_CHECK_SENTRY(thd); - delete thd; -#endif - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - -err_no_thd: - - free_root(&worker_mem_root, MYF(0)); - thread_safe_decrement(workers_count, &LOCK_workers_count); - -#ifndef DBUG_FAULTY_THR - my_thread_end(); - pthread_exit(0); -#endif - DBUG_RETURN(0); // Can't return anything here -} - - -/* - Loads all ENABLED events from mysql.event into the prioritized - queue. Called during scheduler main thread initialization. Compiles - the events. Creates Event_timed instances for every ENABLED event - from mysql.event. - - SYNOPSIS - evex_load_events_from_db() - thd - Thread context. Used for memory allocation in some cases. - - RETURNS - 0 OK - !0 Error - - NOTES - Reports the error to the console -*/ - -static int -evex_load_events_from_db(THD *thd) -{ - TABLE *table; - READ_RECORD read_record_info; - int ret= -1; - uint count= 0; - - DBUG_ENTER("evex_load_events_from_db"); - - if ((ret= evex_open_event_table(thd, TL_READ, &table))) - { - sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); - DBUG_RETURN(SP_OPEN_TABLE_FAILED); - } - - VOID(pthread_mutex_lock(&LOCK_event_arrays)); - - init_read_record(&read_record_info, thd, table ,NULL,1,0); - while (!(read_record_info.read_record(&read_record_info))) - { - Event_timed *et; - if (!(et= new Event_timed)) - { - DBUG_PRINT("evex_load_events_from_db", ("Out of memory")); - ret= -1; - goto end; - } - DBUG_PRINT("evex_load_events_from_db", ("Loading event from row.")); - - if ((ret= et->load_from_row(&evex_mem_root, table))) - { - sql_print_error("SCHEDULER: Error while loading from mysql.event. " - "Table probably corrupted"); - goto end; - } - if (et->status != MYSQL_EVENT_ENABLED) - { - DBUG_PRINT("evex_load_events_from_db",("%s is disabled",et->name.str)); - delete et; - continue; - } - - DBUG_PRINT("evex_load_events_from_db", - ("Event %s loaded from row. Time to compile", et->name.str)); - - switch (ret= et->compile(thd, &evex_mem_root)) { - case EVEX_MICROSECOND_UNSUP: - sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " - "supported but found in mysql.event"); - goto end; - case EVEX_COMPILE_ERROR: - sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", - et->dbname.str, et->name.str); - goto end; - default: - break; - } - - /* let's find when to be executed */ - if (et->compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." - " Skipping", et->dbname.str, et->name.str); - continue; - } - - DBUG_PRINT("evex_load_events_from_db", ("Adding to the exec list.")); - - evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) et); - DBUG_PRINT("evex_load_events_from_db", ("%p %*s", - et, et->name.length,et->name.str)); - count++; - } - - ret= 0; - -end: - VOID(pthread_mutex_unlock(&LOCK_event_arrays)); - end_read_record(&read_record_info); - - /* Force close to free memory */ - thd->version--; - - close_thread_tables(thd); - if (!ret) - sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); - DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); - - DBUG_RETURN(ret); -} - - -/* - The update method of the global variable event_scheduler. - If event_scheduler is switched from 0 to 1 then the scheduler main - thread is started. - - SYNOPSIS - event_executor_worker() - thd - Thread context (unused) - car - the new value - - Returns - 0 OK (always) -*/ - -bool -sys_var_event_executor::update(THD *thd, set_var *var) -{ - /* here start the thread if not running. */ - DBUG_ENTER("sys_var_event_executor::update"); - VOID(pthread_mutex_lock(&LOCK_evex_running)); - *value= var->save_result.ulong_value; - - DBUG_PRINT("new_value", ("%d", *value)); - if ((my_bool) *value && !evex_is_running) - { - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - init_events(); - } else - VOID(pthread_mutex_unlock(&LOCK_evex_running)); - - DBUG_RETURN(0); -} - - -extern LEX_STRING warning_level_names[]; - -typedef void (*sql_print_xxx_func)(const char *format, ...); -static sql_print_xxx_func sql_print_xxx_handlers[3] = -{ - sql_print_information, - sql_print_warning, - sql_print_error -}; - - -/* - Prints the stack of infos, warnings, errors from thd to - the console so it can be fetched by the logs-into-tables and - checked later. - - Synopsis - evex_print_warnings - thd - thread used during the execution of the event - et - the event itself - - Returns - 0 - OK (always) - -*/ - -bool -evex_print_warnings(THD *thd, Event_timed *et) -{ - MYSQL_ERROR *err; - DBUG_ENTER("evex_show_warnings"); - char msg_buf[1024]; - char prefix_buf[512]; - String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info); - prefix.length(0); - - List_iterator_fast<MYSQL_ERROR> it(thd->warn_list); - while ((err= it++)) - { - String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); - /* set it to 0 or we start adding at the end. That's the trick ;) */ - err_msg.length(0); - if (!prefix.length()) - { - prefix.append("SCHEDULER: ["); - - append_identifier(thd,&prefix,et->definer_user.str,et->definer_user.length); - prefix.append('@'); - append_identifier(thd,&prefix,et->definer_host.str,et->definer_host.length); - prefix.append("][", 2); - append_identifier(thd,&prefix, et->dbname.str, et->dbname.length); - prefix.append('.'); - append_identifier(thd,&prefix, et->name.str, et->name.length); - prefix.append("] ", 2); - } - - err_msg.append(prefix); - err_msg.append(err->msg, strlen(err->msg), system_charset_info); - err_msg.append("]"); - DBUG_ASSERT(err->level < 3); - (sql_print_xxx_handlers[err->level])("%*s", err_msg.length(), err_msg.c_ptr()); - } - - - DBUG_RETURN(FALSE); -} diff --git a/sql/event_priv.h b/sql/event_priv.h index 6b23136847e9dd837238f6e252c1055a45d2e087..b0a18377aceaf60dd3c21cba9a9ed94ed0a2bfef 100644 --- a/sql/event_priv.h +++ b/sql/event_priv.h @@ -1,4 +1,6 @@ -/* Copyright (C) 2004-2005 MySQL AB +#ifndef _EVENT_PRIV_H_ +#define _EVENT_PRIV_H_ +/* Copyright (C) 2004-2006 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,8 +16,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _EVENT_PRIV_H_ -#define _EVENT_PRIV_H_ #include "mysql_priv.h" @@ -23,11 +23,6 @@ #define EVENT_EXEC_ALREADY_EXEC 1 #define EVENT_EXEC_CANT_FORK 2 -#define EVEX_USE_QUEUE - -#define UNLOCK_MUTEX_AND_BAIL_OUT(__mutex, __label) \ - { VOID(pthread_mutex_unlock(&__mutex)); goto __label; } - #define EVEX_DB_FIELD_LEN 64 #define EVEX_NAME_FIELD_LEN 64 #define EVEX_MAX_INTERVAL_VALUE 2147483647L @@ -44,39 +39,49 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, int event_timed_compare_q(void *vptr, byte* a, byte *b); -int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); - +int +db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, + uint *rows_affected); +int +db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett, + TABLE *tbl, MEM_ROOT *root); -#define EXEC_QUEUE_QUEUE_NAME executing_queue -#define EXEC_QUEUE_DARR_NAME evex_executing_queue +int +db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, + uint *rows_affected); +int +db_drop_events_from_table(THD *thd, LEX_STRING *db); -#define EVEX_QUEUE_TYPE QUEUE -#define EVEX_PTOQEL byte * +int +sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); -#define EVEX_EQ_NAME executing_queue -#define evex_queue_first_element(queue, __cast) ((__cast)queue_top(queue)) -#define evex_queue_element(queue, idx, __cast) ((__cast)queue_element(queue, idx)) -#define evex_queue_delete_element(queue, idx) queue_remove(queue, idx) -#define evex_queue_destroy(queue) delete_queue(queue) -#define evex_queue_first_updated(queue) queue_replaced(queue) -#define evex_queue_insert(queue, element) queue_insert_safe(queue, element); +/* Compares only the name part of the identifier */ +bool +event_timed_name_equal(Event_timed *et, LEX_STRING *name); +/* Compares only the schema part of the identifier */ +bool +event_timed_db_equal(Event_timed *et, LEX_STRING *db); +/* + Compares only the definer part of the identifier. Use during DROP USER + to drop user's events. (Still not implemented) +*/ +bool +event_timed_definer_equal(Event_timed *et, LEX_STRING *definer); -void -evex_queue_init(EVEX_QUEUE_TYPE *queue); +/* Compares the whole identifier*/ +bool +event_timed_identifier_equal(Event_timed *a, Event_timed *b); -#define evex_queue_num_elements(queue) queue.elements +bool +change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *s_ctx, + Security_context **backup); -extern bool evex_is_running; -extern MEM_ROOT evex_mem_root; -extern pthread_mutex_t LOCK_event_arrays, - LOCK_workers_count, - LOCK_evex_running; -extern ulonglong evex_main_thread_id; -extern QUEUE EVEX_EQ_NAME; +void +restore_security_context(THD *thd, Security_context *backup); #endif /* _EVENT_PRIV_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc new file mode 100644 index 0000000000000000000000000000000000000000..784c87c0e8ef2e26cb819720b3f639011e781256 --- /dev/null +++ b/sql/event_scheduler.cc @@ -0,0 +1,2411 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "event_priv.h" +#include "event.h" +#include "event_scheduler.h" +#include "sp_head.h" + +/* + ToDo: + 1. Talk to Alik to get a check for configure.in for my_time_t and time_t + 2. Look at guardian.h|cc to see its life cycle, has similarities. +*/ + + +/* + The scheduler is implemented as class Event_scheduler. Only one instance is + kept during the runtime of the server, by implementing the Singleton DP. + Object instance is always there because the memory is allocated statically + and initialized when the OS loader loads mysqld. This initialization is + bare. Extended initialization is done during the call to + Event_scheduler::init() in Events::init(). The reason for that late initialization + is that some subsystems needed to boot the Scheduler are not available at + earlier stages of the mysqld boot procedure. Events::init() is called in + mysqld.cc . If the mysqld is started with --event-scheduler=0 then + no initialization takes place and the scheduler is unavailable during this + server run. The server should be started with --event-scheduler=1 to have + the scheduler initialized and able to execute jobs. This starting alwa + s implies that the jobs execution will start immediately. If the server + is started with --event-scheduler=2 then the scheduler is started in suspended + state. Default state, if --event-scheduler is not specified is 2. + + The scheduler only manages execution of the events. Their creation, + alteration and deletion is delegated to other routines found in event.cc . + These routines interact with the scheduler : + - CREATE EVENT -> Event_scheduler::add_event() + - ALTER EVENT -> Event_scheduler::replace_event() + - DROP EVENT -> Event_scheduler::drop_event() + + There is one mutex in the single Event_scheduler object which controls + the simultaneous access to the objects invariants. Using one lock makes + it easy to follow the workflow. This mutex is LOCK_scheduler_data. It is + initialized in Event_scheduler::init(). Which in turn is called by the + Facade class Events in event.cc, coming from init_thread_environment() from + mysqld.cc -> no concurrency at this point. It's destroyed in + Events::destroy_mutexes() called from clean_up_mutexes() in mysqld.cc . + + The full initialization is done in Event_scheduler::init() called from + Events::init(). It's done before any requests coming in, so this is a + guarantee for not having concurrency. + + The scheduler is started with Event_scheduler::start() and stopped with + Event_scheduler::stop(). When the scheduler starts it loads all events + from mysql.event table. Unfortunately, there is a race condition between + the event disk management functions and the scheduler ones + (add/replace/drop_event & load_events_from_db()), because the operations + do not happen under one global lock but the disk operations are guarded + by the MYISAM lock on mysql.event. In the same time, the queue operations + are guarded by LOCK_scheduler_data. If the scheduler is start()-ed during + server startup and stopped()-ed during server shutdown (in Events::shutdown() + called by kill_server() in mysqld.cc) these races does not exist. + + Since the user may want to temporarily inhibit execution of events the + scheduler can be suspended and then it can be forced to resume its + operations. The API call to perform these is + Event_scheduler::suspend_or_resume(enum enum_suspend_or_resume) . + When the scheduler is suspended the main scheduler thread, which ATM + happens to have thread_id 1, locks on a condition COND_suspend_or_resume. + When this is signal is sent for the reverse operation the main scheduler + loops continues to roll and execute events. + + When the scheduler is suspended all add/replace/drop_event() operations + work as expected and the modify the queue but no events execution takes + place. + + In contrast to the previous scheduler implementation, found in + event_executor.cc, the start, shutdown, suspend and resume are synchronous + operations. As a whole all operations are synchronized and no busy waits + are used except in stop_all_running_events(), which waits until all + running event worker threads have finished. It would have been nice to + use a conditional on which this method will wait and the last thread to + finish would signal it but this implies subclassing THD. + + The scheduler does not keep a counter of how many event worker threads are + running, at any specific moment, because this will copy functionality + already existing in the server. Namely, all THDs are registered in the + global `threads` array. THD has member variable system_thread which + identifies the type of thread. Connection threads being NON_SYSTEM_THREAD, + all other have their enum value. Important for the scheduler are + SYSTEM_THREAD_EVENT_SCHEDULER and SYSTEM_THREAD_EVENT_WORKER. + + Class THD subclasses class ilink, which is the linked list of all threads. + When a THD instance is destroyed it's being removed from threads, thus + no manual intervention is needed. On the contrary registering is manual + with threads.append() . Traversing the threads array every time a subclass + of THD, for instance if we would have had THD_scheduler_worker to see + how many events we have and whether the scheduler is shutting down will + take much time and lead to a deadlock. stop_all_running_events() is called + under LOCK_scheduler_data. If the THD_scheduler_worker was aware of + the single Event_scheduler instance it will try to check + Event_scheduler::state but for this it would need to acquire + LOCK_scheduler_data => deadlock. Thus stop_all_running_events() uses a + busy wait. + + DROP DATABASE DDL should drop all events defined in a specific schema. + DROP USER also should drop all events who has as definer the user being + dropped (this one is not addressed at the moment but a hook exists). For + this specific needs Event_scheduler::drop_matching_events() is + implemented. Which expects a callback to be applied on every object in + the queue. Thus events that match specific schema or user, will be + removed from the queue. The exposed interface is : + - Event_scheduler::drop_schema_events() + - Event_scheduler::drop_user_events() + + This bulk dropping happens under LOCK_scheduler_data, thus no two or + more threads can execute it in parallel. However, DROP DATABASE is also + synchronized, currently, in the server thus this does not impact the + overall performance. In addition, DROP DATABASE is not that often + executed DDL. + + Though the interface to the scheduler is only through the public methods + of class Event_scheduler, there are currently few functions which are + used during its operations. Namely : + - static evex_print_warnings() + After every event execution all errors/warnings are dumped, so the user + can see in case of a problem what the problem was. + + - static init_event_thread() + This function is both used by event_scheduler_thread() and + event_worker_thread(). It initializes the THD structure. The + initialization looks pretty similar to the one in slave.cc done for the + replication threads. However, though the similarities it cannot be + factored out to have one routine. + + - static event_scheduler_thread() + Because our way to register functions to be used by the threading library + does not allow usage of static methods this function is used to start the + scheduler in it. It does THD initialization and then calls + Event_scheduler::run(). + + - static event_worker_thread() + With already stated the reason for not being able to use methods, this + function executes the worker threads. + + The execution of events is, to some extent, synchronized to inhibit race + conditions when Event_timed::thread_id is being updated with the thread_id of + the THD in which the event is being executed. The thread_id is in the + Event_timed object because we need to be able to kill quickly a specific + event during ALTER/DROP EVENT without traversing the global `threads` array. + However, this makes the scheduler's code more complicated. The event worker + thread is started by Event_timed::spawn_now(), which in turn calls + pthread_create(). The thread_id which will be associated in init_event_thread + is not known in advance thus the registering takes place in + event_worker_thread(). This registering has to be synchronized under + LOCK_scheduler_data, so no kill_event() on a object in + replace_event/drop_event/drop_matching_events() could take place. + + This synchronization is done through class Worker_thread_param that is + local to this file. Event_scheduler::execute_top() is called under + LOCK_scheduler_data. This method : + 1. Creates an instance of Worker_thread_param on the stack + 2. Locks Worker_thread_param::LOCK_started + 3. Calls Event_timed::spawn_now() which in turn creates a new thread. + 4. Locks on Worker_thread_param::COND_started_or_stopped and waits till the + worker thread send signal. The code is spurious wake-up safe because + Worker_thread_param::started is checked. + 5. The worker thread initializes its THD, then sets Event_timed::thread_id, + sets Worker_thread_param::started to TRUE and sends back + Worker_thread_param::COND_started. From this moment on, the event + is being executed and could be killed by using Event_timed::thread_id. + When Event_timed::spawn_thread_finish() is called in the worker thread, + it sets thread_id to 0. From this moment on, the worker thread should not + touch the Event_timed instance. + + + The life-cycle of the server is a FSA. + enum enum_state Event_scheduler::state keeps the state of the scheduler. + + The states are: + + |---UNINITIALIZED + | + | |------------------> IN_SHUTDOWN + --> INITIALIZED -> COMMENCING ---> RUNNING ----------| + ^ ^ | | ^ | + | |- CANTSTART <--| | |- SUSPENDED <-| + |______________________________| + + - UNINITIALIZED :The object is created and only the mutex is initialized + - INITIALIZED :All member variables are initialized + - COMMENCING :The scheduler is starting, no other attempt to start + should succeed before the state is back to INITIALIZED. + - CANTSTART :Set by the ::run() method in case it can't start for some + reason. In this case the connection thread that tries to + start the scheduler sees that some error has occurred and + returns an error to the user. Finally, the connection + thread sets the state to INITIALIZED, so further attempts + to start the scheduler could be made. + - RUNNING :The scheduler is running. New events could be added, + dropped, altered. The scheduler could be stopped. + - SUSPENDED :Like RUNNING but execution of events does not take place. + Operations on the memory queue are possible. + - IN_SHUTDOWN :The scheduler is shutting down, due to request by setting + the global event_scheduler to 0/FALSE, or because of a + KILL command sent by a user to the master thread. + + In every method the macros LOCK_SCHEDULER_DATA() and UNLOCK_SCHEDULER_DATA() + are used for (un)locking purposes. They are used to save the programmer + from typing everytime + lock_data(__FUNCTION__, __LINE__); + All locking goes through Event_scheduler::lock_data() and ::unlock_data(). + These two functions then record in variables where for last time + LOCK_scheduler_data was locked and unlocked (two different variables). In + multithreaded environment, in some cases they make no sense but are useful for + inspecting deadlocks without having the server debug log turned on and the + server is still running. + + The same strategy is used for conditional variables. + Event_scheduler::cond_wait() is invoked from all places with parameter + an enum enum_cond_vars. In this manner, it's possible to inspect the last + on which condition the last call to cond_wait() was waiting. If the server + was started with debug trace switched on, the trace file also holds information + about conditional variables used. +*/ + + +#define LOCK_SCHEDULER_DATA() lock_data(__FUNCTION__,__LINE__) +#define UNLOCK_SCHEDULER_DATA() unlock_data(__FUNCTION__,__LINE__) + + +#ifndef DBUG_OFF +static +LEX_STRING states_names[] = +{ + {(char*) STRING_WITH_LEN("UNINITIALIZED")}, + {(char*) STRING_WITH_LEN("INITIALIZED")}, + {(char*) STRING_WITH_LEN("COMMENCING")}, + {(char*) STRING_WITH_LEN("CANTSTART")}, + {(char*) STRING_WITH_LEN("RUNNING")}, + {(char*) STRING_WITH_LEN("SUSPENDED")}, + {(char*) STRING_WITH_LEN("IN_SHUTDOWN")} +}; +#endif + + +Event_scheduler +Event_scheduler::singleton; + + +const char * const +Event_scheduler::cond_vars_names[Event_scheduler::COND_LAST] = +{ + "new work", + "started or stopped", + "suspend or resume" +}; + + +class Worker_thread_param +{ +public: + Event_timed *et; + pthread_mutex_t LOCK_started; + pthread_cond_t COND_started; + bool started; + + Worker_thread_param(Event_timed *etn):et(etn), started(FALSE) + { + pthread_mutex_init(&LOCK_started, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_started, NULL); + } + + ~Worker_thread_param() + { + pthread_mutex_destroy(&LOCK_started); + pthread_cond_destroy(&COND_started); + } +}; + + +/* + Prints the stack of infos, warnings, errors from thd to + the console so it can be fetched by the logs-into-tables and + checked later. + + SYNOPSIS + evex_print_warnings + thd - thread used during the execution of the event + et - the event itself +*/ + +static void +evex_print_warnings(THD *thd, Event_timed *et) +{ + MYSQL_ERROR *err; + DBUG_ENTER("evex_print_warnings"); + if (!thd->warn_list.elements) + DBUG_VOID_RETURN; + + char msg_buf[10 * STRING_BUFFER_USUAL_SIZE]; + char prefix_buf[5 * STRING_BUFFER_USUAL_SIZE]; + String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info); + prefix.length(0); + prefix.append("SCHEDULER: ["); + + append_identifier(thd, &prefix, et->definer_user.str, et->definer_user.length); + prefix.append('@'); + append_identifier(thd, &prefix, et->definer_host.str, et->definer_host.length); + prefix.append("][", 2); + append_identifier(thd,&prefix, et->dbname.str, et->dbname.length); + prefix.append('.'); + append_identifier(thd,&prefix, et->name.str, et->name.length); + prefix.append("] ", 2); + + List_iterator_fast<MYSQL_ERROR> it(thd->warn_list); + while ((err= it++)) + { + String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); + /* set it to 0 or we start adding at the end. That's the trick ;) */ + err_msg.length(0); + err_msg.append(prefix); + err_msg.append(err->msg, strlen(err->msg), system_charset_info); + err_msg.append("]"); + DBUG_ASSERT(err->level < 3); + (sql_print_message_handlers[err->level])("%*s", err_msg.length(), + err_msg.c_ptr()); + } + DBUG_VOID_RETURN; +} + + +/* + Inits an scheduler thread handler, both the main and a worker + + SYNOPSIS + init_event_thread() + thd - the THD of the thread. Has to be allocated by the caller. + + NOTES + 1. The host of the thead is my_localhost + 2. thd->net is initted with NULL - no communication. + + RETURN VALUE + 0 OK + -1 Error +*/ + +static int +init_event_thread(THD** t, enum enum_thread_type thread_type) +{ + THD *thd= *t; + thd->thread_stack= (char*)t; // remember where our stack is + DBUG_ENTER("init_event_thread"); + thd->client_capabilities= 0; + thd->security_ctx->master_access= 0; + thd->security_ctx->db_access= 0; + thd->security_ctx->host_or_ip= (char*)my_localhost; + my_net_init(&thd->net, 0); + thd->net.read_timeout= slave_net_timeout; + thd->slave_thread= 0; + thd->options|= OPTION_AUTO_IS_NULL; + thd->client_capabilities|= CLIENT_MULTI_RESULTS; + thd->real_id=pthread_self(); + VOID(pthread_mutex_lock(&LOCK_thread_count)); + thd->thread_id= thread_id++; + threads.append(thd); + thread_count++; + thread_running++; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + if (init_thr_lock() || thd->store_globals()) + { + thd->cleanup(); + DBUG_RETURN(-1); + } + +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif + + /* + Guarantees that we will see the thread in SHOW PROCESSLIST though its + vio is NULL. + */ + thd->system_thread= thread_type; + + thd->proc_info= "Initialized"; + thd->version= refresh_version; + thd->set_time(); + + DBUG_RETURN(0); +} + + +/* + Inits the main scheduler thread and then calls Event_scheduler::run() + of arg. + + SYNOPSIS + event_scheduler_thread() + arg void* ptr to Event_scheduler + + NOTES + 1. The host of the thead is my_localhost + 2. thd->net is initted with NULL - no communication. + 3. The reason to have a proxy function is that it's not possible to + use a method as function to be executed in a spawned thread: + - our pthread_hander_t macro uses extern "C" + - separating thread setup from the real execution loop is also to be + considered good. + + RETURN VALUE + 0 OK +*/ + +pthread_handler_t +event_scheduler_thread(void *arg) +{ + /* needs to be first for thread_stack */ + THD *thd= NULL; + Event_scheduler *scheduler= (Event_scheduler *) arg; + + DBUG_ENTER("event_scheduler_thread"); + + my_thread_init(); + pthread_detach_this_thread(); + + /* note that constructor of THD uses DBUG_ ! */ + if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_SCHEDULER)) + { + sql_print_error("SCHEDULER: Cannot init manager event thread."); + scheduler->report_error_during_start(); + } + else + { + thd->security_ctx->set_user((char*)"event_scheduler"); + + sql_print_information("SCHEDULER: Manager thread booting"); + if (Event_scheduler::check_system_tables(thd)) + scheduler->report_error_during_start(); + else + scheduler->run(thd); + + /* + NOTE: Don't touch `scheduler` after this point because we have notified + the + thread which shuts us down that we have finished cleaning. In this + very moment a new scheduler thread could be started and a crash is + not welcome. + */ + } + + /* + If we cannot create THD then don't decrease because we haven't touched + thread_count and thread_running in init_event_thread() which was never + called. In init_event_thread() thread_count and thread_running are + always increased even in the case the method returns an error. + */ + if (thd) + { + thd->proc_info= "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + net_end(&thd->net); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete thd; + pthread_mutex_unlock(&LOCK_thread_count); + } + my_thread_end(); + DBUG_RETURN(0); // Can't return anything here +} + + +/* + Function that executes an event in a child thread. Setups the + environment for the event execution and cleans after that. + + SYNOPSIS + event_worker_thread() + arg The Event_timed object to be processed + + RETURN VALUE + 0 OK +*/ + +pthread_handler_t +event_worker_thread(void *arg) +{ + THD *thd; /* needs to be first for thread_stack */ + Worker_thread_param *param= (Worker_thread_param *) arg; + Event_timed *event= param->et; + int ret; + bool startup_error= FALSE; + Security_context *save_ctx; + /* this one is local and not needed after exec */ + Security_context security_ctx; + + DBUG_ENTER("event_worker_thread"); + DBUG_PRINT("enter", ("event=[%s.%s]", event->dbname.str, event->name.str)); + + my_thread_init(); + pthread_detach_this_thread(); + + if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_WORKER)) + { + sql_print_error("SCHEDULER: Startup failure."); + startup_error= TRUE; + event->spawn_thread_finish(thd); + } + else + event->set_thread_id(thd->thread_id); + + DBUG_PRINT("info", ("master_access=%d db_access=%d", + thd->security_ctx->master_access, thd->security_ctx->db_access)); + /* + If we don't change it before we send the signal back, then an intermittent + DROP EVENT will take LOCK_scheduler_data and try to kill this thread, because + event->thread_id is already real. However, because thd->security_ctx->user + is not initialized then a crash occurs in kill_one_thread(). Thus, we have + to change the context before sending the signal. We are under + LOCK_scheduler_data being held by Event_scheduler::run() -> ::execute_top(). + */ + change_security_context(thd, event->definer_user, event->definer_host, + event->dbname, &security_ctx, &save_ctx); + DBUG_PRINT("info", ("master_access=%d db_access=%d", + thd->security_ctx->master_access, thd->security_ctx->db_access)); + + /* Signal the scheduler thread that we have started successfully */ + pthread_mutex_lock(¶m->LOCK_started); + param->started= TRUE; + pthread_cond_signal(¶m->COND_started); + pthread_mutex_unlock(¶m->LOCK_started); + + if (!startup_error) + { + thd->init_for_queries(); + thd->enable_slow_log= TRUE; + + event->set_thread_id(thd->thread_id); + sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu", + event->dbname.str, event->name.str, + event->definer.str, thd->thread_id); + + ret= event->execute(thd, thd->mem_root); + evex_print_warnings(thd, event); + sql_print_information("SCHEDULER: [%s.%s of %s] executed. RetCode=%d", + event->dbname.str, event->name.str, + event->definer.str, ret); + if (ret == EVEX_COMPILE_ERROR) + sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", + event->dbname.str, event->name.str, + event->definer.str); + else if (ret == EVEX_MICROSECOND_UNSUP) + sql_print_information("SCHEDULER: MICROSECOND is not supported"); + + DBUG_PRINT("info", ("master_access=%d db_access=%d", + thd->security_ctx->master_access, thd->security_ctx->db_access)); + + /* If true is returned, we are expected to free it */ + if (event->spawn_thread_finish(thd)) + { + DBUG_PRINT("info", ("Freeing object pointer")); + delete event; + } + } + + if (thd) + { + thd->proc_info= "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + /* + Free it here because net.vio is NULL for us => THD::~THD will check it + and won't call net_end(&net); See also replication code. + */ + net_end(&thd->net); + DBUG_PRINT("info", ("Worker thread %lu exiting", thd->thread_id)); + VOID(pthread_mutex_lock(&LOCK_thread_count)); + thread_count--; + thread_running--; + delete thd; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + } + + my_thread_end(); + DBUG_RETURN(0); // Can't return anything here +} + + +/* + Constructor of class Event_scheduler. + + SYNOPSIS + Event_scheduler::Event_scheduler() +*/ + +Event_scheduler::Event_scheduler() + :state(UNINITIALIZED), start_scheduler_suspended(FALSE), + thread_id(0), mutex_last_locked_at_line(0), + mutex_last_unlocked_at_line(0), mutex_last_locked_in_func(""), + mutex_last_unlocked_in_func(""), cond_waiting_on(COND_NONE), + mutex_scheduler_data_locked(FALSE) +{ +} + + +/* + Returns the singleton instance of the class. + + SYNOPSIS + Event_scheduler::get_instance() + + RETURN VALUE + address +*/ + +Event_scheduler* +Event_scheduler::get_instance() +{ + DBUG_ENTER("Event_scheduler::get_instance"); + DBUG_RETURN(&singleton); +} + + +/* + The implementation of full-fledged initialization. + + SYNOPSIS + Event_scheduler::init() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_scheduler::init() +{ + int i= 0; + bool ret= FALSE; + DBUG_ENTER("Event_scheduler::init"); + DBUG_PRINT("enter", ("this=%p", this)); + + LOCK_SCHEDULER_DATA(); + for (;i < COND_LAST; i++) + if (pthread_cond_init(&cond_vars[i], NULL)) + { + sql_print_error("SCHEDULER: Unable to initalize conditions"); + ret= TRUE; + goto end; + } + + /* init memory root */ + init_alloc_root(&scheduler_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + + if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, + event_timed_compare_q, NULL, 30 /*auto_extent*/)) + { + sql_print_error("SCHEDULER: Can't initialize the execution queue"); + ret= TRUE; + goto end; + } + + if (sizeof(my_time_t) != sizeof(time_t)) + { + sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." + "The scheduler may not work correctly. Stopping."); + DBUG_ASSERT(0); + ret= TRUE; + goto end; + } + + state= INITIALIZED; +end: + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(ret); +} + + +/* + Frees all memory allocated by the scheduler object. + + SYNOPSIS + Event_scheduler::destroy() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +void +Event_scheduler::destroy() +{ + DBUG_ENTER("Event_scheduler"); + + LOCK_SCHEDULER_DATA(); + switch (state) { + case UNINITIALIZED: + break; + case INITIALIZED: + delete_queue(&queue); + free_root(&scheduler_root, MYF(0)); + int i; + for (i= 0; i < COND_LAST; i++) + pthread_cond_destroy(&cond_vars[i]); + state= UNINITIALIZED; + break; + default: + sql_print_error("SCHEDULER: Destroying while state is %d", state); + /* I trust my code but ::safe() > ::sorry() */ + DBUG_ASSERT(0); + break; + } + UNLOCK_SCHEDULER_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + Adds an event to the scheduler queue + + SYNOPSIS + Event_scheduler::add_event() + et The event to add + check_existence Whether to check if already loaded. + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +enum Event_scheduler::enum_error_code +Event_scheduler::add_event(THD *thd, Event_timed *et, bool check_existence) +{ + enum enum_error_code res; + Event_timed *et_new; + DBUG_ENTER("Event_scheduler::add_event"); + DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); + + LOCK_SCHEDULER_DATA(); + if (!is_running_or_suspended()) + { + DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(OP_OK); + } + if (check_existence && find_event(et, FALSE)) + { + res= OP_ALREADY_EXISTS; + goto end; + } + + /* We need to load the event on scheduler_root */ + if (!(res= load_event(thd, et, &et_new))) + { + queue_insert_safe(&queue, (byte *) et_new); + DBUG_PRINT("info", ("Sending COND_new_work")); + pthread_cond_signal(&cond_vars[COND_new_work]); + } + else if (res == OP_DISABLED_EVENT) + res= OP_OK; +end: + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(res); +} + + +/* + Drops an event from the scheduler queue + + SYNOPSIS + Event_scheduler::drop_event() + etn The event to drop + state Wait the event or kill&drop + + RETURN VALUE + FALSE OK (replaced or scheduler not working) + TRUE Failure +*/ + +bool +Event_scheduler::drop_event(THD *thd, Event_timed *et) +{ + int res; + Event_timed *et_old; + DBUG_ENTER("Event_scheduler::drop_event"); + DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); + + LOCK_SCHEDULER_DATA(); + if (!is_running_or_suspended()) + { + DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(OP_OK); + } + + if (!(et_old= find_event(et, TRUE))) + DBUG_PRINT("info", ("No such event found, probably DISABLED")); + + UNLOCK_SCHEDULER_DATA(); + + /* See comments in ::replace_event() why this is split in two parts. */ + if (et_old) + { + switch ((res= et_old->kill_thread(thd))) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et_old->flags |= EVENT_FREE_WHEN_FINISHED; + break; + case 0: + /* + kill_thread() waits till the spawned thread finishes after it's + killed. Hence, we delete here memory which is no more referenced from + a running thread. + */ + delete et_old; + /* + We don't signal COND_new_work here because: + 1. Even if the dropped event is on top of the queue this will not + move another one to be executed before the time the one on the + top (but could be at the same second as the dropped one) + 2. If this was the last event on the queue, then pthread_cond_timedwait + in ::run() will finish and then see that the queue is empty and + call cond_wait(). Hence, no need to interrupt the blocked + ::run() thread. + */ + break; + default: + sql_print_error("SCHEDULER: Got unexpected error %d", res); + DBUG_ASSERT(0); + } + } + + DBUG_RETURN(FALSE); +} + + +/* + Replaces an event in the scheduler queue + + SYNOPSIS + Event_scheduler::replace_event() + et The event to replace(add) into the queue + state Async or sync stopping + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk + OP_ALREADY_EXISTS Event already in the queue +*/ + +enum Event_scheduler::enum_error_code +Event_scheduler::replace_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, + LEX_STRING *new_name) +{ + enum enum_error_code res; + Event_timed *et_old, *et_new= NULL; + LEX_STRING old_schema, old_name; + + DBUG_ENTER("Event_scheduler::replace_event"); + DBUG_PRINT("enter", ("thd=%p et=%p et=[%s.%s] lock=%p", + thd, et, et->dbname.str, et->name.str, &LOCK_scheduler_data)); + + LOCK_SCHEDULER_DATA(); + if (!is_running_or_suspended()) + { + DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(OP_OK); + } + + if (!(et_old= find_event(et, TRUE))) + DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED", + et->dbname.str, et->name.str)); + + if (new_schema && new_name) + { + old_schema= et->dbname; + old_name= et->name; + et->dbname= *new_schema; + et->name= *new_name; + } + /* + We need to load the event (it's strings but on the object itself) + on scheduler_root. et_new could be NULL : + 1. Error occured + 2. If the replace is DISABLED, we don't load it into the queue. + */ + if (!(res= load_event(thd, et, &et_new))) + { + queue_insert_safe(&queue, (byte *) et_new); + DBUG_PRINT("info", ("Sending COND_new_work")); + pthread_cond_signal(&cond_vars[COND_new_work]); + } + else if (res == OP_DISABLED_EVENT) + res= OP_OK; + + if (new_schema && new_name) + { + et->dbname= old_schema; + et->name= old_name; + } + + UNLOCK_SCHEDULER_DATA(); + /* + Andrey: Is this comment still truthful ??? + + We don't move this code above because a potential kill_thread will call + THD::awake(). Which in turn will try to acqure mysys_var->current_mutex, + which is LOCK_scheduler_data on which the COND_new_work in ::run() locks. + Hence, we try to acquire a lock which we have already acquired and we run + into an assert. Holding LOCK_scheduler_data however is not needed because + we don't touch any invariant of the scheduler anymore. ::drop_event() does + the same. + */ + if (et_old) + { + switch (et_old->kill_thread(thd)) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et_old->flags |= EVENT_FREE_WHEN_FINISHED; + break; + case 0: + /* + kill_thread() waits till the spawned thread finishes after it's + killed. Hence, we delete here memory which is no more referenced from + a running thread. + */ + delete et_old; + /* + We don't signal COND_new_work here because: + 1. Even if the dropped event is on top of the queue this will not + move another one to be executed before the time the one on the + top (but could be at the same second as the dropped one) + 2. If this was the last event on the queue, then pthread_cond_timedwait + in ::run() will finish and then see that the queue is empty and + call cond_wait(). Hence, no need to interrupt the blocked + ::run() thread. + */ + break; + default: + DBUG_ASSERT(0); + } + } + + DBUG_RETURN(res); +} + + +/* + Searches for an event in the scheduler queue + + SYNOPSIS + Event_scheduler::find_event() + etn The event to find + comparator The function to use for comparing + remove_from_q If found whether to remove from the Q + + RETURN VALUE + NULL Not found + otherwise Address + + NOTE + The caller should do the locking also the caller is responsible for + actual signalling in case an event is removed from the queue + (signalling COND_new_work for instance). +*/ + +Event_timed * +Event_scheduler::find_event(Event_timed *etn, bool remove_from_q) +{ + uint i; + DBUG_ENTER("Event_scheduler::find_event"); + + for (i= 0; i < queue.elements; ++i) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", etn->dbname.str, etn->name.str, + et->dbname.str, et->name.str)); + if (event_timed_identifier_equal(etn, et)) + { + if (remove_from_q) + queue_remove(&queue, i); + DBUG_RETURN(et); + } + } + + DBUG_RETURN(NULL); +} + + +/* + Drops all events from the in-memory queue and disk that match + certain pattern evaluated by a comparator function + + SYNOPSIS + Event_scheduler::drop_matching_events() + thd THD + pattern A pattern string + comparator The function to use for comparing + + RETURN VALUE + -1 Scheduler not working + >=0 Number of dropped events + + NOTE + Expected is the caller to acquire lock on LOCK_scheduler_data +*/ + +void +Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, + bool (*comparator)(Event_timed *,LEX_STRING *)) +{ + DBUG_ENTER("Event_scheduler::drop_matching_events"); + DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern->length, pattern->str, + state)); + if (is_running_or_suspended()) + { + uint i= 0, dropped= 0; + while (i < queue.elements) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); + if (comparator(et, pattern)) + { + /* + The queue is ordered. If we remove an element, then all elements after + it will shift one position to the left, if we imagine it as an array + from left to the right. In this case we should not increment the + counter and the (i < queue.elements) condition is ok. + */ + queue_remove(&queue, i); + + /* See replace_event() */ + switch (et->kill_thread(thd)) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et->flags |= EVENT_FREE_WHEN_FINISHED; + ++dropped; + break; + case 0: + delete et; + ++dropped; + break; + default: + DBUG_ASSERT(0); + } + } + else + i++; + } + DBUG_PRINT("info", ("Dropped %lu", dropped)); + } + /* + Don't send COND_new_work because no need to wake up the scheduler thread. + When it wakes next time up it will recalculate how much more it should + sleep if the top of the queue has been changed by this method. + */ + + DBUG_VOID_RETURN; +} + + +/* + Drops all events from the in-memory queue and disk that are from + certain schema. + + SYNOPSIS + Event_scheduler::drop_schema_events() + thd THD + db The schema name + + RETURN VALUE + -1 Scheduler not working + >=0 Number of dropped events +*/ + +int +Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema) +{ + int ret; + DBUG_ENTER("Event_scheduler::drop_schema_events"); + LOCK_SCHEDULER_DATA(); + if (is_running_or_suspended()) + drop_matching_events(thd, schema, event_timed_db_equal); + + ret= db_drop_events_from_table(thd, schema); + UNLOCK_SCHEDULER_DATA(); + + DBUG_RETURN(ret); +} + + +extern pthread_attr_t connection_attrib; + + +/* + Starts the event scheduler + + SYNOPSIS + Event_scheduler::start() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_scheduler::start() +{ + bool ret= FALSE; + pthread_t th; + DBUG_ENTER("Event_scheduler::start"); + + LOCK_SCHEDULER_DATA(); + /* If already working or starting don't make another attempt */ + DBUG_ASSERT(state == INITIALIZED); + if (state > INITIALIZED) + { + DBUG_PRINT("info", ("scheduler is already running or starting")); + ret= TRUE; + goto end; + } + + /* + Now if another thread calls start it will bail-out because the branch + above will be executed. Thus no two or more child threads will be forked. + If the child thread cannot start for some reason then `state` is set + to CANTSTART and COND_started is also signaled. In this case we + set `state` back to INITIALIZED so another attempt to start the scheduler + can be made. + */ + state= COMMENCING; + /* Fork */ + if (pthread_create(&th, &connection_attrib, event_scheduler_thread, + (void*)this)) + { + DBUG_PRINT("error", ("cannot create a new thread")); + state= INITIALIZED; + ret= TRUE; + goto end; + } + + /* Wait till the child thread has booted (w/ or wo success) */ + while (!is_running_or_suspended() && state != CANTSTART) + cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); + + /* + If we cannot start for some reason then don't prohibit further attempts. + Set back to INITIALIZED. + */ + if (state == CANTSTART) + { + state= INITIALIZED; + ret= TRUE; + goto end; + } + +end: + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(ret); +} + + +/* + Starts the event scheduler in suspended mode. + + SYNOPSIS + Event_scheduler::start_suspended() + + RETURN VALUE + TRUE OK + FALSE Error +*/ + +bool +Event_scheduler::start_suspended() +{ + DBUG_ENTER("Event_scheduler::start_suspended"); + start_scheduler_suspended= TRUE; + DBUG_RETURN(start()); +} + + + +/* + Report back that we cannot start. Used for ocasions where + we can't go into ::run() and have to report externally. + + SYNOPSIS + Event_scheduler::report_error_during_start() +*/ + +inline void +Event_scheduler::report_error_during_start() +{ + DBUG_ENTER("Event_scheduler::report_error_during_start"); + + LOCK_SCHEDULER_DATA(); + state= CANTSTART; + DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); + pthread_cond_signal(&cond_vars[COND_started_or_stopped]); + UNLOCK_SCHEDULER_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + The internal loop of the event scheduler + + SYNOPSIS + Event_scheduler::run() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Failure +*/ + +bool +Event_scheduler::run(THD *thd) +{ + int ret; + struct timespec abstime; + DBUG_ENTER("Event_scheduler::run"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + LOCK_SCHEDULER_DATA(); + ret= load_events_from_db(thd); + + if (!ret) + { + thread_id= thd->thread_id; + state= start_scheduler_suspended? SUSPENDED:RUNNING; + start_scheduler_suspended= FALSE; + } + else + state= CANTSTART; + + DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); + pthread_cond_signal(&cond_vars[COND_started_or_stopped]); + if (ret) + { + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(TRUE); + } + if (!check_n_suspend_if_needed(thd)) + UNLOCK_SCHEDULER_DATA(); + + sql_print_information("SCHEDULER: Manager thread started with id %lu", + thd->thread_id); + abstime.tv_nsec= 0; + while (is_running_or_suspended()) + { + Event_timed *et; + + LOCK_SCHEDULER_DATA(); + if (check_n_wait_for_non_empty_queue(thd)) + continue; + + /* On TRUE data is unlocked, go back to the beginning */ + if (check_n_suspend_if_needed(thd)) + continue; + + /* Guaranteed locked here */ + if (state == IN_SHUTDOWN || shutdown_in_progress) + { + UNLOCK_SCHEDULER_DATA(); + break; + } + DBUG_ASSERT(state == RUNNING); + + et= (Event_timed *)queue_top(&queue); + + /* Skip disabled events */ + if (et->status != Event_timed::ENABLED) + { + sql_print_error("SCHEDULER: Found a disabled event %*s.%*s in the queue", + et->dbname.length, et->dbname.str, et->name.length, + et->name.str); + queue_remove(&queue, 0); + /* ToDo: check this again */ + delete et; + UNLOCK_SCHEDULER_DATA(); + continue; + } + thd->proc_info= (char *)"Computing"; + DBUG_PRINT("evex manager",("computing time to sleep till next exec")); + /* Timestamp is in UTC */ + abstime.tv_sec= sec_since_epoch_TIME(&et->execute_at); + + thd->end_time(); + if (abstime.tv_sec > thd->query_start()) + { + /* Event trigger time is in the future */ + thd->proc_info= (char *)"Sleep"; + DBUG_PRINT("info", ("Going to sleep. Should wakeup after approx %d secs", + abstime.tv_sec - thd->query_start())); + DBUG_PRINT("info", ("Entering condition because waiting for activation")); + /* + Use THD::enter_cond()/exit_cond() or we won't be able to kill a + sleeping thread. Though ::stop() can do it by sending COND_new_work + an user can't by just issuing 'KILL x'; . In the latter case + pthread_cond_timedwait() will wait till `abstime`. + "Sleeping until next time" + */ + thd->enter_cond(&cond_vars[COND_new_work],&LOCK_scheduler_data,"Sleeping"); + + pthread_cond_timedwait(&cond_vars[COND_new_work], &LOCK_scheduler_data, + &abstime); + + DBUG_PRINT("info", ("Manager woke up. state is %d", state)); + /* + If we get signal we should recalculate the whether it's the right time + because there could be : + 1. Spurious wake-up + 2. The top of the queue was changed (new one becase of add/drop/replace) + */ + /* This will do implicit UNLOCK_SCHEDULER_DATA() */ + thd->exit_cond(""); + } + else + { + thd->proc_info= (char *)"Executing"; + /* + Execute the event. An error may occur if a thread cannot be forked. + In this case stop the manager. + We should enter ::execute_top() with locked LOCK_scheduler_data. + */ + int ret= execute_top(thd); + UNLOCK_SCHEDULER_DATA(); + if (ret) + break; + } + } + + thd->proc_info= (char *)"Cleaning"; + + LOCK_SCHEDULER_DATA(); + /* + It's possible that a user has used (SQL)COM_KILL. Hence set the appropriate + state because it is only set by ::stop(). + */ + if (state != IN_SHUTDOWN) + { + DBUG_PRINT("info", ("We got KILL but the but not from ::stop()")); + state= IN_SHUTDOWN; + } + UNLOCK_SCHEDULER_DATA(); + + sql_print_information("SCHEDULER: Shutting down"); + + thd->proc_info= (char *)"Cleaning queue"; + clean_queue(thd); + THD_CHECK_SENTRY(thd); + + /* free mamager_root memory but don't destroy the root */ + thd->proc_info= (char *)"Cleaning memory root"; + free_root(&scheduler_root, MYF(0)); + THD_CHECK_SENTRY(thd); + + /* + We notify the waiting thread which shutdowns us that we have cleaned. + There are few more instructions to be executed in this pthread but + they don't affect manager structures thus it's safe to signal already + at this point. + */ + LOCK_SCHEDULER_DATA(); + thd->proc_info= (char *)"Sending shutdown signal"; + DBUG_PRINT("info", ("Sending COND_started_or_stopped")); + if (state == IN_SHUTDOWN) + pthread_cond_signal(&cond_vars[COND_started_or_stopped]); + + state= INITIALIZED; + /* + We set it here because ::run() can stop not only because of ::stop() + call but also because of `KILL x` + */ + thread_id= 0; + sql_print_information("SCHEDULER: Stopped"); + UNLOCK_SCHEDULER_DATA(); + + /* We have modified, we set back */ + thd->query= NULL; + thd->query_length= 0; + + DBUG_RETURN(FALSE); +} + + +/* + Executes the top element of the queue. Auxiliary method for ::run(). + + SYNOPSIS + Event_scheduler::execute_top() + + RETURN VALUE + FALSE OK + TRUE Failure + + NOTE + NO locking is done. EXPECTED is that the caller should have locked + the queue (w/ LOCK_scheduler_data). +*/ + +bool +Event_scheduler::execute_top(THD *thd) +{ + int spawn_ret_code; + bool ret= FALSE; + DBUG_ENTER("Event_scheduler::execute_top"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + Event_timed *et= (Event_timed *)queue_top(&queue); + + /* Is it good idea to pass a stack address ?*/ + Worker_thread_param param(et); + + pthread_mutex_lock(¶m.LOCK_started); + /* + We don't lock LOCK_scheduler_data fpr workers_increment() because it's a + pre-requisite for calling the current_method. + */ + switch ((spawn_ret_code= et->spawn_now(event_worker_thread, ¶m))) { + case EVENT_EXEC_CANT_FORK: + /* + We don't lock LOCK_scheduler_data here because it's a pre-requisite + for calling the current_method. + */ + sql_print_error("SCHEDULER: Problem while trying to create a thread"); + ret= TRUE; + break; + case EVENT_EXEC_ALREADY_EXEC: + /* + We don't lock LOCK_scheduler_data here because it's a pre-requisite + for calling the current_method. + */ + sql_print_information("SCHEDULER: %s.%s in execution. Skip this time.", + et->dbname.str, et->name.str); + if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) + queue_remove(&queue, 0);// 0 is top, internally 1 + else + queue_replaced(&queue); + break; + default: + DBUG_ASSERT(!spawn_ret_code); + if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) + queue_remove(&queue, 0);// 0 is top, internally 1 + else + queue_replaced(&queue); + /* + We don't lock LOCK_scheduler_data here because it's a pre-requisite + for calling the current_method. + */ + if (likely(!spawn_ret_code)) + { + /* Wait the forked thread to start */ + do { + pthread_cond_wait(¶m.COND_started, ¶m.LOCK_started); + } while (!param.started); + } + /* + param was allocated on the stack so no explicit delete as well as + in this moment it's no more used in the spawned thread so it's safe + to be deleted. + */ + break; + } + pthread_mutex_unlock(¶m.LOCK_started); + /* `param` is on the stack and will be destructed by the compiler */ + + DBUG_RETURN(ret); +} + + +/* + Cleans the scheduler's queue. Auxiliary method for ::run(). + + SYNOPSIS + Event_scheduler::clean_queue() + thd Thread +*/ + +void +Event_scheduler::clean_queue(THD *thd) +{ + CHARSET_INFO *scs= system_charset_info; + uint i; + DBUG_ENTER("Event_scheduler::clean_queue"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + LOCK_SCHEDULER_DATA(); + stop_all_running_events(thd); + UNLOCK_SCHEDULER_DATA(); + + sql_print_information("SCHEDULER: Emptying the queue"); + + /* empty the queue */ + for (i= 0; i < queue.elements; ++i) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + et->free_sp(); + delete et; + } + resize_queue(&queue, 0); + + DBUG_VOID_RETURN; +} + + +/* + Stops all running events + + SYNOPSIS + Event_scheduler::stop_all_running_events() + thd Thread + + NOTE + LOCK_scheduler data must be acquired prior to call to this method +*/ + +void +Event_scheduler::stop_all_running_events(THD *thd) +{ + CHARSET_INFO *scs= system_charset_info; + uint i; + DYNAMIC_ARRAY running_threads; + THD *tmp; + DBUG_ENTER("Event_scheduler::stop_all_running_events"); + DBUG_PRINT("enter", ("workers_count=%d", workers_count())); + + my_init_dynamic_array(&running_threads, sizeof(ulong), 10, 10); + + bool had_super= FALSE; + VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + if (tmp->command == COM_DAEMON) + continue; + if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) + push_dynamic(&running_threads, (gptr) &tmp->thread_id); + } + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + /* We need temporarily SUPER_ACL to be able to kill our offsprings */ + if (!(thd->security_ctx->master_access & SUPER_ACL)) + thd->security_ctx->master_access|= SUPER_ACL; + else + had_super= TRUE; + + char tmp_buff[10*STRING_BUFFER_USUAL_SIZE]; + char int_buff[STRING_BUFFER_USUAL_SIZE]; + String tmp_string(tmp_buff, sizeof(tmp_buff), scs); + String int_string(int_buff, sizeof(int_buff), scs); + tmp_string.length(0); + + for (i= 0; i < running_threads.elements; ++i) + { + int ret; + ulong thd_id= *dynamic_element(&running_threads, i, ulong*); + + int_string.set((longlong) thd_id,scs); + tmp_string.append(int_string); + if (i < running_threads.elements - 1) + tmp_string.append(' '); + + if ((ret= kill_one_thread(thd, thd_id, FALSE))) + { + sql_print_error("SCHEDULER: Error killing %lu code=%d", thd_id, ret); + break; + } + } + if (running_threads.elements) + sql_print_information("SCHEDULER: Killing workers :%s", tmp_string.c_ptr()); + + if (!had_super) + thd->security_ctx->master_access &= ~SUPER_ACL; + + delete_dynamic(&running_threads); + + sql_print_information("SCHEDULER: Waiting for worker threads to finish"); + + while (workers_count()) + my_sleep(100000); + + DBUG_VOID_RETURN; +} + + +/* + Stops the event scheduler + + SYNOPSIS + Event_scheduler::stop() + + RETURN VALUE + OP_OK OK + OP_CANT_KILL Error during stopping of manager thread + OP_NOT_RUNNING Manager not working + + NOTE + The caller must have acquited LOCK_scheduler_data. +*/ + +enum Event_scheduler::enum_error_code +Event_scheduler::stop() +{ + THD *thd= current_thd; + DBUG_ENTER("Event_scheduler::stop"); + DBUG_PRINT("enter", ("thd=%p", current_thd)); + + LOCK_SCHEDULER_DATA(); + if (!is_running_or_suspended()) + { + /* + One situation to be here is if there was a start that forked a new + thread but the new thread did not acquire yet LOCK_scheduler_data. + Hence, in this case return an error. + */ + DBUG_PRINT("info", ("manager not running but %d. doing nothing", state)); + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(OP_NOT_RUNNING); + } + state= IN_SHUTDOWN; + + DBUG_PRINT("info", ("Manager thread has id %d", thread_id)); + sql_print_information("SCHEDULER: Killing manager thread %lu", thread_id); + + /* + Sending the COND_new_work to ::run() is a way to get this working without + race conditions. If we use kill_one_thread() it will call THD::awake() and + because in ::run() both THD::enter_cond()/::exit_cond() are used, + THD::awake() will try to lock LOCK_scheduler_data. If we UNLOCK it before, + then the pthread_cond_signal(COND_started_or_stopped) could be signaled in + ::run() and we can miss the signal before we relock. A way is to use + another mutex for this shutdown procedure but better not. + */ + pthread_cond_signal(&cond_vars[COND_new_work]); + /* Or we are suspended - then we should wake up */ + pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); + + /* Guarantee we don't catch spurious signals */ + sql_print_information("SCHEDULER: Waiting the manager thread to reply"); + while (state != INITIALIZED) + { + DBUG_PRINT("info", ("Waiting for COND_started_or_stopped from the manager " + "thread. Current value of state is %d . " + "workers count=%d", state, workers_count())); + cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); + } + DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); + UNLOCK_SCHEDULER_DATA(); + + DBUG_RETURN(OP_OK); +} + + +/* + Suspends or resumes the scheduler. + SUSPEND - it won't execute any event till resumed. + RESUME - it will resume if suspended. + + SYNOPSIS + Event_scheduler::suspend_or_resume() + + RETURN VALUE + OP_OK OK +*/ + +enum Event_scheduler::enum_error_code +Event_scheduler::suspend_or_resume( + enum Event_scheduler::enum_suspend_or_resume action) +{ + DBUG_ENTER("Event_scheduler::suspend_or_resume"); + DBUG_PRINT("enter", ("action=%d", action)); + + LOCK_SCHEDULER_DATA(); + + if ((action == SUSPEND && state == SUSPENDED) || + (action == RESUME && state == RUNNING)) + { + DBUG_PRINT("info", ("Either trying to suspend suspended or resume " + "running scheduler. Doing nothing.")); + } + else + { + /* Wake the main thread up if he is asleep */ + DBUG_PRINT("info", ("Sending signal")); + if (action==SUSPEND) + { + state= SUSPENDED; + pthread_cond_signal(&cond_vars[COND_new_work]); + } + else + { + state= RUNNING; + pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); + } + DBUG_PRINT("info", ("Waiting on COND_suspend_or_resume")); + cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); + DBUG_PRINT("info", ("Got response")); + } + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(OP_OK); +} + + +/* + Returns the number of executing events. + + SYNOPSIS + Event_scheduler::workers_count() +*/ + +uint +Event_scheduler::workers_count() +{ + THD *tmp; + uint count= 0; + + DBUG_ENTER("Event_scheduler::workers_count"); + VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + if (tmp->command == COM_DAEMON) + continue; + if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) + ++count; + } + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + DBUG_PRINT("exit", ("%d", count)); + DBUG_RETURN(count); +} + + +/* + Checks and suspends if needed + + SYNOPSIS + Event_scheduler::check_n_suspend_if_needed() + thd Thread + + RETURN VALUE + FALSE Not suspended, we haven't slept + TRUE We were suspended. LOCK_scheduler_data is unlocked. + + NOTE + The caller should have locked LOCK_scheduler_data! + The mutex will be unlocked in case this function returns TRUE +*/ + +bool +Event_scheduler::check_n_suspend_if_needed(THD *thd) +{ + bool was_suspended= FALSE; + DBUG_ENTER("Event_scheduler::check_n_suspend_if_needed"); + if (thd->killed && !shutdown_in_progress) + { + state= SUSPENDED; + thd->killed= THD::NOT_KILLED; + } + if (state == SUSPENDED) + { + thd->enter_cond(&cond_vars[COND_suspend_or_resume], &LOCK_scheduler_data, + "Suspended"); + /* Send back signal to the thread that asked us to suspend operations */ + pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); + sql_print_information("SCHEDULER: Suspending operations"); + was_suspended= TRUE; + } + while (state == SUSPENDED) + { + cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); + DBUG_PRINT("info", ("Woke up after waiting on COND_suspend_or_resume")); + if (state != SUSPENDED) + { + pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); + sql_print_information("SCHEDULER: Resuming operations"); + } + } + if (was_suspended) + { + if (queue.elements) + { + uint i; + DBUG_PRINT("info", ("We have to recompute the execution times")); + + for (i= 0; i < queue.elements; i++) + ((Event_timed*)queue_element(&queue, i))->compute_next_execution_time(); + queue_fix(&queue); + } + /* This will implicitly unlock LOCK_scheduler_data */ + thd->exit_cond(""); + } + DBUG_RETURN(was_suspended); +} + + +/* + Checks for empty queue and waits till new element gets in + + SYNOPSIS + Event_scheduler::check_n_wait_for_non_empty_queue() + thd Thread + + RETURN VALUE + FALSE Did not wait - LOCK_scheduler_data still locked. + TRUE Waited - LOCK_scheduler_data unlocked. + + NOTE + The caller should have locked LOCK_scheduler_data! +*/ + +bool +Event_scheduler::check_n_wait_for_non_empty_queue(THD *thd) +{ + bool slept= FALSE; + DBUG_ENTER("Event_scheduler::check_n_wait_for_non_empty_queue"); + DBUG_PRINT("enter", ("q.elements=%lu state=%s", + queue.elements, states_names[state])); + + if (!queue.elements) + thd->enter_cond(&cond_vars[COND_new_work], &LOCK_scheduler_data, + "Empty queue, sleeping"); + + /* Wait in a loop protecting against catching spurious signals */ + while (!queue.elements && state == RUNNING) + { + slept= TRUE; + DBUG_PRINT("info", ("Entering condition because of empty queue")); + cond_wait(COND_new_work, &LOCK_scheduler_data); + DBUG_PRINT("info", ("Manager woke up. Hope we have events now. state=%d", + state)); + /* + exit_cond does implicit mutex_UNLOCK, we needed it locked if + 1. we loop again + 2. end the current loop and start doing calculations + */ + } + if (slept) + thd->exit_cond(""); + + DBUG_PRINT("exit", ("q.elements=%lu state=%s thd->killed=%d", + queue.elements, states_names[state], thd->killed)); + + DBUG_RETURN(slept); +} + + +/* + Wrapper for pthread_mutex_lock + + SYNOPSIS + Event_scheduler::lock_data() + mutex Mutex to lock + line The line number on which the lock is done + + RETURN VALUE + Error code of pthread_mutex_lock() +*/ + +inline void +Event_scheduler::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler::lock_mutex"); + DBUG_PRINT("enter", ("mutex_lock=%p func=%s line=%u", + &LOCK_scheduler_data, func, line)); + pthread_mutex_lock(&LOCK_scheduler_data); + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_scheduler_data_locked= TRUE; + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_mutex_unlock + + SYNOPSIS + Event_scheduler::unlock_data() + mutex Mutex to unlock + line The line number on which the unlock is done +*/ + +inline void +Event_scheduler::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler::UNLOCK_mutex"); + DBUG_PRINT("enter", ("mutex_unlock=%p func=%s line=%u", + &LOCK_scheduler_data, func, line)); + mutex_last_unlocked_at_line= line; + mutex_scheduler_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + pthread_mutex_unlock(&LOCK_scheduler_data); + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_cond_wait + + SYNOPSIS + Event_scheduler::cond_wait() + cond Conditional to wait for + mutex Mutex of the conditional + + RETURN VALUE + Error code of pthread_cond_wait() +*/ + +inline int +Event_scheduler::cond_wait(enum Event_scheduler::enum_cond_vars cond, + pthread_mutex_t *mutex) +{ + int ret; + DBUG_ENTER("Event_scheduler::cond_wait"); + DBUG_PRINT("enter", ("cond=%s mutex=%p", cond_vars_names[cond], mutex)); + ret= pthread_cond_wait(&cond_vars[cond_waiting_on=cond], mutex); + cond_waiting_on= COND_NONE; + DBUG_RETURN(ret); +} + + +/* + Checks whether the scheduler is in a running or suspended state. + + SYNOPSIS + Event_scheduler::is_running_or_suspended() + + RETURN VALUE + TRUE Either running or suspended + FALSE IN_SHUTDOWN, not started, etc. +*/ + +inline bool +Event_scheduler::is_running_or_suspended() +{ + return (state == SUSPENDED || state == RUNNING); +} + + +/* + Returns the current state of the scheduler + + SYNOPSIS + Event_scheduler::get_state() +*/ + +enum Event_scheduler::enum_state +Event_scheduler::get_state() +{ + enum Event_scheduler::enum_state ret; + DBUG_ENTER("Event_scheduler::get_state"); + /* lock_data & unlock_data are not static */ + pthread_mutex_lock(&singleton.LOCK_scheduler_data); + ret= singleton.state; + pthread_mutex_unlock(&singleton.LOCK_scheduler_data); + DBUG_RETURN(ret); +} + + +/* + Returns whether the scheduler was initialized. + + SYNOPSIS + Event_scheduler::initialized() + + RETURN VALUE + FALSE Was not initialized so far + TRUE Was initialized +*/ + +bool +Event_scheduler::initialized() +{ + DBUG_ENTER("Event_scheduler::initialized"); + DBUG_RETURN(Event_scheduler::get_state() != UNINITIALIZED); +} + + +/* + Returns the number of elements in the queue + + SYNOPSIS + Event_scheduler::events_count() + + RETURN VALUE + 0 Number of Event_timed objects in the queue +*/ + +uint +Event_scheduler::events_count() +{ + uint n; + DBUG_ENTER("Event_scheduler::events_count"); + LOCK_SCHEDULER_DATA(); + n= queue.elements; + UNLOCK_SCHEDULER_DATA(); + + DBUG_RETURN(n); +} + + +/* + Looks for a named event in mysql.event and then loads it from + the table, compiles and inserts it into the cache. + + SYNOPSIS + Event_scheduler::load_event() + thd THD + etn The name of the event to load and compile on scheduler's root + etn_new The loaded event + + RETURN VALUE + NULL Error during compile or the event is non-enabled. + otherwise Address +*/ + +enum Event_scheduler::enum_error_code +Event_scheduler::load_event(THD *thd, Event_timed *etn, Event_timed **etn_new) +{ + int ret= 0; + MEM_ROOT *tmp_mem_root; + Event_timed *et_loaded= NULL; + Open_tables_state backup; + + DBUG_ENTER("Event_scheduler::load_and_compile_event"); + DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str)); + + thd->reset_n_backup_open_tables_state(&backup); + /* No need to use my_error() here because db_find_event() has done it */ + { + sp_name spn(etn->dbname, etn->name); + ret= db_find_event(thd, &spn, &etn->definer, &et_loaded, NULL, + &scheduler_root); + } + thd->restore_backup_open_tables_state(&backup); + /* In this case no memory was allocated so we don't need to clean */ + if (ret) + DBUG_RETURN(OP_LOAD_ERROR); + + if (et_loaded->status != Event_timed::ENABLED) + { + /* + We don't load non-enabled events. + In db_find_event() `et_new` was allocated on the heap and not on + scheduler_root therefore we delete it here. + */ + delete et_loaded; + DBUG_RETURN(OP_DISABLED_EVENT); + } + + et_loaded->compute_next_execution_time(); + *etn_new= et_loaded; + + DBUG_RETURN(OP_OK); +} + + +/* + Loads all ENABLED events from mysql.event into the prioritized + queue. Called during scheduler main thread initialization. Compiles + the events. Creates Event_timed instances for every ENABLED event + from mysql.event. + + SYNOPSIS + Event_scheduler::load_events_from_db() + thd - Thread context. Used for memory allocation in some cases. + + RETURN VALUE + 0 OK + !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, + EVEX_COMPILE_ERROR) - in all these cases mysql.event was + tampered. + + NOTES + Reports the error to the console +*/ + +int +Event_scheduler::load_events_from_db(THD *thd) +{ + TABLE *table; + READ_RECORD read_record_info; + int ret= -1; + uint count= 0; + bool clean_the_queue= FALSE; + /* Compile the events on this root but only for syntax check, then discard */ + MEM_ROOT boot_root; + + DBUG_ENTER("Event_scheduler::load_events_from_db"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + if (state > COMMENCING) + { + DBUG_ASSERT(0); + sql_print_error("SCHEDULER: Trying to load events while already running."); + DBUG_RETURN(EVEX_GENERAL_ERROR); + } + + if ((ret= Events::open_event_table(thd, TL_READ, &table))) + { + sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); + DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); + } + + init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + init_read_record(&read_record_info, thd, table ,NULL,1,0); + while (!(read_record_info.read_record(&read_record_info))) + { + Event_timed *et; + if (!(et= new Event_timed)) + { + DBUG_PRINT("info", ("Out of memory")); + clean_the_queue= TRUE; + break; + } + DBUG_PRINT("info", ("Loading event from row.")); + + if ((ret= et->load_from_row(&scheduler_root, table))) + { + clean_the_queue= TRUE; + sql_print_error("SCHEDULER: Error while loading from mysql.event. " + "Table probably corrupted"); + break; + } + if (et->status != Event_timed::ENABLED) + { + DBUG_PRINT("info",("%s is disabled",et->name.str)); + delete et; + continue; + } + + DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); + + /* We load only on scheduler root just to check whether the body compiles */ + switch (ret= et->compile(thd, &boot_root)) { + case EVEX_MICROSECOND_UNSUP: + et->free_sp(); + sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " + "supported but found in mysql.event"); + goto end; + case EVEX_COMPILE_ERROR: + sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", + et->dbname.str, et->name.str); + goto end; + default: + /* Free it, it will be compiled again on the worker thread */ + et->free_sp(); + break; + } + + /* let's find when to be executed */ + if (et->compute_next_execution_time()) + { + sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." + " Skipping", et->dbname.str, et->name.str); + continue; + } + + DBUG_PRINT("load_events_from_db", ("Adding %p to the exec list.")); + queue_insert_safe(&queue, (byte *) et); + count++; + } +end: + end_read_record(&read_record_info); + free_root(&boot_root, MYF(0)); + + if (clean_the_queue) + { + for (count= 0; count < queue.elements; ++count) + queue_remove(&queue, 0); + ret= -1; + } + else + { + ret= 0; + sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); + } + + /* Force close to free memory */ + thd->version--; + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); + DBUG_RETURN(ret); +} + + +/* + Opens mysql.db and mysql.user and checks whether: + 1. mysql.db has column Event_priv at column 20 (0 based); + 2. mysql.user has column Event_priv at column 29 (0 based); + + SYNOPSIS + Event_scheduler::check_system_tables() +*/ + +bool +Event_scheduler::check_system_tables(THD *thd) +{ + TABLE_LIST tables; + bool not_used; + Open_tables_state backup; + bool ret; + + DBUG_ENTER("Event_scheduler::check_system_tables"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + thd->reset_n_backup_open_tables_state(&backup); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "db"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + ret= table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, + mysql_db_table_fields, &mysql_db_table_last_check, + ER_CANNOT_LOAD_FROM_TABLE); + close_thread_tables(thd); + } + if (ret) + DBUG_RETURN(TRUE); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "user"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + if (tables.table->s->fields < 29 || + strncmp(tables.table->field[29]->field_name, + STRING_WITH_LEN("Event_priv"))) + { + sql_print_error("mysql.user has no `Event_priv` column at position 29"); + ret= TRUE; + } + close_thread_tables(thd); + } + + thd->restore_backup_open_tables_state(&backup); + + DBUG_RETURN(ret); +} + + +/* + Inits mutexes. + + SYNOPSIS + Event_scheduler::init_mutexes() +*/ + +void +Event_scheduler::init_mutexes() +{ + pthread_mutex_init(&singleton.LOCK_scheduler_data, MY_MUTEX_INIT_FAST); +} + + +/* + Destroys mutexes. + + SYNOPSIS + Event_scheduler::destroy_mutexes() +*/ + +void +Event_scheduler::destroy_mutexes() +{ + pthread_mutex_destroy(&singleton.LOCK_scheduler_data); +} + + +/* + Dumps some data about the internal status of the scheduler. + + SYNOPSIS + Event_scheduler::dump_internal_status() + thd THD + + RETURN VALUE + 0 OK + 1 Error +*/ + +int +Event_scheduler::dump_internal_status(THD *thd) +{ + DBUG_ENTER("dump_internal_status"); +#ifndef DBUG_OFF + CHARSET_INFO *scs= system_charset_info; + Protocol *protocol= thd->protocol; + List<Item> field_list; + int ret; + char tmp_buff[5*STRING_BUFFER_USUAL_SIZE]; + char int_buff[STRING_BUFFER_USUAL_SIZE]; + String tmp_string(tmp_buff, sizeof(tmp_buff), scs); + String int_string(int_buff, sizeof(int_buff), scs); + tmp_string.length(0); + int_string.length(0); + + field_list.push_back(new Item_empty_string("Name", 20)); + field_list.push_back(new Item_empty_string("Value",20)); + if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(1); + + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("state"), scs); + protocol->store(states_names[singleton.state].str, + states_names[singleton.state].length, + scs); + + ret= protocol->write(); + /* + If not initialized - don't show anything else. get_instance() + will otherwise implicitly initialize it. We don't want that. + */ + if (singleton.state >= INITIALIZED) + { + /* last locked at*/ + /* + The first thing to do, or get_instance() will overwrite the values. + mutex_last_locked_at_line / mutex_last_unlocked_at_line + */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("last locked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + singleton.mutex_last_locked_in_func, + singleton.mutex_last_locked_at_line)); + protocol->store(&tmp_string); + ret= protocol->write(); + + /* last unlocked at*/ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("last unlocked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + singleton.mutex_last_unlocked_in_func, + singleton.mutex_last_unlocked_at_line)); + protocol->store(&tmp_string); + ret= protocol->write(); + + /* waiting on */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("waiting on condition"), scs); + tmp_string.length(scs->cset-> + snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s", + (singleton.cond_waiting_on != COND_NONE) ? + cond_vars_names[singleton.cond_waiting_on]: + "NONE")); + protocol->store(&tmp_string); + ret= protocol->write(); + + Event_scheduler *scheduler= get_instance(); + + /* workers_count */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("workers_count"), scs); + int_string.set((longlong) scheduler->workers_count(), scs); + protocol->store(&int_string); + ret= protocol->write(); + + /* queue.elements */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("queue.elements"), scs); + int_string.set((longlong) scheduler->queue.elements, scs); + protocol->store(&int_string); + ret= protocol->write(); + + /* scheduler_data_locked */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler data locked"), scs); + int_string.set((longlong) scheduler->mutex_scheduler_data_locked, scs); + protocol->store(&int_string); + ret= protocol->write(); + } + send_eof(thd); +#endif + DBUG_RETURN(0); +} diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h new file mode 100644 index 0000000000000000000000000000000000000000..247b4481c9f4b5b2933be59c7ec9b5c198fd1a69 --- /dev/null +++ b/sql/event_scheduler.h @@ -0,0 +1,254 @@ +#ifndef _EVENT_SCHEDULER_H_ +#define _EVENT_SCHEDULER_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +class THD; +typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); + +int +events_init(); + +void +events_shutdown(); + + +class Event_scheduler +{ +public: + /* Return codes */ + enum enum_error_code + { + OP_OK= 0, + OP_NOT_RUNNING, + OP_CANT_KILL, + OP_CANT_INIT, + OP_DISABLED_EVENT, + OP_LOAD_ERROR, + OP_ALREADY_EXISTS + }; + + enum enum_state + { + UNINITIALIZED= 0, + INITIALIZED, + COMMENCING, + CANTSTART, + RUNNING, + SUSPENDED, + IN_SHUTDOWN + }; + + enum enum_suspend_or_resume + { + SUSPEND= 1, + RESUME= 2 + }; + + /* Singleton access */ + static Event_scheduler* + get_instance(); + + /* Methods for queue management follow */ + + enum enum_error_code + add_event(THD *thd, Event_timed *et, bool check_existence); + + bool + drop_event(THD *thd, Event_timed *et); + + enum enum_error_code + replace_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, + LEX_STRING *new_name); + + int + drop_schema_events(THD *thd, LEX_STRING *schema); + + int + drop_user_events(THD *thd, LEX_STRING *definer, uint *dropped_num) + { DBUG_ASSERT(0); return 0;} + + uint + events_count(); + + /* State changing methods follow */ + + bool + start(); + + enum enum_error_code + stop(); + + bool + start_suspended(); + + bool + run(THD *thd); + + enum enum_error_code + suspend_or_resume(enum enum_suspend_or_resume action); + + bool + init(); + + void + destroy(); + + static void + init_mutexes(); + + static void + destroy_mutexes(); + + void + report_error_during_start(); + + /* Information retrieving methods follow */ + + enum enum_state + get_state(); + + bool + initialized(); + + static int + dump_internal_status(THD *thd); + + static bool + check_system_tables(THD *thd); + +private: + Event_timed * + find_event(Event_timed *etn, bool remove_from_q); + + uint + workers_count(); + + bool + is_running_or_suspended(); + + /* helper functions */ + bool + execute_top(THD *thd); + + void + clean_queue(THD *thd); + + void + stop_all_running_events(THD *thd); + + enum enum_error_code + load_event(THD *thd, Event_timed *etn, Event_timed **etn_new); + + int + load_events_from_db(THD *thd); + + void + drop_matching_events(THD *thd, LEX_STRING *pattern, + bool (*)(Event_timed *,LEX_STRING *)); + + bool + check_n_suspend_if_needed(THD *thd); + + bool + check_n_wait_for_non_empty_queue(THD *thd); + + /* Singleton DP is used */ + Event_scheduler(); + + enum enum_cond_vars + { + COND_NONE= -1, + /* + COND_new_work is a conditional used to signal that there is a change + of the queue that should inform the executor thread that new event should + be executed sooner than previously expected, because of add/replace event. + */ + COND_new_work= 0, + /* + COND_started is a conditional used to synchronize the thread in which + ::start() was called and the spawned thread. ::start() spawns a new thread + and then waits on COND_started but also checks when awaken that `state` is + either RUNNING or CANTSTART. Then it returns back. + */ + COND_started_or_stopped, + /* + Conditional used for signalling from the scheduler thread back to the + thread that calls ::suspend() or ::resume. Synchronizing the calls. + */ + COND_suspend_or_resume, + /* Must be always last */ + COND_LAST + }; + + /* Singleton instance */ + static Event_scheduler singleton; + + /* This is the current status of the life-cycle of the manager. */ + enum enum_state state; + + /* Set to start the scheduler in suspended state */ + bool start_scheduler_suspended; + + /* + LOCK_scheduler_data is the mutex which protects the access to the + manager's queue as well as used when signalling COND_new_work, + COND_started and COND_shutdown. + */ + pthread_mutex_t LOCK_scheduler_data; + + /* + Holds the thread id of the executor thread or 0 if the executor is not + running. It is used by ::shutdown() to know which thread to kill with + kill_one_thread(). The latter wake ups a thread if it is waiting on a + conditional variable and sets thd->killed to non-zero. + */ + ulong thread_id; + + pthread_cond_t cond_vars[COND_LAST]; + static const char * const cond_vars_names[COND_LAST]; + + /* The MEM_ROOT of the object */ + MEM_ROOT scheduler_root; + + /* The sorted queue with the Event_timed objects */ + QUEUE queue; + + uint mutex_last_locked_at_line; + uint mutex_last_unlocked_at_line; + const char* mutex_last_locked_in_func; + const char* mutex_last_unlocked_in_func; + enum enum_cond_vars cond_waiting_on; + bool mutex_scheduler_data_locked; + + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); + + void + unlock_data(const char *func, uint line); + + int + cond_wait(enum enum_cond_vars, pthread_mutex_t *mutex); + +private: + /* Prevent use of these */ + Event_scheduler(const Event_scheduler &); + void operator=(Event_scheduler &); +}; + +#endif /* _EVENT_SCHEDULER_H_ */ diff --git a/sql/event_timed.cc b/sql/event_timed.cc index 879f4d6a3c912abf683f2e919d684cf71995a0a9..fd85f5ebecc94c17e1f4dd6ebbe0a9512ad5e746 100644 --- a/sql/event_timed.cc +++ b/sql/event_timed.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2005 MySQL AB +/* Copyright (C) 2004-2006 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,7 +17,82 @@ #define MYSQL_LEX 1 #include "event_priv.h" #include "event.h" -#include "sp.h" +#include "sp_head.h" + + +/* + Constructor + + SYNOPSIS + Event_timed::Event_timed() +*/ + +Event_timed::Event_timed():in_spawned_thread(0),locked_by_thread_id(0), + running(0), thread_id(0), status_changed(false), + last_executed_changed(false), expression(0), + created(0), modified(0), + on_completion(Event_timed::ON_COMPLETION_DROP), + status(Event_timed::ENABLED), sphead(0), + sql_mode(0), body_begin(0), dropped(false), + free_sphead_on_delete(true), flags(0) + +{ + pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST); + pthread_cond_init(&this->COND_finished, NULL); + init(); +} + + +/* + Destructor + + SYNOPSIS + Event_timed::~Event_timed() +*/ + +Event_timed::~Event_timed() +{ + deinit_mutexes(); + + if (free_sphead_on_delete) + free_sp(); +} + + +/* + Destructor + + SYNOPSIS + Event_timed::~deinit_mutexes() +*/ + +void +Event_timed::deinit_mutexes() +{ + pthread_mutex_destroy(&this->LOCK_running); + pthread_cond_destroy(&this->COND_finished); +} + + +/* + Checks whether the event is running + + SYNOPSIS + Event_timed::is_running() +*/ + +bool +Event_timed::is_running() +{ + bool ret; + + VOID(pthread_mutex_lock(&this->LOCK_running)); + ret= running; + VOID(pthread_mutex_unlock(&this->LOCK_running)); + + return ret; +} + /* Init all member variables @@ -238,7 +313,7 @@ Event_timed::init_execute_at(THD *thd, Item *expr) expr how much? new_interval what is the interval - RETURNS + RETURN VALUE 0 OK EVEX_PARSE_ERROR fix_fields failed EVEX_BAD_PARAMS Interval is not positive @@ -342,7 +417,7 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) DATE_ADD(NOW(), INTERVAL 1 DAY) -- start tommorow at same time. - RETURNS + RETURN VALUE 0 OK EVEX_PARSE_ERROR fix_fields failed EVEX_BAD_PARAMS starts before now @@ -408,7 +483,7 @@ Event_timed::init_starts(THD *thd, Item *new_starts) DATE_ADD(NOW(), INTERVAL 1 DAY) -- end tommorow at same time. - RETURNS + RETURN VALUE 0 OK EVEX_PARSE_ERROR fix_fields failed ER_WRONG_VALUE starts distant date (after year 2037) @@ -492,6 +567,9 @@ Event_timed::init_comment(THD *thd, LEX_STRING *set_comment) SYNOPSIS Event_timed::init_definer() + + RETURN VALUE + 0 OK */ int @@ -534,6 +612,10 @@ Event_timed::init_definer(THD *thd) SYNOPSIS Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) + RETURN VALUE + 0 OK + EVEX_GET_FIELD_FAILED Error + NOTES This method is silent on errors and should behave like that. Callers should handle throwing of error messages. The reason is that the class @@ -555,29 +637,29 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) et= this; - if (table->s->fields != EVEX_FIELD_COUNT) + if (table->s->fields != Events::FIELD_COUNT) goto error; if ((et->dbname.str= get_field(mem_root, - table->field[EVEX_FIELD_DB])) == NULL) + table->field[Events::FIELD_DB])) == NULL) goto error; et->dbname.length= strlen(et->dbname.str); if ((et->name.str= get_field(mem_root, - table->field[EVEX_FIELD_NAME])) == NULL) + table->field[Events::FIELD_NAME])) == NULL) goto error; et->name.length= strlen(et->name.str); if ((et->body.str= get_field(mem_root, - table->field[EVEX_FIELD_BODY])) == NULL) + table->field[Events::FIELD_BODY])) == NULL) goto error; et->body.length= strlen(et->body.str); if ((et->definer.str= get_field(mem_root, - table->field[EVEX_FIELD_DEFINER])) == NullS) + table->field[Events::FIELD_DEFINER])) == NullS) goto error; et->definer.length= strlen(et->definer.str); @@ -594,69 +676,71 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) et->definer_host.str= strmake_root(mem_root, ptr + 1, len);/* 1:because of @*/ et->definer_host.length= len; - et->starts_null= table->field[EVEX_FIELD_STARTS]->is_null(); - res1= table->field[EVEX_FIELD_STARTS]->get_date(&et->starts,TIME_NO_ZERO_DATE); + et->starts_null= table->field[Events::FIELD_STARTS]->is_null(); + res1= table->field[Events::FIELD_STARTS]-> + get_date(&et->starts,TIME_NO_ZERO_DATE); - et->ends_null= table->field[EVEX_FIELD_ENDS]->is_null(); - res2= table->field[EVEX_FIELD_ENDS]->get_date(&et->ends, TIME_NO_ZERO_DATE); + et->ends_null= table->field[Events::FIELD_ENDS]->is_null(); + res2= table->field[Events::FIELD_ENDS]->get_date(&et->ends, TIME_NO_ZERO_DATE); - if (!table->field[EVEX_FIELD_INTERVAL_EXPR]->is_null()) - et->expression= table->field[EVEX_FIELD_INTERVAL_EXPR]->val_int(); + if (!table->field[Events::FIELD_INTERVAL_EXPR]->is_null()) + et->expression= table->field[Events::FIELD_INTERVAL_EXPR]->val_int(); else et->expression= 0; /* If res1 and res2 are true then both fields are empty. - Hence if EVEX_FIELD_EXECUTE_AT is empty there is an error. + Hence if Events::FIELD_EXECUTE_AT is empty there is an error. */ - et->execute_at_null= table->field[EVEX_FIELD_EXECUTE_AT]->is_null(); + et->execute_at_null= + table->field[Events::FIELD_EXECUTE_AT]->is_null(); DBUG_ASSERT(!(et->starts_null && et->ends_null && !et->expression && et->execute_at_null)); if (!et->expression && - table->field[EVEX_FIELD_EXECUTE_AT]->get_date(&et->execute_at, - TIME_NO_ZERO_DATE)) + table->field[Events::FIELD_EXECUTE_AT]-> get_date(&et->execute_at, + TIME_NO_ZERO_DATE)) goto error; /* In DB the values start from 1 but enum interval_type starts from 0 */ - if (!table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->is_null()) - et->interval= (interval_type) - ((ulonglong) table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->val_int() - 1); + if (!table->field[Events::FIELD_TRANSIENT_INTERVAL]->is_null()) + et->interval= (interval_type) ((ulonglong) + table->field[Events::FIELD_TRANSIENT_INTERVAL]->val_int() - 1); else et->interval= (interval_type) 0; - et->created= table->field[EVEX_FIELD_CREATED]->val_int(); - et->modified= table->field[EVEX_FIELD_MODIFIED]->val_int(); + et->created= table->field[Events::FIELD_CREATED]->val_int(); + et->modified= table->field[Events::FIELD_MODIFIED]->val_int(); - table->field[EVEX_FIELD_LAST_EXECUTED]-> + table->field[Events::FIELD_LAST_EXECUTED]-> get_date(&et->last_executed, TIME_NO_ZERO_DATE); last_executed_changed= false; /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */ - if ((ptr= get_field(mem_root, table->field[EVEX_FIELD_STATUS])) == NullS) + if ((ptr= get_field(mem_root, table->field[Events::FIELD_STATUS])) == NullS) goto error; DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr)); - et->status= (ptr[0]=='E'? MYSQL_EVENT_ENABLED:MYSQL_EVENT_DISABLED); + et->status= (ptr[0]=='E'? Event_timed::ENABLED:Event_timed::DISABLED); /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */ if ((ptr= get_field(mem_root, - table->field[EVEX_FIELD_ON_COMPLETION])) == NullS) + table->field[Events::FIELD_ON_COMPLETION])) == NullS) goto error; - et->on_completion= (ptr[0]=='D'? MYSQL_EVENT_ON_COMPLETION_DROP: - MYSQL_EVENT_ON_COMPLETION_PRESERVE); + et->on_completion= (ptr[0]=='D'? Event_timed::ON_COMPLETION_DROP: + Event_timed::ON_COMPLETION_PRESERVE); - et->comment.str= get_field(mem_root, table->field[EVEX_FIELD_COMMENT]); + et->comment.str= get_field(mem_root, table->field[Events::FIELD_COMMENT]); if (et->comment.str != NullS) et->comment.length= strlen(et->comment.str); else et->comment.length= 0; - et->sql_mode= (ulong) table->field[EVEX_FIELD_SQL_MODE]->val_int(); + et->sql_mode= (ulong) table->field[Events::FIELD_SQL_MODE]->val_int(); DBUG_RETURN(0); error: @@ -676,7 +760,7 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) i_value quantity of time type interval to add i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...) - RETURNS + RETURN VALUE 0 OK 1 Error @@ -834,6 +918,10 @@ bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec, SYNOPSIS Event_timed::compute_next_execution_time() + RETURN VALUE + FALSE OK + TRUE Error + NOTES The time is set in execute_at, if no more executions the latter is set to 0000-00-00. @@ -843,7 +931,6 @@ bool Event_timed::compute_next_execution_time() { TIME time_now; - my_time_t now; int tmp; DBUG_ENTER("Event_timed::compute_next_execution_time"); @@ -852,7 +939,7 @@ Event_timed::compute_next_execution_time() TIME_to_ulonglong_datetime(&ends), TIME_to_ulonglong_datetime(&last_executed))); - if (status == MYSQL_EVENT_DISABLED) + if (status == Event_timed::DISABLED) { DBUG_PRINT("compute_next_execution_time", ("Event %s is DISABLED", name.str)); @@ -866,14 +953,15 @@ Event_timed::compute_next_execution_time() { DBUG_PRINT("info",("One-time event %s.%s of was already executed", dbname.str, name.str, definer.str)); - dropped= (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP); + dropped= (on_completion == Event_timed::ON_COMPLETION_DROP); DBUG_PRINT("info",("One-time event will be dropped=%d.", dropped)); - status= MYSQL_EVENT_DISABLED; + status= Event_timed::DISABLED; status_changed= true; } goto ret; } + current_thd->end_time(); my_tz_UTC->gmt_sec_to_TIME(&time_now, current_thd->query_start()); DBUG_PRINT("info",("NOW=[%llu]", TIME_to_ulonglong_datetime(&time_now))); @@ -885,9 +973,9 @@ Event_timed::compute_next_execution_time() /* time_now is after ends. don't execute anymore */ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + if (on_completion == Event_timed::ON_COMPLETION_DROP) dropped= true; - status= MYSQL_EVENT_DISABLED; + status= Event_timed::DISABLED; status_changed= true; goto ret; @@ -937,7 +1025,6 @@ Event_timed::compute_next_execution_time() { TIME next_exec; - DBUG_PRINT("info", ("Executed at least once")); if (get_next_time(&next_exec, &starts, &time_now, last_executed.year? &last_executed:&starts, expression, interval)) @@ -946,12 +1033,15 @@ Event_timed::compute_next_execution_time() /* There was previous execution */ if (my_time_compare(&ends, &next_exec) == -1) { - DBUG_PRINT("info", ("Next execution after ENDS. Stop executing.")); + DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.", + name.str)); /* Next execution after ends. No more executions */ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + if (on_completion == Event_timed::ON_COMPLETION_DROP) dropped= true; + status= Event_timed::DISABLED; + status_changed= true; } else { @@ -1006,7 +1096,6 @@ Event_timed::compute_next_execution_time() { TIME next_exec; - DBUG_PRINT("info", ("Executed at least once.")); if (get_next_time(&next_exec, &starts, &time_now, last_executed.year? &last_executed:&starts, expression, interval)) @@ -1042,7 +1131,9 @@ Event_timed::compute_next_execution_time() DBUG_PRINT("info", ("Next execution after ENDS. Stop executing.")); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + status= Event_timed::DISABLED; + status_changed= true; + if (on_completion == Event_timed::ON_COMPLETION_DROP) dropped= true; } else @@ -1083,9 +1174,6 @@ Event_timed::mark_last_executed(THD *thd) my_tz_UTC->gmt_sec_to_TIME(&time_now, (my_time_t) thd->query_start()); last_executed= time_now; /* was execute_at */ -#ifdef ANDREY_0 - last_executed= execute_at; -#endif last_executed_changed= true; } @@ -1125,7 +1213,7 @@ Event_timed::drop(THD *thd) RETURN VALUE 0 OK - SP_OPEN_TABLE_FAILED Error while opening mysql.event for writing + EVEX_OPEN_TABLE_FAILED Error while opening mysql.event for writing EVEX_WRITE_ROW_FAILED On error to write to disk others return code from SE in case deletion of the event @@ -1149,9 +1237,9 @@ Event_timed::update_fields(THD *thd) thd->reset_n_backup_open_tables_state(&backup); - if (evex_open_event_table(thd, TL_WRITE, &table)) + if (Events::open_event_table(thd, TL_WRITE, &table)) { - ret= SP_OPEN_TABLE_FAILED; + ret= EVEX_OPEN_TABLE_FAILED; goto done; } @@ -1165,15 +1253,15 @@ Event_timed::update_fields(THD *thd) if (last_executed_changed) { - table->field[EVEX_FIELD_LAST_EXECUTED]->set_notnull(); - table->field[EVEX_FIELD_LAST_EXECUTED]->store_time(&last_executed, - MYSQL_TIMESTAMP_DATETIME); + table->field[Events::FIELD_LAST_EXECUTED]->set_notnull(); + table->field[Events::FIELD_LAST_EXECUTED]->store_time(&last_executed, + MYSQL_TIMESTAMP_DATETIME); last_executed_changed= false; } if (status_changed) { - table->field[EVEX_FIELD_STATUS]->set_notnull(); - table->field[EVEX_FIELD_STATUS]->store((longlong)status, true); + table->field[Events::FIELD_STATUS]->set_notnull(); + table->field[Events::FIELD_STATUS]->store((longlong)status, true); status_changed= false; } @@ -1215,8 +1303,8 @@ Event_timed::get_create_event(THD *thd, String *buf) DBUG_ENTER("get_create_event"); DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]", body.length, body.str)); - if (expression && - event_reconstruct_interval_expression(&expr_buf, interval, expression)) + if (expression && Events::reconstruct_interval_expression(&expr_buf, interval, + expression)) DBUG_RETURN(EVEX_MICROSECOND_UNSUP); buf->append(STRING_WITH_LEN("CREATE EVENT ")); @@ -1243,12 +1331,12 @@ Event_timed::get_create_event(THD *thd, String *buf) buf->append(STRING_WITH_LEN("'")); } - if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + if (on_completion == Event_timed::ON_COMPLETION_DROP) buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE ")); else buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE ")); - if (status == MYSQL_EVENT_ENABLED) + if (status == Event_timed::ENABLED) buf->append(STRING_WITH_LEN("ENABLE")); else buf->append(STRING_WITH_LEN("DISABLE")); @@ -1273,7 +1361,7 @@ Event_timed::get_create_event(THD *thd, String *buf) thd THD mem_root If != NULL use it to compile the event on it - RETURNS + RETURN VALUE 0 success -99 No rights on this.dbname.str -100 event in execution (parallel execution is impossible) @@ -1283,7 +1371,6 @@ Event_timed::get_create_event(THD *thd, String *buf) int Event_timed::execute(THD *thd, MEM_ROOT *mem_root) { - Security_context *save_ctx; /* this one is local and not needed after exec */ Security_context security_ctx; int ret= 0; @@ -1301,16 +1388,8 @@ Event_timed::execute(THD *thd, MEM_ROOT *mem_root) running= true; VOID(pthread_mutex_unlock(&this->LOCK_running)); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - change_security_context(thd, &security_ctx, &save_ctx); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - if (!sphead && (ret= compile(thd, mem_root))) goto done; - /* Now we are sure we have valid this->sphead so we can copy the context */ - sphead->m_security_ctx= security_ctx; /* THD::~THD will clean this or if there is DROP DATABASE in the SP then it will be free there. It should not point to our buffer which is allocated @@ -1334,12 +1413,11 @@ Event_timed::execute(THD *thd, MEM_ROOT *mem_root) definer_host.str, dbname.str)); ret= -99; } - restore_security_context(thd, save_ctx); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); VOID(pthread_mutex_lock(&this->LOCK_running)); running= false; + /* Will compile every time a new sp_head on different root */ + free_sp(); VOID(pthread_mutex_unlock(&this->LOCK_running)); done: @@ -1361,55 +1439,16 @@ Event_timed::execute(THD *thd, MEM_ROOT *mem_root) /* - Switches the security context - Synopsis - Event_timed::change_security_context() - thd - thread - backup - where to store the old context - - RETURN - 0 - OK - 1 - Error (generates error too) -*/ -bool -Event_timed::change_security_context(THD *thd, Security_context *s_ctx, - Security_context **backup) -{ - DBUG_ENTER("Event_timed::change_security_context"); - DBUG_PRINT("info",("%s@%s@%s",definer_user.str,definer_host.str, dbname.str)); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - s_ctx->init(); - *backup= 0; - if (acl_getroot_no_password(s_ctx, definer_user.str, definer_host.str, - definer_host.str, dbname.str)) - { - my_error(ER_NO_SUCH_USER, MYF(0), definer_user.str, definer_host.str); - DBUG_RETURN(true); - } - *backup= thd->security_ctx; - thd->security_ctx= s_ctx; -#endif - DBUG_RETURN(false); -} - - -/* - Restores the security context - Synopsis - Event_timed::restore_security_context() - thd - thread - backup - switch to this context + Frees the memory of the sp_head object we hold + SYNOPSIS + Event_timed::free_sp() */ void -Event_timed::restore_security_context(THD *thd, Security_context *backup) +Event_timed::free_sp() { - DBUG_ENTER("Event_timed::restore_security_context"); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (backup) - thd->security_ctx= backup; -#endif - DBUG_VOID_RETURN; + delete sphead; + sphead= 0; } @@ -1445,6 +1484,9 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) CHARSET_INFO *old_character_set_client, *old_collation_connection, *old_character_set_results; + Security_context *save_ctx; + /* this one is local and not needed after exec */ + Security_context security_ctx; DBUG_ENTER("Event_timed::compile"); @@ -1488,8 +1530,10 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) thd->query= show_create.c_ptr(); thd->query_length= show_create.length(); - DBUG_PRINT("Event_timed::compile", ("query:%s",thd->query)); + DBUG_PRINT("info", ("query:%s",thd->query)); + change_security_context(thd, definer_user, definer_host, dbname, + &security_ctx, &save_ctx); thd->lex= &lex; lex_start(thd, (uchar*)thd->query, thd->query_length); lex.et_compile_phase= TRUE; @@ -1527,6 +1571,7 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) lex.et->deinit_mutexes(); lex_end(&lex); + restore_security_context(thd, save_ctx); DBUG_PRINT("note", ("return old data on its place. set back NAMES")); thd->lex= old_lex; @@ -1548,72 +1593,63 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) } -/* - Checks whether this thread can lock the object for modification -> - preventing being spawned for execution, and locks if possible. - use ::can_spawn_now() only for basic checking because a race - condition may occur between the check and eventual modification (deletion) - of the object. - - Returns - true - locked - false - cannot lock -*/ - -my_bool -Event_timed::can_spawn_now_n_lock(THD *thd) -{ - my_bool ret= FALSE; - VOID(pthread_mutex_lock(&this->LOCK_running)); - if (!in_spawned_thread) - { - in_spawned_thread= TRUE; - ret= TRUE; - locked_by_thread_id= thd->thread_id; - } - VOID(pthread_mutex_unlock(&this->LOCK_running)); - return ret; -} - - extern pthread_attr_t connection_attrib; /* Checks whether is possible and forks a thread. Passes self as argument. - Returns - EVENT_EXEC_STARTED - OK - EVENT_EXEC_ALREADY_EXEC - Thread not forked, already working - EVENT_EXEC_CANT_FORK - Unable to spawn thread (error) + RETURN VALUE + EVENT_EXEC_STARTED OK + EVENT_EXEC_ALREADY_EXEC Thread not forked, already working + EVENT_EXEC_CANT_FORK Unable to spawn thread (error) */ int -Event_timed::spawn_now(void * (*thread_func)(void*)) +Event_timed::spawn_now(void * (*thread_func)(void*), void *arg) { + THD *thd= current_thd; int ret= EVENT_EXEC_STARTED; - static uint exec_num= 0; DBUG_ENTER("Event_timed::spawn_now"); - DBUG_PRINT("info", ("this=0x%lx", this)); DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str)); VOID(pthread_mutex_lock(&this->LOCK_running)); + + DBUG_PRINT("info", ("SCHEDULER: execute_at of %s is %lld", name.str, + TIME_to_ulonglong_datetime(&execute_at))); + mark_last_executed(thd); + if (compute_next_execution_time()) + { + sql_print_error("SCHEDULER: Error while computing time of %s.%s . " + "Disabling after execution.", dbname.str, name.str); + status= DISABLED; + } + DBUG_PRINT("evex manager", ("[%10s] next exec at [%llu]", name.str, + TIME_to_ulonglong_datetime(&execute_at))); + /* + 1. For one-time event : year is > 0 and expression is 0 + 2. For recurring, expression is != -=> check execute_at_null in this case + */ + if ((execute_at.year && !expression) || execute_at_null) + { + sql_print_information("SCHEDULER: [%s.%s of %s] no more executions " + "after this one", dbname.str, name.str, + definer.str); + flags |= EVENT_EXEC_NO_MORE | EVENT_FREE_WHEN_FINISHED; + } + + update_fields(thd); + if (!in_spawned_thread) { pthread_t th; in_spawned_thread= true; - if (pthread_create(&th, &connection_attrib, thread_func, (void*)this)) + + if (pthread_create(&th, &connection_attrib, thread_func, arg)) { DBUG_PRINT("info", ("problem while spawning thread")); ret= EVENT_EXEC_CANT_FORK; in_spawned_thread= false; } -#ifndef DBUG_OFF - else - { - sql_print_information("SCHEDULER: Started thread %d", ++exec_num); - DBUG_PRINT("info", ("thread spawned")); - } -#endif } else { @@ -1626,55 +1662,207 @@ Event_timed::spawn_now(void * (*thread_func)(void*)) } -void +bool Event_timed::spawn_thread_finish(THD *thd) { + bool should_free; DBUG_ENTER("Event_timed::spawn_thread_finish"); - VOID(pthread_mutex_lock(&this->LOCK_running)); + VOID(pthread_mutex_lock(&LOCK_running)); in_spawned_thread= false; - if ((flags & EVENT_EXEC_NO_MORE) || status == MYSQL_EVENT_DISABLED) + DBUG_PRINT("info", ("Sending COND_finished for thread %d", thread_id)); + thread_id= 0; + if (dropped) + drop(thd); + pthread_cond_broadcast(&COND_finished); + should_free= flags & EVENT_FREE_WHEN_FINISHED; + VOID(pthread_mutex_unlock(&LOCK_running)); + DBUG_RETURN(should_free); +} + + +/* + Kills a running event + SYNOPSIS + Event_timed::kill_thread() + + RETURN VALUE + 0 OK + -1 EVEX_CANT_KILL + !0 Error +*/ + +int +Event_timed::kill_thread(THD *thd) +{ + int ret= 0; + DBUG_ENTER("Event_timed::kill_thread"); + pthread_mutex_lock(&LOCK_running); + DBUG_PRINT("info", ("thread_id=%lu", thread_id)); + + if (thread_id == thd->thread_id) { - DBUG_PRINT("info", ("%s exec no more. to drop=%d", name.str, dropped)); - if (dropped) - drop(thd); - VOID(pthread_mutex_unlock(&this->LOCK_running)); - delete this; - DBUG_VOID_RETURN; + /* + We don't kill ourselves in cases like : + alter event e_43 do alter event e_43 do set @a = 4 because + we will never receive COND_finished. + */ + DBUG_PRINT("info", ("It's not safe to kill ourselves in self altering queries")); + ret= EVEX_CANT_KILL; } - VOID(pthread_mutex_unlock(&this->LOCK_running)); - DBUG_VOID_RETURN; + else if (thread_id && !(ret= kill_one_thread(thd, thread_id, false))) + { + thd->enter_cond(&COND_finished, &LOCK_running, "Waiting for finished"); + DBUG_PRINT("info", ("Waiting for COND_finished from thread %d", thread_id)); + while (thread_id) + pthread_cond_wait(&COND_finished, &LOCK_running); + + DBUG_PRINT("info", ("Got COND_finished")); + /* This will implicitly unlock LOCK_running. Hence we return before that */ + thd->exit_cond(""); + + DBUG_RETURN(0); + } + else if (!thread_id && in_spawned_thread) + { + /* + Because the manager thread waits for the forked thread to update thread_id + this situation is impossible. + */ + DBUG_ASSERT(0); + } + pthread_mutex_unlock(&LOCK_running); + DBUG_PRINT("exit", ("%d", ret)); + DBUG_RETURN(ret); +} + + +/* + Checks whether two events have the same name + + SYNOPSIS + event_timed_name_equal() + + RETURN VALUE + TRUE names are equal + FALSE names are not equal +*/ + +bool +event_timed_name_equal(Event_timed *et, LEX_STRING *name) +{ + return !sortcmp_lex_string(et->name, *name, system_charset_info); } /* - Unlocks the object after it has been locked with ::can_spawn_now_n_lock() + Checks whether two events are in the same schema + + SYNOPSIS + event_timed_db_equal() + + RETURN VALUE + TRUE schemas are equal + FALSE schemas are not equal +*/ + +bool +event_timed_db_equal(Event_timed *et, LEX_STRING *db) +{ + return !sortcmp_lex_string(et->dbname, *db, system_charset_info); +} + + +/* + Checks whether two events have the same definer + + SYNOPSIS + event_timed_definer_equal() Returns - 0 - ok - 1 - not locked by this thread + TRUE definers are equal + FALSE definers are not equal */ -int -Event_timed::spawn_unlock(THD *thd) +bool +event_timed_definer_equal(Event_timed *et, LEX_STRING *definer) { - int ret= 0; - VOID(pthread_mutex_lock(&this->LOCK_running)); - if (!in_spawned_thread) + return !sortcmp_lex_string(et->definer, *definer, system_charset_info); +} + + +/* + Checks whether two events are equal by identifiers + + SYNOPSIS + event_timed_identifier_equal() + + RETURN VALUE + TRUE equal + FALSE not equal +*/ + +bool +event_timed_identifier_equal(Event_timed *a, Event_timed *b) +{ + return event_timed_name_equal(a, &b->name) && + event_timed_db_equal(a, &b->dbname) && + event_timed_definer_equal(a, &b->definer); +} + + +/* + Switches the security context + SYNOPSIS + change_security_context() + thd Thread + user The user + host The host of the user + db The schema for which the security_ctx will be loaded + s_ctx Security context to load state into + backup Where to store the old context + + RETURN VALUE + 0 - OK + 1 - Error (generates error too) +*/ + +bool +change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *s_ctx, + Security_context **backup) +{ + DBUG_ENTER("change_security_context"); + DBUG_PRINT("info",("%s@%s@%s", user.str, host.str, db.str)); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + s_ctx->init(); + *backup= 0; + if (acl_getroot_no_password(s_ctx, user.str, host.str, host.str, db.str)) { - if (locked_by_thread_id == thd->thread_id) - { - in_spawned_thread= FALSE; - locked_by_thread_id= 0; - } - else - { - sql_print_error("A thread tries to unlock when he hasn't locked. " - "thread_id=%ld locked by %ld", - thd->thread_id, locked_by_thread_id); - DBUG_ASSERT(0); - ret= 1; - } + my_error(ER_NO_SUCH_USER, MYF(0), user.str, host.str); + DBUG_RETURN(TRUE); } - VOID(pthread_mutex_unlock(&this->LOCK_running)); - return ret; + *backup= thd->security_ctx; + thd->security_ctx= s_ctx; +#endif + DBUG_RETURN(FALSE); +} + + +/* + Restores the security context + SYNOPSIS + restore_security_context() + thd - thread + backup - switch to this context +*/ + +void +restore_security_context(THD *thd, Security_context *backup) +{ + DBUG_ENTER("restore_security_context"); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (backup) + thd->security_ctx= backup; +#endif + DBUG_VOID_RETURN; } diff --git a/sql/lex.h b/sql/lex.h index 555a68dc38836f516d8cc681b612ed825d2adc18..d541a3fb228c5da392d66990b09c0dc84dbd7b49 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -453,6 +453,7 @@ static SYMBOL symbols[] = { { "RTREE", SYM(RTREE_SYM)}, { "SAVEPOINT", SYM(SAVEPOINT_SYM)}, { "SCHEDULE", SYM(SCHEDULE_SYM)}, + { "SCHEDULER", SYM(SCHEDULER_SYM)}, { "SCHEMA", SYM(DATABASE)}, { "SCHEMAS", SYM(DATABASES)}, { "SECOND", SYM(SECOND_SYM)}, diff --git a/sql/log.cc b/sql/log.cc index fa86064682d971f6c7e0c8598f1b7595d0c9de4e..31133a71757fe24a8b6c1fb8899423eda71814ab 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -55,6 +55,14 @@ static int binlog_commit(THD *thd, bool all); static int binlog_rollback(THD *thd, bool all); static int binlog_prepare(THD *thd, bool all); +sql_print_message_func sql_print_message_handlers[3] = +{ + sql_print_information, + sql_print_warning, + sql_print_error +}; + + /* This is a POD. Please keep it that way! diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 650d2a52b53e57e4268559785304c593037158bf..e4fd4c0da059e011ace8e9056755e2d179503b0a 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -79,7 +79,8 @@ char *sql_strmake_with_convert(const char *str, uint32 arg_length, CHARSET_INFO *from_cs, uint32 max_res_length, CHARSET_INFO *to_cs, uint32 *result_length); -void kill_one_thread(THD *thd, ulong id, bool only_kill_query); +uint kill_one_thread(THD *thd, ulong id, bool only_kill_query); +void sql_kill(THD *thd, ulong id, bool only_kill_query); bool net_request_file(NET* net, const char* fname); char* query_table_status(THD *thd,const char *db,const char *table_name); @@ -1378,10 +1379,13 @@ bool init_errmessage(void); #endif /* MYSQL_SERVER */ void sql_perror(const char *message); + int vprint_msg_to_log(enum loglevel level, const char *format, va_list args); void sql_print_error(const char *format, ...); void sql_print_warning(const char *format, ...); void sql_print_information(const char *format, ...); +typedef void (*sql_print_message_func)(const char *format, ...); +extern sql_print_message_func sql_print_message_handlers[]; /* type of the log table */ #define QUERY_LOG_SLOW 1 diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 11c7e12f84f81ca2bbf9ac57144355b611485d6a..ccf52113c23db9ca7ad4adefea67815e04837375 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -864,8 +864,8 @@ static void close_connections(void) { DBUG_PRINT("quit",("Informing thread %ld that it's time to die", tmp->thread_id)); - /* We skip slave threads on this first loop through. */ - if (tmp->slave_thread) + /* We skip slave threads & scheduler on this first loop through. */ + if (tmp->slave_thread || tmp->system_thread == SYSTEM_THREAD_EVENT_SCHEDULER) continue; tmp->killed= THD::KILL_CONNECTION; @@ -884,6 +884,7 @@ static void close_connections(void) } (void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list + Events::shutdown(); end_slave(); if (thread_count) @@ -1299,6 +1300,7 @@ static void clean_up_mutexes() (void) pthread_mutex_destroy(&LOCK_bytes_sent); (void) pthread_mutex_destroy(&LOCK_bytes_received); (void) pthread_mutex_destroy(&LOCK_user_conn); + Events::destroy_mutexes(); #ifdef HAVE_OPENSSL (void) pthread_mutex_destroy(&LOCK_des_key_file); #ifndef HAVE_YASSL @@ -2854,6 +2856,7 @@ static int init_thread_environment() (void) pthread_mutex_init(&LOCK_server_started, MY_MUTEX_INIT_FAST); (void) pthread_cond_init(&COND_server_started,NULL); sp_cache_init(); + Events::init_mutexes(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); (void) pthread_attr_setdetachstate(&connection_attrib, @@ -2999,7 +3002,6 @@ static int init_server_components() #ifdef HAVE_REPLICATION init_slave_list(); #endif - init_events(); /* Setup logs */ @@ -3623,6 +3625,10 @@ we force server id to 2, but this MySQL server will not act as a slave."); mysqld_server_started= 1; pthread_cond_signal(&COND_server_started); + if (!opt_noacl) + { + Events::init(); + } #if defined(__NT__) || defined(HAVE_SMEM) handle_connections_methods(); #else @@ -3674,7 +3680,6 @@ we force server id to 2, but this MySQL server will not act as a slave."); clean_up(1); wait_for_signal_thread_to_end(); clean_up_mutexes(); - shutdown_events(); my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0); exit(0); @@ -4665,7 +4670,7 @@ enum options_mysqld OPT_MAX_BINLOG_DUMP_EVENTS, OPT_SPORADIC_BINLOG_DUMP_FAIL, OPT_SAFE_USER_CREATE, OPT_SQL_MODE, OPT_HAVE_NAMED_PIPE, - OPT_DO_PSTACK, OPT_EVENT_EXECUTOR, OPT_REPORT_HOST, + OPT_DO_PSTACK, OPT_EVENT_SCHEDULER, OPT_REPORT_HOST, OPT_REPORT_USER, OPT_REPORT_PASSWORD, OPT_REPORT_PORT, OPT_SHOW_SLAVE_AUTH_INFO, OPT_SLAVE_LOAD_TMPDIR, OPT_NO_MIX_TYPE, @@ -4998,9 +5003,9 @@ Disable with --skip-bdb (will save memory).", (gptr*) &global_system_variables.engine_condition_pushdown, (gptr*) &global_system_variables.engine_condition_pushdown, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"event-scheduler", OPT_EVENT_EXECUTOR, "Enable/disable the event scheduler.", - (gptr*) &opt_event_executor, (gptr*) &opt_event_executor, 0, GET_BOOL, NO_ARG, - 0/*default*/, 0/*min-value*/, 1/*max-value*/, 0, 0, 0}, + {"event-scheduler", OPT_EVENT_SCHEDULER, "Enable/disable the event scheduler.", + (gptr*) &Events::opt_event_scheduler, (gptr*) &Events::opt_event_scheduler, 0, GET_STR, + REQUIRED_ARG, 2/*default*/, 0/*min-value*/, 2/*max-value*/, 0, 0, 0}, {"exit-info", 'T', "Used for debugging; Use at your own risk!", 0, 0, 0, GET_LONG, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"external-locking", OPT_USE_LOCKING, "Use system (external) locking. With this option enabled you can run myisamchk to test (not repair) tables while the MySQL server is running.", @@ -7327,6 +7332,24 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), break; } #endif + case OPT_EVENT_SCHEDULER: + if (!argument) + Events::opt_event_scheduler= 2; + else + { + int type; + if ((type=find_type(argument, &Events::opt_typelib, 1)) <= 0) + { + fprintf(stderr,"Unknown option to event-scheduler: %s\n",argument); + exit(1); + } + /* + type= 1 2 3 4 5 6 + (OFF | 0) - (ON | 1) - (2 | SUSPEND) + */ + Events::opt_event_scheduler= (type-1) / 2; + } + break; case (int) OPT_SKIP_NEW: opt_specialflag|= SPECIAL_NO_NEW_FUNC; delay_key_write_options= (uint) DELAY_KEY_WRITE_NONE; diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 9cabe1a3df0001938a44bbf4838b787ebe77595d..66e2aa1c31ca84a7fa1c0818c2e3e9ba285a3817 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -61,12 +61,13 @@ static Slave_log_event* find_slave_event(IO_CACHE* log, static int init_failsafe_rpl_thread(THD* thd) { DBUG_ENTER("init_failsafe_rpl_thread"); + thd->system_thread = SYSTEM_THREAD_DELAYED_INSERT; /* thd->bootstrap is to report errors barely to stderr; if this code is enable again one day, one should check if bootstrap is still needed (maybe this thread has no other error reporting method). */ - thd->system_thread = thd->bootstrap = 1; + thd->bootstrap = 1; thd->security_ctx->skip_grants(); my_net_init(&thd->net, 0); thd->net.read_timeout = slave_net_timeout; diff --git a/sql/set_var.cc b/sql/set_var.cc index ae9e415174494a8aea42e7438bc1181db866fa22..242fe0f60680e87ab6341b6a85030457a23cdc77 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -56,6 +56,8 @@ #include <thr_alarm.h> #include <myisam.h> +#include "event_scheduler.h" + /* WITH_BERKELEY_STORAGE_ENGINE */ extern bool berkeley_shared_data; extern ulong berkeley_max_lock, berkeley_log_buffer_size; @@ -106,7 +108,6 @@ extern ulong ndb_report_thresh_binlog_mem_usage; -extern my_bool event_executor_running_global_var; static HASH system_variable_hash; const char *bool_type_names[]= { "OFF", "ON", NullS }; @@ -225,9 +226,8 @@ sys_var_long_ptr sys_delayed_insert_timeout("delayed_insert_timeout", &delayed_insert_timeout); sys_var_long_ptr sys_delayed_queue_size("delayed_queue_size", &delayed_queue_size); -sys_var_event_executor sys_event_executor("event_scheduler", - (my_bool *) - &event_executor_running_global_var); + +sys_var_event_scheduler sys_event_scheduler("event_scheduler"); sys_var_long_ptr sys_expire_logs_days("expire_logs_days", &expire_logs_days); sys_var_bool_ptr sys_flush("flush", &myisam_flush); @@ -768,7 +768,7 @@ SHOW_VAR init_vars[]= { {sys_div_precincrement.name,(char*) &sys_div_precincrement,SHOW_SYS}, {sys_engine_condition_pushdown.name, (char*) &sys_engine_condition_pushdown, SHOW_SYS}, - {sys_event_executor.name, (char*) &sys_event_executor, SHOW_SYS}, + {sys_event_scheduler.name, (char*) &sys_event_scheduler, SHOW_SYS}, {sys_expire_logs_days.name, (char*) &sys_expire_logs_days, SHOW_SYS}, {sys_flush.name, (char*) &sys_flush, SHOW_SYS}, {sys_flush_time.name, (char*) &sys_flush_time, SHOW_SYS}, @@ -3632,6 +3632,69 @@ byte *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) return (byte*) thd->strdup(buf); } + +/* + The update method of the global variable event_scheduler. + If event_scheduler is switched from 0 to 1 then the scheduler main + thread is resumed and if from 1 to 0 the scheduler thread is suspended + + SYNOPSIS + sys_var_event_scheduler::update() + thd Thread context (unused) + var The new value + + Returns + FALSE OK + TRUE Error +*/ + +bool +sys_var_event_scheduler::update(THD *thd, set_var *var) +{ + enum Event_scheduler::enum_error_code res; + Event_scheduler *scheduler= Event_scheduler::get_instance(); + /* here start the thread if not running. */ + DBUG_ENTER("sys_var_event_scheduler::update"); + + DBUG_PRINT("new_value", ("%lu", (bool)var->save_result.ulong_value)); + if (!scheduler->initialized()) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--event-scheduler=0"); + DBUG_RETURN(true); + } + + if (var->save_result.ulonglong_value < 1 || + var->save_result.ulonglong_value > 2) + { + char buf[64]; + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "event_scheduler", + llstr(var->save_result.ulonglong_value, buf)); + DBUG_RETURN(true); + } + if ((res= scheduler->suspend_or_resume(var->save_result.ulonglong_value == 1? + Event_scheduler::RESUME : + Event_scheduler::SUSPEND))) + my_error(ER_EVENT_SET_VAR_ERROR, MYF(0), (uint) res); + DBUG_RETURN((bool) res); +} + + +byte *sys_var_event_scheduler::value_ptr(THD *thd, enum_var_type type, + LEX_STRING *base) +{ + Event_scheduler *scheduler= Event_scheduler::get_instance(); + + if (!scheduler->initialized()) + thd->sys_var_tmp.long_value= 0; + else if (scheduler->get_state() == Event_scheduler::RUNNING) + thd->sys_var_tmp.long_value= 1; + else + thd->sys_var_tmp.long_value= 2; + + return (byte*) &thd->sys_var_tmp; +} + + /**************************************************************************** Used templates ****************************************************************************/ diff --git a/sql/set_var.h b/sql/set_var.h index e374ac4e9406a3c4e8b08d3d27ca5b0c7b0553fd..1049b154d47034bc827af35bc4c060b484558d17 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -871,13 +871,14 @@ class sys_var_trust_routine_creators :public sys_var_bool_ptr }; -class sys_var_event_executor :public sys_var_bool_ptr +class sys_var_event_scheduler :public sys_var_long_ptr { /* We need a derived class only to have a warn_deprecated() */ public: - sys_var_event_executor(const char *name_arg, my_bool *value_arg) : - sys_var_bool_ptr(name_arg, value_arg) {}; + sys_var_event_scheduler(const char *name_arg) : + sys_var_long_ptr(name_arg, NULL, NULL) {}; bool update(THD *thd, set_var *var); + byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); }; extern void fix_binlog_format_after_update(THD *thd, enum_var_type type); diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index afce112814716f46ecad32b4c7d39cfcd63880fd..7c465678e17d0683689f34123ada53981da05bf2 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5042,7 +5042,7 @@ ER_OPTION_PREVENTS_STATEMENT ger "Der MySQL-Server läuft mit der Option %s und kann diese Anweisung deswegen nicht ausführen" por "O servidor MySQL está rodando com a opção %s razão pela qual não pode executar esse commando" spa "El servidor MySQL está rodando con la opción %s tal que no puede ejecutar este comando" - swe "MySQL är startad med --skip-grant-tables. Pga av detta kan du inte använda detta kommando" + swe "MySQL är startad med %s. Pga av detta kan du inte använda detta kommando" ER_DUPLICATED_VALUE_IN_TYPE eng "Column '%-.100s' has duplicated value '%-.64s' in %s" ger "Feld '%-.100s' hat doppelten Wert '%-.64s' in %s" @@ -5844,4 +5844,7 @@ ER_CANT_CHANGE_TX_ISOLATION 25001 eng "Transaction isolation level can't be changed while a transaction is in progress" ER_DUP_ENTRY_AUTOINCREMENT_CASE eng "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.64s' for key '%-.64s'" - +ER_EVENT_MODIFY_QUEUE_ERROR + eng "Internal scheduler error %d" +ER_EVENT_SET_VAR_ERROR + eng "Error during starting/stopping of the scheduler. Error code %u" diff --git a/sql/sp.cc b/sql/sp.cc index 6f074fd7dceb0fba8e78ed37659b0c2495b5bd16..0e81e627f71ceefc92623c371b7ab914413314be 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -408,15 +408,13 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp, ulong old_sql_mode= thd->variables.sql_mode; ha_rows old_select_limit= thd->variables.select_limit; sp_rcontext *old_spcont= thd->spcont; - + char definer_user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT definer_user_name(definer_user_name_holder, - USERNAME_LENGTH); + LEX_STRING definer_user_name= { definer_user_name_holder, USERNAME_LENGTH }; char definer_host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT definer_host_name(definer_host_name_holder, - HOSTNAME_LENGTH); - + LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH }; + int ret; thd->variables.sql_mode= sql_mode; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index de56e261bd3e489b9f2a031036eae6fcd0c93c36..093ac3a3109de703e4e92158f6609882892ef434 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1823,10 +1823,10 @@ void sp_head::set_definer(const char *definer, uint definerlen) { char user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT user_name(user_name_holder, USERNAME_LENGTH); + LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH }; char host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT host_name(host_name_holder, HOSTNAME_LENGTH); + LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH }; parse_user(definer, definerlen, user_name.str, &user_name.length, host_name.str, &host_name.length); diff --git a/sql/spatial.cc b/sql/spatial.cc index e91653f79d59b193ae4a87084d615c5537843540..9f1f05aa18f931e631ad221777637b25d0d362ee 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -34,8 +34,11 @@ static Geometry::Class_info **ci_collection_end= Geometry::Class_info::Class_info(const char *name, int type_id, void(*create_func)(void *)): - m_name(name, strlen(name)), m_type_id(type_id), m_create_func(create_func) + m_type_id(type_id), m_create_func(create_func) { + m_name.str= (char *) name; + m_name.length= strlen(name); + ci_collection[type_id]= this; } diff --git a/sql/spatial.h b/sql/spatial.h index a6f74a1ada0c5a4910b22eb0e6ea354a3592dc2a..36949ff501496e57d9e4f920679332576de8fe28 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -200,7 +200,7 @@ class Geometry class Class_info { public: - LEX_STRING_WITH_INIT m_name; + LEX_STRING m_name; int m_type_id; void (*m_create_func)(void *); Class_info(const char *name, int type_id, void(*create_func)(void *)); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 4bc89bfe9163098e5e82751abde7e6938e6db1c1..9b2ad209e84f37cf33c04d74b5e6f617fa960b02 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -253,7 +253,8 @@ THD::THD() net.last_error[0]=0; // If error on boot net.query_cache_query=0; // If error on boot ull=0; - system_thread= cleanup_done= abort_on_warning= no_warnings_for_error= 0; + system_thread= NON_SYSTEM_THREAD; + cleanup_done= abort_on_warning= no_warnings_for_error= 0; peer_port= 0; // For SHOW PROCESSLIST #ifdef HAVE_ROW_BASED_REPLICATION transaction.m_pending_rows_event= 0; @@ -512,6 +513,8 @@ void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var) void THD::awake(THD::killed_state state_to_set) { + DBUG_ENTER("THD::awake"); + DBUG_PRINT("enter", ("this=0x%lx", this)); THD_CHECK_SENTRY(this); safe_mutex_assert_owner(&LOCK_delete); @@ -555,6 +558,7 @@ void THD::awake(THD::killed_state state_to_set) } pthread_mutex_unlock(&mysys_var->mutex); } + DBUG_VOID_RETURN; } /* @@ -2031,6 +2035,13 @@ void Security_context::skip_grants() } +bool Security_context::set_user(char *user_arg) +{ + safeFree(user); + user= my_strdup(user_arg, MYF(0)); + return user == 0; +} + /**************************************************************************** Handling of open and locked tables states. diff --git a/sql/sql_class.h b/sql/sql_class.h index 04095ff9acdde99ec8521228921930945fc1c725..a0971b22d3da4a7e86b2617ba1c5f0bedfe9fcc2 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -629,6 +629,8 @@ class Security_context { { return (*priv_host ? priv_host : (char *)"%"); } + + bool set_user(char *user_arg); }; @@ -770,6 +772,19 @@ class Sub_statement_state }; +/* Flags for the THD::system_thread variable */ +enum enum_thread_type +{ + NON_SYSTEM_THREAD= 0, + SYSTEM_THREAD_DELAYED_INSERT= 1, + SYSTEM_THREAD_SLAVE_IO= 2, + SYSTEM_THREAD_SLAVE_SQL= 4, + SYSTEM_THREAD_NDBCLUSTER_BINLOG= 8, + SYSTEM_THREAD_EVENT_SCHEDULER= 16, + SYSTEM_THREAD_EVENT_WORKER= 32 +}; + + /* For each client connection we create a separate thread with THD serving as a thread/connection descriptor @@ -1103,7 +1118,8 @@ class THD :public Statement, long dbug_thread_id; pthread_t real_id; uint tmp_table, global_read_lock; - uint server_status,open_options,system_thread; + uint server_status,open_options; + enum enum_thread_type system_thread; uint32 db_length; uint select_number; //number of select (used for EXPLAIN) /* variables.transaction_isolation is reset to this after each commit */ @@ -1404,11 +1420,6 @@ class THD :public Statement, #define reenable_binlog(A) (A)->options= tmp_disable_binlog__save_options;} -/* Flags for the THD::system_thread (bitmap) variable */ -#define SYSTEM_THREAD_DELAYED_INSERT 1 -#define SYSTEM_THREAD_SLAVE_IO 2 -#define SYSTEM_THREAD_SLAVE_SQL 4 -#define SYSTEM_THREAD_NDBCLUSTER_BINLOG 8 /* Used to hold information about file and file structure in exchainge diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 3d035359b6f46a0e959e5472fac0fbb9920461a6..6d5362c2554a29d9f585313245452ae8ea2037bf 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -871,7 +871,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ - (void)evex_drop_db_events(thd, db); /* QQ Ignore errors for now */ + error= Events::drop_schema_events(thd, db); start_waiting_global_read_lock(thd); /* If this database was the client's selected database, we silently change the diff --git a/sql/sql_error.h b/sql/sql_error.h index 223b50be74497e43f10868a8f93254ddc40563f4..b5cac24d89485142da88968a4a31dfa7827f6c2f 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -40,3 +40,5 @@ void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level, uint code, const char *format, ...); void mysql_reset_errors(THD *thd, bool force); bool mysqld_show_warnings(THD *thd, ulong levels_to_show); + +extern LEX_STRING warning_level_names[]; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index f0bd85367d0a899003702a22240edcfe0a3484b8..3342165a97a3041f86d0b4dda293acac24a27044 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -111,7 +111,8 @@ enum enum_sql_command { SQLCOM_SHOW_AUTHORS, SQLCOM_BINLOG_BASE64_EVENT, SQLCOM_SHOW_PLUGINS, SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, - SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, + SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, + SQLCOM_SHOW_SCHEDULER_STATUS, /* This should be the last !!! */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 33b3118de1aa2fd4853917af5670551dc9863aba..af6ac7d862acedd0ecd75df0cf362ed2ea9002ee 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2049,7 +2049,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { statistic_increment(thd->status_var.com_stat[SQLCOM_KILL], &LOCK_status); ulong id=(ulong) uint4korr(packet); - kill_one_thread(thd,id,false); + sql_kill(thd,id,false); break; } case COM_SET_OPTION: @@ -3836,14 +3836,17 @@ mysql_execute_command(THD *thd) switch (lex->sql_command) { case SQLCOM_CREATE_EVENT: - res= evex_create_event(thd, lex->et, (uint) lex->create_info.options, - &rows_affected); + res= Events::create_event(thd, lex->et, + (uint) lex->create_info.options, + &rows_affected); break; case SQLCOM_ALTER_EVENT: - res= evex_update_event(thd, lex->et, lex->spname, &rows_affected); + res= Events::update_event(thd, lex->et, lex->spname, + &rows_affected); break; case SQLCOM_DROP_EVENT: - res= evex_drop_event(thd, lex->et, lex->drop_if_exists, &rows_affected); + res= Events::drop_event(thd, lex->et, lex->drop_if_exists, + &rows_affected); default:; } DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", @@ -3881,9 +3884,16 @@ mysql_execute_command(THD *thd) my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str); goto error; } - res= evex_show_create_event(thd, lex->spname, lex->et->definer); + res= Events::show_create_event(thd, lex->spname, lex->et->definer); break; } +#ifndef DBUG_OFF + case SQLCOM_SHOW_SCHEDULER_STATUS: + { + res= Events::dump_internal_status(thd); + break; + } +#endif case SQLCOM_CREATE_FUNCTION: // UDF function { if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0)) @@ -4123,7 +4133,7 @@ mysql_execute_command(THD *thd) MYF(0)); goto error; } - kill_one_thread(thd, (ulong)it->val_int(), lex->type & ONLY_KILL_QUERY); + sql_kill(thd, (ulong)it->val_int(), lex->type & ONLY_KILL_QUERY); break; } #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -6917,22 +6927,26 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, return result; } + /* - kill on thread + kills a thread SYNOPSIS kill_one_thread() thd Thread class id Thread id + only_kill_query Should it kill the query or the connection NOTES This is written such that we have a short lock on LOCK_thread_count */ -void kill_one_thread(THD *thd, ulong id, bool only_kill_query) +uint kill_one_thread(THD *thd, ulong id, bool only_kill_query) { THD *tmp; uint error=ER_NO_SUCH_THREAD; + DBUG_ENTER("kill_one_thread"); + DBUG_PRINT("enter", ("id=%lu only_kill=%d", id, only_kill_query)); VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list I_List_iterator<THD> it(threads); while ((tmp=it++)) @@ -6958,8 +6972,25 @@ void kill_one_thread(THD *thd, ulong id, bool only_kill_query) error=ER_KILL_DENIED_ERROR; pthread_mutex_unlock(&tmp->LOCK_delete); } + DBUG_PRINT("exit", ("%d", error)); + DBUG_RETURN(error); +} + - if (!error) +/* + kills a thread and sends response + + SYNOPSIS + sql_kill() + thd Thread class + id Thread id + only_kill_query Should it kill the query or the connection +*/ + +void sql_kill(THD *thd, ulong id, bool only_kill_query) +{ + uint error; + if (!(error= kill_one_thread(thd, id, only_kill_query))) send_ok(thd); else my_error(error, MYF(0), id); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 28bedf62ea5261eb7a53e18ce6a0801b4b10fefc..f94ddc57c8064db4a2be2dca55f2498b063ae43b 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4063,8 +4063,9 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) /* type */ sch_table->field[5]->store(STRING_WITH_LEN("RECURRING"), scs); - if (event_reconstruct_interval_expression(&show_str, et.interval, - et.expression)) + if (Events::reconstruct_interval_expression(&show_str, + et.interval, + et.expression)) DBUG_RETURN(1); sch_table->field[7]->set_notnull(); @@ -4094,13 +4095,13 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) } //status - if (et.status == MYSQL_EVENT_ENABLED) + if (et.status == Event_timed::ENABLED) sch_table->field[12]->store(STRING_WITH_LEN("ENABLED"), scs); else sch_table->field[12]->store(STRING_WITH_LEN("DISABLED"), scs); //on_completion - if (et.on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + if (et.on_completion == Event_timed::ON_COMPLETION_DROP) sch_table->field[13]->store(STRING_WITH_LEN("NOT PRESERVE"), scs); else sch_table->field[13]->store(STRING_WITH_LEN("PRESERVE"), scs); @@ -4152,7 +4153,7 @@ int fill_schema_events(THD *thd, TABLE_LIST *tables, COND *cond) thd->reset_n_backup_open_tables_state(&backup); - if ((ret= evex_open_event_table(thd, TL_READ, &event_table))) + if ((ret= Events::open_event_table(thd, TL_READ, &event_table))) { sql_print_error("Table mysql.event is damaged."); ret= 1; @@ -4161,13 +4162,10 @@ int fill_schema_events(THD *thd, TABLE_LIST *tables, COND *cond) event_table->file->ha_index_init(0, 1); - /* - see others' events only if you have PROCESS_ACL !! - thd->lex->verbose is set either if SHOW FULL EVENTS or - in case of SELECT FROM I_S.EVENTS - */ - verbose= (thd->lex->verbose - && (thd->security_ctx->master_access & PROCESS_ACL)); + /* see others' events only if you have PROCESS_ACL !! */ + verbose= ((thd->lex->verbose || + thd->lex->orig_sql_command != SQLCOM_SHOW_EVENTS) && + (thd->security_ctx->master_access & PROCESS_ACL)); if (verbose && thd->security_ctx->user) { @@ -4176,12 +4174,13 @@ int fill_schema_events(THD *thd, TABLE_LIST *tables, COND *cond) } else { - event_table->field[EVEX_FIELD_DEFINER]->store(definer, strlen(definer), scs); + event_table->field[Events::FIELD_DEFINER]-> + store(definer, strlen(definer), scs); key_len= event_table->key_info->key_part[0].store_length; if (thd->lex->select_lex.db) { - event_table->field[EVEX_FIELD_DB]-> + event_table->field[Events::FIELD_DB]-> store(thd->lex->select_lex.db, strlen(thd->lex->select_lex.db), scs); key_len+= event_table->key_info->key_part[1].store_length; } diff --git a/sql/sql_string.h b/sql/sql_string.h index 0659f684afe41bcdb0156ed588a341e923198b0a..ddae6368228021e7181d731f09e2f08198b1e69e 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -24,8 +24,6 @@ #define NOT_FIXED_DEC 31 #endif -#define STRING_WITH_LEN(X) ((const char*) X), ((uint) (sizeof(X) - 1)) - class String; int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index b2b6b115f7d146d034af26e4caf0507c9a89cf8a..0ea87f3dfe438d95a9777b9f50e453caf0d18d7e 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1412,8 +1412,8 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, } if (table.triggers) { - LEX_STRING_WITH_INIT old_table_name(old_table, strlen(old_table)); - LEX_STRING_WITH_INIT new_table_name(new_table, strlen(new_table)); + LEX_STRING old_table_name= { (char *) old_table, strlen(old_table) }; + LEX_STRING new_table_name= { (char *) new_table, strlen(new_table) }; /* Since triggers should be in the same schema as their subject tables moving table with them between two schemas raises too many questions. diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index fb84d5e4459c8d97f95084df51740514eee6d2bc..4398e0039e81771931de4e308c5deb6b40134dd3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -568,6 +568,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token RTREE_SYM %token SAVEPOINT_SYM %token SCHEDULE_SYM +%token SCHEDULER_SYM %token SECOND_MICROSECOND_SYM %token SECOND_SYM %token SECURITY_SYM @@ -1401,7 +1402,7 @@ opt_ev_status: /* empty */ { $$= 0; } { LEX *lex=Lex; if (!lex->et_compile_phase) - lex->et->status= MYSQL_EVENT_ENABLED; + lex->et->status= Event_timed::ENABLED; $$= 1; } | DISABLE_SYM @@ -1409,7 +1410,7 @@ opt_ev_status: /* empty */ { $$= 0; } LEX *lex=Lex; if (!lex->et_compile_phase) - lex->et->status= MYSQL_EVENT_DISABLED; + lex->et->status= Event_timed::DISABLED; $$= 1; } ; @@ -1473,14 +1474,14 @@ ev_on_completion: { LEX *lex=Lex; if (!lex->et_compile_phase) - lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_PRESERVE; + lex->et->on_completion= Event_timed::ON_COMPLETION_PRESERVE; $$= 1; } | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM { LEX *lex=Lex; if (!lex->et_compile_phase) - lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_DROP; + lex->et->on_completion= Event_timed::ON_COMPLETION_DROP; $$= 1; } ; @@ -8067,15 +8068,24 @@ show_param: if (prepare_schema_table(YYTHD, lex, 0, SCH_TRIGGERS)) YYABORT; } - | opt_full EVENTS_SYM opt_db wild_and_where + | EVENTS_SYM opt_db wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SELECT; lex->orig_sql_command= SQLCOM_SHOW_EVENTS; - lex->select_lex.db= $3; + lex->select_lex.db= $2; if (prepare_schema_table(YYTHD, lex, 0, SCH_EVENTS)) YYABORT; } + | SCHEDULER_SYM STATUS_SYM + { +#ifndef DBUG_OFF + Lex->sql_command= SQLCOM_SHOW_SCHEDULER_STATUS; +#else + yyerror(ER(ER_SYNTAX_ERROR)); + YYABORT; +#endif + } | TABLE_SYM STATUS_SYM opt_db wild_and_where { LEX *lex= Lex; @@ -9512,6 +9522,7 @@ keyword_sp: | ROW_SYM {} | RTREE_SYM {} | SCHEDULE_SYM {} + | SCHEDULER_SYM {} | SECOND_SYM {} | SERIAL_SYM {} | SERIALIZABLE_SYM {} diff --git a/sql/structs.h b/sql/structs.h index 722378875149898c1f6d28964829f3aeee48915c..78f00f72df17237a1fb18e107beed44c576561b0 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -20,22 +20,6 @@ struct st_table; class Field; -typedef struct st_lex_string -{ - char *str; - uint length; -} LEX_STRING; - -typedef struct st_lex_string_with_init :public st_lex_string -{ - st_lex_string_with_init(const char *str_arg, uint length_arg) - { - str= (char*) str_arg; - length= length_arg; - } -} LEX_STRING_WITH_INIT; - - typedef struct st_date_time_format { uchar positions[8]; char time_separator; /* Separator between hour and minute */ diff --git a/sql/table.cc b/sql/table.cc index 52f41f03d0db61827a5eb9884bb24fec73c98d5d..d0caba7fe9e05cd9c0deb314ede9d9e0201f3232 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2401,9 +2401,7 @@ table_check_intact(TABLE *table, uint table_f_count, table running on a old server will be valid. */ field->sql_type(sql_type); - if (sql_type.length() < table_def->type.length - 1 || - strncmp(sql_type.ptr(), - table_def->type.str, + if (strncmp(sql_type.c_ptr_safe(), table_def->type.str, table_def->type.length - 1)) { sql_print_error("(%s) Expected field %s at position %d to have type " diff --git a/storage/ndb/include/mgmapi/mgmapi.h b/storage/ndb/include/mgmapi/mgmapi.h index 4585e78029a3dec748fae552a072ed5fc8be502c..5a0ffcfe2c699147e9043dc5e5d8d579e4ef61ce 100644 --- a/storage/ndb/include/mgmapi/mgmapi.h +++ b/storage/ndb/include/mgmapi/mgmapi.h @@ -849,16 +849,6 @@ extern "C" { enum ndb_mgm_event_category category, int level, struct ndb_mgm_reply* reply); - - /** - * Returns the port number where statistics information is sent - * - * @param handle NDB management handle. - * @param reply Reply message. - * @return -1 on error. - */ - int ndb_mgm_get_stat_port(NdbMgmHandle handle, - struct ndb_mgm_reply* reply); #endif /** diff --git a/storage/ndb/src/kernel/blocks/qmgr/QmgrMain.cpp b/storage/ndb/src/kernel/blocks/qmgr/QmgrMain.cpp index 8772e00f02792a44bb3f57c8bdf7d631ebfef244..4d5ac377a5a9fb6e0bc46bfaf6d2c78b76d59637 100644 --- a/storage/ndb/src/kernel/blocks/qmgr/QmgrMain.cpp +++ b/storage/ndb/src/kernel/blocks/qmgr/QmgrMain.cpp @@ -827,7 +827,7 @@ void Qmgr::execCM_REGCONF(Signal* signal) ptrCheckGuard(myNodePtr, MAX_NDB_NODES, nodeRec); ndbrequire(c_start.m_gsn == GSN_CM_REGREQ); - ndbrequire(myNodePtr.p->phase = ZSTARTING); + ndbrequire(myNodePtr.p->phase == ZSTARTING); cpdistref = cmRegConf->presidentBlockRef; cpresident = cmRegConf->presidentNodeId; diff --git a/storage/ndb/src/kernel/vm/Configuration.cpp b/storage/ndb/src/kernel/vm/Configuration.cpp index e0e414e566983b1fe408ee320c1f26a14e744f17..12badffe0e06ce7a28a747f2f6e34d246cffaed0 100644 --- a/storage/ndb/src/kernel/vm/Configuration.cpp +++ b/storage/ndb/src/kernel/vm/Configuration.cpp @@ -49,7 +49,9 @@ extern EventLogger g_eventLogger; enum ndbd_options { OPT_INITIAL = NDB_STD_OPTIONS_LAST, OPT_NODAEMON, - OPT_FOREGROUND + OPT_FOREGROUND, + OPT_NOWAIT_NODES, + OPT_INITIAL_START }; NDB_STD_OPTS_VARS; @@ -88,11 +90,11 @@ static struct my_option my_long_options[] = " (implies --nodaemon)", (gptr*) &_foreground, (gptr*) &_foreground, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, - { "nowait-nodes", NO_ARG, + { "nowait-nodes", OPT_NOWAIT_NODES, "Nodes that will not be waited for during start", (gptr*) &_nowait_nodes, (gptr*) &_nowait_nodes, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - { "initial-start", NO_ARG, + { "initial-start", OPT_INITIAL_START, "Perform initial start", (gptr*) &_initialstart, (gptr*) &_initialstart, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, diff --git a/storage/ndb/src/mgmapi/mgmapi.cpp b/storage/ndb/src/mgmapi/mgmapi.cpp index b3d2d403dcc679074684af34d73253a43af11119..6dfb48667aa63f076f684615f5cabe3d4e123324 100644 --- a/storage/ndb/src/mgmapi/mgmapi.cpp +++ b/storage/ndb/src/mgmapi/mgmapi.cpp @@ -1306,33 +1306,6 @@ ndb_mgm_listen_event(NdbMgmHandle handle, const int filter[]) return ndb_mgm_listen_event_internal(handle,filter,0); } -extern "C" -int -ndb_mgm_get_stat_port(NdbMgmHandle handle, struct ndb_mgm_reply* /*reply*/) -{ - SET_ERROR(handle, NDB_MGM_NO_ERROR, "Executing: ndb_mgm_get_stat_port"); - const ParserRow<ParserDummy> stat_reply[] = { - MGM_CMD("error", NULL, ""), - MGM_ARG("result", String, Mandatory, "Error message"), - MGM_CMD("get statport reply", NULL, ""), - MGM_ARG("tcpport", Int, Mandatory, "TCP port for statistics"), - MGM_END() - }; - CHECK_HANDLE(handle, -1); - CHECK_CONNECTED(handle, -1); - - Properties args; - const Properties *reply; - reply = ndb_mgm_call(handle, stat_reply, "get statport", &args); - CHECK_REPLY(reply, -1); - - Uint32 port; - reply->get("tcpport", &port); - - delete reply; - return port; -} - extern "C" int ndb_mgm_dump_state(NdbMgmHandle handle, int nodeId, int* _args, diff --git a/storage/ndb/src/mgmsrv/Services.cpp b/storage/ndb/src/mgmsrv/Services.cpp index d7f58d124aad95b402a6254e9b116f1428c5fa57..2731bfd422bcc8b8d59c95df3b70e5cfdb7f0f31 100644 --- a/storage/ndb/src/mgmsrv/Services.cpp +++ b/storage/ndb/src/mgmsrv/Services.cpp @@ -121,8 +121,6 @@ static const unsigned int MAX_WRITE_TIMEOUT = 100 ; const ParserRow<MgmApiSession> commands[] = { - MGM_CMD("get statport", &MgmApiSession::getStatPort, ""), - MGM_CMD("get config", &MgmApiSession::getConfig, ""), MGM_ARG("version", Int, Mandatory, "Configuration version number"), MGM_ARG("node", Int, Optional, "Node ID"), @@ -649,15 +647,6 @@ MgmApiSession::getConfig_common(Parser_t::Context &, return; } -void -MgmApiSession::getStatPort(Parser_t::Context &, - const class Properties &) { - - m_output->println("get statport reply"); - m_output->println("tcpport: %d", 0); - m_output->println(""); -} - void MgmApiSession::insertError(Parser<MgmApiSession>::Context &, Properties const &args) { diff --git a/storage/ndb/src/mgmsrv/Services.hpp b/storage/ndb/src/mgmsrv/Services.hpp index 975202b96df0ae28c166044e8fca28474d985ab2..abe0233cb332fdcee6584560f5f7c57e73fab8de 100644 --- a/storage/ndb/src/mgmsrv/Services.hpp +++ b/storage/ndb/src/mgmsrv/Services.hpp @@ -53,7 +53,6 @@ public: virtual ~MgmApiSession(); void runSession(); - void getStatPort(Parser_t::Context &ctx, const class Properties &args); void getConfig(Parser_t::Context &ctx, const class Properties &args); #ifdef MGM_GET_CONFIG_BACKWARDS_COMPAT void getConfig_old(Parser_t::Context &ctx); diff --git a/support-files/mysql.server.sh b/support-files/mysql.server.sh index bf17375c0ebaea5df3fdeb03dfae5a20ae229f29..bd16297f8e77ce7ca8974bbe4a2aaba34f583fce 100644 --- a/support-files/mysql.server.sh +++ b/support-files/mysql.server.sh @@ -265,7 +265,10 @@ case "$mode" in then # Give extra arguments to mysqld with the my.cnf file. This script may # be overwritten at next upgrade. - $manager --user=$user --pid-file=$pid_file >/dev/null 2>&1 & + "$manager" \ + --mysqld-safe-compatible \ + --user="$user" \ + --pid-file="$pid_file" >/dev/null 2>&1 & wait_for_pid created # Make lock for RedHat / SuSE