record

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package record writes session streams in asciicast v2 format.

Format — ilk satır başlık, sonrası olay satırları:

{"version":2,"width":80,"height":24,"timestamp":1723465329,"env":{"TERM":"xterm-256color"}}
[0.248848,"o","�[?1034h"]
[2.143881,"i","ls\r"]
[3.002100,"r","120x30"]

Kayıt için terminal emülasyonu GEREKMEZ (Ek C.7): bayt akışını olduğu gibi saklıyoruz, emülasyonu oynatıcı yapıyor.

⚠️ SADAKATİN SINIRI: asciicast v2 veriyi JSON DİZESİ olarak saklar ve JSON dizesi rastgele bayt tutamaz. encoding/json geçersiz her UTF-8 baytını U+FFFD (replacement character) ile değiştirir. Yani hedefte `cat /bin/ls` çalıştıran bir oturum BİREBİR kaydedilmez.

Bu formatın seçilmesinin bedeli ve bilerek kabul ediliyor: kayıtların amacı bir insanın oturumu izlemesi, ikili veriyi yeniden üretmek değil. Ama "ham baytları saklıyoruz" demek yanlış olurdu ve bir olay incelemesinde yanlış beklenti üretirdi. (Ölçüldü: TestWriterReplacesInvalidUTF8Bytes bu davranışı sabitliyor.)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotRecorded: oturumun kaydı hiç olmamış (recording_path boş).
	ErrNotRecorded = errors.New("record: session has no recording")

	// ErrOutsideRoot: saklanan yol kayıt kökünün DIŞINDA.
	//
	// Bu bir "bulunamadı" değil, bir REDDETME: kökün dışını gösteren bir
	// yol ya recording.dir değişmiş demektir ya da veritabanına oraya ait
	// olmayan bir değer girmiş demektir. İkisi de sessizce dosya
	// açılacak durumlar değil.
	ErrOutsideRoot = errors.New("record: stored path is outside the recordings root")
)

Kayıt okuma yolundaki hatalar.

View Source
var ErrDiskLow = errors.New("record: not enough free space for a new recording")

ErrDiskLow, kayıt için ayrılan yerde eşiğin altında boş alan kaldı.

Functions

func FreeSpace

func FreeSpace(dir string) (uint64, error)

* FreeSpace, kayıt dizininin bulunduğu dosya sisteminde kalan bayt. * * ⚠️ AYRILMIŞ BLOKLAR SAYILMIYOR (Bavail, Bfree değil). root olmayan * bir süreç ayrılmış bloklara yazamıyor; onları "boş" saymak, gerçekte * dolmuş bir diskte hâlâ yer var demek olurdu.

func NewSessionID

func NewSessionID() (string, error)

NewSessionID returns a random, filesystem-safe session identifier.

func Usage

func Usage(dir string) (files int, bytes int64, skipped int, err error)

* Usage, kayıt dizininin toplam boyutu ve dosya sayısı. * * ⚠️ GÖRÜNÜRLÜK BUDAMADAN ÖNCE GELİR. Operatör ne kadar yer * kapladığını göremeden bir saklama süresi seçemez — ve göremediği * için de seçmez. Varsayılanı "hiç silme" olan bir ayarın işe * yaraması, bu sayının bir yerde yazmasına bağlı.

Types

type Archived

type Archived interface {
	// ArchivedIDs, verilenlerden hangilerinin DOĞRULANMIŞ şekilde
	// arşivlendiğini döner. Kümede olmayan her kimlik "silinemez".
	ArchivedIDs(ctx context.Context, ids []string) (map[string]bool, error)
}

