/* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   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, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "lgman.hpp"
#include "diskpage.hpp"
#include <signaldata/FsRef.hpp>
#include <signaldata/FsConf.hpp>
#include <signaldata/FsOpenReq.hpp>
#include <signaldata/FsCloseReq.hpp>
#include <signaldata/CreateFilegroupImpl.hpp>
#include <signaldata/DropFilegroupImpl.hpp>
#include <signaldata/FsReadWriteReq.hpp>
#include <signaldata/LCP.hpp>
#include <signaldata/SumaImpl.hpp>
#include <signaldata/LgmanContinueB.hpp>
#include <signaldata/GetTabInfo.hpp>
#include <signaldata/NodeFailRep.hpp>
#include <signaldata/DbinfoScan.hpp>
#include <signaldata/CallbackSignal.hpp>
#include <signaldata/DumpStateOrd.hpp>
#include "dbtup/Dbtup.hpp"

#include <EventLogger.hpp>
extern EventLogger * g_eventLogger;

#include <record_types.hpp>

#define JAM_FILE_ID 441


/**
 *
 * IMPORTANT NOTE:
 * ---------------
 * Although the code in many aspects is prepared to handle multiple logfile
 * groups, it can at the moment not handle any more than one logfile group.
 * There is lacking functionality for multiple logfile groups e.g. when
 * executing the UNDO log records.
 *
 * ---<a>-----<b>-----<c>-----<d>---> (time)
 *
 * <a> = start of lcp 1
 * <b> = stop of lcp 1
 * <c> = start of lcp 2
 * <d> = stop of lcp 2
 *
 * If ndb crashes before <d>
 *   the entire undo log from crash point until <a> has to be applied
 *
 * at <d> the undo log can be cut til <c> 
 *
 * LSN = Log Sequence Number, an increasing number which is the id of each
 * UNDO log record. Each page is marked with the last LSN it has been
 * updated with. Thus it is easy to check if a log record should be applied
 * to a page, it should be applied if pageLSN > logLSN. After applying this
 * log record the pageLSN should be set to logLSN.
 *
 * UNDO log entry layout
 * ---------------------
 *
 * There are two types of UNDO log entry types:
 *
 * Type 1 variant:
 * --------------------------------------
 * | data1 | data2 ... | dataN | header |
 * --------------------------------------
 * Type 2 variant:
 * ---------------------------------------------------------------------
 * | next_LSN_low | next_LSN_high | data1 | data2 ... | dataN | header |
 * ---------------------------------------------------------------------
 *
 * Header has 3 fields:
 * Bit 0-15: Length of UNDO log entry
 * Bit 16-30: Type of UNDO log entry
 * Bit 31: Set to to 1 for Type 1 of UNDO log entry and 0 for Type 2
 * (Bit 31 is called UNDO_NEXT_LSN when set to 1)
 *
 * In type 1 the previous LSN record have the LSN of the current record
 * minus one. So we save the 2 words used here. This is the only type
 * used in production software since we currently only support one
 * logfile group.
 *
 * Type 2 is used when we have introduce support for more than one logfile
 * group. Then the previous LSN is not necessarily simply minus one. So
 * here we need to have a back pointer of the LSN value of the previous
 * record.
 *
 * In Type 2 record the length of the UNDO log record doesn't include
 * the extra LSN words. These words are implied by the bit 31 in the
 * header not being set.
 *
 * The current types of UNDO log records are:
 * TUP_ALLOC (UNDO allocate in a page)
 * TUP_UPDATE (UNDO update in a page)
 * TUP_FREE (UNDO free in a page)
 * TUP_CREATE (UNDO allocate a page)
 * TUP_DROP (UNDO deallocate a page)
 * TUP_ALLOC_EXTENT (UNDO allocate an extent of pages)
 * TUP_FREE_EXTENT (UNDO deallocate of an extent of pages)
 *
 * UNDO Page layout
 * ----------------
 * -------------------------------------
 * |   Page Header                     |
 * |------------------------------------
 * |   UNDO log record                 |
 * |------------------------------------
 * |   UNDO log record                 |
 * |------------------------------------
 * | ........... more UNDO log records |
 * |------------------------------------
 *
 * Page Header Layout
 * ------------------
 *
 * -------------------------------------
 * |   LSN High Word                   |
 * -------------------------------------
 * |   LSN Low Word                    |
 * -------------------------------------
 * |   Page type                       |
 * -------------------------------------
 * |   Page position                   |
 * -------------------------------------
 *
 * alloc_log_space, get_log_buffer, add_entry, free_log_space
 * ----------------------------------------------------------
 * LGMAN receives commands to log an UNDO entry from DBTUP.
 * During preparation of a transaction we allocate space in the UNDO log
 * file through the call alloc_log_space in the Logfile_client.
 * 
 * At a later time when we are preparing to actually write this entry into
 * the UNDO log we need a two-step approach, we first ensure there is space
 * in the log buffer for the UNDO log and then finally we send the UNDO log
 * entry.
 * 
 * So writing an entry into the UNDO log normally requires 3 calls through the
 * Logfile_client. First at prepare time we allocate space in UNDO log file,
 * Then at commit time we first allocate space into the UNDO log buffer
 * whereafter we finally send the UNDO log entry.
 *
 * All of this interaction happens between DBTUP in any of the LDM threads
 * and LGMAN which normally executes in the main thread. LGMAN can however
 * execute in the LDM threads through the concept of a Logfile_client which
 * uses a mutex to ensure that access to LGMAN is serialised.
 *
 * During allocation of space in the UNDO log buffer we can discover that no
 * space is available, in this case LGMAN will insert the request into a queue
 * and service it once space is available. This queueing service is handled by
 * the CONTINUEB process using PROCESS_LOG_BUFFER_WAITERS. Since this happens
 * as part of a commit we must continue waiting until we get space, there is
 * no option to quit waiting here since that would break the commit protocol.
 * This means that large commits can take quite long time.
 *
 * TODO: We should ensure that TC is informed of the delay to ensure that TC
 * knows that we have a valid reason for waiting. This makes it easier for
 * TC to track progress on the transaction. It also makes it easier to
 * implement human supportable tracking through ndbinfo tables and so forth.
 *
 * In case we abort the transaction we give back the allocated space in the
 * transaction through calls to free_log_space using the Logfile_client.
 *
 * sync_lsn
 * --------
 * LGMAN participates as the Log manager in the WAL protocol. The WAL
 * (Write Ahead Log) protocol uses the following algorithm. Each time a page
 * is changed in PGMAN an UNDO log entry has been sent to the UNDO log first.
 * This UNDO log entry receives an LSN. The page in PGMAN is updated
 * with the latest LSN. Before writing a page in PGMAN to disk we must
 * ensure that all UNDO log entries up until the page LSN have been written
 * to disk.
 *
 * This is performed using the call sync_lsn using the Logfile_client. Most
 * commonly this occurs from PGMAN before writing a page. It can also occur
 * in relation to LCPs where it is used at end of an LCP to ensure that an LCP
 * can be fully restored (which requires that all UNDO log entries generated
 * as part of LCP is flushed to the disk.
 *
 * Signal END_LCP_REQ
 * ------------------
 * As mentioned in the above section we need to call sync_lsn at end of an LCP.
 * We are informed of LCP end through the signal END_LCP_REQ and will respond
 * with END_LCP_CONF when all logfile groups have completed their sync_lsn
 * calls.
 *
 * exec_lcp_frag_ord
 * -----------------
 * exec_lcp_frag_ord is called from DBLQH each time a new LCP is discovered
 * in DBLQH. So effectively this is called at the start of an LCP.
 *
 * In order to be able to write the UNDO log continously we need to cut the
 * log tail every now and then. We enable cutting of the log tail for both
 * REDO and UNDO logs by running checkpoints. We call our checkpoints, LCPs.
 * At start of a new LCP we can cut away the log tail back until we reach the
 * the start of the previous LCP. As explained above we could cut this already
 * at end of the LCP, but we have no knowledge in LGMAN about when a
 * distributed LCP is completed, so we cut it away instead at the next start
 * of an LCP.
 *
 * So in short LGMAN receives log entries from DBTUP, before PGMAN can write
 * any page it ensures that all log entries of the page have been flushed to
 * disk. To ensure that the log doesn't grow to infinity, we use LCPs to
 * cut the log tail every now and then.
 *
 * Signal START_RECREQ
 * -------------------
 * At restart (system restart or node restart) we need to execute the UNDO log
 * back to the start of an LCP. Given that the UNDO log is used for disk data
 * we don't need to restore the disk pages at first, we simply execute the UNDO
 * log records from the end of the UNDO log until we reach the starting LSN
 * of the LCP. There is an UNDO log entry indicating this.
 *
 * This is implemented in a number of steps where we start to find the head and
 * tail of the UNDO log, then we start reading the UNDO log pages in one
 * parallel process which runs until it runs out of free pages to read more.
 * As soon as new free pages becomes available (through the UNDO execution
 * process) the read undo page process can continue. It sends CONTINUEB
 * signals to itself to continue the process until it completes. When no
 * free pages are around it sends CONTINUEB with a 100msec delay.
 *
 * The UNDO execution process happens in parallel to the read undo pages.
 * As soon as there are UNDO pages to execute it will execute those through
 * use of a DBTUP client. In many cases this will actually send a signal
 * to the proper LDM thread to apply the UNDO log.
 * 
 * TODO:
 *   There is a fairly apparent possibility to improve parallelism in executing
 *   the UNDO log by having multiple outstanding UNDO log records. As long as
 *   they are not directed towards the same page in PGMAN it is safe to send
 *   another UNDO log record. So even with just one instance of LGMAN it is
 *   possible to quite easily keep a number of LDM threads busy applying UNDO
 *   log records.
 *
 * Complications in LGMAN
 * ----------------------
 * There are some practical problems related to implementing the above. We are
 * implementing the above on top of a file system. The only guarantees we get
 * from the file system (and hardly even that) is that all writes issued before
 * an fsync call of a specific file is safe on disk when the fsync call
 * completes. For files in LGMAN all FSWRITEREQ calls are done with fsync
 * integrated as the file is opened with the sync flag set.
 * 
 * This gives us at least the following problems to solve in LGMAN:
 * 1) LGMAN logfile groups can consist of multiple files, we need to ensure
 * that sync_lsn means that before we report any LSN's as written in a new
 * file that all LSN entries of the previous file have been sync:ed.
 * We solve this by special file change logic where we don't report any
 * LSNs as completed in a new file until the previous file have had all its
 * writes completed.
 *
 * 2) We can have a set of pages in LGMAN in unknown state after the last
 * sync in a file. This could be a mix of pages not written at all, pages
 * fully written and even pages that are half-written. It is possible to
 * start executing UNDO log entries all the way from the first written
 * page in the log. If we do must however handle two things, we must ignore
 * all records on unwritten log pages and also on half-written log pages.
 *
 * TODO: For us to discover half-written pages we need a checksum on each
 * page which is currently not supported.
 *
 * We must also ensure that we don't allow any unwritten and half-written
 * log pages once we found the first UNDO log entry that actually needed to
 * be applied. The reason is that when we have found such an entry we know
 * that a sync_lsn up until this log entry was performed since the page
 * had been written only after the sync_lsn returned with success.
 *
 * 3) Finding the last written log pages in this lap is not trivial. At first
 * we can insert new log files at any time. We handle this by performing a
 * sort of the log files at restart based on the first LSN they have recorded
 * in the file. To simplify restart logic we always start by writing only the
 * first page in the file before writing anything more in the file. This means
 * that the file sort only need to look at the first page of a file to be able
 * to sort it in the list of files. Second we ensure that at file change we
 * write the last in the old file and the first page in the new file. This
 * simplifies the restart logic.
 *
 * The above complication is actually only efficiently solvable if we also have
 * a finite limit on how much UNDO log pages we can write before we synch the
 * file. If we synch at least once every e.g. 1 MByte then we're certain that
 * we need not search any further than 1 MByte from the first unwritten page.
 * However if no such limit exists, than we have no way of knowing when to stop
 * searching since e.g. the Linux file system could save as much writes in the
 * file system cache as there is memory in the system (which could be quite
 * substantial amounts in modern machines).
 *
 * To handle this we put a cap on the amount written to the UNDO log per
 * FSWRITEREQ (that includes both an OS file write and an fsync for LGMAN
 * files). We also ensure that at restart we continue scanning ahead
 * as much as the size of this cap, only when we have found a segment of
 * unwritten pages this long after the first unwritten page will we stop
 * the search and point to the page before the first unwritten page as the
 * last written page in the UNDO log.
 *
 * We can also have a set of unwritten/half-written pages in the previous
 * file, but these require no special handling other than skipping them
 * when applying the UNDO log which is part of the normal check for each
 * UNDO page before applying.
 *
 * Unwritten pages are treated in exactly the same fashion as half-written
 * pages (we discover half-written pages through the page checksum being
 * wrong).
 *
 * 4) After we found the last written log page in the UNDO log page we will
 * have to start applying the UNDO log records backwards from this position.
 * If we reach an unwritten/half-written page while proceeding backwards,
 * we need to ignore this page. This is safe since it is part of a set of
 * UNDO log pages which didn't have its synch of the file completed. Thus
 * none of the LSNs can have been applied to any page yet according to the
 * WAL protocol that we follow for the UNDO log.
 *
 * As an extra security effort we keep track of the first UNDO log entry
 * that is actually applied on a page, after we found such a record we cannot
 * find any more unwritten/half-written pages while progressing backwards. If
 * we found an unwritten page/half-written page after this, then there is a
 * real issue and the log is corrupted. We check this condition.
 *
 * Implementation details
 * ----------------------
 *
 * 1) Maintaining free log file space
 * ----------------------------------
 * The amount of free space for an UNDO log file group is maintained by the
 * variable m_free_file_words on the struct Logfile_group.
 *
 * It gets its initial value from either creation of a new log file group
 * in which case this is calculated in create_file_commit. Otherwise it is
 * calculated after executing the UNDO log in stop_run_undo_log.
 *
 * The free space is decremented each time we call alloc_log_space, at creation
 * of special LCP UNDO log records in execEND_LCP_CONF, and exec_lcp_frag_ord.
 * It is also decremented due to changing to a new page where we add a number
 * of NOOP entries to fill all pages, this happens in get_log_buffer when
 * page is full and also when we sync pages in flush_log and force_log_sync.
 *
 * The free space is incremented each time we call free_log_space, in
 * add_entry if we discover that we're not changing file group since last
 * LSN. Most importantly it is incremented in cut_log_tail where we add
 * free space for each page that we move ahead the tail.
 * 
 * So in situations where we get error 1501 (out of UNDO log) we can only
 * get back to a normal situation after completing an LCP and starting a
 * new again immediately.
 *
 * The error 1501 is immediately reported when the log file is full. Log being
 * full here means that all the free space of the file have already been
 * allocated by various transactions. At that point we will report back error
 * 1501 (Out of UNDO log space) to the application. This error will mean that
 * any disk data transactions will be blocked for an extended period of time
 * until a new LCP can free up space again. This is obviously a highly
 * undesirable state and should be avoided by ensuring that the UNDO log
 * is sufficiently big and also by ensuring that LCP write speed is high
 * enough to create new LCPs quicker. This variable is not needed during
 * restart, so it's only used during normal operation.
 *
 * 2) Maintaining free log buffer space
 * ------------------------------------
 * The amount of free UNDO log buffer space is maintained by the variable
 * m_free_buffer_words in the struct Logfile_group.
 *
 * This variable is maintained both at restart and during normal operation.
 * It gets the initial value from the size of the UNDO log buffer which is
 * specified when creating the log file group.
 *
 * During restart we decrement the free space when we allocate a page as
 * initial page, when we allocate to read the UNDO log. We increase the
 * free space when we have completed reading the UNDO log page during
 * UNDO log execution.
 *
 * During normal operation we check the amount of free space in the call
 * get_log_buffer in Logfile_client. If there isn't enough free space the
 * caller must wait for a callback when receiving the return value 1.
 * When a wait is started we check that we have CONTINUEB messages sent
 * with the id PROCESS_LOG_BUFFER_WAITERS, this CONTINUEB calls
 * regularly process_log_buffer_waiters until there are no more waiters.
 *
 * When sending a callback we allocate a list entry that contains the
 * request information needed to send the callback. This list is using
 * memory allocated from the GlobalSharedMemory. This is memory that
 * can be used to some extent by other allocation regions at
 * overallocation. It is used for all data structures describing log buffer
 * requests, all data structures describing page requests when needing to read
 * from disk and finally also for the UNDO log buffer itself.
 *
 * If we have set the config parameter InitialLogFileGroup then the size of the
 * Undo buffer in this specification will be added to the size of
 * GlobalSharedMemory. So in this case the request lists will get the entire
 * GlobalSharedMemory except for its use for overallocation. It's likely over
 * time that more and more resources will be sharing this GlobalSharedMemory,
 * care then needs to be taken that we have sufficient memory to handle these
 * lists. Currently we crash when we run out of this resource.
 *
 * TODO: Ensure that we handle these kind of resource problems in an
 * appropriate manner.
 *
 * In normal operations we decrement the free space when calling flush_log
 * to account for NOOP space, same for force_log_sync, we also decrement
 * it when calling the internal get_log_buffer which is called from
 * add_entry and also other places where we create special UNDO log records.
 * Finally we add back space to the UNDO log buffer when FSWRITECONF returns
 * with the log pages being written and the pages are free to be used for
 * other log pages.
 *
 * Maintaining LSN numbers
 * -----------------------
 * We have a number of LSN variables that are used to maintain the LSNs and
 * their current state:
 * 1) m_next_lsn
 * This is the next LSN that we will write into the UNDO log.
 * This variable exists in two instances. It exists for each logfile group
 * where it represents the last LSN written in this logfile group. It also
 * exists as a global variable for the LGMAN block.
 * Actually LGMAN only supports one log file group, so these numbers will
 * always be equal.
 * 2) m_last_sync_req_lsn
 * This is the highest LSN which is currently in the process of being
 * written to the UNDO log file. The file write of this LSN isn't completed
 * yet.
 * 3) m_last_synced_lsn
 * This is the highest LSN which have been written safely to disk.
 * 4) m_max_sync_req_lsn
 * This is the highest LSN which have been requested for sync to disk by a call
 * to sync_lsn.
 *
 * The condition:
 * m_next_lsn > m_max_sync_req_lsn >= m_last_sync_req_lsn >= m_last_synced_lsn
 * will always be true.
 *
 * Performing a restart (system restart or node restart)
 * -----------------------------------------------------
 * At restart we get started on recovery by receiving the START_RECREQ signal.
 * This signal contains the LCP id that we will restore. Disk data gets its
 * data from only one set of pages since the base information is on disk. The
 * information in LGMAN is used to play the tape backwards figuratively
 * speakin (UNDO) until we reach an UNDO log record that represents this LCP.
 * When we reach this log record we have ensured that all data in the disk
 * data parts are as they were at the time of the LCP. Before completing the
 * UNDO execution we also ensure that all pages in PGMAN are flushed to disk
 * to ensure that the UNDO log we have executed is no longer used. Once we
 * have flushed the PGMAN pages to disk we are done and we can write a new LCP
 * record for the same LCP id. Once this record reaches the disk we will never
 * need to replay the UNDO log already executed. There is no specific write of
 * this record, it will be written as soon as some write of the UNDO log is
 * performed. However if it isn't written before the next crash it isn't a
 * problem since we will simply run through a lot of UNDO logs that have
 * already been applied.
 *
 * The place in the code where we flush the pages in PGMAN is marked by:
 * START_FLUSH_PGMAN_CACHE.
 * The place where we return from flushing PGMAN cache is marked by:
 * END_FLUSH_PGMAN_CACHE.
 *
 * At this point we're done with our part of the restart and we're ready
 * to start generating new UNDO log records which will also happen as
 * part of the processing of REDO log records.
 *
 * At restart we need to discover the following things:
 *
 * 1) We need to sort the UNDO log files in the correct order.
 * This is actually the first step in the restart processing.
 *
 * 2) We need to find the end of the UNDO log.
 * Marked by END_OF_UNDO_LOG_FOUND in code below. We reach this code once for
 * each logfile group defined in the cluster. In most cases we only use one
 * logfile group per cluster which can even be defined in the config file.
 *
 * 3) We need to set up the new head and tail of the UNDO log.
 * The new head is set up to the first non-written UNDO log page and the tail
 * is set up to be the page preceding this (could be in previous file). As we
 * proceed to execute the UNDO log records the tail position will be moved
 * back to its final position.
 *
 * 4) We need to set up the UNDO log such that it starts adding the new
 *    log records at the new end of the UNDO log.
 * 
 * 5) We need to find the next LSN to use for the log records we start to
 *    produce after the restart.
 *
 * 3), 4) and 5) happens when finding the end of the UNDO log.
 *
 * 6) We need to initialise the free space in the buffer and in the files.
 *
 * The most problematic part here is 2).
 * The problem is that we can be in the middle of a file change when we
 * crashed, we can also have large writes to the file system that are in
 * a half-written state. This means that e.g. if the last write to the
 * file system was a write of 128 pages, all of those pages can be in one
 * of 3 states. They can be written, they can be unwritten and they can be
 * half-written. We detect first if they are half-written by using a checksum
 * on the log page. If the checksum was ok we look at the starting LSN to
 * detect whether it was written or not written.
 *
 * Finding the end of the UNDO log means finding the very last of the pages
 * that have been written. If we don't find this log page, then we can end
 * up in a situation where the written pages ahead of the end we found, are
 * put together with new log entries generated after the restart. We could
 * have very complicated bugs in that case which would be more or less
 * impossible to ever detect and find.
 *
 * Also 1) is somewhat tied into 2) and we want this to fairly simple. The
 * sorting happens by reading page 1. To make this searching easier and
 * avoiding that we have to look at more than just page 1, we use a special
 * order of writing at file change.
 * 
 * 1) Write the last pages in file X.
 * 2) Next write page 1 in file X+1.
 * 3) Continue writing as usual in file X+1 as soon as 2) is done.
 * 4) sync_lsn for new file cannot move synced_lsn forward until 1) is done.
 * (Write here means both filesystem write and the fsync to ensure the write
 * saved on disk, or using O_DIRECT flag in file system).
 * The UNDO log file is fixed size after creating it since we start by
 * writing the entire file to ensure that it is allocated on disk. In this
 * case O_DIRECT means that writes behaves as write + fsynch when O_DIRECT
 * flag is set. This is explained in detail in:
 * https://lwn.net/Articles/348739.
 *
 * A special problem to handle here is if we get a half-written page 1. In
 * this case we know that the page must be the next file after the file which
 * has the most recent change. Page 1 will get a correctly written soon after
 * completion of restart since it will soon be the next file to use. We need
 * to crash however if we discover more than one half-written page as page 1.
 * This should never happen unless we have a corrupted file system. This is
 * the case since we will never proceed writing in a file until we have
 * completed writing of page 1, so we can't reach the next file to write
 * before we have completed writing of page 1 in the previous file.
 *
 * Additionally to avoid that we have to search extensive distances for the end
 * of the UNDO log we will set a limit of file writes to 16 MByte as a constant.
 * This means that we need to search 128 pages forward in the file before we
 * can be sure that we have found the end of the UNDO log. We never need to
 * bother searching beyond end of file into the next file due to the file
 * change protocol we are using.
 *
 * While executing the UNDO log backwards we need to look out for unwritten
 * pages and half-written pages. No UNDO records from these pages should be
 * applied. Also we need to get a flag from PGMAN when we reach the first
 * UNDO log record which is actually applied. This UNDO log record represents
 * a point in the UNDO log where we are sure that the LSN must have been
 * sync:ed to this point since the page had been forced to disk which only
 * happens after the log have been sync:ed to disk according to the WAL
 * protocol. So when continuing backwards in the UNDO log file we should not
 * encounter any more unwritten pages or half-written pages. Encountering such
 * a page is an indication of a corrupt file system and thus we cannot proceed
 * with the restart.
 *
 * When looking for end of UNDO log we use the following state variables what
 * we are currently doing:
 *
 * FS_SEARCHING : Binary search
 * FS_SEARCHING_END : Forward linear search bounded by 16MB 'rule'
 * FS_SEARCHING_FINAL_READ : Search completed, re-reading 'final' page before
 *                           applying UNDO log.
 *
 * LSNs are only recorded per page and this represents the last LSN written in
 * this page. So the page_lsn is the highest LSN represented in the file, then
 * the UNDO log before the last record has its LSN implied unless the UNDO log
 * records is a special log record that also stores the LSN.
 */

