text:
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape Portable Runtime (NSPR).
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "primpl.h"

/*
** We override malloc etc. on any platform which has preemption +
** nspr20 user level threads.  When we're debugging, we can make our
** version of malloc fail occasionally.
*/
#ifdef _PR_OVERRIDE_MALLOC

/*
** Thread safe version of malloc, calloc, realloc, free
*/
#include <stdarg.h>

#ifdef DEBUG
#define SANITY
#define EXTRA_SANITY
#else
#undef SANITY
#undef EXTRA_SANITY
#endif

/* Forward decls */
void *_PR_UnlockedMalloc(size_t size);
void _PR_UnlockedFree(void *ptr);
void *_PR_UnlockedRealloc(void *ptr, size_t size);
void *_PR_UnlockedCalloc(size_t n, size_t elsize);

/************************************************************************/

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 *
 */

/*
 * Defining SANITY will enable some checks which will tell you if the users
 * program did botch something
 */

/*
 * Defining EXTRA_SANITY will enable some checks which are mostly related
 * to internal conditions in malloc.c
 */

/*
 * Very verbose progress on stdout...
 */
#if 0
#  define TRACE(foo)    printf  foo
static int malloc_event;
#else
#  define TRACE(foo)
#endif

/* XXX Pick a number, any number */
#   define malloc_pagesize		4096UL
#   define malloc_pageshift		12UL

#ifdef XP_UNIX
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#endif

/*
 * This structure describes a page's worth of chunks.
 */

struct pginfo {
    struct pginfo	*next;	/* next on the free list */
    char		*page;	/* Pointer to the page */
    u_short		size;	/* size of this page's chunks */
    u_short		shift;	/* How far to shift for this size chunks */
    u_short		free;	/* How many free chunks */
    u_short		total;	/* How many chunk */
    u_long		bits[1]; /* Which chunks are free */
};

struct pgfree {
    struct pgfree	*next;	/* next run of free pages */
    struct pgfree	*prev;	/* prev run of free pages */
    char		*page;	/* pointer to free pages */
    char		*end;	/* pointer to end of free pages */
    u_long		size;	/* number of bytes free */
};

/*
 * How many bits per u_long in the bitmap.
 * Change only if not 8 bits/byte
 */
#define	MALLOC_BITS	(8*sizeof(u_long))

/*
 * Magic values to put in the page_directory
 */
#define MALLOC_NOT_MINE	((struct pginfo*) 0)
#define MALLOC_FREE 	((struct pginfo*) 1)
#define MALLOC_FIRST	((struct pginfo*) 2)
#define MALLOC_FOLLOW	((struct pginfo*) 3)
#define MALLOC_MAGIC	((struct pginfo*) 4)

/*
 * Set to one when malloc_init has been called
 */
static	unsigned	initialized;

/*
 * The size of a page.
 * Must be a integral multiplum of the granularity of mmap(2).
 * Your toes will curl if it isn't a power of two
 */
#define malloc_pagemask	((malloc_pagesize)-1)

/*
 * The size of the largest chunk.
 * Half a page.
 */
#define malloc_maxsize	((malloc_pagesize)>>1)

/*
 * malloc_pagesize == 1 << malloc_pageshift
 */
#ifndef malloc_pageshift
static	unsigned	malloc_pageshift;
#endif /* malloc_pageshift */

/*
 * The smallest allocation we bother about.
 * Must be power of two
 */
#ifndef malloc_minsize
static	unsigned  malloc_minsize;
#endif /* malloc_minsize */

/*
 * The largest chunk we care about.
 * Must be smaller than pagesize
 * Must be power of two
 */
#ifndef malloc_maxsize
static	unsigned  malloc_maxsize;
#endif /* malloc_maxsize */

#ifndef malloc_cache
static	unsigned  malloc_cache;
#endif /* malloc_cache */

/*
 * The offset from pagenumber to index into the page directory
 */
static	u_long  malloc_origo;

/*
 * The last index in the page directory we care about
 */
static	u_long  last_index;

/*
 * Pointer to page directory.
 * Allocated "as if with" malloc
 */
static	struct	pginfo **page_dir;

/*
 * How many slots in the page directory
 */
static	unsigned	malloc_ninfo;

/*
 * Free pages line up here
 */
static struct pgfree	free_list;

/*
 * Abort() if we fail to get VM ?
 */
static int malloc_abort;

#ifdef SANITY
/*
 * Are we trying to die ?
 */
static int suicide;
#endif

/*
 * dump statistics
 */
static int malloc_stats;

/*
 * always realloc ?
 */
static int malloc_realloc;

/*
 * my last break.
 */
static void *malloc_brk;

/*
 * one location cache for free-list holders
 */
static struct pgfree *px;

static int set_pgdir(void *ptr, struct  pginfo *info);
static int extend_page_directory(u_long index);

#ifdef SANITY
void
malloc_dump(FILE *fd)
{
    struct pginfo **pd;
    struct pgfree *pf;
    int j;

    pd = page_dir;

    /* print out all the pages */
    for(j=0;j<=last_index;j++) {
	fprintf(fd,"%08lx %5d ",(j+malloc_origo) << malloc_pageshift,j);
	if (pd[j] == MALLOC_NOT_MINE) {
	    for(j++;j<=last_index && pd[j] == MALLOC_NOT_MINE;j++)
		;
	    j--;
	    fprintf(fd,".. %5d not mine\n",	j);
	} else if (pd[j] == MALLOC_FREE) {
	    for(j++;j<=last_index && pd[j] == MALLOC_FREE;j++)
		;
	    j--;
	    fprintf(fd,".. %5d free\n", j);
	} else if (pd[j] == MALLOC_FIRST) {
	    for(j++;j<=last_index && pd[j] == MALLOC_FOLLOW;j++)
		;
	    j--;
	    fprintf(fd,".. %5d in use\n", j);
	} else if (pd[j] < MALLOC_MAGIC) {
	    fprintf(fd,"(%p)\n", pd[j]);
	} else {
	    fprintf(fd,"%p %d (of %d) x %d @ %p --> %p\n",
		pd[j],pd[j]->free, pd[j]->total,
		pd[j]->size, pd[j]->page, pd[j]->next);
	}
    }

    for(pf=free_list.next; pf; pf=pf->next) {
	fprintf(fd,"Free: @%p [%p...%p[ %ld ->%p <-%p\n",
		pf,pf->page,pf->end,pf->size,pf->prev,pf->next);
	if (pf == pf->next) {
		fprintf(fd,"Free_list loops.\n");
		break;
	}
    }

    /* print out various info */
    fprintf(fd,"Minsize\t%d\n",malloc_minsize);
    fprintf(fd,"Maxsize\t%ld\n",malloc_maxsize);
    fprintf(fd,"Pagesize\t%ld\n",malloc_pagesize);
    fprintf(fd,"Pageshift\t%ld\n",malloc_pageshift);
    fprintf(fd,"FirstPage\t%ld\n",malloc_origo);
    fprintf(fd,"LastPage\t%ld %lx\n",last_index+malloc_pageshift,
	(last_index + malloc_pageshift) << malloc_pageshift);
    fprintf(fd,"Break\t%ld\n",(u_long)sbrk(0) >> malloc_pageshift);
}

static void wrterror(char *fmt, ...)
{
    char *q = "malloc() error: ";
    char buf[100];
    va_list ap;

    suicide = 1;

    va_start(ap, fmt);
    PR_vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    fputs(q, stderr);
    fputs(buf, stderr);

    malloc_dump(stderr);
    PR_Abort();
}

static void wrtwarning(char *fmt, ...)
{
    char *q = "malloc() warning: ";
    char buf[100];
    va_list ap;

    va_start(ap, fmt);
    PR_vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    fputs(q, stderr);
    fputs(buf, stderr);
}
#endif /* SANITY */

/*
 * Allocate a number of pages from the OS
 */
static caddr_t
map_pages(int pages, int update)
{
    caddr_t result,tail;

    result = ((caddr_t)sbrk(0)) + malloc_pagemask - 1;
    result = (caddr_t) ((u_long)result & ~malloc_pagemask);
    tail = result + (pages << malloc_pageshift);
    if (!brk(tail)) {
	last_index = ((u_long)tail >> malloc_pageshift) - malloc_origo -1;
	malloc_brk = tail;
	TRACE(("%6d S %p .. %p\n",malloc_event++, result, tail));
	if (!update || last_index < malloc_ninfo ||
	  extend_page_directory(last_index))
	    return result;
    }
    TRACE(("%6d s %d %p %d\n",malloc_event++,pages,sbrk(0),errno));
#ifdef EXTRA_SANITY
    wrterror("map_pages fails\n");
#endif
    return 0;
}

#define set_bit(_pi,_bit) \
    (_pi)->bits[(_bit)/MALLOC_BITS] |= 1L<<((_bit)%MALLOC_BITS)

#define clr_bit(_pi,_bit) \
    (_pi)->bits[(_bit)/MALLOC_BITS] &= ~(1L<<((_bit)%MALLOC_BITS));

#define tst_bit(_pi,_bit) \
    ((_pi)->bits[(_bit)/MALLOC_BITS] & (1L<<((_bit)%MALLOC_BITS)))

/*
 * Extend page directory
 */
static int
extend_page_directory(u_long index)
{
    struct  pginfo **young, **old;
    int i;

    TRACE(("%6d E %lu\n",malloc_event++,index));

    /* Make it this many pages */
    i = index * sizeof *page_dir;
    i /= malloc_pagesize;
    i += 2;

    /* Get new pages, if you used this much mem you don't care :-) */
    young = (struct pginfo**) map_pages(i,0);
    if (!young)
	return 0;

    /* Copy the old stuff */
    memset(young, 0, i * malloc_pagesize);
    memcpy(young, page_dir,
	    malloc_ninfo * sizeof *page_dir);

    /* register the new size */
    malloc_ninfo = i * malloc_pagesize / sizeof *page_dir;

    /* swap the pointers */
    old = page_dir;
    page_dir = young;

    /* Mark the pages */
    index = ((u_long)young >> malloc_pageshift) - malloc_origo;
    page_dir[index] = MALLOC_FIRST;
    while (--i) {
	page_dir[++index] = MALLOC_FOLLOW;
    }

    /* Now free the old stuff */
    _PR_UnlockedFree(old);
    return 1;
}

/*
 * Set entry in page directory.
 * Extend page directory if need be.
 */
static int
set_pgdir(void *ptr, struct  pginfo *info)
{
    u_long index = ((u_long)ptr >> malloc_pageshift) - malloc_origo;

    if (index >= malloc_ninfo && !extend_page_directory(index))
	return 0;
    page_dir[index] = info;
    return 1;
}

/*
 * Initialize the world
 */