* Prune, cutoff'tan ESKİ kayıtları siler. * * ⚠️ DEĞİŞTİRİLME ZAMANINA BAKIYOR, dizin adına değil. Dizin adı * (2026-08-31) kaydın AÇILDIĞI günü söylüyor; uzun süren bir oturum * ertesi güne sarkabiliyor. Ada bakan bir budayıcı, ertesi gün hâlâ * yazılan o dosyayı yaşlı sanıp silerdi. * * ⚠️ AMA mtime YALNIZCA AKTİF YAZILANI KORUR. Çıktı üretmeyen boşta * bir oturumun mtime'ı da eskir; onu openIDs (açık oturum kümesi) * koruyor, mtime değil. * * ⚠️ SIFIR SÜRE HİÇBİR ŞEY SİLMİYOR. Bu fonksiyonun yanlış çağrılması * bütün denetim kaydını silmek demek; "ayar verilmemiş" hâlinin * "hepsini sil" olarak okunması, olabilecek en pahalı sıfır değeri * olurdu.

* Archived, "bu kayıtlar başka bir yerde güvende mi" sorusunu soran * taraf. Uygulaması internal/archive'da; burada yalnızca ARAYÜZ var. * * ⚠️ ARAYÜZ TÜKETİCİ TARAFINDA TANIMLI, ÇÜNKÜ record PAKETİ HİÇBİR * PROJE PAKETİNİ IMPORT ETMİYOR — yalnızca standart kütüphane. Buraya * store'u sokmak, kayıt yazma yolunu veritabanına bağımlı hâle * getirirdi; oysa o yolun tek işi diske yazmak ve hiçbir dış sisteme * bağlı olmaması, "kayıt tutulamıyorsa oturum reddedilir" kuralının * anlamlı kalmasının şartı.

type PruneResult

type PruneResult struct {
	Files int
	Bytes int64
	// Dirs, tamamen boşalıp kaldırılan gün dizinleri.
	Dirs int

	/*
	 * ⚠️ TUTULANLAR DA SAYILIYOR, ve gerekçesi silinenler kadar önemli.
	 *
	 * Arşivlenmemiş bir kayıt saklama süresi dolsa bile silinmiyor.
	 * Doğru davranış bu — ama görünmezse, disk yavaşça doluyor ve
	 * operatör bir gün "oturumlar reddediliyor" diye uyanıyor. Sayılar
	 * her koşuda loglanıyor ki sıkışma günler önce görünsün.
	 */
	KeptUnarchived int
	KeptBytes      int64

	// Unknown, adından oturum kimliği çıkmayan dosyalar: yetim
	// kayıtlar ya da dizine düşmüş yabancılar. Silinmiyor, sayılıyor.
	Unknown int

	// Deleted, silinen oturumların kimlikleri. Çağıran bunları denetim
	// defterine yazıyor: kanıtın kaybolması da bir olay.
	Deleted []string
}

PruneResult, bir budama koşusunun sonucu.

func Prune

func Prune(ctx context.Context, dir string, keepFor time.Duration, now time.Time,
	archived Archived, openIDs map[string]bool) (PruneResult, error)

* Prune, saklama süresi dolan kayıtları siler. * * archived nil ise arşivleme kapalıdır ve davranış eskisiyle aynı: * yaşı geçen dosya silinir. * * ⚠️ archived DOLUYSA KAPI VARSAYILAN OLARAK KAPALI. Yalnızca * "evet, doğrulanmış şekilde arşivlendi" cevabı silmeye izin veriyor. * Sorgu hata verirse KOŞU İPTAL EDİLİYOR — hiçbir şey silinmeden. * Bu, bu dosyadaki diğer hata davranışlarının TERSİ (CheckSpace * ölçemediğinde nil dönüyor, dizin okunamadığında continue ediliyor) * ve fark bilinçli: orada bedel bir oturumun reddedilmemesi, burada * bedel denetim kanıtının yok olması.

type Pruner

type Pruner struct {
	// contains filtered or unexported fields
}

Pruner, eski kayıtları periyodik olarak siler.

func NewPruner

func NewPruner(dir string, keepFor time.Duration, logger *slog.Logger) *Pruner

NewPruner, kapalıysa nil döner — çağıranın ayrıca kontrol etmesi gerekmesin.

func (*Pruner) RunOnce

func (p *Pruner) RunOnce(ctx context.Context) PruneResult

RunOnce, tek bir budama koşusu.

func (*Pruner) Start

func (p *Pruner) Start(ctx context.Context)

