Copy files and preserve permission in Golang [SOLVED]

In this article, we shall be discussing how to copy files and preserve permission in Golang with help of practical examples.

 

Different methods to copy files and preserve permission

Copy is a way of making similar files or directory data from one directory into another. In Golang, we can make use of os, io, and ioutil packages to create functions that can copy files or directories from one source to the destination defined. Below are the methods we shall be using:-

Advertisement
  • Using os package:- Using os.Read() and os.Write() functions
  • Using io package:- provides io.Copy() function
  • Using ioutil package:- ioutil.Read() and ioutil.Write() functions

 

Method 1:- Using os.Read() and os.Write()

In Golang, Os the package provides Read and Write function. os.Read() this function is used to read an input file into a buffer of defined buffer size and os.Write() is used to write the readable content of the buffer into the destination file specified. os.Open() facilitates reading the source file byte by byte.

Example:- Copying a single file

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
)

func FileCopy(file, dst string, bf int64) error {
	//file name and path to be copied
	path := dst + file
	fileStat, errFilestat := os.Stat(file)
	if errFilestat != nil {
		return errFilestat
	}
	// users permission
	perm := fileStat.Mode().Perm()
	fmt.Printf("File permission before copying %v \n", perm)

	if !fileStat.Mode().IsRegular() {
		return fmt.Errorf("%v is not a regular file", file)
	}
	dstPathStat, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("destination %v doesn't exists", dstPathStat.Name())
		}
	} else {
		if !(dstPathStat.Mode().IsRegular()) {
			return fmt.Errorf("CopyFile: destination file %v (%q)", dstPathStat.Name(), dstPathStat.Mode().String())
		}
		if os.SameFile(fileStat, dstPathStat) {
			return fmt.Errorf("updating file since destination of this %v file is the same", dstPathStat.Name())
		}
	}
	//open file
	opFile, err := os.Open(file)
	if err != nil {
		return err
	}
	defer opFile.Close()

	// create file in the destination
	dstFile, err := os.Create(path)
	if err != nil {
		return err
	}
	defer func() {
		createErr := dstFile.Close()
		if err == nil {
			err = createErr
		}
	}()

	err = dstFile.Sync()
	if err != nil {
		panic(err)
	}
	fmt.Println("The destination file path is:- ", dstFile.Name())

	bufValue := make([]byte, bf)
	for {
		r, err := opFile.Read(bufValue)
		if err != nil && err != io.EOF {
			return err
		}
		if r == 0 {
			break
		}

		if _, err := dstFile.Write(bufValue[:r]); err != nil {
			return err
		}
	}
	// destination file stats
	newFileStats, errNewFileStat := os.Stat(dstFile.Name())
	if errNewFileStat != nil {
		return errNewFileStat
	}
	// users permission
	perm2 := newFileStats.Mode().Perm()
	fmt.Printf("File permission After copying %v \n", perm2)
	return err
}

func main() {
	if len(os.Args) != 4 {
		fmt.Printf("usage: %s source destination BUFFERSIZE\n", filepath.Base(os.Args[0]))
		return
	}

	fileName := os.Args[1]
	dst := os.Args[2]
	bf, err := strconv.ParseInt(os.Args[3], 10, 64)
	if err != nil {
		fmt.Printf("Invalid buffer size: %q\n", err)
		return
	}
	fmt.Printf("Copying %s to %s\n", fileName, dst)
	err = FileCopy(fileName, dst, bf)
	if err != nil {
		fmt.Printf("File copying failed: %q\n", err)
	}
}

Output:-

$ go run main.go news.txt file_dst/ 7
Copying news.txt to file_d
File permission before copying -rw-r--r-- 
The destination file path is:- file_dst/news.txt
File permission After copying -rw-r--r--

Explanation:-

In the code above, FileCopy(file, dst string, bf int64) error {} FileCopy function is used to receive a filename i.e news.txt, destination folder i.e foldername/, and buffer size i.e 4. The buffer size is used to determine the length of content to be read. We print out the current permission of the file and check if it exists in the destination folder passed. if it exists we just update the file content, if the content is the same we retain the file unchanged. if errors encountered are returned. Through the use of Golang standard library os we can read users' inputs along the program i.e go run main.go file source, destination, and bytes in that order. this happens in the main function. os.ReadFile is used for reading file portion by portion based on the defined buffer and os.WriteFile is used to content read to the file destination denoted by dst.

 