static void
malloc_init (void)
{
    int i;
    char *p;

    TRACE(("%6d I\n",malloc_event++));
#ifdef DEBUG
    for (p=getenv("MALLOC_OPTIONS"); p && *p; p++) {
	switch (*p) {
	    case 'a': malloc_abort = 0; break;
	    case 'A': malloc_abort = 1; break;
	    case 'd': malloc_stats = 0; break;
	    case 'D': malloc_stats = 1; break;
	    case 'r': malloc_realloc = 0; break;
	    case 'R': malloc_realloc = 1; break;
	    default:
		wrtwarning("Unknown chars in MALLOC_OPTIONS\n");
		break;
	}
    }
#endif

#ifndef malloc_pagesize
    /* determine our pagesize */
    malloc_pagesize = getpagesize();
#endif /* malloc_pagesize */

#ifndef malloc_pageshift
    /* determine how much we shift by to get there */
    for (i = malloc_pagesize; i > 1; i >>= 1)
	malloc_pageshift++;
#endif /* malloc_pageshift */

#ifndef malloc_cache
    malloc_cache = 50 << malloc_pageshift;
#endif /* malloc_cache */

#ifndef malloc_minsize
    /*
     * find the smallest size allocation we will bother about.
     * this is determined as the smallest allocation that can hold
     * it's own pginfo;
     */
    i = 2;
    for(;;) {
	int j;

	/* Figure out the size of the bits */
	j = malloc_pagesize/i;
	j /= 8;
	if (j < sizeof(u_long))
		j = sizeof (u_long);
	if (sizeof(struct pginfo) + j - sizeof (u_long) <= i)
		break;
	i += i;
    }
    malloc_minsize = i;
#endif /* malloc_minsize */

    /* Allocate one page for the page directory */
    page_dir = (struct pginfo **) map_pages(1,0);
#ifdef SANITY
    if (!page_dir)
	wrterror("fatal: my first mmap failed.  (check limits ?)\n");
#endif

    /*
     * We need a maximum of malloc_pageshift buckets, steal these from the
     * front of the page_directory;
     */
    malloc_origo = (u_long) page_dir >> malloc_pageshift;
    malloc_origo -= malloc_pageshift;

    /* Clear it */
    memset(page_dir,0,malloc_pagesize);

    /* Find out how much it tells us */
    malloc_ninfo = malloc_pagesize / sizeof *page_dir;

    /* Plug the page directory into itself */
    i = set_pgdir(page_dir,MALLOC_FIRST);
#ifdef SANITY
    if (!i)
	wrterror("fatal: couldn't set myself in the page directory\n");
#endif

    /* Been here, done that */
    initialized++;
}

/*
 * Allocate a number of complete pages
 */
static void *malloc_pages(size_t size)
{
    void *p,*delay_free = 0;
    int i;
    struct pgfree *pf;
    u_long index;

    /* How many pages ? */
    size += (malloc_pagesize-1);
    size &= ~malloc_pagemask;

    p = 0;
    /* Look for free pages before asking for more */
    for(pf = free_list.next; pf; pf = pf->next) {
#ifdef EXTRA_SANITY
	if (pf->page == pf->end)
	    wrterror("zero entry on free_list\n");
	if (pf->page > pf->end) {
	    TRACE(("%6d !s %p %p %p <%d>\n",malloc_event++,
		pf,pf->page,pf->end,__LINE__));
	    wrterror("sick entry on free_list\n");
	}
	if ((void*)pf->page >= (void*)sbrk(0))
	    wrterror("entry on free_list past brk\n");
	if (page_dir[((u_long)pf->page >> malloc_pageshift) - malloc_origo]
	  != MALLOC_FREE) {
	    TRACE(("%6d !f %p %p %p <%d>\n",malloc_event++,
		pf,pf->page,pf->end,__LINE__));
	    wrterror("non-free first page on free-list\n");
	}
	if (page_dir[((u_long)pf->end >> malloc_pageshift) - 1 - malloc_origo]
	  != MALLOC_FREE)
	    wrterror("non-free last page on free-list\n");
#endif /* EXTRA_SANITY */
	if (pf->size < size)
	    continue;
	else if (pf->size == size) {
	    p = pf->page;
	    if (pf->next)
		    pf->next->prev = pf->prev;
	    pf->prev->next = pf->next;
	    delay_free = pf;
	    break;
	} else {
	    p = pf->page;
	    pf->page += size;
	    pf->size -= size;
	    break;
        }
    }
#ifdef EXTRA_SANITY
    if (p && page_dir[((u_long)p >> malloc_pageshift) - malloc_origo]
      != MALLOC_FREE) {
	wrterror("allocated non-free page on free-list\n");
    }
#endif /* EXTRA_SANITY */

    size >>= malloc_pageshift;

    /* Map new pages */
    if (!p)
	p = map_pages(size,1);

    if (p) {
	/* Mark the pages in the directory */
	index = ((u_long)p >> malloc_pageshift) - malloc_origo;
	page_dir[index] = MALLOC_FIRST;
	for (i=1;i<size;i++)
	    page_dir[index+i] = MALLOC_FOLLOW;
    }
    if (delay_free) {
	if (!px)
	    px = (struct pgfree*)delay_free;
	else
	    _PR_UnlockedFree(delay_free);
    }
    return p;
}

/*
 * Allocate a page of fragments
 */

static int
malloc_make_chunks(int bits)
{
    struct  pginfo *bp;
    void *pp;
    int i,k,l;

    /* Allocate a new bucket */
    pp = malloc_pages(malloc_pagesize);
    if (!pp)
	return 0;
    l = sizeof *bp - sizeof(u_long);
    l += sizeof(u_long) *
	(((malloc_pagesize >> bits)+MALLOC_BITS-1) / MALLOC_BITS);
    if ((1<<(bits)) <= l+l) {
	bp = (struct  pginfo *)pp;
    } else {
	bp = (struct  pginfo *)_PR_UnlockedMalloc(l);
    }
    if (!bp)
	return 0;
    bp->size = (1<<bits);
    bp->shift = bits;
    bp->total = bp->free = malloc_pagesize >> bits;
    bp->next = page_dir[bits];
    bp->page = (char*)pp;
    i = set_pgdir(pp,bp);
    if (!i)
	return 0;

    /* We can safely assume that there is nobody in this chain */
    page_dir[bits] = bp;

    /* set all valid bits in the bits */
    k = bp->total;
    i = 0;
/*
    for(;k-i >= MALLOC_BITS; i += MALLOC_BITS)
	bp->bits[i / MALLOC_BITS] = ~0;
*/
    for(; i < k; i++)
	set_bit(bp,i);

    if (bp != pp)
	return 1;

    /* We may have used the first ones already */
    for(i=0;l > 0;i++) {
	clr_bit(bp,i);
	bp->free--;
	bp->total--;
	l -= (1 << bits);
    }
    return 1;
}

/*
 * Allocate a fragment
 */
static void *malloc_bytes(size_t size)
{
    size_t s;
    int j;
    struct  pginfo *bp;
    int k;
    u_long *lp, bf;

    /* Don't bother with anything less than this */
    if (size < malloc_minsize) {
	size = malloc_minsize;
    }

    /* Find the right bucket */
    j = 1;
    s = size - 1;
    while (s >>= 1) {
        j++;
    }

    /* If it's empty, make a page more of that size chunks */
    if (!page_dir[j] && !malloc_make_chunks(j))
	return 0;

    /* Find first word of bitmap which isn't empty */
    bp = page_dir[j];
    for (lp = bp->bits; !*lp; lp++)
	;

    /* Find that bit */
    bf = *lp;
    k = 0;
    while ((bf & 1) == 0) {
	bf >>= 1;
	k++;
    }

    *lp ^= 1L<<k;                       /* clear it */
    bp->free--;
    if (!bp->free) {
	page_dir[j] = bp->next;
	bp->next = 0;
    }
    k += (lp - bp->bits)*MALLOC_BITS;
    return bp->page + (k << bp->shift);
}

void *_PR_UnlockedMalloc(size_t size)
{
    void *result;

    /* Round up to a multiple of 8 bytes */
    if (size & 7) {
	size = size + 8 - (size & 7);
    }

    if (!initialized)
	malloc_init();

#ifdef SANITY
    if (suicide)
	PR_Abort();
#endif

    if (size <= malloc_maxsize)
	result =  malloc_bytes(size);
    else
	result =  malloc_pages(size);
#ifdef SANITY
    if (malloc_abort && !result)
	wrterror("malloc() returns NULL\n");
#endif
    TRACE(("%6d M %p %d\n",malloc_event++,result,size));

    return result;
}

void *_PR_UnlockedMemalign(size_t alignment, size_t size)
{
    void *result;

    /*
     * alignment has to be a power of 2
     */

    if ((size <= alignment) && (alignment <= malloc_maxsize))
		size = alignment;
    else
           	size += alignment - 1;

    /* Round up to a multiple of 8 bytes */
    if (size & 7) {
	size = size + 8 - (size & 7);
    }

    if (!initialized)
	malloc_init();

#ifdef SANITY
    if (suicide)
	abort();
#endif

    if (size <= malloc_maxsize)
	result =  malloc_bytes(size);
    else
	result =  malloc_pages(size);
#ifdef SANITY
    if (malloc_abort && !result)
	wrterror("malloc() returns NULL\n");
#endif
    TRACE(("%6d A %p %d\n",malloc_event++,result,size));

    if ((u_long)result & (alignment - 1))
    	return ((void *)(((u_long)result + alignment)  & ~(alignment - 1)));
    else
    	return result;
}

void *_PR_UnlockedCalloc(size_t n, size_t nelem)
{
    void *p;

    /* Compute total size and then round up to a double word amount */
    n *= nelem;
    if (n & 7) {
	n = n + 8 - (n & 7);
    }

    /* Get the memory */
    p = _PR_UnlockedMalloc(n);
    if (p) {
	/* Zero it */
	memset(p, 0, n);
    }
    return p;
}

/*
 * Change an allocation's size
 */
void *_PR_UnlockedRealloc(void *ptr, size_t size)
{
    void *p;
    u_long osize,page,index,tmp_index;
    struct pginfo **mp;

    if (!initialized)
	malloc_init();

#ifdef SANITY
    if (suicide)
	PR_Abort();
#endif

    /* used as free() */
    TRACE(("%6d R %p %d\n",malloc_event++, ptr, size));
    if (ptr && !size) {
	_PR_UnlockedFree(ptr);
	return _PR_UnlockedMalloc (1);
    }

    /* used as malloc() */
    if (!ptr) {
	p = _PR_UnlockedMalloc(size);
	return p;
    }

    /* Find the page directory entry for the page in question */
    page = (u_long)ptr >> malloc_pageshift;
    index = page - malloc_origo;

    /*
     * check if memory was allocated by memalign
     */
    tmp_index = index;
    while (page_dir[tmp_index] == MALLOC_FOLLOW)
	tmp_index--;
    if (tmp_index != index) {
	/*
	 * memalign-allocated memory
	 */
	index = tmp_index;
	page = index + malloc_origo;
	ptr = (void *) (page << malloc_pageshift);
    }
    TRACE(("%6d R2 %p %d\n",malloc_event++, ptr, size));

    /* make sure it makes sense in some fashion */
    if (index < malloc_pageshift || index > last_index) {
#ifdef SANITY
	wrtwarning("junk pointer passed to realloc()\n");
#endif
	return 0;
    }

    /* find the size of that allocation, and see if we need to relocate */
    mp = &page_dir[index];
    if (*mp == MALLOC_FIRST) {
	osize = malloc_pagesize;
	while (mp[1] == MALLOC_FOLLOW) {
	    osize += malloc_pagesize;
	    mp++;
	}
        if (!malloc_realloc &&
		size < osize &&
		size > malloc_maxsize &&
	    size > (osize - malloc_pagesize)) {
	    return ptr;
	}
    } else if (*mp >= MALLOC_MAGIC) {
	osize = (*mp)->size;
	if (!malloc_realloc &&
		size < osize &&
	    (size > (*mp)->size/2 || (*mp)->size == malloc_minsize)) {
	    return ptr;
	}
    } else {
#ifdef SANITY
	wrterror("realloc() of wrong page.\n");
#endif
    }

    /* try to reallocate */
    p = _PR_UnlockedMalloc(size);

    if (p) {
	/* copy the lesser of the two sizes */
	if (osize < size)
	    memcpy(p,ptr,osize);
	else
	    memcpy(p,ptr,size);
	_PR_UnlockedFree(ptr);
    }
#ifdef DEBUG
    else if (malloc_abort)
	wrterror("realloc() returns NULL\n");
#endif

    return p;
}

/*
 * Free a sequence of pages
 */

static void
free_pages(char *ptr, u_long page, int index, struct pginfo *info)
{
    int i;
    struct pgfree *pf,*pt;
    u_long l;
    char *tail;

    TRACE(("%6d FP %p %d\n",malloc_event++, ptr, page));
    /* Is it free already ? */
    if (info == MALLOC_FREE) {
#ifdef SANITY
	wrtwarning("freeing free page at %p.\n", ptr);
#endif
	return;
    }

#ifdef SANITY
    /* Is it not the right place to begin ? */
    if (info != MALLOC_FIRST)
	wrterror("freeing wrong page.\n");

    /* Is this really a pointer to a page ? */
    if ((u_long)ptr & malloc_pagemask)
	wrterror("freeing messed up page pointer.\n");
#endif

    /* Count how many pages it is anyway */
    page_dir[index] = MALLOC_FREE;
    for (i = 1; page_dir[index+i] == MALLOC_FOLLOW; i++)
	page_dir[index + i] = MALLOC_FREE;

    l = i << malloc_pageshift;

    tail = ptr+l;

    /* add to free-list */
    if (!px)
	px = (struct pgfree*)_PR_UnlockedMalloc(sizeof *pt);
    /* XXX check success */
    px->page = ptr;
    px->end =  tail;
    px->size = l;
    if (!free_list.next) {
	px->next = free_list.next;
	px->prev = &free_list;
	free_list.next = px;
	pf = px;
	px = 0;
    } else {
	tail = ptr+l;
	for(pf = free_list.next; pf->next && pf->end < ptr; pf = pf->next)
	    ;
	for(; pf; pf = pf->next) {
	    if (pf->end == ptr ) {
		/* append to entry */
		pf->end += l;
		pf->size += l;
		if (pf->next && pf->end == pf->next->page ) {
		    pt = pf->next;
		    pf->end = pt->end;
		    pf->size += pt->size;
		    pf->next = pt->next;
		    if (pf->next)
			pf->next->prev = pf;
		    _PR_UnlockedFree(pt);
		}
	    } else if (pf->page == tail) {
		/* prepend to entry */
		pf->size += l;
		pf->page = ptr;
	    } else if (pf->page > ptr) {
		px->next = pf;
		px->prev = pf->prev;
		pf->prev = px;
		px->prev->next = px;
		pf = px;
		px = 0;
	    } else if (!pf->next) {
		px->next = 0;
		px->prev = pf;
		pf->next = px;
		pf = px;
		px = 0;
	    } else {
		continue;
	    }
	    break;
	}
    }
    if (!pf->next &&
      pf->size > malloc_cache &&
      pf->end == malloc_brk &&
      malloc_brk == (void*)sbrk(0)) {
	pf->end = pf->page + malloc_cache;
	pf->size = malloc_cache;
	TRACE(("%6d U %p %d\n",malloc_event++,pf->end,pf->end - pf->page));
	brk(pf->end);
	malloc_brk = pf->end;
	/* Find the page directory entry for the page in question */
	page = (u_long)pf->end >> malloc_pageshift;
	index = page - malloc_origo;
	/* Now update the directory */
	for(i=index;i <= last_index;)
	    page_dir[i++] = MALLOC_NOT_MINE;
	last_index = index - 1;
    }
}

/*
 * Free a chunk, and possibly the page it's on, if the page becomes empty.
 */

static void
free_bytes(void *ptr, u_long page, int index, struct pginfo *info)
{
    int i;
    struct pginfo **mp;
    void *vp;

    /* Make sure that pointer is multiplum of chunk-size */
#ifdef SANITY
    if ((u_long)ptr & (info->size - 1))
	wrterror("freeing messed up chunk pointer\n");
#endif

    /* Find the chunk number on the page */
    i = ((u_long)ptr & malloc_pagemask) >> info->shift;

    /* See if it's free already */
    if (tst_bit(info,i)) {
#ifdef SANITY
	wrtwarning("freeing free chunk at %p\n", ptr);
#endif
	return;
    }

    /* Mark it free */
    set_bit(info,i);
    info->free++;

    /* If the page was full before, we need to put it on the queue now */
    if (info->free == 1) {
	mp = page_dir + info->shift;
	while (*mp && (*mp)->next && (*mp)->next->page < info->page)
	    mp = &(*mp)->next;
	info->next = *mp;
	*mp = info;
	return;
    }

    /* If this page isn't empty, don't do anything. */
    if (info->free != info->total)
	return;

    /* We may want to keep at least one page of each size chunks around.  */
    mp = page_dir + info->shift;
    if (0 && (*mp == info) && !info->next)
	return;

    /* Find & remove this page in the queue */
    while (*mp != info) {
	mp = &((*mp)->next);
#ifdef EXTRA_SANITY
	if (!*mp) {
		TRACE(("%6d !q %p\n",malloc_event++,info));
		wrterror("Not on queue\n");
	}
#endif
    }
    *mp = info->next;

    /* Free the page & the info structure if need be */
    set_pgdir(info->page,MALLOC_FIRST);
    if((void*)info->page == (void*)info) {
	_PR_UnlockedFree(info->page);
    } else {
	vp = info->page;
	_PR_UnlockedFree(info);
	_PR_UnlockedFree(vp);
    }
}

void _PR_UnlockedFree(void *ptr)
{
    u_long page;
    struct pginfo *info;
    int index, tmp_index;

    TRACE(("%6d F %p\n",malloc_event++,ptr));
    /* This is legal */
    if (!ptr)
	return;

#ifdef SANITY
    /* There wouldn't be anything to free */
    if (!initialized) {
	wrtwarning("free() called before malloc() ever got called\n");
	return;
    }
#endif

#ifdef SANITY
    if (suicide)
	PR_Abort();
#endif

    /* Find the page directory entry for the page in question */
    page = (u_long)ptr >> malloc_pageshift;
    index = page - malloc_origo;

    /*
     * check if memory was allocated by memalign
     */
    tmp_index = index;
    while (page_dir[tmp_index] == MALLOC_FOLLOW)
	tmp_index--;
    if (tmp_index != index) {
	/*
	 * memalign-allocated memory
	 */
	index = tmp_index;
	page = index + malloc_origo;
	ptr = (void *) (page << malloc_pageshift);
    }
    /* make sure it makes sense in some fashion */
    if (index < malloc_pageshift) {
#ifdef SANITY
	wrtwarning("junk pointer %p (low) passed to free()\n", ptr);
#endif
	return;
    }
    if (index > last_index) {
#ifdef SANITY
	wrtwarning("junk pointer %p (high) passed to free()\n", ptr);
#endif
	return;
    }

    /* handle as page-allocation or chunk allocation */
    info = page_dir[index];
    if (info < MALLOC_MAGIC)
        free_pages((char*)ptr, page, index, info);
    else
	free_bytes(ptr,page,index,info);
    return;
}
#endif /* _PR_OVERRIDE_MALLOC */


text:
from process import Process
from process import Command

class Wait(Process):

    """
    Example:

        ''' wait
            Time: 30s
        '''

    """

    params_rules = {
        'Time': ['time', False, None],
    }

    def points(self):
        return [Command('Wait', self.time)]

redpajama_set_name: RedPajamaGithub


text:
Add here your description, what are you changing and why

### Notes
Add here any extra notes like `this is a breaking change` or `adding a new dependency` or leave it as `N/A`


text:
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements. See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership. The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License. You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied. See the License for the
  specific language governing permissions and limitations
  under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jlink-plugin-cli-options-compress2</artifactId>
  <version>99.0</version>
  <packaging>jlink</packaging>
  <name>Maven</name>
  <url>http://maven.apache.org</url>
  <description>Test JLink CLI Options --compress 2.</description>
  <properties>
    <maven.compiler.source>1.9</maven.compiler.source>
    <maven.compiler.target>1.9</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>localhost</groupId>
      <artifactId>first-jar-module-info</artifactId>
      <version>99.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>1.9</source>
          <target>1.9</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jlink-plugin</artifactId>
        <version>@project.version@</version>
        <extensions>true</extensions>
        <configuration>
          <compress>2</compress>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>


text: gTestsubsuite = 'ExecutionContexts';


text:
template <typename T>
void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
  static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
  if (size <= fixed_size) return;
  const T value = assign_from_buf<T>(data);
  data += fixed_size;
  size -= fixed_size;

  if (arg_name_size <= 0 || arg_name_size >= size) return;
  data_to_string arg_name(data, arg_name_size, true);
  data += arg_name_size;
  size -= arg_name_size;

  data_to_string format_str(data, size);
  try {
#if FMT_FUZZ_FORMAT_TO_STRING
    std::string message =
        fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
#else
    fmt::memory_buffer out;
    fmt::format_to(std::back_inserter(out), format_str.get(),
                   fmt::arg(arg_name.data(), value));
#endif
  } catch (std::exception&) {
  }
}

// For dynamic dispatching to an explicit instantiation.
template <typename Callback> void invoke(int type, Callback callback) {
  switch (type) {
  case 0:
    callback(bool());
    break;
  case 1:
    callback(char());
    break;
  case 2:
    using sc = signed char;
    callback(sc());
    break;
  case 3:
    using uc = unsigned char;
    callback(uc());
    break;
  case 4:
    callback(short());
    break;
  case 5:
    using us = unsigned short;
    callback(us());
    break;
  case 6:
    callback(int());
    break;
  case 7:
    callback(unsigned());
    break;
  case 8:
    callback(long());
    break;
  case 9:
    using ul = unsigned long;
    callback(ul());
    break;
  case 10:
    callback(float());
    break;
  case 11:
    callback(double());
    break;
  case 12:
    using LD = long double;
    callback(LD());
    break;
  }
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  if (size <= 3) return 0;

  // Switch types depending on the first byte of the input.
  const auto type = data[0] & 0x0F;
  const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
  data++;
  size--;

  invoke(type, [=](auto arg) {
    invoke_fmt<decltype(arg)>(data, size, arg_name_size);
  });
  return 0;
}


text:
package org.apache.hadoop.hbase.backup.impl;

import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BACKUP_LIST_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BANDWIDTH;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_BANDWIDTH_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_KEEP;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_KEEP_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_LIST;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PATH;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_PATH_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_RECORD_NUMBER;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_RECORD_NUMBER_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET_BACKUP_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_LIST_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_WORKERS;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_WORKERS_DESC;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME_DESC;

import java.io.IOException;
import java.net.URI;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.BackupAdmin;
import org.apache.hadoop.hbase.backup.BackupInfo;
import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
import org.apache.hadoop.hbase.backup.BackupRequest;
import org.apache.hadoop.hbase.backup.BackupRestoreConstants;
import org.apache.hadoop.hbase.backup.BackupRestoreConstants.BackupCommand;
import org.apache.hadoop.hbase.backup.BackupType;
import org.apache.hadoop.hbase.backup.HBackupFileSystem;
import org.apache.hadoop.hbase.backup.util.BackupSet;
import org.apache.hadoop.hbase.backup.util.BackupUtils;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.yetus.audience.InterfaceAudience;

import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;

/**
 * General backup commands, options and usage messages
 */
@InterfaceAudience.Private
public final class BackupCommands {
  public final static String INCORRECT_USAGE = "Incorrect usage";