* Start, döngüyü çalıştırır. ctx bitene kadar dönmüyor. * * ⚠️ AÇILIŞTA HEMEN BİR KOŞU YAPMIYOR. Bir saat beklemek, yanlış * yapılandırılmış bir sürenin (ör. "1h" yerine "1s") ilk saniyede * bütün arşivi silmesine karşı verilmiş bir pencere: operatör * açılıştaki log satırını görüp durdurabiliyor.

func (*Pruner) WithArchive

func (p *Pruner) WithArchive(a Archived) *Pruner

* WithArchive, budayıcıya arşiv kapısını takar. * * ⚠️ Bunu takmadan arşivleme açmak, henüz yüklenmemiş kayıtların * silinmesi demek. serve.go ikisini BİRLİKTE kuruyor; ayrı ayrı * yapılandırılabilir olsalardı biri unutulabilirdi. * * ⚠️ SİLME KAYDINA DOKUNMUYOR: o artık WithAuditLog ile, arşivlemeden * BAĞIMSIZ olarak takılıyor. Denetim yazımını bu kapıya bağlamak, tam * da arşivleme kapalıyken (varsayılan) sessizce kanıt silinmesine yol * açıyordu.

func (*Pruner) WithAuditLog

func (p *Pruner) WithAuditLog(onDeleted func(context.Context, []string)) *Pruner

* WithAuditLog, silinen kayıtların denetim defterine yazılmasını sağlar * — arşivleme AÇIK OLMASA DA. * * ⚠️ NEDEN AYRI: silme geri çağrısı eskiden yalnızca WithArchive ile, * yani arşivleyici varsa takılıyordu. Arşivleme varsayılan KAPALI * olduğu için sıradan kurulumda budayıcı kanıt siliyor ve admin_log'a * hiçbir şey yazmıyordu — panel ise kayıp bir kaydın sebebini "admin * log söyler" diye anlatıyor. Denetim yazımı arşivlemeden bağımsız * olmalı.

func (*Pruner) WithOpenSessions

func (p *Pruner) WithOpenSessions(open func() map[string]bool) *Pruner

WithOpenSessions, açık oturumları veren kaynağı takar (canlı oturum defteri). Prune bu kümedeki kayıtları silmiyor.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store owns the on-disk layout of recordings.

func NewStore

func NewStore(dir string) (*Store, error)

NewStore prepares the recordings root.

func (*Store) CheckSpace

func (s *Store) CheckSpace(minFree uint64) error

* CheckSpace, yeni bir kayıt açmadan önce yer var mı diye bakar. * * ⚠️ SIFIR EŞİK "KAPALI" DEMEK, "her zaman reddet" değil. Ayarın * yokluğu bir kapıyı kapatmamalı. * * ⚠️ ÖLÇÜM YAPILAMIYORSA GEÇİYORUZ. Statfs'in başarısız olması, * diskin dolu olduğunu göstermez — dosya sistemi hakkında hiçbir şey * bilmediğimizi gösterir. Bilmemeyi "dolu" saymak, ölçüm hatasını * kesintiye çevirirdi; asıl koruma zaten yazmanın kendisinin * başarısız olması.

func (*Store) Create

func (s *Store) Create(sessionID string) (*os.File, string, error)

Create opens a new .cast file for sessionID and returns it with its path.

func (*Store) Open

func (s *Store) Open(sessionID, storedPath string) (*os.File, error)

Open, bir oturumun kayıt dosyasını okumak için açar.

⚠️ NEDEN storedPath'i DOĞRUDAN os.Open'a VERMİYORUZ: sessions. recording_path bir VERİTABANI SÜTUNU. Bir veritabanı değerini dosya yolu olarak kullanmak, veritabanına yazabilen her yolu (başka bir yerdeki enjeksiyon, operatörün elle UPDATE'i, geri yüklenen bir dump, ileride eklenecek bir içe aktarma özelliği) yetkili bir admin oturumu üzerinden KEYFİ DOSYA OKUMAYA çevirir. Bariz hedef ca.key_file.

Bu yüzden iki bağımsız kontrol var: sessionID kimlik doğrulaması (Create ile aynı desen) ve yolun kayıt kökünün altında kaldığının ispatı. İkisi de geçmeden dosya açılmıyor.

func (*Store) Root

func (s *Store) Root() string

Root, kayıt kökünü döner (teşhis ve log için).

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer serializes a session into asciicast v2.

⚠️ Eşzamanlılık: Output ve Input FARKLI goroutine'lerden gelir (broker'ın beş akışını hatırla). mu olmadan iki olay satırı iç içe geçer ve dosya bozulur.

