|
|
nono 1.7.0
//
// nono
// Copyright (C) 2023 nono project
// Licensed under nono-license.txt
//
//
// Windrv (相当) ホストドライバ
//
#include "hostwindrv.h"
#include "ascii_ctype.h"
#include "mytime.h"
#include <dirent.h>
#include <inttypes.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(HAVE_STATVFS)
#include <sys/statvfs.h>
#elif defined(HAVE_STATFS)
#include <sys/statfs.h>
#endif
#include <sys/time.h>
#include <algorithm>
// コンストラクタ
HostWindrv::HostWindrv(WindrvDevice *windrv)
: inherited(OBJ_HOSTWINDRV)
{
// ホストデバイスのログレベルは常に VM デバイス(親)に従属なので
// ドライバオブジェクトにログエイリアスは不要。
ClearAlias();
// コンストラクト後ただちにログ出力できるようここで一度追従しておく。
// 以降はホストデバイスの SetLogLevel() で変更する。
loglevel = windrv->loglevel;
}
// デストラクタ
HostWindrv::~HostWindrv()
{
}
// ルートパスの設定。
bool
HostWindrv::InitRootPath(const std::string& rootpath_)
{
rootpath = rootpath_;
// ルートパスのほうは末尾の '/' なし。
// ゲストパスが必ず '/' から始まるため。
while (rootpath.empty() == false && rootpath.back() == '/') {
rootpath.pop_back();
}
if (rootpath.empty()) {
rootpath = ".";
}
struct stat st;
int r = stat(rootpath.c_str(), &st);
if (r < 0) {
putmsg(1, "stat '%s': %s", rootpath.c_str(), strerror(errno));
return false;
}
if (!S_ISDIR(st.st_mode)) {
putmsg(1, "stat '%s': Not directory", rootpath.c_str());
return false;
}
r = access(rootpath.c_str(), R_OK | X_OK);
if (r != 0) {
putmsg(1, "access '%s': %s", rootpath.c_str(), strerror(errno));
return false;
}
return true;
}
// 初期化コマンド。
// 戻り値はステータスコード。
uint32
HostWindrv::Initialize()
{
vdirs.clear();
return 0;
}
// ゲストパスのディレクトリ path に対応する VDir をオープンして返す。
// 見付からなければ NULL を返す。
// path はゲストパスなので "/BIN/" を指定して、マッチしたホストディレクトリ
// (および VDir の検索キー) が "/bin/" とかはある。
VDir *
HostWindrv::OpenVDir(const std::string& path)
{
putmsg(2, "%s: '%s'", __func__, path.c_str());
std::vector<std::string> search_names = SplitPath(path);
std::string cwd = "/";
VDir *vdir;
for (auto it = search_names.begin();;) {
// cwd に対する dir を取得。
vdir = FindVDir(cwd);
if (vdir) {
// 見付かれば更新。
UpdateVDir(vdir);
} else {
// キャッシュになければ、ホストのディレクトリを読んで作成。
putmsg(2, "%s: add '%s'", __func__, cwd.c_str());
vdir = AddVDir(cwd);
if (vdir == NULL) {
// それでもなければ、ない。
putmsg(2, "%s: vdir not found", __func__);
return NULL;
}
}
// サブディレクトリへ降りる必要がなければここまで。
if (it == search_names.end()) {
break;
}
const auto& name = *it;
const VDirent *ent = vdir->MatchName(name, true);
if (ent == NULL) {
putmsg(2, "%s: '%s' not found", __func__, name.c_str());
return NULL;
}
if (ent->IsDir() == false) {
putmsg(2, "%s: '%s' is not dir", __func__, name.c_str());
return NULL;
}
// サブディレクトリが見付かったので、ここに降りて次へ。
++it;
cwd += name;
cwd += '/';
}
vdir->Acquire();
return vdir;
}
// ホスト相対パス path に対応する VDir を返す。
// 見付からなければ NULL を返す。
// この時点では VDir のタイムスタンプは更新しない。
VDir *
HostWindrv::FindVDir(const std::string& path) const
{
std::string nmpath = NormalizePath(path);
auto it = vdirs.find(nmpath);
if (it == vdirs.end()) {
return NULL;
}
return it->second;
}
// 仮想ディレクトリ dir を必要なら更新。
bool
HostWindrv::UpdateVDir(VDir *vdir)
{
return FillVDir(vdir, __func__, false);
}
// ホスト相対パス path に対応する VDir を作成して追加。
VDir *
HostWindrv::AddVDir(const std::string& path)
{
std::string nmpath = NormalizePath(path);
VDir *new_vdir = new VDir(this, nmpath);
if (FillVDir(new_vdir, __func__, true) == false) {
delete new_vdir;
return NULL;
}
// vdirs に追加。
// この時点で存在しないはず。
assert(vdirs.find(nmpath) == vdirs.end());
// 一杯なら、参照した時間がもっとも古いものを削除。
// 本当は Refcount がゼロなやつでないとおかしいはずだが、
// そんなに使用中のはずはないのでいいか…。
if (vdirs.size() >= MaxVDirs) {
auto it = std::min_element(vdirs.begin(), vdirs.end(),
[](const auto& a, const auto& b) {
const auto av = a.second;
const auto bv = b.second;
if (av->Refcount() < bv->Refcount())
return true;
if (av->Refcount() > bv->Refcount())
return false;
return av->GetATime() < bv->GetATime();
}
);
auto old_vdir = it->second;
putmsg(2, "%s Purge '%s'", __func__, old_vdir->GetPath().c_str());
delete old_vdir;
vdirs.erase(it);
}
vdirs[nmpath] = new_vdir;
return new_vdir;
}
// vdir のディレクトリエントリを(再)構成する。
// caller は呼び出し元関数名。ログ表示のため。
// force が true なら常に作成、false なら更新があったときだけ作成。
bool
HostWindrv::FillVDir(VDir *vdir, const char *caller, bool force)
{
struct stat st;
std::string path = vdir->GetPath();
std::string fullpath = rootpath + path;
int r = stat(fullpath.c_str(), &st);
if (r < 0) {
if (errno == ENOENT) {
putmsg(2, "%s: '%s' (%s) not found", caller,
path.c_str(), fullpath.c_str());
} else {
putmsg(2, "%s: '%s' (%s): %s", caller,
path.c_str(), fullpath.c_str(), strerror(errno));
}
return false;
}
#if defined(HAVE_STAT_ST_TIMESPEC)
// 更新判定は st_mtimespec がある環境でだけ行う。
// st_mtime だと同一の1秒内に更新したことが分からないため。(Linux)
if (force == false) {
// OS のディレクトリが VDir より新しいか調べる。
if (vdir->GetMTime() == timespec2nsec(st.st_mtimespec)) {
return true;
}
putmsg(2, "%s: '%s' (%s) has been updated", caller,
path.c_str(), fullpath.c_str());
}
#endif
auto& list = vdir->list;
list.clear();
// ホストのディレクトリエントリから仮想ディレクトリエントリを作成。
DIR *dir = opendir(fullpath.c_str());
if (dir == NULL) {
putmsg(2, "%s: opendir '%s' (%s): %s", caller,
path.c_str(), fullpath.c_str(), strerror(errno));
return false;
}
for (struct dirent *d; (d = readdir(dir)) != NULL; ) {
// VDirent の属性はディレクトリかファイルか、のみ。
bool isdir;
if (d->d_type == DT_DIR) {
isdir = true;
} else if (d->d_type == DT_REG) {
isdir = false;
} else if (d->d_type == DT_LNK) {
// リンク先を調べる
std::string linkname = fullpath + std::string(d->d_name);
struct stat linkst;
r = stat(linkname.c_str(), &linkst);
if (r < 0) {
continue;
}
if (S_ISDIR(linkst.st_mode)) {
isdir = true;
} else if (S_ISREG(linkst.st_mode)) {
isdir = false;
} else {
continue;
}
} else {
continue;
}
if (IsHuman68kFilename(d->d_name) == false) {
continue;
}
list.emplace_back(std::string(d->d_name), isdir);
}
closedir(dir);
// ファイル名をチェック。
if (Windrv::option_case_sensitive == false) {
// 大文字小文字を区別しないモードでは "a.bak" と "a.BAK" は
// 同一なので、どちらもゲストに出現させない。
for (int i = 0, iend = list.size() - 1; i < iend; i++) {
if (list[i].IsValid() == false) {
continue;
}
for (int j = i + 1, jend = list.size(); j < jend; j++) {
if (strcasecmp(list[i].name.c_str(),
list[j].name.c_str()) == 0)
{
list[i].Invalidate();
list[j].Invalidate();
}
}
}
}
if (loglevel >= 3) {
putmsgn("%s: Enumerating '%s'...", caller, path.c_str());
for (int i = 0; i < list.size(); i++) {
putmsgn(" %c%c%c '%s'",
(list[i].IsValid() ? '-' : 'I'),
(list[i].IsArchive() ? 'A' : '-'),
(list[i].IsDir() ? 'D' : '-'),
list[i].name.c_str());
}
}
// タイムスタンプを更新。
#if defined(HAVE_STAT_ST_TIMESPEC)
vdir->SetMTime(timespec2nsec(st.st_mtimespec));
#endif
vdir->UpdateATime();
return true;
}
// パスを分解して返す。
// "/a/bc/" なら { "a", "bc" } に。
// "/" なら { } を返す。
/*static*/ std::vector<std::string>
HostWindrv::SplitPath(const std::string& path)
{
// 先頭と末尾の '/' を取り除く。
int s = 0;
int e = path.size();
for (; s < e; s++) {
if (path[s] != '/') {
break;
}
}
for (e--; e >= s; e--) {
if (path[e] != '/') {
break;
}
}
std::string tmppath = path.substr(s, e - s + 1);
// '/' で分解。
// path が "/a/bc/" なら tmppath は "a/bc" になっているので
// これで { "a", "bc" } になる。
std::vector<std::string> sep = string_split(tmppath, '/');
return sep;
}
// 相対パス文字列を正規化する。
// vdirs[] のキーに使う場合用。
/*static*/ std::string
HostWindrv::NormalizePath(const std::string& path)
{
std::string src = "/" + path + "/";
std::string dst;
bool sep = false;
for (auto c : src) {
if (sep) {
if (c != '/') {
sep = false;
dst += c;
}
} else {
if (c == '/') {
sep = true;
}
dst += c;
}
}
return dst;
}
// vdir をクローズする。
void
HostWindrv::CloseVDir(VDir *vdir)
{
if (vdir) {
vdir->Release();
}
}
// fname が Human68k で使えるファイル名なら true を返す。
bool
HostWindrv::IsHuman68kFilename(const char *fname) const
{
if (strcmp(fname, ".") == 0 || strcmp(fname, "..") == 0) {
return true;
}
const std::string name = fname; //ToGuestCharset(fname);
for (auto c : name) {
// TODO: 記号どこまで使えるか調べる
if (!is_ascii_alnum(c) && strchr("-._", c) == NULL) {
return false;
}
}
const char *p = strchr(name.c_str(), '.');
if (p == NULL) {
// '.' がない場合は 8+10 文字以内でなければいけない。
if (name.size() > 18) {
return false;
}
} else {
const char *e = strrchr(name.c_str(), '.');
if (p == e) {
// '.' が1つなら、1.1 文字以上 8+10.3 文字以内でなければいけない。
int len1 = p - name.c_str();
int len2 = strlen(p + 1);
if (len1 < 1 || len1 > 18) {
return false;
}
if (len2 < 1 || len2 > 3) {
return false;
}
} else {
// '.' が複数は未対応にするか。
return false;
}
}
return true;
}
// ゲスト文字コードの文字列をホスト文字コードに変換。
std::string
HostWindrv::ToHostCharset(const std::string& src) const
{
// XXX Not supported yet
return src;
}
// ホスト文字コードの文字列をゲスト文字コードに変換。
std::string
HostWindrv::ToGuestCharset(const std::string& src) const
{
// XXX Not supported yet
return src;
}
// メディア容量を取得する。
// 成功すれば cap にパラメータを書き出して 0 を返す。
// 失敗すればステータスコードを返す。
uint32
HostWindrv::GetCapacity(Human68k::capacity *cap)
{
uint64 totalbytes;
uint64 availbytes;
uint bytes_per_sector;
uint sectors_per_cluster;
uint total_clusters;
uint avail_clusters;
// メディアの総容量と空き容量を取得。
#if defined(HAVE_STATVFS)
struct statvfs buf;
int r = statvfs(rootpath.c_str(), &buf);
if (r < 0) {
putmsg(1, "statvfs: %s: %s", rootpath.c_str(), strerror(errno));
return Human68k::RES_INVALID_PARAM;
}
#elif defined(HAVE_STATFS)
struct statfs buf;
int r = statfs(rootpath.c_str(), &buf);
if (r < 0) {
putmsg(1, "statfs: %s: %s", rootpath.c_str(), strerror(errno));
return Human68k::RES_INVALID_PARAM;
}
#else
putmsg(0, "%s: No statfs() nor statvfs()", __func__);
return Human68k::RES_INVALID_PARAM;
#endif
totalbytes = (uint64)buf.f_bsize * (uint64)buf.f_blocks;
availbytes = (uint64)buf.f_bsize * (uint64)buf.f_bavail;
// 空き容量/総容量ともに 2GB に制限する。
if (availbytes >= 0x8000'0000U) {
availbytes = 0x7fff'ffff;
}
if (totalbytes >= 0x8000'0000U) {
totalbytes = 0x8000'0000U;
}
// クラスタを適当にでっちあげる。
bytes_per_sector = 512;
sectors_per_cluster = 128;
total_clusters = totalbytes / (sectors_per_cluster * bytes_per_sector);
avail_clusters = availbytes / (sectors_per_cluster * bytes_per_sector);
cap->avail_clusters = avail_clusters;
cap->total_clusters = total_clusters;
cap->sectors_per_cluster = sectors_per_cluster;
cap->bytes_per_sector = bytes_per_sector;
return 0;
}
// ホストパス path の示すファイルの Human68k 属性を返す。
// エラーの場合はステータスコードを返す。
// stp があれば stat(2) の結果を書き戻す。
uint32
HostWindrv::GetAttributeInternal(const std::string& fullpath,
struct stat *stp) const
{
struct stat stbuf;
uint32 attr;
int r;
if (stp == NULL) {
stp = &stbuf;
}
r = stat(fullpath.c_str(), stp);
if (r < 0) {
putmsg(1, "%s: stat: %s: %s",
__func__, fullpath.c_str(), strerror(errno));
return Human68k::RES_INVALID_PARAM; // 何返すのがいいか
}
// 属性。
if (S_ISDIR(stp->st_mode)) {
attr = Human68k::ATTR_DIR;
} else if (S_ISREG(stp->st_mode)) {
attr = Human68k::ATTR_ARCHIVE;
} else {
putmsg(1, "%s: st_mode is %o", __func__, stp->st_mode);
return Human68k::RES_INVALID_PARAM; // 何返すのがいいか
}
// 自分に書き込み権がないものは全部 ReadOnly。
r = access(fullpath.c_str(), W_OK);
if (r < 0) {
if (errno == EACCES || errno == EROFS) {
attr |= Human68k::ATTR_RDONLY;
} else {
putmsg(1, "%s: access: %s: %s",
__func__, fullpath.c_str(), strerror(errno));
return Human68k::RES_INVALID_PARAM; // 何返すのがいいか
}
}
return attr;
}
// path の示すファイルについて FILES の所定のフィールドを埋めて返す。
bool
HostWindrv::GetFileStat(Human68k::FILES *files, const std::string& path) const
{
struct stat st;
assert(files);
std::string fullpath = rootpath + path;
uint32 attr = GetAttributeInternal(fullpath, &st);
if ((int32)attr < 0) {
return false;
}
files->attr = attr;
uint32 datetime = Human68k::Unixtime2DateTime(st.st_mtime);
files->date = datetime >> 16;
files->time = datetime & 0xffff;
// 2GB を超えるファイルは扱わないことにする。
if (st.st_size >= 0x8000'0000) {
putmsg(1, "%s: %s: size exceeds", __func__, fullpath.c_str());
return false;
}
files->size = (uint32)st.st_size;
return true;
}
// ホスト相対パス path で示されるディレクトリを作成する。
uint32
HostWindrv::MakeDir(const std::string& path)
{
std::string fullpath = rootpath + path;
int r = mkdir(fullpath.c_str(), 0755);
if (r < 0) {
putmsg(1, "%s: mkdir %s: %s", __func__,
fullpath.c_str(), strerror(errno));
return -1; // ?
}
return 0;
}
// ホスト相対パス path で示されるディレクトリを削除する。
// 削除するディレクトリは空でなければならない。
uint32
HostWindrv::RemoveDir(const std::string& path)
{
std::string fullname = rootpath + path;
int r = rmdir(fullname.c_str());
if (r < 0) {
putmsg(1, "%s: rmdir %s: %s", __func__,
fullname.c_str(), strerror(errno));
if (errno == ENOTEMPTY) {
return Human68k::RES_CANNOT_DELETE;
}
return -1; // ?
}
return 0;
}
// ホスト相対パス oldpath を newpath にリネームもしくは移動する。
uint32
HostWindrv::Rename(const std::string& oldpath, const std::string& newpath)
{
std::string oldfullpath = rootpath + oldpath;
std::string newfullpath = rootpath + newpath;
int r = rename(oldfullpath.c_str(), newfullpath.c_str());
if (r < 0) {
putmsg(1, "%s: rename %s %s: %s", __func__,
oldfullpath.c_str(), newfullpath.c_str(), strerror(errno));
return -1; // ?
}
return 0;
}
// ホスト相対パス path で示されるファイルを削除する。
// ディレクトリは削除出来ない。
uint32
HostWindrv::Delete(const std::string& path)
{
std::string fullname = rootpath + path;
int r = unlink(fullname.c_str());
if (r < 0) {
putmsg(1, "%s: unlink %s: %s", __func__,
fullname.c_str(), strerror(errno));
return -1; // ?
}
return 0;
}
// ホスト相対パス path で示されるファイルの属性を返す。
// 属性は Human68k::ATTR_*。
// エラーの場合はステータスコードを返す。
uint32
HostWindrv::GetAttribute(const std::string& path)
{
std::string fullname = rootpath + path;
uint32 result = GetAttributeInternal(fullname, NULL);
return result;
}
// ホスト相対パス path で示されるファイルの属性を attr に変更する。
// attr は Human68k::ATTR_*。
// 現状 RDONLY の上げ下げのみ対応。
uint32
HostWindrv::SetAttribute(const std::string& path, uint8 newattr)
{
struct stat st;
std::string fullname = rootpath + path;
uint32 attr = GetAttributeInternal(fullname, &st);
attr &= Human68k::ATTR_RDONLY;
newattr &= Human68k::ATTR_RDONLY;
mode_t newmode;
if (attr == 0 && newattr != 0) {
newmode = st.st_mode & ~0200;
} else if (attr != 0 && newattr == 0) {
newmode = st.st_mode | 0200;
} else {
return 0;
}
int r = chmod(fullname.c_str(), newmode);
if (r < 0) {
putmsg(1, "%s: chmod(%03o) %s: %s", __func__,
newmode & 0777, fullname.c_str(), strerror(errno));
return -1; // ?
}
return 0;
}
// ホスト相対パス path で示されるファイルを作成する。
// 属性 attr は今の所 RDONLY しか見ていない。
uint32
HostWindrv::CreateFile(Windrv::FCB *fcb, const std::string& path, uint8 attr)
{
assert(fcb);
int mode = 0666;
if ((attr & Human68k::ATTR_RDONLY)) {
mode &= ~0222;
}
std::string fullname = rootpath + path;
fcb->fd = open(fullname.c_str(), O_CREAT | O_TRUNC | O_WRONLY, mode);
if (fcb->fd < 0) {
putmsg(1, "%s: create %s: %s", __func__,
fullname.c_str(), strerror(errno));
return Human68k::RES_INVALID_FUNC; // ?
}
return 0;
}
// ホスト相対パス path で示されるファイルをオープンする。
// fcb->mode は Human68k::OPEN_* のいずれか。
// オープン出来れば、fcb に諸々をセットして 0 を返す。
// o fcb->date, fcb->time に MS-DOS 形式の日付時刻
// o fcb->size にファイルサイズ
// o fcb->fd にディスクリプタ
// 失敗すればステータスコードを返す。
uint32
HostWindrv::Open(Windrv::FCB *fcb, const std::string& path)
{
assert(fcb);
// 全世界的に同じ値だと思うけど…。
int openmode;
switch (fcb->GetMode()) {
case Human68k::OPEN_RDONLY: openmode = O_RDONLY; break;
case Human68k::OPEN_WRONLY: openmode = O_WRONLY; break;
case Human68k::OPEN_RDWR: openmode = O_RDWR; break;
default:
return Human68k::RES_INVALID_MODE;
}
std::string fullname = rootpath + path;
fcb->fd = open(fullname.c_str(), openmode);
if (fcb->fd < 0) {
putmsg(1, "%s: open %s: %s", __func__,
fullname.c_str(), strerror(errno));
return Human68k::RES_INVALID_FUNC; // ?
}
struct stat st;
int r = fstat(fcb->fd, &st);
if (r < 0) {
putmsg(1, "%s: fstat %s: %s", __func__,
fullname.c_str(), strerror(errno));
Close(fcb);
return Human68k::RES_INVALID_FUNC; // ?
}
fcb->SetDateTime(Human68k::Unixtime2DateTime(st.st_mtime));
fcb->SetSize(st.st_size);
return 0;
}
// FCB をクローズする。
void
HostWindrv::Close(Windrv::FCB *fcb)
{
if (fcb->fd >= 0) {
close(fcb->fd);
fcb->fd = -1;
}
}
// fcb(->fd) から dst に len バイト読み込む。
// ここでは fcb は更新不要。
uint32
HostWindrv::Read(Windrv::FCB *fcb, void *dst, uint32 len)
{
assert(fcb);
auto n = read(fcb->fd, dst, len);
if (n < 0) {
putmsg(1, "%s: %s", __func__, strerror(errno));
return -1;
}
return (uint32)n;
}
// fcb(->fd) に src から len バイトを書き出す。
// ここでは fcb は更新不要。
uint32
HostWindrv::Write(Windrv::FCB *fcb, const void *src, uint32 len)
{
assert(fcb);
auto n = write(fcb->fd, src, len);
if (n < 0) {
putmsg(1, "%s: %s", __func__, strerror(errno));
return -1;
}
return (uint32)n;
}
// シークする。
// 戻り値はシーク後の先頭からの現在位置。
// ここでは fcb は更新不要。
uint32
HostWindrv::Seek(Windrv::FCB *fcb, int32 offset, int whence)
{
assert(fcb);
auto n = lseek(fcb->fd, (off_t)offset, whence);
if (n < 0) {
putmsg(1, "%s: lseek($%x, %d): %s", __func__,
offset, whence, strerror(errno));
return -1;
}
return (uint32)n;
}
// フラッシュ。
uint32
HostWindrv::Flush()
{
return 0;
}
// ファイルの更新日時(MS-DOS形式)を取得する。
uint32
HostWindrv::GetFileTime(Windrv::FCB *fcb)
{
assert(fcb);
struct stat st;
int r = fstat(fcb->fd, &st);
if (r < 0) {
putmsg(1, "%s: fstat: %s", __func__, strerror(errno));
return -1;
}
uint32 result = Human68k::Unixtime2DateTime(st.st_mtime);
return result;
}
// ファイルの更新日時を設定する。
// datetime は MS-DOS 形式。
uint32
HostWindrv::SetFileTime(Windrv::FCB *fcb, uint32 datetime)
{
assert(fcb);
// [0] が atime、[1] が mtime。
struct timeval times[2];
uint64 now = get_hosttime_usec();
times[0].tv_sec = now / 1000'000U;
times[0].tv_usec = now % 1000'000U;
times[1].tv_sec = Human68k::DateTime2Unixtime(datetime);
times[1].tv_usec = 0;
int r = futimes(fcb->fd, times);
if (r < 0) {
putmsg(1, "%s: futimes: %s", __func__, strerror(errno));
return -1;
}
return 0;
}
#if defined(HAVE_STAT_ST_TIMESPEC)
/*static*/ uint64
HostWindrv::timespec2nsec(const struct timespec& ts)
{
return sec_to_nsec(ts.tv_sec) + ts.tv_nsec;
}
#endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.