@@ -10,6 +10,7 @@ import (
1010 "errors"
1111 "fmt"
1212 "io"
13+ "math"
1314 "os"
1415 "sort"
1516 "sync"
@@ -35,7 +36,26 @@ const (
3536
3637 iniChunksCap = 16
3738
38- completed = 1 // Ufest.Flags
39+ maxChunkSize = 5 * cos .GiB
40+ maxMetaKeys = 1000
41+ )
42+
43+ const (
44+ completed uint16 = 1 << 0 // Ufest.Flags
45+ )
46+
47+ // on-disk xattr
48+ const (
49+ xattrChunk = "user.ais.chunk"
50+
51+ xattrChunkDflt = memsys .DefaultBufSize
52+ xattrChunkMax = memsys .DefaultBuf2Size // NOTE: 64K hard limit
53+ )
54+
55+ const (
56+ utag = "chunk-manifest"
57+ itag = "invalid " + utag
58+ tooShort = "failed to unpack: too short"
3959)
4060
4161type (
@@ -47,13 +67,14 @@ type (
4767 Flags uint16 // bit flags (future use)
4868 MD5 string // S3/legacy
4969 }
50- Ufest struct {
51- ID string // upload/manifest ID
52- Created time.Time // creation time
53- Num uint16 // number of chunks (so far)
54- Flags uint16 // bit flags { completed, ...}
55- Chunks []Uchunk
56- Metadata map [string ]string // remote object metadata
70+ mdpair struct { k , v string }
71+ Ufest struct {
72+ ID string // upload/manifest ID
73+ Created time.Time // creation time
74+ Num uint16 // number of chunks (so far)
75+ Flags uint16 // bit flags { completed, ...}
76+ Chunks []Uchunk
77+ mdmap map [string ]string // remote object metadata (gets sorted when packing)
5778
5879 // runtime state
5980 Lom * LOM
@@ -63,20 +84,6 @@ type (
6384 }
6485)
6586
66- // on-disk xattr
67- const (
68- xattrChunk = "user.ais.chunk"
69-
70- xattrChunkDflt = memsys .DefaultBufSize
71- xattrChunkMax = memsys .DefaultBuf2Size // maximum 64K
72- )
73-
74- const (
75- utag = "chunk-manifest"
76- itag = "invalid " + utag
77- tooShort = "failed to unpack: too short"
78- )
79-
8087func NewUfest (id string , lom * LOM ) * Ufest {
8188 startTime := time .Now ()
8289 if id == "" {
@@ -93,10 +100,32 @@ func NewUfest(id string, lom *LOM) *Ufest {
93100
94101func (u * Ufest ) Completed () bool { return u .Flags & completed == completed }
95102
103+ // NOTE:
104+ // - GetMeta() returns a reference that callers must not mutate
105+ // - alternatively, clone it and call SetMeta
106+ // - on-disk order is deterministic (sorted at pack time)
107+ func (u * Ufest ) SetMeta (md map [string ]string ) error {
108+ if l := len (md ); l > maxMetaKeys {
109+ return fmt .Errorf ("%s: number of metadata entries %d exceeds %d limit" , utag , l , maxMetaKeys )
110+ }
111+ u .mdmap = md
112+ return nil
113+ }
114+ func (u * Ufest ) GetMeta () map [string ]string { return u .mdmap }
115+
96116func (u * Ufest ) Lock () { u .mu .Lock () }
97117func (u * Ufest ) Unlock () { u .mu .Unlock () }
98118
99- func (u * Ufest ) Add (c * Uchunk ) error {
119+ func (u * Ufest ) Add (c * Uchunk , size , num int64 ) error {
120+ if size > maxChunkSize {
121+ return fmt .Errorf ("%s [add] chunk size %d exceeds %d limit" , utag , size , maxChunkSize )
122+ }
123+ c .Siz = size
124+ if num > math .MaxUint16 || len (u .Chunks ) >= math .MaxUint16 {
125+ return fmt .Errorf ("%s [add] chunk number (%d, %d) exceeds %d limit" , utag , num , len (u .Chunks ), math .MaxUint16 )
126+ }
127+ c .Num = uint16 (num )
128+
100129 u .mu .Lock ()
101130 defer u .mu .Unlock ()
102131
@@ -115,7 +144,7 @@ func (u *Ufest) Add(c *Uchunk) error {
115144 dup := & u .Chunks [idx ]
116145 if idx < l && dup .Num == c .Num {
117146 if err := cos .RemoveFile (dup .Path ); err != nil {
118- return fmt .Errorf ("failed to replace chunk [%d, %s]: %v" , c .Num , dup .Path , err )
147+ return fmt .Errorf ("%s [add] failed to replace chunk [%d, %s]: %v" , utag , c .Num , dup .Path , err )
119148 }
120149 u .Size += c .Siz - dup .Siz
121150 * dup = * c
@@ -164,7 +193,7 @@ func (u *Ufest) Abort(lom *LOM) error {
164193func (u * Ufest ) ChunkName (num int ) (string , error ) {
165194 lom := u .Lom
166195 if lom == nil {
167- return "" , errors .New (" nil lom" )
196+ return "" , errors .New (utag + ": nil lom" )
168197 }
169198 if num <= 0 {
170199 return "" , fmt .Errorf ("%s: invalid chunk number (%d)" , _utag (lom .Cname ()), num )
@@ -293,7 +322,9 @@ func (u *Ufest) Store(lom *LOM) error {
293322 }
294323
295324 // write
296- if err := lom .setXchunk (sgl .Bytes ()); err != nil {
325+ b := sgl .Bytes ()
326+ if err := lom .setXchunk (b ); err != nil {
327+ u .Flags &^= completed
297328 return err
298329 }
299330 lom .md .lid .setlmfl (lmflChunk ) // TODO -- FIXME: persist
@@ -340,12 +371,21 @@ func (u *Ufest) pack(w io.Writer) {
340371 binary .BigEndian .PutUint16 (b16 [:], u .Flags )
341372 w .Write (b16 [:])
342373
343- // metadata map
344- binary .BigEndian .PutUint16 (b16 [:], uint16 (len (u .Metadata )))
374+ // metadata
375+ l := len (u .mdmap )
376+ binary .BigEndian .PutUint16 (b16 [:], uint16 (l ))
345377 w .Write (b16 [:])
346- for k , v := range u .Metadata {
347- _packStr (w , k )
348- _packStr (w , v )
378+ if l > 0 {
379+ mdslice := make ([]mdpair , 0 , l )
380+ for k , v := range u .mdmap {
381+ mdslice = append (mdslice , mdpair {k , v })
382+ }
383+ sort .Slice (mdslice , func (i , j int ) bool { return mdslice [i ].k < mdslice [j ].k }) // deterministic
384+ for _ , kv := range mdslice {
385+ _packStr (w , kv .k )
386+ _packStr (w , kv .v )
387+ }
388+ clear (mdslice )
349389 }
350390
351391 // chunks
@@ -435,7 +475,7 @@ func (u *Ufest) unpack(data []byte) (err error) {
435475 offset += cos .SizeofI16
436476
437477 if metaCount > 0 {
438- u .Metadata = make (map [string ]string , metaCount )
478+ u .mdmap = make (map [string ]string , metaCount )
439479 for range metaCount {
440480 var k , v string
441481 if k , offset , err = _unpackStr (data , offset ); err != nil {
@@ -444,7 +484,7 @@ func (u *Ufest) unpack(data []byte) (err error) {
444484 if v , offset , err = _unpackStr (data , offset ); err != nil {
445485 return err
446486 }
447- u .Metadata [k ] = v
487+ u .mdmap [k ] = v
448488 }
449489 }
450490
0 commit comments