go-hep.org/x/hep@v0.38.1/xrootd/xrdproto/dirlist/dirlist.go (about) 1 // Copyright ©2018 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package dirlist contains the structures describing request and response 6 // for dirlist request used to obtain the contents of a directory. 7 package dirlist // import "go-hep.org/x/hep/xrootd/xrdproto/dirlist" 8 9 import ( 10 "bytes" 11 "errors" 12 "fmt" 13 14 "go-hep.org/x/hep/xrootd/internal/xrdenc" 15 "go-hep.org/x/hep/xrootd/xrdfs" 16 "go-hep.org/x/hep/xrootd/xrdproto" 17 ) 18 19 // RequestID is the id of the request, it is sent as part of message. 20 // See xrootd protocol specification for details: http://xrootd.org/doc/dev45/XRdv310.pdf, 2.3 Client Request Format. 21 const RequestID uint16 = 3004 22 23 // Response is a response for the dirlist request, 24 // which contains a slice of entries containing the entry name and the entry stat info, 25 // and a WithStatInfo flag indicating whether a request asked for a stat info. 26 type Response struct { 27 Entries []xrdfs.EntryStat 28 WithStatInfo bool 29 } 30 31 // RespID implements xrdproto.Response.RespID. 32 func (resp *Response) RespID() uint16 { return RequestID } 33 34 // MarshalXrd implements xrdproto.Marshaler. 35 func (o Response) MarshalXrd(wBuffer *xrdenc.WBuffer) error { 36 entries := o.Entries 37 if o.WithStatInfo { 38 firstEntry := []xrdfs.EntryStat{ 39 {EntryName: ".", HasStatInfo: true}, 40 } 41 entries = append(firstEntry, entries...) 42 } 43 44 consistent := true 45 for i := range entries { 46 if entries[i].HasStatInfo != o.WithStatInfo { 47 consistent = false 48 } 49 } 50 51 if !consistent { 52 // TODO: keep this error or remove it? 53 return errors.New("xrootd: all entries of dirlist.Response should either have stat info or not") 54 } 55 56 for i := range entries { 57 nameSeparator := "\n" 58 statInfoSeparator := "\n" 59 if i == len(entries)-1 { 60 if o.WithStatInfo { 61 statInfoSeparator = "\x00" 62 } else { 63 nameSeparator = "\x00" 64 } 65 } 66 67 wBuffer.WriteBytes([]byte(entries[i].EntryName + nameSeparator)) 68 if !o.WithStatInfo { 69 continue 70 } 71 72 if err := entries[i].MarshalXrd(wBuffer); err != nil { 73 return err 74 } 75 76 wBuffer.WriteBytes([]byte(statInfoSeparator)) 77 } 78 79 return nil 80 } 81 82 // UnmarshalXrd implements xrdproto.Unmarshaler 83 // When stat information is supported by the server, the format is 84 // 85 // ".\n" 86 // "0 0 0 0\n" 87 // "dirname\n" 88 // "id size flags modtime\n" 89 // ... 90 // 0 91 // 92 // Otherwise, the format is the following: 93 // 94 // "dirname\n" 95 // ... 96 // 0 97 // 98 // See xrootd protocol specification, page 45 for further details. 99 func (o *Response) UnmarshalXrd(rBuffer *xrdenc.RBuffer) error { 100 if rBuffer.Len() == 0 { 101 return nil 102 } 103 104 data := bytes.TrimRight(rBuffer.Bytes(), "\x00") 105 lines := bytes.Split(data, []byte{'\n'}) 106 107 // FIXME(sbinet): drop the extra call to bytes.Equal when 108 // https://github.com/xrootd/xrootd/issues/739 109 // is fixed or clarified. 110 if !(bytes.HasPrefix(data, []byte(".\n0 0 0 0\n")) || bytes.Equal(data, []byte(".\n0 0 0 0"))) { 111 // That means that the server doesn't support returning stat information. 112 o.Entries = make([]xrdfs.EntryStat, len(lines)) 113 o.WithStatInfo = false 114 for i, v := range lines { 115 o.Entries[i] = xrdfs.EntryStat{EntryName: string(v)} 116 } 117 return nil 118 } 119 120 if len(lines)%2 != 0 { 121 return fmt.Errorf("xrootd: wrong response size for the dirlist request: want even number of lines, got %d", len(lines)) 122 } 123 124 lines = lines[2:] 125 o.Entries = make([]xrdfs.EntryStat, len(lines)/2) 126 o.WithStatInfo = true 127 128 for i := 0; i < len(lines); i += 2 { 129 var rbuf = xrdenc.NewRBuffer(lines[i+1]) 130 err := o.Entries[i/2].UnmarshalXrd(rbuf) 131 if err != nil { 132 return err 133 } 134 o.Entries[i/2].EntryName = string(lines[i]) 135 } 136 137 return nil 138 } 139 140 // Request holds the dirlist request parameters. 141 type Request struct { 142 _ [15]byte 143 Options RequestOptions 144 Path string 145 } 146 147 // RequestOptions specifies what should be returned as part of response. 148 type RequestOptions byte 149 150 const ( 151 None RequestOptions = 0 // None specifies that no addition information except entry names is required. 152 WithStatInfo RequestOptions = 2 // WithStatInfo specifies that stat information for every entry is required. 153 ) 154 155 // NewRequest forms a Request according to provided path. 156 func NewRequest(path string) *Request { 157 return &Request{Options: WithStatInfo, Path: path} 158 } 159 160 // ReqID implements xrdproto.Request.ReqID. 161 func (req *Request) ReqID() uint16 { return RequestID } 162 163 // ShouldSign implements xrdproto.Request.ShouldSign. 164 func (req *Request) ShouldSign() bool { return false } 165 166 // MarshalXrd implements xrdproto.Marshaler. 167 func (o Request) MarshalXrd(wBuffer *xrdenc.WBuffer) error { 168 wBuffer.Next(15) 169 wBuffer.WriteU8(byte(o.Options)) 170 wBuffer.WriteStr(o.Path) 171 return nil 172 } 173 174 // UnmarshalXrd implements xrdproto.Unmarshaler. 175 func (o *Request) UnmarshalXrd(rBuffer *xrdenc.RBuffer) error { 176 rBuffer.Skip(15) 177 o.Options = RequestOptions(rBuffer.ReadU8()) 178 o.Path = rBuffer.ReadStr() 179 return nil 180 } 181 182 // Opaque implements xrdproto.FilepathRequest.Opaque. 183 func (req *Request) Opaque() string { 184 return xrdproto.Opaque(req.Path) 185 } 186 187 // SetOpaque implements xrdproto.FilepathRequest.SetOpaque. 188 func (req *Request) SetOpaque(opaque string) { 189 xrdproto.SetOpaque(&req.Path, opaque) 190 }