diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2022-10-28 01:48:42 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2022-10-28 01:48:42 +0000 |
commit | 1ac3fbe1c9c07fed0c5124467c4aa1ca5ee933ec (patch) | |
tree | 0d67ba923261c9453ddb3aff383462c72bc801bb /src/main/java | |
parent | 90691382cf708cf1c3d8ab0797d3ce74ce691ce3 (diff) | |
download | jackcess-1ac3fbe1c9c07fed0c5124467c4aa1ca5ee933ec.tar.gz jackcess-1ac3fbe1c9c07fed0c5124467c4aa1ca5ee933ec.zip |
Fix edge case which can cause table to be considered corrupt. Fixes #156
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1385 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/main/java')
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java | 165 |
1 files changed, 87 insertions, 78 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java b/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java index c67bcba..6928cfa 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java @@ -36,7 +36,7 @@ public class UsageMap /** bit index value for an invalid page number */ private static final int INVALID_BIT_INDEX = -1; - + /** owning database */ private final DatabaseImpl _database; /** Page number of the map table declaration */ @@ -84,7 +84,7 @@ public class UsageMap public DatabaseImpl getDatabase() { return _database; } - + public JetFormat getFormat() { return getDatabase().getFormat(); } @@ -106,7 +106,7 @@ public class UsageMap int umapPageNum = ByteUtil.get3ByteInt(buf); return read(database, umapPageNum, umapRowNum, false); } - + /** * @param database database that contains this usage map * @param pageNum Page number that this usage map is contained in @@ -130,7 +130,7 @@ public class UsageMap pageChannel.readPage(tableBuffer, pageNum); short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format); int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format); - tableBuffer.limit(rowEnd); + tableBuffer.limit(rowEnd); byte mapType = tableBuffer.get(rowStart); UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart); rtn.initHandler(mapType, isGlobal); @@ -141,16 +141,16 @@ public class UsageMap throws IOException { if (mapType == MAP_TYPE_INLINE) { - _handler = (isGlobal ? new GlobalInlineHandler() : + _handler = (isGlobal ? new GlobalInlineHandler() : new InlineHandler()); } else if (mapType == MAP_TYPE_REFERENCE) { - _handler = (isGlobal ? new GlobalReferenceHandler() : + _handler = (isGlobal ? new GlobalReferenceHandler() : new ReferenceHandler()); } else { throw new IOException(MSG_PREFIX_UNRECOGNIZED_MAP + mapType); } } - + public PageCursor cursor() { return new PageCursor(); } @@ -158,7 +158,7 @@ public class UsageMap public int getPageCount() { return _pageNumbers.cardinality(); } - + protected short getRowStart() { return _rowStart; } @@ -170,27 +170,27 @@ public class UsageMap protected void setStartOffset(int startOffset) { _startOffset = startOffset; } - + protected int getStartOffset() { return _startOffset; } - + protected ByteBuffer getTableBuffer() { return _tableBuffer; } - + protected int getTablePageNumber() { return _tablePageNum; } - + protected int getStartPage() { return _startPage; } - + protected int getEndPage() { return _endPage; } - + protected BitSet getPageNumbers() { return _pageNumbers; } @@ -206,7 +206,7 @@ public class UsageMap } protected int getFirstPageNumber() { - return bitIndexToPageNumber(getNextBitIndex(-1), + return bitIndexToPageNumber(getNextBitIndex(-1), RowIdImpl.LAST_PAGE_NUMBER); } @@ -214,12 +214,12 @@ public class UsageMap return bitIndexToPageNumber( getNextBitIndex(pageNumberToBitIndex(curPage)), RowIdImpl.LAST_PAGE_NUMBER); - } - + } + protected int getNextBitIndex(int curIndex) { return _pageNumbers.nextSetBit(curIndex + 1); - } - + } + protected int getLastPageNumber() { return bitIndexToPageNumber(getPrevBitIndex(_pageNumbers.length()), RowIdImpl.FIRST_PAGE_NUMBER); @@ -229,16 +229,16 @@ public class UsageMap return bitIndexToPageNumber( getPrevBitIndex(pageNumberToBitIndex(curPage)), RowIdImpl.FIRST_PAGE_NUMBER); - } - + } + protected int getPrevBitIndex(int curIndex) { --curIndex; while((curIndex >= 0) && !_pageNumbers.get(curIndex)) { --curIndex; } return curIndex; - } - + } + protected int bitIndexToPageNumber(int bitIndex, int invalidPageNumber) { return((bitIndex >= 0) ? (_startPage + bitIndex) : invalidPageNumber); @@ -256,20 +256,20 @@ public class UsageMap _startPage = 0; _endPage = 0; ++_modCount; - + // clear out the table data (everything except map type) int tableStart = getRowStart() + 1; int tableEnd = getRowEnd(); ByteUtil.clearRange(_tableBuffer, tableStart, tableEnd); } - + protected void writeTable() throws IOException { // note, we only want to write the row data with which we are working getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart); } - + /** * Read in the page numbers in this inline map */ @@ -305,7 +305,7 @@ public class UsageMap public boolean containsPageNumber(int pageNumber) { return _handler.containsPageNumber(pageNumber); } - + /** * Add a page number to this usage map */ @@ -313,23 +313,23 @@ public class UsageMap ++_modCount; _handler.addOrRemovePageNumber(pageNumber, true, false); } - + /** * Remove a page number from this usage map */ - public void removePageNumber(int pageNumber) - throws IOException + public void removePageNumber(int pageNumber) + throws IOException { removePageNumber(pageNumber, true); } - - private void removePageNumber(int pageNumber, boolean force) - throws IOException + + private void removePageNumber(int pageNumber, boolean force) + throws IOException { ++_modCount; _handler.addOrRemovePageNumber(pageNumber, false, force); } - + protected void updateMap(int absolutePageNumber, int bufferRelativePageNumber, ByteBuffer buffer, boolean add, boolean force) @@ -349,7 +349,7 @@ public class UsageMap " usage map, expected range " + _startPage + " to " + _endPage); } - + //Apply the bitmask if (add) { b |= bitmask; @@ -373,13 +373,13 @@ public class UsageMap // clear out the main table (inline usage map data and start page) clearTableAndPages(); - + // set the new map type _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE); // write the new table data writeTable(); - + // set new handler _handler = new ReferenceHandler(); @@ -444,7 +444,12 @@ public class UsageMap ranges.add(String.valueOf(rangeStart)); } } - + + private static int toValidStartPage(int startPage) { + // start page must be a multiple of 8 + return ((startPage / 8) * 8); + } + private abstract class Handler { protected Handler() { @@ -454,7 +459,7 @@ public class UsageMap return(isPageWithinRange(pageNumber) && getPageNumbers().get(pageNumberToBitIndex(pageNumber))); } - + /** * @param pageNumber Page number to add or remove from this map * @param add True to add it, false to remove it @@ -476,7 +481,7 @@ public class UsageMap private class InlineHandler extends Handler { private final int _maxInlinePages; - + protected InlineHandler() throws IOException { _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8; @@ -496,15 +501,15 @@ public class UsageMap protected final int getInlineDataEnd() { return getRowEnd(); } - + /** * Sets the page range for an inline usage map starting from the given * page. */ private void setInlinePageRange(int startPage) { setPageRange(startPage, startPage + getMaxInlinePages()); - } - + } + @Override public void addOrRemovePageNumber(int pageNumber, boolean add, boolean force) @@ -518,7 +523,7 @@ public class UsageMap force); // Write the updated map back to disk writeTable(); - + } else { // uh-oh, we've split our britches. what now? @@ -531,7 +536,7 @@ public class UsageMap throws IOException { // determine what our status is before taking action - + if(add) { int firstPage = getFirstPageNumber(); @@ -547,11 +552,14 @@ public class UsageMap } else { firstPage = pageNumber; } + + firstPage = toValidStartPage(firstPage); + if((lastPage - firstPage + 1) < getMaxInlinePages()) { // we can still fit within an inline map moveToNewStartPage(firstPage, pageNumber); - + } else { // not going to happen, need to promote the usage map to a // reference map @@ -635,7 +643,7 @@ public class UsageMap // since we assuming out-of-range bits are "on". Note, we are leaving // small holes in the database here (leaving behind some free pages), // but it's not the end of the world. - + if(!add) { int firstPage = getFirstPageNumber(); @@ -666,10 +674,11 @@ public class UsageMap throws IOException { int oldEndPage = getEndPage(); - int newStartPage = - ((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber : - // just shift a little and discard any initial unused pages. - (newPageNumber - (getMaxInlinePages() / 2))); + int newStartPage = + toValidStartPage( + ((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber : + // just shift a little and discard any initial unused pages. + (newPageNumber - (getMaxInlinePages() / 2)))); // move the current data moveToNewStartPage(newStartPage, PageChannel.INVALID_PAGE_NUMBER); @@ -708,19 +717,19 @@ public class UsageMap */ private class ReferenceHandler extends Handler { - /** Buffer that contains the current reference map page */ + /** Buffer that contains the current reference map page */ private final TempPageHolder _mapPageHolder = TempPageHolder.newHolder(TempBufferHolder.Type.SOFT); private final int _maxPagesPerUsageMapPage; - + private ReferenceHandler() throws IOException { - _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE - + _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE - getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8); int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4; setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA); setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage)); - + // there is no "start page" for a reference usage map, so we get an // extra page reference on top of the number of page references that fit // in the table @@ -745,7 +754,7 @@ public class UsageMap protected final int getMaxPagesPerUsagePage() { return _maxPagesPerUsageMapPage; } - + @Override public void addOrRemovePageNumber(int pageNumber, boolean add, boolean force) @@ -774,11 +783,11 @@ public class UsageMap mapPageBuffer, add, force); getPageChannel().writePage(mapPageBuffer, mapPageNum); } - + /** * Create a new usage map page and update the map declaration with a * pointer to it. - * @param pageIndex Index of the page reference within the map declaration + * @param pageIndex Index of the page reference within the map declaration */ private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException { @@ -789,14 +798,14 @@ public class UsageMap writeTable(); return mapPageBuffer; } - + private int calculateMapPagePointerOffset(int pageIndex) { return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS + (pageIndex * 4); } - protected ByteBuffer allocateNewUsageMapPage(int pageIndex) - throws IOException + protected ByteBuffer allocateNewUsageMapPage(int pageIndex) + throws IOException { ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel()); mapPageBuffer.put(PageTypes.USAGE_MAP); @@ -850,7 +859,7 @@ public class UsageMap super.addOrRemovePageNumber(pageNumber, add, force); while(_pendingPage != null) { - + // while updating our usage map, we needed to allocate a new page (and // thus mark a new page as used). we delayed that marking so that we // didn't get into an infinite loop. now that we completed the @@ -862,12 +871,12 @@ public class UsageMap _pendingPage = null; super.addOrRemovePageNumber(removedPageNumber, false, true); - } - } + } + } @Override protected ByteBuffer allocateNewUsageMapPage(int pageIndex) - throws IOException + throws IOException { try { // keep track of the fact that we are actively allocating a page for our @@ -882,7 +891,7 @@ public class UsageMap // load this map, which is fine because we should only add pages which // represent the size of the database currently in use). int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA; - ByteUtil.fillRange(mapPageBuffer, dataStart, + ByteUtil.fillRange(mapPageBuffer, dataStart, getFormat().PAGE_SIZE - dataStart); int maxPagesPerUmapPage = getMaxPagesPerUsagePage(); @@ -924,7 +933,7 @@ public class UsageMap public UsageMap getUsageMap() { return UsageMap.this; } - + /** * Returns the DirHandler for the given direction */ @@ -938,7 +947,7 @@ public class UsageMap */ public boolean isUpToDate() { return(UsageMap.this._modCount == _lastModCount); - } + } /** * @return valid page number if there was another page to read, @@ -972,7 +981,7 @@ public class UsageMap } checkForModification(); - + _prevPageNumber = _curPageNumber; _curPageNumber = handler.getAnotherPageNumber(_curPageNumber); return _curPageNumber; @@ -1019,7 +1028,7 @@ public class UsageMap { restorePosition(curPageNumber, _curPageNumber); } - + /** * Restores a current and previous position for the cursor. */ @@ -1055,14 +1064,14 @@ public class UsageMap } return pageNumber; } - + @Override public String toString() { return getClass().getSimpleName() + " CurPosition " + _curPageNumber + ", PrevPosition " + _prevPageNumber; } - - + + /** * Handles moving the cursor in a given direction. Separates cursor * logic from value storage. @@ -1072,7 +1081,7 @@ public class UsageMap public abstract int getBeginningPageNumber(); public abstract int getEndPageNumber(); } - + /** * Handles moving the cursor forward. */ @@ -1093,7 +1102,7 @@ public class UsageMap return RowIdImpl.LAST_PAGE_NUMBER; } } - + /** * Handles moving the cursor backward. */ @@ -1114,7 +1123,7 @@ public class UsageMap return RowIdImpl.FIRST_PAGE_NUMBER; } } - - } - + + } + } |