Files
smbfst/fstops/Create.c
2024-07-14 13:48:45 -05:00

479 lines
17 KiB
C

/*
* Copyright (c) 2024 Stephen Heumann
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "defs.h"
#include <gsos.h>
#include <prodos.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "smb2/smb2.h"
#include "driver/driver.h"
#include "gsos/gsosutils.h"
#include "smb2/fileinfo.h"
#include "helpers/path.h"
#include "utils/endian.h"
#include "fst/fstspecific.h"
#include "helpers/filetype.h"
#include "helpers/datetime.h"
#include "helpers/attributes.h"
#include "helpers/errors.h"
#include "helpers/createcontext.h"
#include "helpers/closerequest.h"
#include "helpers/fsattributes.h"
#include "fst/fstdata.h"
#define extendExistingFile 0x8005
/*
* Notes on Create behavior under ProDOS FST:
*
* If storageType == $8005, most other params (except resourceEOF) are ignored.
* eof/resourceEOF indicate space to preallocate, but do not set the EOF.
* fileType = $0F with storageType not specified creates a directory.
* fileType = $0F with non-directory storageType creates a file with type $0F.
* storageType = $000D (directoryFile) forces file type to $0F.
* storageType = $8005 on a nonexistent file works like storageType = $0005.
*
* We generally try to match this behavior.
*/
Word Create(void *pblock, struct GSOSDP *gsosdp, Word pcount) {
Word result;
DIB *dib;
static SMB2_FILEID fileID;
Word retval = 0;
uint16_t msgLen;
SMB2_WRITE_Request *writeReq;
uint16_t createMsgNum, writeMsgNum, closeMsgNum;
bool ignoreAFPInfoErrors;
uint32_t attributes, initialAttributes;
uint64_t creationTime = 0;
// Settings for create, initialized to default values
Word access =
readEnable | writeEnable | renameEnable | destroyEnable | backupNeeded;
FileType fileType = {0, 0};
Word storageType = standardFile;
static uint64_t eof;
static uint64_t resourceEOF;
ProDOSTime createDateTime = {0, 0};
eof = 0;
resourceEOF = 0;
dib = GetDIB(gsosdp, 1);
if (dib == NULL)
return volNotFound;
/*
* Get parameters
*/
if (pcount == 0) {
#define pblock ((FileRec*)pblock)
access = pblock->fAccess;
fileType.fileType = pblock->fileType;
fileType.auxType = pblock->auxType;
storageType = pblock->storageType;
if (storageType <= 0x0003)
storageType = pblock->storageType = standardFile;
createDateTime.date = pblock->createDate;
createDateTime.time = pblock->createTime;
#undef pblock
} else {
#define pblock ((CreateRecGS*)pblock)
if (pcount >= 2) {
access = pblock->access;
if (pcount >= 3) {
fileType.fileType = pblock->fileType;
if (fileType.fileType == DIRECTORY_FILETYPE)
storageType = directoryFile;
if (pcount >= 4) {
fileType.auxType = pblock->auxType;
if (pcount >= 5) {
storageType = pblock->storageType;
if (storageType <= 0x0003)
storageType = pblock->storageType = standardFile;
if (pcount >= 6) {
eof = pblock->eof;
if (pcount >= 7) {
resourceEOF = pblock->resourceEOF;
}}}}}}
#undef pblock
}
/*
* Validate and convert parameters
*/
if (access & ~(readEnable | writeEnable | renameEnable |
destroyEnable | backupNeeded | fileInvisible))
return paramRangeErr;
if (storageType != standardFile && storageType != extendedFile
&& storageType != directoryFile && storageType != extendExistingFile)
return paramRangeErr;
if (storageType == directoryFile)
fileType.fileType = DIRECTORY_FILETYPE;
attributes = GetFileAttributes(access, storageType == directoryFile, dib);
// Ensure we have write access initially, even if file will be read-only.
// This is needed so we can set up the file.
initialAttributes = attributes & ~(uint32_t)FILE_ATTRIBUTE_READONLY;
if (initialAttributes == 0)
initialAttributes = FILE_ATTRIBUTE_NORMAL;
if (createDateTime.date != 0 || createDateTime.time != 0)
creationTime = ProDOSTimeToFiletime(createDateTime, dib->session);
if (storageType != extendExistingFile) {
/*
* Create file
*/
createRequest.SecurityFlags = 0;
createRequest.RequestedOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
createRequest.ImpersonationLevel = Impersonation;
createRequest.SmbCreateFlags = 0;
createRequest.Reserved = 0;
createRequest.NameOffset =
sizeof(SMB2Header) + offsetof(SMB2_CREATE_Request, Buffer);
createRequest.CreateContextsOffset = 0;
createRequest.CreateContextsLength = 0;
if (storageType == extendExistingFile) {
createRequest.CreateDisposition = FILE_OPEN;
createRequest.FileAttributes = 0;
} else {
createRequest.CreateDisposition = FILE_CREATE;
createRequest.FileAttributes = initialAttributes;
}
if (storageType == directoryFile) {
createRequest.CreateOptions = FILE_DIRECTORY_FILE;
} else {
createRequest.CreateOptions = FILE_NON_DIRECTORY_FILE;
}
createRequest.DesiredAccess =
FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | DELETE;
createRequest.ShareAccess = FILE_SHARE_DELETE;
// translate filename to SMB format
createRequest.NameLength = GSOSDPPathToSMB(gsosdp, 1,
createRequest.Buffer,
sizeof(msg.body) - offsetof(SMB2_CREATE_Request, Buffer));
if (createRequest.NameLength == 0xFFFF)
return badPathSyntax;
msgLen = sizeof(createRequest) + createRequest.NameLength;
if (eof != 0) {
if (storageType == standardFile || storageType == extendedFile) {
AddCreateContext(SMB2_CREATE_ALLOCATION_SIZE, &eof,
sizeof(eof), &msgLen);
// ignore errors (allocation size isn't very important)
}
}
result = SendRequestAndGetResponse(dib, SMB2_CREATE, msgLen);
if (result != rsDone) {
retval = ConvertError(result);
if (retval == fileNotFound)
retval = pathNotFound;
return retval;
}
volChangedDevNum = dib->DIBDevNum;
fileID = createResponse.FileId;
}
if (storageType == extendedFile || storageType == extendExistingFile) {
/*
* Create resource fork
*/
createRequest.SecurityFlags = 0;
createRequest.RequestedOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
createRequest.ImpersonationLevel = Impersonation;
createRequest.SmbCreateFlags = 0;
createRequest.Reserved = 0;
createRequest.NameOffset =
sizeof(SMB2Header) + offsetof(SMB2_CREATE_Request, Buffer);
createRequest.CreateContextsOffset = 0;
createRequest.CreateContextsLength = 0;
if (storageType == extendedFile) {
createRequest.CreateDisposition = FILE_OPEN_IF;
} else {
createRequest.CreateDisposition = FILE_CREATE;
}
createRequest.CreateOptions = FILE_NON_DIRECTORY_FILE;
createRequest.FileAttributes = initialAttributes;
createRequest.DesiredAccess =
FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
createRequest.ShareAccess = FILE_SHARE_DELETE;
// translate filename to SMB format
createRequest.NameLength = GSOSDPPathToSMB(gsosdp, 1,
createRequest.Buffer,
sizeof(msg.body) - offsetof(SMB2_CREATE_Request, Buffer));
if (createRequest.NameLength == 0xFFFF) {
retval = badPathSyntax;
goto close_on_error;
}
// add resource fork suffix
if (createRequest.NameLength >
sizeof(msg.body) - offsetof(SMB2_CREATE_Request, Buffer)
- sizeof(resourceForkSuffix))
return badPathSyntax;
memcpy(createRequest.Buffer + createRequest.NameLength,
resourceForkSuffix, sizeof(resourceForkSuffix));
createRequest.NameLength += sizeof(resourceForkSuffix);
msgLen = sizeof(createRequest) + createRequest.NameLength;
if (resourceEOF != 0) {
AddCreateContext(SMB2_CREATE_ALLOCATION_SIZE, &resourceEOF,
sizeof(resourceEOF), &msgLen);
// ignore errors (allocation size isn't very important)
}
createMsgNum = EnqueueRequest(dib, SMB2_CREATE, msgLen);
closeMsgNum = EnqueueCloseRequest(dib, &fileIDFromPrevious);
if (closeMsgNum == 0xFFFF) {
retval = fstError;
goto close_on_error;
}
SendMessages(dib);
result = GetResponse(dib, createMsgNum);
if (result != rsDone) {
if (storageType == extendExistingFile
&& result == rsFailed
&& msg.smb2Header.Status == STATUS_OBJECT_NAME_COLLISION) {
retval = resExistsErr;
} else {
retval = ConvertError(result);
}
}
if (!retval)
volChangedDevNum = dib->DIBDevNum;
result = GetResponse(dib, closeMsgNum);
if (result != rsDone && retval == 0)
retval = ConvertError(result);
if (retval != 0)
goto close_on_error;
}
if (storageType != extendExistingFile) {
/*
* Create and set AFP Info
*/
createRequest.SecurityFlags = 0;
createRequest.RequestedOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
createRequest.ImpersonationLevel = Impersonation;
createRequest.SmbCreateFlags = 0;
createRequest.Reserved = 0;
createRequest.NameOffset =
sizeof(SMB2Header) + offsetof(SMB2_CREATE_Request, Buffer);
createRequest.CreateContextsOffset = 0;
createRequest.CreateContextsLength = 0;
createRequest.CreateDisposition = FILE_OPEN_IF;
createRequest.CreateOptions = FILE_NON_DIRECTORY_FILE;
createRequest.FileAttributes = initialAttributes;
createRequest.DesiredAccess =
FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
createRequest.ShareAccess = FILE_SHARE_DELETE;
// translate filename to SMB format
createRequest.NameLength = GSOSDPPathToSMB(gsosdp, 1,
createRequest.Buffer,
sizeof(msg.body) - offsetof(SMB2_CREATE_Request, Buffer));
if (createRequest.NameLength == 0xFFFF) {
retval = badPathSyntax;
goto close_on_error;
}
// add AFP Info suffix
if (createRequest.NameLength >
sizeof(msg.body) - offsetof(SMB2_CREATE_Request, Buffer)
- sizeof(afpInfoSuffix)) {
retval = badPathSyntax;
goto close_on_error;
}
memcpy(createRequest.Buffer + createRequest.NameLength,
afpInfoSuffix, sizeof(afpInfoSuffix));
createRequest.NameLength += sizeof(afpInfoSuffix);
createMsgNum = EnqueueRequest(dib, SMB2_CREATE,
sizeof(createRequest) + createRequest.NameLength);
/*
* Create and save AFP Info record (including Finder Info)
*/
writeReq = (SMB2_WRITE_Request*)nextMsg->Body;
if (!SpaceAvailable(sizeof(*writeReq) + sizeof(AFPInfo))) {
retval = fstError;
goto close_on_error;
}
writeReq->DataOffset =
sizeof(SMB2Header) + offsetof(SMB2_WRITE_Request, Buffer);
writeReq->Length = sizeof(AFPInfo);
writeReq->Offset = 0;
writeReq->FileId = fileIDFromPrevious;
writeReq->Channel = 0;
writeReq->RemainingBytes = 0;
writeReq->WriteChannelInfoOffset = 0;
writeReq->WriteChannelInfoLength = 0;
writeReq->Flags = 0;
#define afpInfo ((AFPInfo*)writeReq->Buffer)
memset(afpInfo, 0, sizeof(AFPInfo));
afpInfo->signature = AFPINFO_SIGNATURE;
afpInfo->version = AFPINFO_VERSION;
afpInfo->backupTime = 0x80000000; // indicating "never backed up"
afpInfo->prodosType = fileType.fileType;
afpInfo->prodosAuxType = fileType.auxType;
if (storageType != directoryFile)
afpInfo->finderInfo.typeCreator =
FileTypeToTypeCreator(fileType, NULL);
#undef afpInfo
writeMsgNum = EnqueueRequest(dib, SMB2_WRITE,
sizeof(*writeReq) + sizeof(AFPInfo));
closeMsgNum = EnqueueCloseRequest(dib, &fileIDFromPrevious);
if (closeMsgNum == 0xFFFF) {
retval = fstError;
goto close_on_error;
}
SendMessages(dib);
ignoreAFPInfoErrors = false;
result = GetResponse(dib, createMsgNum);
if (result != rsDone) {
/*
* If we get STATUS_OBJECT_NAME_INVALID for the AFP info (after
* successfully creating the main stream with the same base name),
* this presumably means that the filesystem does not support
* named streams. STATUS_OBJECT_NAME_NOT_FOUND may also mean that.
* We will not report an error in these cases, because
* if we did it would prevent us from creating files on such
* filesystems at all. This way, we can at least create files,
* although the filetype and Finder Info will not be set correctly.
*/
if (result == rsFailed &&
(msg.smb2Header.Status == STATUS_OBJECT_NAME_INVALID
|| (msg.smb2Header.Status == STATUS_OBJECT_NAME_NOT_FOUND
&& !(GetFSAttributes(dib, &fileID) & FILE_NAMED_STREAMS))))
{
ignoreAFPInfoErrors = true;
} else {
retval = ConvertError(result);
}
}
result = GetResponse(dib, writeMsgNum);
if (result != rsDone && retval == 0 && !ignoreAFPInfoErrors)
retval = ConvertError(result);
result = GetResponse(dib, closeMsgNum);
if (result != rsDone && retval == 0 && !ignoreAFPInfoErrors)
retval = ConvertError(result);
if (retval != 0)
goto close_on_error;
if (attributes != initialAttributes || creationTime != 0) {
/*
* Set final attributes and creation time
*/
setInfoRequest.InfoType = SMB2_0_INFO_FILE;
setInfoRequest.FileInfoClass = FileBasicInformation;
setInfoRequest.BufferLength = sizeof(FILE_BASIC_INFORMATION);
setInfoRequest.BufferOffset =
sizeof(SMB2Header) + offsetof(SMB2_SET_INFO_Request, Buffer);
setInfoRequest.Reserved = 0;
setInfoRequest.AdditionalInformation = 0;
setInfoRequest.FileId = fileID;
#define info ((FILE_BASIC_INFORMATION *)setInfoRequest.Buffer)
info->CreationTime = creationTime;
info->LastAccessTime = 0;
info->LastWriteTime = 0;
info->ChangeTime = 0;
info->FileAttributes = attributes;
info->Reserved = 0;
#undef info
result = SendRequestAndGetResponse(dib, SMB2_SET_INFO,
sizeof(setInfoRequest) + sizeof(FILE_BASIC_INFORMATION));
if (result != rsDone) {
retval = ConvertError(result);
goto close_on_error;
}
}
}
close_on_error:
if (storageType != extendExistingFile) {
if (retval != 0) {
/*
* Put file in delete-pending state if there was an error
*/
setInfoRequest.InfoType = SMB2_0_INFO_FILE;
setInfoRequest.FileInfoClass = FileDispositionInformation;
setInfoRequest.BufferLength = sizeof(FILE_DISPOSITION_INFORMATION);
setInfoRequest.BufferOffset =
sizeof(SMB2Header) + offsetof(SMB2_SET_INFO_Request, Buffer);
setInfoRequest.Reserved = 0;
setInfoRequest.AdditionalInformation = 0;
setInfoRequest.FileId = fileID;
#define info ((FILE_DISPOSITION_INFORMATION *)setInfoRequest.Buffer)
info->DeletePending = 1;
#undef info
SendRequestAndGetResponse(dib, SMB2_SET_INFO,
sizeof(setInfoRequest) + sizeof(FILE_DISPOSITION_INFORMATION));
// Ignore errors here (we already have an error to report)
}
SendCloseRequestAndGetResponse(dib, &fileID);
// Ignore errors here (file is already created, or already have error)
}
return retval;
}