package main import ( "encoding/binary" "encoding/json" "fmt" "log" "net/http" "os" "path/filepath" "strconv" "strings" "sync" "time" "unsafe" ) const ( PGSZ = 16384 HDRSZ = 4096 PHDR = 20 // PageHdr actual size (with alignment) DP_HDR = PHDR + 4 + 8 + 8 // DataPage header: PageHdr(20) + Count(4) + Min(8) + Max(8) = 40 IDX_HDR = 32 // PHDR(20) + pad(1) + IndexType(1) + EntryLen(2) + Count(4) + IsLeaf(1) + pad(3) META_SZ = 60 // IndexMeta entry size (with Go alignment padding) TINFO_SZ = 80 // TableInfo struct size (with alignment) PG_HEAD = 0 PG_TABLE = 1 PG_DATA = 2 PG_HEAP = 3 PG_IMETA = 4 PG_IDATA = 5 ) // ===== Data Structures ===== type TInfo struct { Name string `json:"name"` RowN uint64 `json:"rowN"` FSz uint16 `json:"fsz"` DP0 uint32 `json:"dp0"` IP0 uint32 `json:"ip0"` HP0 uint32 `json:"hp0"` ActiveVer byte `json:"activeVer"` MasterFields []MFInfo `json:"masterFields"` Versions []VInfo `json:"versions"` Idxs []IInfo `json:"idxs"` } type MFInfo struct { Name string `json:"name"` Type byte `json:"type"` TypeName string `json:"typeName"` Sz int `json:"sz"` Dropped bool `json:"dropped"` } type VInfo struct { Ver byte `json:"ver"` Fields []string `json:"fields"` Indices []byte `json:"indices"` } type FInfo struct { Name string `json:"name"` Type byte `json:"type"` Sz int `json:"sz"` } type IInfo struct { Name string `json:"name"` Type byte `json:"type"` TS string `json:"ts"` EP uint32 `json:"ep"` Flds []int `json:"flds"` } type DP struct { ID uint32 `json:"id"` Nx uint32 `json:"nx"` Pv uint32 `json:"pv"` Cn uint32 `json:"cn"` Mx uint64 `json:"mx"` Mn uint64 `json:"mn"` Us float64 `json:"us"` Rs []DR `json:"rs"` } type DR struct { I int `json:"i"` PKID uint64 `json:"pk"` SchemaV byte `json:"sv"` Hex string `json:"h"` Decoded string `json:"decoded"` } type IP struct { ID uint32 `json:"id"` Nx uint32 `json:"nx"` Pv uint32 `json:"pv"` Lf bool `json:"lf"` Cn uint32 `json:"cn"` Us float64 `json:"us"` Ents []IE `json:"ents"` IdxType byte `json:"it"` } type IE struct { K string `json:"k"` P uint32 `json:"p"` DP uint64 `json:"dp"` } type HdrInfo struct { Magic string `json:"magic"` Version uint32 `json:"ver"` PageSize uint32 `json:"pgsz"` NextFree uint32 `json:"nf"` TablePage uint32 `json:"tp"` TableCount uint32 `json:"tc"` CreateTime string `json:"ct"` ModifyTime string `json:"mt"` FreeStart uint32 `json:"fs"` FreeCount uint32 `json:"fc"` GlobalFlags uint64 `json:"gf"` Comment string `json:"cm"` } type HeapChain struct { Pages []HeapPageInfo `json:"pages"` Total int `json:"total"` } type HeapPageInfo struct { ID uint32 `json:"id"` Next uint32 `json:"nx"` Prev uint32 `json:"pv"` Use uint32 `json:"use"` Us float64 `json:"us"` } type FreePages struct { Pages []uint32 `json:"pages"` Total int `json:"total"` } var ( dbF string fp string hdr [HDRSZ]byte tabs []TInfo mu sync.RWMutex ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) fmt.Println("╔══════════════════════════════════════════════╗") fmt.Println("║ TsDB2 Analyzer v3.0 (Schema Aware) ║") fmt.Println("║ http://localhost:8081 ║") fmt.Println("╚══════════════════════════════════════════════╝") http.HandleFunc("/", homeH) http.HandleFunc("/api/list-dir", ldH) http.HandleFunc("/api/open-db", openH) http.HandleFunc("/api/stats", stH) http.HandleFunc("/api/tables", taH) http.HandleFunc("/api/table/", tdH) http.HandleFunc("/api/data-page", dpH) http.HandleFunc("/api/index-page", ipH) http.HandleFunc("/api/btree", btH) http.HandleFunc("/api/header", hdrH) http.HandleFunc("/api/heap-chain", heapH) http.HandleFunc("/api/free-pages", freeH) http.HandleFunc("/api/pages", pagesH) http.HandleFunc("/api/find-pk", findPkH) http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir(".")))) log.Fatal(http.ListenAndServe(":8081", nil)) } // ===== Low-level page reading ===== func rp(pid uint32) ([]byte, error) { off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ) b := make([]byte, PGSZ) f, e := os.Open(fp) if e != nil { return nil, e } defer f.Close() _, e = f.ReadAt(b, off) return b, e } func rHdr() { f, e := os.Open(fp) if e != nil { return } defer f.Close() f.ReadAt(hdr[:], 0) } // ===== Parse all tables from TablePage chain ===== func pTabs() []TInfo { var R []TInfo b := make([]byte, PGSZ) f, e := os.Open(fp) if e != nil { return R } defer f.Close() tp := binary.LittleEndian.Uint32(hdr[16:20]) if tp == 0 { return R } for pid := tp; pid != 0; { f.ReadAt(b, int64(HDRSZ)+int64(pid-1)*int64(PGSZ)) us := binary.LittleEndian.Uint32(b[16:20]) if us > PGSZ { us = PGSZ } off := uint32(PHDR) for off+TINFO_SZ <= us && off+TINFO_SZ <= PGSZ { tb := b[off : off+TINFO_SZ] fl := tb[24] if fl&0x80 != 0 { off += TINFO_SZ + uint32(binary.LittleEndian.Uint16(tb[58:60])) continue } nm := strings.TrimRight(string(tb[:24]), "\x00") rn := binary.LittleEndian.Uint64(tb[32:40]) // Parse new schema format fo := off + TINFO_SZ activeVer, masterFields, versions := parseSchemaFields(b, fo) // Parse indexes var is []IInfo iip := binary.LittleEndian.Uint32(tb[64:68]) for ip := iip; ip != 0; { ib, e := rp(ip) if e != nil { break } if ib[0] != PG_IMETA { break } iu := binary.LittleEndian.Uint32(ib[16:20]) if iu > PGSZ { iu = PGSZ } io := uint32(PHDR) for io+META_SZ <= iu && io+META_SZ <= PGSZ { im := ib[io : io+META_SZ] // IndexMeta struct: Name[24] + Type(1) + FieldCnt(1) + FieldIDs[16] + pad(2) + EntryPage(4) + Prev(4) + Next(4) + Flag(1) + pad(3) = 60 if im[56]&0x80 == 0 { // Flag at offset 56 in := strings.TrimRight(string(im[:24]), "\x00") it := im[24] // IndexType at offset 24 ic := int(im[25]) // FieldCnt at offset 25 ep := binary.LittleEndian.Uint32(im[44:48]) // EntryPage at offset 44 (after 2B padding) // 验证 EntryPage 指向有效索引页 (防止已删除表的孤立条目) if ep != 0 { pb, pe := rp(ep) if pe != nil || pb[0] != PG_IDATA { ep = 0 // 无效页, 跳过 } } if ep != 0 { var fds []int for j := 0; j < ic; j++ { fds = append(fds, int(im[26+j])) // FieldIDs at offset 26 } is = append(is, IInfo{in, it, tNm(it), ep, fds}) } } io += META_SZ } ip = binary.LittleEndian.Uint32(ib[8:12]) } R = append(R, TInfo{ Name: nm, RowN: rn, FSz: binary.LittleEndian.Uint16(tb[56:58]), DP0: binary.LittleEndian.Uint32(tb[48:52]), IP0: binary.LittleEndian.Uint32(tb[64:68]), HP0: binary.LittleEndian.Uint32(tb[40:44]), ActiveVer: activeVer, MasterFields: masterFields, Versions: versions, Idxs: is, }) off += TINFO_SZ + uint32(binary.LittleEndian.Uint16(tb[58:60])) } nx := binary.LittleEndian.Uint32(b[8:12]) if nx == 0 || nx == pid { break } pid = nx } return R } // parseSchemaFields 解析新版 schema 字段定义链 // 格式: [activeVer(1B)] [masterCount(1B)] [type+len+name]×N [verCount(1B)] [verDefs...] func parseSchemaFields(b []byte, off uint32) (byte, []MFInfo, []VInfo) { if int(off)+2 > len(b) { return 0, nil, nil } pos := int(off) activeVer := b[pos] pos++ masterCount := int(b[pos]) pos++ var masterFields []MFInfo for i := 0; i < masterCount && pos+2 <= len(b); i++ { typ := b[pos] nameLen := int(b[pos+1]) pos += 2 if pos+nameLen > len(b) { break } name := strings.TrimRight(string(b[pos:pos+nameLen]), "\x00") pos += nameLen dropped := typ&0x80 != 0 realType := typ & 0x7F masterFields = append(masterFields, MFInfo{ Name: name, Type: realType, TypeName: fName(realType), Sz: fSz(realType), Dropped: dropped, }) } var versions []VInfo if pos < len(b) { verCount := int(b[pos]) pos++ for i := 0; i < verCount && pos < len(b); i++ { verNo := b[pos] pos++ if pos >= len(b) { break } fieldCount := int(b[pos]) pos++ indices := make([]byte, fieldCount) if pos+fieldCount <= len(b) { copy(indices, b[pos:pos+fieldCount]) pos += fieldCount } // Build field name list for this version var fieldNames []string for _, idx := range indices { if int(idx) < len(masterFields) { mf := masterFields[idx] n := mf.Name if mf.Dropped { n += " (dropped)" } fieldNames = append(fieldNames, n) } else { fieldNames = append(fieldNames, "?") } } versions = append(versions, VInfo{Ver: verNo, Fields: fieldNames, Indices: indices}) } } return activeVer, masterFields, versions } // ===== Field type helpers ===== func fSz(t byte) int { switch t { case 1: return 1 case 2: return 2 case 3: return 4 case 4: return 8 case 5: return 16 case 6: return 32 case 7: return 64 case 8: return 128 case 9: return 256 default: return 0 } } func tNm(t byte) string { switch t { case 1: return "Time" case 2: return "Primary" case 3: return "String" case 4: return "Compose" case 5: return "BTree" default: return "?" } } func fName(t byte) string { switch t { case 1: return "int8" case 2: return "int16" case 3: return "int32" case 4: return "int64" case 5: return "16byte" case 6: return "32byte" case 7: return "64byte" case 8: return "128byte" case 9: return "256byte" default: return "?" } } // ===== Read DataPage with schema version awareness ===== // rDP reads a DataPage (sequential row layout, no directory) func rDP(pid uint32) *DP { b, e := rp(pid) if e != nil { return nil } if b[0] != PG_DATA { return nil } cn := binary.LittleEndian.Uint32(b[PHDR : PHDR+4]) mn := binary.BigEndian.Uint64(b[PHDR+4 : PHDR+12]) mx := binary.BigEndian.Uint64(b[PHDR+12 : PHDR+20]) us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100 var rs []DR off := DP_HDR for i := uint32(0); i < cn && off+4 <= len(b); i++ { rl := int(binary.LittleEndian.Uint32(b[off : off+4])) off += 4 if off+rl > len(b) || rl <= 0 { break } storeRow := b[off : off+rl] off += rl // storeRow = [pkID(8B BE)] + rowData var pkID uint64 var rowData []byte if len(storeRow) >= 8 { pkID = binary.BigEndian.Uint64(storeRow[:8]) rowData = storeRow[8:] } else { rowData = storeRow } schemaV := byte(0) if len(rowData) > 0 { schemaV = rowData[0] } hx := fmt.Sprintf("%x", storeRow[:min(len(storeRow), 64)]) if len(storeRow) > 64 { hx += "..." } decoded := decodeRowData(rowData, schemaV) rs = append(rs, DR{int(i), pkID, schemaV, hx, decoded}) } return &DP{pid, binary.LittleEndian.Uint32(b[8:12]), binary.LittleEndian.Uint32(b[12:16]), cn, mx, mn, us, rs} } // decodeRowData 尝试解码行数据 (显示 schemaVer + 各字段值) func decodeRowData(rowData []byte, schemaV byte) string { if len(rowData) < 1 { return "" } // 查找对应表的版本信息来解码 // 简单解码: 显示 schemaVer + hex parts := []string{fmt.Sprintf("v%d", schemaV)} data := rowData[1:] // skip schemaVer if len(data) == 0 { return strings.Join(parts, " ") } // 尝试按字段类型解码 (需要知道该版本的字段列表) // 通用方式: 显示前几个字节的值 for _, t := range tabs { sv := findVersion(t, schemaV) if sv == nil { continue } off := 0 for _, idx := range sv.Indices { if int(idx) >= len(t.MasterFields) { break } mf := t.MasterFields[idx] if mf.Dropped { continue } sz := mf.Sz if off+sz > len(data) { break } val := decodeFieldValue(data[off:off+sz], mf.Type) parts = append(parts, mf.Name+"="+val) off += sz } return strings.Join(parts, ", ") } return strings.Join(parts, " ") } func findVersion(t TInfo, ver byte) *VInfo { for i := range t.Versions { if t.Versions[i].Ver == ver { return &t.Versions[i] } } return nil } func decodeFieldValue(buf []byte, typ byte) string { switch typ { case 1: return fmt.Sprintf("%d", int8(buf[0])) case 2: return fmt.Sprintf("%d", int16(binary.BigEndian.Uint16(buf))) case 3: return fmt.Sprintf("%d", int32(binary.BigEndian.Uint32(buf))) case 4: return fmt.Sprintf("%d", int64(binary.BigEndian.Uint64(buf))) default: s := strings.TrimRight(string(buf), "\x00") if len(s) == 0 { return "(empty)" } return s } } // ===== Index page reading ===== func rIP(pid uint32, it byte) *IP { b, e := rp(pid) if e != nil { return nil } if b[0] != PG_IDATA { return nil } lf := b[28] == 1 cn := binary.LittleEndian.Uint32(b[24:28]) el := int(binary.LittleEndian.Uint16(b[22:24])) if el <= 4 { el = 12 } ents := pEE(b, int(cn), el, it, lf) us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100 return &IP{pid, binary.LittleEndian.Uint32(b[8:12]), binary.LittleEndian.Uint32(b[12:16]), lf, cn, us, ents, it} } func pEE(b []byte, cn, el int, it byte, lf bool) []IE { var ents []IE for i := 0; i < cn; i++ { o := IDX_HDR + i*el if o+el > len(b) { break } entry := b[o : o+el] var ie IE switch it { case 2: ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8])) ie.P = binary.BigEndian.Uint32(entry[8:12]) case 1: ie.K = fmt.Sprintf("%d ~ %d", binary.BigEndian.Uint64(entry[:8]), binary.BigEndian.Uint64(entry[8:16])) ie.P = binary.BigEndian.Uint32(entry[16:20]) case 3: ie.K = strings.TrimRight(string(entry[:16]), "\x00") ie.DP = binary.BigEndian.Uint64(entry[16:24]) if lf { ie.P = binary.BigEndian.Uint32(entry[20:24]) } case 4: nk := (el - 8) / 8 parts := make([]string, nk) for j := 0; j < nk; j++ { parts[j] = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[j*8:])) } ie.K = strings.Join(parts, ", ") ie.DP = binary.BigEndian.Uint64(entry[nk*8:]) if lf { ie.P = binary.BigEndian.Uint32(entry[el-4:]) } case 5: if lf { kl := el - 8 if kl == 8 { ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8])) } else { ie.K = fmt.Sprintf("%x", entry[:kl]) } ie.DP = binary.BigEndian.Uint64(entry[kl:]) ie.P = binary.BigEndian.Uint32(entry[el-4:]) } else { kl := el - 4 if kl == 8 { ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8])) } else { ie.K = fmt.Sprintf("%x", entry[:kl]) } ie.P = binary.BigEndian.Uint32(entry[el-4:]) } default: ie.K = fmt.Sprintf("%x", entry[:el-4]) ie.P = binary.BigEndian.Uint32(entry[el-4:]) } ents = append(ents, ie) } return ents } func rBT(pid uint32, it byte) *IP { return rIP(pid, it) } // ===== Header + Heap + Free ===== func rHdrInfo() *HdrInfo { f, e := os.Open(fp) if e != nil { return nil } defer f.Close() b := make([]byte, 4096) f.ReadAt(b, 0) magic := string(b[:4]) ver := binary.LittleEndian.Uint32(b[4:8]) pgsz := binary.LittleEndian.Uint32(b[8:12]) nf := binary.LittleEndian.Uint32(b[12:16]) tp := binary.LittleEndian.Uint32(b[16:20]) tc := binary.LittleEndian.Uint32(b[20:24]) ct := int64(binary.LittleEndian.Uint64(b[24:32])) mt := int64(binary.LittleEndian.Uint64(b[32:40])) fs := binary.LittleEndian.Uint32(b[40:44]) fc := binary.LittleEndian.Uint32(b[44:48]) gf := binary.LittleEndian.Uint64(b[48:56]) cm := strings.TrimRight(string(b[312:824]), "\x00") return &HdrInfo{magic, ver, pgsz, nf, tp, tc, time.Unix(0, ct).Format("2006-01-02 15:04:05"), time.Unix(0, mt).Format("2006-01-02 15:04:05"), fs, fc, gf, cm} } func rHeapChain(hp0 uint32) *HeapChain { var pages []HeapPageInfo for pid := hp0; pid != 0; { b, e := rp(pid) if e != nil || b[0] != PG_HEAP { break } nx := binary.LittleEndian.Uint32(b[8:12]) pv := binary.LittleEndian.Uint32(b[12:16]) use := binary.LittleEndian.Uint32(b[16:20]) us := float64(use) / float64(PGSZ) * 100 pages = append(pages, HeapPageInfo{pid, nx, pv, use, us}) if nx == pid || nx == 0 { break } pid = nx } return &HeapChain{pages, len(pages)} } func rFreePages() *FreePages { f, e := os.Open(fp) if e != nil { return &FreePages{nil, 0} } defer f.Close() freeStart := binary.LittleEndian.Uint32(hdr[40:44]) var pages []uint32 for pid := freeStart; pid != 0; { pages = append(pages, pid) b := make([]byte, PGSZ) off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ) if _, e := f.ReadAt(b, off); e != nil { break } nx := binary.BigEndian.Uint32(b[:4]) if nx == pid || nx == 0 { break } pid = nx } return &FreePages{pages, len(pages)} } // ===== Page visualization ===== type PageItem struct { ID uint32 `json:"id"` Type int `json:"t"` TS string `json:"ts"` Us float64 `json:"us"` } type PagesResult struct { Pages []PageItem `json:"pages"` Total int `json:"total"` Counts map[string]int `json:"counts"` } func rPages() *PagesResult { fi, e := os.Stat(fp) if e != nil { return &PagesResult{} } totalPg := int(fi.Size() / PGSZ) if totalPg <= 1 { return &PagesResult{} } // page 0 is not used (header is separate), pages start from 1 numPages := totalPg - 1 // subtract header page if numPages <= 0 { return &PagesResult{} } f, e := os.Open(fp) if e != nil { return &PagesResult{} } defer f.Close() pages := make([]PageItem, 0, numPages) counts := map[string]int{"hdr": 0, "table": 0, "data": 0, "heap": 0, "imeta": 0, "idata": 0, "free": 0, "other": 0} typeNames := map[int]string{0: "free", 1: "table", 2: "data", 3: "heap", 4: "imeta", 5: "idata"} // Read free page list for marking freeSet := make(map[uint32]bool) freeStart := binary.LittleEndian.Uint32(hdr[40:44]) for pid := freeStart; pid != 0; { freeSet[pid] = true b := make([]byte, PGSZ) off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ) if _, e := f.ReadAt(b, off); e != nil { break } nx := binary.BigEndian.Uint32(b[:4]) if nx == pid || nx == 0 { break } pid = nx } for i := 1; i <= numPages; i++ { pid := uint32(i) b := make([]byte, PGSZ) off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ) if _, e := f.ReadAt(b, off); e != nil { break } pt := int(b[0]) us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100 if us > 100 { us = 100 } if freeSet[pid] { pt = 0 // mark as free } tn := typeNames[pt] if tn == "" { tn = "other" } counts[tn]++ pages = append(pages, PageItem{pid, pt, tn, us}) } return &PagesResult{pages, len(pages), counts} } func pagesH(w http.ResponseWriter, r *http.Request) { if fp == "" { jw(w, map[string]any{"e": "no db"}) return } jw(w, rPages()) } // findPkH 通过主键 B+ 树查找 PKID 对应的 DataPage func findPkH(w http.ResponseWriter, r *http.Request) { if fp == "" { jw(w, map[string]any{"e": "no db"}) return } pkStr := r.URL.Query().Get("pk") rootStr := r.URL.Query().Get("root") var pk, root uint32 fmt.Sscanf(pkStr, "%d", &pk) fmt.Sscanf(rootStr, "%d", &root) if root == 0 { jw(w, map[string]any{"e": "no root"}) return } dp := findDPByPK(root, uint64(pk)) if dp == 0 { jw(w, map[string]any{"e": "not found"}) return } jw(w, map[string]any{"dp": dp, "pk": pk}) } // findDPByPK 在主键 B+ 树中查找 PKID 对应的 DataPage ID func findDPByPK(rootPID uint32, targetPK uint64) uint32 { cur := rootPID for { b, e := rp(cur) if e != nil || b[0] != PG_IDATA { return 0 } lf := b[28] == 1 cn := int(binary.LittleEndian.Uint32(b[24:28])) el := int(binary.LittleEndian.Uint16(b[22:24])) if el <= 4 { el = 12 } if lf { // 叶子节点: 查找匹配的 PK for i := 0; i < cn; i++ { o := IDX_HDR + i*el if o+el > len(b) { break } key := binary.BigEndian.Uint64(b[o : o+8]) if key == targetPK { return binary.BigEndian.Uint32(b[o+8 : o+12]) } } return 0 } // 内部节点: 二分查找最后一个 key <= targetPK lo, hi := 0, cn-1 pos := 0 for lo <= hi { mid := (lo + hi) / 2 o := IDX_HDR + mid*el if o+8 > len(b) { break } key := binary.BigEndian.Uint64(b[o : o+8]) if key <= targetPK { pos = mid lo = mid + 1 } else { hi = mid - 1 } } o := IDX_HDR + pos*el childPID := binary.BigEndian.Uint32(b[o+el-4 : o+el]) if childPID == 0 { return 0 } cur = childPID } } // ===== Utility ===== func min(a, b int) int { if a < b { return a } return b } func fB(n int64) string { if n < 1024 { return fmt.Sprintf("%d B", n) } if n < 1048576 { return fmt.Sprintf("%.1f KB", float64(n)/1024) } if n < 1073741824 { return fmt.Sprintf("%.2f MB", float64(n)/1048576) } return fmt.Sprintf("%.2f GB", float64(n)/1073741824) } func fE(p string) bool { _, e := os.Stat(p); return e == nil } func jw(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(v) } // ===== HTTP Handlers ===== func homeH(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate") w.Write([]byte(pg)) } func ldH(w http.ResponseWriter, r *http.Request) { p := r.URL.Query().Get("path") if p == "" { p, _ = os.Getwd() } if !filepath.IsAbs(p) { c, _ := os.Getwd() p = filepath.Join(c, p) } es, e := os.ReadDir(p) if e != nil { jw(w, map[string]any{"err": e.Error()}) return } type F struct { N string `json:"n"` D bool `json:"d"` S int64 `json:"s"` } var ds, fs []F for _, x := range es { i, _ := x.Info() s := int64(0) if i != nil { s = i.Size() } if x.IsDir() { ds = append(ds, F{x.Name(), true, s}) } else { fs = append(fs, F{x.Name(), false, s}) } } jw(w, map[string]any{"p": p, "pp": filepath.Dir(p), "d": ds, "f": fs}) } func openH(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "no", 405) return } var q struct { P string `json:"p"` } json.NewDecoder(r.Body).Decode(&q) if !fE(q.P) { c, _ := os.Getwd() q2 := filepath.Join(c, q.P) if fE(q2) { q.P = q2 } else { jw(w, map[string]any{"ok": false, "e": "not found"}) return } } mu.Lock() fp = q.P rHdr() tabs = pTabs() mu.Unlock() fi, _ := os.Stat(fp) dbF = fp jw(w, map[string]any{"ok": true, "s": st(fi)}) } func st(fi os.FileInfo) map[string]any { s := int64(0) if fi != nil { s = fi.Size() } return map[string]any{"f": dbF, "s": s, "fs": fB(s), "pg": int(s / PGSZ), "tc": len(tabs)} } func stH(w http.ResponseWriter, r *http.Request) { if dbF == "" { jw(w, map[string]any{"e": "no db"}) return } fi, _ := os.Stat(fp) jw(w, st(fi)) } func taH(w http.ResponseWriter, r *http.Request) { jw(w, tabs) } func tdH(w http.ResponseWriter, r *http.Request) { n := r.URL.Path[len("/api/table/"):] for _, t := range tabs { if t.Name == n { jw(w, t) return } } jw(w, map[string]any{"e": "not found"}) } func dpH(w http.ResponseWriter, r *http.Request) { var pid uint32 fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid) d := rDP(pid) if d == nil { jw(w, map[string]any{"e": "err"}) return } jw(w, d) } func ipH(w http.ResponseWriter, r *http.Request) { var pid uint32 fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid) it, _ := strconv.Atoi(r.URL.Query().Get("t")) d := rIP(pid, byte(it)) if d == nil { jw(w, map[string]any{"e": "err"}) return } jw(w, d) } func btH(w http.ResponseWriter, r *http.Request) { var pid uint32 fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid) it, _ := strconv.Atoi(r.URL.Query().Get("t")) d := rBT(pid, byte(it)) if d == nil { jw(w, map[string]any{"e": "err"}) return } jw(w, d) } func hdrH(w http.ResponseWriter, r *http.Request) { d := rHdrInfo() if d == nil { jw(w, map[string]any{"e": "err"}) return } jw(w, d) } func heapH(w http.ResponseWriter, r *http.Request) { var hp0 uint32 fmt.Sscanf(r.URL.Query().Get("id"), "%d", &hp0) jw(w, rHeapChain(hp0)) } func freeH(w http.ResponseWriter, r *http.Request) { jw(w, rFreePages()) } var _ = unsafe.Sizeof(0)