[toc]

Original is not easy, pay attention to the public number: Qiya cloud storage, get more dry goods.

Antecedents feed

In the previous article, we implemented a Hello World file system with a complete piece of code. This file system shows you an interesting IO path. As readers have noticed, this file system is read-only and does not support writes. The contents of file Hello are not stored on disk, but are returned directly by the program.

Today we will extend the HelloFS file system with three enhancements:

  • File system added writable functionality;
  • Storage encryption and decryption;
  • Distributed data storage;

So, we will complete a distributed encrypted HelloFS minimalist file system. Looking forward to?

Three major enhancements

Add write functionality

The helloFS file system shown in the previous article is a read-only file system. Remember that a file system is responsible for storing and reading data. A file system that cannot write data is incomplete.

root@ubuntu:~# echo test > /mnt/myfs/hello
-bash: echo: write error: Input/output error
Copy the code

We need to add write support to HelloFS. How?

Highlight: Just giveFileObject implementationWriteInterface.

func (File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
    // Receive IO requests and store data to a file
    err := ioutil.WriteFile("/tmp/hellofs.01", req.Data, 0666)
    iferr ! =nil {
        return err
    }
    // Set size after write success
    resp.Size = len(req.Data)
    return nil
}
Copy the code

As above, write the data received by the write request to the file/TMP /hellofs.01

Add encryption function

What does encryption mean? Is to make others can not understand!

What is the purpose? For data security.

Generally speaking, sensitive data cannot be stored in clear text, so that even if the data is stolen, the contents cannot be read, and the damage can be controlled.

Take for example, Xiao Ming writes diary every day (aside: serious person who writes diary), one day diary is secretly read, large probability person set will jump. However, if Xiaoming used oracle bone script to write, no one could understand the diary even if it was stolen, and the damage would only be to lose a notebook, which would be harmless to people.

There are many ways to encrypt data, but one principle must be followed: the encryption algorithm must be reversible. This principle should be easy to understand, right? Encryption is only for theft, but the storage of data is naturally to read data, if you use the top genius algorithm to store to disk, read the time but can not decrypt the original data, that is awkward.

Small thought: this and site storage password is not the same oh, site storage password with irreversible algorithm encryption, do you know why? Cause: Password equality check does not compare the password string equality directly, but the password hash value equality.

Ok, so we’ve understood the purpose and form of encryption. For the sake of demonstration, instead of using fancy encryption algorithms, we’ll use base64 to serialize the original data, then write it to the file (to make it look like a garble), and then deserialize it to read the original data.

str := "hello world"
encoded := base64.StdEncoding.EncodeToString([]byte(str))
Copy the code

Encryption effect:

hello world  =>  aGVsbG8gd29ybGQ=
Copy the code

These garbled strings feel good, isn’t the first glance is not what! Interested in finding a Base64 encoding and decoding site to try, but also the same effect oh.

Added distributed attributes

Distribution is a big topic, and the essence is to organize discrete nodes into an organic whole. A general distributed architecture has three components:

  • The Client Client
  • Meta Metadata Center
  • Server Server

In order to simplify the processing, this time remove metadata center, using the most classical C/S structure, demonstrating a simple distributed file system. The Fuse protocol is parsed by the Client to get the IO request packet, which is then forwarded to the Server node for storage. You’ll find that what looks like a local file system actually stores data on another machine.

Let’s Go to the actual combat: Go file hellofs-client.go file hellofs-server.go file hellofs-client.go file hellofs-server.go file helloFs-client. go file helloFs-server.

  • Client:hellofs-client.goA file that listens for fuse requests, parses them, encrypts them and forwards them to the Server, receives the response from the Server, decrypts them, and replies to fuse responses.
  • Server:hellofs-server.goFile, which is responsible for listening to requests sent by clients and reading and writing files from disks.

Schematic diagram of module functions:

Go code practice

Okay, now what about the client side and the server side?

Note: in order to highlight the key points of the article and reduce the length of the code, we only post the key snippets of code here. But Kisae has ready the complete code for you to compile. Pay attention to the public number, reply homemade file system can be obtained.

hellofs-client

First, let’s take a look at the implementation of the added Write interface:

// hellofs-client.go
// Implement this interface to have write function
func (File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
    // Write requests are forwarded to hellofs-server
    _, err := http.Post(serverNodeAddr+"/hello/write"."text/plain", bytes.NewReader(req.Data))
    iferr ! =nil {
        return err
    }
    // Write successfully, set the length
    resp.Size = len(req.Data)
    return nil
}
Copy the code

Summary:

  1. To achieve theWrite, the write function is added to the file system.
  2. Internal implementation is very simple, directly proxy to the Server process, through Http protocol. For example, write requests are forwarded directly to the Server/hello/writeInterface;

hellofs-server

Hellofs-server. go implements an Http server that receives and processes requests that hellofs-client.go forwards. Let’s start with a Write implementation:

// hellofs-server.go
// Server implements an HTTP Server