#define DEBUG_UNDO_EXECUTION 0
#define DEBUG_SEARCH_LOG_HEAD 0

#define FREE_BUFFER_MARGIN (2 * File_formats::UNDO_PAGE_WORDS)

Lgman::Lgman(Block_context & ctx) :
  SimulatedBlock(LGMAN, ctx),
  m_tup(0),
  m_logfile_group_list(m_logfile_group_pool),
  m_logfile_group_hash(m_logfile_group_pool),
  m_client_mutex("lgman-client", 2, true)
{
  BLOCK_CONSTRUCTOR(Lgman);
  
  // Add received signals
  addRecSignal(GSN_STTOR, &Lgman::execSTTOR);
  addRecSignal(GSN_READ_CONFIG_REQ, &Lgman::execREAD_CONFIG_REQ);
  addRecSignal(GSN_DUMP_STATE_ORD, &Lgman::execDUMP_STATE_ORD);
  addRecSignal(GSN_DBINFO_SCANREQ, &Lgman::execDBINFO_SCANREQ);
  addRecSignal(GSN_CONTINUEB, &Lgman::execCONTINUEB);
  addRecSignal(GSN_NODE_FAILREP, &Lgman::execNODE_FAILREP);

  addRecSignal(GSN_CREATE_FILE_IMPL_REQ, &Lgman::execCREATE_FILE_IMPL_REQ);
  addRecSignal(GSN_CREATE_FILEGROUP_IMPL_REQ,
               &Lgman::execCREATE_FILEGROUP_IMPL_REQ);

  addRecSignal(GSN_DROP_FILE_IMPL_REQ, &Lgman::execDROP_FILE_IMPL_REQ);
  addRecSignal(GSN_DROP_FILEGROUP_IMPL_REQ,
               &Lgman::execDROP_FILEGROUP_IMPL_REQ);

  addRecSignal(GSN_FSWRITEREQ, &Lgman::execFSWRITEREQ);
  addRecSignal(GSN_FSWRITEREF, &Lgman::execFSWRITEREF, true);
  addRecSignal(GSN_FSWRITECONF, &Lgman::execFSWRITECONF);

  addRecSignal(GSN_FSOPENREF, &Lgman::execFSOPENREF, true);
  addRecSignal(GSN_FSOPENCONF, &Lgman::execFSOPENCONF);

  addRecSignal(GSN_FSCLOSECONF, &Lgman::execFSCLOSECONF);
  
  addRecSignal(GSN_FSREADREF, &Lgman::execFSREADREF, true);
  addRecSignal(GSN_FSREADCONF, &Lgman::execFSREADCONF);

  addRecSignal(GSN_END_LCP_REQ, &Lgman::execEND_LCP_REQ);
  addRecSignal(GSN_SUB_GCP_COMPLETE_REP, &Lgman::execSUB_GCP_COMPLETE_REP);
  addRecSignal(GSN_START_RECREQ, &Lgman::execSTART_RECREQ);
  
  addRecSignal(GSN_END_LCP_CONF, &Lgman::execEND_LCP_CONF);

  addRecSignal(GSN_GET_TABINFOREQ, &Lgman::execGET_TABINFOREQ);
  addRecSignal(GSN_CALLBACK_ACK, &Lgman::execCALLBACK_ACK);

  m_next_lsn = 1;
  m_logfile_group_hash.setSize(10);

  if (isNdbMtLqh()) {
    jam();
    int ret = m_client_mutex.create();
    ndbrequire(ret == 0);
  }

  {
    CallbackEntry& ce = m_callbackEntry[THE_NULL_CALLBACK];
    ce.m_function = TheNULLCallback.m_callbackFunction;
    ce.m_flags = 0;
  }
  {
    CallbackEntry& ce = m_callbackEntry[ENDLCP_CALLBACK];
    ce.m_function = safe_cast(&Lgman::endlcp_callback);
    ce.m_flags = 0;
  }
  {
    CallbackTable& ct = m_callbackTable;
    ct.m_count = COUNT_CALLBACKS;
    ct.m_entry = m_callbackEntry;
    m_callbackTableAddr = &ct;
  }
}
  
Lgman::~Lgman()
{
  if (isNdbMtLqh()) {
    (void)m_client_mutex.destroy();
  }
}

void
Lgman::client_lock(BlockNumber block, int line)
{
  if (isNdbMtLqh()) {
#ifdef VM_TRACE
    Uint32 bno = blockToMain(block);
    Uint32 ino = blockToInstance(block);
#endif
    D("try lock " << bno << "/" << ino << V(line));
    int ret = m_client_mutex.lock();
    ndbrequire(ret == 0);
    D("got lock " << bno << "/" << ino << V(line));
  }
}

void
Lgman::client_unlock(BlockNumber block, int line)
{
  if (isNdbMtLqh()) {
#ifdef VM_TRACE
    Uint32 bno = blockToMain(block);
    Uint32 ino = blockToInstance(block);
#endif
    D("unlock " << bno << "/" << ino << V(line));
    int ret = m_client_mutex.unlock();
    ndbrequire(ret == 0);
  }
}

BLOCK_FUNCTIONS(Lgman)

void 
Lgman::execREAD_CONFIG_REQ(Signal* signal)
{
  jamEntry();

  const ReadConfigReq * req = (ReadConfigReq*)signal->getDataPtr();

  Uint32 ref = req->senderRef;
  Uint32 senderData = req->senderData;

  const ndb_mgm_configuration_iterator * p = 
    m_ctx.m_config.getOwnConfigIterator();
  ndbrequire(p != 0);

  Pool_context pc;
  pc.m_block = this;
  m_log_waiter_pool.wo_pool_init(RT_LGMAN_LOG_WAITER, pc);
  m_file_pool.init(RT_LGMAN_FILE, pc);
  m_logfile_group_pool.init(RT_LGMAN_FILEGROUP, pc);
  // 10 -> 150M
  m_data_buffer_pool.setSize(40);

  ReadConfigConf * conf = (ReadConfigConf*)signal->getDataPtrSend();
  conf->senderRef = reference();
  conf->senderData = senderData;
  sendSignal(ref, GSN_READ_CONFIG_CONF, signal, 
	     ReadConfigConf::SignalLength, JBB);
}

void
Lgman::execSTTOR(Signal* signal) 
{
  jamEntry();                            
  Uint32 startPhase = signal->theData[1];
  switch (startPhase) {
  case 1:
    m_tup = globalData.getBlock(DBTUP);
    ndbrequire(m_tup != 0);
    break;
  }
  sendSTTORRY(signal);
}

void
Lgman::sendSTTORRY(Signal* signal)
{
  signal->theData[0] = 0;
  signal->theData[3] = 1;
  signal->theData[4] = 2;
  signal->theData[5] = 3;
  signal->theData[6] = 4;
  signal->theData[7] = 5;
  signal->theData[8] = 6;
  signal->theData[9] = 255; // No more start phases from missra
  sendSignal(NDBCNTR_REF, GSN_STTORRY, signal, 10, JBB);
}

void
Lgman::execCONTINUEB(Signal* signal){
  jamEntry();

  Uint32 type= signal->theData[0];
  Uint32 ptrI = signal->theData[1];
  client_lock(number(), __LINE__);
  switch(type){
  case LgmanContinueB::FILTER_LOG:
    jam();
    break;
  case LgmanContinueB::CUT_LOG_TAIL:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    cut_log_tail(signal, ptr);
    break;
  }
  case LgmanContinueB::FLUSH_LOG:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    flush_log(signal, ptr, signal->theData[2]);
    break;
  }
  case LgmanContinueB::PROCESS_LOG_BUFFER_WAITERS:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    process_log_buffer_waiters(signal, ptr);
    break;
  }
  case LgmanContinueB::FIND_LOG_HEAD:
    jam();
    Ptr<Logfile_group> ptr;
    if(ptrI != RNIL)
    {
      jam();
      m_logfile_group_pool.getPtr(ptr, ptrI);
      find_log_head(signal, ptr);
    }
    else
    {
      jam();
      init_run_undo_log(signal);
    }
    break;
  case LgmanContinueB::EXECUTE_UNDO_RECORD:
    jam();
    {
      Ptr<Logfile_group> ptr;
      m_logfile_group_list.first(ptr);
      if (signal->theData[1] == 1 && !ptr.p->m_applied)
      {
        /**
         * The variable m_applied is set the first UNDO log record which is
         * applied, we signal if an UNDO log record was applied in the
         * CONTINUEB signal to execute the next UNDO log record.
         */
        jam();
        ptr.p->m_applied = true;
      }
    }
    execute_undo_record(signal);
    break;
  case LgmanContinueB::STOP_UNDO_LOG:
    jam();
    stop_run_undo_log(signal);
    break;
  case LgmanContinueB::READ_UNDO_LOG:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    read_undo_log(signal, ptr);
    break;
  }
  case LgmanContinueB::PROCESS_LOG_SYNC_WAITERS:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    process_log_sync_waiters(signal, ptr);
    break;
  }
  case LgmanContinueB::FORCE_LOG_SYNC:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    force_log_sync(signal, ptr, signal->theData[2], signal->theData[3]);
    break;
  }
  case LgmanContinueB::DROP_FILEGROUP:
  {
    jam();
    Ptr<Logfile_group> ptr;
    m_logfile_group_pool.getPtr(ptr, ptrI);
    if ((ptr.p->m_state & Logfile_group::LG_THREAD_MASK) ||
        ptr.p->m_outstanding_fs > 0)
    {
      jam();
      sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 100, 
			  signal->length());
      break;
    }
    Uint32 ref = signal->theData[2];
    Uint32 data = signal->theData[3];
    drop_filegroup_drop_files(signal, ptr, ref, data);
    break;
  }
  }
  client_unlock(number(), __LINE__);
}

void
Lgman::execNODE_FAILREP(Signal* signal)
{
  jamEntry();
  const NodeFailRep * rep = (NodeFailRep*)signal->getDataPtr();
  NdbNodeBitmask failed; 
  failed.assign(NdbNodeBitmask::Size, rep->theNodes);

  /* Block level cleanup */
  for(unsigned i = 1; i < MAX_NDB_NODES; i++) {
    jam();
    if(failed.get(i)) {
      jam();
      Uint32 elementsCleaned = simBlockNodeFailure(signal, i); // No callback
      ndbassert(elementsCleaned == 0); // No distributed fragmented signals
      (void) elementsCleaned; // Remove compiler warning
    }//if
  }//for
}

void
Lgman::execDUMP_STATE_ORD(Signal* signal){
  jamNoBlock();  /* Due to bug#20135976 */
  if (signal->theData[0] == DumpStateOrd::LgmanDumpUndoStateClusterLog || 
      signal->theData[0] == DumpStateOrd::LgmanDumpUndoStateLocalLog)
  {
    const bool clusterLog = (signal->theData[0] == 
                             DumpStateOrd::LgmanDumpUndoStateClusterLog);
    char tmp[1024];
    Ptr<Logfile_group> ptr;
    m_logfile_group_list.first(ptr);
    while(!ptr.isNull())
    {
      BaseString::snprintf(tmp, sizeof(tmp),
                           "lfg %u state: %x fs: %u lsn "
                           " [ next: %llu s(req): %llu s:ed: %llu lcp: %llu ] "
                           " waiters: %d %d",
                           ptr.p->m_logfile_group_id, ptr.p->m_state,
                           ptr.p->m_outstanding_fs,
                           ptr.p->m_next_lsn, ptr.p->m_last_sync_req_lsn,
                           ptr.p->m_last_synced_lsn, ptr.p->m_last_lcp_lsn,
                           !ptr.p->m_log_buffer_waiters.isEmpty(),
                           !ptr.p->m_log_sync_waiters.isEmpty());
      if (clusterLog)
        infoEvent("%s", tmp);
      ndbout_c("%s", tmp);

      BaseString::snprintf(tmp, sizeof(tmp),
                           "   callback_buffer_words: %u"
                           " free_buffer_words: %u free_file_words: %llu",
                           ptr.p->m_callback_buffer_words,
                           ptr.p->m_free_buffer_words,
                           ptr.p->m_free_file_words);
      if (clusterLog)
        infoEvent("%s", tmp);
      ndbout_c("%s", tmp);
      if (!ptr.p->m_log_buffer_waiters.isEmpty())
      {
	Ptr<Log_waiter> waiter;
	Local_log_waiter_list 
	  list(m_log_waiter_pool, ptr.p->m_log_buffer_waiters);
	list.first(waiter);
        BaseString::snprintf(tmp, sizeof(tmp),
                             "  head(waiters).sz: %u %u",
                             waiter.p->m_size,
                             FREE_BUFFER_MARGIN);
        if (clusterLog)
          infoEvent("%s", tmp);
        ndbout_c("%s", tmp);
      }
      if (!ptr.p->m_log_sync_waiters.isEmpty())
      {
	Ptr<Log_waiter> waiter;
	Local_log_waiter_list 
	  list(m_log_waiter_pool, ptr.p->m_log_sync_waiters);
	list.first(waiter);
        BaseString::snprintf(tmp, sizeof(tmp),
                             "  m_last_synced_lsn: %llu head(waiters %x).m_sync_lsn: %llu",
                             ptr.p->m_last_synced_lsn,
                             waiter.i,
                             waiter.p->m_sync_lsn);
        if (clusterLog)
          infoEvent("%s", tmp);
        ndbout_c("%s", tmp);
	
	while(!waiter.isNull())
	{
	  ndbout_c("ptr: %x %p lsn: %llu next: %x",
		   waiter.i, waiter.p, waiter.p->m_sync_lsn, waiter.p->nextList);
	  list.next(waiter);
	}
      }
      m_logfile_group_list.next(ptr);
    }
  }
  if (signal->theData[0] == DumpStateOrd::LgmanCheckCallbacksClear)
  {
    bool crash = false;
    Ptr<Logfile_group> ptr;
    for (m_logfile_group_list.first(ptr); !ptr.isNull();
         m_logfile_group_list.next(ptr))
    {
      if (ptr.p->m_callback_buffer_words != 0)
      {
        crash = true;
        break;
      }
    }

    if (crash)
    {
      ndbout_c("Detected logfile-group with non zero m_callback_buffer_words");
      signal->theData[0] = DumpStateOrd::LgmanDumpUndoStateLocalLog;
      execDUMP_STATE_ORD(signal);
      ndbrequire(false);
    }
#ifdef VM_TRACE
    else
    {
      ndbout_c("Check for non zero m_callback_buffer_words OK!");
    }
#endif
  }
}

void
Lgman::execDBINFO_SCANREQ(Signal *signal)
{
  DbinfoScanReq req= *(DbinfoScanReq*)signal->theData;
  const Ndbinfo::ScanCursor* cursor = 
    CAST_CONSTPTR(Ndbinfo::ScanCursor, DbinfoScan::getCursorPtr(&req));
  Ndbinfo::Ratelimit rl;

  jamEntry();

  switch(req.tableId) {
  case Ndbinfo::LOGSPACES_TABLEID:
  {
    jam();
    Uint32 startBucket = cursor->data[0];
    Logfile_group_hash_iterator iter;
    m_logfile_group_hash.next(startBucket, iter);

    while (!iter.curr.isNull())
    {
      jam();

      Uint32 currentBucket = iter.bucket;
      Ptr<Logfile_group> ptr = iter.curr;

      Uint64 free = ptr.p->m_free_file_words*4;

      Uint64 total = 0;
      Local_undofile_list list(m_file_pool, ptr.p->m_files);
      Ptr<Undofile> filePtr;
      for (list.first(filePtr); !filePtr.isNull(); list.next(filePtr))
      {
        jam();
        total += (Uint64)filePtr.p->m_file_size *
          (Uint64)File_formats::NDB_PAGE_SIZE;
      }

      Uint64 high = 0; // TODO

      Ndbinfo::Row row(signal, req);
      row.write_uint32(getOwnNodeId());
      row.write_uint32(1); // log type, 1 = DD-UNDO
      row.write_uint32(ptr.p->m_logfile_group_id); // log id
      row.write_uint32(0); // log part

      row.write_uint64(total);          // total allocated
      row.write_uint64((total-free));   // currently in use
      row.write_uint64(high);           // in use high water mark
      ndbinfo_send_row(signal, req, row, rl);

      // move to next
      if (m_logfile_group_hash.next(iter) == false)
      {
        jam(); // no more...
        break;
      }
      else if (iter.bucket == currentBucket)
      {
        jam();
        continue; // we need to iterate an entire bucket
      }
      else if (rl.need_break(req))
      {
        jam();
        ndbinfo_send_scan_break(signal, req, rl, iter.bucket);
        return;
      }
    }
    break;
  }

  case Ndbinfo::LOGBUFFERS_TABLEID:
  {
    jam();
    Uint32 startBucket = cursor->data[0];
    Logfile_group_hash_iterator iter;
    m_logfile_group_hash.next(startBucket, iter);

    while (!iter.curr.isNull())
    {
      jam();

      Uint32 currentBucket = iter.bucket;
      Ptr<Logfile_group> ptr = iter.curr;

      Uint64 free = ptr.p->m_free_buffer_words*4;
      Uint64 total = ptr.p->m_total_buffer_words*4;
      Uint64 high = 0; // TODO

      Ndbinfo::Row row(signal, req);
      row.write_uint32(getOwnNodeId());
      row.write_uint32(1); // log type, 1 = DD-UNDO
      row.write_uint32(ptr.p->m_logfile_group_id); // log id
      row.write_uint32(0); // log part

      row.write_uint64(total);          // total allocated
      row.write_uint64((total-free));   // currently in use
      row.write_uint64(high);           // in use high water mark
      ndbinfo_send_row(signal, req, row, rl);

      // move to next
      if (m_logfile_group_hash.next(iter) == false)
      {
        jam(); // no more...
        break;
      }
      else if (iter.bucket == currentBucket)
      {
        jam();
        continue; // we need to iterate an entire bucket
      }
      else if (rl.need_break(req))
      {
        jam();
        ndbinfo_send_scan_break(signal, req, rl, iter.bucket);
        return;
      }
    }
    break;
  }

  default:
    break;
  }

  ndbinfo_send_scan_conf(signal, req, rl);
}

