diff --git a/server.toml b/server.toml index dc2813d..8e034e2 100644 --- a/server.toml +++ b/server.toml @@ -51,6 +51,7 @@ read_only = true [AFP.Volumes.TestVolume] name = "Test Volume" # Volume name. Max 31 characters. path = 'C:\Mac\Test' +read_only = false appledouble_mode = "modern" # per-volume override; "modern" (._ sidecars) or "legacy" (.appledouble folder) rebuild_desktop_db = false diff --git a/service/afp/directory.go b/service/afp/directory.go index baa16c7..6d16803 100644 --- a/service/afp/directory.go +++ b/service/afp/directory.go @@ -323,9 +323,6 @@ func (s *Service) handleSetDirParms(req *FPSetDirParmsReq) (*FPSetDirParmsRes, i } func (s *Service) handleCreateDir(req *FPCreateDirReq) (*FPCreateDirRes, int32) { - if s.fs == nil { - return &FPCreateDirRes{}, ErrAccessDenied - } if s.volumeIsReadOnly(req.VolumeID) { return &FPCreateDirRes{}, ErrVolLocked } diff --git a/service/afp/file.go b/service/afp/file.go index 5db889b..1254402 100644 --- a/service/afp/file.go +++ b/service/afp/file.go @@ -24,9 +24,6 @@ func (s *Service) handleSetFileParms(req *FPSetFileParmsReq) (*FPSetFileParmsRes } func (s *Service) handleCreateFile(req *FPCreateFileReq) (*FPCreateFileRes, int32) { - if s.fs == nil { - return &FPCreateFileRes{}, ErrAccessDenied - } if s.volumeIsReadOnly(req.VolumeID) { return &FPCreateFileRes{}, ErrVolLocked } @@ -41,6 +38,7 @@ func (s *Service) handleCreateFile(req *FPCreateFileReq) (*FPCreateFileRes, int3 if req.HasFlag(FPCreateFileFlagHardCreate) { f, err := backend.CreateFile(targetPath) if err != nil { + netlog.Debug("[AFP] FPCreateFile hard create %q failed: %v", targetPath, err) return &FPCreateFileRes{}, ErrAccessDenied } f.Close() @@ -50,6 +48,7 @@ func (s *Service) handleCreateFile(req *FPCreateFileReq) (*FPCreateFileRes, int3 if os.IsExist(err) { return &FPCreateFileRes{}, ErrObjectExists } + netlog.Debug("[AFP] FPCreateFile %q failed: %v", targetPath, err) return &FPCreateFileRes{}, ErrAccessDenied } f.Close() diff --git a/service/afp/filedir.go b/service/afp/filedir.go index 2c11661..c634899 100644 --- a/service/afp/filedir.go +++ b/service/afp/filedir.go @@ -12,18 +12,18 @@ import ( func (s *Service) handleGetFileDirParms(req *FPGetFileDirParmsReq) (*FPGetFileDirParmsRes, int32) { if req.FileBitmap == 0 && req.DirBitmap == 0 { - return &FPGetFileDirParmsRes{}, ErrBitmapErr + return nil, ErrBitmapErr } if req.FileBitmap&^enumerateFileBitmapMask != 0 || req.DirBitmap&^enumerateDirBitmapMask != 0 { - return &FPGetFileDirParmsRes{}, ErrBitmapErr + return nil, ErrBitmapErr } if req.Path != "" && req.PathType != PathTypeShortNames && req.PathType != PathTypeLongNames { - return &FPGetFileDirParmsRes{}, ErrParamErr + return nil, ErrParamErr } parentPath, ok := s.resolveDIDPath(req.VolumeID, req.DirID) if !ok && req.DirID != 0 { - return emptyGetFileDirParmsRes(req), ErrObjectNotFound + return nil, ErrObjectNotFound } else if !ok && req.DirID == 0 { parentPath, _ = s.resolveDIDPath(req.VolumeID, CNIDRoot) } @@ -32,10 +32,7 @@ func (s *Service) handleGetFileDirParms(req *FPGetFileDirParmsReq) (*FPGetFileDi if req.Path != "" { resolvedPath, errCode := s.resolvePath(parentPath, req.Path, req.PathType) if errCode != NoErr { - if errCode == ErrObjectNotFound { - return emptyGetFileDirParmsRes(req), ErrObjectNotFound - } - return &FPGetFileDirParmsRes{}, errCode + return nil, errCode } targetPath = resolvedPath } @@ -48,12 +45,12 @@ func (s *Service) handleGetFileDirParms(req *FPGetFileDirParmsReq) (*FPGetFileDi } else { backend := s.fsForPath(targetPath) if backend == nil { - return emptyGetFileDirParmsRes(req), ErrObjectNotFound + return nil, ErrObjectNotFound } info, err = backend.Stat(targetPath) } if err != nil { - return emptyGetFileDirParmsRes(req), ErrObjectNotFound + return nil, ErrObjectNotFound } targetPath = infoPath @@ -76,21 +73,6 @@ func (s *Service) handleGetFileDirParms(req *FPGetFileDirParmsReq) (*FPGetFileDi return res, NoErr } -func emptyGetFileDirParmsRes(req *FPGetFileDirParmsReq) *FPGetFileDirParmsRes { - // Preserve a valid reply layout (bitmaps + File/DirFlag + pad) even on - // ObjectNotFound so clients can parse the envelope deterministically. - isFile := true - if req.FileBitmap == 0 && req.DirBitmap != 0 { - isFile = false - } - return &FPGetFileDirParmsRes{ - FileBitmap: req.FileBitmap, - DirBitmap: req.DirBitmap, - IsFile: isFile, - Data: nil, - } -} - func (s *Service) handleRename(req *FPRenameReq) (*FPRenameRes, int32) { if s.volumeIsReadOnly(req.VolumeID) { return &FPRenameRes{}, ErrVolLocked @@ -195,9 +177,6 @@ func (s *Service) handleSetFileDirParms(req *FPSetFileDirParmsReq) (*FPSetFileDi } func (s *Service) handleDelete(req *FPDeleteReq) (*FPDeleteRes, int32) { - if s.fs == nil { - return &FPDeleteRes{}, ErrAccessDenied - } if s.volumeIsReadOnly(req.VolumeID) { return &FPDeleteRes{}, ErrVolLocked } diff --git a/service/afp/filedir_pack.go b/service/afp/filedir_pack.go index fa55100..9b93652 100644 --- a/service/afp/filedir_pack.go +++ b/service/afp/filedir_pack.go @@ -233,11 +233,7 @@ func (s *Service) packFileInfo(buf *bytes.Buffer, volumeID uint16, bitmap uint16 fixedSize := calcFileParamsSize(bitmap) if bitmap&FileBitmapAttributes != 0 { - attr := uint16(0) - if s.volumeIsReadOnly(volumeID) { - attr |= FileAttrWriteInhibit - } - binutil.WriteU16(buf, attr) + binutil.WriteU16(buf, 0) } if bitmap&FileBitmapParentDID != 0 { pdir := s.getPathDID(volumeID, parentPath) diff --git a/service/afp/getfiledirparms_error_response_test.go b/service/afp/getfiledirparms_error_response_test.go deleted file mode 100644 index 57e6ec6..0000000 --- a/service/afp/getfiledirparms_error_response_test.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build afp || all - -package afp - -import ( - "encoding/binary" - "testing" -) - -func TestHandleGetFileDirParms_ObjectNotFoundReturnsStructuredResponse(t *testing.T) { - root := t.TempDir() - s := NewService("TestServer", []VolumeConfig{{Name: "Vol", Path: root}}, &LocalFileSystem{}, nil) - - req := &FPGetFileDirParmsReq{ - VolumeID: 1, - DirID: CNIDRoot, - FileBitmap: FileBitmapLongName, - DirBitmap: DirBitmapLongName, - PathType: 2, - Path: "missing", - } - - res, errCode := s.handleGetFileDirParms(req) - if errCode != ErrObjectNotFound { - t.Fatalf("errCode=%d, want ErrObjectNotFound (%d)", errCode, ErrObjectNotFound) - } - if res == nil { - t.Fatalf("expected non-nil response on ErrObjectNotFound") - } - - wire := res.Marshal() - if len(wire) != 6 { - t.Fatalf("wire len=%d, want 6 (bitmaps + flag + pad)", len(wire)) - } - if got := binary.BigEndian.Uint16(wire[0:2]); got != req.FileBitmap { - t.Fatalf("file bitmap=%#04x, want %#04x", got, req.FileBitmap) - } - if got := binary.BigEndian.Uint16(wire[2:4]); got != req.DirBitmap { - t.Fatalf("dir bitmap=%#04x, want %#04x", got, req.DirBitmap) - } - if wire[5] != 0x00 { - t.Fatalf("reserved pad byte=%#02x, want 0x00", wire[5]) - } -} - -func TestHandleGetFileDirParms_ObjectNotFoundDirOnlyRequestUsesDirFlag(t *testing.T) { - root := t.TempDir() - s := NewService("TestServer", []VolumeConfig{{Name: "Vol", Path: root}}, &LocalFileSystem{}, nil) - - req := &FPGetFileDirParmsReq{ - VolumeID: 1, - DirID: CNIDRoot, - FileBitmap: 0, - DirBitmap: DirBitmapLongName, - PathType: 2, - Path: "missing", - } - - res, errCode := s.handleGetFileDirParms(req) - if errCode != ErrObjectNotFound { - t.Fatalf("errCode=%d, want ErrObjectNotFound (%d)", errCode, ErrObjectNotFound) - } - if res == nil { - t.Fatalf("expected non-nil response on ErrObjectNotFound") - } - - wire := res.Marshal() - if len(wire) != 6 { - t.Fatalf("wire len=%d, want 6", len(wire)) - } - if wire[4] != 0x80 { - t.Fatalf("File/DirFlag=%#02x, want 0x80 for dir-only request", wire[4]) - } -} diff --git a/service/afp/getfiledirparms_validation_test.go b/service/afp/getfiledirparms_validation_test.go index c4a0351..69fef9e 100644 --- a/service/afp/getfiledirparms_validation_test.go +++ b/service/afp/getfiledirparms_validation_test.go @@ -19,8 +19,8 @@ func TestHandleGetFileDirParms_RejectsZeroBitmaps(t *testing.T) { if errCode != ErrBitmapErr { t.Fatalf("errCode=%d, want %d", errCode, ErrBitmapErr) } - if res == nil { - t.Fatalf("expected non-nil response") + if res != nil { + t.Fatalf("expected nil response on error, got %+v", res) } } @@ -41,8 +41,8 @@ func TestHandleGetFileDirParms_RejectsUnsupportedBitmapBits(t *testing.T) { if errCode != ErrBitmapErr { t.Fatalf("errCode=%d, want %d", errCode, ErrBitmapErr) } - if res == nil { - t.Fatalf("expected non-nil response") + if res != nil { + t.Fatalf("expected nil response on error, got %+v", res) } } @@ -61,7 +61,7 @@ func TestHandleGetFileDirParms_RejectsInvalidPathTypeWhenPathPresent(t *testing. if errCode != ErrParamErr { t.Fatalf("errCode=%d, want %d", errCode, ErrParamErr) } - if res == nil { - t.Fatalf("expected non-nil response") + if res != nil { + t.Fatalf("expected nil response on error, got %+v", res) } } diff --git a/service/afp/testdata/fpgetsrvrinfores_basic.hex b/service/afp/testdata/fpgetsrvrinfores_basic.hex index 725b3c6..bacd0ce 100644 --- a/service/afp/testdata/fpgetsrvrinfores_basic.hex +++ b/service/afp/testdata/fpgetsrvrinfores_basic.hex @@ -1 +1 @@ -0016001f004d000080000b5465737420536572766572084f6d6e6954616c6b030e41465056657273696f6e20312e310e41465056657273696f6e20322e300e41465056657273696f6e20322e31020f4e6f20557365722041757468656e7410436c6561727478742050617373777264 +001600230051000080000b54657374205365727665720c436c6173736963537461636b030e41465056657273696f6e20312e310e41465056657273696f6e20322e300e41465056657273696f6e20322e31020f4e6f20557365722041757468656e7410436c6561727478742050617373777264 diff --git a/service/afp/testdata/fpgetsrvrmsgres_basic.hex b/service/afp/testdata/fpgetsrvrmsgres_basic.hex index b5c691f..f92abbc 100644 --- a/service/afp/testdata/fpgetsrvrmsgres_basic.hex +++ b/service/afp/testdata/fpgetsrvrmsgres_basic.hex @@ -1 +1 @@ -000100031357656c636f6d6520746f204f6d6e6954616c6b +000100031757656c636f6d6520746f20436c6173736963537461636b