  public final static String TOP_LEVEL_NOT_ALLOWED =
    "Top level (root) folder is not allowed to be a backup destination";

  public static final String USAGE = "Usage: hbase backup COMMAND [command-specific arguments]\n"
    + "where COMMAND is one of:\n" + "  create     create a new backup image\n"
    + "  delete     delete an existing backup image\n"
    + "  describe   show the detailed information of a backup image\n"
    + "  history    show history of all successful backups\n"
    + "  progress   show the progress of the latest backup request\n"
    + "  set        backup set management\n" + "  repair     repair backup system table\n"
    + "  merge      merge backup images\n"
    + "Run \'hbase backup COMMAND -h\' to see help message for each command\n";

  public static final String CREATE_CMD_USAGE =
    "Usage: hbase backup create <type> <backup_path> [options]\n"
      + "  type           \"full\" to create a full backup image\n"
      + "                 \"incremental\" to create an incremental backup image\n"
      + "  backup_path     Full path to store the backup image\n";

  public static final String PROGRESS_CMD_USAGE = "Usage: hbase backup progress <backup_id>\n"
    + "  backup_id       Backup image id (optional). If no id specified, the command will show\n"
    + "                  progress for currently running backup session.";
  public static final String NO_INFO_FOUND = "No info was found for backup id: ";
  public static final String NO_ACTIVE_SESSION_FOUND = "No active backup sessions found.";

  public static final String DESCRIBE_CMD_USAGE =
    "Usage: hbase backup describe <backup_id>\n" + "  backup_id       Backup image id\n";

  public static final String HISTORY_CMD_USAGE = "Usage: hbase backup history [options]";

  public static final String DELETE_CMD_USAGE = "Usage: hbase backup delete [options]";

  public static final String REPAIR_CMD_USAGE = "Usage: hbase backup repair\n";

  public static final String SET_CMD_USAGE = "Usage: hbase backup set COMMAND [name] [tables]\n"
    + "  name            Backup set name\n" + "  tables          Comma separated list of tables.\n"
    + "COMMAND is one of:\n" + "  add             add tables to a set, create a set if needed\n"
    + "  remove          remove tables from a set\n"
    + "  list            list all backup sets in the system\n" + "  describe        describe set\n"
    + "  delete          delete backup set\n";
  public static final String MERGE_CMD_USAGE = "Usage: hbase backup merge [backup_ids]\n"
    + "  backup_ids      Comma separated list of backup image ids.\n";

  public static final String USAGE_FOOTER = "";

  public static abstract class Command extends Configured {
    CommandLine cmdline;
    Connection conn;

    Command(Configuration conf) {
      if (conf == null) {
        conf = HBaseConfiguration.create();
      }
      setConf(conf);
    }

    public void execute() throws IOException {
      if (cmdline.hasOption("h") || cmdline.hasOption("help")) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      // Create connection
      conn = ConnectionFactory.createConnection(getConf());
      if (requiresNoActiveSession()) {
        // Check active session
        try (BackupSystemTable table = new BackupSystemTable(conn)) {
          List<BackupInfo> sessions = table.getBackupInfos(BackupState.RUNNING);

          if (sessions.size() > 0) {
            System.err.println("Found backup session in a RUNNING state: ");
            System.err.println(sessions.get(0));
            System.err.println("This may indicate that a previous session has failed abnormally.");
            System.err.println("In this case, backup recovery is recommended.");
            throw new IOException("Active session found, aborted command execution");
          }
        }
      }
      if (requiresConsistentState()) {
        // Check failed delete
        try (BackupSystemTable table = new BackupSystemTable(conn)) {
          String[] ids = table.getListOfBackupIdsFromDeleteOperation();

          if (ids != null && ids.length > 0) {
            System.err.println("Found failed backup DELETE coommand. ");
            System.err.println("Backup system recovery is required.");
            throw new IOException("Failed backup DELETE found, aborted command execution");
          }

          ids = table.getListOfBackupIdsFromMergeOperation();
          if (ids != null && ids.length > 0) {
            System.err.println("Found failed backup MERGE coommand. ");
            System.err.println("Backup system recovery is required.");
            throw new IOException("Failed backup MERGE found, aborted command execution");
          }
        }
      }
    }

    public void finish() throws IOException {
      if (conn != null) {
        conn.close();
      }
    }

    protected abstract void printUsage();

    /**
     * The command can't be run if active backup session is in progress
     * @return true if no active sessions are in progress
     */
    protected boolean requiresNoActiveSession() {
      return false;
    }

    /**
     * Command requires consistent state of a backup system Backup system may become inconsistent
     * because of an abnormal termination of a backup session or delete command
     * @return true, if yes
     */
    protected boolean requiresConsistentState() {
      return false;
    }
  }

  private BackupCommands() {
    throw new AssertionError("Instantiating utility class...");
  }

  public static Command createCommand(Configuration conf, BackupCommand type, CommandLine cmdline) {
    Command cmd;
    switch (type) {
      case CREATE:
        cmd = new CreateCommand(conf, cmdline);
        break;
      case DESCRIBE:
        cmd = new DescribeCommand(conf, cmdline);
        break;
      case PROGRESS:
        cmd = new ProgressCommand(conf, cmdline);
        break;
      case DELETE:
        cmd = new DeleteCommand(conf, cmdline);
        break;
      case HISTORY:
        cmd = new HistoryCommand(conf, cmdline);
        break;
      case SET:
        cmd = new BackupSetCommand(conf, cmdline);
        break;
      case REPAIR:
        cmd = new RepairCommand(conf, cmdline);
        break;
      case MERGE:
        cmd = new MergeCommand(conf, cmdline);
        break;
      case HELP:
      default:
        cmd = new HelpCommand(conf, cmdline);
        break;
    }
    return cmd;
  }

  static int numOfArgs(String[] args) {
    if (args == null) {
      return 0;
    }

    return args.length;
  }

  public static class CreateCommand extends Command {
    CreateCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    protected boolean requiresNoActiveSession() {
      return true;
    }

    @Override
    protected boolean requiresConsistentState() {
      return true;
    }

    @Override
    public void execute() throws IOException {
      if (cmdline == null || cmdline.getArgs() == null) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      String[] args = cmdline.getArgs();
      if (args.length != 3) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      if (
        !BackupType.FULL.toString().equalsIgnoreCase(args[1])
          && !BackupType.INCREMENTAL.toString().equalsIgnoreCase(args[1])
      ) {
        System.out.println("ERROR: invalid backup type: " + args[1]);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      if (!verifyPath(args[2])) {
        System.out.println("ERROR: invalid backup destination: " + args[2]);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      String targetBackupDir = args[2];
      // Check if backup destination is top level (root) folder - not allowed
      if (isRootFolder(targetBackupDir)) {
        throw new IOException(TOP_LEVEL_NOT_ALLOWED);
      }
      String tables;

      // Check if we have both: backup set and list of tables
      if (cmdline.hasOption(OPTION_TABLE) && cmdline.hasOption(OPTION_SET)) {
        System.out
          .println("ERROR: You can specify either backup set or list" + " of tables, but not both");
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      // Creates connection
      super.execute();
      // Check backup set
      String setName = null;
      if (cmdline.hasOption(OPTION_SET)) {
        setName = cmdline.getOptionValue(OPTION_SET);
        tables = getTablesForSet(setName);

        if (tables == null) {
          System.out
            .println("ERROR: Backup set '" + setName + "' is either empty or does not exist");
          printUsage();
          throw new IOException(INCORRECT_USAGE);
        }
      } else {
        tables = cmdline.getOptionValue(OPTION_TABLE);
      }
      int bandwidth = cmdline.hasOption(OPTION_BANDWIDTH)
        ? Integer.parseInt(cmdline.getOptionValue(OPTION_BANDWIDTH))
        : -1;
      int workers = cmdline.hasOption(OPTION_WORKERS)
        ? Integer.parseInt(cmdline.getOptionValue(OPTION_WORKERS))
        : -1;

      if (cmdline.hasOption(OPTION_YARN_QUEUE_NAME)) {
        String queueName = cmdline.getOptionValue(OPTION_YARN_QUEUE_NAME);
        // Set system property value for MR job
        System.setProperty("mapreduce.job.queuename", queueName);
      }

      try (BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        BackupRequest.Builder builder = new BackupRequest.Builder();
        BackupRequest request = builder.withBackupType(BackupType.valueOf(args[1].toUpperCase()))
          .withTableList(
            tables != null ? Lists.newArrayList(BackupUtils.parseTableNames(tables)) : null)
          .withTargetRootDir(targetBackupDir).withTotalTasks(workers)
          .withBandwidthPerTasks(bandwidth).withBackupSetName(setName).build();
        String backupId = admin.backupTables(request);
        System.out.println("Backup session " + backupId + " finished. Status: SUCCESS");
      } catch (IOException e) {
        System.out.println("Backup session finished. Status: FAILURE");
        throw e;
      }
    }

    private boolean isRootFolder(String targetBackupDir) {
      Path p = new Path(targetBackupDir);
      return p.isRoot();
    }

    private boolean verifyPath(String path) {
      try {
        Path p = new Path(path);
        Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create();
        URI uri = p.toUri();

        if (uri.getScheme() == null) {
          return false;
        }

        FileSystem.get(uri, conf);
        return true;
      } catch (Exception e) {
        return false;
      }
    }

    private String getTablesForSet(String name) throws IOException {
      try (final BackupSystemTable table = new BackupSystemTable(conn)) {
        List<TableName> tables = table.describeBackupSet(name);

        if (tables == null) {
          return null;
        }

        return StringUtils.join(tables, BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(CREATE_CMD_USAGE);
      Options options = new Options();
      options.addOption(OPTION_WORKERS, true, OPTION_WORKERS_DESC);
      options.addOption(OPTION_BANDWIDTH, true, OPTION_BANDWIDTH_DESC);
      options.addOption(OPTION_SET, true, OPTION_SET_BACKUP_DESC);
      options.addOption(OPTION_TABLE, true, OPTION_TABLE_LIST_DESC);
      options.addOption(OPTION_YARN_QUEUE_NAME, true, OPTION_YARN_QUEUE_NAME_DESC);
      options.addOption(OPTION_DEBUG, false, OPTION_DEBUG_DESC);

      HelpFormatter helpFormatter = new HelpFormatter();
      helpFormatter.setLeftPadding(2);
      helpFormatter.setDescPadding(8);
      helpFormatter.setWidth(100);
      helpFormatter.setSyntaxPrefix("Options:");
      helpFormatter.printHelp(" ", null, options, USAGE_FOOTER);
    }
  }

  public static class HelpCommand extends Command {
    HelpCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {
      if (cmdline == null) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      String[] args = cmdline.getArgs();
      if (args == null || args.length == 0) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      if (args.length != 2) {
        System.out.println("ERROR: Only supports help message of a single command type");
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      String type = args[1];

      if (BackupCommand.CREATE.name().equalsIgnoreCase(type)) {
        System.out.println(CREATE_CMD_USAGE);
      } else if (BackupCommand.DESCRIBE.name().equalsIgnoreCase(type)) {
        System.out.println(DESCRIBE_CMD_USAGE);
      } else if (BackupCommand.HISTORY.name().equalsIgnoreCase(type)) {
        System.out.println(HISTORY_CMD_USAGE);
      } else if (BackupCommand.PROGRESS.name().equalsIgnoreCase(type)) {
        System.out.println(PROGRESS_CMD_USAGE);
      } else if (BackupCommand.DELETE.name().equalsIgnoreCase(type)) {
        System.out.println(DELETE_CMD_USAGE);
      } else if (BackupCommand.SET.name().equalsIgnoreCase(type)) {
        System.out.println(SET_CMD_USAGE);
      } else {
        System.out.println("Unknown command : " + type);
        printUsage();
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(USAGE);
    }
  }

  public static class DescribeCommand extends Command {
    DescribeCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {
      if (cmdline == null || cmdline.getArgs() == null) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      String[] args = cmdline.getArgs();
      if (args.length != 2) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      super.execute();

      String backupId = args[1];
      try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) {
        BackupInfo info = sysTable.readBackupInfo(backupId);
        if (info == null) {
          System.out.println("ERROR: " + backupId + " does not exist");
          printUsage();
          throw new IOException(INCORRECT_USAGE);
        }
        System.out.println(info.getShortDescription());
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(DESCRIBE_CMD_USAGE);
    }
  }

  public static class ProgressCommand extends Command {
    ProgressCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {

      if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length == 1) {
        System.out.println(
          "No backup id was specified, " + "will retrieve the most recent (ongoing) session");
      }
      String[] args = cmdline == null ? null : cmdline.getArgs();
      if (args != null && args.length > 2) {
        System.err.println("ERROR: wrong number of arguments: " + args.length);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      super.execute();

      String backupId = (args == null || args.length <= 1) ? null : args[1];
      try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) {
        BackupInfo info = null;

        if (backupId != null) {
          info = sysTable.readBackupInfo(backupId);
        } else {
          List<BackupInfo> infos = sysTable.getBackupInfos(BackupState.RUNNING);
          if (infos != null && infos.size() > 0) {
            info = infos.get(0);
            backupId = info.getBackupId();
            System.out.println("Found ongoing session with backupId=" + backupId);
          }
        }
        int progress = info == null ? -1 : info.getProgress();
        if (progress < 0) {
          if (backupId != null) {
            System.out.println(NO_INFO_FOUND + backupId);
          } else {
            System.err.println(NO_ACTIVE_SESSION_FOUND);
          }
        } else {
          System.out.println(backupId + " progress=" + progress + "%");
        }
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(PROGRESS_CMD_USAGE);
    }
  }

  public static class DeleteCommand extends Command {
    DeleteCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    protected boolean requiresNoActiveSession() {
      return true;
    }

    @Override
    public void execute() throws IOException {

      if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length < 1) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      if (!cmdline.hasOption(OPTION_KEEP) && !cmdline.hasOption(OPTION_LIST)) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      super.execute();
      if (cmdline.hasOption(OPTION_KEEP)) {
        executeDeleteOlderThan(cmdline);
      } else if (cmdline.hasOption(OPTION_LIST)) {
        executeDeleteListOfBackups(cmdline);
      }
    }

    private void executeDeleteOlderThan(CommandLine cmdline) throws IOException {
      String value = cmdline.getOptionValue(OPTION_KEEP);
      int days = 0;
      try {
        days = Integer.parseInt(value);
      } catch (NumberFormatException e) {
        throw new IOException(value + " is not an integer number");
      }
      final long fdays = days;
      BackupInfo.Filter dateFilter = new BackupInfo.Filter() {
        @Override
        public boolean apply(BackupInfo info) {
          long currentTime = EnvironmentEdgeManager.currentTime();
          long maxTsToDelete = currentTime - fdays * 24 * 3600 * 1000;
          return info.getCompleteTs() <= maxTsToDelete;
        }
      };
      List<BackupInfo> history = null;
      try (final BackupSystemTable sysTable = new BackupSystemTable(conn);
        BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        history = sysTable.getBackupHistory(-1, dateFilter);
        String[] backupIds = convertToBackupIds(history);
        int deleted = admin.deleteBackups(backupIds);
        System.out.println("Deleted " + deleted + " backups. Total older than " + days + " days: "
          + backupIds.length);
      } catch (IOException e) {
        System.err.println("Delete command FAILED. Please run backup repair tool to restore backup "
          + "system integrity");
        throw e;
      }
    }

    private String[] convertToBackupIds(List<BackupInfo> history) {
      String[] ids = new String[history.size()];
      for (int i = 0; i < ids.length; i++) {
        ids[i] = history.get(i).getBackupId();
      }
      return ids;
    }

    private void executeDeleteListOfBackups(CommandLine cmdline) throws IOException {
      String value = cmdline.getOptionValue(OPTION_LIST);
      String[] backupIds = value.split(",");

      try (BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        int deleted = admin.deleteBackups(backupIds);
        System.out.println("Deleted " + deleted + " backups. Total requested: " + backupIds.length);
      } catch (IOException e) {
        System.err.println("Delete command FAILED. Please run backup repair tool to restore backup "
          + "system integrity");
        throw e;
      }

    }

    @Override
    protected void printUsage() {
      System.out.println(DELETE_CMD_USAGE);
      Options options = new Options();
      options.addOption(OPTION_KEEP, true, OPTION_KEEP_DESC);
      options.addOption(OPTION_LIST, true, OPTION_BACKUP_LIST_DESC);

      HelpFormatter helpFormatter = new HelpFormatter();
      helpFormatter.setLeftPadding(2);
      helpFormatter.setDescPadding(8);
      helpFormatter.setWidth(100);
      helpFormatter.setSyntaxPrefix("Options:");
      helpFormatter.printHelp(" ", null, options, USAGE_FOOTER);

    }
  }

  public static class RepairCommand extends Command {
    RepairCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {
      super.execute();

      String[] args = cmdline == null ? null : cmdline.getArgs();
      if (args != null && args.length > 1) {
        System.err.println("ERROR: wrong number of arguments: " + args.length);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create();
      try (final Connection conn = ConnectionFactory.createConnection(conf);
        final BackupSystemTable sysTable = new BackupSystemTable(conn)) {
        // Failed backup
        BackupInfo backupInfo;
        List<BackupInfo> list = sysTable.getBackupInfos(BackupState.RUNNING);
        if (list.size() == 0) {
          // No failed sessions found
          System.out.println("REPAIR status: no failed sessions found."
            + " Checking failed delete backup operation ...");
          repairFailedBackupDeletionIfAny(conn, sysTable);
          repairFailedBackupMergeIfAny(conn, sysTable);
          return;
        }
        backupInfo = list.get(0);
        // If this is a cancel exception, then we've already cleaned.
        // set the failure timestamp of the overall backup
        backupInfo.setCompleteTs(EnvironmentEdgeManager.currentTime());
        // set failure message
        backupInfo.setFailedMsg("REPAIR status: repaired after failure:\n" + backupInfo);
        // set overall backup status: failed
        backupInfo.setState(BackupState.FAILED);
        // compose the backup failed data
        String backupFailedData = "BackupId=" + backupInfo.getBackupId() + ",startts="
          + backupInfo.getStartTs() + ",failedts=" + backupInfo.getCompleteTs() + ",failedphase="
          + backupInfo.getPhase() + ",failedmessage=" + backupInfo.getFailedMsg();
        System.out.println(backupFailedData);
        TableBackupClient.cleanupAndRestoreBackupSystem(conn, backupInfo, conf);
        // If backup session is updated to FAILED state - means we
        // processed recovery already.
        sysTable.updateBackupInfo(backupInfo);
        sysTable.finishBackupExclusiveOperation();
        System.out.println("REPAIR status: finished repair failed session:\n " + backupInfo);
      }
    }

    private void repairFailedBackupDeletionIfAny(Connection conn, BackupSystemTable sysTable)
      throws IOException {
      String[] backupIds = sysTable.getListOfBackupIdsFromDeleteOperation();
      if (backupIds == null || backupIds.length == 0) {
        System.out.println("No failed backup DELETE operation found");
        // Delete backup table snapshot if exists
        BackupSystemTable.deleteSnapshot(conn);
        return;
      }
      System.out.println("Found failed DELETE operation for: " + StringUtils.join(backupIds));
      System.out.println("Running DELETE again ...");
      // Restore table from snapshot
      BackupSystemTable.restoreFromSnapshot(conn);
      // Finish previous failed session
      sysTable.finishBackupExclusiveOperation();
      try (BackupAdmin admin = new BackupAdminImpl(conn)) {
        admin.deleteBackups(backupIds);
      }
      System.out.println("DELETE operation finished OK: " + StringUtils.join(backupIds));
    }

    public static void repairFailedBackupMergeIfAny(Connection conn, BackupSystemTable sysTable)
      throws IOException {

      String[] backupIds = sysTable.getListOfBackupIdsFromMergeOperation();
      if (backupIds == null || backupIds.length == 0) {
        System.out.println("No failed backup MERGE operation found");
        // Delete backup table snapshot if exists
        BackupSystemTable.deleteSnapshot(conn);
        return;
      }
      System.out.println("Found failed MERGE operation for: " + StringUtils.join(backupIds));
      // Check if backup .tmp exists
      BackupInfo bInfo = sysTable.readBackupInfo(backupIds[0]);
      String backupRoot = bInfo.getBackupRootDir();
      FileSystem fs = FileSystem.get(new Path(backupRoot).toUri(), new Configuration());
      String backupId = BackupUtils.findMostRecentBackupId(backupIds);
      Path tmpPath = HBackupFileSystem.getBackupTmpDirPathForBackupId(backupRoot, backupId);
      if (fs.exists(tmpPath)) {
        // Move data back
        Path destPath = HBackupFileSystem.getBackupPath(backupRoot, backupId);
        if (!fs.delete(destPath, true)) {
          System.out.println("Failed to delete " + destPath);
        }
        boolean res = fs.rename(tmpPath, destPath);
        if (!res) {
          throw new IOException(
            "MERGE repair: failed  to rename from " + tmpPath + " to " + destPath);
        }
        System.out
          .println("MERGE repair: renamed from " + tmpPath + " to " + destPath + " res=" + res);
      } else {
        checkRemoveBackupImages(fs, backupRoot, backupIds);
      }
      // Restore table from snapshot
      BackupSystemTable.restoreFromSnapshot(conn);
      // Unlock backup system
      sysTable.finishBackupExclusiveOperation();
      // Finish previous failed session
      sysTable.finishMergeOperation();

      System.out.println("MERGE repair operation finished OK: " + StringUtils.join(backupIds));
    }

    private static void checkRemoveBackupImages(FileSystem fs, String backupRoot,
      String[] backupIds) throws IOException {
      String mergedBackupId = BackupUtils.findMostRecentBackupId(backupIds);
      for (String backupId : backupIds) {
        if (backupId.equals(mergedBackupId)) {
          continue;
        }
        Path path = HBackupFileSystem.getBackupPath(backupRoot, backupId);
        if (fs.exists(path)) {
          if (!fs.delete(path, true)) {
            System.out.println("MERGE repair removing: " + path + " - FAILED");
          } else {
            System.out.println("MERGE repair removing: " + path + " - OK");
          }
        }
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(REPAIR_CMD_USAGE);
    }
  }

  public static class MergeCommand extends Command {
    MergeCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    protected boolean requiresNoActiveSession() {
      return true;
    }

    @Override
    protected boolean requiresConsistentState() {
      return true;
    }

    @Override
    public void execute() throws IOException {
      super.execute();

      String[] args = cmdline == null ? null : cmdline.getArgs();
      if (args == null || (args.length != 2)) {
        System.err
          .println("ERROR: wrong number of arguments: " + (args == null ? null : args.length));
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      String[] backupIds = args[1].split(",");
      if (backupIds.length < 2) {
        String msg = "ERROR: can not merge a single backup image. "
          + "Number of images must be greater than 1.";
        System.err.println(msg);
        throw new IOException(msg);

      }
      Configuration conf = getConf() != null ? getConf() : HBaseConfiguration.create();
      try (final Connection conn = ConnectionFactory.createConnection(conf);
        final BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        admin.mergeBackups(backupIds);
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(MERGE_CMD_USAGE);
    }
  }

  public static class HistoryCommand extends Command {
    private final static int DEFAULT_HISTORY_LENGTH = 10;

    HistoryCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {
      int n = parseHistoryLength();
      final TableName tableName = getTableName();
      final String setName = getTableSetName();
      BackupInfo.Filter tableNameFilter = new BackupInfo.Filter() {
        @Override
        public boolean apply(BackupInfo info) {
          if (tableName == null) {
            return true;
          }

          List<TableName> names = info.getTableNames();
          return names.contains(tableName);
        }
      };
      BackupInfo.Filter tableSetFilter = new BackupInfo.Filter() {
        @Override
        public boolean apply(BackupInfo info) {
          if (setName == null) {
            return true;
          }

          String backupId = info.getBackupId();
          return backupId.startsWith(setName);
        }
      };
      Path backupRootPath = getBackupRootPath();
      List<BackupInfo> history;
      if (backupRootPath == null) {
        // Load from backup system table
        super.execute();
        try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) {
          history = sysTable.getBackupHistory(n, tableNameFilter, tableSetFilter);
        }
      } else {
        // load from backup FS
        history =
          BackupUtils.getHistory(getConf(), n, backupRootPath, tableNameFilter, tableSetFilter);
      }
      for (BackupInfo info : history) {
        System.out.println(info.getShortDescription());
      }
    }

    private Path getBackupRootPath() throws IOException {
      String value = null;
      try {
        value = cmdline.getOptionValue(OPTION_PATH);

        if (value == null) {
          return null;
        }

        return new Path(value);
      } catch (IllegalArgumentException e) {
        System.out.println("ERROR: Illegal argument for backup root path: " + value);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
    }

    private TableName getTableName() throws IOException {
      String value = cmdline.getOptionValue(OPTION_TABLE);

      if (value == null) {
        return null;
      }

      try {
        return TableName.valueOf(value);
      } catch (IllegalArgumentException e) {
        System.out.println("Illegal argument for table name: " + value);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
    }

    private String getTableSetName() {
      return cmdline.getOptionValue(OPTION_SET);
    }

    private int parseHistoryLength() throws IOException {
      String value = cmdline.getOptionValue(OPTION_RECORD_NUMBER);
      try {
        if (value == null) {
          return DEFAULT_HISTORY_LENGTH;
        }

        return Integer.parseInt(value);
      } catch (NumberFormatException e) {
        System.out.println("Illegal argument for history length: " + value);
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(HISTORY_CMD_USAGE);
      Options options = new Options();
      options.addOption(OPTION_RECORD_NUMBER, true, OPTION_RECORD_NUMBER_DESC);
      options.addOption(OPTION_PATH, true, OPTION_PATH_DESC);
      options.addOption(OPTION_TABLE, true, OPTION_TABLE_DESC);
      options.addOption(OPTION_SET, true, OPTION_SET_DESC);

      HelpFormatter helpFormatter = new HelpFormatter();
      helpFormatter.setLeftPadding(2);
      helpFormatter.setDescPadding(8);
      helpFormatter.setWidth(100);
      helpFormatter.setSyntaxPrefix("Options:");
      helpFormatter.printHelp(" ", null, options, USAGE_FOOTER);
    }
  }

  public static class BackupSetCommand extends Command {
    private final static String SET_ADD_CMD = "add";
    private final static String SET_REMOVE_CMD = "remove";
    private final static String SET_DELETE_CMD = "delete";
    private final static String SET_DESCRIBE_CMD = "describe";
    private final static String SET_LIST_CMD = "list";

    BackupSetCommand(Configuration conf, CommandLine cmdline) {
      super(conf);
      this.cmdline = cmdline;
    }

    @Override
    public void execute() throws IOException {
      // Command-line must have at least one element
      if (cmdline == null || cmdline.getArgs() == null || cmdline.getArgs().length < 2) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }

      String[] args = cmdline.getArgs();
      String cmdStr = args[1];
      BackupCommand cmd = getCommand(cmdStr);

      switch (cmd) {
        case SET_ADD:
          processSetAdd(args);
          break;
        case SET_REMOVE:
          processSetRemove(args);
          break;
        case SET_DELETE:
          processSetDelete(args);
          break;
        case SET_DESCRIBE:
          processSetDescribe(args);
          break;
        case SET_LIST:
          processSetList();
          break;
        default:
          break;
      }
    }

    private void processSetList() throws IOException {
      super.execute();

      // List all backup set names
      // does not expect any args
      try (BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        List<BackupSet> list = admin.listBackupSets();
        for (BackupSet bs : list) {
          System.out.println(bs);
        }
      }
    }

    private void processSetDescribe(String[] args) throws IOException {
      if (args == null || args.length != 3) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      super.execute();

      String setName = args[2];
      try (final BackupSystemTable sysTable = new BackupSystemTable(conn)) {
        List<TableName> tables = sysTable.describeBackupSet(setName);
        BackupSet set = tables == null ? null : new BackupSet(setName, tables);
        if (set == null) {
          System.out.println("Set '" + setName + "' does not exist.");
        } else {
          System.out.println(set);
        }
      }
    }

    private void processSetDelete(String[] args) throws IOException {
      if (args == null || args.length != 3) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      super.execute();

      String setName = args[2];
      try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        boolean result = admin.deleteBackupSet(setName);
        if (result) {
          System.out.println("Delete set " + setName + " OK.");
        } else {
          System.out.println("Set " + setName + " does not exist");
        }
      }
    }

    private void processSetRemove(String[] args) throws IOException {
      if (args == null || args.length != 4) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      super.execute();

      String setName = args[2];
      String[] tables = args[3].split(",");
      TableName[] tableNames = toTableNames(tables);
      try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        admin.removeFromBackupSet(setName, tableNames);
      }
    }

    private TableName[] toTableNames(String[] tables) {
      TableName[] arr = new TableName[tables.length];
      for (int i = 0; i < tables.length; i++) {
        arr[i] = TableName.valueOf(tables[i]);
      }
      return arr;
    }

    private void processSetAdd(String[] args) throws IOException {
      if (args == null || args.length != 4) {
        printUsage();
        throw new IOException(INCORRECT_USAGE);
      }
      super.execute();
      String setName = args[2];
      TableName[] tableNames =
        Splitter.on(',').splitToStream(args[3]).map(TableName::valueOf).toArray(TableName[]::new);
      try (final BackupAdminImpl admin = new BackupAdminImpl(conn)) {
        admin.addToBackupSet(setName, tableNames);
      }
    }

    private BackupCommand getCommand(String cmdStr) throws IOException {
      switch (cmdStr) {
        case SET_ADD_CMD:
          return BackupCommand.SET_ADD;
        case SET_REMOVE_CMD:
          return BackupCommand.SET_REMOVE;
        case SET_DELETE_CMD:
          return BackupCommand.SET_DELETE;
        case SET_DESCRIBE_CMD:
          return BackupCommand.SET_DESCRIBE;
        case SET_LIST_CMD:
          return BackupCommand.SET_LIST;
        default:
          System.out.println("ERROR: Unknown command for 'set' :" + cmdStr);
          printUsage();
          throw new IOException(INCORRECT_USAGE);
      }
    }

    @Override
    protected void printUsage() {
      System.out.println(SET_CMD_USAGE);
    }
  }
}


text:
layout: page
title:	mysql install
category: blog
description:
---
# Preface
mysql安装

# Install
安装后默认的密码帐号为空:
host=kw.get('host', 'localhost'),
port=kw.get('port', 3306),
user=kw.get('user', ''),
password=kw.get('password', ''),

## Via Source

	./configure --prefix=/usr/local/mysql
	make && make install

## Via yum

	yum install mysql-server mysql php-mysql
	yum install mariadb-server mariadb php-mysql

	#Set the MySQL service to start on boot
	chkconfig --levels 235 mysqld on

	#start
	service mysqld start
	systemctl start mariadb
	# systemctl enable mariadb
	# systemctl status mariadb

	# setting the root password, removing anonymous users, and so on.
	mysql_secure_installation
		Set root password? [Y/n] y        [设置root用户密码]
		...
		Remove anonymous users? [Y/n] y            [删除匿名用户]
		...
		Disallow root login remotely? [Y/n] n            [禁止root远程登录]
		...
		Remove test database and access to it? [Y/n] y       [删除test数据库]
		...
		Reload privilege tables now? [Y/n] y        [刷新权限]

	#Log into MySQL
	mysql -u root
		#Set the root user password for all local domains
		SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new-password');
		SET PASSWORD FOR 'root'@'localhost.localdomain' = PASSWORD('new-password');
		SET PASSWORD FOR 'root'@'127.0.0.1' = PASSWORD('new-password');
		#Drop the Any user
		DROP USER ''@'localhost';
		DROP USER ''@'localhost.localdomain';

	# 设置远程登录
	GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'IDENTIFIED BY '123456' WITH GRANT OPTION;
	FLUSH PRIVILEGES ;

### step by step

## via brew
在mac中安装 mysql

	$ brew install mariadb

## mycli
> http://mycli.net/install

	brew install mycli

for centos

	sudo yum install python-pip
	pip3 install mycli
	#sudo easy_install mycli

# Config

## Securing your MySQL installation
It is always recommended to secure your MySQL installation. To do this, type the following on your Terminal:

	$ mysql_secure_installation
	Set root password? [Y/n] Y
	Remove anonymous users? [Y/n] Y
	Disallow root login remotely? [Y/n] Y
	Remove test database and access to it? [Y/n] Y
	Reload privilege tables now? [Y/n] Y

## add user

	mysql> CREATE USER 'monty'@'localhost' IDENTIFIED BY 'passwd';
	mysql> GRANT ALL PRIVILEGES ON database.* TO 'monty'@'localhost' ;
	mysql> GRANT ALL PRIVILEGES ON database.* TO 'monty'@'localhost' WITH GRANT OPTION; # 带grant 权限

For more: http://dev.mysql.com/doc/refman/5.0/en/resetting-permissions.html

## update mysql

	sudo mysql_upgrade -uroot -p

# Reset Passwd

## forget root

### via init sql

	$ sudo service mysqld stop
	$ echo "UPDATE mysql.user SET Password=PASSWORD('123456') WHERE User='root'; FLUSH PRIVILEGES;" > init.sql
	$ mysqld_safe --init-file=init.sql

### via skip grant

	$ mysqld  --skip-grant-tables
	//or
	$ mysqld_safe  --skip-grant-tables
	> UPDATE mysql.user SET Password=PASSWORD('123456') WHERE User='root';
	> FLUSH PRIVILEGES;

## forget user Passwd
If u have root authority

	> SET PASSWORD FOR 'jeffrey'@'localhost' = PASSWORD('cleartext password');
	# current user
	> SET PASSWORD = PASSWORD('cleartext password');

## current user
select current_user();

# Admin

## Common options

	-h host
	--defaults-file=/path_to_my.cnf
	--no-defaults
	--print-defaults

## Start Stop

### On MAC OSX
To have launchd start mysql at login:
```
    ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents
```

Then to load mysql now:

    launchctl load/unload ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

Or, if you don't want/need launchctl, you can just run:

    mysql.server start/stop

### On Linux

	sudo service mysqld start/stop/restart
	# or
	mysqld_safe --user=mysql &

## mysqld & mysql_safe
mysql_safe is used to manager mysqld , it added some feature:
1. restarting the server when an error occurs
2. logging runtime information to an error log
3. specify a --mysqld or --mysqld-version option to mysqld_safe

	# view options
	mysqld --verbose --help
	mysqld --verbose --help | grep '^datadir'

	$ mysql -V
	mysql  Ver 15.1 Distrib 10.2.6-MariaDB, for osx10.11 (x86_64) using readline 5.1
	$ mysqld --print-defaults
	mysqld would have been started with the following arguments:

### params

	--safe-updates 防止没有where 的删除

### exec sql batch

	mysqld_safe --init_file=test.sql

	# 不执行没有where 子句的delete/update
	mysqld_safe --safe-update

### default table type
	mysqld_safe --default-table-type=innnodb

### debug

	> [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist
	mysql_install_db --user=mysql --datadir=/var/lib/mysql

	> Table 'mysql.plugin' doesn't exist
	mysqld_safe
	mysql_upgrade; # 升级数据库

	> mysql header
	sudo yum install -y mysql-devel

## my.conf

	sudo cp /usr/local/Cellar/mariadb/10.0.10/support-files/my-large.cnf /etc/my.cnf

### scope
包括:

	[client]
	[mysqld]
	[mysqldump]

### Find my.cnf

	mysqld --verbose --help | grep -A 1 "Default options"

### Connect

	# 并发访问最大连接数
	max_connections=151
	#mysql 的每个连接必须由主线程接收并委托给一个新线程处理, back_log 用于确定一个主线程的连接排队数
	back_log=150
	port=3306
	# 禁用dns
	skip_name_resolve
	# 限制为本地连接
	skip_networking

### autocomplete
tab autocomplete

	[mysql]
	auto-rehash

### log
log-slow-queries:

	[mysqld] only
	# 记录非索引查询(expire long_query_time)
	log-slow-queries = /tmp/mysql-slow.log
	log_queries_not_using_indexes=0

for mysql (>=5.6):

	# log slow queries (>long_query_time ) 可以用mysqldumpslow分析此日志
	[mysqld] only
	slow_query_log=1
	slow_query_log_file=/tmp/mysql-slow.log
	long_query_time=2
	log_queries_not_using_indexes = 1

### port

	#default
	[client]
	[mysqld]
	port		= 3306
	socket		= /tmp/mysql.sock

### network

	# fobidden dns (Only ip and locahost are allowed)
	skip_name_resolve
	# Only local app could connect mysqld
	skip_networking

### user

	$ mysqld_safe --user=mysql &

### data dir

	$ mysqld_safe --datadir=/usr/local/var/mysql --user=hilojack

find default datadir:

	$ mysqld --verbose --help |grep datadir

#### innodb datadir

	#default
	innodb_data_home_dir = /usr/local/var/mysql

可用shell 查看:

	mysqld --verbose --help | grep 'innodb-data-home-dir'

## status

### config variables

	//show config variables
	mysqladmin -uroot variables
	mysql> show VARIABLES;
	mysql> show VARIABLES like '%word%';

### server status

	//用`status` 或者 `\s` 获取服务器状态信息: uptime/ssl/socket/charset
	mysql>\s
	mysqladmin status
	// show system detail status
	mysql> show STATUS like '%word%';
	mysqladmin extended-status

### mysqladmin
mysqladmin 功能是mysql 的子集,主要用于完成: 监视服务状态、关闭守护进程

	mysqladmin [options] variables
	mysqladmin [options] status

Refer to `man mysqladmin`

#### flush 刷新

	mysqladmin flush-hosts # 刷新主机缓存,当接收到大量失败连接请求时(由max_connect_errors) 就需要清hosts 以解除对请求的阻塞
	mysqladmin flush-logs #关闭并重新打开所有的日志文件
	mysqladmin flush-status # 刷新状态
	mysqladmin flush-tables # 关闭打开的表
	mysqladmin refresh #flush-tables + flush-logs
	mysqladmin flush-threads # 清楚线程缓存
	mysqladmin flush-privilege #(alias to reload) 重新加载权限表,当使用了GRANT 或 REVOKE 命令就不需要

#### kill

	mysqladmin start-master #启动主服务器
	mysqladmin start-slave #启动从服务器

	mysqladmin kill id1,id2,...,idN #kill mysql进程id1...
	mysqladmin -uroot -p shutdown #关server

#### passwd

	mysqladmin password new-passwd
	/usr/bin/mysqladmin -u root password 'new-password'
	/usr/bin/mysqladmin -u root -h 49846707911b password 'new-password'

#### process

	mysqladmin processlist # 进程列表
	mysqladmin ping # 用ping 验证进程是否运行

## mysqlshow
列出数据库-表-字段:

	mysqlshow [options] [db [table [colume]]]

Example:

	mysqlshow 'test\_name'

## dump & import

	$ mysqldump -hroot -p123 source_db [table] | mysql -uUser -pPasswd dst_db; # 包含数据

### mysqldump
用于导出表结构或者数据. 默认导出的是'*.sql' 格式,输出到标准输出。默认包含 insert 和 create 语句

	mysqldump [opt] database [table] > db.sql
	mysqldump [opt] --databases [options] db1 db2 ...
	mysqldump [opt] --all-databases
		--no-create-info
		-d, --no-data
		--compact
			Produce less verbose output. This option enables the --skip-add-drop-table, --skip-add-locks,
			--skip-comments, --skip-disable-keys, and --skip-set-charset options.
		--add-drop-table
		--tab=backup_path
			Produce tab-separated text-format data files.
		--fields-terminated-by=str
			The string for separating column values (default: tab).
		--fields-enclosed-by=char
			The character within which to enclose column values (default: no character).
		-q quick
			Do not cache each query result

> 一定要加`-q`, 否则会占用大量的swap

Example:

	$ echo 'create database dst_db'| mysql -uUser -pPasswd #if no dst_db
	# or
	$ mysqladmin create dst_db
	$ mysqldump -hroot -p123 source_db | mysql -uUser -pPasswd dst_db; # 包含数据

### mysqlhotcopy
是优化的mysqldump 但只支持本机且只支持MyISAM/Archive. 这个我不用的

	mysqlhotcopy [options] --suffix=.sql db1 db2 ... dbN /backup/
	# 用posix正则匹配db
	mysqlhotcopy [options] db./^preg$/ /backup/

### mysqlimport
导入数据(.csv 数据而非.sql 数据)时,可以通过mysqlimport 为字段做定界:

	mysqlimport [options] --fields-terminated-by=\t dbname file1.txt [... fileN.txt]

Example:
1. First, create the directory for the output files and dump the database:

	shell> mkdir DUMPDIR
	shell> mysqldump --tab=DUMPDIR db_name # it will create *.sql(create tables), and *.txt(tab separator data for each table ) in DUMPDIR

Then transfer the files in the DUMPDIR directory to some corresponding directory on the target machine and load the files into MySQL there:

	shell> mysqladmin create db_name           # create database
	shell> cat DUMPDIR/*.sql | mysql db_name   # create tables in database
	shell> mysqlimport db_name DUMPDIR/*.txt   # load data into tables

## myisamchk
检查表是否有损坏(只能检查MyISAM, 且必須关闭守护进程):

	myisamchk [options] /path/to/db/table_name.MYI
		--check 简单检查
		--medium-check 中度
		--extend-check 扩展检查
		-i 查看扩展信息
		-r 尝试修复错误

## mysqlcheck
mysqlcheck用于检查所有类型的表, 不需要关闭守护进程:

	mysqlcheck [options] db [table]
	mysqlcheck [options] --databases db1 db2 ... dbN
	mysqlcheck [options] --all-databases
		-a 分析
		-r 修复
		-o 优化
	mysqlcheck -r db table

# mysql client

	mysql -uroot -hhost -pxx database
	echo cmds | mysql
	mysql -e cmd -e cmd

## which database

	//show which database is in use
	SELECT DATABASE();

## exec
mysql client 有自己的interactive cli 模式,这种模式下每条语句以";"结束,也可以标记`\G`, `\H`,或者别名`status` 结束
也有non-active 方式

### exec sql batch
除了Mysql_safe外,mysql 客户端本身也可以批量执行sql命令:

	mysql -uroot < file.sql
	mysql> source < file.sql

	# force to execute sql(ignore error)
	mysql -f < file.sql

### exec cmd

	mysql -uroot -e 'user test; select * from test;'

### auto complete
It support that use 'tab' to complete command

	mysql --auto-rehash

## format & shell

	#垂直显示
	mysql> select * form db.tb\G
	mysql --vertical
	#prompt
	mysql -uroot --prompt='\u@\h \d>'
	[mysql]
		prompt=\u@\h \d>
	#html
	mysql --html < a.sql > db.html
	#xml
	mysql -X < a.sql > db.xml

### pager results

	mysql --paper=less; #分页显示程序

在mysql cli中使用pager:

	> pager grep 'pattern';
	> pager less
	> pager cat > /dev/null
	> pager cat > /dev/null
	> pager vim -
	> pager tee -a a.txt

	//close pager
	> nopager

## log cmd

### log results only

    shell> mysql -e 'select * from tb limit 10' > output.txt
    mysql> pager cat - >> output.txt
    mysql> pager tee -a output.txt

The file `output.txt` is stored on server's datadir(On mac OSX: /usr/local/var/mysql/). And it cannot be an existing file

    mysql> select * from db into outfile 'output.txt'

### log stdout

#### log stdout via shell
`\T file.log` 用于记录标准输出(包含命令提示符,命令,结果),`\t` 则结束日志记录

	--tee=file_name
		Append a copy of output to the given file. This option works only in interactive mode.

#### log stdout via mysql command
`\T file.log` 用于记录标准输出(包含命令提示符,命令,结果),`\t` 则结束日志记录

	mysql>\T a.log
	mysql> exec command
	mysql>\t or notee

## FLUSH

	//重新加载权限表
	FLUSH PRIVILEGES; alias to 'reload';
	//刷新连接的主机列表
	FLUSH HOSTS;
	//关闭打开的表,终止对表的查询
	FLUSH TABLES tbl_name [, tbl_name] ...
	// 重置状态变量为0
	FLUSH STATUS
	//Close and reopen all log files;
	flush logs;

## privileges

### Remote Access MySQL

You have to put this as root:

	GRANT ALL PRIVILEGES ON *.* TO  'USERNAME'@'IP'  IDENTIFIED  BY  'PASSWORD';

where IP is the IP you want to allow acess and USERNAME is the user you use to connect.
If you want to allow access from any IP just put `%` instead of your IP .

Then you only have to put

	# 重新加载权限表
	FLUSH PRIVILEGES

Or restart mysql server and that's it

## protocol
	mysql --protocol={TCP|SOCKET|PIPE|MEMORY} (tcp 协议的话需要打开3306端口)

> mysql client will access mysql unix socket via locahost, tcp/ip via 127.0.0.1

## describe

	describe db.table;
	desc db.table;


text:
Ext.define('Todo.view.MethodCall', {
	extend: 'Ext.form.Panel',

	title: 'Direct method calls, exceptions',
	tplWriteMode: 'append',
	tpl: '{data}
',
	autoScroll: true,
	bodyPadding: 5,

	dockedItems: [ {
		xtype: 'toolbar',
		docked: 'top',
		items: [ {
			xtype: 'button',
			text: 'Test call, one parameter',
			handler: function(bt) {
				var me = bt.up('panel');
				todoServiceExt.testMe({
					test: true
				}, function(result, event) {
					// you can grab useful info from event
					var transaction = event.getTransaction(), status = event.status;
					console.log(transaction);
					me.updateContent('Status     : ' + status);
					me.updateContent('Result     : ' + result);
					me.updateContent('
');
				});
			}
		}, {
			xtype: 'button',
			text: 'Test call, empty parameter object',
			handler: function(bt) {
				var me = bt.up('panel');
				todoServiceExt.testMe({}, function(result, event) {
					console.log(event.getTransaction());
					me.updateContent('Status     : ' + event.status);
					me.updateContent('Result     : ' + result);
					me.updateContent('
');
				});
			}
		}, {
			xtype: 'button',
			text: 'Read table page',
			handler: function(bt) {
				var me = bt.up('panel');
				todoService.read({
					page: 1,
					start: 0,
					limit: 10
				}, function(result, event) {
					me.updateContent(Ext.encode(result));
					me.updateContent('
');
				});
			}
		}, {
			xtype: 'button',
			text: 'Exception',
			handler: function(bt) {
				var me = bt.up('panel');
				todoServiceExt.testException(function(result, event) {
					me.updateContent('Status   : ' + event.status);
					me.updateContent('Event    : ' + Ext.encode(event));
					me.updateContent('
');
				});
			}
		} ]
	} ],

	updateContent: function(content) {
		var me = this;
		me.update({
			data: content
		});
		me.body.scroll('b', 100000, true);
	}

});


text:
require 'spec_helper'

describe "Gift" do
  before :each do
    @user ||= User.create!(name: "John Doe")
    @gift = Gift.create!(amount: 2)
  end

  it "code length is 20" do
    @gift.code.length.should eq 20
  end

  it "redemption removes points from user" do
    @gift.redeem @user
    @user.points.should eq 3
  end

  it "can be redeemed if the user has enough points" do
    @another_gift = Gift.create!(amount: 5)
    @another_gift.redeem @user
    @user.points.should eq 0
  end

  it "marked as reusable can be redeemed by multiple users" do
    @another_user ||= User.create!(name: "Jane Smith")
    @reusable_gift = Gift.create!(amount: 4, reusable: true)
    @reusable_gift.redeem @user
    @reusable_gift.redeem @another_user
    @another_user.points.should eq 1
  end

  context 'validates' do
    it "returns false if it can not be redeemed" do
      @another_gift = Gift.create!(amount: 50)
      @another_gift.redeem(@user).should eq false
    end

    it "can not be redeemed if the user has not enough points" do
      @another_gift = Gift.create!(amount: 50)
      @another_gift.redeem @user
      @user.points.should eq 5
    end

    it "can be redeemed only once per user" do
      @gift.redeem @user
      @gift.redeem @user
      @gift.redeem @user
      @user.points.should eq 3
    end

    it "can be redeemed only once if it is disposable" do
      @another_user ||= User.create!(name: "Jane Smith")
      @gift.redeem @user
      @gift.redeem @another_user
      @another_user.points.should eq 5
    end

    it "can not be redeemed if it expired" do
      @another_gift = Gift.create!(amount: 5, expires_at: DateTime.now)
      @another_gift.redeem @user
      @user.points.should eq 5
    end

    it "can be redeemed if it expires in the future" do
      @another_gift = Gift.create!(amount: 3, expires_at: (DateTime.now + 1.day))
      @another_gift.redeem @user
      @user.points.should eq 2
    end

    it "can not be redeemed if custom validators returned false" do
      @expired_gift = Gift.create!(amount: 20000)
      @expired_gift.redeem @user
      @user.points.should eq 5
    end
  end

  context 'error' do
    it "can not be redeemed if the user has not enough points" do
      @another_gift = Gift.create!(amount: 50)
      @another_gift.redeem @user
      @another_gift.errors.messages.count.should eq 1
    end

    it "message specified if it is not redeemable" do
      @another_user ||= User.create!(name: "Jane Smith")
      @gift.redeem @user
      @gift.redeem @another_user
      @gift.errors.messages.count.should eq 1
    end

    it "message specified if it expired" do
      @another_gift = Gift.create!(amount: 5, expires_at: DateTime.now)
      @another_gift.redeem @user
      @another_gift.errors.messages.count.should eq 1
    end
  end

end


text:
package com.paulhammant.buildradiator.radiator.model;

public class WrongBuildState extends BuildRadiatorException {
    public WrongBuildState() {
        super("wrong build state");
    }
}


text:
iOS 7 style side menu with parallax effect inspired by Dribbble shots ([first](http://dribbble.com/shots/1116265-Instasave-iPhone-App) and [second](http://dribbble.com/shots/1114754-Social-Feed-iOS7)).

Since version 4.0 you can add menu view controllers on both left and right sides of your content view controller.

## Requirements
* Xcode 5 or higher
* Apple LLVM compiler
* iOS 6.0 or higher
* ARC

## Demo

Build and run the `RESideMenuExample` project in Xcode to see `RESideMenu` in action. For storyboards integration demo, build and run `RESideMenuStoryboardsExample`.

## Installation

### CocoaPods

The recommended approach for installating `RESideMenu` is via the [CocoaPods](http://cocoapods.org/) package manager, as it provides flexible dependency management and dead simple installation.
For best results, it is recommended that you install via CocoaPods >= **0.28.0** using Git >= **1.8.0** installed via Homebrew.

Install CocoaPods if not already available:

``` bash
$ [sudo] gem install cocoapods
$ pod setup
```

Change to the directory of your Xcode project:

``` bash
$ cd /path/to/MyProject
$ touch Podfile
$ edit Podfile
```

Edit your Podfile and add RESideMenu:

``` bash
platform :ios, '6.0'
pod 'RESideMenu', '~> 4.0.7'
```

Install into your Xcode project:

``` bash
$ pod install
```

Open your project in Xcode from the .xcworkspace file (not the usual project file)

``` bash
$ open MyProject.xcworkspace
```

Please note that if your installation fails, it may be because you are installing with a version of Git lower than CocoaPods is expecting. Please ensure that you are running Git >= **1.8.0** by executing `git --version`. You can get a full picture of the installation details by executing `pod install --verbose`.

### Manual Install

All you need to do is drop `RESideMenu` files into your project, and add `#include "RESideMenu.h"` to the top of classes that will use it.

## Example Usage

In your AppDelegate's `- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions` create the view controller and assign content and menu view controllers.

``` objective-c
// Create content and menu controllers
//
DEMONavigationController *navigationController = [[DEMONavigationController alloc] initWithRootViewController:[[DEMOHomeViewController alloc] init]];
DEMOLeftMenuViewController *leftMenuViewController = [[DEMOLeftMenuViewController alloc] init];
DEMORightMenuViewController *rightMenuViewController = [[DEMORightMenuViewController alloc] init];

// Create side menu controller
//
RESideMenu *sideMenuViewController = [[RESideMenu alloc] initWithContentViewController:navigationController
                                                                leftMenuViewController:leftMenuViewController
                                                               rightMenuViewController:rightMenuViewController];
sideMenuViewController.backgroundImage = [UIImage imageNamed:@"Stars"];

// Make it a root controller
//
self.window.rootViewController = sideMenuViewController;
```

Present the menu view controller:

```objective-c
[self.sideMenuViewController presentLeftMenuViewController];
```

or

```objective-c
[self.sideMenuViewController presentRightMenuViewController];
```

Switch content view controllers:

```objective-c
#import <RESideMenu/RESideMenu.h>

....

[self.sideMenuViewController setContentViewController:viewController animated:YES];
[self.sideMenuViewController hideMenuViewController];
```

## Storyboards Example

1. Create a subclass of `RESideMenu`. In this example we call it `DEMORootViewController`.
2. In the Storyboard designate the root view's owner as `DEMORootViewController`.
3. Make sure to `#import "RESideMenu.h"` in `DEMORootViewController.h`.
4. Add more view controllers to your Storyboard, and give them identifiers "leftMenuViewController", "rightMenuViewController" and "contentViewController". Note that in the new XCode the identifier is called "Storyboard ID" and can be found in the Identity inspector.
5. Add a method `awakeFromNib` to `DEMORootViewController.m` with the following code:

```objective-c
- (void)awakeFromNib
{
    self.contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"contentViewController"];
    self.leftMenuViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"leftMenuController"];
    self.rightMenuViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"rightMenuController"];
}
```

## Customization

You can customize the following properties of `RESideMenu`:

``` objective-c
@property (assign, readwrite, nonatomic) NSTimeInterval animationDuration;
@property (strong, readwrite, nonatomic) UIImage *backgroundImage;
@property (assign, readwrite, nonatomic) BOOL panGestureEnabled;
@property (assign, readwrite, nonatomic) BOOL panFromEdge;
@property (assign, readwrite, nonatomic) NSUInteger panMinimumOpenThreshold;
@property (assign, readwrite, nonatomic) BOOL interactivePopGestureRecognizerEnabled;
@property (assign, readwrite, nonatomic) BOOL scaleContentView;
@property (assign, readwrite, nonatomic) BOOL scaleBackgroundImageView;
@property (assign, readwrite, nonatomic) BOOL scaleMenuView;
@property (assign, readwrite, nonatomic) BOOL contentViewShadowEnabled;
@property (assign, readwrite, nonatomic) UIColor *contentViewShadowColor;
@property (assign, readwrite, nonatomic) CGSize contentViewShadowOffset;
@property (assign, readwrite, nonatomic) CGFloat contentViewShadowOpacity;
@property (assign, readwrite, nonatomic) CGFloat contentViewShadowRadius;
@property (assign, readwrite, nonatomic) CGFloat contentViewScaleValue;
@property (assign, readwrite, nonatomic) CGFloat contentViewInLandscapeOffsetCenterX;
@property (assign, readwrite, nonatomic) CGFloat contentViewInPortraitOffsetCenterX;
@property (assign, readwrite, nonatomic) CGFloat parallaxMenuMinimumRelativeValue;
@property (assign, readwrite, nonatomic) CGFloat parallaxMenuMaximumRelativeValue;
@property (assign, readwrite, nonatomic) CGFloat parallaxContentMinimumRelativeValue;
@property (assign, readwrite, nonatomic) CGFloat parallaxContentMaximumRelativeValue;
@property (assign, readwrite, nonatomic) CGAffineTransform menuViewControllerTransformation;
@property (assign, readwrite, nonatomic) BOOL parallaxEnabled;
@property (assign, readwrite, nonatomic) BOOL bouncesHorizontally;
@property (assign, readwrite, nonatomic) UIStatusBarStyle menuPreferredStatusBarStyle;
@property (assign, readwrite, nonatomic) BOOL menuPrefersStatusBarHidden;
```

If you set a backgroundImage, don't forget to set the Menu View Controller's background color to clear color.

You can implement `RESideMenuDelegate` protocol to receive the following messages:

```objective-c
- (void)sideMenu:(RESideMenu *)sideMenu didRecognizePanGesture:(UIPanGestureRecognizer *)recognizer;
- (void)sideMenu:(RESideMenu *)sideMenu willShowMenuViewController:(UIViewController *)menuViewController;
- (void)sideMenu:(RESideMenu *)sideMenu didShowMenuViewController:(UIViewController *)menuViewController;
- (void)sideMenu:(RESideMenu *)sideMenu willHideMenuViewController:(UIViewController *)menuViewController;
- (void)sideMenu:(RESideMenu *)sideMenu didHideMenuViewController:(UIViewController *)menuViewController;
```

## Contact

Roman Efimov

- https://github.com/romaonthego
- https://twitter.com/romaonthego
- romefimov@gmail.com

## License

RESideMenu is available under the MIT license.

Copyright © 2013 Roman Efimov.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.