void
Lgman::execCREATE_FILEGROUP_IMPL_REQ(Signal* signal){
  jamEntry();
  CreateFilegroupImplReq* req= (CreateFilegroupImplReq*)signal->getDataPtr();

  Uint32 senderRef = req->senderRef;
  Uint32 senderData = req->senderData;
  
  Ptr<Logfile_group> ptr;
  CreateFilegroupImplRef::ErrorCode err = CreateFilegroupImplRef::NoError;
  do {
    if (m_logfile_group_hash.find(ptr, req->filegroup_id))
    {
      jam();
      err = CreateFilegroupImplRef::FilegroupAlreadyExists;
      break;
    }
    
    if (!m_logfile_group_list.isEmpty())
    {
      jam();
      err = CreateFilegroupImplRef::OneLogfileGroupLimit;
      break;
    }

    if (!m_logfile_group_pool.seize(ptr))
    {
      jam();
      err = CreateFilegroupImplRef::OutOfFilegroupRecords;
      break;
    }

    new (ptr.p) Logfile_group(req);
    
    if (!alloc_logbuffer_memory(ptr, req->logfile_group.buffer_size))
    {
      jam();
      err= CreateFilegroupImplRef::OutOfLogBufferMemory;
      m_logfile_group_pool.release(ptr);
      break;
    }
    
    m_logfile_group_hash.add(ptr);
    m_logfile_group_list.addLast(ptr);

    if ((getNodeState().getNodeRestartInProgress() &&
         getNodeState().starting.restartType !=
         NodeState::ST_INITIAL_NODE_RESTART)||
        getNodeState().getSystemRestartInProgress())
    {
      ptr.p->m_state = Logfile_group::LG_STARTING;
    }
    
    CreateFilegroupImplConf* conf= 
      (CreateFilegroupImplConf*)signal->getDataPtr();
    conf->senderData = senderData;
    conf->senderRef = reference();
    sendSignal(senderRef, GSN_CREATE_FILEGROUP_IMPL_CONF, signal,
	       CreateFilegroupImplConf::SignalLength, JBB);
    
    return;
  } while(0);
  
  CreateFilegroupImplRef* ref= (CreateFilegroupImplRef*)signal->getDataPtr();
  ref->senderData = senderData;
  ref->senderRef = reference();
  ref->errorCode = err;
  sendSignal(senderRef, GSN_CREATE_FILEGROUP_IMPL_REF, signal,
	     CreateFilegroupImplRef::SignalLength, JBB);
}

void
Lgman::execDROP_FILEGROUP_IMPL_REQ(Signal* signal)
{
  jamEntry();

  Uint32 errorCode = 0;
  DropFilegroupImplReq req = *(DropFilegroupImplReq*)signal->getDataPtr();  
  do 
  {
    Ptr<Logfile_group> ptr;
    if (!m_logfile_group_hash.find(ptr, req.filegroup_id))
    {
      errorCode = DropFilegroupImplRef::NoSuchFilegroup;
      break;
    }
    
    if (ptr.p->m_version != req.filegroup_version)
    {
      errorCode = DropFilegroupImplRef::InvalidFilegroupVersion;
      break;
    }
    
    switch(req.requestInfo){
    case DropFilegroupImplReq::Prepare:
      break;
    case DropFilegroupImplReq::Commit:
      m_logfile_group_list.remove(ptr);
      ptr.p->m_state |= Logfile_group::LG_DROPPING;
      signal->theData[0] = LgmanContinueB::DROP_FILEGROUP;
      signal->theData[1] = ptr.i;
      signal->theData[2] = req.senderRef;
      signal->theData[3] = req.senderData;
      sendSignal(reference(), GSN_CONTINUEB, signal, 4, JBB);
      return;
    case DropFilegroupImplReq::Abort:
      break;
    default:
      ndbrequire(false);
    }
  } while(0);
  
  if (errorCode)
  {
    DropFilegroupImplRef* ref = 
      (DropFilegroupImplRef*)signal->getDataPtrSend();
    ref->senderRef = reference();
    ref->senderData = req.senderData;
    ref->errorCode = errorCode;
    sendSignal(req.senderRef, GSN_DROP_FILEGROUP_IMPL_REF, signal,
	       DropFilegroupImplRef::SignalLength, JBB);
  }
  else
  {
    DropFilegroupImplConf* conf = 
      (DropFilegroupImplConf*)signal->getDataPtrSend();
    conf->senderRef = reference();
    conf->senderData = req.senderData;
    sendSignal(req.senderRef, GSN_DROP_FILEGROUP_IMPL_CONF, signal,
	       DropFilegroupImplConf::SignalLength, JBB);
  }
}

void
Lgman::drop_filegroup_drop_files(Signal* signal,
				 Ptr<Logfile_group> ptr,
				 Uint32 ref, Uint32 data)
{
  jam();
  ndbrequire(! (ptr.p->m_state & Logfile_group::LG_THREAD_MASK));
  ndbrequire(ptr.p->m_outstanding_fs == 0);

  Local_undofile_list list(m_file_pool, ptr.p->m_files);
  Ptr<Undofile> file_ptr;

  if (list.first(file_ptr))
  {
    jam();
    ndbrequire(! (file_ptr.p->m_state & Undofile::FS_OUTSTANDING));
    file_ptr.p->m_create.m_senderRef = ref;
    file_ptr.p->m_create.m_senderData = data;
    create_file_abort(signal, ptr, file_ptr);
    return;
  }

  Local_undofile_list metalist(m_file_pool, ptr.p->m_meta_files);
  if (metalist.first(file_ptr))
  {
    jam();
    metalist.remove(file_ptr);
    list.addLast(file_ptr);
    file_ptr.p->m_create.m_senderRef = ref;
    file_ptr.p->m_create.m_senderData = data;
    create_file_abort(signal, ptr, file_ptr);
    return;
  }

  free_logbuffer_memory(ptr);
  m_logfile_group_hash.release(ptr);
  DropFilegroupImplConf *conf = (DropFilegroupImplConf*)signal->getDataPtr();  
  conf->senderData = data;
  conf->senderRef = reference();
  sendSignal(ref, GSN_DROP_FILEGROUP_IMPL_CONF, signal,
	     DropFilegroupImplConf::SignalLength, JBB);
}

void
Lgman::execCREATE_FILE_IMPL_REQ(Signal* signal)
{
  jamEntry();
  CreateFileImplReq* req= (CreateFileImplReq*)signal->getDataPtr();
  
  Uint32 senderRef = req->senderRef;
  Uint32 senderData = req->senderData;
  Uint32 requestInfo = req->requestInfo;
  
  Ptr<Logfile_group> ptr;
  CreateFileImplRef::ErrorCode err = CreateFileImplRef::NoError;
  SectionHandle handle(this, signal);
  do {
    if (!m_logfile_group_hash.find(ptr, req->filegroup_id))
    {
      jam();
      err = CreateFileImplRef::InvalidFilegroup;
      break;
    }

    if (ptr.p->m_version != req->filegroup_version)
    {
      jam();
      err = CreateFileImplRef::InvalidFilegroupVersion;
      break;
    }

    Ptr<Undofile> file_ptr;
    switch(requestInfo){
    case CreateFileImplReq::Commit:
    {
      jam();
      ndbrequire(find_file_by_id(file_ptr, ptr.p->m_meta_files, req->file_id));
      file_ptr.p->m_create.m_senderRef = req->senderRef;
      file_ptr.p->m_create.m_senderData = req->senderData;
      create_file_commit(signal, ptr, file_ptr);
      return;
    }
    case CreateFileImplReq::Abort:
    {
      Uint32 senderRef = req->senderRef;
      Uint32 senderData = req->senderData;
      if (find_file_by_id(file_ptr, ptr.p->m_meta_files, req->file_id))
      {
        jam();
	file_ptr.p->m_create.m_senderRef = senderRef;
	file_ptr.p->m_create.m_senderData = senderData;
	create_file_abort(signal, ptr, file_ptr);
      }
      else
      {
	CreateFileImplConf* conf= (CreateFileImplConf*)signal->getDataPtr();
        jam();
	conf->senderData = senderData;
	conf->senderRef = reference();
	sendSignal(senderRef, GSN_CREATE_FILE_IMPL_CONF, signal,
		   CreateFileImplConf::SignalLength, JBB);
      }
      return;
    }
    default: // prepare
      break;
    }
    
    if (!m_file_pool.seize(file_ptr))
    {
      jam();
      err = CreateFileImplRef::OutOfFileRecords;
      break;
    }

    ndbrequire(handle.m_cnt > 0);
    
    if (ERROR_INSERTED(15000) ||
        (sizeof(void*) == 4 && req->file_size_hi & 0xFFFFFFFF))
    {
      jam();
      err = CreateFileImplRef::FileSizeTooLarge;
      break;
    }
    
    Uint64 sz = (Uint64(req->file_size_hi) << 32) + req->file_size_lo;
    if (sz < 1024*1024)
    {
      jam();
      err = CreateFileImplRef::FileSizeTooSmall;
      break;
    }

    new (file_ptr.p) Undofile(req, ptr.i);

    Local_undofile_list tmp(m_file_pool, ptr.p->m_meta_files);
    tmp.addLast(file_ptr);
    
    open_file(signal, file_ptr, req->requestInfo, &handle);
    return;
  } while(0);

  releaseSections(handle);
  CreateFileImplRef* ref= (CreateFileImplRef*)signal->getDataPtr();
  ref->senderData = senderData;
  ref->senderRef = reference();
  ref->errorCode = err;
  sendSignal(senderRef, GSN_CREATE_FILE_IMPL_REF, signal,
	     CreateFileImplRef::SignalLength, JBB);
}

void
Lgman::open_file(Signal* signal, Ptr<Undofile> ptr,
		 Uint32 requestInfo,
		 SectionHandle * handle)
{
  FsOpenReq* req = (FsOpenReq*)signal->getDataPtrSend();
  req->userReference = reference();
  req->userPointer = ptr.i;
  
  memset(req->fileNumber, 0, sizeof(req->fileNumber));
  FsOpenReq::setVersion(req->fileNumber, 4); // Version 4 = specified filename
  FsOpenReq::v4_setBasePath(req->fileNumber, FsOpenReq::BP_DD_UF);

  req->fileFlags = 0;
  req->fileFlags |= FsOpenReq::OM_READWRITE;
  req->fileFlags |= FsOpenReq::OM_DIRECT;
  req->fileFlags |= FsOpenReq::OM_SYNC;
  switch(requestInfo){
  case CreateFileImplReq::Create:
    req->fileFlags |= FsOpenReq::OM_CREATE_IF_NONE;
    req->fileFlags |= FsOpenReq::OM_INIT;
    ptr.p->m_state = Undofile::FS_CREATING;
    break;
  case CreateFileImplReq::CreateForce:
    req->fileFlags |= FsOpenReq::OM_CREATE;
    req->fileFlags |= FsOpenReq::OM_INIT;
    ptr.p->m_state = Undofile::FS_CREATING;
    break;
  case CreateFileImplReq::Open:
    req->fileFlags |= FsOpenReq::OM_CHECK_SIZE;
    ptr.p->m_state = Undofile::FS_OPENING;
    break;
  default:
    ndbrequire(false);
  }

  req->page_size = File_formats::NDB_PAGE_SIZE;
  Uint64 size = (Uint64)ptr.p->m_file_size * (Uint64)File_formats::NDB_PAGE_SIZE;
  req->file_size_hi = (Uint32)(size >> 32);
  req->file_size_lo = (Uint32)(size & 0xFFFFFFFF);

  sendSignal(NDBFS_REF, GSN_FSOPENREQ, signal, FsOpenReq::SignalLength, JBB,
	     handle);
}

/**
 * This code is called during initialisation of the file to ensure that the
 * file content is properly set when the file is created, it is a direct
 * function call via block methods from the file system thread into this
 * block. So this means that we are not allowed to change any block variables
 * and even for reading we have to be careful. The pages are allocated in
 * NDBFS from the DataMemory in DBTUP. So these pages we are allowed to
 * change since they are owned at this moment by the NDB file system thread.
 */
void
Lgman::execFSWRITEREQ(Signal* signal)
{
  jamNoBlock();
  Ptr<Undofile> ptr;
  Ptr<GlobalPage> page_ptr;
  FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtr();
  
  m_file_pool.getPtr(ptr, req->userPointer);
  m_shared_page_pool.getPtr(page_ptr, req->data.pageData[0]);

  if (req->varIndex == 0)
  {
    File_formats::Undofile::Zero_page* page = 
      (File_formats::Undofile::Zero_page*)page_ptr.p;
    page->m_page_header.init(File_formats::FT_Undofile, 
			     getOwnNodeId(),
			     ndbGetOwnVersion(),
			     (Uint32)time(0));
    page->m_file_id = ptr.p->m_file_id;
    page->m_logfile_group_id = ptr.p->m_create.m_logfile_group_id;
    page->m_logfile_group_version = ptr.p->m_create.m_logfile_group_version;
    page->m_undo_pages = ptr.p->m_file_size - 1; // minus zero page
  }
  else if (req->varIndex == 1)
  {
    /**
     * We write an UNDO END log record into the very first page. This is to
     * ensure that we don't pass this point if we crash before completing
     * the first synch to the UNDO log. Without this log record we could
     * have written other pages but not this page. In that case we would
     * have no way to distinguish when we find the end of the UNDO log.
     */
    File_formats::Undofile::Undo_page* page = 
      (File_formats::Undofile::Undo_page*)page_ptr.p;
    page->m_page_header.m_page_lsn_hi = 0;
    page->m_page_header.m_page_lsn_lo = 0;
    page->m_words_used = 1;
    page->m_data[0] = (File_formats::Undofile::UNDO_END << 16) | 1 ;
    page->m_page_header.m_page_type = File_formats::PT_Undopage;
  }
  else
  {
    File_formats::Undofile::Undo_page* page = 
      (File_formats::Undofile::Undo_page*)page_ptr.p;
    page->m_page_header.m_page_lsn_hi = 0;
    page->m_page_header.m_page_lsn_lo = 0;
    page->m_page_header.m_page_type = File_formats::PT_Undopage;
    page->m_words_used = 0;
  }
}

void
Lgman::execFSOPENREF(Signal* signal)
{
  jamNoBlock();

  Ptr<Undofile> ptr;  
  Ptr<Logfile_group> lg_ptr;
  FsRef* ref = (FsRef*)signal->getDataPtr();

  Uint32 errCode = ref->errorCode;
  Uint32 osErrCode = ref->osErrorCode;

  m_file_pool.getPtr(ptr, ref->userPointer);
  m_logfile_group_pool.getPtr(lg_ptr, ptr.p->m_logfile_group_ptr_i);

  {
    CreateFileImplRef* ref= (CreateFileImplRef*)signal->getDataPtr();
    ref->senderData = ptr.p->m_create.m_senderData;
    ref->senderRef = reference();
    ref->errorCode = CreateFileImplRef::FileError;
    ref->fsErrCode = errCode;
    ref->osErrCode = osErrCode;

    sendSignal(ptr.p->m_create.m_senderRef, GSN_CREATE_FILE_IMPL_REF, signal,
	       CreateFileImplRef::SignalLength, JBB);
  }

  Local_undofile_list meta(m_file_pool, lg_ptr.p->m_meta_files);
  meta.release(ptr);
}

#define HEAD 0
#define TAIL 1

void
Lgman::execFSOPENCONF(Signal* signal)
{
  jamEntry();
  Ptr<Undofile> ptr;  

  FsConf* conf = (FsConf*)signal->getDataPtr();
  
  Uint32 fd = conf->filePointer;
  m_file_pool.getPtr(ptr, conf->userPointer);

  ptr.p->m_fd = fd;

  {
    Uint32 senderRef = ptr.p->m_create.m_senderRef;
    Uint32 senderData = ptr.p->m_create.m_senderData;
    
    CreateFileImplConf* conf= (CreateFileImplConf*)signal->getDataPtr();
    conf->senderData = senderData;
    conf->senderRef = reference();
    sendSignal(senderRef, GSN_CREATE_FILE_IMPL_CONF, signal,
	       CreateFileImplConf::SignalLength, JBB);
  }
}

bool 
Lgman::find_file_by_id(Ptr<Undofile>& ptr, 
		       Local_undofile_list::Head& head, Uint32 id)
{
  Local_undofile_list list(m_file_pool, head);
  for(list.first(ptr); !ptr.isNull(); list.next(ptr))
    if(ptr.p->m_file_id == id)
      return true;
  return false;
}

void
Lgman::create_file_commit(Signal* signal, 
			  Ptr<Logfile_group> lg_ptr, 
			  Ptr<Undofile> ptr)
{
  Uint32 senderRef = ptr.p->m_create.m_senderRef;
  Uint32 senderData = ptr.p->m_create.m_senderData;

  bool first= false;
  if(ptr.p->m_state == Undofile::FS_CREATING &&
     (lg_ptr.p->m_state & Logfile_group::LG_ONLINE))
  {
    jam();
    Local_undofile_list free(m_file_pool, lg_ptr.p->m_files);
    Local_undofile_list meta(m_file_pool, lg_ptr.p->m_meta_files);
    first= free.isEmpty();
    meta.remove(ptr);
    if(!first)
    {
      /**
       * Add log file next after current head
       */
      Ptr<Undofile> curr;
      m_file_pool.getPtr(curr, lg_ptr.p->m_file_pos[HEAD].m_ptr_i);
      if(free.next(curr))
        free.insertBefore(ptr, curr);
      else
        free.addLast(ptr);

      ptr.p->m_state = Undofile::FS_ONLINE | Undofile::FS_EMPTY;
    }
    else
    {
      /**
       * First file isn't empty as it can be written to at any time
       */
      free.addLast(ptr);
      ptr.p->m_state = Undofile::FS_ONLINE;
      lg_ptr.p->m_state |= Logfile_group::LG_FLUSH_THREAD;
      signal->theData[0] = LgmanContinueB::FLUSH_LOG;
      signal->theData[1] = lg_ptr.i;
      signal->theData[2] = 0;
      sendSignal(reference(), GSN_CONTINUEB, signal, 3, JBB);
    }
  }
  else
  {
    ptr.p->m_state = Undofile::FS_SORTING;
  }
  
  ptr.p->m_online.m_lsn = 0;
  ptr.p->m_online.m_outstanding = 0;
  
  Uint64 add= ptr.p->m_file_size - 1;
  lg_ptr.p->m_free_file_words += add * File_formats::UNDO_PAGE_WORDS;

  if(first)
  {
    jam();
    
    Buffer_idx tmp= { ptr.i, 0 };
    lg_ptr.p->m_file_pos[HEAD] = lg_ptr.p->m_file_pos[TAIL] = tmp;
    
    /**
     * Init log tail pointer
     */
    lg_ptr.p->m_tail_pos[0] = tmp;
    lg_ptr.p->m_tail_pos[1] = tmp;
    lg_ptr.p->m_tail_pos[2] = tmp;
    lg_ptr.p->m_next_reply_ptr_i = ptr.i;
  }

  validate_logfile_group(lg_ptr, "create_file_commit");

  CreateFileImplConf* conf= (CreateFileImplConf*)signal->getDataPtr();
  conf->senderData = senderData;
  conf->senderRef = reference();
  sendSignal(senderRef, GSN_CREATE_FILE_IMPL_CONF, signal,
	     CreateFileImplConf::SignalLength, JBB);
}

