package store import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "strings" ) type Store interface { CreatePartition(ctx context.Context, name string) error // Blob ?? WriteBlob(ctx context.Context, digest string, r io.Reader) error ReadBlob(ctx context.Context, digest string) (io.ReadCloser, error) BlobExists(ctx context.Context, digest string) (bool, error) GetBlobSize(ctx context.Context, digest string) (int64, error) // Manifest ?? WriteManifest(ctx context.Context, digest string, content []byte) error ReadManifest(ctx context.Context, digest string) ([]byte, error) ManifestExists(ctx context.Context, digest string) (bool, error) // Upload ?? CreateUpload(ctx context.Context, uuid string) (io.WriteCloser, error) AppendUpload(ctx context.Context, uuid string, r io.Reader) (int64, error) GetUploadSize(ctx context.Context, uuid string) (int64, error) FinalizeUpload(ctx context.Context, uuid string, digest string) error DeleteUpload(ctx context.Context, uuid string) error } type fileStore struct { baseDir string } var ( Default Store ) func Init(ctx context.Context, dataDir string) error { Default = &fileStore{ baseDir: dataDir, } return nil } func (s *fileStore) CreatePartition(ctx context.Context, name string) error { dirs := []string{ filepath.Join(s.baseDir, name, "blobs"), filepath.Join(s.baseDir, name, "manifests"), filepath.Join(s.baseDir, name, "uploads"), } for _, dir := range dirs { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create directory %s: %w", dir, err) } } return nil } // blobPath ?? digest ?? blob ???? // ??: blobs/sha256/abc/def.../digest func (s *fileStore) blobPath(digest string) (string, error) { // ?? digest???: sha256:abc123... parts := strings.SplitN(digest, ":", 2) if len(parts) != 2 { return "", fmt.Errorf("invalid digest format: %s", digest) } algo := parts[0] hash := parts[1] if algo != "sha256" { return "", fmt.Errorf("unsupported digest algorithm: %s", algo) } // ??? 2 ????????????? 2 ????????? if len(hash) < 4 { return "", fmt.Errorf("invalid hash length: %s", hash) } path := filepath.Join(s.baseDir, "registry", "blobs", algo, hash[:2], hash[2:4], hash) return path, nil } func (s *fileStore) WriteBlob(ctx context.Context, digest string, r io.Reader) error { path, err := s.blobPath(digest) if err != nil { return err } // ??????? if _, err := os.Stat(path); err == nil { return nil // ???????? } // ???? if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return fmt.Errorf("failed to create blob directory: %w", err) } // ???? f, err := os.Create(path) if err != nil { return fmt.Errorf("failed to create blob file: %w", err) } defer f.Close() // ???? digest ?? hasher := sha256.New() tee := io.TeeReader(r, hasher) if _, err := io.Copy(f, tee); err != nil { os.Remove(path) return fmt.Errorf("failed to write blob: %w", err) } // ?? digest calculated := "sha256:" + hex.EncodeToString(hasher.Sum(nil)) if calculated != digest { os.Remove(path) return fmt.Errorf("digest mismatch: expected %s, got %s", digest, calculated) } return nil } func (s *fileStore) ReadBlob(ctx context.Context, digest string) (io.ReadCloser, error) { path, err := s.blobPath(digest) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("blob not found: %w", err) } return f, nil } func (s *fileStore) BlobExists(ctx context.Context, digest string) (bool, error) { path, err := s.blobPath(digest) if err != nil { return false, err } _, err = os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func (s *fileStore) GetBlobSize(ctx context.Context, digest string) (int64, error) { path, err := s.blobPath(digest) if err != nil { return 0, err } info, err := os.Stat(path) if err != nil { return 0, err } return info.Size(), nil } // manifestPath ?? digest ?? manifest ???? func (s *fileStore) manifestPath(digest string) (string, error) { parts := strings.SplitN(digest, ":", 2) if len(parts) != 2 { return "", fmt.Errorf("invalid digest format: %s", digest) } algo := parts[0] hash := parts[1] if algo != "sha256" { return "", fmt.Errorf("unsupported digest algorithm: %s", algo) } path := filepath.Join(s.baseDir, "registry", "manifests", algo, hash[:2], hash[2:4], hash) return path, nil } func (s *fileStore) WriteManifest(ctx context.Context, digest string, content []byte) error { path, err := s.manifestPath(digest) if err != nil { return err } // ???? if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return fmt.Errorf("failed to create manifest directory: %w", err) } // ?? digest hasher := sha256.New() hasher.Write(content) calculated := "sha256:" + hex.EncodeToString(hasher.Sum(nil)) if calculated != digest { return fmt.Errorf("digest mismatch: expected %s, got %s", digest, calculated) } // ???? if err := os.WriteFile(path, content, 0644); err != nil { return fmt.Errorf("failed to write manifest: %w", err) } return nil } func (s *fileStore) ReadManifest(ctx context.Context, digest string) ([]byte, error) { path, err := s.manifestPath(digest) if err != nil { return nil, err } content, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("manifest not found: %w", err) } return content, nil } func (s *fileStore) ManifestExists(ctx context.Context, digest string) (bool, error) { path, err := s.manifestPath(digest) if err != nil { return false, err } _, err = os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } // uploadPath ?????????? func (s *fileStore) uploadPath(uuid string) string { return filepath.Join(s.baseDir, "registry", "uploads", uuid) } func (s *fileStore) CreateUpload(ctx context.Context, uuid string) (io.WriteCloser, error) { path := s.uploadPath(uuid) // ???? if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return nil, fmt.Errorf("failed to create upload directory: %w", err) } f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("failed to create upload file: %w", err) } return f, nil } func (s *fileStore) AppendUpload(ctx context.Context, uuid string, r io.Reader) (int64, error) { path := s.uploadPath(uuid) f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return 0, fmt.Errorf("failed to open upload file: %w", err) } defer f.Close() n, err := io.Copy(f, r) if err != nil { return 0, fmt.Errorf("failed to write to upload: %w", err) } return n, nil } func (s *fileStore) GetUploadSize(ctx context.Context, uuid string) (int64, error) { path := s.uploadPath(uuid) info, err := os.Stat(path) if err != nil { return 0, err } return info.Size(), nil } func (s *fileStore) FinalizeUpload(ctx context.Context, uuid string, digest string) error { uploadPath := s.uploadPath(uuid) blobPath, err := s.blobPath(digest) if err != nil { return err } // ???? blob ???????????? if _, err := os.Stat(blobPath); err == nil { os.Remove(uploadPath) return nil } // ?????? if err := os.MkdirAll(filepath.Dir(blobPath), 0755); err != nil { return fmt.Errorf("failed to create blob directory: %w", err) } // ?? digest f, err := os.Open(uploadPath) if err != nil { return fmt.Errorf("failed to open upload file: %w", err) } defer f.Close() hasher := sha256.New() if _, err := io.Copy(hasher, f); err != nil { return fmt.Errorf("failed to calculate digest: %w", err) } calculated := "sha256:" + hex.EncodeToString(hasher.Sum(nil)) if calculated != digest { return fmt.Errorf("digest mismatch: expected %s, got %s", digest, calculated) } // ???? if err := os.Rename(uploadPath, blobPath); err != nil { return fmt.Errorf("failed to finalize upload: %w", err) } return nil } func (s *fileStore) DeleteUpload(ctx context.Context, uuid string) error { path := s.uploadPath(uuid) return os.Remove(path) }