From d393cea96e06863e68359ccb35626e309f1de4d2 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 17 Jun 2025 18:09:02 +0300 Subject: [PATCH 1/2] Improve globbing by getting size at the time we find files This stops having to stat files post-find, instead we can save away the size as we find the files. (ie. We use filepath.Walk instead of os.ReadDir so we get access to the fs.FileInfo immediately.) --- cpm/cpm_bdos.go | 48 ++++++++++++------------------------------------ fcb/fcb.go | 37 ++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/cpm/cpm_bdos.go b/cpm/cpm_bdos.go index 57d5ade..5fab21f 100644 --- a/cpm/cpm_bdos.go +++ b/cpm/cpm_bdos.go @@ -635,8 +635,10 @@ func BdosSysCallFileClose(cpm *CPM) error { // BdosSysCallFindFirst finds the first filename, on disk, that matches the glob in the FCB supplied in DE. func BdosSysCallFindFirst(cpm *CPM) error { + // The pointer to the FCB ptr := cpm.CPU.States.DE.U16() + // Get the bytes which make up the FCB entry. xxx := cpm.Memory.GetRange(ptr, fcb.SIZE) @@ -727,25 +729,12 @@ func BdosSysCallFindFirst(cpm *CPM) error { // Create a new FCB and store it in the DMA entry x := fcb.FromString(res[0].Name) - // Get the file-size in records, and add to the FCB - tmp, err := os.OpenFile(res[0].Host, os.O_RDONLY, 0644) - if err == nil { - defer tmp.Close() - - fi, err := tmp.Stat() - if err == nil { - - fileSize := fi.Size() + // Get file size, in blocks. + x.RC = uint8(res[0].Size / blkSize) - // Get file size, in blocks - x.RC = uint8(fileSize / blkSize) - - // If the size is bigger than a multiple we deal with that. - if fileSize > int64(int64(x.RC)*int64(blkSize)) { - x.RC++ - } - - } + // If the size is bigger than a multiple we deal with that. + if res[0].Size > int64(int64(x.RC)*int64(blkSize)) { + x.RC++ } // Log the first result we're returning. @@ -783,25 +772,12 @@ func BdosSysCallFindNext(cpm *CPM) error { // Create a new FCB and store it in the DMA entry x := fcb.FromString(res.Name) - // Get the file-size in records, and add to the FCB - tmp, err := os.OpenFile(res.Host, os.O_RDONLY, 0644) - if err == nil { - defer tmp.Close() - - fi, err := tmp.Stat() - if err == nil { - - fileSize := fi.Size() - - // Get file size, in blocks - x.RC = uint8(fileSize / blkSize) + // Get file size, in blocks. + x.RC = uint8(res.Size / blkSize) - // If the size is bigger than a multiple we deal with that. - if fileSize > int64(int64(x.RC)*int64(blkSize)) { - x.RC++ - } - - } + // If the size is bigger than a multiple we deal with that. + if res.Size > int64(int64(x.RC)*int64(blkSize)) { + x.RC++ } // Log that we're returning the next result. diff --git a/fcb/fcb.go b/fcb/fcb.go index dc6423c..779e4e3 100644 --- a/fcb/fcb.go +++ b/fcb/fcb.go @@ -2,8 +2,8 @@ package fcb import ( + "io/fs" "log/slog" - "os" "path/filepath" "strings" "unicode" @@ -71,6 +71,9 @@ type Find struct { // Name is the name as CP/M would see it. // This will be upper-cased and in 8.3 format. Name string + + // Size contains the size of the file. + Size int64 } // GetName returns the name component of an FCB entry. @@ -377,36 +380,40 @@ func (f *FCB) DoesMatch(name string) bool { func (f *FCB) GetMatches(prefix string) ([]Find, error) { var ret []Find - // Find files in the directory - files, err := os.ReadDir(prefix) - if err != nil { - return ret, err - } + err := filepath.Walk(prefix, func(path string, info fs.FileInfo, err error) error { - // For each file - for _, file := range files { + if err != nil { + return err + } // Ignore directories, we only care about files. - if file.IsDir() { - continue + if info.IsDir() { + return nil } - name := strings.ToUpper(file.Name()) + // Upper-case, and remove prefix. + name := filepath.Base(strings.ToUpper(path)) + if f.DoesMatch(name) { var ent Find // Populate the host-path before we do anything else. - ent.Host = filepath.Join(prefix, file.Name()) + ent.Host = filepath.Join(path) - // populate the name, but note it needs to be upper-cased + // populate the name ent.Name = name + // populate the size too + ent.Size = info.Size() + // append ret = append(ret, ent) } - } + + return nil + }) // Return the entries we found, if any. - return ret, nil + return ret, err } From 6deb71b1e5db997fd4bdc6d9a64a203bd5be504e Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Tue, 17 Jun 2025 18:13:22 +0300 Subject: [PATCH 2/2] Improved test-cases --- fcb/fcb_test.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/fcb/fcb_test.go b/fcb/fcb_test.go index b4bbfae..dfe08f5 100644 --- a/fcb/fcb_test.go +++ b/fcb/fcb_test.go @@ -2,6 +2,7 @@ package fcb import ( "fmt" + "sort" "testing" ) @@ -207,13 +208,30 @@ func TestGetMatches(t *testing.T) { t.Fatalf("failed to get matches") } - if len(out) != 1 { - t.Fatalf("unexpected number of matches") + if len(out) < 10 { + t.Fatalf("unexpected number of matches got %d", len(out)) } - if out[0].Host != "../main.go" { + + // sort the files - so we can be predictable + sort.Slice(out, func(i, j int) bool { + return out[i].Name < out[j].Name + }) + + // first file, alphabetically + if out[0].Host != "../ccp/ccp.go" { t.Fatalf("unexpected name %s", out[0].Host) } + found := false + for _, e := range out { + if e.Host == "../static/static.go" { + found = true + } + } + if !found { + t.Fatalf("failed to find static.go") + } + _, err = f.GetMatches("!>>//path/not/found") if err == nil { t.Fatalf("expected error on bogus directory, got none")