github.com/pygolin/runtime@v0.0.0-20201208210830-a62e3cd39798/file.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package runtime 16 17 import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "reflect" 25 "strings" 26 "sync" 27 ) 28 29 // File represents Python 'file' objects. 30 type File struct { 31 Object 32 // mutex synchronizes the state of the File struct, not access to the 33 // underlying os.File. So, for example, when doing file reads and 34 // writes we only acquire a read lock. 35 mutex sync.Mutex 36 mode string 37 open bool 38 Softspace int `attr:"softspace" attr_mode:"rw"` 39 reader *bufio.Reader 40 file *os.File 41 skipNextLF bool 42 univNewLine bool 43 close *Object 44 } 45 46 // NewFileFromFD creates a file object from the given file descriptor fd. 47 func NewFileFromFD(fd uintptr, close *Object) *File { 48 // TODO: Use fcntl or something to get the mode of the descriptor. 49 file := &File{ 50 Object: Object{typ: FileType}, 51 mode: "?", 52 open: true, 53 file: os.NewFile(fd, "<fdopen>"), 54 } 55 if close != None { 56 file.close = close 57 } 58 file.reader = bufio.NewReader(file.file) 59 return file 60 } 61 62 func toFileUnsafe(o *Object) *File { 63 return (*File)(o.toPointer()) 64 } 65 66 func (f *File) name() string { 67 name := "<uninitialized file>" 68 if f.file != nil { 69 name = f.file.Name() 70 } 71 return name 72 } 73 74 // ToObject upcasts f to an Object. 75 func (f *File) ToObject() *Object { 76 return &f.Object 77 } 78 79 func (f *File) readLine(maxBytes int) (string, error) { 80 var buf bytes.Buffer 81 numBytesRead := 0 82 for maxBytes < 0 || numBytesRead < maxBytes { 83 b, err := f.reader.ReadByte() 84 if err == io.EOF { 85 break 86 } 87 if err != nil { 88 return "", err 89 } 90 if b == '\r' && f.univNewLine { 91 f.skipNextLF = true 92 buf.WriteByte('\n') 93 break 94 } else if b == '\n' { 95 if f.skipNextLF { 96 f.skipNextLF = false 97 continue // Do not increment numBytesRead. 98 } else { 99 buf.WriteByte(b) 100 break 101 } 102 } else { 103 buf.WriteByte(b) 104 } 105 numBytesRead++ 106 } 107 return buf.String(), nil 108 } 109 110 func (f *File) writeString(s string) error { 111 f.mutex.Lock() 112 defer f.mutex.Unlock() 113 if !f.open { 114 return io.ErrClosedPipe 115 } 116 if _, err := f.file.Write([]byte(s)); err != nil { 117 return err 118 } 119 120 return nil 121 } 122 123 // FileType is the object representing the Python 'file' type. 124 var FileType = newBasisType("file", reflect.TypeOf(File{}), toFileUnsafe, ObjectType) 125 126 func fileInit(f *Frame, o *Object, args Args, _ KWArgs) (*Object, *BaseException) { 127 argc := len(args) 128 expectedTypes := []*Type{StrType, StrType} 129 if argc == 1 { 130 expectedTypes = expectedTypes[:1] 131 } 132 if raised := checkFunctionArgs(f, "__init__", args, expectedTypes...); raised != nil { 133 return nil, raised 134 } 135 mode := "r" 136 if argc > 1 { 137 mode = toStrUnsafe(args[1]).Value() 138 } 139 // TODO: Do something with the binary mode flag. 140 var flag int 141 switch mode { 142 case "a", "ab": 143 flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND 144 case "r", "rb", "rU", "U": 145 flag = os.O_RDONLY 146 case "r+", "r+b": 147 flag = os.O_RDWR 148 // Difference between r+ and a+ is that a+ automatically creates file. 149 case "a+": 150 flag = os.O_RDWR | os.O_CREATE | os.O_APPEND 151 case "w+": 152 flag = os.O_RDWR | os.O_CREATE 153 case "w", "wb": 154 flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC 155 default: 156 return nil, f.RaiseType(ValueErrorType, fmt.Sprintf("invalid mode string: %q", mode)) 157 } 158 file := toFileUnsafe(o) 159 file.mutex.Lock() 160 defer file.mutex.Unlock() 161 osFile, err := os.OpenFile(toStrUnsafe(args[0]).Value(), flag, 0644) 162 if err != nil { 163 return nil, f.RaiseType(IOErrorType, err.Error()) 164 } 165 file.mode = mode 166 file.open = true 167 file.file = osFile 168 file.reader = bufio.NewReader(osFile) 169 file.univNewLine = strings.HasSuffix(mode, "U") 170 return None, nil 171 } 172 173 func fileEnter(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 174 if raised := checkMethodArgs(f, "__enter__", args, FileType); raised != nil { 175 return nil, raised 176 } 177 return args[0], nil 178 } 179 180 func fileExit(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 181 if raised := checkMethodVarArgs(f, "__exit__", args, FileType); raised != nil { 182 return nil, raised 183 } 184 closeFunc, raised := GetAttr(f, args[0], NewStr("close"), nil) 185 if raised != nil { 186 return nil, raised 187 } 188 _, raised = closeFunc.Call(f, nil, nil) 189 if raised != nil { 190 return nil, raised 191 } 192 return None, nil 193 } 194 195 func fileClose(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 196 if raised := checkMethodArgs(f, "close", args, FileType); raised != nil { 197 return nil, raised 198 } 199 file := toFileUnsafe(args[0]) 200 file.mutex.Lock() 201 defer file.mutex.Unlock() 202 ret := None 203 if file.open { 204 var raised *BaseException 205 if file.close != nil { 206 ret, raised = file.close.Call(f, args, nil) 207 } else if file.file != nil { 208 if err := file.file.Close(); err != nil { 209 raised = f.RaiseType(IOErrorType, err.Error()) 210 } 211 } 212 if raised != nil { 213 return nil, raised 214 } 215 } 216 file.open = false 217 return ret, nil 218 } 219 220 func fileClosed(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 221 if raised := checkMethodArgs(f, "closed", args, FileType); raised != nil { 222 return nil, raised 223 } 224 file := toFileUnsafe(args[0]) 225 file.mutex.Lock() 226 c := !file.open 227 file.mutex.Unlock() 228 return GetBool(c).ToObject(), nil 229 } 230 231 func fileFileno(f *Frame, args Args, _ KWArgs) (ret *Object, raised *BaseException) { 232 if raised := checkMethodArgs(f, "fileno", args, FileType); raised != nil { 233 return nil, raised 234 } 235 file := toFileUnsafe(args[0]) 236 file.mutex.Lock() 237 if file.open { 238 ret = NewInt(int(file.file.Fd())).ToObject() 239 } else { 240 raised = f.RaiseType(ValueErrorType, "I/O operation on closed file") 241 } 242 file.mutex.Unlock() 243 return ret, raised 244 } 245 246 func fileGetName(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 247 if raised := checkMethodArgs(f, "_get_name", args, FileType); raised != nil { 248 return nil, raised 249 } 250 file := toFileUnsafe(args[0]) 251 file.mutex.Lock() 252 name := file.name() 253 file.mutex.Unlock() 254 return NewStr(name).ToObject(), nil 255 } 256 257 func fileIter(f *Frame, o *Object) (*Object, *BaseException) { 258 return o, nil 259 } 260 261 func fileNext(f *Frame, o *Object) (ret *Object, raised *BaseException) { 262 file := toFileUnsafe(o) 263 file.mutex.Lock() 264 defer file.mutex.Unlock() 265 if !file.open { 266 return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file") 267 } 268 line, err := file.readLine(-1) 269 if err != nil { 270 return nil, f.RaiseType(IOErrorType, err.Error()) 271 } 272 if line == "" { 273 return nil, f.Raise(StopIterationType.ToObject(), nil, nil) 274 } 275 return NewStr(line).ToObject(), nil 276 } 277 278 func fileRead(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 279 file, size, raised := fileParseReadArgs(f, "read", args) 280 if raised != nil { 281 return nil, raised 282 } 283 file.mutex.Lock() 284 defer file.mutex.Unlock() 285 if !file.open { 286 return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file") 287 } 288 var data []byte 289 var err error 290 if size < 0 { 291 data, err = ioutil.ReadAll(file.file) 292 } else { 293 data = make([]byte, size) 294 var n int 295 n, err = file.reader.Read(data) 296 data = data[:n] 297 } 298 if err != nil && err != io.EOF { 299 return nil, f.RaiseType(IOErrorType, err.Error()) 300 } 301 return NewStr(string(data)).ToObject(), nil 302 } 303 304 func fileReadLine(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 305 file, size, raised := fileParseReadArgs(f, "readline", args) 306 if raised != nil { 307 return nil, raised 308 } 309 file.mutex.Lock() 310 defer file.mutex.Unlock() 311 if !file.open { 312 return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file") 313 } 314 line, err := file.readLine(size) 315 if err != nil { 316 return nil, f.RaiseType(IOErrorType, err.Error()) 317 } 318 return NewStr(line).ToObject(), nil 319 } 320 321 func fileReadLines(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 322 // NOTE: The size hint behavior here is slightly different than 323 // CPython. Here we read no more lines than necessary. In CPython a 324 // minimum of 8KB or more will be read. 325 file, size, raised := fileParseReadArgs(f, "readlines", args) 326 if raised != nil { 327 return nil, raised 328 } 329 file.mutex.Lock() 330 defer file.mutex.Unlock() 331 if !file.open { 332 return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file") 333 } 334 var lines []*Object 335 numBytesRead := 0 336 for size < 0 || numBytesRead < size { 337 line, err := file.readLine(-1) 338 if err != nil { 339 return nil, f.RaiseType(IOErrorType, err.Error()) 340 } 341 if line != "" { 342 lines = append(lines, NewStr(line).ToObject()) 343 } 344 if !strings.HasSuffix(line, "\n") { 345 break 346 } 347 numBytesRead += len(line) 348 } 349 return NewList(lines...).ToObject(), nil 350 } 351 352 func fileRepr(f *Frame, o *Object) (*Object, *BaseException) { 353 file := toFileUnsafe(o) 354 file.mutex.Lock() 355 defer file.mutex.Unlock() 356 var openState string 357 if file.open { 358 openState = "open" 359 } else { 360 openState = "closed" 361 } 362 var mode string 363 if file.mode != "" { 364 mode = file.mode 365 } else { 366 mode = "<uninitialized file>" 367 } 368 return NewStr(fmt.Sprintf("<%s file %q, mode %q at %p>", openState, file.name(), mode, file)).ToObject(), nil 369 } 370 371 func fileWrite(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 372 if raised := checkMethodArgs(f, "write", args, FileType, StrType); raised != nil { 373 return nil, raised 374 } 375 file := toFileUnsafe(args[0]) 376 file.mutex.Lock() 377 defer file.mutex.Unlock() 378 if !file.open { 379 return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file") 380 } 381 if _, err := file.file.Write([]byte(toStrUnsafe(args[1]).Value())); err != nil { 382 return nil, f.RaiseType(IOErrorType, err.Error()) 383 } 384 return None, nil 385 } 386 387 func initFileType(dict map[string]*Object) { 388 // TODO: Make enter/exit into slots. 389 dict["__enter__"] = newBuiltinFunction("__enter__", fileEnter).ToObject() 390 dict["__exit__"] = newBuiltinFunction("__exit__", fileExit).ToObject() 391 dict["close"] = newBuiltinFunction("close", fileClose).ToObject() 392 dict["closed"] = newBuiltinFunction("closed", fileClosed).ToObject() 393 dict["fileno"] = newBuiltinFunction("fileno", fileFileno).ToObject() 394 dict["name"] = newProperty(newBuiltinFunction("_get_name", fileGetName).ToObject(), nil, nil).ToObject() 395 dict["read"] = newBuiltinFunction("read", fileRead).ToObject() 396 dict["readline"] = newBuiltinFunction("readline", fileReadLine).ToObject() 397 dict["readlines"] = newBuiltinFunction("readlines", fileReadLines).ToObject() 398 dict["write"] = newBuiltinFunction("write", fileWrite).ToObject() 399 FileType.slots.Init = &initSlot{fileInit} 400 FileType.slots.Iter = &unaryOpSlot{fileIter} 401 FileType.slots.Next = &unaryOpSlot{fileNext} 402 FileType.slots.Repr = &unaryOpSlot{fileRepr} 403 } 404 405 func fileParseReadArgs(f *Frame, method string, args Args) (*File, int, *BaseException) { 406 expectedTypes := []*Type{FileType, ObjectType} 407 argc := len(args) 408 if argc == 1 { 409 expectedTypes = expectedTypes[:1] 410 } 411 if raised := checkMethodArgs(f, method, args, expectedTypes...); raised != nil { 412 return nil, 0, raised 413 } 414 size := -1 415 if argc > 1 { 416 o, raised := IntType.Call(f, args[1:], nil) 417 if raised != nil { 418 return nil, 0, raised 419 } 420 size = toIntUnsafe(o).Value() 421 } 422 return toFileUnsafe(args[0]), size, nil 423 } 424 425 var ( 426 // Stdin is an alias for sys.stdin. 427 Stdin = NewFileFromFD(os.Stdin.Fd(), nil) 428 // Stdout is an alias for sys.stdout. 429 Stdout = NewFileFromFD(os.Stdout.Fd(), nil) 430 // Stderr is an alias for sys.stderr. 431 Stderr = NewFileFromFD(os.Stderr.Fd(), nil) 432 )