How to overwrite a symlink in Go? - go

I would like to overwrite a symlink using Go but I couldn't find how to do it.
If I try to create the symlink and it already exists an error is returned.
My code:
err := os.Symlink(filePath, symlinkPath)
if err != nil {
fmt.Println(err)
}
I guess the symlink must be removed and then created again. Is that right? If so, how can I unlink the symlink?

Just check that symlink exists and delete it before creating new one
if _, err := os.Lstat(symlinkPath); err == nil {
os.Remove(symlinkPath)
}

Note that #Vadyus's answer hides actual filesystem errors while running lstat. For example, if your disk is broken and Lstat fails, you will still run os.Remove and ignore its error (DANGEROUS, unless you like to debug things for hours).
The following snippets checks for file existence and other errors correctly:
if _, err := os.Lstat(symlinkPath); err == nil {
if err := os.Remove(symlinkPath); err != nil {
return fmt.Errorf("failed to unlink: %+v", err)
}
} else if os.IsNotExist(err) {
return fmt.Errorf("failed to check symlink: %+v", err)
}

The other answers here are correct...but there are two small issues:
There is a tiny data race where the new symlink will be created elsewhere before here but after the delete, leaving it in a potentially inconsistent state.
If the program were to die / crash before the symlink is created but after the previous one is deleted, it may again leave things in an inconsistent state.
The more atomic way to handle this is to create a temporary symlink and then rename it over the original:
symlinkPathTmp := symlinkPath + ".tmp"
if err := os.Remove(symlinkPathTmp); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Symlink(filePath, symlinkPathTmp); err != nil {
return err
}
if err := os.Rename(symlinkPathTmp, symlinkPath); err != nil {
return err
}
There is still a small race between deleting the temporary link and re-creating it, but it won't risk leaving the primarily link in an inconsistent state. Ideally, we would be able to work around that by using a randomized name for the temporary link, but Go's TempFile always creates a new file, so it isn't quite as useful. (You could maybe call TempFile, then delete the file name and re-use the name, which would be riskier but still safer then just appending a constant .tmp suffix.)
Even with that race however, you still gain the atomic-ness where any interruptions won't result in a missing link.
Note that this is dependent on Posix behavior and may not work on Windows (why would you be using symlinks on Windows anyway?), but it's a technique shared by many macOS/Linux tools that need atomic symlink replacements.

Related

check file existence without using os.Stat after locking the path

When I lock a file path with flock then check the file existence it return no error even though there is no file in that path. The code follows:
filePath := filepath.Join(r.path, fmt.Sprintf("%s_event.json", eventId))
fileLock := flock.New(filePath)
fileLock.Lock()
defer fileLock.Close()
_, err = os.Stat(filePath)
if err != nil {
if os.IsNotExist(errs) {
return event, EventNotFound{}
}
return
}
But when at first check the Stat then lock the file it works. I need to check it before that. Every idea is welcome!

How to extract .7z files in Go

