package meta import ( "bytes" "errors" "fmt" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.etcd.io/bbolt" ) // bucket name for locked objects. var bucketNameLocked = []byte(invalidBase58String + "Locked") // suffix for container buckets with objects of type LOCK. const bucketNameSuffixLockers = invalidBase58String + "LOCKER" // returns name of the bucket with objects of type LOCK for specified container. func bucketNameLockers(idCnr cid.ID) []byte { return []byte(idCnr.String() + bucketNameSuffixLockers) } // ErrLockIrregularObject is returned when trying to lock an irregular object. var ErrLockIrregularObject = errors.New("locking irregular object") // Lock marks objects as locked with another object. All objects are from the // specified container. // // Allows locking regular objects only (otherwise returns ErrLockIrregularObject). // // Locked list should be unique. Panics if it is empty. func (db *DB) Lock(cnr cid.ID, locker oid.ID, locked []oid.ID) error { if len(locked) == 0 { panic("empty locked list") } return db.boltDB.Update(func(tx *bbolt.Tx) error { // check if all objects are regular bucketKeysLocked := make([][]byte, len(locked)) for i := range locked { bucketKeysLocked[i] = objectKey(&locked[i]) } if firstIrregularObjectType(tx, cnr, bucketKeysLocked...) != object.TypeRegular { return ErrLockIrregularObject } bucketLocked, err := tx.CreateBucketIfNotExists(bucketNameLocked) if err != nil { return fmt.Errorf("create global bucket for locked objects: %w", err) } bucketLockedContainer, err := bucketLocked.CreateBucketIfNotExists([]byte(cnr.String())) if err != nil { return fmt.Errorf("create container bucket for locked objects %v: %w", cnr, err) } keyLocker := objectKey(&locker) var exLockers [][]byte var updLockers []byte loop: for i := range bucketKeysLocked { // decode list of already existing lockers exLockers, err = decodeList(bucketLockedContainer.Get(bucketKeysLocked[i])) if err != nil { return fmt.Errorf("decode list of object lockers: %w", err) } for i := range exLockers { if bytes.Equal(exLockers[i], keyLocker) { continue loop } } // update the list of lockers if exLockers == nil { updLockers = keyLocker } else { updLockers, err = encodeList(append(exLockers, keyLocker)) if err != nil { // maybe continue for the best effort? return fmt.Errorf("encode list of object lockers: %w", err) } } // write updated list of lockers err = bucketLockedContainer.Put(bucketKeysLocked[i], updLockers) if err != nil { return fmt.Errorf("update list of object lockers: %w", err) } } return nil }) } // checks if specified object is locked in the specified container. func objectLocked(tx *bbolt.Tx, idCnr cid.ID, idObj oid.ID) bool { bucketLocked := tx.Bucket(bucketNameLocked) if bucketLocked != nil { bucketLockedContainer := bucketLocked.Bucket([]byte(idCnr.String())) if bucketLockedContainer != nil { return bucketLockedContainer.Get(objectKey(&idObj)) != nil } } return false }