blob: 1b037792e923b8575a5172c1750d0bd5eefb9d22 [file] [log] [blame]
/*
* Copyright (c) 2000-2001 Silicon Graphics, Inc.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <xfs/xfs.h>
#include <xfs/jdm.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <dirent.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include "config.h"
#include "types.h"
#include "util.h"
#include "openutil.h"
#include "mlog.h"
#include "global.h"
#include "drive.h"
#include "media.h"
#include "content.h"
#include "content_inode.h"
#include "inomap.h"
#include "mmap.h"
#include "arch_xlate.h"
/* structure definitions used locally ****************************************/
/* restores the inomap into a file
*/
#define PERS_NAME "inomap"
/* reserve the first page for persistent state
*/
struct pers {
size64_t hnkcnt;
/* number of hunks in map
*/
size64_t segcnt;
/* number of segments
*/
xfs_ino_t last_ino_added;
};
typedef struct pers pers_t;
#define PERSSZ perssz
/* declarations of externally defined global symbols *************************/
extern size_t pgsz;
/* forward declarations of locally defined static functions ******************/
/* inomap primitives
*/
static int map_getset(xfs_ino_t, int, bool_t);
static int map_set(xfs_ino_t ino, int);
static seg_t * map_getsegment(xfs_ino_t ino);
/* definition of locally defined global variables ****************************/
/* definition of locally defined static variables *****************************/
static int pers_fd = -1;
/* file descriptor for persistent inomap backing store
*/
/* context for inomap construction - initialized by inomap_restore_pers
*/
static uint64_t hnkcnt;
static uint64_t segcnt;
static hnk_t *roothnkp = 0;
static hnk_t *tailhnkp;
static seg_t *lastsegp;
static xfs_ino_t last_ino_added;
/* map context and operators
*/
static inline void
SEG_SET_BITS(seg_t *segp, xfs_ino_t ino, int state)
{
register xfs_ino_t relino;
register uint64_t mask;
register uint64_t clrmask;
relino = ino - segp->base;
mask = (uint64_t)1 << relino;
clrmask = ~mask;
switch(state) {
case 0:
segp->lobits &= clrmask;
segp->mebits &= clrmask;
segp->hibits &= clrmask;
break;
case 1:
segp->lobits |= mask;
segp->mebits &= clrmask;
segp->hibits &= clrmask;
break;
case 2:
segp->lobits &= clrmask;
segp->mebits |= mask;
segp->hibits &= clrmask;
break;
case 3:
segp->lobits |= mask;
segp->mebits |= mask;
segp->hibits &= clrmask;
break;
case 4:
segp->lobits &= clrmask;
segp->mebits &= clrmask;
segp->hibits |= mask;
break;
case 5:
segp->lobits |= mask;
segp->mebits &= clrmask;
segp->hibits |= mask;
break;
case 6:
segp->lobits &= clrmask;
segp->mebits |= mask;
segp->hibits |= mask;
break;
case 7:
segp->lobits |= mask;
segp->mebits |= mask;
segp->hibits |= mask;
break;
}
}
static inline int
SEG_GET_BITS(seg_t *segp, xfs_ino_t ino)
{
int state;
register xfs_ino_t relino;
register uint64_t mask;
relino = ino - segp->base;
mask = (uint64_t)1 << relino;
if (segp->lobits & mask) {
state = 1;
} else {
state = 0;
}
if (segp->mebits & mask) {
state |= 2;
}
if (segp->hibits & mask) {
state |= 4;
}
return state;
}
/* definition of locally defined global functions ****************************/
rv_t
inomap_restore_pers(drive_t *drivep,
content_inode_hdr_t *scrhdrp,
char *hkdir)
{
drive_ops_t *dop = drivep->d_opsp;
char *perspath;
pers_t *persp;
hnk_t *pershnkp;
hnk_t *tmphnkp;
int fd;
/* REFERENCED */
int nread;
int rval;
/* REFERENCED */
int rval1;
int i;
bool_t ok;
/* sanity checks
*/
assert(INOPERSEG == (sizeof(((seg_t *)0)->lobits) * NBBY));
assert(sizeof(hnk_t) == HNKSZ);
assert(sizeof(pers_t) <= PERSSZ);
/* get inomap info from media hdr
*/
hnkcnt = scrhdrp->cih_inomap_hnkcnt;
segcnt = scrhdrp->cih_inomap_segcnt;
last_ino_added = scrhdrp->cih_inomap_lastino;
/* truncate and open the backing store
*/
perspath = open_pathalloc(hkdir, PERS_NAME, 0);
(void)unlink(perspath);
fd = open(perspath,
O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR);
if (fd < 0) {
mlog(MLOG_NORMAL | MLOG_ERROR, _(
"could not open %s: %s\n"),
perspath,
strerror(errno));
return RV_ERROR;
}
/* mmap the persistent hdr and space for the map
*/
persp = (pers_t *) mmap_autogrow(
PERSSZ
+
sizeof(hnk_t) * (size_t)hnkcnt,
fd,
(off64_t)0);
if (persp == (pers_t *)-1) {
mlog(MLOG_NORMAL | MLOG_ERROR, _(
"unable to map %s: %s\n"),
perspath,
strerror(errno));
return RV_ERROR;
}
/* load the pers hdr
*/
persp->hnkcnt = hnkcnt;
persp->segcnt = segcnt;
persp->last_ino_added = last_ino_added;
tmphnkp = (hnk_t *)calloc((size_t)hnkcnt, sizeof(hnk_t));
assert(tmphnkp);
/* read the map in from media
*/
nread = read_buf((char *)tmphnkp,
sizeof(hnk_t) * (size_t)hnkcnt,
(void *)drivep,
(rfp_t)dop->do_read,
(rrbfp_t)dop->do_return_read_buf,
&rval);
pershnkp = (hnk_t *)((char *)persp + PERSSZ);
for(i = 0; i < hnkcnt; i++) {
xlate_hnk(&tmphnkp[i], &pershnkp[i], 1);
}
free(tmphnkp);
mlog(MLOG_NITTY, "inomap_restore_pers: pre-munmap\n");
/* close up
*/
rval1 = munmap((void *)persp,
PERSSZ
+
sizeof(hnk_t) * (size_t)hnkcnt);
assert(! rval1);
(void)close(fd);
free((void *)perspath);
mlog(MLOG_NITTY, "inomap_restore_pers: post-munmap\n");
/* check the return code from read
*/
switch(rval) {
case 0:
assert((size_t)nread == sizeof(hnk_t) * (size_t)hnkcnt);
ok = inomap_sync_pers(hkdir);
if (! ok) {
return RV_ERROR;
}
return RV_OK;
case DRIVE_ERROR_EOD:
case DRIVE_ERROR_EOF:
case DRIVE_ERROR_EOM:
case DRIVE_ERROR_MEDIA:
case DRIVE_ERROR_CORRUPTION:
return RV_CORRUPT;
case DRIVE_ERROR_DEVICE:
return RV_DRIVE;
case DRIVE_ERROR_CORE:
default:
return RV_CORE;
}
}
/* peels inomap from media
*/
rv_t
inomap_discard(drive_t *drivep, content_inode_hdr_t *scrhdrp)
{
drive_ops_t *dop = drivep->d_opsp;
uint64_t tmphnkcnt;
/* REFERENCED */
int nread;
int rval;
/* get inomap info from media hdr
*/
tmphnkcnt = scrhdrp->cih_inomap_hnkcnt;
/* read the map in from media
*/
nread = read_buf(0,
sizeof(hnk_t) * (size_t)tmphnkcnt,
(void *)drivep,
(rfp_t)dop->do_read,
(rrbfp_t)dop->do_return_read_buf,
&rval);
/* check the return code from read
*/
switch(rval) {
case 0:
assert((size_t)nread == sizeof(hnk_t) * (size_t)hnkcnt);
return RV_OK;
case DRIVE_ERROR_EOD:
case DRIVE_ERROR_EOF:
case DRIVE_ERROR_EOM:
case DRIVE_ERROR_MEDIA:
case DRIVE_ERROR_CORRUPTION:
return RV_CORRUPT;
case DRIVE_ERROR_DEVICE:
return RV_DRIVE;
case DRIVE_ERROR_CORE:
default:
return RV_CORE;
}
}
bool_t
inomap_sync_pers(char *hkdir)
{
char *perspath;
pers_t *persp;
hnk_t *hnkp;
/* sanity checks
*/
assert(sizeof(hnk_t) == HNKSZ);
/* only needed once per session
*/
if (pers_fd >= 0) {
return BOOL_TRUE;
}
/* open the backing store. if not present, ok, hasn't been created yet
*/
perspath = open_pathalloc(hkdir, PERS_NAME, 0);
pers_fd = open(perspath, O_RDWR);
if (pers_fd < 0) {
return BOOL_TRUE;
}
/* mmap the persistent hdr
*/
persp = (pers_t *) mmap_autogrow(
PERSSZ,
pers_fd,
(off64_t)0);
if (persp == (pers_t *)-1) {
mlog(MLOG_NORMAL | MLOG_ERROR, _(
"unable to map %s hdr: %s\n"),
perspath,
strerror(errno));
return BOOL_FALSE;
}
/* read the pers hdr
*/
hnkcnt = persp->hnkcnt;
segcnt = persp->segcnt;
last_ino_added = persp->last_ino_added;
/* mmap the pers inomap
*/
assert(hnkcnt * sizeof(hnk_t) <= (size64_t)INT32MAX);
roothnkp = (hnk_t *) mmap_autogrow(
sizeof(hnk_t) * (size_t)hnkcnt,
pers_fd,
(off64_t)PERSSZ);
if (roothnkp == (hnk_t *)-1) {
mlog(MLOG_NORMAL | MLOG_ERROR, _(
"unable to map %s: %s\n"),
perspath,
strerror(errno));
return BOOL_FALSE;
}
/* correct the next pointers
*/
for (hnkp = roothnkp
;
hnkp < roothnkp + (int)hnkcnt - 1
;
hnkp++) {
hnkp->nextp = hnkp + 1;
}
hnkp->nextp = 0;
/* calculate the tail pointers
*/
tailhnkp = hnkp;
assert(hnkcnt > 0);
lastsegp = &tailhnkp->seg[(int)(segcnt
-
SEGPERHNK * (hnkcnt - 1)
-
1)];
/* now all inomap operators will work
*/
return BOOL_TRUE;
}
/* de-allocate the persistent inomap
*/
void
inomap_del_pers(char *hkdir)
{
char *perspath = open_pathalloc(hkdir, PERS_NAME, 0);
(void)unlink(perspath);
free((void *)perspath);
}
/* mark all included non-dirs as MAP_NDR_NOREST
*/
void
inomap_sanitize(void)
{
hnk_t *hnkp;
seg_t *segp;
/* step through all hunks, segs, and inos
*/
for (hnkp = roothnkp
;
hnkp != 0
;
hnkp = hnkp->nextp) {
for (segp = hnkp->seg
;
segp < hnkp->seg + SEGPERHNK
;
segp++) {
xfs_ino_t ino;
if (hnkp == tailhnkp && segp > lastsegp) {
return;
}
for (ino = segp->base
;
ino < segp->base + INOPERSEG
;
ino++) {
int state;
if (ino > last_ino_added) {
return;
}
state = SEG_GET_BITS(segp, ino);
if (state == MAP_NDR_CHANGE) {
state = MAP_NDR_NOREST;
SEG_SET_BITS(segp, ino, state);
}
}
}
}
}
/* called to mark a non-dir ino as TO be restored
*/
void
inomap_rst_add(xfs_ino_t ino)
{
assert(pers_fd >= 0);
(void)map_set(ino, MAP_NDR_CHANGE);
}
/* called to mark a non-dir ino as NOT to be restored
*/
void
inomap_rst_del(xfs_ino_t ino)
{
assert(pers_fd >= 0);
(void)map_set(ino, MAP_NDR_NOREST);
}
/* called to ask if any inos in the given range need to be restored.
* range is inclusive
*/
bool_t
inomap_rst_needed(xfs_ino_t firstino, xfs_ino_t lastino)
{
hnk_t *hnkp;
seg_t *segp;
/* if inomap not restored/resynced, just say yes
*/
if (! roothnkp) {
return BOOL_TRUE;
}
/* may be completely out of range
*/
if (firstino > last_ino_added) {
return BOOL_FALSE;
}
/* find the hunk/seg containing first ino or any ino beyond
*/
for (hnkp = roothnkp ; hnkp != 0 ; hnkp = hnkp->nextp) {
if (firstino > hnkp->maxino) {
continue;
}
for (segp = hnkp->seg; segp < hnkp->seg + SEGPERHNK ; segp++){
if (hnkp == tailhnkp && segp > lastsegp) {
return BOOL_FALSE;
}
if (firstino < segp->base + INOPERSEG) {
goto begin;
}
}
}
return BOOL_FALSE;
begin:
/* search until at least one ino is needed or until beyond last ino
*/
for (; ;) {
xfs_ino_t ino;
if (segp->base > lastino) {
return BOOL_FALSE;
}
for (ino = segp->base ; ino < segp->base + INOPERSEG ; ino++){
int state;
if (ino < firstino) {
continue;
}
if (ino > lastino) {
return BOOL_FALSE;
}
state = SEG_GET_BITS(segp, ino);
if (state == MAP_NDR_CHANGE) {
return BOOL_TRUE;
}
}
segp++;
if (hnkp == tailhnkp && segp > lastsegp) {
return BOOL_FALSE;
}
if (segp >= hnkp->seg + SEGPERHNK) {
hnkp = hnkp->nextp;
if (! hnkp) {
return BOOL_FALSE;
}
segp = hnkp->seg;
}
}
/* NOTREACHED */
}
/* calls the callback for all inos with an inomap state included
* in the state mask. stops iteration when inos exhaused or cb
* returns FALSE.
*/
void
inomap_cbiter(int statemask,
bool_t (* cbfunc)(void *ctxp, xfs_ino_t ino),
void *ctxp)
{
hnk_t *hnkp;
seg_t *segp;
/* step through all hunks, segs, and inos
*/
for (hnkp = roothnkp
;
hnkp != 0
;
hnkp = hnkp->nextp) {
for (segp = hnkp->seg
;
segp < hnkp->seg + SEGPERHNK
;
segp++) {
xfs_ino_t ino;
if (hnkp == tailhnkp && segp > lastsegp) {
return;
}
for (ino = segp->base
;
ino < segp->base + INOPERSEG
;
ino++) {
int state;
if (ino > last_ino_added) {
return;
}
state = SEG_GET_BITS(segp, ino);
if (statemask & (1 << state)) {
bool_t ok;
ok = (cbfunc)(ctxp, ino);
if (! ok) {
return;
}
}
}
}
}
}
/* definition of locally defined static functions ****************************/
/* map_getset - locates and gets the state of the specified ino,
* and optionally sets the state to a new value.
*/
static int
map_getset(xfs_ino_t ino, int newstate, bool_t setflag)
{
int state;
seg_t *segp;
if ((segp = map_getsegment(ino)) == NULL) {
return MAP_INO_UNUSED;
}
state = SEG_GET_BITS(segp, ino);
if (setflag) {
SEG_SET_BITS(segp, ino, newstate);
}
return state;
}
static seg_t *
map_getsegment(xfs_ino_t ino)
{
uint64_t min;
uint64_t max;
uint64_t hnk;
uint64_t seg;
/* Use binary search to find the hunk that contains the inode number,
* if any. This counts on the fact that all the hunks are contiguous
* in memory and therefore can be treated as an array instead of a
* list.
*/
min = 0;
max = hnkcnt - 1;
while (max >= min) {
hnk = min + ((max - min) / 2);
if (roothnkp[hnk].seg[0].base > ino) {
max = hnk - 1; /* in a lower hunk */
} else if (roothnkp[hnk].maxino < ino) {
min = hnk + 1; /* in a higher hunk */
} else {
break; /* we have a winner! */
}
}
if (max < min) {
return NULL; /* inode number fell between hunks */
}
/* Use binary search to find the segment within the hunk that contains
* the inode number, if any.
*/
min = 0;
if (hnk == hnkcnt - 1) {
max = segcnt - SEGPERHNK * (hnkcnt - 1) - 1;
} else {
max = SEGPERHNK - 1;
}
while (max >= min) {
seg = min + ((max - min) / 2);
if (roothnkp[hnk].seg[seg].base > ino) {
max = seg - 1; /* in a lower segment */
} else if (roothnkp[hnk].seg[seg].base + INOPERSEG <= ino) {
min = seg + 1; /* in a higher segment */
} else {
return &roothnkp[hnk].seg[seg]; /* we have a winner! */
}
}
return NULL;
}
static int
map_set(xfs_ino_t ino, int state)
{
int oldstate;
oldstate = map_getset(ino, state, BOOL_TRUE);
return oldstate;
}