void
Lgman::create_file_abort(Signal* signal, 
			 Ptr<Logfile_group> lg_ptr, 
			 Ptr<Undofile> ptr)
{
  if (ptr.p->m_fd == RNIL)
  {
    ((FsConf*)signal->getDataPtr())->userPointer = ptr.i;
    execFSCLOSECONF(signal);
    return;
  }

  FsCloseReq *req= (FsCloseReq*)signal->getDataPtrSend();
  req->filePointer = ptr.p->m_fd;
  req->userReference = reference();
  req->userPointer = ptr.i;
  req->fileFlag = 0;
  FsCloseReq::setRemoveFileFlag(req->fileFlag, true);
  
  sendSignal(NDBFS_REF, GSN_FSCLOSEREQ, signal, 
	     FsCloseReq::SignalLength, JBB);
}

void
Lgman::execFSCLOSECONF(Signal* signal)
{
  Ptr<Undofile> ptr;
  Ptr<Logfile_group> lg_ptr;
  Uint32 ptrI = ((FsConf*)signal->getDataPtr())->userPointer;
  m_file_pool.getPtr(ptr, ptrI);
  
  Uint32 senderRef = ptr.p->m_create.m_senderRef;
  Uint32 senderData = ptr.p->m_create.m_senderData;
  
  m_logfile_group_pool.getPtr(lg_ptr, ptr.p->m_logfile_group_ptr_i);

  if (lg_ptr.p->m_state & Logfile_group::LG_DROPPING)
  {
    jam();
    {
      Local_undofile_list list(m_file_pool, lg_ptr.p->m_files);
      list.release(ptr);
    }
    drop_filegroup_drop_files(signal, lg_ptr, senderRef, senderData);
  }
  else
  {
    jam();
    Local_undofile_list list(m_file_pool, lg_ptr.p->m_meta_files);
    list.release(ptr);

    CreateFileImplConf* conf= (CreateFileImplConf*)signal->getDataPtr();
    conf->senderData = senderData;
    conf->senderRef = reference();
    sendSignal(senderRef, GSN_CREATE_FILE_IMPL_CONF, signal,
	       CreateFileImplConf::SignalLength, JBB);
  }
}

void
Lgman::execDROP_FILE_IMPL_REQ(Signal* signal)
{
  jamEntry();
  ndbrequire(false);
}

#define CONSUMER 0
#define PRODUCER 1

Lgman::Logfile_group::Logfile_group(const CreateFilegroupImplReq* req)
{
  m_logfile_group_id = req->filegroup_id;
  m_version = req->filegroup_version;
  m_state = LG_ONLINE;
  m_outstanding_fs = 0;
  m_next_reply_ptr_i = RNIL;
  
  m_applied = false;
  /**
   * We initialise this variable to 0 to indicate that we haven't received
   * any log records yet, this is used in a number of checks, probably better
   * to remove those checks and do the same work even when no LSNs have been
   * produced, but keep it for now as it is easiest.
   */
  m_next_lsn = 0;
  m_last_synced_lsn = 0;
  m_last_sync_req_lsn = 0;
  m_max_sync_req_lsn = 0;
  m_last_read_lsn = 0;
  m_file_pos[0].m_ptr_i= m_file_pos[1].m_ptr_i = RNIL;

  m_free_file_words = 0;
  m_total_buffer_words = 0;
  m_free_buffer_words = 0;
  m_callback_buffer_words = 0;

  m_pos[CONSUMER].m_current_page.m_ptr_i = RNIL;// { m_buffer_pages, idx }
  m_pos[CONSUMER].m_current_pos.m_ptr_i = RNIL; // { page ptr.i, m_words_used}
  m_pos[PRODUCER].m_current_page.m_ptr_i = RNIL;// { m_buffer_pages, idx }
  m_pos[PRODUCER].m_current_pos.m_ptr_i = RNIL; // { page ptr.i, m_words_used}

  m_tail_pos[2].m_ptr_i= RNIL;
  m_tail_pos[2].m_idx= ~0;
  
  m_tail_pos[0] = m_tail_pos[1] = m_tail_pos[2];
}

bool
Lgman::alloc_logbuffer_memory(Ptr<Logfile_group> ptr, Uint32 bytes)
{
  Uint32 pages= (((bytes + 3) >> 2) + File_formats::NDB_PAGE_SIZE_WORDS - 1)
    / File_formats::NDB_PAGE_SIZE_WORDS;
  Uint32 requested= pages;
  {
    Page_map map(m_data_buffer_pool, ptr.p->m_buffer_pages);
    while(pages)
    {
      Uint32 ptrI;
      Uint32 cnt = pages > 64 ? 64 : pages;
      m_ctx.m_mm.alloc_pages(RG_DISK_OPERATIONS, &ptrI, &cnt, 1);
      if (cnt)
      {
	Buffer_idx range;
	range.m_ptr_i= ptrI;
	range.m_idx = cnt;
        
	if (map.append((Uint32*)&range, 2) == false)
        {
          /**
           * Failed to append page-range...
           *   jump out of alloc routine
           */
          jam();
          m_ctx.m_mm.release_pages(RG_DISK_OPERATIONS, 
                                   range.m_ptr_i, range.m_idx);
          break;
        }
	pages -= range.m_idx;
      }
      else
      {
	break;
      }
    }
  }

  if(pages)
  {
    /* Could not allocate all of the requested memory.
     * So release that already allocated.
     */
    free_logbuffer_memory(ptr);
    return false;
  }
  
#if defined VM_TRACE || defined ERROR_INSERT
  ndbout << "DD lgman: fg id:" << ptr.p->m_logfile_group_id << " undo buffer pages/bytes:" << (requested-pages) << "/" << (requested-pages)*File_formats::NDB_PAGE_SIZE << endl;
#endif
  
  init_logbuffer_pointers(ptr);
  return true;
}

void
Lgman::init_logbuffer_pointers(Ptr<Logfile_group> ptr)
{
  Page_map map(m_data_buffer_pool, ptr.p->m_buffer_pages);
  Page_map::Iterator it;
  union {
    Uint32 tmp[2];
    Buffer_idx range;
  };
  
  map.first(it);
  tmp[0] = *it.data;
  ndbrequire(map.next(it));
  tmp[1] = *it.data;
  
  ptr.p->m_pos[CONSUMER].m_current_page.m_ptr_i = 0;      // Index in page map
  ptr.p->m_pos[CONSUMER].m_current_page.m_idx = range.m_idx - 1;// left range
  ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i = range.m_ptr_i; // Which page
  ptr.p->m_pos[CONSUMER].m_current_pos.m_idx = 0;               // Page pos
  
  ptr.p->m_pos[PRODUCER].m_current_page.m_ptr_i = 0;      // Index in page map
  ptr.p->m_pos[PRODUCER].m_current_page.m_idx = range.m_idx - 1;// left range
  ptr.p->m_pos[PRODUCER].m_current_pos.m_ptr_i = range.m_ptr_i; // Which page
  ptr.p->m_pos[PRODUCER].m_current_pos.m_idx = 0;               // Page pos

  Uint32 pages= range.m_idx;
  while(map.next(it))
  {
    tmp[0] = *it.data;
    ndbrequire(map.next(it));
    tmp[1] = *it.data;
    pages += range.m_idx;
  }
  
  ptr.p->m_total_buffer_words =
    ptr.p->m_free_buffer_words = pages * File_formats::UNDO_PAGE_WORDS;
}

Uint32
Lgman::compute_free_file_pages(Ptr<Logfile_group> ptr)
{
  Buffer_idx head= ptr.p->m_file_pos[HEAD];
  Buffer_idx tail= ptr.p->m_file_pos[TAIL];
  Uint32 pages = 0;
  if (head.m_ptr_i == tail.m_ptr_i && head.m_idx < tail.m_idx)
  {
    pages += tail.m_idx - head.m_idx;
  }
  else
  {
    Ptr<Undofile> file;
    m_file_pool.getPtr(file, head.m_ptr_i);
    Local_undofile_list list(m_file_pool, ptr.p->m_files);
    
    do 
    {
      pages += (file.p->m_file_size - head.m_idx - 1);
      if(!list.next(file))
	list.first(file);
      head.m_idx = 0;
    } while(file.i != tail.m_ptr_i);
    
    pages += tail.m_idx - head.m_idx;
  }
  return pages;
}

void
Lgman::free_logbuffer_memory(Ptr<Logfile_group> ptr)
{
  union {
    Uint32 tmp[2];
    Buffer_idx range;
  };

  Page_map map(m_data_buffer_pool, ptr.p->m_buffer_pages);

  Page_map::Iterator it;
  map.first(it);
  while(!it.isNull())
  {
    tmp[0] = *it.data;
    ndbrequire(map.next(it));
    tmp[1] = *it.data;
    
    m_ctx.m_mm.release_pages(RG_DISK_OPERATIONS, range.m_ptr_i, range.m_idx);
    map.next(it);
  }
  map.release();
}

Lgman::Undofile::Undofile(const struct CreateFileImplReq* req, Uint32 ptrI)
{
  m_fd = RNIL;
  m_file_id = req->file_id;
  m_logfile_group_ptr_i= ptrI;
  
  Uint64 pages = req->file_size_hi;
  pages = (pages << 32) | req->file_size_lo;
  pages /= GLOBAL_PAGE_SIZE;
  m_file_size = Uint32(pages);
#if defined VM_TRACE || defined ERROR_INSERT
  ndbout << "DD lgman: file id:" << m_file_id << " undofile pages/bytes:" << m_file_size << "/" << m_file_size*GLOBAL_PAGE_SIZE << endl;
#endif

  m_create.m_senderRef = req->senderRef; // During META
  m_create.m_senderData = req->senderData; // During META
  m_create.m_logfile_group_id = req->filegroup_id;
}

Logfile_client::Logfile_client(SimulatedBlock* block, 
			       Lgman* lgman, Uint32 logfile_group_id,
                               bool lock)
{
  Uint32 bno = block->number();
  Uint32 ino = block->instance();
  m_client_block= block;
  m_block= numberToBlock(bno, ino);
  m_lgman= lgman;
  m_lock = lock;
  m_logfile_group_id= logfile_group_id;
  D("client ctor " << bno << "/" << ino);
  if (m_lock)
    m_lgman->client_lock(m_block, 0);
}

Logfile_client::~Logfile_client()
{
#ifdef VM_TRACE
  Uint32 bno = blockToMain(m_block);
  Uint32 ino = blockToInstance(m_block);
#endif
  D("client dtor " << bno << "/" << ino);
  if (m_lock)
    m_lgman->client_unlock(m_block, 0);
}

int
Logfile_client::sync_lsn(Signal* signal, 
			 Uint64 lsn, Request* req, Uint32 flags)
{
  Ptr<Lgman::Logfile_group> ptr;
  if(m_lgman->m_logfile_group_list.first(ptr))
  {
    if(ptr.p->m_last_synced_lsn >= lsn)
    {
      return 1;
    }
    
    bool empty= false;
    Ptr<Lgman::Log_waiter> wait;
    {
      Lgman::Local_log_waiter_list
	list(m_lgman->m_log_waiter_pool, ptr.p->m_log_sync_waiters);
      
      empty= list.isEmpty();
      if (!list.seizeLast(wait))
	return -1;
      
      wait.p->m_block= m_block;
      wait.p->m_sync_lsn= lsn;
      memcpy(&wait.p->m_callback, &req->m_callback, 
	     sizeof(SimulatedBlock::CallbackPtr));

      ptr.p->m_max_sync_req_lsn = lsn > ptr.p->m_max_sync_req_lsn ?
	lsn : ptr.p->m_max_sync_req_lsn;
    }
    
    if(ptr.p->m_last_sync_req_lsn < lsn && 
       ! (ptr.p->m_state & Lgman::Logfile_group::LG_FORCE_SYNC_THREAD))
    { 
      ptr.p->m_state |= Lgman::Logfile_group::LG_FORCE_SYNC_THREAD;
      signal->theData[0] = LgmanContinueB::FORCE_LOG_SYNC;
      signal->theData[1] = ptr.i;
      signal->theData[2] = (Uint32)(lsn >> 32);
      signal->theData[3] = (Uint32)(lsn & 0xFFFFFFFF);
      m_client_block->sendSignalWithDelay(m_lgman->reference(), 
                                          GSN_CONTINUEB, signal, 10, 4);
    }
    return 0;
  }
  return -1;
}

void
Lgman::force_log_sync(Signal* signal, 
		      Ptr<Logfile_group> ptr, 
		      Uint32 lsn_hi, Uint32 lsn_lo)
{
  Local_log_waiter_list list(m_log_waiter_pool, ptr.p->m_log_sync_waiters);
  Uint64 force_lsn = lsn_hi; force_lsn <<= 32; force_lsn += lsn_lo;

  if(ptr.p->m_last_sync_req_lsn < force_lsn)
  {
    /**
     * Do force
     */
    Buffer_idx pos= ptr.p->m_pos[PRODUCER].m_current_pos;
    GlobalPage *page = m_shared_page_pool.getPtr(pos.m_ptr_i);
  
    Uint32 free= File_formats::UNDO_PAGE_WORDS - pos.m_idx;
    if(pos.m_idx) // don't flush empty page...
    {
      Uint64 lsn= ptr.p->m_next_lsn - 1;
      
      File_formats::Undofile::Undo_page* undo= 
	(File_formats::Undofile::Undo_page*)page;
      undo->m_page_header.m_page_lsn_lo = (Uint32)(lsn & 0xFFFFFFFF);
      undo->m_page_header.m_page_lsn_hi = (Uint32)(lsn >> 32);
      undo->m_words_used= File_formats::UNDO_PAGE_WORDS - free;
      
      /**
       * Update free space with extra NOOP
       */
      ndbrequire(ptr.p->m_free_file_words >= free);
      ndbrequire(ptr.p->m_free_buffer_words > free);
      ptr.p->m_free_file_words -= free;
      ptr.p->m_free_buffer_words -= free;
      
      validate_logfile_group(ptr, "force_log_sync");

      next_page(ptr.p, PRODUCER);
      ptr.p->m_pos[PRODUCER].m_current_pos.m_idx = 0;
    }
  }

  
  
  Uint64 max_req_lsn = ptr.p->m_max_sync_req_lsn;
  if(max_req_lsn > force_lsn && 
     max_req_lsn > ptr.p->m_last_sync_req_lsn)
  {
    ndbrequire(ptr.p->m_state & Lgman::Logfile_group::LG_FORCE_SYNC_THREAD);
    signal->theData[0] = LgmanContinueB::FORCE_LOG_SYNC;
    signal->theData[1] = ptr.i;
    signal->theData[2] = (Uint32)(max_req_lsn >> 32);
    signal->theData[3] = (Uint32)(max_req_lsn & 0xFFFFFFFF);
    sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 10, 4);
  }
  else
  {
    ptr.p->m_state &= ~(Uint32)Lgman::Logfile_group::LG_FORCE_SYNC_THREAD;
  }
}

void
Lgman::process_log_sync_waiters(Signal* signal, Ptr<Logfile_group> ptr)
{
  Local_log_waiter_list 
    list(m_log_waiter_pool, ptr.p->m_log_sync_waiters);

  if(list.isEmpty())
  {
    return;
  }

  bool removed= false;
  Ptr<Log_waiter> waiter;
  list.first(waiter);
  Uint32 logfile_group_id = ptr.p->m_logfile_group_id;

  if(waiter.p->m_sync_lsn <= ptr.p->m_last_synced_lsn)
  {
    removed= true;
    Uint32 block = waiter.p->m_block;
    CallbackPtr & callback = waiter.p->m_callback;
    sendCallbackConf(signal, block, callback, logfile_group_id,
                     LgmanContinueB::PROCESS_LOG_SYNC_WAITERS, 0);
    
    list.releaseFirst(/* waiter */);
  }
  
  if(removed && !list.isEmpty())
  {
    ptr.p->m_state |= Logfile_group::LG_SYNC_WAITERS_THREAD;
    signal->theData[0] = LgmanContinueB::PROCESS_LOG_SYNC_WAITERS;
    signal->theData[1] = ptr.i;
    sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
  }
  else
  {
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_SYNC_WAITERS_THREAD;
  }
}


Uint32*
Lgman::get_log_buffer(Ptr<Logfile_group> ptr, Uint32 sz)
{
  GlobalPage *page;
  page=m_shared_page_pool.getPtr(ptr.p->m_pos[PRODUCER].m_current_pos.m_ptr_i);
  
  Uint32 total_free= ptr.p->m_free_buffer_words;
  ndbrequire(total_free >= sz);
  Uint32 pos= ptr.p->m_pos[PRODUCER].m_current_pos.m_idx;
  Uint32 free= File_formats::UNDO_PAGE_WORDS - pos;

  if(sz <= free)
  {
next:
    // fits this page wo/ problem
    ndbrequire(total_free >= sz);
    ptr.p->m_free_buffer_words = total_free - sz;
    ptr.p->m_pos[PRODUCER].m_current_pos.m_idx = pos + sz;
    return ((File_formats::Undofile::Undo_page*)page)->m_data + pos;
  }
  
  /**
   * It didn't fit page...fill page with a NOOP log entry
   */
  Uint64 lsn= ptr.p->m_next_lsn - 1;
  File_formats::Undofile::Undo_page* undo= 
    (File_formats::Undofile::Undo_page*)page;
  undo->m_page_header.m_page_lsn_lo = (Uint32)(lsn & 0xFFFFFFFF);
  undo->m_page_header.m_page_lsn_hi = (Uint32)(lsn >> 32);
  undo->m_words_used= File_formats::UNDO_PAGE_WORDS - free;
  
  /**
   * Update free space with extra NOOP
   */
  ndbrequire(ptr.p->m_free_file_words >= free);
  ptr.p->m_free_file_words -= free;

  validate_logfile_group(ptr, "get_log_buffer");
  
  pos= 0;
  assert(total_free >= free);
  total_free -= free;
  page= m_shared_page_pool.getPtr(next_page(ptr.p, PRODUCER));
  goto next;
}

Uint32 
Lgman::next_page(Logfile_group* ptrP, Uint32 i)
{
  Uint32 page_ptr_i= ptrP->m_pos[i].m_current_pos.m_ptr_i;
  Uint32 left_in_range= ptrP->m_pos[i].m_current_page.m_idx;
  if(left_in_range > 0)
  {
    ptrP->m_pos[i].m_current_page.m_idx = left_in_range - 1;
    ptrP->m_pos[i].m_current_pos.m_ptr_i = page_ptr_i + 1;
    return page_ptr_i + 1;
  }
  else
  {
    Lgman::Page_map map(m_data_buffer_pool, ptrP->m_buffer_pages);
    Uint32 pos= (ptrP->m_pos[i].m_current_page.m_ptr_i + 2) % map.getSize();
    Lgman::Page_map::Iterator it;
    map.position(it, pos);

    union {
      Uint32 tmp[2];
      Lgman::Buffer_idx range;
    };
    
    tmp[0] = *it.data; map.next(it);
    tmp[1] = *it.data;
    
    ptrP->m_pos[i].m_current_page.m_ptr_i = pos;           // New index in map
    ptrP->m_pos[i].m_current_page.m_idx = range.m_idx - 1; // Free pages 
    ptrP->m_pos[i].m_current_pos.m_ptr_i = range.m_ptr_i;  // Current page
    // No need to set ptrP->m_current_pos.m_idx, that is set "in higher"-func
    return range.m_ptr_i;
  }
}

int
Logfile_client::get_log_buffer(Signal* signal, Uint32 sz, 
			       SimulatedBlock::CallbackPtr* callback)
{
  sz += 2; // lsn
  Lgman::Logfile_group key;
  key.m_logfile_group_id= m_logfile_group_id;
  Ptr<Lgman::Logfile_group> ptr;
  if(m_lgman->m_logfile_group_hash.find(ptr, key))
  {
    Uint32 callback_buffer = ptr.p->m_callback_buffer_words;
    Uint32 free_buffer = ptr.p->m_free_buffer_words;
    if (free_buffer >= (sz + callback_buffer + FREE_BUFFER_MARGIN) &&
        ptr.p->m_log_buffer_waiters.isEmpty())
    {
      ptr.p->m_callback_buffer_words = callback_buffer + sz;
      return 1;
    }
    
    bool empty= false;
    {
      Ptr<Lgman::Log_waiter> wait;
      Lgman::Local_log_waiter_list
	list(m_lgman->m_log_waiter_pool, ptr.p->m_log_buffer_waiters);
      
      empty= list.isEmpty();
      if (!list.seizeFirst(wait))
      {
	return -1;
      }      

      wait.p->m_size= sz;
      wait.p->m_block= m_block;
      memcpy(&wait.p->m_callback, callback,sizeof(SimulatedBlock::CallbackPtr));
    }

    return 0;
  }
  return -1;
}

NdbOut&
operator<<(NdbOut& out, const Lgman::Buffer_idx& pos)
{
  out << "[ " 
      << pos.m_ptr_i << " "
      << pos.m_idx << " ]";
  return out;
}

NdbOut&
operator<<(NdbOut& out, const Lgman::Logfile_group::Position& pos)
{
  out << "[ (" 
      << pos.m_current_page.m_ptr_i << " "
      << pos.m_current_page.m_idx << ") ("
      << pos.m_current_pos.m_ptr_i << " "
      << pos.m_current_pos.m_idx << ") ]";
  return out;
}

void
Lgman::flush_log(Signal* signal, Ptr<Logfile_group> ptr, Uint32 force)
{
  Logfile_group::Position consumer= ptr.p->m_pos[CONSUMER];
  Logfile_group::Position producer= ptr.p->m_pos[PRODUCER];
 
  jamEntry();

  if (consumer.m_current_page == producer.m_current_page)
  {
    jam();
    Buffer_idx pos = producer.m_current_pos;

#if 0
    if (force)
    {
      ndbout_c("force: %d ptr.p->m_file_pos[HEAD].m_ptr_i= %x", 
	       force, ptr.p->m_file_pos[HEAD].m_ptr_i);
      ndbout_c("consumer.m_current_page: %d %d producer.m_current_page: %d %d",
	       consumer.m_current_page.m_ptr_i, consumer.m_current_page.m_idx,
	       producer.m_current_page.m_ptr_i, producer.m_current_page.m_idx);
    }
#endif
    if (! (ptr.p->m_state & Logfile_group::LG_DROPPING))
    {
      jam();

      if (ptr.p->m_log_buffer_waiters.isEmpty() || pos.m_idx == 0)
      {
        jam();
	force =  0;
      }
      else if (ptr.p->m_free_buffer_words < FREE_BUFFER_MARGIN)
      {
        jam();
        force = 2;
      }

      if (force < 2 || ptr.p->m_outstanding_fs)
      {
        jam();
	signal->theData[0] = LgmanContinueB::FLUSH_LOG;
	signal->theData[1] = ptr.i;
	signal->theData[2] = force + 1;
	sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 
			    force ? 10 : 100, 3);
	return;
      }
      else
      {
        jam();
	GlobalPage *page = m_shared_page_pool.getPtr(pos.m_ptr_i);
	
	Uint32 free= File_formats::UNDO_PAGE_WORDS - pos.m_idx;

	ndbout_c("force flush %d %d outstanding: %u isEmpty(): %u",
                 pos.m_idx, ptr.p->m_free_buffer_words,
                 ptr.p->m_outstanding_fs,
                 ptr.p->m_log_buffer_waiters.isEmpty());
	
	ndbrequire(pos.m_idx); // don't flush empty page...
	Uint64 lsn= ptr.p->m_next_lsn - 1;
	
	File_formats::Undofile::Undo_page* undo= 
	  (File_formats::Undofile::Undo_page*)page;
	undo->m_page_header.m_page_lsn_lo = (Uint32)(lsn & 0xFFFFFFFF);
	undo->m_page_header.m_page_lsn_hi = (Uint32)(lsn >> 32);
	undo->m_words_used= File_formats::UNDO_PAGE_WORDS - free;
	
	/**
	 * Update free space with extra NOOP
	 */
	ndbrequire(ptr.p->m_free_file_words >= free);
	ndbrequire(ptr.p->m_free_buffer_words > free);
	ptr.p->m_free_file_words -= free;
	ptr.p->m_free_buffer_words -= free;
         
	validate_logfile_group(ptr, "force_log_flush");
	
	next_page(ptr.p, PRODUCER);
	ptr.p->m_pos[PRODUCER].m_current_pos.m_idx = 0;
	producer = ptr.p->m_pos[PRODUCER];
	// break through
      }
    }
    else
    {
      jam();
      ptr.p->m_state &= ~(Uint32)Logfile_group::LG_FLUSH_THREAD;
      return;
    }
  }
  
  bool full= false;
  Uint32 tot= 0;
  while(!(consumer.m_current_page == producer.m_current_page) && !full)
  {
    jam();
    validate_logfile_group(ptr, "before flush log");

    Uint32 cnt; // pages written
    Uint32 page= consumer.m_current_pos.m_ptr_i;
    if(consumer.m_current_page.m_ptr_i == producer.m_current_page.m_ptr_i)
    {
      /**
       * In same range
       */
      jam();

      if(producer.m_current_pos.m_ptr_i > page)
      {
        /**
         * producer ahead of consumer in same chunk
         */
	jam();
	Uint32 tmp= producer.m_current_pos.m_ptr_i - page;
	cnt= write_log_pages(signal, ptr, page, tmp);
	assert(cnt <= tmp);
	
	consumer.m_current_pos.m_ptr_i += cnt;
	consumer.m_current_page.m_idx -= cnt;
	full= (tmp > cnt);
      }
      else
      {
        /**
         * consumer ahead of producer in same chunk
         */
	Uint32 tmp= consumer.m_current_page.m_idx + 1;
	cnt= write_log_pages(signal, ptr, page, tmp);
	assert(cnt <= tmp);

	if(cnt == tmp)
	{
	  jam();
	  /**
	   * Entire chunk is written
	   *   move to next
	   */
	  ptr.p->m_pos[CONSUMER].m_current_page.m_idx= 0;
	  next_page(ptr.p, CONSUMER);
	  consumer = ptr.p->m_pos[CONSUMER];
 	}
	else
	{
	  jam();
	  /**
	   * Failed to write entire chunk...
	   */
	  full= true;
	  consumer.m_current_page.m_idx -= cnt;
	  consumer.m_current_pos.m_ptr_i += cnt;
	}
      }
    }
    else
    {
      Uint32 tmp= consumer.m_current_page.m_idx + 1;
      cnt= write_log_pages(signal, ptr, page, tmp);
      assert(cnt <= tmp);

      if(cnt == tmp)
      {
	jam();
	/**
	 * Entire chunk is written
	 *   move to next
	 */
	ptr.p->m_pos[CONSUMER].m_current_page.m_idx= 0;
	next_page(ptr.p, CONSUMER);
	consumer = ptr.p->m_pos[CONSUMER];
      }
      else
      {
	jam();
	/**
	 * Failed to write entire chunk...
	 */
	full= true;
	consumer.m_current_page.m_idx -= cnt;
	consumer.m_current_pos.m_ptr_i += cnt;
      }
    }

    tot += cnt;
    if(cnt)
      validate_logfile_group(ptr, " after flush_log");
  }

  ptr.p->m_pos[CONSUMER]= consumer;
  
  if (! (ptr.p->m_state & Logfile_group::LG_DROPPING))
  {
    signal->theData[0] = LgmanContinueB::FLUSH_LOG;
    signal->theData[1] = ptr.i;
    signal->theData[2] = 0;
    sendSignal(reference(), GSN_CONTINUEB, signal, 3, JBB);
  }
  else
  {
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_FLUSH_THREAD;
  }
}

/*
 * Overloaded UNDO buffer creates waiters for buffer space.
 * As in direct return from Logfile_client::get_log_buffer()
 * the FREE_BUFFER_MARGIN allows for a possible NOOP entry
 * when the logged entry does not fit on current page.
 *
 * In non-MT case the entry is added in same time-slice.
 * In MT case callback is via signals.  Here the problem is
 * that we cannot account for multiple NOOP entries created
 * in non-deterministic order.  So we serialize processing
 * to one entry at a time via CALLBACK_ACK signals.  This all
 * happens in memory at commit and should not have major impact.
 */
void
Lgman::process_log_buffer_waiters(Signal* signal, Ptr<Logfile_group> ptr)
{
  Uint32 free_buffer= ptr.p->m_free_buffer_words;
  Uint32 callback_buffer = ptr.p->m_callback_buffer_words;
  Local_log_waiter_list 
    list(m_log_waiter_pool, ptr.p->m_log_buffer_waiters);

  if (list.isEmpty())
  {
    jam();
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_WAITERS_THREAD;
    return;
  }
  
  bool removed= false;
  Ptr<Log_waiter> waiter;
  list.first(waiter);
  Uint32 sz  = waiter.p->m_size;
  Uint32 logfile_group_id = ptr.p->m_logfile_group_id;
  if (sz + callback_buffer + FREE_BUFFER_MARGIN < free_buffer)
  {
    jam();
    removed= true;
    Uint32 block = waiter.p->m_block;
    CallbackPtr & callback = waiter.p->m_callback;
    ptr.p->m_callback_buffer_words += sz;
    sendCallbackConf(signal, block, callback, logfile_group_id,
                     LgmanContinueB::PROCESS_LOG_BUFFER_WAITERS, 0);

    list.releaseFirst(/* waiter */);
  }
  
  if (removed && !list.isEmpty())
  {
    jam();
    ptr.p->m_state |= Logfile_group::LG_WAITERS_THREAD;
    // continue via CALLBACK_ACK
  }
  else
  {
    jam();
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_WAITERS_THREAD;
  }
}

void
Lgman::execCALLBACK_ACK(Signal* signal)
{
  BlockReference senderRef = signal->getSendersBlockRef();
  BlockNumber senderBlock = refToMain(senderRef);

  const CallbackAck* ack = (const CallbackAck*)signal->getDataPtr();
  Uint32 logfile_group_id = ack->senderData;
  Uint32 callbackInfo = ack->callbackInfo;

  Ptr<Logfile_group> ptr;
  ndbrequire(m_logfile_group_hash.find(ptr, logfile_group_id));

  // using ContinueB as convenience

  switch (callbackInfo) {
  case LgmanContinueB::PROCESS_LOG_BUFFER_WAITERS:
    jam();
    ndbrequire(senderBlock == DBTUP || senderBlock == LGMAN);
    break;
  // no PROCESS_LOG_SYNC_WAITERS yet (or ever)
  default:
    ndbrequire(false);
    break;
  }

  signal->theData[0] = callbackInfo;
  signal->theData[1] = ptr.i;
  sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
}

/**
 * 512 => 512 * 32 kByte = 16 MByte
 * This means that we can have at most 16 MBytes of UNDO log pages
 * outstanding at any time. This number represents a compromise
 * between not having to search so far ahead in the UNDO log when
 * restarting and ensuring that we can get good throughput in
 * writing to the UNDO log.
 */
#define MAX_UNDO_PAGES_OUTSTANDING 512

Uint32
Lgman::write_log_pages(Signal* signal, Ptr<Logfile_group> ptr,
		       Uint32 pageId, Uint32 in_pages)
{
  assert(in_pages);
  Ptr<Undofile> filePtr;
  Buffer_idx head= ptr.p->m_file_pos[HEAD];
  Buffer_idx tail= ptr.p->m_file_pos[TAIL];
  m_file_pool.getPtr(filePtr, head.m_ptr_i);
  
  if(filePtr.p->m_online.m_outstanding > 0)
  {
    jam();
    return 0;
  }
  
  Uint32 sz= filePtr.p->m_file_size - 1; // skip zero
  Uint32 max, pages= in_pages;

  if(!(head.m_ptr_i == tail.m_ptr_i && head.m_idx < tail.m_idx))
  {
    max= sz - head.m_idx;
  }
  else
  {
    max= tail.m_idx - head.m_idx;
  }

  if (head.m_idx == 0)
  {
    /**
     * The first write in a file is always just one page. The reason is that
     * we want the restart logic to sort the UNDO log files to be easy. We
     * need to add a recommendation that the UNDO log should have at least
     * 2 files if possible and also to be of reasonably large size.
     *
     * If we allow larger writes of the first page we also need to adapt the
     * file sort logic during restarts to look further into the file before
     * concluding the file sort order of a file.
     */
    jam();
    pages = 1;
  }
  if (pages > MAX_UNDO_PAGES_OUTSTANDING)
  {
    /**
     * We will never write more than 16 MBytes per write. If more is
     * available it will have to wait until we come back after this
     * write. This is to ensure that we have a limit that can be used
     * to find the last written UNDO log page during restart.
     *
     * LGMAN files are opened with the OM_SYNC flag set, so each
     * FSWRITEREQ is translated into both a file system write and
     * a fsync call (unless using O_DIRECT when no fsync call is
     * needed). See above comment on O_DIRECT and why no fsync calls
     * are needed.
     */
    jam();
    pages = MAX_UNDO_PAGES_OUTSTANDING;
  }

  FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
  req->filePointer = filePtr.p->m_fd;
  req->userReference = reference();
  req->userPointer = filePtr.i;
  req->varIndex = 1+head.m_idx; // skip zero page
  req->numberOfPages = pages;
  req->data.pageData[0] = pageId;
  req->operationFlag = 0;
  FsReadWriteReq::setFormatFlag(req->operationFlag,
				FsReadWriteReq::fsFormatSharedPage);

  if(max > pages)
  {
    /**
     * The write will be entirely within the current file, just proceed with
     * the write and se states accordingly.
     */
    jam();
    max= pages;
    head.m_idx += max;
    ptr.p->m_file_pos[HEAD] = head;  

    sendSignal(NDBFS_REF, GSN_FSWRITEREQ, signal, 
               FsReadWriteReq::FixedLength + 1, JBA);

    ptr.p->m_outstanding_fs++;
    filePtr.p->m_online.m_outstanding = max;
    filePtr.p->m_state |= Undofile::FS_OUTSTANDING;

    File_formats::Undofile::Undo_page *page= (File_formats::Undofile::Undo_page*)
      m_shared_page_pool.getPtr(pageId + max - 1);
    Uint64 lsn = 0;
    lsn += page->m_page_header.m_page_lsn_hi; lsn <<= 32;
    lsn += page->m_page_header.m_page_lsn_lo;
    
    filePtr.p->m_online.m_lsn = lsn;  // Store last writereq lsn on file
    ptr.p->m_last_sync_req_lsn = lsn; // And logfile_group
  }
  else
  {
    /**
     * We need to write the last part of this UNDO log file, this includes
     * changing into a new file.
     */
    jam();
    req->numberOfPages = max;
    
    sendSignal(NDBFS_REF, GSN_FSWRITEREQ, signal, 
               FsReadWriteReq::FixedLength + 1, JBA);

    ptr.p->m_outstanding_fs++;
    filePtr.p->m_online.m_outstanding = max;
    filePtr.p->m_state |= Undofile::FS_OUTSTANDING;

    File_formats::Undofile::Undo_page *page= (File_formats::Undofile::Undo_page*)
      m_shared_page_pool.getPtr(pageId + max - 1);
    Uint64 lsn = 0;
    lsn += page->m_page_header.m_page_lsn_hi; lsn <<= 32;
    lsn += page->m_page_header.m_page_lsn_lo;
    
    filePtr.p->m_online.m_lsn = lsn;  // Store last writereq lsn on file
    ptr.p->m_last_sync_req_lsn = lsn; // And logfile_group
    
    Ptr<Undofile> next = filePtr;
    Local_undofile_list files(m_file_pool, ptr.p->m_files);
    if(!files.next(next))
    {
      jam();
      files.first(next);
    }
    SimulatedBlock* fs = globalData.getBlock(NDBFS);
    ndbout_c("LGMAN: changing file from %s to %s",
             fs->get_filename(filePtr.p->m_fd),
             fs->get_filename(next.p->m_fd));
    filePtr.p->m_state |= Undofile::FS_MOVE_NEXT;
    next.p->m_state &= ~(Uint32)Undofile::FS_EMPTY;

    head.m_idx= 0;
    head.m_ptr_i= next.i;
    ptr.p->m_file_pos[HEAD] = head;
    if(max < pages)
      max += write_log_pages(signal, ptr, pageId + max, pages - max);
  }
  
  assert(max);
  return max;
}

void
Lgman::execFSWRITEREF(Signal* signal)
{
  jamEntry();
  SimulatedBlock::execFSWRITEREF(signal);
  ndbrequire(false);
}

void
Lgman::execFSWRITECONF(Signal* signal)
{
  jamEntry();
  client_lock(number(), __LINE__);
  FsConf * conf = (FsConf*)signal->getDataPtr();
  Ptr<Undofile> ptr;
  m_file_pool.getPtr(ptr, conf->userPointer);

  ndbrequire(ptr.p->m_state & Undofile::FS_OUTSTANDING);
  ptr.p->m_state &= ~(Uint32)Undofile::FS_OUTSTANDING;

  Ptr<Logfile_group> lg_ptr;
  m_logfile_group_pool.getPtr(lg_ptr, ptr.p->m_logfile_group_ptr_i);
  
  Uint32 cnt= lg_ptr.p->m_outstanding_fs;
  ndbrequire(cnt);
  
  if(lg_ptr.p->m_next_reply_ptr_i == ptr.i)
  {
    Uint32 tot= 0;
    Uint64 lsn = 0;
    {
      Local_undofile_list files(m_file_pool, lg_ptr.p->m_files);
      while(cnt && ! (ptr.p->m_state & Undofile::FS_OUTSTANDING))
      {
	Uint32 state= ptr.p->m_state;
	Uint32 pages= ptr.p->m_online.m_outstanding;
	ndbrequire(pages);
	ptr.p->m_online.m_outstanding= 0;
	ptr.p->m_state &= ~(Uint32)Undofile::FS_MOVE_NEXT;
	tot += pages;
	cnt--;
	
	lsn = ptr.p->m_online.m_lsn;
	
	if((state & Undofile::FS_MOVE_NEXT) && !files.next(ptr))
	  files.first(ptr);
      }
    }
    
    ndbassert(tot);
    lg_ptr.p->m_outstanding_fs = cnt;
    lg_ptr.p->m_free_buffer_words += (tot * File_formats::UNDO_PAGE_WORDS);
    lg_ptr.p->m_next_reply_ptr_i = ptr.i;
    lg_ptr.p->m_last_synced_lsn = lsn;

    if(! (lg_ptr.p->m_state & Logfile_group::LG_SYNC_WAITERS_THREAD))
    {
      process_log_sync_waiters(signal, lg_ptr);
    }

    if(! (lg_ptr.p->m_state & Logfile_group::LG_WAITERS_THREAD))
    {
      process_log_buffer_waiters(signal, lg_ptr);
    }
  }
  else
  {
    ndbout_c("miss matched writes");
  }
  client_unlock(number(), __LINE__);
  
  return;
}

void
Lgman::exec_lcp_frag_ord(Signal* signal, SimulatedBlock* client_block)
{
  jamBlock(client_block);

  LcpFragOrd * ord = (LcpFragOrd *)signal->getDataPtr();
  Uint32 lcp_id= ord->lcpId;
  Uint32 frag_id = ord->fragmentId;
  Uint32 table_id = ord->tableId;

  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);
  
  Uint32 entry= lcp_id == m_latest_lcp ? 
    File_formats::Undofile::UNDO_LCP : File_formats::Undofile::UNDO_LCP_FIRST;
  if(!ptr.isNull() && ! (ptr.p->m_state & Logfile_group::LG_CUT_LOG_THREAD))
  {
    jamBlock(client_block);
    ptr.p->m_state |= Logfile_group::LG_CUT_LOG_THREAD;
    signal->theData[0] = LgmanContinueB::CUT_LOG_TAIL;
    signal->theData[1] = ptr.i;
    client_block->sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
  }
  
  if(!ptr.isNull() && ptr.p->m_next_lsn)
  {
    Uint32 undo[3];
    undo[0] = lcp_id;
    undo[1] = (table_id << 16) | frag_id;
    undo[2] = (entry << 16 ) | (sizeof(undo) >> 2);

    Uint64 next_lsn= m_next_lsn;
    
    if(ptr.p->m_next_lsn == next_lsn
#ifdef VM_TRACE
       && ((rand() % 100) > 50)
#endif
       )
    {
      undo[2] |= (File_formats::Undofile::UNDO_NEXT_LSN << 16);
      Uint32 *dst= get_log_buffer(ptr, sizeof(undo) >> 2);
      memcpy(dst, undo, sizeof(undo));
      ndbrequire(ptr.p->m_free_file_words >= (sizeof(undo) >> 2));
      ptr.p->m_free_file_words -= (sizeof(undo) >> 2);
    }
    else
    {
      Uint32 *dst= get_log_buffer(ptr, (sizeof(undo) >> 2) + 2);      
      * dst++ = (Uint32)(next_lsn >> 32);
      * dst++ = (Uint32)(next_lsn & 0xFFFFFFFF);
      memcpy(dst, undo, sizeof(undo));
      ndbrequire(ptr.p->m_free_file_words >= (sizeof(undo) >> 2));
      ptr.p->m_free_file_words -= ((sizeof(undo) >> 2) + 2);
    }
    ptr.p->m_last_lcp_lsn = next_lsn;
    m_next_lsn = ptr.p->m_next_lsn = next_lsn + 1;

    validate_logfile_group(ptr, "execLCP_FRAG_ORD");
  }
  
  while(!ptr.isNull())
  {
    if (ptr.p->m_next_lsn)
    {
      /**
       * First LCP_FRAGORD for each LCP, sets tail pos
       */
      if(m_latest_lcp != lcp_id)
      {
	ptr.p->m_tail_pos[0] = ptr.p->m_tail_pos[1];
	ptr.p->m_tail_pos[1] = ptr.p->m_tail_pos[2];
	ptr.p->m_tail_pos[2] = ptr.p->m_file_pos[HEAD];
      }
      
      if(0)
	ndbout_c
	  ("execLCP_FRAG_ORD (%d %d) (%d %d) (%d %d) free pages: %ld", 
	   ptr.p->m_tail_pos[0].m_ptr_i, ptr.p->m_tail_pos[0].m_idx,
	   ptr.p->m_tail_pos[1].m_ptr_i, ptr.p->m_tail_pos[1].m_idx,
	   ptr.p->m_tail_pos[2].m_ptr_i, ptr.p->m_tail_pos[2].m_idx,
	   (long) (ptr.p->m_free_file_words / File_formats::UNDO_PAGE_WORDS));
    }
    m_logfile_group_list.next(ptr);
  }
  
  m_latest_lcp = lcp_id;
}

