ciderpress/linux/MakeDisk.cpp

405 lines
9.8 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Create a blank disk image, format it, and copy some files onto it.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../diskimg/DiskImg.h"
using namespace DiskImgLib;
#define nil NULL
#define NELEM(x) (sizeof(x) / sizeof((x)[0]))
/*
* Globals.
*/
FILE* gLog = nil;
pid_t gPid = getpid();
/*
* Show usage info.
*/
void
Usage(const char* argv0)
{
fprintf(stderr,
"Usage: %s {dos|prodos|pascal} size image-filename.po input-file1 ...\n",
argv0);
fprintf(stderr, "\n");
fprintf(stderr, "Example: makedisk prodos 800k foo.po file1.txt file2.txt\n");
}
/*
* Create a ProDOS-ordered disk image.
*
* Returns a DiskImg pointer on success, or nil on failure.
*/
DiskImg*
CreateDisk(const char* fileName, long blockCount)
{
DIError dierr;
DiskImg* pDiskImg = nil;
pDiskImg = new DiskImg;
dierr = pDiskImg->CreateImage(
fileName,
nil, // storageName
DiskImg::kOuterFormatNone,
DiskImg::kFileFormatUnadorned,
DiskImg::kPhysicalFormatSectors,
nil, // pNibbleDescr
DiskImg::kSectorOrderProDOS,
DiskImg::kFormatGenericProDOSOrd,
blockCount,
true); // no need to format the image
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: CreateImage failed: %s\n",
DIStrError(dierr));
delete pDiskImg;
pDiskImg = nil;
}
return pDiskImg;
}
/*
* Copy files to the disk.
*/
int
CopyFiles(DiskFS* pDiskFS, int argc, char** argv)
{
DIError dierr;
DiskFS::CreateParms parms;
A2File* pNewFile;
struct CreateParms {
const char* pathName; // full pathname
char fssep;
int storageType; // determines normal, subdir, or forked
long fileType;
long auxType;
int access;
time_t createWhen;
time_t modWhen;
};
argv--;
while (argc--) {
argv++;
// Confirm this is a regular file, and not a directory.
struct stat sb;
if (stat(*argv, &sb) != 0) {
fprintf(stderr, "Warning: unable to stat '%s'\n", *argv);
continue;
}
if ((sb.st_mode & S_IFREG) == 0) {
printf("--- Skipping '%s'\n", *argv);
continue;
}
printf("+++ Adding '%s'\n", *argv);
/*
* Use external pathname as internal pathname. This isn't quite
* right, since things like "../" will end up getting converted
* to something we don't want, but it'll do for now.
*/
parms.pathName = *argv;
parms.fssep = '/'; // UNIX fssep
parms.storageType = DiskFS::kStorageSeedling; // not forked, not dir
parms.fileType = 0; // NON
parms.auxType = 0; // $0000
parms.access = DiskFS::kFileAccessUnlocked;
parms.createWhen = time(nil);
parms.modWhen = time(nil);
/*
* Create a new, empty file. The "pNewFile" pointer does not belong
* to us, so we should not delete it later, or try to access it
* after the underlying file is deleted.
*/
dierr = pDiskFS->CreateFile(&parms, &pNewFile);
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: unable to create '%s': %s\n",
*argv, DIStrError(dierr));
return -1;
}
/*
* Load the input file into memory.
*/
FILE* fp;
char* buf;
long len;
fp = fopen(*argv, "r");
if (fp == nil) {
fprintf(stderr, "ERROR: unable to open input file '%s': %s\n",
*argv, strerror(errno));
return -1;
}
if (fseek(fp, 0, SEEK_END) != 0) {
fprintf(stderr, "ERROR: unable to seek input file '%s': %s\n",
*argv, strerror(errno));
fclose(fp);
return -1;
}
len = ftell(fp);
if (len >= 1L<<31) { // 2GB
fprintf(stderr, "Warning: file '%s' too large, skipping\n", *argv);
fclose(fp);
continue;
}
rewind(fp);
buf = new char[len];
if (buf == nil) {
fprintf(stderr, "ERROR: unable to alloc %ld bytes\n", len);
fclose(fp);
return -1;
}
if (fread(buf, len, 1, fp) != 1) {
fprintf(stderr, "ERROR: fread of %ld bytes from '%s' failed: %s\n",
len, *argv, strerror(errno));
fclose(fp);
delete[] buf;
return -1;
}
fclose(fp);
/*
* Write the buffer to the disk image.
*
* The A2FileDescr object is created by "Open" and deleted by
* "Close".
*/
A2FileDescr* pFD;
dierr = pNewFile->Open(&pFD, true);
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: unable to open new file '%s': %s\n",
pNewFile->GetPathName(), DIStrError(dierr));
delete[] buf;
return -1;
}
dierr = pFD->Write(buf, len);
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: failed writing to '%s': %s\n",
pNewFile->GetPathName(), DIStrError(dierr));
pFD->Close();
pDiskFS->DeleteFile(pNewFile);
delete[] buf;
return -1;
}
delete[] buf;
dierr = pFD->Close();
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: failed while closing '%s': %s\n",
pNewFile->GetPathName(), DIStrError(dierr));
return -1;
}
}
return 0;
}
/*
* Process the request.
*
* Returns 0 on success, -1 on failure.
*/
int
Process(const char* formatName, const char* sizeStr,
const char* outputFileName, int argc, char** argv)
{
DiskImg::FSFormat format;
long blockCount;
if (strcasecmp(formatName, "dos") == 0)
format = DiskImg::kFormatDOS33;
else if (strcasecmp(formatName, "prodos") == 0)
format = DiskImg::kFormatProDOS;
else if (strcasecmp(formatName, "pascal") == 0)
format = DiskImg::kFormatPascal;
else {
fprintf(stderr, "ERROR: invalid format '%s'\n", formatName);
return -1;
}
if (strcasecmp(sizeStr, "140k") == 0)
blockCount = 280;
else if (strcasecmp(sizeStr, "800k") == 0)
blockCount = 1600;
else {
blockCount = atoi(sizeStr);
if (blockCount <= 0 || blockCount > 65536) {
fprintf(stderr, "ERROR: invalid size '%s'\n", sizeStr);
return -1;
}
}
if (access(outputFileName, F_OK) == 0) {
fprintf(stderr, "ERROR: output file '%s' already exists\n",
outputFileName);
return -1;
}
assert(argc >= 1);
assert(*argv != nil);
const char* volName;
DiskImg* pDiskImg;
DiskFS* pDiskFS;
DIError dierr;
/*
* Prepare the disk image file.
*/
pDiskImg = CreateDisk(outputFileName, blockCount);
if (pDiskImg == nil)
return -1;
if (format == DiskImg::kFormatDOS33)
volName = "DOS"; // put DOS 3.3 in tracks 0-2
else
volName = "TEST";
dierr = pDiskImg->FormatImage(format, volName);
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: unable to format disk: %s\n",
DIStrError(dierr));
delete pDiskImg;
return -1;
}
/*
* Prepare to access the image as a filesystem.
*/
pDiskFS = pDiskImg->OpenAppropriateDiskFS(false);
if (pDiskFS == nil) {
fprintf(stderr, "ERROR: unable to open appropriate DiskFS\n");
delete pDiskImg;
return -1;
}
dierr = pDiskFS->Initialize(pDiskImg, DiskFS::kInitFull);
if (dierr != kDIErrNone) {
fprintf(stderr, "ERROR: unable to initialize DiskFS: %s\n",
DIStrError(dierr));
delete pDiskFS;
delete pDiskImg;
return -1;
}
/*
* Copy the files over.
*/
if (CopyFiles(pDiskFS, argc, argv) != 0) {
delete pDiskFS;
delete pDiskImg;
return -1;
}
/*
* Clean up. Note "CloseImage" isn't strictly necessary, but it gives
* us an opportunity to detect failures.
*/
delete pDiskFS;
if (pDiskImg->CloseImage() != 0) {
fprintf(stderr, "WARNING: CloseImage failed: %s\n",
DIStrError(dierr));
}
delete pDiskImg;
return 0;
}
/*
* Handle a debug message from the DiskImg library.
*/
/*static*/ void
MsgHandler(const char* file, int line, const char* msg)
{
assert(file != nil);
assert(msg != nil);
#ifdef _DEBUG
fprintf(gLog, "%05u %s", gPid, msg);
#endif
}
/*
* Process args.
*/
int
main(int argc, char** argv)
{
#ifdef _DEBUG
const char* kLogFile = "makedisk-log.txt";
gLog = fopen(kLogFile, "w");
if (gLog == nil) {
fprintf(stderr, "ERROR: unable to open log file\n");
exit(1);
}
#endif
#ifdef _DEBUG
printf("Log file is '%s'\n", kLogFile);
#endif
Global::SetDebugMsgHandler(MsgHandler);
Global::AppInit();
if (argc < 5) {
Usage(argv[0]);
exit(2);
}
const char* formatName;
const char* sizeStr;
const char* outputFileName;
argv++;
formatName = *argv++;
sizeStr = *argv++;
outputFileName = *argv++;
argc -= 4;
if (Process(formatName, sizeStr, outputFileName, argc, argv) == 0)
fprintf(stderr, "Success!\n");
else
fprintf(stderr, "Failed.\n");
Global::AppCleanup();
#ifdef _DEBUG
fclose(gLog);
#endif
exit(0);
}