freebsd-dev/lib/libdisk/chunk.c
John Baldwin 53465bf736 - Make New_Disk() non-static so it can be used in Create_Chunk_DWIM().
- In Create_Chunk_DWIM(), if there is a freebsd chunk that has no
  children chunks, then trying to add a child part chunk will fail even
  though there is free space.  Handle this special case by adding an
  unused chunk the full size of the freebsd chunk as a child of the
  freebsd chunk before adding the new part chunk.  This situation can
  happen when changing the type of an existing slice to be a FreeBSD
  slice type or when installing onto a blank disk on Alpha (which has
  no slices.)

Reviewed by:	phk
MFC after:	2 days
2003-01-10 19:25:38 +00:00

558 lines
12 KiB
C

/*
* ----------------------------------------------------------------------------
* "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
* ----------------------------------------------------------------------------
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <err.h>
#include "libdisk.h"
struct chunk *
New_Chunk(void)
{
struct chunk *c;
c = malloc(sizeof *c);
if (c != NULL)
memset(c, 0, sizeof *c);
return (c);
}
/* Is c2 completely inside c1 ? */
static int
Chunk_Inside(const struct chunk *c1, const struct chunk *c2)
{
/* if c1 ends before c2 do */
if (c1->end < c2->end)
return 0;
/* if c1 starts after c2 do */
if (c1->offset > c2->offset)
return 0;
return 1;
}
static struct chunk *
Find_Mother_Chunk(struct chunk *chunks, u_long offset, u_long end,
chunk_e type)
{
struct chunk *c1, *c2, ct;
ct.offset = offset;
ct.end = end;
switch (type) {
case whole:
if (Chunk_Inside(chunks, &ct))
return chunks;
case extended:
for (c1 = chunks->part; c1; c1 = c1->next) {
if (c1->type != type)
continue;
if (Chunk_Inside(c1, &ct))
return c1;
}
return 0;
case freebsd:
for (c1 = chunks->part; c1; c1 = c1->next) {
if (c1->type == type)
if (Chunk_Inside(c1, &ct))
return c1;
if (c1->type != extended)
continue;
for (c2 = c1->part; c2; c2 = c2->next)
if (c2->type == type && Chunk_Inside(c2, &ct))
return c2;
}
return 0;
default:
warn("Unsupported mother type in Find_Mother_Chunk");
return 0;
}
}
void
Free_Chunk(struct chunk *c1)
{
if(c1 == NULL)
return;
if(c1->private_data && c1->private_free)
(*c1->private_free)(c1->private_data);
if(c1->part != NULL)
Free_Chunk(c1->part);
if(c1->next != NULL)
Free_Chunk(c1->next);
if (c1->name != NULL)
free(c1->name);
if (c1->sname != NULL)
free(c1->sname);
free(c1);
}
struct chunk *
Clone_Chunk(const struct chunk *c1)
{
struct chunk *c2;
if(!c1)
return NULL;
c2 = New_Chunk();
if (c2 == NULL)
return NULL;
*c2 = *c1;
if (c1->private_data && c1->private_clone)
c2->private_data = c2->private_clone(c2->private_data);
c2->name = strdup(c2->name);
if (c2->sname != NULL)
c2->sname = strdup(c2->sname);
c2->next = Clone_Chunk(c2->next);
c2->part = Clone_Chunk(c2->part);
return c2;
}
int
Insert_Chunk(struct chunk *c2, u_long offset, u_long size, const char *name,
chunk_e type, int subtype, u_long flags, const char *sname)
{
struct chunk *ct,*cs;
/* We will only insert into empty spaces */
if (c2->type != unused)
return __LINE__;
ct = New_Chunk();
if (ct == NULL)
return __LINE__;
ct->disk = c2->disk;
ct->offset = offset;
ct->size = size;
ct->end = offset + size - 1;
ct->type = type;
if (sname != NULL)
ct->sname = strdup(sname);
ct->name = strdup(name);
ct->subtype = subtype;
ct->flags = flags;
if (!Chunk_Inside(c2, ct)) {
Free_Chunk(ct);
return __LINE__;
}
if (type == freebsd || type == extended) {
cs = New_Chunk();
if (cs == NULL)
return __LINE__;
cs->disk = c2->disk;
cs->offset = offset;
cs->size = size;
cs->end = offset + size - 1;
cs->type = unused;
if (sname != NULL)
cs->sname = strdup(sname);
cs->name = strdup("-");
ct->part = cs;
}
/* Make a new chunk for any trailing unused space */
if (c2->end > ct->end) {
cs = New_Chunk();
if (cs == NULL)
return __LINE__;
*cs = *c2;
cs->disk = c2->disk;
cs->offset = ct->end + 1;
cs->size = c2->end - ct->end;
if (c2->sname != NULL)
cs->sname = strdup(c2->sname);
if (c2->name)
cs->name = strdup(c2->name);
c2->next = cs;
c2->size -= c2->end - ct->end;
c2->end = ct->end;
}
/* If no leading unused space just occupy the old chunk */
if (c2->offset == ct->offset) {
c2->sname = ct->sname;
c2->name = ct->name;
c2->type = ct->type;
c2->part = ct->part;
c2->subtype = ct->subtype;
c2->flags = ct->flags;
ct->sname = NULL;
ct->name = NULL;
ct->part = 0;
Free_Chunk(ct);
return 0;
}
/* else insert new chunk and adjust old one */
c2->end = ct->offset - 1;
c2->size -= ct->size;
ct->next = c2->next;
c2->next = ct;
return 0;
}
int
Add_Chunk(struct disk *d, long offset, u_long size, const char *name,
chunk_e type, int subtype, u_long flags, const char *sname)
{
struct chunk *c1, *c2, ct;
u_long end = offset + size - 1;
ct.offset = offset;
ct.end = end;
ct.size = size;
if (type == whole) {
d->chunks = c1 = New_Chunk();
if (c1 == NULL)
return __LINE__;
c2 = c1->part = New_Chunk();
if (c2 == NULL)
return __LINE__;
c2->disk = c1->disk = d;
c2->offset = c1->offset = offset;
c2->size = c1->size = size;
c2->end = c1->end = end;
c1->sname = strdup(sname);
c2->sname = strdup("-");
c1->name = strdup(name);
c2->name = strdup("-");
c1->type = type;
c2->type = unused;
c1->flags = flags;
c1->subtype = subtype;
return 0;
}
c1 = 0;
/* PLATFORM POLICY BEGIN ------------------------------------- */
switch(platform) {
case p_i386:
switch (type) {
case fat:
case mbr:
case extended:
case freebsd:
c1 = Find_Mother_Chunk(d->chunks, offset, end, whole);
break;
case part:
c1 = Find_Mother_Chunk(d->chunks, offset, end, freebsd);
break;
default:
return(-1);
}
break;
case p_ia64:
switch (type) {
case freebsd:
subtype = 0xa5;
/* FALL THROUGH */
case fat:
case efi:
case mbr:
c1 = Find_Mother_Chunk(d->chunks, offset, end, whole);
break;
case part:
c1 = Find_Mother_Chunk(d->chunks, offset, end,
freebsd);
if (!c1)
c1 = Find_Mother_Chunk(d->chunks, offset, end,
whole);
break;
default:
return (-1);
}
break;
case p_pc98:
switch (type) {
case fat:
case pc98:
case freebsd:
c1 = Find_Mother_Chunk(d->chunks, offset, end, whole);
break;
case part:
c1 = Find_Mother_Chunk(d->chunks, offset, end, freebsd);
break;
default:
return(-1);
}
break;
case p_sparc64:
case p_alpha:
switch (type) {
case freebsd:
c1 = Find_Mother_Chunk(d->chunks, offset, end, whole);
break;
case part:
c1 = Find_Mother_Chunk(d->chunks, offset, end, freebsd);
break;
default:
return(-1);
}
break;
default:
return (-1);
}
/* PLATFORM POLICY END ---------------------------------------- */
if(!c1)
return __LINE__;
for(c2 = c1->part; c2; c2 = c2->next) {
if (c2->type != unused)
continue;
if(!Chunk_Inside(c2, &ct))
continue;
/* PLATFORM POLICY BEGIN ------------------------------------- */
if (platform == p_sparc64) {
offset = Prev_Cyl_Aligned(d, offset);
size = Next_Cyl_Aligned(d, size);
} else if (platform == p_i386 || platform == p_pc98) {
if (type != freebsd)
break;
if (!(flags & CHUNK_ALIGN))
break;
if (offset == d->chunks->offset &&
end == d->chunks->end)
break;
/* Round down to prev cylinder */
offset = Prev_Cyl_Aligned(d,offset);
/* Stay inside the parent */
if (offset < c2->offset)
offset = c2->offset;
/* Round up to next cylinder */
offset = Next_Cyl_Aligned(d, offset);
/* Keep one track clear in front of parent */
if (offset == c1->offset)
offset = Next_Track_Aligned(d, offset + 1);
/* Work on the (end+1) */
size += offset;
/* Round up to cylinder */
size = Next_Cyl_Aligned(d, size);
/* Stay inside parent */
if ((size-1) > c2->end)
size = c2->end + 1;
/* Round down to cylinder */
size = Prev_Cyl_Aligned(d, size);
/* Convert back to size */
size -= offset;
}
break;
/* PLATFORM POLICY END ------------------------------------- */
}
if (c2 == NULL)
return (__LINE__);
return Insert_Chunk(c2, offset, size, name, type, subtype, flags,
sname);
}
char *
ShowChunkFlags(struct chunk *c)
{
static char ret[10];
int i = 0;
if (c->flags & CHUNK_ACTIVE)
ret[i++] = 'A';
if (c->flags & CHUNK_ALIGN)
ret[i++] = '=';
if (c->flags & CHUNK_IS_ROOT)
ret[i++] = 'R';
ret[i++] = '\0';
return ret;
}
static void
Print_Chunk(struct chunk *c1,int offset)
{
int i;
if (!c1)
return;
for (i = 0; i < offset - 2; i++)
putchar(' ');
for (; i < offset; i++)
putchar('-');
putchar('>');
for (; i < 10; i++)
putchar(' ');
printf("%p %8ld %8lu %8lu %-8s %-16s %-8s 0x%02x %s",
c1, c1->offset, c1->size, c1->end, c1->name, c1->sname,
chunk_name(c1->type), c1->subtype,
ShowChunkFlags(c1));
putchar('\n');
Print_Chunk(c1->part, offset + 2);
Print_Chunk(c1->next, offset);
}
void
Debug_Chunk(struct chunk *c1)
{
Print_Chunk(c1,2);
}
int
Delete_Chunk(struct disk *d, struct chunk *c)
{
return(Delete_Chunk2(d, c, 0));
}
int
Delete_Chunk2(struct disk *d, struct chunk *c, int rflags)
{
struct chunk *c1, *c2, *c3;
u_long offset = c->offset;
switch (c->type) {
case whole:
case unused:
return 1;
case extended:
c1 = Find_Mother_Chunk(d->chunks, c->offset, c->end, whole);
break;
case part:
c1 = Find_Mother_Chunk(d->chunks, c->offset, c->end, freebsd);
break;
default:
c1 = Find_Mother_Chunk(d->chunks, c->offset, c->end, extended);
if (c1 == NULL)
c1 = Find_Mother_Chunk(d->chunks, c->offset, c->end,
whole);
break;
}
if (c1 == NULL)
return 1;
for (c2 = c1->part; c2; c2 = c2->next) {
if (c2 == c) {
c2->type = unused;
c2->subtype = 0;
c2->flags = 0;
if (c2->sname != NULL)
free(c2->sname);
c2->sname = strdup("-");
free(c2->name);
c2->name = strdup("-");
Free_Chunk(c2->part);
c2->part =0;
goto scan;
}
}
return 1;
scan:
/*
* Collapse multiple unused elements together, and attempt
* to extend the previous chunk into the freed chunk.
*
* We only extend non-unused elements which are marked
* for newfs (we can't extend working filesystems), and
* only if we are called with DELCHUNK_RECOVER.
*/
for (c2 = c1->part; c2; c2 = c2->next) {
if (c2->type != unused) {
if (c2->offset + c2->size != offset ||
(rflags & DELCHUNK_RECOVER) == 0 ||
(c2->flags & CHUNK_NEWFS) == 0) {
continue;
}
/* else extend into free area */
}
if (!c2->next)
continue;
if (c2->next->type != unused)
continue;
c3 = c2->next;
c2->size += c3->size;
c2->end = c3->end;
c2->next = c3->next;
c3->next = 0;
Free_Chunk(c3);
goto scan;
}
Fixup_Names(d);
return 0;
}
#if 0
int
Collapse_Chunk(struct disk *d, struct chunk *c1)
{
struct chunk *c2, *c3;
if (c1->next && Collapse_Chunk(d, c1->next))
return 1;
if (c1->type == unused && c1->next && c1->next->type == unused) {
c3 = c1->next;
c1->size += c3->size;
c1->end = c3->end;
c1->next = c3->next;
c3->next = 0;
Free_Chunk(c3);
return 1;
}
c3 = c1->part;
if (!c3)
return 0;
if (Collapse_Chunk(d, c1->part))
return 1;
if (c1->type == whole)
return 0;
if (c3->type == unused && c3->size == c1->size) {
Delete_Chunk(d, c1);
return 1;
}
if (c3->type == unused) {
c2 = New_Chunk();
if (c2 == NULL)
barfout(1, "malloc failed");
*c2 = *c1;
c1->next = c2;
c1->disk = d;
c1->sname = strdup("-");
c1->name = strdup("-");
c1->part = 0;
c1->type = unused;
c1->flags = 0;
c1->subtype = 0;
c1->size = c3->size;
c1->end = c3->end;
c2->offset += c1->size;
c2->size -= c1->size;
c2->part = c3->next;
c3->next = 0;
Free_Chunk(c3);
return 1;
}
for (c2 = c3; c2->next; c2 = c2->next)
c3 = c2;
if (c2 && c2->type == unused) {
c3->next = 0;
c2->next = c1->next;
c1->next = c2;
c1->size -= c2->size;
c1->end -= c2->size;
return 1;
}
return 0;
}
#endif