I have a 7z archive of a number of .txt files. I am trying to list all the files in the archive and upload them to an s3 bucket. But I'm having trouble with extracting .7z archives on Go. To do this, I found a package github.com/gen2brain/go-unarr (imported as extractor) and this is what I have so far
content, err := ioutil.ReadFile("sample_archive.7z")
if err != nil {
fmt.Printf("err: %+v", err)
}
a, err := extractor.NewArchiveFromMemory(content)
if err != nil {
fmt.Printf("err: %+v", err)
}
lst, _ := a.List()
fmt.Printf("lst: %+v", last)
This prints a list of all the files in the archive. But this has two issues.
It reads files from local using ioutil and the input of NewArchiveFromMemory must be of type []byte. But I can't read from local and will have to use a file from memory of type os.file. So I will either have to find a different method or convert the os.file to []byte. There's another method NewArchiveFromReader(r io.Reader). But this is returning an error saying Bad File Descriptor.
file, err := os.OpenFile(
path,
os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
0666,
)
a, err := extractor.NewArchiveFromReader(file)
if err != nil {
fmt.Printf("ERROR: %+v", err)
}
lst, _ := a.List()
fmt.Printf("files: %+v\n", lst)
I am able to get the list of the files in the archive. And using Extract(destinaltion_path string), I can also extract it to a local directory. But I want the extracted files also in os.file format ( ie. a list of os.file since there will be multiple files ).
How can I change my current code to achieve both the above targets? Is there any other library to do this?
os.File implements the io.Reader interface (because it has a Read([]byte) (int, error) method defined), so you can use NewArchiveFromReader(file) without any conversions needed. You can read up on Go interfaces for more background on why that works.
If you're okay with extracting to a local directory, you can do that and then read the files back in (warning, may contain typos):
func extractAndOpenAll(*extractor.Archive) ([]*os.File, error) {
err := a.Extract("/tmp/path") // consider using ioutil.TempDir()
if err != nil {
return nil, err
}
filestats, err := ioutil.ReadDir("/tmp/path")
if err != nil {
return nil, err
}
# warning: all these file handles must be closed by the caller,
# which is why even the error case here returns the list of files.
# if you forget, your process might leak file handles.
files := make([]*os.File, 0)
for _, fs := range(filestats) {
file, err := os.Open(fs.Name())
if err != nil {
return files, err
}
files = append(files, file)
}
return files, nil
}
It is possible to use the archived files without writing back to disk (https://github.com/gen2brain/go-unarr#read-all-entries-from-archive), but whether or not you should do that instead depends on what your next step is.

Is there a way to determine if current step is a directory?

I need to implement sftp client that connects to a host, read all available files in a specified folder, then check if a particular file matches a pattern and copy it to according local directory. Problem is that i can't find a way to.
I tried to use client.Walk but cannot figure out a way to understand if this is a directory and skip it:
walker := client.Walk(startDir)
for walker.Step() {
if err := walker.Err(); err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
filePath := walker.Path()
}
How can I determine if the current iteration is directory?
You may use Walker.Stat() to obtain info about the most recent file or directory visited by a call to Walker.Step(). It returns you a value of type os.FileInfo which has an IsDir() method.
For example:
for walker.Step() {
if err := walker.Err(); err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if fi := walker.Stat(); fi.IsDir() {
continue // Skip dir
}
// ...
}

how to get the location of the current file in revel

I am using golang revel web framework and
I am trying to create a sqlite db in the current working directory.
model.go
func New(dbName string,table string) *Db {
_,filename,_,_ := runtime.Caller(1)
db , err := sql.Open("sqlite3",path.Join(path.Dir(filename),dbName))
if err != nil {
log.Fatal(err)
}
err = db.Ping()
if err != nil {
log.Panic(err)
}
database := &Db{Database:db}
_,err = db.Exec("create table %s" +
"( id integer primary key, " +
"name varchar(100),"+
"email varchar(100),"+
"branch varchar(100),"+
"help varchar(100)",)
if err != nil {
log.Fatal(err)
}
}
I have a test in place which just calls this function.
whenever i run the test using revel test or by going to the localhost:9000/#tests, the function Panics and the error message is
cannot open the database file.
The reason that is happening is because the filename returned by runtime.Caller(1) is /usr/local/go/src/runtime/asm_amd64.s for which the program has no permission.
if i directly write ./foo.db, even then the error shows.
I tried os.Getwd() which return empty string.
I also tried filepath.Abs(filepath.Dir(os.Args[0]))
but that returned /home/girish/GoProjects/bin/revel.d which is the revel binary.
So whats the best way to find the directory of the model.go?
It doesn't make sense to get the directory of the model.go file at runtime, because the compiled executable could be on a completely different filesystem.
You may want to get the directory of where the running executable was started from:
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
dir will be the folder where the program lives at runtime.

idiomatic way to get os err after call

If I do
s, err := os.Stat(path)
and err != nil I need to know if the file doesn't exist vs I don't have permission to access it, etc. How do I get the underlying error code? Reading the os package docs it seems to suggest that I read the text of the error string - surely not?
What FUZxxl says.
From the os.Stat documentation:
Stat returns a FileInfo describing the named file. If there is an error, it will be of type *PathError.
PathError is documented on the same page, stating that it holds the operation that caused the error, the path to the file that caused it and the underlying system's error. In case the file was not found when calling os.Stat, the returned error would be something like this:
&PathError{"stat", "/your/file", syscall.Errno(2)}
Since the underlying error is inherently depending on the OS you use, the only thing that you can do is to
understand PathError.Err. For UNIX systems the syscall package has the Errno error type returned by syscalls like syscall.Stat. You can compare this value with the constants in the syscall package and handle the error (Click to play):
stat, err := os.Stat(file)
if perr, ok := err.(*os.PathError); ok {
switch perr.Err.(syscall.Errno) {
case syscall.ENOENT: fmt.Println("No such file or directory.")
default: panic("Unknown error")
}
}
The shorter way of doing this is to use os.IsNotExist which does pretty much the above
and is, most importantly, platform independent:
stat, err := os.Stat(file)
if err != nil && os.IsNotExist(err) {
// ...
}
The other answer is great, but I wanted add a note about this suggestion:
stat, err := os.Stat(file)
if err != nil && os.IsNotExist(err) {
// ...
}
I found that in many cases, I was needing to take different actions depending on
each test, so in reality you have three branches here. Here is code I use for
that:
stat, err := os.Stat(file)
if os.IsNotExist(err) {
// branch one
} else if err != nil {
// branch two
}
// branch three

Resources