public VersionYesNoBlock(CopyDiskBlock command, ushort code, string herald) { Code = code; Herald = new BCPLString(herald); Length = (ushort)((6 + herald.Length + 2) / 2); // +2 for length of BCPL string and to round up to next word length Command = (ushort)command; }
private void CopyDiskWorkerThread() { // TODO: enforce state (i.e. reject out-of-order block types.) while (_running) { // Retrieve length of this block (in bytes): int length = Channel.ReadUShort() * 2; // Sanity check that length is a reasonable value. if (length > 2048) { Channel.SendAbort("Block length is invalid."); throw new InvalidOperationException(String.Format("Insane block length ({0})", length)); } // Retrieve type CopyDiskBlock blockType = (CopyDiskBlock)Channel.ReadUShort(); // Read rest of block starting at offset 4 (so deserialization works) byte[] data = new byte[length]; Channel.Read(ref data, data.Length - 4, 4); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk block type is {0}", blockType); switch (blockType) { case CopyDiskBlock.Version: { VersionYesNoBlock vbIn = (VersionYesNoBlock)Serializer.Deserialize(data, typeof(VersionYesNoBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk client is version {0}, '{1}'", vbIn.Code, vbIn.Herald.ToString()); // Send the response: VersionYesNoBlock vbOut = new VersionYesNoBlock(CopyDiskBlock.Version, vbIn.Code, "LCM+L IFS CopyDisk of 26-Jan-2016"); Channel.Send(Serializer.Serialize(vbOut)); } break; case CopyDiskBlock.Login: { LoginBlock login = (LoginBlock)Serializer.Deserialize(data, typeof(LoginBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Login is for user '{0}', password '{1}', connection '{2}', connection password '{3}'.", login.UserName, login.UserPassword, login.ConnName, login.ConnPassword); _userToken = AuthenticateUser(login.UserName.ToString(), login.UserPassword.ToString()); if (_userToken != null) { // // Send a "Yes" response back. // VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "Come on in, the water's fine."); Channel.Send(Serializer.Serialize(yes)); } else { // // Send a "No" response back indicating the login failure. // VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.IllegalOrIncorrectPassword, "Invalid username or password."); Channel.Send(Serializer.Serialize(no), true); } } break; case CopyDiskBlock.SendDiskParamsR: { SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for reading is '{0}'", p.UnitName); // // See if the pack image exists, return HereAreDiskParams if so, or No if not. // If the image exists, save the path for future use. // // Some sanity (and security) checks: // Name must be a filename only, no paths of any kind allowed. // Oh, and the file must exist in the directory holding disk packs. // string diskPath = GetPathForDiskImage(p.UnitName.ToString()); if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) || !File.Exists(diskPath)) { // Invalid name, return No reponse. VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name."); Channel.Send(Serializer.Serialize(no)); } else { // // Attempt to open the image file and read it into memory. // try { using (FileStream packStream = new FileStream(diskPath, FileMode.Open, FileAccess.Read)) { // TODO: determine pack type rather than assuming Diablo 31 _pack = new DiabloPack(DiabloDiskType.Diablo31); _pack.Load(packStream, diskPath, true /* reverse byte order */); } // Send a "HereAreDiskParams" response indicating success. // HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry); Channel.Send(Serializer.Serialize(diskParams)); } catch { // If we fail for any reason, return a "No" response. // TODO: can we be more helpful here? VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Image could not be opened."); Channel.Send(Serializer.Serialize(no)); } } } break; case CopyDiskBlock.SendDiskParamsW: { SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for writing is '{0}'", p.UnitName); // // Some sanity (and security) checks: // Name must be a filename only, no paths of any kind allowed. // Oh, and the file must not exist in the directory holding disk packs. // string diskPath = GetPathForDiskImage(p.UnitName.ToString()); if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) || File.Exists(diskPath)) { // Invalid name, return No reponse. VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name or image already exists."); Channel.Send(Serializer.Serialize(no)); } else { // // Create a new in-memory disk image. We will write it out to disk when the transfer is completed. // // TODO: determine pack type based on disk params rather than assuming Diablo 31 _pack = new DiabloPack(DiabloDiskType.Diablo31); _pack.PackName = diskPath; // Send a "HereAreDiskParams" response indicating success. // HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry); Channel.Send(Serializer.Serialize(diskParams)); } } break; case CopyDiskBlock.HereAreDiskParams: { HereAreDiskParamsBFSBlock diskParams = (HereAreDiskParamsBFSBlock)Serializer.Deserialize(data, typeof(HereAreDiskParamsBFSBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Disk params are: Type {0}, C/H/S {1}/{2}/{3}", diskParams.DiskType, diskParams.Cylinders, diskParams.Heads, diskParams.Sectors); } break; case CopyDiskBlock.RetrieveDisk: case CopyDiskBlock.StoreDisk: { TransferParametersBlock transferParameters = (TransferParametersBlock)Serializer.Deserialize(data, typeof(TransferParametersBlock)); _startAddress = _pack.DiskAddressToVirtualAddress(transferParameters.StartAddress); _endAddress = _pack.DiskAddressToVirtualAddress(transferParameters.EndAddress); // Validate that the user is allowed to store. if (blockType == CopyDiskBlock.StoreDisk) { if (_userToken.Privileges != IFSPrivileges.ReadWrite) { VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitWriteProtected, "You do not have permission to store disk images."); Channel.Send(Serializer.Serialize(no)); break; } } Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer is from block {0} to block {1}", transferParameters.StartAddress, transferParameters.EndAddress); // Validate start/end parameters if (_endAddress <= _startAddress || _startAddress > _pack.MaxAddress || _endAddress > _pack.MaxAddress) { VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnknownCommand, "Transfer parameters are invalid."); Channel.Send(Serializer.Serialize(no)); } else { // We're OK. Save the parameters and send a Yes response. VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "You are cleared for launch."); Channel.Send(Serializer.Serialize(yes)); // // And send the requested range of pages if this is a Retrieve operation // (otherwise wait for a HereIsDiskPage block from the client.) // if (blockType == CopyDiskBlock.RetrieveDisk) { for (int i = _startAddress; i < _endAddress + 1; i++) { DiabloDiskSector sector = _pack.GetSector(i); HereIsDiskPageBlock block = new HereIsDiskPageBlock(sector.Header, sector.Label, sector.Data); Channel.Send(Serializer.Serialize(block), false /* do not flush */); if ((i % 100) == 0) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sent page {0}", i); } } // Send "EndOfTransfer" block to finish the transfer. EndOfTransferBlock endTransfer = new EndOfTransferBlock(0); Channel.Send(Serializer.Serialize(endTransfer)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Send done."); } else { _currentAddress = _startAddress; } } } break; case CopyDiskBlock.HereIsDiskPage: { if (_currentAddress > _endAddress) { Channel.SendAbort("Invalid address for page."); _running = false; break; } if ((_currentAddress % 100) == 0) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Received page {0}", _currentAddress); } if (data.Length < 512 + 16 + 4 + 4) { // Incomplete ("incorrigable") page, indicating an unreadable or empty sector, just copy // the header/label data in and leave an empty data page. HereIsDiskPageIncorrigableBlock diskPage = (HereIsDiskPageIncorrigableBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageIncorrigableBlock)); DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, new byte[512]); _pack.SetSector(_currentAddress, sector); _currentAddress++; Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Page is empty / incorrigable."); } else { HereIsDiskPageBlock diskPage = (HereIsDiskPageBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageBlock)); DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, diskPage.Data); _pack.SetSector(_currentAddress, sector); _currentAddress++; } } break; case CopyDiskBlock.EndOfTransfer: { // No data in block. If we aren't currently at the end of the transfer, the transfer has been aborted. // Do nothing right now. if (_currentAddress < _endAddress) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer was aborted."); _running = false; } else { try { // Commit disk image to disk. using (FileStream packStream = new FileStream(_pack.PackName, FileMode.OpenOrCreate, FileAccess.Write)) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saving {0}...", _pack.PackName); _pack.Save(packStream, true /* reverse byte order */); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saved."); } } catch (Exception e) { // Log error, reset state. Log.Write(LogType.Error, LogComponent.CopyDisk, "Failed to save pack {0} - {1}", _pack.PackName, e.Message); } } } break; case CopyDiskBlock.SendErrors: { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sending error summary..."); // No data in block. Send list of errors we encountered. (There should always be none since we're perfect and have no disk errors.) HereAreErrorsBFSBlock errorBlock = new HereAreErrorsBFSBlock(0, 0); Channel.Send(Serializer.Serialize(errorBlock)); } break; default: Log.Write(LogType.Warning, LogComponent.CopyDisk, "Unhandled CopyDisk block {0}", blockType); break; } } if (OnExit != null) { OnExit(this); } }