KİLİT DİSİPLİNİ — üç katman:

dış  (Output/Input/Resize/Close)  → mu'yu BURADA al
orta (writeStream/flush)          → mu tutuluyor varsayar
alt  (writeEvent)                 → mu tutuluyor varsayar

sync.Mutex reentrant DEĞİLDİR: iç katmanlar kilit alsaydı Close→flush→ writeEvent zinciri kendi kendini kilitlerdi. Tek giriş noktası dış katman olduğu için disiplin tek yerde toplanıyor.

func NewWriter

func NewWriter(w io.WriteCloser, width, height int, env map[string]string) (*Writer, error)

NewWriter writes the asciicast header to w and starts the clock.

func (*Writer) Close

func (w *Writer) Close() (err error)

Close flushes any buffered bytes and closes the underlying writer.

func (*Writer) Err

func (w *Writer) Err() error

func (*Writer) Input

func (w *Writer) Input(b []byte) error

Input records user→target bytes as an "i" event.

⚠️ Girdi kaydı VARSAYILAN KAPALI olmalı (config record_input: false): kullanıcının yazdığı her şey girdidir — sudo parolası dahil. Bu metod yalnızca açıkça istendiğinde çağrılır; kararı çağıran verir.

func (*Writer) InputStream

func (w *Writer) InputStream() io.Writer

InputStream is the "i" counterpart of OutputStream.

func (*Writer) OnFailure

func (w *Writer) OnFailure(fn func(error))

OnFailure, kayıt yazımı ilk kez başarısız olduğunda çağrılacak fonksiyonu kurar.

⚠️ NEDEN VAR: bu kanca eklenene kadar oturum ortasındaki yazma hataları YUTULUYORDU — akış devam ediyor, oturum kayıtsız sürüyor ve hatanın tek izi kapanışta bir log satırı oluyordu. Açılışta "kayıt açılamazsa oturum reddedilir" (proxy.Open ErrUnavailable döner) denirken, açıldıktan sonra aynı arıza sessizce kabul ediliyordu: politika yarım uygulanıyordu.

Bir saldırgan diski doldurarak ya da kayıt dosyasını kaldırarak KENDİ oturumunu ve o an açık DİĞER oturumları kayıtsız hâle getirebiliyordu — ürünü kayıt olan bir sistemde bu, denetimi kapatmanın yolu demek.

Dinlemeye başlamadan ÖNCE çağrılmalı.

func (*Writer) Output

func (w *Writer) Output(b []byte) error

Output records target→user bytes as an "o" event.

func (*Writer) OutputStream

func (w *Writer) OutputStream() io.Writer

OutputStream returns an io.Writer recording everything as "o" events.

func (*Writer) Resize

func (w *Writer) Resize(cols, rows int) error

* Resize records a terminal size change as an "r" event ("120x30"). * * ⚠️ HATASI KAYIT ANAHTARINA BAĞLI — VE DEĞİLDİ. noteFailure yalnızca * akış sarmalayıcılarından (OutputWriter/InputWriter) çağrılıyordu; * Resize bir Writer metodu ve hatası hiçbir zaman oraya ulaşmıyordu. * Sonuç, "kaydedilemeyen oturum geçmez" kuralında küçük ama gerçek bir * delik: pencere boyutu diske yazılamıyorken oturum akmaya devam * ediyor, kayıt sessizce eksiliyor ve broker yalnızca log'a bir satır * bırakıyordu. * * ⚠️ noteFailure KİLİDİN DIŞINDA çağrılıyor: kendisi w.mu'yu alıyor ve * `defer w.mu.Unlock()` altında çağırmak kilitlenme olurdu.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL