github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-runtime-src/runtime/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 grumpy
    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  )