void
Lgman::execEND_LCP_REQ(Signal* signal)
{
  EndLcpReq* req= (EndLcpReq*)signal->getDataPtr();
  ndbrequire(m_latest_lcp == req->backupId);
  m_end_lcp_senderdata = req->senderData;

  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);
  bool wait= false;
  while(!ptr.isNull())
  {
    Uint64 lcp_lsn = ptr.p->m_last_lcp_lsn;
    if(ptr.p->m_last_synced_lsn < lcp_lsn)
    {
      wait= true;
      if(signal->getSendersBlockRef() != reference())
      {
        D("Logfile_client - execEND_LCP_REQ");
	Logfile_client tmp(this, this, ptr.p->m_logfile_group_id);
	Logfile_client::Request req;
	req.m_callback.m_callbackData = ptr.i;
	req.m_callback.m_callbackIndex = ENDLCP_CALLBACK;
	ndbrequire(tmp.sync_lsn(signal, lcp_lsn, &req, 0) == 0);
      }
    }
    else
    {
      ptr.p->m_last_lcp_lsn = 0;
    }
    m_logfile_group_list.next(ptr);
  }
  
  if(wait)
  {
    return;
  }

  EndLcpConf* conf = (EndLcpConf*)signal->getDataPtrSend();
  conf->senderData = m_end_lcp_senderdata;
  conf->senderRef = reference();
  sendSignal(DBLQH_REF, GSN_END_LCP_CONF,
             signal, EndLcpConf::SignalLength, JBB);
}

void
Lgman::endlcp_callback(Signal* signal, Uint32 ptr, Uint32 res)
{
  EndLcpReq* req= (EndLcpReq*)signal->getDataPtr();
  req->backupId = m_latest_lcp;
  req->senderData = m_end_lcp_senderdata;
  execEND_LCP_REQ(signal);
}

void
Lgman::cut_log_tail(Signal* signal, Ptr<Logfile_group> ptr)
{
  bool done= true;
  if (likely(ptr.p->m_next_lsn))
  {
    Buffer_idx tmp= ptr.p->m_tail_pos[0];
    Buffer_idx tail= ptr.p->m_file_pos[TAIL];
    
    Ptr<Undofile> filePtr;
    m_file_pool.getPtr(filePtr, tail.m_ptr_i);
    
    if(!(tmp == tail))
    {
      Uint32 free;
      if(tmp.m_ptr_i == tail.m_ptr_i && tail.m_idx < tmp.m_idx)
      {
	free= tmp.m_idx - tail.m_idx; 
	ptr.p->m_free_file_words += free * File_formats::UNDO_PAGE_WORDS;
	ptr.p->m_file_pos[TAIL] = tmp;
      }
      else
      {
	free= filePtr.p->m_file_size - tail.m_idx - 1;
	ptr.p->m_free_file_words += free * File_formats::UNDO_PAGE_WORDS;
	
	Ptr<Undofile> next = filePtr;
	Local_undofile_list files(m_file_pool, ptr.p->m_files);
	while(files.next(next) && (next.p->m_state & Undofile::FS_EMPTY))
	  ndbrequire(next.i != filePtr.i);
	if(next.isNull())
	{
	  jam();
	  files.first(next);
	  while((next.p->m_state & Undofile::FS_EMPTY) && files.next(next))
	    ndbrequire(next.i != filePtr.i);
	}
	
	tmp.m_idx= 0;
	tmp.m_ptr_i= next.i;
	ptr.p->m_file_pos[TAIL] = tmp;
	done= false;      
      }
    } 
    
    validate_logfile_group(ptr, "cut log");
  }
  
  if (done)
  {
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_CUT_LOG_THREAD;
    m_logfile_group_list.next(ptr); 
  }
  
  if(!done || !ptr.isNull())
  {
    ptr.p->m_state |= Logfile_group::LG_CUT_LOG_THREAD;
    signal->theData[0] = LgmanContinueB::CUT_LOG_TAIL;
    signal->theData[1] = ptr.i;
    sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
  }
}

void
Lgman::execSUB_GCP_COMPLETE_REP(Signal* signal)
{
  jamEntry();

  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);

  /**
   * Filter all logfile groups in parallell
   */
  return; // NOT IMPLETMENT YET
  
  signal->theData[0] = LgmanContinueB::FILTER_LOG;
  while(!ptr.isNull())
  {
    signal->theData[1] = ptr.i;
    sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
    m_logfile_group_list.next(ptr);
  }
}

int
Lgman::alloc_log_space(Uint32 ref, Uint32 words)
{
  ndbrequire(words);
  words += 2; // lsn
  Logfile_group key;
  key.m_logfile_group_id= ref;
  Ptr<Logfile_group> ptr;
  if(m_logfile_group_hash.find(ptr, key) && 
     ptr.p->m_free_file_words >= (words + (4 * File_formats::UNDO_PAGE_WORDS)))
  {
    ptr.p->m_free_file_words -= words;
    validate_logfile_group(ptr, "alloc_log_space");
    return 0;
  }
  
  if(ptr.isNull())
  {
    return -1;
  }
  
  return 1501;
}

int
Lgman::free_log_space(Uint32 ref, Uint32 words)
{
  ndbrequire(words);
  Logfile_group key;
  key.m_logfile_group_id= ref;
  Ptr<Logfile_group> ptr;
  if(m_logfile_group_hash.find(ptr, key))
  {
    ptr.p->m_free_file_words += (words + 2);
    validate_logfile_group(ptr, "free_log_space");
    return 0;
  }
  ndbrequire(false);
  return -1;
}

Uint64
Logfile_client::add_entry(const Change* src, Uint32 cnt)
{
  Uint32 i, tot= 0;
  for(i= 0; i<cnt; i++)
  {
    tot += src[i].len;
  }
  
  Uint32 *dst;
  Uint64 next_lsn= m_lgman->m_next_lsn;
  {
    Lgman::Logfile_group key;
    key.m_logfile_group_id= m_logfile_group_id;
    Ptr<Lgman::Logfile_group> ptr;
    if(m_lgman->m_logfile_group_hash.find(ptr, key))
    {
      Uint32 callback_buffer = ptr.p->m_callback_buffer_words;
      Uint64 next_lsn_filegroup= ptr.p->m_next_lsn;
      if(next_lsn_filegroup == next_lsn
#ifdef VM_TRACE
	 && ((rand() % 100) > 50)
#endif
	 )
      {
	dst= m_lgman->get_log_buffer(ptr, tot);
	for(i= 0; i<cnt; i++)
	{
	  memcpy(dst, src[i].ptr, 4*src[i].len);
	  dst += src[i].len;
	}
	* (dst - 1) |= (File_formats::Undofile::UNDO_NEXT_LSN << 16);
	ptr.p->m_free_file_words += 2;
	m_lgman->validate_logfile_group(ptr);
      }
      else
      {
	dst= m_lgman->get_log_buffer(ptr, tot + 2);
	* dst++ = (Uint32)(next_lsn >> 32);
	* dst++ = (Uint32)(next_lsn & 0xFFFFFFFF);
	for(i= 0; i<cnt; i++)
	{
	  memcpy(dst, src[i].ptr, 4*src[i].len);
	  dst += src[i].len;
	}
      }
      /**
       * for callback_buffer, always allocats 2 extra...
       *   not knowing if LSN must be added or not
       */
      tot += 2;

      if (unlikely(! (tot <= callback_buffer)))
      {
        abort();
      }
      ptr.p->m_callback_buffer_words = callback_buffer - tot;
    }
    
    m_lgman->m_next_lsn = ptr.p->m_next_lsn = next_lsn + 1;
    
    return next_lsn;
  }
}

void
Lgman::execSTART_RECREQ(Signal* signal)
{
  m_latest_lcp = signal->theData[0];
  
  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);

  if(ptr.i != RNIL)
  {
    infoEvent("LGMAN: Applying undo to LCP: %d", m_latest_lcp);
    ndbout_c("Applying undo to LCP: %d", m_latest_lcp);
    find_log_head(signal, ptr);
    return;
  }
  
  signal->theData[0] = reference();
  sendSignal(DBLQH_REF, GSN_START_RECCONF, signal, 1, JBB);
}

void
Lgman::find_log_head(Signal* signal, Ptr<Logfile_group> ptr)
{
  ndbrequire(ptr.p->m_state & 
             (Logfile_group::LG_STARTING | Logfile_group::LG_SORTING));

  if(ptr.p->m_meta_files.isEmpty() && ptr.p->m_files.isEmpty())
  {
    jam();
    /**
     * Logfile_group wo/ any files 
     */
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_STARTING;
    ptr.p->m_state |= Logfile_group::LG_ONLINE;
    m_logfile_group_list.next(ptr);
    signal->theData[0] = LgmanContinueB::FIND_LOG_HEAD;
    signal->theData[1] = ptr.i;
    sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
    return;
  }

  ptr.p->m_state = Logfile_group::LG_SORTING;
  
  /**
   * Read first page from each undofile (1 file at a time...)
   */
  Local_undofile_list files(m_file_pool, ptr.p->m_meta_files);
  Ptr<Undofile> file_ptr;
  files.first(file_ptr);
  
  if(!file_ptr.isNull())
  {
    /**
     * Use log buffer memory when reading
     */
    Uint32 page_id = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
    file_ptr.p->m_online.m_outstanding= page_id;
    
    FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
    req->filePointer = file_ptr.p->m_fd;
    req->userReference = reference();
    req->userPointer = file_ptr.i;
    req->varIndex = 1; // skip zero page
    req->numberOfPages = 1;
    req->data.pageData[0] = page_id;
    req->operationFlag = 0;
    FsReadWriteReq::setFormatFlag(req->operationFlag,
				  FsReadWriteReq::fsFormatSharedPage);
    
    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal,
	       FsReadWriteReq::FixedLength + 1, JBA);

    ptr.p->m_outstanding_fs++;
    file_ptr.p->m_state |= Undofile::FS_OUTSTANDING;
    return;
  }
  else
  {
    /**
     * All files have read first page
     *   and m_files is sorted acording to lsn
     */
    ndbrequire(!ptr.p->m_files.isEmpty());
    Local_undofile_list read_files(m_file_pool, ptr.p->m_files);
    read_files.last(file_ptr);
    

    /**
     * Init binary search
     */
    ptr.p->m_state = Logfile_group::LG_SEARCHING;
    file_ptr.p->m_state = Undofile::FS_SEARCHING;
    ptr.p->m_file_pos[TAIL].m_idx = 1;                   // left page
    ptr.p->m_file_pos[HEAD].m_idx = file_ptr.p->m_file_size;
    ptr.p->m_file_pos[HEAD].m_ptr_i = ((file_ptr.p->m_file_size - 1) >> 1) + 1;
    
    Uint32 page_id = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
    file_ptr.p->m_online.m_outstanding= page_id;

    FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
    req->filePointer = file_ptr.p->m_fd;
    req->userReference = reference();
    req->userPointer = file_ptr.i;
    req->varIndex = ptr.p->m_file_pos[HEAD].m_ptr_i;
    req->numberOfPages = 1;
    req->data.pageData[0] = page_id;
    req->operationFlag = 0;
    FsReadWriteReq::setFormatFlag(req->operationFlag,
				  FsReadWriteReq::fsFormatSharedPage);
    
    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal, 
	       FsReadWriteReq::FixedLength + 1, JBA);
    
    ptr.p->m_outstanding_fs++;
    file_ptr.p->m_state |= Undofile::FS_OUTSTANDING;
    return;
  }
}

void
Lgman::execFSREADCONF(Signal* signal)
{
  jamEntry();
  client_lock(number(), __LINE__);

  Ptr<Undofile> file_ptr;
  Ptr<Logfile_group> lg_ptr;
  FsConf* conf = (FsConf*)signal->getDataPtr();
  
  m_file_pool.getPtr(file_ptr, conf->userPointer);
  m_logfile_group_pool.getPtr(lg_ptr, file_ptr.p->m_logfile_group_ptr_i);

  ndbrequire(file_ptr.p->m_state & Undofile::FS_OUTSTANDING);
  file_ptr.p->m_state &= ~(Uint32)Undofile::FS_OUTSTANDING;
  
  Uint32 cnt= lg_ptr.p->m_outstanding_fs;
  ndbrequire(cnt);
  
  if((file_ptr.p->m_state & Undofile::FS_EXECUTING)== Undofile::FS_EXECUTING)
  {
    jam();
    
    if(lg_ptr.p->m_next_reply_ptr_i == file_ptr.i)
    {
      Uint32 tot= 0;
      Local_undofile_list files(m_file_pool, lg_ptr.p->m_files);
      while(cnt && ! (file_ptr.p->m_state & Undofile::FS_OUTSTANDING))
      {
	Uint32 state= file_ptr.p->m_state;
	Uint32 pages= file_ptr.p->m_online.m_outstanding;
	ndbrequire(pages);
	file_ptr.p->m_online.m_outstanding= 0;
	file_ptr.p->m_state &= ~(Uint32)Undofile::FS_MOVE_NEXT;
	tot += pages;
	cnt--;
	
	if((state & Undofile::FS_MOVE_NEXT) && !files.prev(file_ptr))
	  files.last(file_ptr);
      }
      
      lg_ptr.p->m_outstanding_fs = cnt;
      lg_ptr.p->m_pos[PRODUCER].m_current_pos.m_idx += tot;
      lg_ptr.p->m_next_reply_ptr_i = file_ptr.i;
    }
    client_unlock(number(), __LINE__);
    return;
  }
  
  lg_ptr.p->m_outstanding_fs = cnt - 1;

  Ptr<GlobalPage> page_ptr;
  m_shared_page_pool.getPtr(page_ptr, file_ptr.p->m_online.m_outstanding);
  file_ptr.p->m_online.m_outstanding= 0;
  
  File_formats::Undofile::Undo_page* page = 
    (File_formats::Undofile::Undo_page*)page_ptr.p;
  
  Uint64 lsn = 0;
  lsn += page->m_page_header.m_page_lsn_hi; lsn <<= 32;
  lsn += page->m_page_header.m_page_lsn_lo;

  switch(file_ptr.p->m_state){
  case Undofile::FS_SORTING:
    jam();
    break;
  case Undofile::FS_SEARCHING:
    jam();
    find_log_head_in_file(signal, lg_ptr, file_ptr, lsn);
    client_unlock(number(), __LINE__);
    return;
  case Undofile::FS_SEARCHING_END:
    jam();
    find_log_head_end_check(signal, lg_ptr, file_ptr, lsn);
    client_unlock(number(), __LINE__);
    return;
  case Undofile::FS_SEARCHING_FINAL_READ:
    jam();
    find_log_head_complete(signal, lg_ptr, file_ptr);
    client_unlock(number(), __LINE__);
    return;
  default:
  case Undofile::FS_EXECUTING:
  case Undofile::FS_CREATING:
  case Undofile::FS_DROPPING:
  case Undofile::FS_ONLINE:
  case Undofile::FS_OPENING:
  case Undofile::FS_EMPTY:
    jam();
    ndbrequire(false);
  }

  /**
   * Prepare for execution
   */
  file_ptr.p->m_state = Undofile::FS_EXECUTING;
  file_ptr.p->m_online.m_lsn = lsn;
  file_ptr.p->m_start_lsn = lsn;
  
  /**
   * Insert into m_files
   */
  {
    Local_undofile_list meta(m_file_pool, lg_ptr.p->m_meta_files);  
    Local_undofile_list files(m_file_pool, lg_ptr.p->m_files);
    meta.remove(file_ptr);

    Ptr<Undofile> loop;  
    files.first(loop);
    while(!loop.isNull() && loop.p->m_online.m_lsn <= lsn)
      files.next(loop);
    
    if(loop.isNull())
    {
      /**
       * File has highest lsn, add last
       */
      jam();
      files.addLast(file_ptr);
    }
    else
    {
      /**
       * Insert file in correct position in file list
       */
      files.insertBefore(file_ptr, loop);
    }
  }
  find_log_head(signal, lg_ptr);
  client_unlock(number(), __LINE__);
}
  
void
Lgman::execFSREADREF(Signal* signal)
{
  jamEntry();
  SimulatedBlock::execFSREADREF(signal);
  ndbrequire(false);
}

/**
 * We're performing a binary search to find the end of the UNDO log.
 * We're comparing with the LSN number found so far in the file.
 * At start we know the LSN of the first page, so any pages with
 * larger LSN have been written after this page, so when the page
 * LSN is greater than the highest found so far, then we move forward
 * in the binary search, otherwise we will move backwards.
 */
void
Lgman::find_log_head_in_file(Signal* signal, 
                             Ptr<Logfile_group> ptr, 
                             Ptr<Undofile> file_ptr,
                             Uint64 last_lsn)
{ 
  Uint32 curr= ptr.p->m_file_pos[HEAD].m_ptr_i;
  Uint32 head= ptr.p->m_file_pos[HEAD].m_idx;
  Uint32 tail= ptr.p->m_file_pos[TAIL].m_idx;

  ndbrequire(head > tail);
  Uint32 diff = head - tail;
  
  if(DEBUG_SEARCH_LOG_HEAD)
    printf("tail: %d(%lld) head: %d last: %d(%lld) -> ", 
	   tail, file_ptr.p->m_online.m_lsn,
	   head, curr, last_lsn);
  if(last_lsn > file_ptr.p->m_online.m_lsn)
  {
    /**
     * Move forward in binary search since page LSN is higher than the largest
     * LSN found so far.
     */
    if(DEBUG_SEARCH_LOG_HEAD)
      printf("moving tail ");
    
    file_ptr.p->m_online.m_lsn = last_lsn;
    ptr.p->m_file_pos[TAIL].m_idx = tail = curr;
  }
  else
  {
    /**
     * A page with lower LSN than the highest is found, this means that the
     * page wasn't written in this log lap. This means that we're now close
     * to the end of the UNDO log. We'll continue the binary search to the
     * left in the log file.
     */
    if(DEBUG_SEARCH_LOG_HEAD)
      printf("moving head ");

    ptr.p->m_file_pos[HEAD].m_idx = head = curr;
  }
  
  if(diff > 1)
  {
    // We need to find more pages to be sure...
    ptr.p->m_file_pos[HEAD].m_ptr_i = curr = ((head + tail) >> 1);

    if(DEBUG_SEARCH_LOG_HEAD)    
      ndbout_c("-> new search tail: %d(%lld) head: %d -> %d", 
	       tail, file_ptr.p->m_online.m_lsn,
	       head, curr);

    Uint32 page_id = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
    file_ptr.p->m_online.m_outstanding= page_id;
    
    FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
    req->filePointer = file_ptr.p->m_fd;
    req->userReference = reference();
    req->userPointer = file_ptr.i;
    req->varIndex = curr;
    req->numberOfPages = 1;
    req->data.pageData[0] = page_id;
    req->operationFlag = 0;
    FsReadWriteReq::setFormatFlag(req->operationFlag,
				  FsReadWriteReq::fsFormatSharedPage);
    
    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal, 
	       FsReadWriteReq::FixedLength + 1, JBA);
    
    ptr.p->m_outstanding_fs++;
    file_ptr.p->m_state |= Undofile::FS_OUTSTANDING;
    return;
  }
  
  ndbrequire(diff == 1);
  /**
   * We have found the end of the UNDO log through a binary search of the
   * UNDO log pages. There can still be pages up to 16 MByte ahead of us.
   * We will look for more pages up to 16 MBytes ahead or until we find the
   * end of the file.
   *
   * The reason is that we synch up to 16 MByte at a time to the
   * UNDO log, so this means that we must take into account the
   * case that the UNDO log is not written consecutively at the
   * end.
   *
   * We want to find the last page which have been written to
   * avoid weirdness later when applying the UNDO log. We will
   * keep track that no records are applied until we have executed
   * backwards until we have found this end since it is impossible
   * that a log record is applied if it lies in this region of
   * written/unwritten log pages. This is because of that we
   * use the WAL protocol to write pages to disk.
   */

  if(DEBUG_SEARCH_LOG_HEAD)    
    ndbout_c("-> found last page in binary search: %d", tail);

  /**
   * m_next_lsn indicates next LSN to write, so we step this forward one
   * step to ensure that we don't write one more record with the same
   * LSN number.
   */
  m_next_lsn = ptr.p->m_next_lsn = (file_ptr.p->m_online.m_lsn + 1);
  ptr.p->m_last_read_lsn = file_ptr.p->m_online.m_lsn;
  ptr.p->m_last_synced_lsn = file_ptr.p->m_online.m_lsn;
  
  /**
   * Set HEAD and TAIL position to use when we start logging again.
   * We might have to change those during the check of the end of
   * the log file search check. But we won't change the file, only
   * end page index.
   */
  ptr.p->m_file_pos[HEAD].m_ptr_i = file_ptr.i;
  ptr.p->m_file_pos[HEAD].m_idx = tail;
  
  ptr.p->m_file_pos[TAIL].m_ptr_i = file_ptr.i;
  ptr.p->m_file_pos[TAIL].m_idx = tail - 1;
  ptr.p->m_next_reply_ptr_i = file_ptr.i;


  file_ptr.p->m_state = Undofile::FS_SEARCHING_END;

  file_ptr.p->m_online.m_current_scan_index = curr;
  file_ptr.p->m_online.m_current_scanned_pages = 0;
  file_ptr.p->m_online.m_binary_search_end = true;
  find_log_head_end_check(signal, ptr, file_ptr, last_lsn);
}

