Skip to content

os: opening an existing file with O_CREATE|O_TRUNC and permission 0 changes file to be read-only on Windows #38225

Closed
@cmMcKeevek

Description

@cmMcKeevek

What version of Go are you using (go version)?

$ go version
go version go1.14.1 windows/amd64

Does this issue reproduce with the latest release?

As far as I can tell, 1.14.1 is the latest version, so yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Kevin\AppData\Local\go-build
set GOENV=C:\Users\Kevin\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GONOPROXY=gitlab.com/CampMinder
set GONOSUMDB=gitlab.com/CampMinder
set GOOS=windows
set GOPATH=C:\Users\Kevin\go
set GOPRIVATE=gitlab.com/CampMinder
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\Kevin\AppData\Local\Temp\go-build932902826=/tmp/go-build -gno-record-gcc-switches

What did you do?

package main

import (
	"log"
	"os"
	"os/exec"
)

func canWrite(filepath string) (bool, error) {
	file, err := os.OpenFile(filepath, os.O_WRONLY, 0666)
	if err != nil {
		if os.IsPermission(err) {
			return false, err
		}
	}
	file.Close()
	return true, nil
}

func main() {
	filename := "test.go"

	// Clean up previous run
	_, err := os.Stat(filename)
	if err == nil {
		os.Remove(filename)
	}

	// Create file
	f, err := os.Create(filename)
	if err != nil {
		log.Fatalf("error creating file, %s\n", err)
	}
	defer f.Close()

	// Check file was created without read-only
	result, err := canWrite(filename)
	if err != nil {
		log.Printf("error checking file %s\n", err)
	}
	log.Printf("Is %s writable? : [%v]\n", filename, result)

	// Populate test file with code that goimports will change
	_, err = f.WriteString(`
	package main

	func main() {
		fmt.Printf("%s", time.Now())
	}
	`)
	if err != nil {
		log.Fatal(err)
	}

	// Check that the file is still not read-only
	result, err = canWrite(filename)
	if err != nil {
		log.Printf("error checking file %s\n", err)
	}
	log.Printf("Is %s writable? : [%v]\n", filename, result)

	// Run goimports to change the file with `-w`
	cmd := exec.Command("goimports", "-w", filename)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		log.Fatalf("goimports failed with %s\n", err)
	}

	// Check that the file is now, erroneously, read-only
	result, err = canWrite(filename)
	if err != nil {
		log.Printf("error checking file %s\n", err)
	}
	log.Printf("Is %s writable? : [%v]\n", filename, result)
}

What did you expect to see?

I expected that the file remain writable after goimports -w

What did you see instead?

goimports -w leaves the formatted file with the read-only flag set.

Bonus information

I suspect the problem is right here
https://github.com/golang/tools/blob/9fc00b0a7ff6189dbfbf7326432f0857c366fd6a/cmd/goimports/goimports.go#L163

and that it was caused by this
https://golang.org/doc/go1.14#windows

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions