File Management with Go

File Management with Go

A Security Engineering Perspective

Introduction

Security engineers make use of Unix or Linux systems for the majority of their tasks. A popular feature with such systems is how everything is treated as a file. Files, processes, directories, sockets and even connected devices are treated as files. This holds true for virtually everything in the computer ecosystem. Consequently, a security engineer using Golang must be able to manage files effectively with the language. In this article, file management with Google’s Go Programming Language (Golang) is explained. The article expounds on the process of creating, truncating, deleting, opening, closing, renaming, and moving files comfortably in Golang. Accessing and manipulating file properties is also addressed in this article.

Creating New Files

Creating a new file is relatively easy on the terminal, but there may be need to directly transfer data collected to a file in-code. Similar to the touch command in a Linux terminal, a new file can be created with Golang like this:

package main
import (
        "log"
        "os"
)
func main() {
 cre8, err := os.Create("cre8.txt")
 if err != nil {
   log.Fatal(err)
 }
log.Println(cre8)
 cre8.Close()
}

One major feature that sets Go apart from other modern languages is its ability to handle possible errors during code time. This practices saves the Go developer from running into compiler errors every do often, from creating bugs, and guarantees the developer more productive time.

Truncating Files

File truncation means attenuating a file by restricting it to a preferred maximum size. If the file is smaller that the specified truncate limit, the os.Truncate method will instead increase the file size to fit. Truncation is explained by code below in limiting a file.

package main
import (
   "log"
   "os")

func main() {
  err := os.Truncate("cre8.txt", 100)
  if err != nil {
     log.Fatal(err)
   }
}

The example code above truncates the file to a 100 byte file. If the file is below 100 bytes, the compiler writes enough lines of null bytes to the bottom of the file. If the file is above 100 bytes, the compiler trims away everything above the 100th bite.

Accessing File Properties

The os package also has a Stat method for fetching the attributes of a file. This may be it’s name, size, permissions, last modified time, and whether it is adirectory. There is also the FileInfo.Sys() interface which contains information about the source of the file. The file source is usually a file system on the harddisk.

package main
import (
       "fmt"
       "os"
       "log"
)
func main() {
   fileInfo, err := os.Stat("cre8.txt")
   if err != nil {
      log.Fatal(err)
   }
   fmt.Println("File name:", fileInfo.Name())
   fmt.Println("Size in bytes:", fileInfo.Size())
   fmt.Println("Permissions:", fileInfo.Mode())
   fmt.Println("Last modified:", fileInfo.ModTime())
   fmt.Println("Is Directory: ", fileInfo.IsDir())
   fmt.Printf("System interface type: %T\n", fileInfo.Sys())
   fmt.Printf("System info: %+v\n\n", fileInfo.Sys())
}

Opening and Closing Files

To open a file and/or close it, the os.Open() and os.Close() methods are used. To get more options and privileges after opening the file, the os.OpenFile() method may be used instead. The code sample below shows how both are done in Go.

package main
import (
   "log"
   "os"
)
func main()  {   
   file, err := os.Open("cre8.txt")
   if err != nil {
      log.Fatal(err)
   }
   file.Close()

   file, err = os.OpenFile("cre8.txt", os.O_APPEND, 0666)
   if err != nil {
      log.Fatal(err)
   }
   file.Close()
}

Reading and Writing to Files

The owner of a file may find the need to change the ownership, timestamp, and permissions. To do so in Go, a number of functions exist in the os package like

  • os.Chmod()
  • os.Chown()
  • os.Chtimes()

To practically understand their use case in Go, see the code sample below.

package main
import (
   "log"
   "os"
   "time"
)
func main() {
    err := os.Chmod("cre8.txt", 0777)
   if err != nil {
      log.Println(err)
   }
    err = os.Chown("cre8.txt", os.Getuid(), os.Getgid())
   if err != nil {
      log.Println(err)
   }
   // Change timestamps   
twoDaysFromNow := time.Now().Add(48 * time.Hour)
   lastAccessTime := twoDaysFromNow
   lastModifyTime := twoDaysFromNow
   err = os.Chtimes("cre8.txt", lastAccessTime, lastModifyTime)
   if err != nil {
      log.Println(err)
   }
}

Renaming and Deleting Files

Renaming and moving file are synonymous terms in Go. As such, these operations are done with one method - os.Rename().

package main
import (
   "log"   "os"
)

func main() {
   original := "cre8.txt"
   new := "cre8ed.txt"
   err := os.Rename(original, new)
   if err != nil {
      log.Fatal(err)
   }
}

This also works for renaming or moving directories. To delete files, the os.Remove() method is used.

package main
import (
   "log"
   "os"
)

func main() {
   err := os.Remove("test.txt")
   if err != nil {
      log.Fatal(err)
   }
}

Archiving and Compressing Files

Archiving a file means saving it and other files into a folder and zipping it. Go’s archive package allows the programmer to either zip or far files. An example of the use-case is seen below.

package main
import (
   "archive/zip"
   "log"
   "os"
)

func main() {  
   outFile, err := os.Create("cre8.zip")
   if err != nil {
      log.Fatal(err)
   }
   defer outFile.Close()

   zipWriter := zip.NewWriter(outFile)   
   var filesToArchive = []struct {
      Name, Body string
   }{
      {"test.txt", "String contents of file"},
      {"test2.txt", "\x61\x62\x63\n"},
   }
   for _, file := range filesToArchive {
      fileWriter, err := zipWriter.Create(file.Name)
      if err != nil {
         log.Fatal(err)
      }
      _, err = fileWriter.Write([]byte(file.Body))
      if err != nil {
         log.Fatal(err)
      }
   }
   err = zipWriter.Close()
   if err != nil {
      log.Fatal(err)
   }
}

To compress files, Go has several functions for several kinds of file formats. These are available in the compress package in the standard documentation.

package main
import (
   "compress/gzip"
   "log"
   "os"
)

func main() {
  err := os.Create("cre8.txt.gz")
   if err != nil {
      log.Fatal(err)
   }
   gzipWriter := gzip.NewWriter(outputFile)
   defer gzipWriter.Close()
   _, err = gzipWriter.Write([]byte("Gophers rule!\n"))
   if err != nil {
      log.Fatal(err)
   }
   log.Println("Compressedbdata written to file.")
}

There are lots of other possible actions that can be achieved in file management with Golang. Reading through the standard library documentation for the os and ioutil packages would expose a lot of them.

A series of follow-up articles will be published on Security related practices.

My name is MacBobby Chibuzor and I am a Software Engineer and Technical Writer for Wevolver. I research and write about Blockchain, Security, Web Development, Data Structures and Algorithms, Machine Learning and IoT.