void
Lgman::find_log_head_end_check(Signal* signal,
                               Ptr<Logfile_group> ptr,
                               Ptr<Undofile> file_ptr,
                               Uint64 last_lsn)
{
  Uint32 curr = file_ptr.p->m_online.m_current_scan_index;
  Uint32 scanned_pages = file_ptr.p->m_online.m_current_scanned_pages;

  if(last_lsn > file_ptr.p->m_online.m_lsn)
  {
    /**
     * We did actually find a written page after the end which the binary
     * search found. We need to record this as the new end of the UNDO
     * log.
     */
    jam();
    if (file_ptr.p->m_online.m_binary_search_end)
    {
      jam();
      file_ptr.p->m_online.m_binary_search_end = false;
      g_eventLogger->info("LGMAN: Found written page after end found by binary"
                          " search, binary search head found: %u",
                          ptr.p->m_file_pos[HEAD].m_idx);
    }
    ptr.p->m_file_pos[HEAD].m_idx = curr;
    ptr.p->m_file_pos[TAIL].m_idx = curr - 1;

    /**
     * m_next_lsn indicates next LSN to write, so we step this forward one
     * step to ensure that we don't write one more record with the same
     * LSN number.
     */
    m_next_lsn = ptr.p->m_next_lsn = (file_ptr.p->m_online.m_lsn + 1);
    ptr.p->m_last_read_lsn = file_ptr.p->m_online.m_lsn;
    ptr.p->m_last_synced_lsn = file_ptr.p->m_online.m_lsn;
  }

  curr++;
  scanned_pages++;
  file_ptr.p->m_online.m_current_scan_index = curr;
  file_ptr.p->m_online.m_current_scanned_pages = scanned_pages;
  if ((curr < file_ptr.p->m_file_size) &&
      (scanned_pages <= MAX_UNDO_PAGES_OUTSTANDING))
  {
    jam();
    Uint32 page_id = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
    file_ptr.p->m_online.m_outstanding= page_id;

    FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
    req->filePointer = file_ptr.p->m_fd;
    req->userReference = reference();
    req->userPointer = file_ptr.i;
    req->varIndex = curr;
    req->numberOfPages = 1;
    req->data.pageData[0] = page_id;
    req->operationFlag = 0;
    FsReadWriteReq::setFormatFlag(req->operationFlag,
                                  FsReadWriteReq::fsFormatSharedPage);

    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal,
               FsReadWriteReq::FixedLength + 1, JBA);

    ptr.p->m_outstanding_fs++;
    file_ptr.p->m_state |= Undofile::FS_OUTSTANDING;
    return;
  }
  jam();

  /**
   * Now we are done with the search for the end. However when starting to
   * execute the UNDO log we expect the first page to be already read. So
   * we reread the last page in the UNDO log that we found.
   */
  Uint32 page_id = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
  file_ptr.p->m_online.m_outstanding= page_id;
  curr = ptr.p->m_file_pos[HEAD].m_idx;

  FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
  req->filePointer = file_ptr.p->m_fd;
  req->userReference = reference();
  req->userPointer = file_ptr.i;
  req->varIndex = curr;
  req->numberOfPages = 1;
  req->data.pageData[0] = page_id;
  req->operationFlag = 0;
  FsReadWriteReq::setFormatFlag(req->operationFlag,
                                FsReadWriteReq::fsFormatSharedPage);

  sendSignal(NDBFS_REF, GSN_FSREADREQ, signal,
             FsReadWriteReq::FixedLength + 1, JBA);

  ptr.p->m_outstanding_fs++;
  file_ptr.p->m_state = Undofile::FS_SEARCHING_FINAL_READ;
  file_ptr.p->m_state |= Undofile::FS_OUTSTANDING;
  return;
}

void
Lgman::find_log_head_complete(Signal *signal,
                              Ptr<Logfile_group> ptr,
                              Ptr<Undofile> file_ptr)
{
  Uint32 head = ptr.p->m_file_pos[HEAD].m_idx;
  /**
   * END_OF_UNDO_LOG_FOUND
   *
   * We have found the end of the UNDO log. This is the position
   * from which we will start the UNDO execution and the position
   * from which we later will start adding new UNDO log records.
   * We have just reread the last UNDO log page, so we are ready
   * to start executing the UNDO log.
   */
  ptr.p->m_state = 0;
  file_ptr.p->m_state = Undofile::FS_EXECUTING;

  {
    Local_undofile_list files(m_file_pool, ptr.p->m_files);
    if(head == 1)
    {
      /**
       * HEAD is first page in a file...
       *   -> TAIL should be last page in previous file
       */
      Ptr<Undofile> prev = file_ptr;
      if(!files.prev(prev))
      {
	files.last(prev);
      }
      ptr.p->m_file_pos[TAIL].m_ptr_i = prev.i;
      ptr.p->m_file_pos[TAIL].m_idx = prev.p->m_file_size - 1;
      ptr.p->m_next_reply_ptr_i = prev.i;
    }
    
    SimulatedBlock* fs = globalData.getBlock(NDBFS);
    infoEvent("LGMAN: Undo head - %s page: %d lsn: %lld",
	      fs->get_filename(file_ptr.p->m_fd), 
	      head, file_ptr.p->m_online.m_lsn);
    g_eventLogger->info("LGMAN: Undo head - %s page: %d lsn: %lld",
                        fs->get_filename(file_ptr.p->m_fd),
                        head, file_ptr.p->m_online.m_lsn);
    
    for(files.prev(file_ptr); !file_ptr.isNull(); files.prev(file_ptr))
    {
      infoEvent("   - next - %s(%lld)", 
		fs->get_filename(file_ptr.p->m_fd), 
		file_ptr.p->m_online.m_lsn);

      g_eventLogger->info("   - next - %s(%lld)", 
                          fs->get_filename(file_ptr.p->m_fd),
                          file_ptr.p->m_online.m_lsn);
    }
  }
  
  /**
   * Start next logfile group
   */
  m_logfile_group_list.next(ptr);
  signal->theData[0] = LgmanContinueB::FIND_LOG_HEAD;
  signal->theData[1] = ptr.i;
  sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
}

void
Lgman::init_run_undo_log(Signal* signal)
{
  /**
   * Perform initial sorting of logfile groups
   */
  Ptr<Logfile_group> group;
  Logfile_group_list& list= m_logfile_group_list;
  Logfile_group_list::Head tmpHead;
  bool found_any = false;
  {
    Logfile_group_list::Local tmp(m_logfile_group_pool, tmpHead);


    list.first(group);
    while (!group.isNull())
    {
      Ptr<Logfile_group> ptr= group;
      list.next(group);
      list.remove(ptr);

      if (ptr.p->m_state & Logfile_group::LG_ONLINE)
      {
        /**
         * No logfiles in group
         */
        jam();
        tmp.addLast(ptr);
        continue;
      }

      found_any = true;

      {
        /**
         * Init buffer pointers
         */
        ptr.p->m_free_buffer_words -= File_formats::UNDO_PAGE_WORDS;
        ptr.p->m_pos[CONSUMER].m_current_page.m_idx = 0; // 0 more pages read
        ptr.p->m_pos[PRODUCER].m_current_page.m_idx = 0; // 0 more pages read

        Uint32 page = ptr.p->m_pos[CONSUMER].m_current_pos.m_ptr_i;
        File_formats::Undofile::Undo_page* pageP =
          (File_formats::Undofile::Undo_page*)m_shared_page_pool.getPtr(page);

        ptr.p->m_pos[CONSUMER].m_current_pos.m_idx = pageP->m_words_used;
        ptr.p->m_pos[PRODUCER].m_current_pos.m_idx = 1;
        ptr.p->m_last_read_lsn++;

        ptr.p->m_consumer_file_pos = ptr.p->m_file_pos[HEAD];
      }

      /**
       * Start producer thread
       */
      signal->theData[0] = LgmanContinueB::READ_UNDO_LOG;
      signal->theData[1] = ptr.i;
      sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
    
      /**
       * Insert in correct position in list of logfile_group's
       */
      Ptr<Logfile_group> pos;
      for (tmp.first(pos); !pos.isNull(); tmp.next(pos))
        if (ptr.p->m_last_read_lsn >= pos.p->m_last_read_lsn)
          break;
    
      if (pos.isNull())
        tmp.addLast(ptr);
      else
        tmp.insertBefore(ptr, pos);
    
      ptr.p->m_state =
        Logfile_group::LG_EXEC_THREAD | Logfile_group::LG_READ_THREAD;
    }
  }
  ndbassert(list.isEmpty());
  list.appendList(tmpHead);

  if (found_any == false)
  {
    /**
     * No logfilegroup had any logfiles
     */
    jam();
    signal->theData[0] = reference();
    sendSignal(DBLQH_REF, GSN_START_RECCONF, signal, 1, JBB);
    return;
  }
  
  execute_undo_record(signal);
}

void
Lgman::read_undo_log(Signal* signal, Ptr<Logfile_group> ptr)
{
  Uint32 cnt, free= ptr.p->m_free_buffer_words;

  if(! (ptr.p->m_state & Logfile_group::LG_EXEC_THREAD))
  {
    jam();
    /**
     * Logfile_group is done...
     */
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_READ_THREAD;
    stop_run_undo_log(signal);
    return;
  }
  
  if(free <= File_formats::UNDO_PAGE_WORDS)
  {
    signal->theData[0] = LgmanContinueB::READ_UNDO_LOG;
    signal->theData[1] = ptr.i;
    sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 100, 2);
    return;
  }

  Logfile_group::Position producer= ptr.p->m_pos[PRODUCER];
  Logfile_group::Position consumer= ptr.p->m_pos[CONSUMER];

  if(producer.m_current_page.m_idx == 0)
  {
    /**
     * zero pages left in range -> switch range
     */
    Lgman::Page_map::Iterator it;
    Page_map map(m_data_buffer_pool, ptr.p->m_buffer_pages);
    Uint32 sz = map.getSize();
    Uint32 pos= (producer.m_current_page.m_ptr_i + sz - 2) % sz;
    map.position(it, pos);
    union {
      Uint32 _tmp[2];
      Lgman::Buffer_idx range;
    };
    _tmp[0] = *it.data; map.next(it); _tmp[1] = *it.data;
    producer.m_current_page.m_ptr_i = pos;
    producer.m_current_page.m_idx = range.m_idx;
    producer.m_current_pos.m_ptr_i = range.m_ptr_i + range.m_idx;
  }
  
  if(producer.m_current_page.m_ptr_i == consumer.m_current_page.m_ptr_i &&
     producer.m_current_pos.m_ptr_i > consumer.m_current_pos.m_ptr_i)
  {
    Uint32 max= 
      producer.m_current_pos.m_ptr_i - consumer.m_current_pos.m_ptr_i - 1;
    ndbrequire(free >= max * File_formats::UNDO_PAGE_WORDS);
    cnt= read_undo_pages(signal, ptr, producer.m_current_pos.m_ptr_i, max);
    ndbrequire(cnt <= max);    
    producer.m_current_pos.m_ptr_i -= cnt;
    producer.m_current_page.m_idx -= cnt;
  } 
  else
  {
    Uint32 max= producer.m_current_page.m_idx;
    ndbrequire(free >= max * File_formats::UNDO_PAGE_WORDS);
    cnt= read_undo_pages(signal, ptr, producer.m_current_pos.m_ptr_i, max);
    ndbrequire(cnt <= max);
    producer.m_current_pos.m_ptr_i -= cnt;
    producer.m_current_page.m_idx -= cnt;
  } 
  
  ndbrequire(free >= cnt * File_formats::UNDO_PAGE_WORDS);
  free -= (cnt * File_formats::UNDO_PAGE_WORDS);
  ptr.p->m_free_buffer_words = free;
  ptr.p->m_pos[PRODUCER] = producer;  

  signal->theData[0] = LgmanContinueB::READ_UNDO_LOG;
  signal->theData[1] = ptr.i;

  if(free > File_formats::UNDO_PAGE_WORDS)
    sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB);
  else
    sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 100, 2);
}

Uint32
Lgman::read_undo_pages(Signal* signal, Ptr<Logfile_group> ptr, 
		       Uint32 pageId, Uint32 pages)
{
  ndbrequire(pages);
  Ptr<Undofile> filePtr;
  Buffer_idx tail= ptr.p->m_file_pos[TAIL];
  m_file_pool.getPtr(filePtr, tail.m_ptr_i);
  
  if(filePtr.p->m_online.m_outstanding > 0)
  {
    jam();
    return 0;
  }

  Uint32 max= tail.m_idx;

  FsReadWriteReq* req= (FsReadWriteReq*)signal->getDataPtrSend();
  req->filePointer = filePtr.p->m_fd;
  req->userReference = reference();
  req->userPointer = filePtr.i;
  req->operationFlag = 0;
  FsReadWriteReq::setFormatFlag(req->operationFlag, 
				FsReadWriteReq::fsFormatSharedPage);


  if(max > pages)
  {
    jam();
    tail.m_idx -= pages;

    req->varIndex = 1 + tail.m_idx;
    req->numberOfPages = pages;
    req->data.pageData[0] = pageId - pages;
    ptr.p->m_file_pos[TAIL] = tail;
    
    if(DEBUG_UNDO_EXECUTION)
      ndbout_c("a reading from file: %d page(%d-%d) into (%d-%d)",
	       ptr.i, 1 + tail.m_idx, 1+tail.m_idx+pages-1,
	       pageId - pages, pageId - 1);

    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal, 
	       FsReadWriteReq::FixedLength + 1, JBA);
    
    ptr.p->m_outstanding_fs++;
    filePtr.p->m_state |= Undofile::FS_OUTSTANDING;
    filePtr.p->m_online.m_outstanding = pages;
    max = pages;
  }
  else
  {
    jam();

    ndbrequire(tail.m_idx - max == 0);
    req->varIndex = 1;
    req->numberOfPages = max;
    req->data.pageData[0] = pageId - max;
    
    if(DEBUG_UNDO_EXECUTION)
      ndbout_c("b reading from file: %d page(%d-%d) into (%d-%d)",
	       ptr.i, 1 , 1+max-1,
	       pageId - max, pageId - 1);
    
    sendSignal(NDBFS_REF, GSN_FSREADREQ, signal, 
	       FsReadWriteReq::FixedLength + 1, JBA);
    
    ptr.p->m_outstanding_fs++;
    filePtr.p->m_online.m_outstanding = max;
    filePtr.p->m_state |= Undofile::FS_OUTSTANDING | Undofile::FS_MOVE_NEXT;
    
    Ptr<Undofile> prev = filePtr;
    {
      Local_undofile_list files(m_file_pool, ptr.p->m_files);
      if(!files.prev(prev))
      {
	jam();
	files.last(prev);
      }
    }
    if(DEBUG_UNDO_EXECUTION)
      ndbout_c("changing file from %d to %d", filePtr.i, prev.i);

    tail.m_idx= prev.p->m_file_size - 1;
    tail.m_ptr_i= prev.i;
    ptr.p->m_file_pos[TAIL] = tail;
    if(max < pages && filePtr.i != prev.i)
      max += read_undo_pages(signal, ptr, pageId - max, pages - max);
  }
  
  return max;

}

void
Lgman::execute_undo_record(Signal* signal)
{
  /**
   * This code isn't prepared to handle more than one logfile group.
   * To support multiple logfile groups one needs to adapt this code
   * for this requirement.
   */

  Uint64 lsn;
  const Uint32* ptr;
  if((ptr = get_next_undo_record(&lsn)))
  {
    Uint32 len= (* ptr) & 0xFFFF;
    Uint32 type= (* ptr) >> 16;
    Uint32 mask= type & (~((Uint32)File_formats::Undofile::UNDO_NEXT_LSN));
    switch(mask){
    case File_formats::Undofile::UNDO_END:
      g_eventLogger->info("LGMAN: Stop UNDO log execution at LSN %llu,"
                          " found END record",
                          lsn);
      stop_run_undo_log(signal);
      return;
    case File_formats::Undofile::UNDO_LCP:
    case File_formats::Undofile::UNDO_LCP_FIRST:
    {
      Uint32 lcp = * (ptr - len + 1);
      if(m_latest_lcp && lcp > m_latest_lcp)
      {
        if (0)
        {
	  const Uint32 * base = ptr - len + 1;
          Uint32 lcp = base[0];
          Uint32 tableId = base[1] >> 16;
          Uint32 fragId = base[1] & 0xFFFF;

	  ndbout_c("NOT! ignoring lcp: %u tab: %u frag: %u", 
		   lcp, tableId, fragId);
	}
      }

      if(m_latest_lcp == 0 || 
	 lcp < m_latest_lcp || 
	 (lcp == m_latest_lcp && 
	  mask == File_formats::Undofile::UNDO_LCP_FIRST))
      {
        g_eventLogger->info("LGMAN: Stop UNDO log execution at LSN %llu,"
                            " found LCP record",
                            lsn);
	stop_run_undo_log(signal);
	return;
      }
      // Fallthrough
    }
    case File_formats::Undofile::UNDO_TUP_ALLOC:
    case File_formats::Undofile::UNDO_TUP_UPDATE:
    case File_formats::Undofile::UNDO_TUP_FREE:
    case File_formats::Undofile::UNDO_TUP_CREATE:
    case File_formats::Undofile::UNDO_TUP_DROP:
    case File_formats::Undofile::UNDO_TUP_ALLOC_EXTENT:
    case File_formats::Undofile::UNDO_TUP_FREE_EXTENT:
      {
        Dbtup_client tup(this, m_tup);
        tup.disk_restart_undo(signal, lsn, mask, ptr - len + 1, len);
        jamEntry();
      }
      return;
    default:
      ndbrequire(false);
    }
  }
  signal->theData[0] = LgmanContinueB::EXECUTE_UNDO_RECORD;
  signal->theData[1] = 0; /* Not applied flag */
  sendSignal(LGMAN_REF, GSN_CONTINUEB, signal, 2, JBB);
  return;
}

/**
 * Move back one page in the file position of the currently
 * executing UNDO log. Change to previous file if needed.
 */
void Lgman::update_consumer_file_pos(Ptr<Logfile_group> lg_ptr)
{
  Buffer_idx consumer_file_pos = lg_ptr.p->m_consumer_file_pos;
  if (consumer_file_pos.m_idx == 1)
  {
    /* We switch to a new file now */
    jam();
    Ptr<Undofile> filePtr;
    m_file_pool.getPtr(filePtr, consumer_file_pos.m_ptr_i);
    Ptr<Undofile> prev = filePtr;
    Local_undofile_list files(m_file_pool, lg_ptr.p->m_files);
    if(!files.prev(prev))
    {
      jam();
      files.last(prev);
    }
    consumer_file_pos.m_ptr_i = prev.i;
    consumer_file_pos.m_idx = prev.p->m_file_size - 1;
  }
  else
  {
    jam();
    consumer_file_pos.m_idx--;
  }
  lg_ptr.p->m_consumer_file_pos = consumer_file_pos;
}

const Uint32*
Lgman::get_next_undo_record(Uint64 * this_lsn)
{
  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);

  Logfile_group::Position consumer= ptr.p->m_pos[CONSUMER];
  if(ptr.p->m_pos[PRODUCER].m_current_pos.m_idx < 2)
  {
    jam();
    /**
     * Wait for fetching pages...
     */
    return 0;
  }
  
  Uint32 pos = consumer.m_current_pos.m_idx;
  Uint32 page = consumer.m_current_pos.m_ptr_i;
  
  File_formats::Undofile::Undo_page* pageP=(File_formats::Undofile::Undo_page*)
    m_shared_page_pool.getPtr(page);


  if (ptr.p->m_last_read_lsn == (Uint64)1)
  {
    /**
     * End of log, we hadn't concluded any LCPs before the crash.
     * So we find the end of the log by noting that we expect this LSN
     * number to be 0 which doesn't exist.
     *
     * When initialising we have also written an UNDO_END log record
     * into the first page, so we should not be able to run past that
     * point even when the first pages in the file are unwritten and
     * others ahead are written which is a difficult corner case to
     * handle.
     */
    pageP->m_data[0] = (File_formats::Undofile::UNDO_END << 16) | 1 ;
    pageP->m_page_header.m_page_lsn_hi = 0;
    pageP->m_page_header.m_page_lsn_lo = 0;
    ptr.p->m_pos[CONSUMER].m_current_pos.m_idx= pageP->m_words_used = 1;
    this_lsn = 0;
    return pageP->m_data;
  }

  /**
   * Before we start using the page we need to verify that the page has a
   * LSN which is what we expect it to be. It needs to be bigger than the
   * LSN of the first LSN in the file and it needs to be smaller than the
   * LSN of the previous UNDO log record applied since we are applying the
   * UNDO log backwards.
   *
   * If we discover a page which has an invalid LSN it means this page was
   * unwritten and should be ignored. Such pages are ok to encounter at the
   * end of the UNDO log. If we encounter such a page after we already
   * applied an UNDO log then the WAL protocol says that this is wrong since
   * we should have sync:ed everything before that LSN and thus no unwritten
   * pages are ok to encounter anymore.
   */

  Uint32 page_position = pageP->m_words_used;
  bool ignore_page = false;
  bool new_page;

  if (page_position == pos)
  {
    /**
     * This is the first log entry in a new page, we need to
     * verify this page before we start using it.
     */
    Uint64 page_lsn = pageP->m_page_header.m_page_lsn_hi;
    page_lsn <<= 32;
    page_lsn += pageP->m_page_header.m_page_lsn_lo;
    if (page_lsn != (ptr.p->m_last_read_lsn - 1))
    {
      jam();
      /**
       * The page LSN wasn't the expected one. We need to verify that
       * it is ok that this page is here.
       */
      Ptr<Undofile> filePtr;
      m_file_pool.getPtr(filePtr, ptr.p->m_consumer_file_pos.m_ptr_i);
      /**
       * Due to an old bug we can have rewrite an LSN number, so we can
       * only assert on that we don't write a second LSN with the same
       * number, but we require that it isn't more than two LSNs with
       * the same number.
       *
       * We can upgrade this assert to a require when we are sure that
       * the log wasn't produced by these older versions.
       */
      ndbassert(page_lsn < (ptr.p->m_last_read_lsn - 1));
      ndbrequire(page_lsn < (ptr.p->m_last_read_lsn - 1) ||
                 page_lsn == ptr.p->m_last_read_lsn);
      if (filePtr.p->m_start_lsn <= page_lsn)
      {
        /**
         * A normal page, continue as usual.
         * However given that we now have skipped over a few pages we need to
         * set back the last read LSN to be the last LSN of the previous page
         * that was never written, or in other words this pageLSN + 1.
         */
        jam();
        ptr.p->m_last_read_lsn = page_lsn + 1;
        SimulatedBlock* fs = globalData.getBlock(NDBFS);
        g_eventLogger->info("LGMAN: Continue applying log records in written"
                            "page: %u in the file %s",
                            ptr.p->m_consumer_file_pos.m_idx,
                            fs->get_filename(filePtr.p->m_fd));
      }
      else
      {
        jam();
        if (ptr.p->m_applied)
        {
          /**
           * We need to crash since we found a not OK page after an UNDO log
           * record have already been applied.
           */
          SimulatedBlock* fs = globalData.getBlock(NDBFS);
          g_eventLogger->info("LGMAN: File %s have wrong pageLSN in page: %u",
                              fs->get_filename(filePtr.p->m_fd),
                              ptr.p->m_consumer_file_pos.m_idx);
          progError(__LINE__, NDBD_EXIT_SR_UNDOLOG);
        }
        SimulatedBlock* fs = globalData.getBlock(NDBFS);
        g_eventLogger->info("LGMAN: Ignoring log records in unwritten page: "
                            "%u in the file %s",
                            ptr.p->m_consumer_file_pos.m_idx,
                            fs->get_filename(filePtr.p->m_fd));
        ignore_page = true;
        new_page = true;
      }
    }
  }
  /**
   * Read the UNDO record
   */
  Uint32 *record = NULL;
  if (!ignore_page)
  {
    record= pageP->m_data + pos - 1;
    Uint32 len= (* record) & 0xFFFF;
    ndbrequire(len);
    Uint32 *prev= record - len;
    Uint64 lsn = 0;

    if (((* record) >> 16) & (File_formats::Undofile::UNDO_NEXT_LSN))
    {
      /* This was a Type 1 record, previous LSN is -1 of current */
      jam();
      lsn = ptr.p->m_last_read_lsn - 1;
      ndbrequire((Int64)lsn >= 0);
    }
    else
    {
      /**
       * This was a Type 2 record, previous LSN given by LSNs in the UNDO
       * log record, see UNDO log record layout in beginning of file.
       */
      ndbrequire(pos >= 3);
      jam();
      lsn += * (prev - 1); lsn <<= 32;
      lsn += * (prev - 0);
      len += 2;
      ndbrequire((Int64)lsn >= 0);
    }
    *this_lsn = ptr.p->m_last_read_lsn = lsn;
    ndbrequire(pos >= len);
    new_page = (pos == len);
    consumer.m_current_pos.m_idx -= len;
  }

  /**
   * Now step back to previous UNDO log record. Also change to new page
   * if necessary.
   */

  if (new_page)
  {
    /**
     * Switching to next page in our backwards scan of UNDO log pages.
     *
     * The length of the UNDO record is the same as the position of the
     * header of the UNDO log record which means that this was the first
     * UNDO log record in the page and thus the last UNDO log record to
     * apply in the page. We prepare moving to the next page.
     */
    ndbrequire(ptr.p->m_pos[PRODUCER].m_current_pos.m_idx);
    ptr.p->m_pos[PRODUCER].m_current_pos.m_idx--;

    if(consumer.m_current_page.m_idx)
    {
      consumer.m_current_page.m_idx--;   // left in range
      consumer.m_current_pos.m_ptr_i--; // page
    }
    else
    {
      // 0 pages left in range...switch range
      Lgman::Page_map::Iterator it;
      Page_map map(m_data_buffer_pool, ptr.p->m_buffer_pages);
      Uint32 sz = map.getSize();
      Uint32 tmp = (consumer.m_current_page.m_ptr_i + sz - 2) % sz;
      
      map.position(it, tmp);
      union {
	Uint32 _tmp[2];
	Lgman::Buffer_idx range;
      };
      
      _tmp[0] = *it.data;
      map.next(it);
      _tmp[1] = *it.data;
      
      consumer.m_current_page.m_idx = range.m_idx - 1; // left in range
      consumer.m_current_page.m_ptr_i = tmp;           // pos in map

      consumer.m_current_pos.m_ptr_i = range.m_ptr_i + range.m_idx - 1; // page
    }

    if(DEBUG_UNDO_EXECUTION)
      ndbout_c("reading from %d", consumer.m_current_pos.m_ptr_i);

    ptr.p->m_free_buffer_words += File_formats::UNDO_PAGE_WORDS;

    /**
     * We have switched to a new page. Before starting to apply this page
     * we need to ensure that this page is containing valid entries to
     * apply. We might come to pages that don't have an accurate set of
     * LSNs. This should however never happen after we reach an UNDO log
     * record that have been applied due to the WAL protocol. If this
     * happens the UNDO log is inconsistent and we need to crash with
     * a report about this.
     *
     * We will verify the LSN of the page by checking that the LSN of the
     * page is bigger than or equal to the LSN of the first page in the
     * file. To do this we need to keep track of the current file and to
     * keep track of this we simply keep track of the file position of the
     * UNDO logs we execute.
     *
     * We will then verify that the page LSN is smaller than the highest
     * we reached so far, but still bigger or equal to the page LSN of the
     * first page in the file we are currently executing the UNDO log in.
     */
    update_consumer_file_pos(ptr);

    pageP=(File_formats::Undofile::Undo_page*)
      m_shared_page_pool.getPtr(consumer.m_current_pos.m_ptr_i);

    consumer.m_current_pos.m_idx = pageP->m_words_used;
  }
  ptr.p->m_pos[CONSUMER] = consumer;

  /**
   * Re-sort log file groups
   * This is code prepared for future use of multiple logfile groups.
   * We comment it out for now.
   */
#if 0
  Ptr<Logfile_group> sort = ptr;
  if(m_logfile_group_list.next(sort))
  {
    while(!sort.isNull() && sort.p->m_last_read_lsn > lsn)
      m_logfile_group_list.next(sort);
    
    if(sort.i != ptr.p->nextList)
    {
      m_logfile_group_list.remove(ptr);
      if(sort.isNull())
        m_logfile_group_list.addLast(ptr);
      else
        m_logfile_group_list.insertBefore(ptr, sort);
    }
  }
#endif
  return record;
}

void
Lgman::stop_run_undo_log(Signal* signal)
{
  bool running = false, outstanding = false;
  Ptr<Logfile_group> ptr;
  m_logfile_group_list.first(ptr);
  while(!ptr.isNull())
  {
    jam();
    /**
     * Mark exec thread as completed
     */
    ptr.p->m_state &= ~(Uint32)Logfile_group::LG_EXEC_THREAD;

    if(ptr.p->m_state & Logfile_group::LG_READ_THREAD)
    {
      /**
       * Thread is still running...wait for it to complete
       */
      running = true;
    }
    else if(ptr.p->m_outstanding_fs)
    {
      outstanding = true; // a FSREADREQ is outstanding...wait for it
    }
    else if(ptr.p->m_state != Logfile_group::LG_ONLINE)
    {
      /**
       * Fix log TAIL
       */
      ndbrequire(ptr.p->m_state == 0);
      ptr.p->m_state = Logfile_group::LG_ONLINE;
      Buffer_idx tail= ptr.p->m_file_pos[TAIL];
      Uint32 pages= ptr.p->m_pos[PRODUCER].m_current_pos.m_idx;
      
      while(pages)
      {
	Ptr<Undofile> file;
	m_file_pool.getPtr(file, tail.m_ptr_i);
	Uint32 page= tail.m_idx;
	Uint32 size= file.p->m_file_size;
	ndbrequire(size >= page);
	Uint32 diff= size - page;
	
	if(pages >= diff)
	{
	  pages -= diff;
	  Local_undofile_list files(m_file_pool, ptr.p->m_files);
	  if(!files.next(file))
	    files.first(file);
	  tail.m_idx = 1;
	  tail.m_ptr_i= file.i;
	}
	else
	{
	  tail.m_idx += pages;
	  pages= 0;
	}
      }
      ptr.p->m_tail_pos[0] = tail;
      ptr.p->m_tail_pos[1] = tail;
      ptr.p->m_tail_pos[2] = tail;
      ptr.p->m_file_pos[TAIL] = tail;

      init_logbuffer_pointers(ptr);

      {
	Buffer_idx head= ptr.p->m_file_pos[HEAD];
	Ptr<Undofile> file;
	m_file_pool.getPtr(file, head.m_ptr_i);
	if (head.m_idx == file.p->m_file_size - 1)
	{
	  Local_undofile_list files(m_file_pool, ptr.p->m_files);
	  if(!files.next(file))
	  {
	    jam();
	    files.first(file);
	  }
	  head.m_idx = 0;
	  head.m_ptr_i = file.i;
	  ptr.p->m_file_pos[HEAD] = head;
	}
      }
      
      client_lock(number(), __LINE__);
      ptr.p->m_free_file_words = (Uint64)File_formats::UNDO_PAGE_WORDS * 
	(Uint64)compute_free_file_pages(ptr);
      client_unlock(number(), __LINE__);
      ptr.p->m_next_reply_ptr_i = ptr.p->m_file_pos[HEAD].m_ptr_i;
      
      ptr.p->m_state |= Logfile_group::LG_FLUSH_THREAD;
      signal->theData[0] = LgmanContinueB::FLUSH_LOG;
      signal->theData[1] = ptr.i;
      signal->theData[2] = 0;
      sendSignal(reference(), GSN_CONTINUEB, signal, 3, JBB);

      if(1)
      {
	SimulatedBlock* fs = globalData.getBlock(NDBFS);
	Ptr<Undofile> hf, tf;
	m_file_pool.getPtr(tf, tail.m_ptr_i);
	m_file_pool.getPtr(hf,  ptr.p->m_file_pos[HEAD].m_ptr_i);
	infoEvent("LGMAN: Logfile group: %d ", ptr.p->m_logfile_group_id);
        g_eventLogger->info("LGMAN: Logfile group: %d ",
                            ptr.p->m_logfile_group_id);
	infoEvent("  head: %s page: %d",
                  fs->get_filename(hf.p->m_fd),
                  ptr.p->m_file_pos[HEAD].m_idx);
        g_eventLogger->info("  head: %s page: %d",
                            fs->get_filename(hf.p->m_fd),
                            ptr.p->m_file_pos[HEAD].m_idx);
	infoEvent("  tail: %s page: %d",
		  fs->get_filename(tf.p->m_fd), tail.m_idx);
        g_eventLogger->info("  tail: %s page: %d",
                            fs->get_filename(tf.p->m_fd), tail.m_idx);
      }
    }
    
    m_logfile_group_list.next(ptr);
  }
  
  if(running)
  {
    jam();
    return;
  }
  
  if(outstanding)
  {
    jam();
    signal->theData[0] = LgmanContinueB::STOP_UNDO_LOG;
    sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 100, 1);
    return;
  }
  
  infoEvent("LGMAN: Flushing page cache after undo completion");
  g_eventLogger->info("LGMAN: Flushing page cache after undo completion");

  /**
   * START_FLUSH_PGMAN_CACHE
   * 
   * Start flushing pages (a form of a local LCP)
   *
   * As part of a restart we want to ensure that we don't need to replay
   * the UNDO log again. This is done by ensuring that all pages
   * currently in PGMAN cache is flushed to disk. We do this by faking
   * a LCP to PGMAN by a first LCP_FRAG_ORD followed by END_LCP_REQ.
   */

  LcpFragOrd * ord = (LcpFragOrd *)signal->getDataPtr();
  ord->lcpId = m_latest_lcp;
  sendSignal(PGMAN_REF, GSN_LCP_FRAG_ORD, signal, 
	     LcpFragOrd::SignalLength, JBB);
  
  EndLcpReq* req= (EndLcpReq*)signal->getDataPtr();
  req->senderData = 0;
  req->senderRef = reference();
  req->backupId = m_latest_lcp;
  sendSignal(PGMAN_REF, GSN_END_LCP_REQ, signal, 
	     EndLcpReq::SignalLength, JBB);
}

void
Lgman::execEND_LCP_CONF(Signal* signal)
{
  {
    Dbtup_client tup(this, m_tup);
    tup.disk_restart_undo(signal, 0, File_formats::Undofile::UNDO_END, 0, 0);
    jamEntry();
  }
  
  /**
   * END_FLUSH_PGMAN_CACHE
   *
   * PGMAN has completed flushing all pages
   *
   * Insert "fake" LCP record preventing undo to be "rerun"
   */

  Uint32 undo[3];
  undo[0] = m_latest_lcp;
  undo[1] = (0 << 16) | 0;
  undo[2] = (File_formats::Undofile::UNDO_LCP_FIRST << 16 ) 
    | (sizeof(undo) >> 2);
  
  Ptr<Logfile_group> ptr;
  ndbrequire(m_logfile_group_list.first(ptr));

  Uint64 next_lsn= m_next_lsn;
  if(ptr.p->m_next_lsn == next_lsn
#ifdef VM_TRACE
     && ((rand() % 100) > 50)
#endif
     )
  {
    undo[2] |= File_formats::Undofile::UNDO_NEXT_LSN << 16;
    Uint32 *dst= get_log_buffer(ptr, sizeof(undo) >> 2);
    memcpy(dst, undo, sizeof(undo));
    ndbrequire(ptr.p->m_free_file_words >= (sizeof(undo) >> 2));
    ptr.p->m_free_file_words -= (sizeof(undo) >> 2);
  }
  else
  {
    Uint32 *dst= get_log_buffer(ptr, (sizeof(undo) >> 2) + 2);      
    * dst++ = (Uint32)(next_lsn >> 32);
    * dst++ = (Uint32)(next_lsn & 0xFFFFFFFF);
    memcpy(dst, undo, sizeof(undo));
    ndbrequire(ptr.p->m_free_file_words >= ((sizeof(undo) >> 2) + 2));
    ptr.p->m_free_file_words -= ((sizeof(undo) >> 2) + 2);
  }
  m_next_lsn = ptr.p->m_next_lsn = next_lsn + 1;

  ptr.p->m_last_synced_lsn = next_lsn;
  while(m_logfile_group_list.next(ptr))
    ptr.p->m_last_synced_lsn = next_lsn;
  
  infoEvent("LGMAN: Flushing complete");
  g_eventLogger->info("LGMAN: Flushing complete");

  signal->theData[0] = reference();
  sendSignal(DBLQH_REF, GSN_START_RECCONF, signal, 1, JBB);
}

#ifdef VM_TRACE
void 
Lgman::validate_logfile_group(Ptr<Logfile_group> ptr, const char * heading)
{
  do 
  {
    if (ptr.p->m_file_pos[HEAD].m_ptr_i == RNIL)
      break;
    
    Uint32 pages = compute_free_file_pages(ptr);
    
    Uint32 group_pages = 
      ((ptr.p->m_free_file_words + File_formats::UNDO_PAGE_WORDS - 1)/ File_formats::UNDO_PAGE_WORDS) ;
    Uint32 last = ptr.p->m_free_file_words % File_formats::UNDO_PAGE_WORDS;
    
    if(! (pages >= group_pages))
    {
      ndbout << heading << " Tail: " << ptr.p->m_file_pos[TAIL] 
	     << " Head: " << ptr.p->m_file_pos[HEAD] 
	     << " free: " << group_pages << "(" << last << ")" 
	     << " found: " << pages;
      for(Uint32 i = 0; i<3; i++)
      {
	ndbout << " - " << ptr.p->m_tail_pos[i];
      }
      ndbout << endl;
      
      ndbrequire(pages >= group_pages);
    }
  } while(0);
}
#endif

void Lgman::execGET_TABINFOREQ(Signal* signal)
{
  jamEntry();

  if(!assembleFragments(signal))
  {
    return;
  }

  GetTabInfoReq * const req = (GetTabInfoReq *)&signal->theData[0];

  const Uint32 reqType = req->requestType & (~GetTabInfoReq::LongSignalConf);
  BlockReference retRef= req->senderRef;
  Uint32 senderData= req->senderData;
  Uint32 tableId= req->tableId;

  if(reqType == GetTabInfoReq::RequestByName)
  {
    jam();
    SectionHandle handle(this, signal);
    releaseSections(handle);

    sendGET_TABINFOREF(signal, req, GetTabInfoRef::NoFetchByName);
    return;
  }

  Logfile_group key;
  key.m_logfile_group_id= tableId;
  Ptr<Logfile_group> ptr;
  m_logfile_group_hash.find(ptr, key);

  if(ptr.p->m_logfile_group_id != tableId)
  {
    jam();

    sendGET_TABINFOREF(signal, req, GetTabInfoRef::InvalidTableId);
    return;
  }


  GetTabInfoConf *conf = (GetTabInfoConf *)&signal->theData[0];

  conf->senderData= senderData;
  conf->tableId= tableId;
  conf->freeWordsHi= (Uint32)(ptr.p->m_free_file_words >> 32);
  conf->freeWordsLo= (Uint32)(ptr.p->m_free_file_words & 0xFFFFFFFF);
  conf->tableType= DictTabInfo::LogfileGroup;
  conf->senderRef= reference();
  sendSignal(retRef, GSN_GET_TABINFO_CONF, signal,
	     GetTabInfoConf::SignalLength, JBB);
}

void Lgman::sendGET_TABINFOREF(Signal* signal,
			       GetTabInfoReq * req,
			       GetTabInfoRef::ErrorCode errorCode)
{
  jamEntry();
  GetTabInfoRef * const ref = (GetTabInfoRef *)&signal->theData[0];
  /**
   * The format of GetTabInfo Req/Ref is the same
   */
  BlockReference retRef = req->senderRef;
  ref->errorCode = errorCode;

  sendSignal(retRef, GSN_GET_TABINFOREF, signal, signal->length(), JBB);
}
