macemu/BasiliskII/src/Unix/disk_sparsebundle.cpp

316 lines
8.2 KiB
C++

/*
* disk_sparsebundle.cpp - Apple sparse bundle implementation
*
* Basilisk II (C) Dave Vasilevsky
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "disk_unix.h"
#include "tinyxml2.h"
#include <errno.h>
#include <limits.h>
#include <algorithm>
#if defined __APPLE__ && defined __MACH__
#define __MACOSX__ 1
#endif
struct disk_sparsebundle : disk_generic {
disk_sparsebundle(const char *bands, int fd, bool read_only,
loff_t band_size, loff_t total_size)
: token_fd(fd), read_only(read_only), band_size(band_size),
total_size(total_size), band_dir(strdup(bands)),
band_cur(-1), band_fd(-1), band_alloc(-1) {
}
virtual ~disk_sparsebundle() {
if (band_fd != -1)
close(band_fd);
close(token_fd);
free(band_dir);
}
virtual bool is_read_only() { return read_only; }
virtual loff_t size() { return total_size; }
virtual size_t read(void *buf, loff_t offset, size_t length) {
return band_do(&disk_sparsebundle::band_read, buf, offset, length);
}
virtual size_t write(void *buf, loff_t offset, size_t length) {
return band_do(&disk_sparsebundle::band_write, buf, offset, length);
}
protected:
int token_fd; // lockfile
bool read_only;
loff_t band_size, total_size;
char *band_dir; // directory containing band files
// Currently open band
loff_t band_cur; // index of the band
int band_fd; // -1 if not open
loff_t band_alloc; // how much space is already used?
typedef ssize_t (disk_sparsebundle::*band_func)(char *buf, loff_t band,
size_t offset, size_t len);
// Split an (offset, length) operation into bands.
size_t band_do(band_func func, void *buf, loff_t offset, size_t length) {
char *b = (char*)buf;
loff_t band = offset / band_size;
size_t done = 0;
while (length) {
if (offset >= total_size)
break;
size_t start = offset % band_size;
size_t segment = std::min((size_t)band_size - start, length);
ssize_t err = (this->*func)(b, band, start, segment);
if (err > 0)
done += err;
if (err < segment)
break;
b += segment;
offset += segment;
length -= segment;
++band;
}
return done;
}
// Open a band by index. It's ok if the band is already open.
enum open_ret {
OPEN_FAILED = 0,
OPEN_NOENT, // Band doesn't exist yet
OPEN_OK,
};
open_ret open_band(loff_t band, bool create) {
if (band_cur == band)
return OPEN_OK;
char path[PATH_MAX + 1];
if (snprintf(path, PATH_MAX, "%s/%lx", band_dir,
(unsigned long)band) >= PATH_MAX) {
return OPEN_FAILED;
}
if (band_fd != -1)
close(band_fd);
band_alloc = -1;
band_cur = -1;
int oflags = read_only ? O_RDONLY : O_RDWR;
if (create)
oflags |= O_CREAT;
band_fd = open(path, oflags, 0644);
if (band_fd == -1) {
return (!create && errno == ENOENT) ? OPEN_NOENT : OPEN_FAILED;
}
// Get the allocated size
if (!read_only) {
band_alloc = lseek(band_fd, 0, SEEK_END);
if (band_alloc == -1)
band_alloc = band_size;
}
band_cur = band;
return OPEN_OK;
}
ssize_t band_read(char *buf, loff_t band, size_t off, size_t len) {
open_ret st = open_band(band, false);
if (st == OPEN_FAILED)
return -1;
// Unallocated bytes
size_t want = (st == OPEN_NOENT || off >= band_alloc) ? 0
: std::min(len, (size_t)band_alloc - off);
if (want) {
if (lseek(band_fd, off, SEEK_SET) == -1)
return -1;
ssize_t err = ::read(band_fd, buf, want);
if (err < want)
return err;
}
memset(buf + want, 0, len - want);
return len;
}
ssize_t band_write(char *buf, loff_t band, size_t off, size_t len) {
// If space is unused, don't needlessly fill it with zeros
// Find min length such that all trailing chars are zero:
size_t nz = len;
for (; nz > 0 && !buf[nz-1]; --nz)
; // pass
open_ret st = open_band(band, nz);
if (st != OPEN_OK)
return st == OPEN_NOENT ? len : -1;
if (lseek(band_fd, off, SEEK_SET) == -1)
return -1;
size_t space = (off >= band_alloc ? 0 : band_alloc - off);
size_t want = std::max(nz, std::min(space, len));
ssize_t err = ::write(band_fd, buf, want);
if (err >= 0)
band_alloc = std::max(band_alloc, loff_t(off + err));
if (err < want)
return err;
return len;
}
};
using tinyxml2::XML_NO_ERROR;
using tinyxml2::XMLElement;
// Simplistic plist parser
struct plist {
plist() : doc(true, tinyxml2::COLLAPSE_WHITESPACE) { }
bool open(const char *path) {
if (doc.LoadFile(path) != XML_NO_ERROR)
return false;
tinyxml2::XMLHandle hnd(&doc);
dict = hnd.FirstChildElement("plist").FirstChildElement("dict")
.ToElement();
return dict;
}
const char *str_val(const char *key) {
return value(key, "string");
}
bool int_val(const char *key, loff_t *i) {
const char *v = value(key, "integer");
if (!v || !*v)
return false;
char *endp;
long long ll = strtoll(v, &endp, 10);
if (*endp)
return false;
*i = ll;
return true;
}
protected:
tinyxml2::XMLDocument doc;
XMLElement *dict;
const char *value(const char *key, const char *type) {
// Assume it's a flat plist
XMLElement *cur = dict->FirstChildElement();
bool found_key = false;
while (cur) {
if (found_key) {
if (strcmp(cur->Name(), type) != 0)
return NULL;
return cur->GetText();
}
found_key = strcmp(cur->Name(), "key") == 0
&& strcmp(cur->GetText(), key) == 0;
cur = cur->NextSiblingElement();
}
return NULL;
}
};
static int try_open(const char *path, bool read_only, bool *locked) {
int oflags = (read_only ? O_RDONLY : O_RDWR);
int lockflags = 0;
#if defined(__MACOSX__)
lockflags = O_NONBLOCK | (read_only ? O_SHLOCK : O_EXLOCK);
#endif
int fd = open(path, oflags | lockflags);
#if defined(__MACOSX__)
if (fd == -1) {
if (errno == EOPNOTSUPP) { // no locking support, try again
fd = open(path, oflags);
} else if (errno == EAGAIN) { // locked
*locked = true;
}
}
#endif
return fd;
}
disk_generic::status disk_sparsebundle_factory(const char *path,
bool read_only, disk_generic **disk) {
// Does it look like a sparsebundle?
char buf[PATH_MAX + 1];
if (snprintf(buf, PATH_MAX, "%s/%s", path, "Info.plist") >= PATH_MAX)
return disk_generic::DISK_UNKNOWN;
plist pl;
if (!pl.open(buf))
return disk_generic::DISK_UNKNOWN;
const char *type;
if (!(type = pl.str_val("diskimage-bundle-type")))
return disk_generic::DISK_UNKNOWN;
if (strcmp(type, "com.apple.diskimage.sparsebundle") != 0)
return disk_generic::DISK_UNKNOWN;
// Find the sparsebundle parameters
loff_t version, band_size, total_size;
if (!pl.int_val("bundle-backingstore-version", &version) || version != 1) {
fprintf(stderr, "sparsebundle: Bad version\n");
return disk_generic::DISK_UNKNOWN;
}
if (!pl.int_val("band-size", &band_size)
|| !pl.int_val("size", &total_size)) {
fprintf(stderr, "sparsebundle: Can't find size\n");
return disk_generic::DISK_INVALID;
}
// Check if we can open it
if (snprintf(buf, PATH_MAX, "%s/%s", path, "token") >= PATH_MAX)
return disk_generic::DISK_INVALID;
bool locked = false;
int token = try_open(buf, read_only, &locked);
if (token == -1 && !read_only) { // try again, read-only
token = try_open(buf, true, &locked);
if (token != -1 && !read_only)
fprintf(stderr, "sparsebundle: Can only mount read-only\n");
read_only = true;
}
if (token == -1) {
if (locked)
fprintf(stderr, "sparsebundle: Refusing to double-mount\n");
else
perror("sparsebundle: open failed:");
return disk_generic::DISK_INVALID;
}
// We're good to go!
if (snprintf(buf, PATH_MAX, "%s/%s", path, "bands") >= PATH_MAX)
return disk_generic::DISK_INVALID;
*disk = new disk_sparsebundle(buf, token, read_only, band_size,
total_size);
return disk_generic::DISK_VALID;
}