Maconv/src/disk/pack.cc

171 lines
4.4 KiB
C++

/*
Pack files into a single disk image.
Copyright (C) 2019, Guillaume Gonnet
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 3 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, see <https://www.gnu.org/licenses/>.
*/
#include "disk/disk.h"
#include "commands.h"
#include <libhfs/hfs.h>
#include <libhfs/data.h>
#include <path.hpp>
#include <fstream>
#include <algorithm>
#include <unordered_set>
namespace maconv {
namespace disk {
// Type for seen files.
using SeenList = std::unordered_set<std::string>;
// Pack a directory into the disk.
static void PackDirectory(const Path &localp, hfsvol *vol);
// Write a single fork to an HFS file.
static void WriteFork(hfsfile *hfile, fs::File &file, bool is_res)
{
int size = is_res ? file.res_size : file.data_size;
uint8_t *data = is_res ? file.res : file.data;
hfs_setfork(hfile, is_res ? 1 : 0);
hfs_write(hfile, data, size);
}
// Pack a file into the disk.
static void PackFile(const Path &file, hfsvol *vol, SeenList &seen)
{
// Unpack the local file.
UnPacked u = UnPackLocalFile(file.string(), false);
// Insert file name(s) to seen list.
seen.insert(u.n1);
if (!u.n2.empty()) seen.insert(u.n2);
// Sanitize file name, type and creator.
auto filename = u.file.filename.substr(0, 31);
std::replace(filename.begin(), filename.end(), ':', '_');
char type[5] = {0}, creator[5] = {0};
d_putsl((unsigned char *)type, u.file.type);
d_putsl((unsigned char *)creator, u.file.creator);
// Create the file on the disk and write forks.
hfsfile *hfile = hfs_create(vol, filename.c_str(), type, creator);
if (hfile == nullptr)
StopOnError("can't create HFS file %s", filename.c_str());
WriteFork(hfile, u.file, false);
WriteFork(hfile, u.file, true);
// Set other attributes.
hfsdirent ent;
hfs_fstat(hfile, &ent);
ent.crdate = u.file.creation_date;
ent.mddate = u.file.modif_date;
ent.fdflags = u.file.flags;
ent.fdflags &= ~HFS_FNDR_ISINVISIBLE;
hfs_fsetattr(hfile, &ent);
hfs_close(hfile);
}
// Create a directory on the HFS disk and pack it.
static void CreateAndPackDirectory(const Path &localp, hfsvol *vol)
{
unsigned long current = hfs_getcwd(vol);
auto dirname = Path(localp).trim().filename().substr(0, 31);
std::replace(dirname.begin(), dirname.end(), ':', '_');
if (hfs_mkdir(vol, dirname.c_str()) != 0)
StopOnError("can't create HFS folder %s", dirname.c_str());
hfs_chdir(vol, dirname.c_str());
PackDirectory(localp, vol);
hfs_setcwd(vol, current);
}
// Pack a directory into the disk.
static void PackDirectory(const Path &localp, hfsvol *vol)
{
SeenList seen;
for (auto &file : Path::listdir(localp)) {
if (seen.count(file.string()) == 1) continue;
if (file.is_file())
PackFile(file, vol, seen);
else if (file.is_directory())
CreateAndPackDirectory(file, vol);
}
}
// Create a new file disk and mount it.
static hfsvol *CreateAndMountNewDisk(const std::string &filename,
const std::string &volname)
{
// Sanitize the volume name.
auto clean_name = volname.substr(0, 27);
std::replace(clean_name.begin(), clean_name.end(), ':', '_');
// Truncate the disk file.
int size = 20971520;
std::ofstream(filename, std::ios::binary | std::ios::trunc)
.seekp(size-1).put(0);
// Create and mount the file.
if (hfs_format(filename.c_str(), 0, 0, clean_name.c_str(), 0, NULL) != 0)
StopOnError("can't format disk file to HFS");
hfsvol *vol = hfs_mount(filename.c_str(), 0, HFS_MODE_RDWR);
if (vol == nullptr)
StopOnError("can't mount output disk image");
return vol;
}
// Pack files into a single disk image.
void PackDiskImage(const std::string &folder, const std::string &out,
const std::string &volname)
{
hfsvol *vol = CreateAndMountNewDisk(out, volname);
PackDirectory(folder, vol);
hfs_umount(vol);
}
} // namespace disk
} // namespace maconv