func main(a) {
    //...
    http.HandleFunc("/hello/write", hellofsWrite)

    log.Println("start")
    err := http.ListenAndServe(": 8899".nil)
    iferr ! =nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func hellofsWrite(w http.ResponseWriter, r *http.Request) {
	log.Printf("hellfs server get req=> write")
	data, err := ioutil.ReadAll(r.Body)
	iferr ! =nil {
		w.WriteHeader(500)}// Encrypt user data
	encryptStr := base64.StdEncoding.EncodeToString(data)
	// Store encrypted data
	err = ioutil.WriteFile(helloFilePath, []byte(encryptStr), 0666)
	iferr ! =nil {
		w.WriteHeader(500)}return
}
Copy the code

Let’s test that out

Step 1: Compile the binaries

Hellofs-client. go (client) and hellofs-server.go (server);

Compile client

root@ubuntu:~/code/gopher/src/myfs# go build -gcflags "-N -l" ./hellofs-client.go
Copy the code

Compile server

root@ubuntu:~/code/gopher/src/myfs# go build -gcflags "-N -l" ./hellofs-server.go
Copy the code

Generate the binary files hellofs-client and hellofs-server.

Step 2: Mount the file system (client of HelloFS)

This step, also described in the previous article, is to start the client of HelloFS and listen for FUSE requests.

root@ubuntu:~/code/gopher/src/myfs# ./hellofs-client --mountpoint=/mnt/myfs --fuse.debug=true
Copy the code

The process starts without any errors and can proceed to the next step.

Step 3: Start the server of HelloFS

The server and client are used to transfer data over the network. The HelloFs-Server can run anywhere and does not need to be deployed on the same machine as the HelloFs-Client. The serverNodeAddr in hellofs-client.go is correctly configured to the address where hellofs-server runs.

This is also the characteristic of distribution: connecting multiple nodes into an organic whole through the network.

Note: for simplicity, the author is deploying on a single machine, using 127.0.0.1 loop network:

// If you have multiple machine nodes, you can try to run hellofs-server on other nodes
var (
    serverNodeAddr = "http://localhost:8899"
)
Copy the code

Ok, try starting the server:

root@ubuntu:~/code/gopher/src/myfs# ./hellofs-server
2021/06/20 11:18:51 start
Copy the code

At this point, our environment is all set.

Let’s start testing.

Ls -l If we look at the file on the homebrew file system, we can see that hello’s permission has changed to 666.

root@ubuntu:~# ll /mnt/myfs/hello 
-rw-rw-rw- 1 root root 0 Jun 20 11:20 /mnt/myfs/hello
Copy the code

Cat, there’s nothing there.

root@ubuntu:~# cat /mnt/myfs/hello
Copy the code

So let’s try to write some strings, like this, so I’m going to write the string “qiyacloud” echo into the file.

root@ubuntu:~# echo "qiyacloud" > /mnt/myfs/hello
Copy the code

That’s success. Hellofs-server received a write request.

root@ubuntu:~/code/gopher/src/myfs# ./hellofs-server 
2021/06/20 11:18:51 start
......
2021/06/20 11:25:50 hellfs server get req=> write
Copy the code

This also shows that the distributed node direct network communication is successful.

Ok, now let’s read the Hello file and see what it has.

root@ubuntu:~# cat /mnt/myfs/hello 
qiyacloud
Copy the code

Read the correct data, “qiyacloud” is what we wrote earlier.

Here’s the point: Ok, now explore the ultimate question, where does the data we write live?

We find the directory where the Hello-server process resides and find a file qiya.hellofs.001 in the directory.

root@ubuntu:~/code/gopher/src/myfs# ls -l
total 15076
...
-rwxr-xr-x 1 root root 7459058 Jun 20 11:18 hellofs-server
-rw-r--r-- 1 root root      16 Jun 20 11:25 qiya.hellofs.001
Copy the code

Let’s cat through this file and see what’s inside.

root@ubuntu:~/code/gopher/src/myfs# cat qiya.hellofs.001 
cWl5YWNsb3VkCg==
Copy the code

This file stores the string “cWl5YWNsb3VkCg==”, which is the Base64 encoded content of “qiyacloud”.

So, we observed two things:

  1. When we write echo “qiyacloud” to a local file, we find that the string is stored across the kernel, across the nodehello-serverOf the directory in whichqiya.hellofs.001File;
  2. “Qiyacloud” is not stored in clear text, but is stored in a file encrypted by the Base64 algorithm, but this behavior is not perceived by the user;

At this point, our distributed encrypted file system is complete.

Take a look at the complete IO request flow as shown below:

Original is not easy, pay attention to the public number: Qiya cloud storage, get more dry goods.

conclusion

  1. FileimplementationWriteAfter the interface, the VFS can take over the logic to call write, so that the file system can write.
  2. Using Base64 encoding to encrypt user data so that the raw data is invisible is easy;
  3. Self-made HellFS uses a C/S architecture for a minimalist, distributed look;
  4. hellofs-clientFrom the kernel/dev/fuseThe FUSE protocol is parsed, and the IO request is forwarded to the Server node via Http.
  5. hellofs-serverreceivehellofs-clientData transmitted over the network is encrypted and stored in a file. When you read the request, you read the file, you decrypt base64, and you send it backhellofs-client;

Afterword.

Self-made FS demo is a minimalist case, the core is to let children intuitively feel some concepts of files, distributed, encryption, omit complex exception processing. Note that if you’re coding in a production environment, it’s much more than that.

In order to highlight the focus of the article, reduce the length of the code, the article only posted the key fragments. Hellofs-client. go, hellofs-server.go

So, distributed encryption helloFS you up and running?

Original is not easy, pay attention to the public number: Qiya cloud storage, get more dry goods.