Move to protobuf for the webapp, major overhaul to easyinstall.sh, code comment translations (#229)

* Making saving and loading config files work with protobuf

* Formatted the Status column, and fixed the available ID logic

* Updated handling of removed status for devices without image file support

* Comment update

* Fixed typo

* Updated logging

* Updated handling of removed status for devices without image file support

* Comment update

* Fixed typo

* Updated logging

* Better handling of device status

* Updated parameter handling

* Updated setting default interfaces

* Revert "Updated setting default interfaces"

This reverts commit 210abc775d.

* Revert "Updated parameter handling"

This reverts commit 35302addd5.

* Abort with a 404 if rascsi is not running. Use any protobuf response to determine whether rascsi is running (should hardly be required anymore due to the other change, but just in case).

* Move id reservation back into __main__

* Remove check for device type when validating Removed image

* Leverage device property data for better status messages

* Remove redundant string sanitation when reading config csv file

* Clean up device list generation

* Cleanup

* Remove duplicates before building valid scsi id list

* Fully translated cfilesystem.h code comments to English; partially translated cfilesystem.cpp

* rascsi supports reserving IDs

* Updated help message

* Replaced BOOL by bool

* Logging update

* Logging update

* Cleanup

* Restructure the easyinstall.sh script to combine the install/update flows, and disallow installing the webapp by itself

* Remove redundant steps handled in Makefile

* Add the functionality to specify connect_type through a parameter

* Add validation to the argument parser allowing only STANDARD and FULLSPEC as options

* Complete translation of code comments for cfilesystem.h; partial translation for cfilesystem.cpp

* Cleanup

* Merge parts of the Network Assistant script by sonique6784; fix the run_choice startup parameter

* Improve on the network setup messages

* Fix routing address

* Add checks for previous configuration; cleanup

* Cleanup

* Remove redundant step in wired setup. Improve messages.

* Cleanup

* Added default parameters to device properties

* Return parameters a device was set up with

* Add flows for configuring custom network settings; adopting some logic by –sonique6784

* Improved device initialization

* Updated default parameter handling

* Updated default parameter handling

* Fixed typo

* Comment updates

* Comment update

* Make iso generation work again, and add error handling to urllib actions

* Manage default parameters in the respective device

* Print available network interfaces. Clean up step and improve descriptive messages.

* Make the script clean up previous configurations

* Make the script only show relevant interfaces

* Partial translation of cfilesystem.cpp

* Do not pass empty parameter string

* Added supports_params flag

* Completely translate code comments in cfilesystem.cpp

* Show rascsi-web status after installing

* Refactoring

* Made comparisons more consistent

* Updated error handling

* Updated exception handling

* Made comparisons more consistent

* Updated error handling

* Overlooked code comment translation

* Renaming

* Better error handling for socket connection

* Disable two NEC hd image types due to issue#232

* Comment update

* NEC sectors size must be 512 bytes

* Updated logging

* Updated vendor name handling

* Updated handling of media loading/unloading

* Comment update

* NEC sectors size must be 512 bytes

* Updated logging

* Updated vendor name handling

* Updated handling of media loading/unloading

* Better handling of removable disks in the web ui

* Added stoppable property and stopped status

* Made MO stoppable

* Removed duplicate code

* Removed duplicate code

* Copy read-only property

* Renaming

* Add an assistant for reserving scsi ids

* Don't show action if no device attached

* Implement a device_info app path, and cut down on device columns always shown

* Cleanup

* Removed duplicate code, added START/STOP

* Improved default parameter handling

* Updated load/eject handling

* Logging update

* Fixed typo

* Verified START/STOP UNIT

* Updated logging

* Updated status handling

* Updated status handling

* More status handling updates

* Logging update

* Made instance fields local variables

* Removed duplicate code, added START/STOP

* Improved default parameter handling

* Updated load/eject handling

* Logging update

* Fixed typo

* Verified START/STOP UNIT

* Updated logging

* Updated status handling

* Updated status handling

* More status handling updates

* Logging update

* Made instance fields local variables

* Made disk_t private

* Made some data structures private

* Fixed ARM compile issue

* Fast forward instead of rebase existing git repo

* Fixed ctapdriver initialization issue

* Reset read-only status when opening an image file

* Cleanup

* Cleanup

* Made logging more consistent

* Updated log level

* Cleanup

* Log load/eject on error level for testing

* Revert "Log load/eject on error level for testing"

This reverts commit d35a15ea8e.

* Assume drive is not ready after having been stopped

* Updated status handling

* Make the csv config files store all relevant device data for reading

* Read 9 column csv config files

* Fixed typo

* Rebuild manpage

* Fixed issue #234 (MODE SENSE (10) returns wrong mode parameter header)

* Removed unused code

* Enum data type update

* Removed duplicate range check

* Removed duplicate code

* Removed more duplicate code

* Logging update

* SCCD sector size was not meant to be configurable

* Better error handling for csv reading and writing

* Updated configurable sector size properties

* Removed assertion

* Improved error handling

* Updated error handling

* Re-added special error handling only relevant for SASI

* Added TODOs

* Comment update

* Added override modifier

* Removed obsolete debug flag (related code was not called)

* Comment and logging updates

* Removed obsolete try/catch

* Revert "Removed obsolete try/catch"

This reverts commit 39ca12d8b1.

* Comment update

* Removed duplicate code

* Updated error messages, use more foreach loops

* Avoid storing RaSCSI generated product info in config file

* Updated logging

* Logging update

* Save config files in json instead of csv

* Fix bugs with json config loading

* Refactoring & remove unused code

* Refactoring

* Display upper case file endings in file management list

* Only show product vendor for non-RaSCSI devices in the device list

* Translate code comment

* Refactoring

* Fix bad identation

* Improve valid file extension handling

* Add validation when attaching removable media

* Display valid file endings under the file list

* Cleanup

* Don't store 0 block size

* Fix indentation

* Read and write config files in key:pair format

* Add section for controlling logging

* README update

* Added block_count

* Cleanup, fix typos

* Support attaching CD-ROM with custom block size

* Evaluate block size when inserting a media

* rasctl display capacity if available

* Info message update

* Use kwargs for device attachment

* Fix bugs in attach_image kwargs; make config file more readable

* POC for attaching device with profile

* Only list product types valid for the particular image file

* Perform validation of HDD image size based on the product profile

* Implement sidecar config files for drive images.

* Added missing product name to NEC vital product data

* MO block size depends on capacity only

* Better error handling for device sidecar config loading

* Extended property/status display

* Property display update

* Updated error handling

* Handle image sizes in bytes internally

* Revert change

* Resolve bad merge

Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
This commit is contained in:
Daniel Markstedt 2021-09-14 19:51:12 -07:00 committed by GitHub
parent 3e7f317c49
commit 8a3642bf9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1999 additions and 1537 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ core
*.swp
__pycache__
src/web/current
src/web/rascsi_interface_pb2.py
src/oled_monitor/current
src/raspberrypi/hfdisk/
*~

View File

@ -20,6 +20,32 @@ logo="""
echo -e $logo
}
function showMacNetworkWired(){
logo="""
                              .-~-.-~~~-.~-.\n
 ╔═══════╗                  .(              )\n
 ║|¯¯¯¯¯|║                 /               \`.\n
 ║|_____|║>--------------<~               .   )\n
 ║ .  __ ║                 (              :'-'\n
 ╚╦═════╦╝                  ~-.________.:'\n
  ¯¯¯¯¯¯¯\n
"""
echo -e $logo
}
function showMacNetworkWireless(){
logo="""
                              .-~-.-~~~-.~-.\n
 ╔═══════╗        .(       .(              )\n
 ║|¯¯¯¯¯|║  .(  .(        /               \`.\n
 ║|_____|║ .o    o       ~               .   )\n
 ║ .  __ ║  '(  '(        (              :'-'\n
 ╚╦═════╦╝        '(       ~-.________.:'\n
  ¯¯¯¯¯¯¯\n
"""
echo -e $logo
}
VIRTUAL_DRIVER_PATH=/home/pi/images
HFS_FORMAT=/usr/bin/hformat
HFDISK_BIN=/usr/bin/hfdisk
@ -41,18 +67,19 @@ function initialChecks() {
fi
}
# install all dependency packages for RaSCSI Service
function installPackages() {
sudo apt-get update && sudo apt install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv nginx libpcap-dev protobuf-compiler bridge-utils python3-dev libev-dev libevdev2 -y
}
# install all dependency packages for RaSCSI Service
# compile and install RaSCSI Service
function installRaScsi() {
installPackages
sudo systemctl stop rascsi
cd ~/RASCSI/src/raspberrypi
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
make clean
make all CONNECT_TYPE=${CONNECT_TYPE-FULLSPEC}
sudo make install CONNECT_TYPE=${CONNECT_TYPE-FULLSPEC}
sudoIsReady=$(sudo grep -c "rascsi" /etc/sudoers)
@ -71,7 +98,39 @@ www-data ALL=NOPASSWD: /sbin/shutdown, /sbin/reboot
sudo systemctl start rascsi
}
# install everything required to run an HTTP server (Nginx + Python Flask App)
function installRaScsiWebInterface() {
echo "Compiling the Python protobuf library..."
[ -f ~/RASCSI/src/web/rascsi_interface.proto ] && rm ~/RASCSI/src/web/rascsi_interface.proto
protoc -I=/home/pi/RASCSI/src/raspberrypi/ --python_out=/home/pi/RASCSI/src/web/ rascsi_interface.proto
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
sudo usermod -a -G pi www-data
sudo systemctl reload nginx
echo "Installing the rascsi-web.service configuration..."
sudo cp ~/RASCSI/src/web/service-infra/rascsi-web.service /etc/systemd/system/rascsi-web.service
sudo systemctl daemon-reload
sudo systemctl enable rascsi-web
sudo systemctl start rascsi-web
}
function createImagesDir() {
if [ -d $VIRTUAL_DRIVER_PATH ]; then
echo "The $VIRTUAL_DRIVER_PATH directory already exists."
else
echo "The $VIRTUAL_DRIVER_PATH directory does not exist; creating..."
mkdir -p $VIRTUAL_DRIVER_PATH
chmod -R 775 $VIRTUAL_DRIVER_PATH
fi
}
function stopOldWebInterface() {
sudo systemctl stop rascsi-web
APACHE_STATUS=$(sudo systemctl status apache2 &> /dev/null; echo $?)
if [ "$APACHE_STATUS" -eq 0 ] ; then
echo "Stopping old Apache2 RaSCSI Web..."
@ -80,28 +139,6 @@ function stopOldWebInterface() {
fi
}
# install everything required to run an HTTP server (Nginx + Python Flask App)
function installRaScsiWebInterface() {
stopOldWebInterface
installPackages
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
mkdir -p $VIRTUAL_DRIVER_PATH
chmod -R 775 $VIRTUAL_DRIVER_PATH
groups www-data
sudo usermod -a -G pi www-data
groups www-data
sudo systemctl reload nginx
sudo cp ~/RASCSI/src/web/service-infra/rascsi-web.service /etc/systemd/system/rascsi-web.service
sudo systemctl daemon-reload
sudo systemctl enable rascsi-web
sudo systemctl start rascsi-web
}
function updateRaScsiGit() {
echo "Updating checked out branch $GIT_REMOTE/$GIT_BRANCH"
cd ~/RASCSI
@ -112,8 +149,7 @@ function updateRaScsiGit() {
stashed=1
fi
git fetch $GIT_REMOTE
git rebase $GIT_REMOTE/$GIT_BRANCH
git pull --ff-only
if [ $stashed -eq 1 ]; then
echo "Reapplying local changes..."
@ -121,33 +157,14 @@ function updateRaScsiGit() {
fi
}
function updateRaScsi() {
updateRaScsiGit
installPackages
sudo systemctl stop rascsi
cd ~/RASCSI/src/raspberrypi
make clean
make all CONNECT_TYPE=FULLSPEC
sudo make install CONNECT_TYPE=FULLSPEC
sudo systemctl start rascsi
}
function updateRaScsiWebInterface() {
stopOldWebInterface
updateRaScsiGit
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
echo "Restarting rascsi-web services..."
sudo systemctl restart rascsi-web
sudo systemctl restart nginx
}
function showRaScsiStatus() {
sudo systemctl status rascsi | tee
}
function showRaScsiWebStatus() {
sudo systemctl status rascsi-web | tee
}
function createDrive600MB() {
createDrive 600 "HD600"
}
@ -184,6 +201,22 @@ function formatDrive() {
fi
# Inject hfdisk commands to create Drive with correct partitions
# https://www.codesrc.com/mediawiki/index.php/HFSFromScratch
# i initialize partition map
# continue with default first block
# C Create 1st partition with type specified next)
# continue with default
# 32 32 blocks (required for HFS+)
# Driver_Partition Partition Name
# Apple_Driver Partition Type (available types: Apple_Driver, Apple_Driver43, Apple_Free, Apple_HFS...)
# C Create 2nd partition with type specified next
# continue with default first block
# continue with default block size (rest of the disk)
# ${volumeName} Partition name provided by user
# Apple_HFS Partition Type
# w Write partition map to disk
# y Confirm partition table
# p Print partition map
(echo i; echo ; echo C; echo ; echo 32; echo "Driver_Partition"; echo "Apple_Driver"; echo C; echo ; echo ; echo "${volumeName}"; echo "Apple_HFS"; echo w; echo y; echo p;) | $HFDISK_BIN "$diskPath"
partitionOk=$?
@ -242,55 +275,208 @@ function createDrive() {
fi
}
function setupWiredNetworking() {
echo "Setting up wired network..."
LAN_INTERFACE=eth0
echo "$LAN_INTERFACE will be configured for network forwarding with DHCP."
echo ""
echo "WARNING: If you continue, the IP address of your Pi may change upon reboot."
echo "Please make sure you will not lose access to the Pi system."
echo ""
echo "Do you want to proceed with network configuration using the default settings? Y/n"
read REPLY
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
echo "Available wired interfaces on this system:"
ip -o addr show scope link | awk '{split($0, a); print $2}' | grep eth
echo "Please type the wired interface you want to use and press Enter:"
read -r SELECTED
LAN_INTERFACE=$SELECTED
fi
if [ $(grep -c "^denyinterfaces" /etc/dhcpcd.conf) -ge 1 ]; then
echo "WARNING: Network forwarding may already have been configured. Proceeding will overwrite the configuration."
echo "Press enter to continue or CTRL-C to exit"
read REPLY
sudo sed -i /^denyinterfaces/d /etc/dhcpcd.conf
fi
sudo echo "denyinterfaces $LAN_INTERFACE" >> /etc/dhcpcd.conf
echo "Modified /etc/dhcpcd.conf"
# default config file is made for eth0, this will set the right net interface
sudo bash -c 'sed s/eth0/'"$LAN_INTERFACE"'/g /home/pi/RASCSI/src/raspberrypi/os_integration/rascsi_bridge > /etc/network/interfaces.d/rascsi_bridge'
echo "Modified /etc/network/interfaces.d/rascsi_bridge"
echo "Configuration completed!"
echo "Please make sure you attach ia DaynaPORT network adapter to the RaSCSI configuration."
echo "Either use the Web UI, or do this on the command line (assuming SCSI ID 6): \"rascsi -ID 6 -t scdp $LAN_INTERFACE\""
echo ""
echo "We need to reboot your Pi"
echo "Press Enter to reboot or CTRL-C to exit"
read
echo "Rebooting..."
sleep 3
sudo reboot
}
function setupWirelessNetworking() {
NETWORK="10.10.20"
IP=$NETWORK.2 # Macintosh or Device IP
NETWORK_MASK="255.255.255.0"
CIDR="24"
ROUTER_IP=$NETWORK.1
ROUTING_ADDRESS=$NETWORK.0/$CIDR
WLAN_INTERFACE="wlan0"
echo "$WLAN_INTERFACE will be configured for network forwarding with static IP assignment."
echo "Configure your Macintosh or other device with the following:"
echo "IP Address (static): $IP"
echo "Router Address: $ROUTER_IP"
echo "Subnet Mask: $NETWORK_MASK"
echo "DNS Server: Any public DNS server"
echo ""
echo "Do you want to proceed with network configuration using the default settings? Y/n"
read REPLY
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
echo "Available wireless interfaces on this system:"
ip -o addr show scope link | awk '{split($0, a); print $2}' | grep wlan
echo "Please type the wireless interface you want to use and press Enter:"
read -r WLAN_INTERFACE
echo "Base IP address (ex. 10.10.20):"
read -r NETWORK
echo "CIDR for Subnet Mask (ex. '24' for 255.255.255.0):"
read -r CIDR
ROUTER_IP=$NETWORK.1
ROUTING_ADDRESS=$NETWORK.0/$CIDR
fi
if [ $(grep -c "^net.ipv4.ip_forward=1" /etc/sysctl.conf) -ge 1 ]; then
echo "WARNING: Network forwarding may already have been configured. Proceeding will overwrite the configuration."
echo "Press enter to continue or CTRL-C to exit"
read REPLY
else
sudo bash -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf'
echo "Modified /etc/sysctl.conf"
fi
sudo iptables --flush
sudo iptables -t nat -F
sudo iptables -X
sudo iptables -Z
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -t nat -A POSTROUTING -o $WLAN_INTERFACE -s $ROUTING_ADDRESS -j MASQUERADE
# Check if iptables-persistent is installed
IPTABLES_PERSISTENT=$(dpkg -s iptables-persistent | grep Status | grep -c "install ok")
if [ $IPTABLES_PERSISTENT -eq 0 ]; then
sudo apt-get install iptables-persistent --assume-yes
else
sudo iptables-save --file /etc/iptables/rules.v4
fi
echo "Modified /etc/iptables/rules.v4"
echo "Configuration completed!"
echo ""
echo "Please make sure you attach a DaynaPORT network adapter to the RaSCSI configuration"
echo "Either use the Web UI, or do this on the command line (assuming SCSI ID 6): \"rascsi -ID 6 -t scdp $WLAN_INTERFACE:$ROUTER_IP/$CIDR\""
echo ""
echo "We need to reboot your Pi"
echo "Press Enter to reboot or CTRL-C to exit"
read REPLY
echo "Rebooting..."
sleep 3
sudo reboot
}
function reserveScsiIds() {
if [ ! -f /etc/systemd/system/rascsi-web.service ]; then
echo "This feature depends on the RaSCSI Web UI being installed. Please install RaSCSI Web before continuing."
exit
fi
sudo systemctl stop rascsi-web
echo "Please type the SCSI ID(s) that you want to reserve and press Enter:"
echo "The input should be a string of digits without separators, e.g. \"017\" for IDs 0, 1, and 7."
read -r RESERVED_IDS
sudo sed -i /^ExecStart=/d /etc/systemd/system/rascsi-web.service
sudo sed -i "8 i ExecStart=/home/pi/RASCSI/src/web/start.sh --reserved_ids=$RESERVED_IDS" /etc/systemd/system/rascsi-web.service
sudo systemctl daemon-reload
sudo systemctl start rascsi-web
}
function runChoice() {
case $1 in
0)
echo "Installing RaSCSI Service + Web interface"
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive"
stopOldWebInterface
updateRaScsiGit
createImagesDir
installPackages
installRaScsi
installRaScsiWebInterface
createDrive600MB
showRaScsiStatus
echo "Installing RaSCSI Service + Web interface - Complete!"
showRaScsiWebStatus
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive - Complete!"
;;
1)
echo "Installing RaSCSI Service"
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface"
stopOldWebInterface
updateRaScsiGit
createImagesDir
installPackages
installRaScsi
installRaScsiWebInterface
showRaScsiStatus
echo "Installing RaSCSI Service - Complete!"
showRaScsiWebStatus
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface - Complete!"
;;
2)
echo "Installing RaSCSI Web interface"
installRaScsiWebInterface
echo "Installing RaSCSI Web interface - Complete!"
;;
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC})"
updateRaScsiGit
createImagesDir
installPackages
installRaScsi
showRaScsiStatus
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) - Complete!"
;;
3)
echo "Updating RaSCSI Service + Web interface"
updateRaScsi
updateRaScsiWebInterface
showRaScsiStatus
echo "Updating RaSCSI Service + Web interface - Complete!"
;;
4)
echo "Updating RaSCSI Service"
updateRaScsi
showRaScsiStatus
echo "Updating RaSCSI Service - Complete!"
;;
5)
echo "Updating RaSCSI Web interface"
updateRaScsiWebInterface
echo "Updating RaSCSI Web interface - Complete!"
;;
6)
echo "Creating a 600MB drive"
createDrive600MB
echo "Creating a 600MB drive - Complete!"
;;
7)
4)
echo "Creating a custom drive"
createDriveCustom
echo "Creating a custom drive - Complete!"
;;
5)
echo "Configuring wired network bridge"
showMacNetworkWired
setupWiredNetworking
echo "Configuring wired network bridge - Complete!"
;;
6)
echo "Configuring wifi network bridge"
showMacNetworkWireless
setupWirelessNetworking
echo "Configuring wifi network bridge - Complete!"
;;
7)
echo "Reserving SCSI IDs"
reserveScsiIds
showRaScsiWebStatus
echo "Reserving SCSI IDs - Complete!"
;;
-h|--help|h|help)
showMenu
;;
@ -314,25 +500,53 @@ function readChoice() {
function showMenu() {
echo ""
echo "Choose among the following options:"
echo "INSTALL"
echo " 0) install RaSCSI Service + web interface + 600MB Drive (recommended)"
echo " 1) install RaSCSI Service (initial)"
echo " 2) install RaSCSI Web interface"
echo "UPDATE"
echo " 3) update RaSCSI Service + web interface (recommended)"
echo " 4) update RaSCSI Service"
echo " 5) update RaSCSI Web interface"
echo "CREATE EMPTY DRIVE"
echo " 6) 600MB drive (recommended)"
echo " 7) custom drive size (up to 4000MB)"
echo "INSTALL/UPDATE RASCSI (${CONNECT_TYPE-FULLSPEC} version)"
echo " 0) install or update RaSCSI Service + web interface + 600MB Drive (recommended)"
echo " 1) install or update RaSCSI Service + web interface"
echo " 2) install or update RaSCSI Service"
echo "CREATE EMPTY DRIVE IMAGE"
echo " 3) 600MB drive (recommended)"
echo " 4) custom drive size (up to 4000MB)"
echo "NETWORK ASSISTANT"
echo " 5) configure network forwarding over Ethernet (DHCP)"
echo " 6) configure network forwarding over WiFi (static IP)"
echo "MISCELLANEOUS"
echo " 7) reserve SCSI IDs"
}
# parse arguments
while [ "$1" != "" ]; do
PARAM=`echo $1 | awk -F= '{print $1}'`
VALUE=`echo $1 | awk -F= '{print $2}'`
case $PARAM in
-c | --connect_type)
CONNECT_TYPE=$VALUE
;;
-r | --run_choice)
RUN_CHOICE=$VALUE
;;
*)
echo "ERROR: unknown parameter \"$PARAM\""
exit 1
;;
esac
case $VALUE in
FULLSPEC | STANDARD | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7)
;;
*)
echo "ERROR: unknown option \"$VALUE\""
exit 1
;;
esac
shift
done
showRaSCSILogo
initialChecks
if [ -z "${1}" ]; then # $1 is unset, show menu
if [ -z "${RUN_CHOICE}" ]; then # RUN_CHOICE is unset, show menu
showMenu
readChoice
else
runChoice "$1"
runChoice "$RUN_CHOICE"
fi

File diff suppressed because it is too large Load Diff

View File

@ -127,7 +127,7 @@ namespace Human68k {
};
struct namests_t {
BYTE wildcard; ///< Wildcard array
BYTE wildcard; ///< Wildcard character length
BYTE drive; ///< Drive number
BYTE path[65]; ///< Path (subdirectory +/)
BYTE name[8]; ///< File name (PADDING 0x20)
@ -332,45 +332,44 @@ enum {
// Bit2430 Duplicate file identification mark 0:Automatic 1127:Chars
};
/// ファイルシステム動作フラグ
/// File system operational flag
/**
01
(USBストレージとか)
Normal is 0. Becomes 1 if attempting to mount in read-only mode.
Reserving the other values for future use.
Insurance against hard-to-detect devices such as homemade USB storage.
*/
enum {
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: 強制書き込み禁止
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: 強制リムーバブルメディア
FSFLAG_MANUAL = 0x00000004, ///< Bit2: 強制手動イジェクト
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
};
//===========================================================================
//
/// まるっとリングリスト
/// Full ring list
///
/// 先頭(root.next)が最も新しいオブジェクト。
/// 末尾(root.prev)が最も古い/未使用オブジェクト。
/// コード効率追求のため、delete時は必ずポインタをアップキャストすること。
/// First (root.next) is the most recent object.
/// Last (root.prev) is the oldest / unused object.
/// For code optimization purposes, always upcast the pointer when deleting.
//
//===========================================================================
class CRing {
public:
// 基本ファンクション
CRing() { Init(); } ///< デフォルトコンストラクタ
~CRing() { Remove(); } ///< デストラクタ final
void Init() { next = prev = this; } ///< 初期化
CRing() { Init(); }
~CRing() { Remove(); }
void Init() { next = prev = this; }
CRing* Next() const { return next; } ///< 次の要素を取得
CRing* Prev() const { return prev; } ///< 前の要素を取得
CRing* Next() const { return next; } ///< Get the next element
CRing* Prev() const { return prev; } ///< Get the previous element
void Insert(CRing* pRoot)
{
// 該当オブジェクトを切り離し
// Separate the relevant objects
ASSERT(next);
ASSERT(prev);
next->prev = prev;
prev->next = next;
// リング先頭へ挿入
// Insert into the beginning of the ring
ASSERT(pRoot);
ASSERT(pRoot->next);
next = pRoot->next;
@ -378,16 +377,16 @@ public:
pRoot->next->prev = this;
pRoot->next = this;
}
///< オブジェクト切り離し & リング先頭へ挿入
///< Separate objects & insert into the beginning of the ring
void InsertTail(CRing* pRoot)
{
// 該当オブジェクトを切り離し
// Separate the relevant objects
ASSERT(next);
ASSERT(prev);
next->prev = prev;
prev->next = next;
// リング末尾へ挿入
// Insert into the end of the ring
ASSERT(pRoot);
ASSERT(pRoot->prev);
next = pRoot;
@ -395,13 +394,13 @@ public:
pRoot->prev->next = this;
pRoot->prev = this;
}
///< オブジェクト切り離し & リング末尾へ挿入
///< Separate objects & insert into the end of the ring
void InsertRing(CRing* pRoot)
{
if (next == prev) return;
// リング先頭へ挿入
// Insert into the beginning of the ring
ASSERT(pRoot);
ASSERT(pRoot->next);
pRoot->next->prev = prev;
@ -409,554 +408,537 @@ public:
pRoot->next = next;
next->prev = pRoot;
// 自分自身を空にする
// Empty self
next = prev = this;
}
///< 自分以外のオブジェクト切り離し & リング先頭へ挿入
///< Separate objects except self & insert into the beginning of the ring
void Remove()
{
// 該当オブジェクトを切り離し
// Separate the relevant objects
ASSERT(next);
ASSERT(prev);
next->prev = prev;
prev->next = next;
// 安全のため自分自身を指しておく (何度切り離しても問題ない)
// To be safe, assign self (nothing stops you from separating any number of times)
next = prev = this;
}
///< オブジェクト切り離し
///< Separate objects
private:
CRing* next; ///< 次の要素
CRing* prev; ///< 前の要素
CRing* next; ///< Next element
CRing* prev; ///< Previous element
};
//===========================================================================
//
/// ディレクトリエントリ ファイル名
/// Directory Entry: File Name
//
//===========================================================================
class CHostFilename {
public:
// 基本ファンクション
CHostFilename(); ///< デフォルトコンストラクタ
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< オフセット位置取得
CHostFilename();
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
void SetHost(const TCHAR* szHost); ///< ホスト側の名称を設定
const TCHAR* GetHost() const { return m_szHost; } ///< ホスト側の名称を取得
void ConvertHuman(int nCount = -1); ///< Human68k側の名称を変換
void CopyHuman(const BYTE* szHuman); ///< Human68k側の名称を複製
BOOL isReduce() const; ///< Human68k側の名称が加工されたか調査
BOOL isCorrect() const { return m_bCorrect; } ///< Human68k側のファイル名規則に合致しているか調査
const BYTE* GetHuman() const { return m_szHuman; } ///< Human68kファイル名を取得
void SetHost(const TCHAR* szHost); ///< Set the name of the host
const TCHAR* GetHost() const { return m_szHost; } ///< Get the name of the host
void ConvertHuman(int nCount = -1); ///< Convert the Human68k name
void CopyHuman(const BYTE* szHuman); ///< Copy the Human68k name
BOOL isReduce() const; ///< Inspect if the Human68k name is generated
BOOL isCorrect() const { return m_bCorrect; } ///< Inspect if the Human68k file name adhers to naming rules
const BYTE* GetHuman() const { return m_szHuman; } ///< Get Human68k file name
const BYTE* GetHumanLast() const
{ return m_pszHumanLast; } ///< Human68kファイル名を取得
const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Human68kファイル名を取得
void SetEntryName(); ///< Human68kディレクトリエントリを設定
{ return m_pszHumanLast; } ///< Get Human68k file name
const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Get Human68k file name
void SetEntryName(); ///< Set Human68k directory entry
void SetEntryAttribute(BYTE nHumanAttribute)
{ m_dirHuman.attr = nHumanAttribute; } ///< Human68kディレクトリエントリを設定
{ m_dirHuman.attr = nHumanAttribute; } ///< Set Human68k directory entry
void SetEntrySize(DWORD nHumanSize)
{ m_dirHuman.size = nHumanSize; } ///< Human68kディレクトリエントリを設定
{ m_dirHuman.size = nHumanSize; } ///< Set Human68k directory entry
void SetEntryDate(WORD nHumanDate)
{ m_dirHuman.date = nHumanDate; } ///< Human68kディレクトリエントリを設定
{ m_dirHuman.date = nHumanDate; } ///< Set Human68k directory entry
void SetEntryTime(WORD nHumanTime)
{ m_dirHuman.time = nHumanTime; } ///< Human68kディレクトリエントリを設定
{ m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry
void SetEntryCluster(WORD nHumanCluster)
{ m_dirHuman.cluster = nHumanCluster; } ///< Human68kディレクトリエントリを設定
{ m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry
const Human68k::dirent_t* GetEntry() const
{ return &m_dirHuman; } ///< Human68kディレクトリエントリを取得
BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Human68kディレクトリエントリの属性判定
{ return &m_dirHuman; } ///< Get Human68k directory entry
BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes
BOOL isSameEntry(const Human68k::dirent_t* pdirHuman) const
{ ASSERT(pdirHuman); return memcmp(&m_dirHuman, pdirHuman, sizeof(m_dirHuman)) == 0; }
///< Human68kディレクトリエントリの一致判定
///< Determine Human68k directory entry match
// パス名操作
static const BYTE* SeparateExt(const BYTE* szHuman); ///< Human68kファイル名から拡張子を分離
// Path name operations
static const BYTE* SeparateExt(const BYTE* szHuman); ///< Extract extension from Human68k file name
private:
static BYTE* CopyName(BYTE* pWrite, const BYTE* pFirst, const BYTE* pLast);
///< Human68k側のファイル名要素をコピー
///< Copy Human68k file name elements
const BYTE* m_pszHumanLast; ///< 該当エントリのHuman68k内部名の終端位置
const BYTE* m_pszHumanExt; ///< 該当エントリのHuman68k内部名の拡張子位置
BOOL m_bCorrect; ///< 該当エントリのHuman68k内部名が正しければ真
BYTE m_szHuman[24]; ///< 該当エントリのHuman68k内部名
Human68k::dirent_t m_dirHuman; ///< 該当エントリのHuman68k全情報
TCHAR m_szHost[FILEPATH_MAX]; ///< 該当エントリのホスト側の名称 (可変長)
const BYTE* m_pszHumanLast; ///< Last position of the Human68k internal name of the relevant entry
const BYTE* m_pszHumanExt; ///< Position of the extension of the Human68k internal name of the relevant entry
BOOL m_bCorrect; ///< TRUE if the relevant entry of the Human68k internal name is correct
BYTE m_szHuman[24]; ///< Human68k internal name of the relevant entry
Human68k::dirent_t m_dirHuman; ///< All information for the Human68k relevant entry
TCHAR m_szHost[FILEPATH_MAX]; ///< The host name of the relevant entry (variable length)
};
//===========================================================================
//
/// ディレクトリエントリ パス名
/// Directory entry: path name
///
/// Human68k側のパス名は、必ず先頭が/で始まり、末尾が/で終わる。
/// ユニット番号は持たない。
/// 高速化のため、ホスト側の名称にはベースパス部分も含む。
/// A file path in Human68k always begings with / and ends with /
/// They don't hold unit numbers.
/// Include the base path part of the name on the host side for a performance boost.
//
//===========================================================================
/** @note
Human68kのアプリは
Most Human68k applications are written in a way that expects time stamps not to
get updated for new directories created as a result of file operations, which
triggers updates to directory entires.
However, on the host file system, new directories do typically get an updated time stamp.
The unfortunate outcome is that when copying a directory for instance, the time stamp
will get overwritten even if the application did not intend for the time stamp to get updated.
FATタイムスタンプのエミュレーション
Human68k側の期待する結果と一致させる
Here follows an implementation of a directory cache FAT time stamp emulation feature.
At the time of a file system update on the host side, time stamp information will be restored
in order to achieve expected behavior on the Human68k side.
*/
class CHostPath: public CRing {
/// メモリ管理用
/// For memory management
struct ring_t {
CRing r; ///< 円環
CHostFilename f; ///< 実体
CRing r;
CHostFilename f;
};
public:
/// 検索用バッファ
/// Search buffer
struct find_t {
DWORD count; ///< 検索実行回数 + 1 (0のときは以下の値は無効)
DWORD id; ///< 次回検索を続行するパスのエントリ識別ID
const ring_t* pos; ///< 次回検索を続行する位置 (識別ID一致時)
Human68k::dirent_t entry; ///< 次回検索を続行するエントリ内容
DWORD count; ///< Search execution count + 1 (When 0 the below value is invalid)
DWORD id; ///< Entry unique ID for the path of the next search
const ring_t* pos; ///< Position of the next search (When identical to unique ID)
Human68k::dirent_t entry; ///< Contents of the next seach entry
void Clear() { count = 0; } ///< 初期化
void Clear() { count = 0; } ///< Initialize
};
// 基本ファンクション
CHostPath(); ///< デフォルトコンストラクタ
~CHostPath(); ///< デストラクタ final
void Clean(); ///< 再利用のための初期化
CHostPath();
~CHostPath();
void Clean(); ///< Initialialize for reuse
void SetHuman(const BYTE* szHuman); ///< Human68k側の名称を直接指定する
void SetHost(const TCHAR* szHost); ///< ホスト側の名称を直接指定する
BOOL isSameHuman(const BYTE* szHuman) const; ///< Human68k側の名称を比較する
BOOL isSameChild(const BYTE* szHuman) const; ///< Human68k側の名称を比較する
const TCHAR* GetHost() const { return m_szHost; } ///< ホスト側の名称の獲得
void SetHuman(const BYTE* szHuman); ///< Directly specify the name on the Human68k side
void SetHost(const TCHAR* szHost); ///< Directly specify the name on the host side
BOOL isSameHuman(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
BOOL isSameChild(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
const TCHAR* GetHost() const { return m_szHost; } ///< Obtain the name on the host side
const CHostFilename* FindFilename(const BYTE* szHuman, DWORD nHumanAttribute = Human68k::AT_ALL) const;
///< ファイル名を検索
///< Find file name
const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const;
///< ファイル名を検索 (ワイルドカード対応)
BOOL isRefresh(); ///< ファイル変更が行なわれたか確認
void Refresh(); ///< ファイル再構成
void Backup(); /// ホスト側のタイムスタンプを保存
void Restore() const; /// ホスト側のタイムスタンプを復元
void Release(); ///< 更新
///< Find file name (with support for wildcards)
BOOL isRefresh(); ///< Check that the file change has been done
void Refresh(); ///< Refresh file
void Backup(); /// Backup the time stamp on the host side
void Restore() const; /// Restore the time stamp on the host side
void Release(); ///< Update
// CHostEntryが利用する外部API
static void InitId() { g_nId = 0; } ///< 識別ID生成用カウンタ初期化
// CHostEntry is an external API that we use
static void InitId() { g_nId = 0; } ///< Initialize the counter for the unique ID generation
private:
static ring_t* Alloc(size_t nLength); ///< ファイル名領域確保
static void Free(ring_t* pRing); ///< ファイル名領域解放
static ring_t* Alloc(size_t nLength); ///< Allocate memory for the file name
static void Free(ring_t* pRing); ///< Release memory for the file name
static int Compare(const BYTE* pFirst, const BYTE* pLast, const BYTE* pBufFirst, const BYTE* pBufLast);
///< 文字列比較 (ワイルドカード対応)
///< Compare string (with support for wildcards)
CRing m_cRing; ///< CHostFilename連結用
time_t m_tBackup; ///< 時刻復元用
BOOL m_bRefresh; ///< 更新フラグ
DWORD m_nId; ///< 識別ID (値が変化した場合は更新を意味する)
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< 該当エントリのHuman68k内部名
TCHAR m_szHost[FILEPATH_MAX]; ///< 該当エントリのホスト側の名称
CRing m_cRing; ///< For CHostFilename linking
time_t m_tBackup; ///< For time stamp restoration
BOOL m_bRefresh; ///< Refresh flag
DWORD m_nId; ///< Unique ID (When the value has changed, it means an update has been made)
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< The internal Human68k name for the relevant entry
TCHAR m_szHost[FILEPATH_MAX]; ///< The host side name for the relevant entry
static DWORD g_nId; ///< 識別ID生成用カウンタ
static DWORD g_nId; ///< Counter for the unique ID generation
};
//===========================================================================
//
/// ファイル検索処理
/// File search processing
///
/// Human68k側のファイル名を内部Unicodeで処理するのは正直キツい。と
/// いうわけで、全てBYTEに変換して処理する。変換処理はディレクトリエ
/// ントリキャッシュが一手に担い、WINDRV側はすべてシフトJISのみで扱
/// えるようにする。
/// また、Human68k側名称は、完全にベースパス指定から独立させる。
/// It's pretty much impossible to process Human68k file names as Unicode internally.
/// So, we carry out binary conversion for processing. We leave it up to the
/// directory entry cache to handle the conversion, which allows WINDRV to read
/// everything as Shift-JIS. Additionally, it allows Human68k names to be
/// fully independent of base path assignments.
///
/// ファイルを扱う直前に、ディレクトリエントリのキャッシュを生成する。
/// ディレクトリエントリの生成処理は高コストのため、一度生成したエントリは
/// 可能な限り維持して使い回す。
/// We create directory entry cache just before file handling.
/// Since creating directory entires is very costly, we try to reuse created entries
/// as much as humanly possible.
///
/// ファイル検索は3方式。すべてCHostFiles::Find()で処理する。
/// 1. パス名のみ検索 属性はディレクトリのみ _CHKDIR _CREATE
/// 2. パス名+ファイル名+属性の検索 _OPEN
/// 3. パス名+ワイルドカード+属性の検索 _FILES _NFILES
/// 検索結果は、ディレクトリエントリ情報として保持しておく。
/// There are three kinds of file search. They are all processed in CHostFiles::Find()
/// 1. Search by path name only; the only attribute is 'directory'; _CHKDIR _CREATE
/// 2. Path + file name + attribute search; _OPEN
/// 3. Path + wildcard + attribute search; _FILES _NFILES
/// The search results are kept as directory entry data.
//
//===========================================================================
class CHostFiles {
public:
// 基本ファンクション
CHostFiles() { SetKey(0); Init(); } ///< デフォルトコンストラクタ
void Init(); ///< 初期化
CHostFiles() { SetKey(0); Init(); }
void Init();
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< 検索キー設定
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< 検索キー比較
void SetPath(const Human68k::namests_t* pNamests); ///< パス名・ファイル名を内部で生成
BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< ルートディレクトリ判定
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< ワイルドカードによるファイル検索を有効化
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< パス名のみを有効化
BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< パス名のみ設定か判定
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetPath(const Human68k::namests_t* pNamests); ///< Create path and file name internally
BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< Check if root directory
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< Enable file search using wildcards
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< Enable only path names
BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< Check if set to only path names
void SetAttribute(DWORD nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; }
///< 検索属性を設定
BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Human68k側でファイルを検索しホスト側の情報を生成
const CHostFilename* Find(CHostPath* pPath); ///< ファイル名検索
void SetEntry(const CHostFilename* pFilename); ///< Human68k側の検索結果保存
void SetResult(const TCHAR* szPath); ///< ホスト側の名称を設定
void AddResult(const TCHAR* szPath); ///< ホスト側の名称にファイル名を追加
void AddFilename(); ///< ホスト側の名称にHuman68kの新規ファイル名を追加
///< Set search attribute
BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side
const CHostFilename* Find(CHostPath* pPath); ///< Find file name
void SetEntry(const CHostFilename* pFilename); ///< Store search results on the Human68k side
void SetResult(const TCHAR* szPath); ///< Set names on the host side
void AddResult(const TCHAR* szPath); ///< Add file name to the name on the host side
void AddFilename(); ///< Add the new Human68k file name to the name on the host side
const TCHAR* GetPath() const { return m_szHostResult; } ///< ホスト側の名称を取得
const TCHAR* GetPath() const { return m_szHostResult; } ///< Get the name on the host side
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Human68kディレクトリエントリを取得
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Get Human68k directory entry
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Human68k属性を取得
WORD GetDate() const { return m_dirHuman.date; } ///< Human68k日付を取得
WORD GetTime() const { return m_dirHuman.time; } ///< Human68k時刻を取得
DWORD GetSize() const { return m_dirHuman.size; } ///< Human68kファイルサイズを取得
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Human68kファイル名を取得
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Human68kファイル名検索結果を取得
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Human68kパス名を取得
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute
WORD GetDate() const { return m_dirHuman.date; } ///< Get Human68k date
WORD GetTime() const { return m_dirHuman.time; } ///< Get Human68k time
DWORD GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Get Human68k file name
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Get Human68k file name search results
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
private:
DWORD m_nKey; ///< Human68kのFILESバッファアドレス 0なら未使用
DWORD m_nHumanWildcard; ///< Human68kのワイルドカード情報
DWORD m_nHumanAttribute; ///< Human68kの検索属性
CHostPath::find_t m_findNext; ///< 次回検索位置情報
Human68k::dirent_t m_dirHuman; ///< 検索結果 Human68kファイル情報
BYTE m_szHumanFilename[24]; ///< Human68kのファイル名
BYTE m_szHumanResult[24]; ///< 検索結果 Human68kファイル名
DWORD m_nKey; ///< FILES buffer address for Human68k; 0 is unused
DWORD m_nHumanWildcard; ///< Human68k wildcard data
DWORD m_nHumanAttribute; ///< Human68k search attribute
CHostPath::find_t m_findNext; ///< Next search location data
Human68k::dirent_t m_dirHuman; ///< Search results: Human68k file data
BYTE m_szHumanFilename[24]; ///< Human68k file name
BYTE m_szHumanResult[24]; ///< Search results: Human68k file name
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
///< Human68kのパス名
TCHAR m_szHostResult[FILEPATH_MAX]; ///< 検索結果 ホスト側のフルパス名
///< Human68k path name
TCHAR m_szHostResult[FILEPATH_MAX]; ///< Search results: host's full path name
};
//===========================================================================
//
/// ファイル検索領域 マネージャ
/// File search memory manager
//
//===========================================================================
class CHostFilesManager {
public:
#ifdef _DEBUG
// 基本ファンクション
~CHostFilesManager(); ///< デストラクタ final
~CHostFilesManager();
#endif // _DEBUG
void Init(); ///< 初期化 (ドライバ組込み時)
void Clean(); ///< 解放 (起動・リセット時)
void Init(); ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
CHostFiles* Alloc(DWORD nKey); ///< 確保
CHostFiles* Search(DWORD nKey); ///< 検索
void Free(CHostFiles* pFiles); ///< 解放
CHostFiles* Alloc(DWORD nKey);
CHostFiles* Search(DWORD nKey);
void Free(CHostFiles* pFiles);
private:
/// メモリ管理用
/// For memory management
struct ring_t {
CRing r; ///< 円環
CHostFiles f; ///< 実体
CRing r;
CHostFiles f;
};
CRing m_cRing; ///< CHostFiles連結用
CRing m_cRing; ///< For attaching to CHostFiles
};
//===========================================================================
//
/// FCB処理
/// FCB processing
//
//===========================================================================
class CHostFcb {
public:
// 基本ファンクション
CHostFcb() { SetKey(0); Init(); } ///< デフォルトコンストラクタ
~CHostFcb() { Close(); } ///< デストラクタ final
void Init(); ///< 初期化
CHostFcb() { SetKey(0); Init(); }
~CHostFcb() { Close(); }
void Init();
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< 検索キー設定
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< 検索キー比較
void SetUpdate() { m_bUpdate = TRUE; } ///< 更新
BOOL isUpdate() const { return m_bUpdate; } ///< 更新状態取得
BOOL SetMode(DWORD nHumanMode); ///< ファイルオープンモードを設定
void SetFilename(const TCHAR* szFilename); ///< ファイル名を設定
void SetHumanPath(const BYTE* szHumanPath); ///< Human68kパス名を設定
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Human68kパス名を取得
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetUpdate() { m_bUpdate = TRUE; } ///< Update
BOOL isUpdate() const { return m_bUpdate; } ///< Get update state
BOOL SetMode(DWORD nHumanMode); ///< Set file open mode
void SetFilename(const TCHAR* szFilename); ///< Set file name
void SetHumanPath(const BYTE* szHumanPath); ///< Set Human68k path name
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< ファイル作成
BOOL Open(); ///< ファイルオープン
BOOL Rewind(DWORD nOffset); ///< ファイルシーク
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< ファイル読み込み
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< ファイル書き込み
BOOL Truncate(); ///< ファイル切り詰め
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< ファイルシーク
BOOL TimeStamp(DWORD nHumanTime); ///< ファイル時刻設定
BOOL Close(); ///< ファイルクローズ
BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< Create file
BOOL Open(); ///< Open file
BOOL Rewind(DWORD nOffset); ///< Seek file
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
BOOL Truncate(); ///< Truncate file
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
BOOL TimeStamp(DWORD nHumanTime); ///< Set file time stamp
BOOL Close(); ///< Close file
private:
DWORD m_nKey; ///< Human68kのFCBバッファアドレス (0なら未使用)
BOOL m_bUpdate; ///< 更新フラグ
FILE* m_pFile; ///< ホスト側のファイルオブジェクト
const char* m_pszMode; ///< ホスト側のファイルオープンモード
bool m_bFlag; ///< ホスト側のファイルオープンフラグ
DWORD m_nKey; ///< Human68k FCB buffer address (0 if unused)
BOOL m_bUpdate; ///< Update flag
FILE* m_pFile; ///< Host side file object
const char* m_pszMode; ///< Host side file open mode
bool m_bFlag; ///< Host side file open flag
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
///< Human68kのパス名
TCHAR m_szFilename[FILEPATH_MAX]; ///< ホスト側のファイル名
///< Human68k path name
TCHAR m_szFilename[FILEPATH_MAX]; ///< Host side file name
};
//===========================================================================
//
/// FCB処理 マネージャ
/// FCB processing manager
//
//===========================================================================
class CHostFcbManager {
public:
#ifdef _DEBUG
// 基本ファンクション
~CHostFcbManager(); ///< デストラクタ final
~CHostFcbManager();
#endif // _DEBUG
void Init(); ///< 初期化 (ドライバ組込み時)
void Clean(); ///< 解放 (起動・リセット時)
void Init(); ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
CHostFcb* Alloc(DWORD nKey); ///< 確保
CHostFcb* Search(DWORD nKey); ///< 検索
void Free(CHostFcb* p); ///< 解放
CHostFcb* Alloc(DWORD nKey);
CHostFcb* Search(DWORD nKey);
void Free(CHostFcb* p);
private:
/// メモリ管理用
/// For memory management
struct ring_t {
CRing r; ///< 円環
CHostFcb f; ///< 実体
CRing r;
CHostFcb f;
};
CRing m_cRing; ///< CHostFcb連結用
CRing m_cRing; ///< For attaching to CHostFcb
};
//===========================================================================
//
/// ホスト側ドライブ
/// Host side drive
///
/// ドライブ毎に必要な情報の保持に専念し、管理はCHostEntryで行なう。
/// Keeps the required data for each drive, managed in CHostEntry.
//
//===========================================================================
class CHostDrv
{
public:
// 基本ファンクション
CHostDrv(); ///< デフォルトコンストラクタ
~CHostDrv(); ///< デストラクタ final
void Init(const TCHAR* szBase, DWORD nFlag); ///< 初期化 (デバイス起動とロード)
CHostDrv();
~CHostDrv();
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
BOOL isWriteProtect() const { return m_bWriteProtect; } ///< 書き込み禁止か?
BOOL isEnable() const { return m_bEnable; } ///< アクセス可能か?
BOOL isMediaOffline(); ///< メディアチェック
BYTE GetMediaByte() const; ///< メディアバイトの取得
DWORD GetStatus() const; ///< ドライブ状態の取得
void SetEnable(BOOL bEnable); ///< メディア状態設定
BOOL CheckMedia(); ///< メディア交換チェック
void Update(); ///< メディア状態更新
void Eject(); ///< イジェクト
void GetVolume(TCHAR* szLabel); ///< ボリュームラベルの取得
BOOL GetVolumeCache(TCHAR* szLabel) const; ///< キャッシュからボリュームラベルを取得
DWORD GetCapacity(Human68k::capacity_t* pCapacity); ///< 容量の取得
BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< キャッシュから容量を取得
BOOL isWriteProtect() const { return m_bWriteProtect; }
BOOL isEnable() const { return m_bEnable; } ///< Is it accessible?
BOOL isMediaOffline();
BYTE GetMediaByte() const;
DWORD GetStatus() const;
void SetEnable(BOOL bEnable); ///< Set media status
BOOL CheckMedia(); ///< Check if media was changed
void Update(); ///< Update media status
void Eject();
void GetVolume(TCHAR* szLabel); ///< Get volume label
BOOL GetVolumeCache(TCHAR* szLabel) const; ///< Get volume label from cache
DWORD GetCapacity(Human68k::capacity_t* pCapacity);
BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< Get capacity from cache
// キャッシュ操作
void CleanCache(); ///< 全てのキャッシュを更新する
void CleanCache(const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを更新する
void CleanCacheChild(const BYTE* szHumanPath); ///< 指定されたパス以下のキャッシュを全て更新する
void DeleteCache(const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを削除する
CHostPath* FindCache(const BYTE* szHuman); ///< 指定されたパスがキャッシュされているか検索する
CHostPath* CopyCache(CHostFiles* pFiles); ///< キャッシュ情報を元に、ホスト側の名称を獲得する
CHostPath* MakeCache(CHostFiles* pFiles); ///< ホスト側の名称の構築に必要な情報をすべて取得する
BOOL Find(CHostFiles* pFiles); ///< ホスト側の名称を検索 (パス名+ファイル名(省略可)+属性)
// Cache operations
void CleanCache(); ///< Update all cache
void CleanCache(const BYTE* szHumanPath); ///< Update cache for the specified path
void CleanCacheChild(const BYTE* szHumanPath); ///< Update all cache below the specified path
void DeleteCache(const BYTE* szHumanPath); ///< Delete the cache for the specified path
CHostPath* FindCache(const BYTE* szHuman); ///< Inspect if the specified path is cached
CHostPath* CopyCache(CHostFiles* pFiles); ///< Acquire the host side name on the basis of cache information
CHostPath* MakeCache(CHostFiles* pFiles); ///< Get all required data to construct a host side name
BOOL Find(CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
private:
// パス名操作
// Path name operations
static const BYTE* SeparateCopyFilename(const BYTE* szHuman, BYTE* szBuffer);
///< Human68kフルパス名から先頭の要素を分離・コピー
///< Split and copy the first element of the Human68k full path name
// 排他制御
void Lock() {}
void Unlock() {}
/// メモリ管理用
/// For memory management
struct ring_t {
CRing r; ///< 円環
CHostPath f; ///< 実体
CRing r;
CHostPath f;
};
BOOL m_bWriteProtect; ///< 書き込み禁止ならTRUE
BOOL m_bEnable; ///< メディアが利用可能ならTRUE
DWORD m_nRing; ///< パス名保持数
CRing m_cRing; ///< CHostPath連結用
Human68k::capacity_t m_capCache; ///< セクタ情報キャッシュ sectors == 0 なら未キャッシュ
BOOL m_bVolumeCache; ///< ボリュームラベル読み込み済みならTRUE
TCHAR m_szVolumeCache[24]; ///< ボリュームラベルキャッシュ
TCHAR m_szBase[FILEPATH_MAX]; ///< ベースパス
BOOL m_bWriteProtect; ///< TRUE if write-protected
BOOL m_bEnable; ///< TRUE if media is usable
DWORD m_nRing; ///< Number of stored path names
CRing m_cRing; ///< For attaching to CHostPath
Human68k::capacity_t m_capCache; ///< Sector data cache: if "sectors == 0" then not cached
BOOL m_bVolumeCache; ///< TRUE if the volume label has been read
TCHAR m_szVolumeCache[24]; ///< Volume label cache
TCHAR m_szBase[FILEPATH_MAX]; ///< Base path
};
//===========================================================================
//
/// ディレクトリエントリ管理
/// Directory entry management
//
//===========================================================================
class CHostEntry {
public:
// 基本ファンクション
CHostEntry(); ///< デフォルトコンストラクタ
~CHostEntry(); ///< デストラクタ final
void Init(); ///< 初期化 (ドライバ組込み時)
void Clean(); ///< 解放 (起動・リセット時)
CHostEntry();
~CHostEntry();
void Init(); ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
// キャッシュ操作
void CleanCache(); ///< 全てのキャッシュを更新する
void CleanCache(DWORD nUnit); ///< 指定されたユニットのキャッシュを更新する
void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを更新する
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパス以下のキャッシュを全て更新する
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを削除する
BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< ホスト側の名称を検索 (パス名+ファイル名(省略可)+属性)
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< ホスト側ファイルシステム状態変化通知
// Cache operations
void CleanCache(); ///< Update all cache
void CleanCache(DWORD nUnit); ///< Update cache for the specified unit
void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache for the specified path
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache below the specified path
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< Delete cache for the specified path
BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system
// ドライブオブジェクト操作
void SetDrv(DWORD nUnit, CHostDrv* pDrv); ///< ドライブ設定
BOOL isWriteProtect(DWORD nUnit) const; ///< 書き込み禁止か?
BOOL isEnable(DWORD nUnit) const; ///< アクセス可能か?
BOOL isMediaOffline(DWORD nUnit); ///< メディアチェック
BYTE GetMediaByte(DWORD nUnit) const; ///< メディアバイトの取得
DWORD GetStatus(DWORD nUnit) const; ///< ドライブ状態の取得
BOOL CheckMedia(DWORD nUnit); ///< メディア交換チェック
void Eject(DWORD nUnit); ///< イジェクト
void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< ボリュームラベルの取得
BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< キャッシュからボリュームラベルを取得
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< 容量の取得
// Drive object operations
void SetDrv(DWORD nUnit, CHostDrv* pDrv);
BOOL isWriteProtect(DWORD nUnit) const;
BOOL isEnable(DWORD nUnit) const; ///< Is it accessible?
BOOL isMediaOffline(DWORD nUnit);
BYTE GetMediaByte(DWORD nUnit) const;
DWORD GetStatus(DWORD nUnit) const; ///< Get drive status
BOOL CheckMedia(DWORD nUnit); ///< Media change check
void Eject(DWORD nUnit);
void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< Get volume label
BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label from cache
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity);
BOOL GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const;
///< キャッシュからクラスタサイズを取得
///< Get cluster size from cache
/// 定数
enum {
DriveMax = 10 ///< ドライブ最大候補数
DriveMax = 10 ///< Max number of drive candidates
};
private:
CHostDrv* m_pDrv[DriveMax]; ///< ホスト側ドライブオブジェクト
DWORD m_nTimeout; ///< 最後にタイムアウトチェックを行なった時刻
CHostDrv* m_pDrv[DriveMax]; ///< Host side drive object
DWORD m_nTimeout; ///< Last time a timeout check was carried out
};
//===========================================================================
//
/// ホスト側ファイルシステム
/// Host side file system
//
//===========================================================================
/** @note
Current state of affairs:
XM6の設計思想とは反するがclass Windrvまたはclass CWindrvに直接
class CFileSysへのポインタを持たせる方法を模索するべきである
While it violates the design philosophy of XM6, we should find a way for
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
This way, we get the following benefits.
1
Benefit no. 1
Makes it possible to manage a large number of command handler methods in one place.
There is a high chance the command handlers will change drastically because of
host system architectural changes, so we will save a huge amount of maintenance work
in the long run.
2
XM6では複数のファイルシステムオブジェクトを同時に使うような実装は
Benefit no. 2
We would get rid of virtual funcion code for processing table creation and lookup.
It is not feasible to implement code in XM6 for simultaneous use of file system objects.
Therefore file system object polymorphism is a waste of CPU cycles.
windrv.h内のFILESYS_FAST_STRUCTUREの値を変えてコンパイラの吐くソース
I made the change as an experiment. Performance did improve.
The improvement was obvious from looking at the source the compiler spit out
after changing the FILESYS_FAST_STRUCTURE value in windrv.h.
You may understand now why I decided to rant here.
class CWindrv内にclass CFileSysを直接設置した
(class CHostを廃止して直接置きたい)
The easy solution is to put the content of 'class CFileSys' into 'class CWindrv'.
(To be honest, I really want to deprecate 'class CHost'... I wonder if there's a good way...)
*/
class CFileSys
{
public:
// 基本ファンクション
CFileSys(); ///< デフォルトコンストラクタ
virtual ~CFileSys() {}; ///< デストラクタ
CFileSys();
virtual ~CFileSys() {};
// 初期化・終了
void Reset(); ///< リセット (全クローズ)
void Init(); ///< 初期化 (デバイス起動とロード)
void Reset(); ///< Reset (close all)
void Init(); ///< Initialization (device startup and load)
// コマンドハンドラ
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - デバイス起動
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - ディレクトリチェック
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - ディレクトリ作成
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - ディレクトリ削除
// Command handlers
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - Directory check
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - Create directory
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - Delete directory
int Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew);
///< $44 - ファイル名変更
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - ファイル削除
///< $44 - Change file name
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - Delete file
int Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute);
///< $46 - ファイル属性取得/設定
///< $46 - Get / set file attribute
int Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles);
///< $47 - ファイル検索
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - ファイル次検索
///< $47 - Find file
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - Find next file
int Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce);
///< $49 - ファイル作成
///< $49 - Create file
int Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb);
///< $4A - ファイルオープン
int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - ファイルクローズ
///< $4A - Open file
int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - Close file
int Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, DWORD nSize);
///< $4C - ファイル読み込み
///< $4C - Read file
int Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, DWORD nSize);
///< $4D - ファイル書き込み
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - ファイルシーク
///< $4D - Write file
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - Seek file
DWORD TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime);
///< $4F - ファイル時刻取得/設定
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - 容量取得
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - ドライブ状態検査/制御
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - DPB取得
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - セクタ読み込み
int DiskWrite(DWORD nUnit); ///< $54 - セクタ書き込み
///< $4F - Get / set file timestamp
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - Get capacity
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - Inspect / control drive status
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - Get DPB
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - Read sectors
int DiskWrite(DWORD nUnit); ///< $54 - Write sectors
int Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL
int Flush(DWORD nUnit); ///< $56 - フラッシュ
int CheckMedia(DWORD nUnit); ///< $57 - メディア交換チェック
int Lock(DWORD nUnit); ///< $58 - 排他制御
int Flush(DWORD nUnit); ///< $56 - Flush
int CheckMedia(DWORD nUnit); ///< $57 - Media change check
int Lock(DWORD nUnit); ///< $58 - Lock
void SetOption(DWORD nOption); ///< オプション設定
DWORD GetOption() const { return m_nOption; } ///< オプション取得
DWORD GetDefault() const { return m_nOptionDefault; } ///< デフォルトオプション取得
static DWORD GetFileOption() { return g_nOption; } ///< ファイル名変換オプション取得
void SetOption(DWORD nOption); ///< Set option
DWORD GetOption() const { return m_nOption; } ///< Get option
DWORD GetDefault() const { return m_nOptionDefault; } ///< Get default options
static DWORD GetFileOption() { return g_nOption; } ///< Get file name change option
void ShellNotify(DWORD nEvent, const TCHAR* szPath)
{ m_cEntry.ShellNotify(nEvent, szPath); } ///< ホスト側ファイルシステム状態変化通知
{ m_cEntry.ShellNotify(nEvent, szPath); } ///< Notify host side file system status change
/// 定数
enum {
DriveMax = CHostEntry::DriveMax ///< ドライブ最大候補数
DriveMax = CHostEntry::DriveMax ///< Max number of drive candidates
};
private:
// 内部補助用
void InitOption(const Human68k::argument_t* pArgument); ///< オプション初期化
BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< ボリュームラベル取得
void InitOption(const Human68k::argument_t* pArgument);
BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< Get volume label
DWORD m_nUnits; ///< 現在のドライブオブジェクト数 (レジューム毎に変化)
DWORD m_nUnits; ///< Number of current drive objects (Changes for every resume)
DWORD m_nOption; ///< 現在の動作フラグ
DWORD m_nOptionDefault; ///< リセット時の動作フラグ
DWORD m_nOption; ///< Current runtime flag
DWORD m_nOptionDefault; ///< Runtime flag at reset
DWORD m_nDrives; ///< ベースパス状態復元用の候補数 (0なら毎回スキャン)
DWORD m_nDrives; ///< Number of candidates for base path status restoration (scan every time if 0)
DWORD m_nKernel; ///< カーネルチェック用カウンタ
DWORD m_nKernelSearch; ///< NULデバイスの先頭アドレス
DWORD m_nKernel; ///< Counter for kernel check
DWORD m_nKernelSearch; ///< Initial address for NUL device
DWORD m_nHostSectorCount; ///< 擬似セクタ番号
DWORD m_nHostSectorCount; ///< Virtual sector identifier
CHostFilesManager m_cFiles; ///< ファイル検索領域
CHostFcbManager m_cFcb; ///< FCB操作領域
CHostEntry m_cEntry; ///< ドライブオブジェクトとディレクトリエントリ
CHostFilesManager m_cFiles; ///< File search memory
CHostFcbManager m_cFcb; ///< FCB operation memory
CHostEntry m_cEntry; ///< Drive object and directory entry
DWORD m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX];
///< 擬似セクタの指すファイル実体
///< Entity that the virtual sector points to
DWORD m_nFlag[DriveMax]; ///< ベースパス状態復元用の動作フラグ候補
TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< ベースパス状態復元用の候補
static DWORD g_nOption; ///< ファイル名変換フラグ
DWORD m_nFlag[DriveMax]; ///< Candidate runtime flag for base path restoration
TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< Candidate for base path restoration
static DWORD g_nOption; ///< File name change flag
};

View File

@ -99,7 +99,7 @@ BOOL Fileio::Open(const char *fname, OpenMode mode, BOOL directIO)
break;
case ReadWrite:
// CD-ROMからの読み込みはRWが成功してしまう
// Make sure RW does not succeed when reading from CD-ROM
if (access(fname, 0x06) != 0) {
return FALSE;
}

View File

@ -1,10 +1,7 @@
import fnmatch
import os
import subprocess
import time
import io
import re
import sys
import logging
from ractl_cmds import (
attach_image,
@ -14,6 +11,42 @@ from ractl_cmds import (
from settings import *
def list_files():
from fnmatch import translate
valid_file_types = list(VALID_FILE_SUFFIX)
valid_file_types = ["*." + s for s in valid_file_types]
valid_file_types = r"|".join([translate(x) for x in valid_file_types])
from re import match, IGNORECASE
files_list = []
for path, dirs, files in os.walk(base_dir):
# Only list valid file types
files = [f for f in files if match(valid_file_types, f, IGNORECASE)]
files_list.extend(
[
(
os.path.join(path, file),
"{:,.1f}".format(
os.path.getsize(os.path.join(path, file)) / float(1 << 20),
),
os.path.getsize(os.path.join(path, file)),
)
for file in files
]
)
return files_list
def list_config_files():
files_list = []
for root, dirs, files in os.walk(base_dir):
for file in files:
if file.endswith(".json"):
files_list.append(file)
return files_list
def create_new_image(file_name, type, size):
if file_name == "":
file_name = "new_image." + str(int(time.time())) + "." + type
@ -42,14 +75,6 @@ def unzip_file(file_name):
return True
def rascsi_service(action):
# start/stop/restart
return (
subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode
== 0
)
def download_file_to_iso(scsi_id, url):
import urllib.request
@ -60,14 +85,19 @@ def download_file_to_iso(scsi_id, url):
tmp_full_path = tmp_dir + file_name
iso_filename = base_dir + file_name + ".iso"
urllib.request.urlretrieve(url, tmp_full_path)
try:
urllib.request.urlretrieve(url, tmp_full_path)
except:
# TODO: Capture a more descriptive error message
return {"status": False, "msg": "Error loading the URL"}
# iso_filename = make_cd(tmp_full_path, None, None) # not working yet
iso_proc = subprocess.run(
["genisoimage", "-hfs", "-o", iso_filename, tmp_full_path], capture_output=True
)
if iso_proc.returncode != 0:
return iso_proc
return attach_image(scsi_id, iso_filename, "SCCD")
return {"status": False, "msg": iso_proc}
return attach_image(scsi_id, type="SCCD", image=iso_filename)
def download_image(url):
@ -76,50 +106,69 @@ def download_image(url):
file_name = url.split("/")[-1]
full_path = base_dir + file_name
urllib.request.urlretrieve(url, full_path)
def write_config_csv(file_name):
import csv
# This method takes the output of 'rasctl -l' and parses it into csv format:
# 0: ID
# 1: Unit Number (unused in rascsi-web)
# 2: Device Type
# 3: Device Status (includes the path to a loaded image file)
# TODO: Remove the dependence on rasctl; e.g. when implementing protobuf for rascsi-web
try:
with open(file_name, "w") as csv_file:
writer = csv.writer(csv_file)
for device in list_devices():
if device["type"] != "-":
device_info = list (device.values())
# Match a *nix file path inside column 3, cutting out the last chunk that starts with a space
filesearch = re.search("(^(/[^/ ]*)+)(\s.*)*$", device_info[3])
if filesearch is None:
device_info[3] = ""
else:
device_info[3] = filesearch.group(1)
writer.writerow(device_info)
return True
urllib.request.urlretrieve(url, full_path)
return {"status": True, "msg": "Downloaded the URL"}
except:
print ("Could not open file for writing: ", file_name)
return False
def read_config_csv(file_name):
import csv
# TODO: Capture a more descriptive error message
return {"status": False, "msg": "Error loading the URL"}
def write_config(file_name):
from json import dump
try:
with open(file_name) as csv_file:
with open(file_name, "w") as json_file:
devices = list_devices()[0]
for device in devices:
# Remove keys that we don't want to store in the file
del device["status"]
del device["file"]
# It's cleaner not to store an empty parameter for every device without media
if device["image"] == "":
device["image"] = None
# RaSCSI product names will be generated on the fly by RaSCSI
if device["vendor"] == "RaSCSI":
device["vendor"] = device["product"] = device["revision"] = None
# A block size of 0 is how RaSCSI indicates N/A for block size
if device["block_size"] == 0:
device["block_size"] = None
# Convert to a data type that can be serialized
device["params"] = list(device["params"])
dump(devices, json_file, indent=4)
return {"status": True, "msg": f"Successfully wrote to file: {file_name}"}
#TODO: more verbose error handling of file system errors
except:
logging.error(f"Could not write to file: {file_name}")
return {"status": False, "msg": f"Could not write to file: {file_name}"}
def read_config(file_name):
from json import load
try:
with open(file_name) as json_file:
detach_all()
config_reader = csv.reader(csv_file)
#TODO: Remove hard-coded string sanitation (e.g. after implementing protobuf)
exclude_list = ("X68000 HOST BRIDGE", "DaynaPort SCSI/Link", " (WRITEPROTECT)", "NO MEDIA")
for row in config_reader:
image_name = row[3]
for e in exclude_list:
image_name = image_name.replace(e, "")
attach_image(row[0], image_name, row[2])
return True
devices = load(json_file)
for row in devices:
process = attach_image(row["id"], device_type=row["device_type"], image=row["image"], unit=int(row["un"]), \
params=row["params"], vendor=row["vendor"], product=row["product"], \
revision=row["revision"], block_size=row["block_size"])
if process["status"] == True:
return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"}
else:
return {"status": process["status"], "msg": process["msg"]}
#TODO: more verbose error handling of file system errors
except:
print ("Could not access file: ", file_name)
return False
logging.error(f"Could not read file: {file_name}")
return {"status": False, "msg": f"Could not read file: {file_name}"}
def read_device_config(file_name):
from json import load
try:
with open(file_name) as json_file:
conf = load(json_file)
return {"status": True, "msg": f"Read data from file: {file_name}", "conf": conf}
#TODO: more verbose error handling of file system errors
except:
logging.error(f"Could not read file: {file_name}")
return {"status": False, "msg": f"Could not read file: {file_name}"}

View File

@ -29,3 +29,12 @@ def running_version():
.strip()
)
return ra_web_version + " " + pi_version
def is_bridge_setup():
from subprocess import run
process = run(["brctl", "show"], capture_output=True)
output = process.stdout.decode("utf-8")
if "rascsi_bridge" in output:
return True
return False

View File

@ -1,164 +1,328 @@
import fnmatch
import subprocess
import re
import logging
from settings import *
import rascsi_interface_pb2 as proto
valid_file_suffix = ["*.hda", "*.hdn", "*.hdi", "*.nhd", "*.hdf", "*.hds", "*.hdr", "*.iso", "*.cdr", "*.toast", "*.img", "*.zip"]
valid_file_types = r"|".join([fnmatch.translate(x) for x in valid_file_suffix])
def get_server_info():
command = proto.PbCommand()
command.operation = proto.PbOperation.SERVER_INFO
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
version = str(result.server_info.major_version) + "." +\
str(result.server_info.minor_version) + "." +\
str(result.server_info.patch_version)
log_levels = result.server_info.log_levels
current_log_level = result.server_info.current_log_level
return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level}
def validate_scsi_id(scsi_id):
from re import match
if match("[0-7]", str(scsi_id)) != None:
return {"status": True, "msg": "Valid SCSI ID."}
else:
return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"}
def is_active():
process = subprocess.run(["systemctl", "is-active", "rascsi"], capture_output=True)
return process.stdout.decode("utf-8").strip() == "active"
def list_files():
files_list = []
for path, dirs, files in os.walk(base_dir):
# Only list valid file types
files = [f for f in files if re.match(valid_file_types, f)]
files_list.extend(
[
(
os.path.join(path, file),
# TODO: move formatting to template
"{:,.0f}".format(
os.path.getsize(os.path.join(path, file)) / float(1 << 20)
)
+ " MB",
)
for file in files
]
)
return files_list
def list_config_files():
files_list = []
for root, dirs, files in os.walk(base_dir):
for file in files:
if file.endswith(".csv"):
files_list.append(file)
return files_list
def get_valid_scsi_ids(devices, invalid_list):
def get_valid_scsi_ids(devices, invalid_list, occupied_ids):
for device in devices:
if device["file"] != "NO MEDIA" and device["file"] != "-":
invalid_list.append(int(device["id"]))
# Make it possible to insert images on top of a
# removable media device currently without an image attached
if "No Media" in device["status"]:
occupied_ids.remove(device["id"])
valid_list = list(range(8))
for id in invalid_list:
try:
valid_list.remove(int(id))
except:
logging.warning("Invalid SCSI id " + str(id))
valid_list.reverse()
return valid_list
# Combine lists and remove duplicates
invalid_ids = list(set(invalid_list + occupied_ids))
valid_ids = list(range(8))
for id in invalid_ids:
valid_ids.remove(int(id))
valid_ids.reverse()
return valid_ids
def get_type(scsi_id):
return list_devices()[int(scsi_id)]["type"]
device = proto.PbDeviceDefinition()
device.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.DEVICE_INFO
command.devices.append(device)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
# Assuming that only one PbDevice object is present in the response
try:
result_type = proto.PbDeviceType.Name(result.device_info.devices[0].type)
return {"status": result.status, "msg": result.msg, "device_type": result_type}
except:
return {"status": result.status, "msg": result.msg, "device_type": None}
def attach_image(scsi_id, image, image_type):
if image_type == "SCCD" and get_type(scsi_id) == "SCCD":
return insert(scsi_id, image)
def attach_image(scsi_id, **kwargs):
# Handling the inserting of media into an attached removable type device
currently_attached = get_type(scsi_id)["device_type"]
device_type = kwargs.get("device_type", None)
if device_type in REMOVABLE_DEVICE_TYPES and currently_attached in REMOVABLE_DEVICE_TYPES:
if currently_attached != device_type:
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
else:
return insert(scsi_id, kwargs.get("image", ""))
# Handling attaching a new device
else:
return subprocess.run(
["rasctl", "-c", "attach", "-t", image_type, "-i", scsi_id, "-f", image],
capture_output=True,
)
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
if "device_type" in kwargs.keys():
logging.warning(kwargs["device_type"])
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
if "unit" in kwargs.keys():
devices.unit = kwargs["unit"]
if "image" in kwargs.keys():
if kwargs["image"] not in [None, ""]:
devices.params.append(kwargs["image"])
if "params" in kwargs.keys():
for p in kwargs["params"]:
devices.params.append(p)
if "vendor" in kwargs.keys():
if kwargs["vendor"] not in [None, ""]:
devices.vendor = kwargs["vendor"]
if "product" in kwargs.keys():
if kwargs["product"] not in [None, ""]:
devices.product = kwargs["product"]
if "revision" in kwargs.keys():
if kwargs["revision"] not in [None, ""]:
devices.revision = kwargs["revision"]
if "block_size" in kwargs.keys():
if kwargs["block_size"] not in [None, ""]:
devices.block_size = int(kwargs["block_size"])
command = proto.PbCommand()
command.operation = proto.PbOperation.ATTACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def detach_by_id(scsi_id):
return subprocess.run(["rasctl", "-c" "detach", "-i", scsi_id], capture_output=True)
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.DETACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def detach_all():
for scsi_id in range(0, 8):
subprocess.run(["rasctl", "-c" "detach", "-i", str(scsi_id)])
command = proto.PbCommand()
command.operation = proto.PbOperation.DETACH_ALL
def disconnect_by_id(scsi_id):
return subprocess.run(
["rasctl", "-c", "disconnect", "-i", scsi_id], capture_output=True
)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def eject_by_id(scsi_id):
return subprocess.run(["rasctl", "-i", scsi_id, "-c", "eject"])
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.EJECT
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def insert(scsi_id, image):
return subprocess.run(
["rasctl", "-i", scsi_id, "-c", "insert", "-f", image], capture_output=True
)
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
devices.params.append(image)
command = proto.PbCommand()
command.operation = proto.PbOperation.INSERT
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def attach_daynaport(scsi_id):
return subprocess.run(
["rasctl", "-i", scsi_id, "-c", "attach", "-t", "scdp"],
capture_output=True,
)
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
devices.type = proto.PbDeviceType.SCDP
command = proto.PbCommand()
command.operation = proto.PbOperation.ATTACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def is_bridge_setup(interface):
process = subprocess.run(["brctl", "show"], capture_output=True)
output = process.stdout.decode("utf-8")
if "rascsi_bridge" in output and interface in output:
return True
return False
def list_devices(scsi_id=None):
from os import path
command = proto.PbCommand()
command.operation = proto.PbOperation.DEVICE_INFO
# If method is called with scsi_id parameter, return the info on those devices
# Otherwise, return the info on all attached devices
if scsi_id != None:
device = proto.PbDeviceDefinition()
device.id = int(scsi_id)
command.devices.append(device)
def daynaport_setup_bridge(interface):
return subprocess.run(
[f"{base_dir}../RASCSI/src/raspberrypi/setup_bridge.sh", interface],
capture_output=True,
)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
def rascsi_service(action):
# start/stop/restart
return (
subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode
== 0
)
def list_devices():
device_list = []
occupied_ids = []
n = 0
while n < len(result.device_info.devices):
did = result.device_info.devices[n].id
dun = result.device_info.devices[n].unit
dtype = proto.PbDeviceType.Name(result.device_info.devices[n].type)
dstat = result.device_info.devices[n].status
dprop = result.device_info.devices[n].properties
# Building the status string
# TODO: This formatting should probably be moved elsewhere
dstat_msg = []
if dprop.read_only == True:
dstat_msg.append("Read-Only")
if dstat.protected == True and dprop.protectable == True:
dstat_msg.append("Write-Protected")
if dstat.removed == True and dprop.removable == True:
dstat_msg.append("No Media")
if dstat.locked == True and dprop.lockable == True:
dstat_msg.append("Locked")
dpath = result.device_info.devices[n].file.name
dfile = path.basename(dpath)
dparam = result.device_info.devices[n].params
dven = result.device_info.devices[n].vendor
dprod = result.device_info.devices[n].product
drev = result.device_info.devices[n].revision
dblock = result.device_info.devices[n].block_size
device_list.append({"id": did, "un": dun, "device_type": dtype, \
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
occupied_ids.append(did)
n += 1
return device_list, occupied_ids
def sort_and_format_devices(device_list, occupied_ids):
# Add padding devices and sort the list
for id in range(8):
device_list.append({"id": str(id), "un": "-", "type": "-", "file": "-"})
output = subprocess.run(["rasctl", "-l"], capture_output=True).stdout.decode(
"utf-8"
)
for line in output.splitlines():
# Valid line to process, continue
if (
not line.startswith("+")
and not line.startswith("| ID |")
and (
not line.startswith("No device is installed.")
or line.startswith("No images currently attached.")
)
and len(line) > 0
):
line.rstrip()
device = {}
segments = line.split("|")
if len(segments) > 4:
idx = int(segments[1].strip())
device_list[idx]["id"] = str(idx)
device_list[idx]["un"] = segments[2].strip()
device_list[idx]["type"] = segments[3].strip()
device_list[idx]["file"] = segments[4].strip()
if id not in occupied_ids:
device_list.append({"id": id, "type": "-", \
"status": "-", "file": "-", "product": "-"})
# Sort list of devices by id
device_list.sort(key=lambda dic: str(dic["id"]))
return device_list
def reserve_scsi_ids(reserved_scsi_ids):
scsi_ids = ",".join(list(reserved_scsi_ids))
return subprocess.run(["rasctl", "-r", scsi_ids])
command = proto.PbCommand()
command.operation = proto.PbOperation.RESERVE
command.params.append(reserved_scsi_ids)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def set_log_level(log_level):
'''Sends a command to the server to change the log level. Takes target log level as an argument'''
command = proto.PbCommand()
command.operation = proto.PbOperation.LOG_LEVEL
command.params.append(str(log_level))
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def send_pb_command(payload):
# Host and port number where rascsi is listening for socket connections
HOST = 'localhost'
PORT = 6868
counter = 0
tries = 100
error_msg = ""
import socket
while counter < tries:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
return send_over_socket(s, payload)
except socket.error as error:
counter += 1
logging.warning("The RaSCSI service is not responding - attempt " + \
str(counter) + "/" + str(tries))
error_msg = str(error)
logging.error(error_msg)
# After failing all attempts, throw a 404 error
from flask import abort
abort(404, "Failed to connect to RaSCSI at " + str(HOST) + ":" + str(PORT) + \
" with error: " + error_msg + ". Is the RaSCSI service running?")
def send_over_socket(s, payload):
from struct import pack, unpack
# Prepending a little endian 32bit header with the message size
s.send(pack("<i", len(payload)))
s.send(payload)
# Receive the first 4 bytes to get the response header
response = s.recv(4)
if len(response) >= 4:
# Extracting the response header to get the length of the response message
response_length = unpack("<i", response)[0]
# Reading in chunks, to handle a case where the response message is very large
chunks = []
bytes_recvd = 0
while bytes_recvd < response_length:
chunk = s.recv(min(response_length - bytes_recvd, 2048))
if chunk == b'':
from flask import abort
logging.error("Read an empty chunk from the socket. Socket connection may have dropped unexpectedly.")
abort(503, "Lost connection to RaSCSI. Please go back and try again. If the issue persists, please report a bug.")
chunks.append(chunk)
bytes_recvd = bytes_recvd + len(chunk)
response_message = b''.join(chunks)
return response_message
else:
from flask import abort
logging.error("The response from RaSCSI did not contain a protobuf header.")
abort(500, "Did not get a valid response from RaSCSI. Please go back and try again. If the issue persists, please report a bug.")

View File

@ -10,3 +10,4 @@ rsrcfork==1.8.0
waitress==1.4.4
zope.event==4.5.0
zope.interface==5.1.2
protobuf>=3.17.3

View File

@ -1,4 +1,14 @@
import os
from os import getenv
base_dir = os.getenv("BASE_DIR", "/home/pi/images/")
MAX_FILE_SIZE = os.getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
base_dir = getenv("BASE_DIR", "/home/pi/images/")
DEFAULT_CONFIG = base_dir + "default.json"
MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
HARDDRIVE_FILE_SUFFIX = ("hda", "hdn", "hdi", "nhd", "hdf", "hds")
CDROM_FILE_SUFFIX = ("iso", "cdr", "toast", "img")
REMOVABLE_FILE_SUFFIX = ("hdr",)
ARCHIVE_FILE_SUFFIX = ("zip",)
VALID_FILE_SUFFIX = HARDDRIVE_FILE_SUFFIX + REMOVABLE_FILE_SUFFIX + \
CDROM_FILE_SUFFIX + ARCHIVE_FILE_SUFFIX
REMOVABLE_DEVICE_TYPES = ("SCCD", "SCRM", "SCMO")

View File

@ -41,4 +41,9 @@ table, tr, td {
color: white;
font-size:20px;
background-color:green;
}
}
td.inactive {
text-align:center;
background-color:tan;
}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block header %}
{% if active %}
{% if server_info["status"] == True %}
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Running</span>
{% else %}
<span style="display: inline-block; width: 100%; color: white; background-color: red; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Stopped</span>
@ -21,12 +21,11 @@
{% block content %}
<h2>Current RaSCSI Configuration</h2>
<p>The <em>default</em> configuration will be loaded when the Web UI starts up.</p>
<p>
<form action="/config/load" method="post">
<select name="name" >
{% for config in config_files %}
<option value="{{config}}">{{config.replace(".csv", '')}}</option>
<option value="{{config}}">{{config.replace(".json", '')}}</option>
{% endfor %}
</select>
<input type="submit" name="load" value="Load" />
@ -42,39 +41,60 @@
<input type="submit" value="Detach All" />
</form>
</p>
<p><small>The <em>default</em> configuration will be loaded when the Web UI starts up, if available.</small></p>
<table cellpadding="3" border="black">
<tbody>
<tr>
<td><b>ID</b></td>
<td><b>Type</b></td>
<td><b>Status</b></td>
<td><b>File</b></td>
<td><b>Action</b></td>
<td><b>Product</b></td>
<td><b>Actions</b></td>
</tr>
{% for device in devices %}
<tr>
{% if device.id not in reserved_scsi_ids %}
{% if device["id"]|string() not in reserved_scsi_ids %}
<td style="text-align:center">{{device.id}}</td>
<td style="text-align:center">{{device.type}}</td>
<td>{{device.file}}</td>
<td>
{% if device.type == "SCCD" and device.file != "NO MEDIA" %}
<td style="text-align:center">{{device.device_type}}</td>
<td style="text-align:center">{{device.status}}</td>
<td style="text-align:left">{{device.file}}</td>
{% if device.vendor == "RaSCSI" %}
<td style="text-align:center">{{device.product}}</td>
{% else %}
<td style="text-align:center">{{device.vendor}} {{device.product}}</td>
{% endif %}
<td style="text-align:left">
{% if device.device_type in removable_device_types and "No Media" not in device.status %}
<form action="/scsi/eject" method="post" onsubmit="return confirm('Eject Disk?')">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Eject" />
</form>
<form action="/scsi/info" method="post">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Info" />
</form>
{% elif device.device_type in ["-"] %}
<div>-</div>
{% else %}
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Detach" />
</form>
<form action="/scsi/info" method="post">
<input type="hidden" name="scsi_id" value="{{device.id}}">
<input type="submit" value="Info" />
</form>
{% endif %}
</td>
{% else %}
<td style="text-align:center">{{device.id}}</td>
<td style="text-align:center">-</td>
<td>Reserved ID</td>
<td>-</td>
<td class="inactive">{{device.id}}</td>
<td class="inactive"></td>
<td class="inactive">Reserved ID</td>
<td class="inactive"></td>
<td class="inactive"></td>
<td class="inactive"></td>
{% endif %}
</tr>
{% endfor %}
@ -96,24 +116,27 @@
<td style="text-align:center">
<form action="/files/download" method="post">
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
<input type="submit" value="{{file[1]}} &#8595;" />
<input type="submit" value="{{file[1]}} MB &#8595;" />
</form>
</td>
<td>
<form action="/scsi/attach" method="post">
<input type="hidden" name="file_name" value="{{file[0]}}">
<input type="hidden" name="file_size" value="{{file[2]}}">
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{id}}">{{id}}</option>
{% endfor %}
</select>
{% if not file[0].lower().endswith(archive_file_suffix) %}
<input type="submit" value="Attach" />
{% endif %}
</form>
<form action="/files/delete" method="post" onsubmit="return confirm('Delete file?')">
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
<input type="submit" value="Delete" />
</form>
{% if file[0].endswith('.zip') or file[0].endswith('.ZIP') %}
{% if file[0].lower().endswith(archive_file_suffix) %}
<form action="/files/unzip" method="post">
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
<input type="submit" value="Unzip" />
@ -124,10 +147,11 @@
{% endfor %}
</tbody>
</table>
<p><small>Supported file types: {{valid_file_suffix|string()}}</small></p>
<hr/>
<h2>Attach Ethernet Adapter</h2>
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.</p>
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link">Host drivers required.</a></p>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
@ -146,9 +170,7 @@
{% if bridge_configured %}
<small>Bridge is currently configured!</small>
{% else %}
<form action="/daynaport/setup" method="post">
<input type="submit" value="Create Bridge" />
</form>
<small>Bridge is automatically configured when a network adapter is attached.</small>
{% endif %}
</td>
</tr>
@ -223,13 +245,14 @@
<input type="text" placeholder="File name" name="file_name"/>
<label for="type">Type:</label>
<select name="type">
<option value="hda">SCSI Hard Disk image (APPLE GENUINE)</option>
<option value="hdn">SCSI Hard Disk image (NEC GENUINE)</option>
<option value="hdi">SCSI Hard Disk image (Anex86 HD image)</option>
<option value="nhd">SCSI Hard Disk image (T98Next HD image)</option>
<option value="hds">SCSI Hard Disk image (Generic - recommended for Atari computers)</option>
<option value="hdr">SCSI Removable Media Disk image (Generic)</option>
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000)</option>
<option value="hda">SCSI Hard Disk image (APPLE GENUINE) [.hda]</option>
<option value="hdn">SCSI Hard Disk image (NEC GENUINE) [.hdn]</option>
<!-- Disabling due to https://github.com/akuker/RASCSI/issues/232
<option value="hdi">SCSI Hard Disk image (Anex86 HD image) [.hdi]</option>
<option value="nhd">SCSI Hard Disk image (T98Next HD image) [.nhd]</option> -->
<option value="hds">SCSI Hard Disk image (Generic - recommended for Atari computers) [.hds]</option>
<option value="hdr">SCSI Removable Media Disk image (Generic) [.hdr]</option>
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000) [.hdf]</option>
</select>
<label for="size">Size(MB):</label>
<input type="number" placeholder="Size(MB)" name="size"/>
@ -240,6 +263,47 @@
</table>
<hr/>
<h2>Logging</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/logs/show" method="post">
<label for="lines">Log Lines:</label>
<input type="text" placeholder="200" name="lines"/>
<label for="scope">Scope:</label>
<select name="scope">
<option value="default">default</option>
<option value="rascsi">rascsi.service</option>
<option value="rascsi-web">rascsi-web.service</option>
</select>
<input type="submit" value="Show Logs" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Server Log Level</h2>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/logs/level" method="post">
<label for="level">Log Level:</label>
<select name="level">
{% for level in server_info["log_levels"] %}
<option value="{{level}}">{{level}}</option>
{% endfor %}
</select>
<input type="submit" value="Set Log Level" />
</form>
</td>
</tr>
</table>
<hr/>
<h2>Raspberry Pi Operations</h2>
<table style="border: none">
<tr style="border: none">
@ -261,9 +325,11 @@
</td>
</tr>
</table>
{% endblock %}
{% block footer %}
<center><tt>{{version}}</tt></center>
<center><a href="/logs">Logs</a></center>
<center><tt>RaSCSI version: <strong>{{server_info["version"]}}</strong></tt></center>
<center><tt>Server log level: <strong>{{server_info["current_log_level"]}}</strong></tt></center>
<center><tt>{{version}}</tt></center>
{% endblock %}

View File

@ -1,35 +1,37 @@
import io
import re
import sys
from flask import Flask, render_template, request, flash, url_for, redirect, send_file, send_from_directory
from file_cmds import (
list_files,
list_config_files,
create_new_image,
download_file_to_iso,
delete_file,
unzip_file,
download_image,
write_config_csv,
read_config_csv,
write_config,
read_config,
read_device_config,
)
from pi_cmds import (
shutdown_pi,
reboot_pi,
running_version,
rascsi_service,
is_bridge_setup,
)
from pi_cmds import shutdown_pi, reboot_pi, running_version, rascsi_service
from ractl_cmds import (
attach_image,
list_devices,
is_active,
list_files,
sort_and_format_devices,
detach_by_id,
eject_by_id,
get_valid_scsi_ids,
attach_daynaport,
is_bridge_setup,
daynaport_setup_bridge,
list_config_files,
detach_all,
valid_file_suffix,
valid_file_types,
reserve_scsi_ids,
get_server_info,
validate_scsi_id,
set_log_level,
)
from settings import *
@ -38,21 +40,28 @@ app = Flask(__name__)
@app.route("/")
def index():
devices = list_devices()
reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS")
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids))
unsorted_devices, occupied_ids = list_devices()
devices = sort_and_format_devices(unsorted_devices, occupied_ids)
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids), occupied_ids)
return render_template(
"index.html",
bridge_configured=is_bridge_setup("eth0"),
bridge_configured=is_bridge_setup(),
devices=devices,
active=is_active(),
files=list_files(),
config_files=list_config_files(),
base_dir=base_dir,
scsi_ids=scsi_ids,
reserved_scsi_ids=reserved_scsi_ids,
reserved_scsi_ids=[reserved_scsi_ids],
max_file_size=MAX_FILE_SIZE,
version=running_version(),
server_info=get_server_info(),
valid_file_suffix=VALID_FILE_SUFFIX,
removable_device_types=REMOVABLE_DEVICE_TYPES,
harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX,
cdrom_file_suffix=CDROM_FILE_SUFFIX,
removable_file_suffix=REMOVABLE_FILE_SUFFIX,
archive_file_suffix=ARCHIVE_FILE_SUFFIX,
)
@app.route('/pwa/<path:path>')
@ -62,10 +71,15 @@ def send_pwa_files(path):
@app.route("/config/save", methods=["POST"])
def config_save():
file_name = request.form.get("name") or "default"
file_name = f"{base_dir}{file_name}.csv"
file_name = f"{base_dir}{file_name}.json"
write_config_csv(file_name)
flash(f"Saved config to {file_name}!")
process = write_config(file_name)
if process["status"] == True:
flash(f"Saved config to {file_name}!")
return redirect(url_for("index"))
else:
flash(f"Failed to saved config to {file_name}!", "error")
flash(f"{process['msg']}", "error")
return redirect(url_for("index"))
@ -75,10 +89,12 @@ def config_load():
file_name = f"{base_dir}{file_name}"
if "load" in request.form:
if read_config_csv(file_name):
process = read_config(file_name)
if process["status"] == True:
flash(f"Loaded config from {file_name}!")
else:
flash(f"Failed to load {file_name}!", "error")
flash(f"{process['msg']}", "error")
elif "delete" in request.form:
if delete_file(file_name):
flash(f"Deleted config {file_name}!")
@ -88,16 +104,20 @@ def config_load():
return redirect(url_for("index"))
@app.route("/logs")
def logs():
import subprocess
@app.route("/logs/show", methods=["POST"])
def show_logs():
lines = request.form.get("lines") or "200"
scope = request.form.get("scope") or "default"
lines = request.args.get("lines") or "100"
process = subprocess.run(["journalctl", "-n", lines], capture_output=True)
from subprocess import run
if scope != "default":
process = run(["journalctl", "-n", lines, "-u", scope], capture_output=True)
else:
process = run(["journalctl", "-n", lines], capture_output=True)
if process.returncode == 0:
headers = {"content-type": "text/plain"}
return process.stdout.decode("utf-8"), 200, headers
return process.stdout.decode("utf-8"), int(lines), headers
else:
flash("Failed to get logs")
flash(process.stdout.decode("utf-8"), "stdout")
@ -105,86 +125,112 @@ def logs():
return redirect(url_for("index"))
@app.route("/logs/level", methods=["POST"])
def log_level():
level = request.form.get("level") or "info"
process = set_log_level(level)
if process["status"] == True:
flash(f"Log level set to {level}!")
return redirect(url_for("index"))
else:
flash(f"Failed to set log level to {level}!", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/daynaport/attach", methods=["POST"])
def daynaport_attach():
scsi_id = request.form.get("scsi_id")
validate = validate_scsi_id(scsi_id)
if validate["status"] == False:
flash(validate["msg"], "error")
return redirect(url_for("index"))
process = attach_daynaport(scsi_id)
if process.returncode == 0:
if process["status"] == True:
flash(f"Attached DaynaPORT to SCSI id {scsi_id}!")
return redirect(url_for("index"))
else:
flash(f"Failed to attach DaynaPORT to SCSI id {scsi_id}!", "error")
flash(process.stdout.decode("utf-8"), "stdout")
flash(process.stderr.decode("utf-8"), "stderr")
return redirect(url_for("index"))
@app.route("/daynaport/setup", methods=["POST"])
def daynaport_setup():
# Future use for wifi
interface = request.form.get("interface") or "eth0"
process = daynaport_setup_bridge(interface)
if process.returncode == 0:
flash(f"Configured DaynaPORT bridge on {interface}!")
return redirect(url_for("index"))
else:
flash(f"Failed to configure DaynaPORT bridge on {interface}!", "error")
flash(process.stdout.decode("utf-8"), "stdout")
flash(process.stderr.decode("utf-8"), "stderr")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/scsi/attach", methods=["POST"])
def attach():
file_name = request.form.get("file_name")
file_size = request.form.get("file_size")
scsi_id = request.form.get("scsi_id")
# Validate image type by suffix
print("file_name", file_name)
print("valid_file_types: ", valid_file_types)
if re.match(valid_file_types, file_name):
if file_name.lower().endswith((".iso", ".cdr", ".toast", ".img")):
image_type = "SCCD"
else:
image_type = "SCHD"
else:
flash(f"Unknown file type. Valid files are: {', '.join(valid_file_suffix)}", "error")
validate = validate_scsi_id(scsi_id)
if validate["status"] == False:
flash(validate["msg"], "error")
return redirect(url_for("index"))
# Validate the SCSI ID
if re.match("[0-7]", str(scsi_id)) == None:
flash(f"Invalid SCSI ID. Should be a number between 0-7", "error")
return redirect(url_for("index"))
kwargs = {"image": file_name}
process = attach_image(scsi_id, file_name, image_type)
if process.returncode == 0:
# Attempt to load the device config sidecar file:
# same base path but .rascsi instead of the original suffix.
from pathlib import Path
device_config = Path(base_dir + str(Path(file_name).stem) + ".rascsi")
if device_config.is_file():
process = read_device_config(device_config)
if process["status"] == False:
flash(process["msg"], "error")
return redirect(url_for("index"))
conf = process["conf"]
conf_file_size = conf["blocks"] * conf["block_size"]
if conf_file_size != 0 and conf_file_size > int(file_size):
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error")
return redirect(url_for("index"))
kwargs["device_type"] = conf["device_type"]
kwargs["vendor"] = conf["vendor"]
kwargs["product"] = conf["product"]
kwargs["revision"] = conf["revision"]
kwargs["block_size"] = conf["block_size"]
# Validate image type by file name suffix as fallback
elif file_name.lower().endswith(CDROM_FILE_SUFFIX):
kwargs["device_type"] = "SCCD"
elif file_name.lower().endswith(REMOVABLE_FILE_SUFFIX):
kwargs["device_type"] = "SCRM"
elif file_name.lower().endswith(HARDDRIVE_FILE_SUFFIX):
kwargs["device_type"] = "SCHD"
process = attach_image(scsi_id, **kwargs)
if process["status"] == True:
flash(f"Attached {file_name} to SCSI id {scsi_id}!")
return redirect(url_for("index"))
else:
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
flash(process.stdout.decode("utf-8"), "stdout")
flash(process.stderr.decode("utf-8"), "stderr")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/scsi/detach_all", methods=["POST"])
def detach_all_devices():
detach_all()
flash("Detached all SCSI devices!")
return redirect(url_for("index"))
process = detach_all()
if process["status"] == True:
flash("Detached all SCSI devices!")
return redirect(url_for("index"))
else:
flash("Failed to detach all SCSI devices!", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/scsi/detach", methods=["POST"])
def detach():
scsi_id = request.form.get("scsi_id")
process = detach_by_id(scsi_id)
if process.returncode == 0:
flash("Detached SCSI id " + scsi_id + "!")
if process["status"] == True:
flash(f"Detached SCSI id {scsi_id}!")
return redirect(url_for("index"))
else:
flash("Failed to detach SCSI id " + scsi_id + "!", "error")
flash(process.stdout, "stdout")
flash(process.stderr, "stderr")
flash(f"Failed to detach SCSI id {scsi_id}!", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@ -192,15 +238,35 @@ def detach():
def eject():
scsi_id = request.form.get("scsi_id")
process = eject_by_id(scsi_id)
if process.returncode == 0:
flash("Ejected scsi id " + scsi_id + "!")
if process["status"] == True:
flash(f"Ejected scsi id {scsi_id}!")
return redirect(url_for("index"))
else:
flash("Failed to eject SCSI id " + scsi_id + "!", "error")
flash(process.stdout, "stdout")
flash(process.stderr, "stderr")
flash(f"Failed to eject SCSI id {scsi_id}!", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/scsi/info", methods=["POST"])
def device_info():
scsi_id = request.form.get("scsi_id")
# Extracting the 0th dictionary in list index 0
device = list_devices(scsi_id)[0][0]
if str(device["id"]) == scsi_id:
flash("=== DEVICE INFO ===")
flash(f"SCSI ID: {device['id']}")
flash(f"Unit: {device['un']}")
flash(f"Type: {device['device_type']}")
flash(f"Status: {device['status']}")
flash(f"File: {device['image']}")
flash(f"Parameters: {device['params']}")
flash(f"Vendor: {device['vendor']}")
flash(f"Product: {device['product']}")
flash(f"Revision: {device['revision']}")
flash(f"Block Size: {device['block_size']}")
return redirect(url_for("index"))
else:
flash(f"Failed to get device info for SCSI id {scsi_id}!", "error")
return redirect(url_for("index"))
@app.route("/pi/reboot", methods=["POST"])
def restart():
@ -231,23 +297,27 @@ def download_file():
scsi_id = request.form.get("scsi_id")
url = request.form.get("url")
process = download_file_to_iso(scsi_id, url)
if process.returncode == 0:
flash("File Downloaded")
if process["status"] == True:
flash(f"File Downloaded and Attached to SCSI id {scsi_id}")
flash(process["msg"])
return redirect(url_for("index"))
else:
flash("Failed to download file", "error")
flash(process.stdout, "stdout")
flash(process.stderr, "stderr")
flash(f"Failed to download and attach file {url}", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/files/download_image", methods=["POST"])
def download_img():
url = request.form.get("url")
# TODO: error handling
download_image(url)
flash("File Downloaded")
return redirect(url_for("index"))
process = download_image(url)
if process["status"] == True:
flash(f"File Downloaded from {url}")
return redirect(url_for("index"))
else:
flash(f"Failed to download file {url}", "error")
flash(process["msg"], "error")
return redirect(url_for("index"))
@app.route("/files/upload/<filename>", methods=["POST"])
@ -256,19 +326,22 @@ def upload_file(filename):
flash("No file provided.", "error")
return redirect(url_for("index"))
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
if os.path.isfile(file_path):
from os import path
file_path = path.join(app.config["UPLOAD_FOLDER"], filename)
if path.isfile(file_path):
flash(f"{filename} already exists.", "error")
return redirect(url_for("index"))
from io import DEFAULT_BUFFER_SIZE
binary_new_file = "bx"
with open(file_path, binary_new_file, buffering=io.DEFAULT_BUFFER_SIZE) as f:
chunk_size = io.DEFAULT_BUFFER_SIZE
with open(file_path, binary_new_file, buffering=DEFAULT_BUFFER_SIZE) as f:
chunk_size = DEFAULT_BUFFER_SIZE
while True:
chunk = request.stream.read(chunk_size)
if len(chunk) == 0:
break
f.write(chunk)
# TODO: display an informative success message
return redirect(url_for("index", filename=filename))
@ -322,17 +395,25 @@ if __name__ == "__main__":
app.secret_key = "rascsi_is_awesome_insecure_secret_key"
app.config["SESSION_TYPE"] = "filesystem"
app.config["UPLOAD_FOLDER"] = base_dir
os.makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True)
from os import makedirs
makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True)
app.config["MAX_CONTENT_LENGTH"] = MAX_FILE_SIZE
if len(sys.argv) >= 2:
app.config["RESERVED_SCSI_IDS"] = str(sys.argv[1])
from sys import argv
if len(argv) >= 2:
# Reserved ids format is a string of digits such as '017'
app.config["RESERVED_SCSI_IDS"] = str(argv[1])
# Reserve SCSI IDs on the backend side to prevent use
reserve_scsi_ids(app.config.get("RESERVED_SCSI_IDS"))
else:
app.config["RESERVED_SCSI_IDS"] = ""
# Load the configuration in default.cvs, if it exists
read_config_csv(f"{base_dir}default.csv")
# Load the default configuration file, if found
from pathlib import Path
default_config = Path(DEFAULT_CONFIG)
if default_config.is_file():
read_config(default_config)
import bjoern
print("Serving rascsi-web...")