44 Using Apis in the Os Package Part 1

44 Using APIs in the os Package - Part 1 #

Today we will talk about the APIs in the os code package. This package allows us to have the ability to manipulate computer operating systems.

Introduction: API in the os package #

The APIs provided by this code package are all platform-independent. So, what does it mean to be platform-independent?

It means that these APIs are based on (or abstracted from) the operating system to provide high-level support for using the functionality of the operating system, but they do not depend on specific operating systems.

Whether it’s Linux, macOS, Windows, FreeBSD, OpenBSD, Plan9, the os package can provide a unified interface. This allows us to manipulate different operating systems in the same way and get similar results.

The APIs in the os package mainly help us use the file system, permission system, environment variables, system processes, and system signals in the operating system.

Among them, the APIs for manipulating the file system are the most extensive. With these APIs, we can not only create and delete files and directories, but also get various information about them, modify their contents, change their access permissions, and so on.

Speaking of which, we have to mention a very commonly used data type: os.File.

From the literal meaning, the os.File type represents a file in the operating system. But in reality, it can represent much more than that. Perhaps you already know that for Unix-like operating systems (including Linux, macOS, FreeBSD, etc.), everything can be seen as a file.

In addition to text files, binary files, compressed files, and directories, there are symbolic links, various physical devices (including built-in or external block-oriented or character-oriented devices), named pipes, and sockets (also known as sockets), and so on.

Therefore, it can be said that there are too many things we can manipulate using the os.File type. However, in order to focus on the os.File itself and make the content of this article more general, here we mainly apply the os.File type to regular files.

The following question starts with the most basic content represented by the os.File type. The question for today is: Which interfaces in the io package are implemented by the os.File type?

The typical answer to this question is as follows.

The os.File type has pointer methods, so it doesn’t implement any interfaces itself except for the empty interface. However, its pointer type implements many interfaces in the io package.

First of all, the *os.File type implements the three core simple interfaces in the io package: io.Reader, io.Writer, and io.Closer.

Secondly, this type also implements three other simple interfaces, namely: io.ReaderAt, io.Seeker, and io.WriterAt.

Because the *os.File type implements these simple interfaces, it also incidentally implements 7 out of 9 of the extended interfaces in the io package.

However, since it does not implement the simple interfaces io.ByteReader and io.RuneReader, it does not implement the corresponding extended interfaces io.ByteScanner and io.RuneScanner.

In short, the value of the os.File type and its pointer type can not only read and write the contents of a file in various ways but also seek and set the starting index position for the next read or write. Additionally, the file can be closed at any time.

However, they cannot specifically read the next byte or the next Unicode character in the file, nor can they perform any read-back operations.

However, the functionality of reading the next byte or character can also be achieved through other means, such as calling its Read method and passing appropriate parameter values.

Problem Analysis #

This question is indirectly asking “How can the os.File type operate on files?” In my previous typical response, I provided a brief answer.

Before going into further details, let’s first see how we can obtain a pointer value of type os.File (referred to as a File value).

In the os package, there are several functions available: Create, NewFile, Open, and OpenFile.

The os.Create function creates a new file based on the given path. It returns a File value and an error value. We can perform read or write operations on the corresponding file using the returned File value.

Moreover, files created using this function can be read and written by all users in the operating system.

In other words, once a file is created in this way, any user logged into the operating system can read its contents at any time or write content to it.

Note that if a file already exists at the path given to os.Create, the function will first clear the existing file’s content and then return it as the first result value.

Additionally, os.Create may return a non-nil error value.

For example, if a certain parent directory on the given path does not exist, the function will return an error value of type *os.PathError to indicate “file or directory does not exist”.

Now let’s look at the os.NewFile function. This function needs to be called with a uintptr value representing the file descriptor and a string value representing the file name.

If the given file descriptor is not valid, the function will return nil; otherwise, it will return a File value representing the corresponding file.

Note that the name of this function can be misleading—it does not create a new file, but rather creates a File value that wraps an already existing file based on its descriptor.

For example, we can obtain a File value that wraps the standard error output like this:

file3 := os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")

And then, we can write some content to the standard error output using this File value:

if file3 != nil {
 defer file3.Close()
 file3.WriteString(
  "The Go language program writes the contents into stderr.\n")
}

The os.Open function opens a file and returns a File value that wraps the file. However, this function can only open files in read-only mode. In other words, we can only read content from the File value returned by this function, but cannot write to it.

If any write methods of this File value are called, an error value indicating a “bad file descriptor” will be returned. In fact, the read-only mode we mentioned earlier is applied to the file descriptor held by the File value.

A file descriptor is represented by a small non-negative integer. It is generally returned by I/O-related system calls and serves as an identifier for a file.

From the operating system’s perspective, this file descriptor is needed for any I/O operations on a file. However, some data types in Go hide this descriptor from us, so we don’t need to constantly focus on and distinguish it (like the os.File type).

In fact, when we call the os.Create function mentioned earlier, or the os.Open function, or the os.OpenFile function to be mentioned later, they all perform the same system call and obtain a file descriptor upon success. This file descriptor will be stored in the File value returned by them.

The os.File type has a pointer method called Fd. When called, it returns a uintptr value representing the file descriptor currently held by the File value.

However, except for the NewFile function in the os package, there is not much else to do with it if you are only working with regular files or directories.

Finally, let’s discuss the os.OpenFile function. This function provides underlying support for both the os.Create and os.Open functions and is the most flexible one.

This function has three parameters named name, flag, and perm. name represents the file path, and flag specifies the modes to apply to the file descriptor, including the read-only mode we mentioned earlier.

In Go, the read-only mode is represented by the os.O_RDONLY constant, which is of type int. Of course, besides the read-only mode, there are several other modes available, which we will discuss in detail later.

The perm parameter of the os.OpenFile function represents the permission mode, and its type is os.FileMode, which is a redefined type based on uint32.

To distinguish them, we can say that the mode indicated by flag is the operation mode, while the mode indicated by perm is the permission mode. The operation mode determines how the file is operated, while the permission mode controls file access permissions. We will discuss more details about the permission mode later.

Several ways to obtain a pointer value of type os.File- Several ways to obtain a pointer value of type os.File

To summarize, using a value of type os.File, we can not only read from, write to, and close a file, but also set the starting index position for the next read or write operation.

In addition to these functions mentioned, the os package also provides the Create function for creating a new file, the NewFile function for wrapping an existing file, and the Open and OpenFile functions for opening existing files.

Summary #

Today we talked about the os code package and its program entities. Firstly, we discussed the significance of the os package and its main uses. The APIs included in the code package are high-level abstractions of various operating system functions, allowing us to manipulate different operating systems in a unified way and achieve similar results.

In this code package, the API for manipulating the file system is the most comprehensive, with the most representative data type being os.File. The os.File type can not only represent files in the operating system, but also many other things. Especially in Unix-like operating systems, it can almost represent everything that can be manipulated, whether it’s software or hardware.

In the next article, I will continue to explain the content of the APIs in the os package. If you have any questions about this knowledge, feel free to leave me a message. Thank you for listening, and see you in the next issue.

Click here to view the detailed code associated with the Go language column articles.