Method 2:-Using io.Copy() function

io package is a Goang standard library that enables us to copy files from source to destination. its similar to the above method. However, it accepts two parameters namely, filename and destination name as shown below. It returns two parameters, integer, and error.
Example:-

Advertisement
package main

import (
	"fmt"
	"io"
	"os"
)

func usingIoCopy(src, dst string) (int64, error) {

	// resolve path related to the file using os.Stat
	fileStats, err := os.Stat(src)
	if err != nil {
		return 0, err
	}
	// check if the file is a regular or not
	if !fileStats.Mode().IsRegular() {
		return 0, fmt.Errorf("%s is not a regular file", src)
	}
	// using open file to read bytes
	fileRead, err := os.Open(src)
	if err != nil {
		return 0, err
	}
	defer fileRead.Close()
	// create file destination if doesnt exist
	fileDst, err := os.Create(dst)
	if err != nil {
		return 0, err
	}
	// ensure file is closed to avoid memory leaks
	defer fileDst.Close()
	// returns bytes [0,12,56] after copying file data to desired destination
	resBytes, err := io.Copy(fileDst, fileRead)
	// Incase of error handle
	return resBytes, err
}

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Please provide two command line arguments!")
		return
	}
	sourceFile := os.Args[1] // desired file to be copied new.txt
	destinationFile := os.Args[2] // desired destination file name file_dst/new.txt

	res, err := usingIoCopy(sourceFile, destinationFile)
	if err != nil {
		fmt.Printf("The copy operation failed %q\n", err)
	} else {
		fmt.Printf("Copied %d bytes!\n", res)
	}
}

Output:-

$ go run main.go new.txt cloud.txt
Copied 7 bytes!

Explanation:-

In the code above, we have created a usingIoCopy(src, dst string) (int64, error){} It accepts the file source, and destination of the data type string and performs all defined processes within the function such as creating a destination file name using os.Create, resolving path-related os.Stats, opening file for reading using os.Open finally copying the file to the desired destination using io.Copy() from the io package returns the total number of bytes copied. In main():- enables reading the file source, and destination and waiting for return values from usingIoCopy() which is in form of bytes and error of nil or message encountered during execution.

 

Method 3:-Using ioutil.ReadFile()and ioutil.WriteFile() functions in Golang

In this method, we will be focusing on ioutil package, which provides us with ioutil.ReadFile() and ioutil.WriteFile functions. The Read() function reads the file content into bytes and the Write() function just performs the act of writing the bytes into the desired final destination file. The below example explains how to implement the two functions.
Example of Using ioutil.ReadFile()and ioutil.WriteFile() functions in Golang


package main

import (
	"fmt"
	"io/ioutil"
	"os"
)

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Kindly provide the filepath/name  and destination")
		return
	}

	srcName := os.Args[1]
	dstPath := os.Args[2]

	input, err := ioutil.ReadFile(srcName)
	if err != nil {
		fmt.Println(err)
		return
	}

	// users permission 
	fileStats, _ := os.Stat(srcName)
	perm := fileStats.Mode().Perm()
	fmt.Printf("Files permission before copying %v \n", perm)
	// writing file to desired destination permission 0644 allows one current user to write and to be read by all users.
	err = ioutil.WriteFile(dstPath, input, 0644)
	if err != nil {
		fmt.Printf(" Issue arise while creating : %v", dstPath)
		fmt.Println(err)
		return
	}
	dstFile, _ := os.Stat(dstPath)
	perms := dstFile.Mode().Perm()
	fmt.Printf("Files permission after copying %v \n", perms)
}

Output:-

$ go run main.go new.txt cloud.txt   
Files permission before copying -rw-r--r-- 
Files permission after copying -rw-r--r--

File/directory existing

$ go run main.go new.txt cloud  
Files permission before copying -rw-r--r-- 
 Issue arises while creating: cloud open cloud: is a directory

Explanation:-

In the above code, we have imported all necessary packages required, we are using three packages namely os, fmt, and ioutil. os package is mostly for reading users' parameters such as filename and destination, fmt for standard output or display while ioutil is mainly to perform key reading and writing file content from a defined file to the desired destination as shown above.

 

Summary

In this article, we have covered various ways to copy files and reserve their original permissions. In Golang we can achieve this by using Golang standard library provided such as os, ioutil,  and io packages. this is helpful in transferring files from one server to another in the software development lifecycle.

 

References

ioutil/io
os

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X