1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file
4 
5 #pragma once
6 
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <unistd.h>
10 
11 #include <fbl/function.h>
12 #include <fbl/string.h>
13 #include <fbl/vector.h>
14 #include <fs-management/mount.h>
15 #include <fvm/fvm.h>
16 #include <lib/zx/time.h>
17 #include <zircon/status.h>
18 #include <zircon/syscalls.h>
19 #include <zircon/types.h>
20 
21 // Macro for printing more information in error logs.
22 // "[File:Line] Error(error_name): Message\n"
23 #define LOG_ERROR(error_code, msg_fmt, ...)        \
24     fprintf(stderr, "[%s:%d] Error(%s): " msg_fmt, \
25             __FILE__, __LINE__, zx_status_get_string(error_code), ##__VA_ARGS__)
26 
27 // Macro for printing more information in stdout.
28 // "[File:Line] Info: Message\n"
29 #define LOG_INFO(msg_fmt, ...)                \
30     fprintf(stdout, "[%s:%d] Info: " msg_fmt, \
31             __FILE__, __LINE__, ##__VA_ARGS__)
32 
33 namespace fs_test_utils {
34 
35 constexpr size_t kPathSize = PATH_MAX;
36 
37 constexpr size_t kFvmBlockSize = FVM_BLOCK_SIZE;
38 
39 // TODO(gevalentno): when ZX-2013 is resolved, make MemFs setup and teardown
40 // part of the test fixture and remove RunWithMemFs.
41 // Workaround that provides a MemFs per process, since it cannot be unbinded
42 // from the process namespace yet.
43 int RunWithMemFs(const fbl::Function<int()>& main_fn);
44 
45 // Available options for the test fixture.
46 //
47 // Note: use_ramdisk and block_device_path are mutually exclusive.
48 struct FixtureOptions {
49 
DefaultFixtureOptions50     static FixtureOptions Default(disk_format_t format) {
51         FixtureOptions options;
52         options.use_ramdisk = true;
53         options.ramdisk_block_size = 512;
54         options.ramdisk_block_count = zx_system_get_physmem() / (2 * options.ramdisk_block_size);
55         options.use_fvm = false;
56         options.fvm_slice_size = kFvmBlockSize * (2 << 10);
57         options.fs_type = format;
58         options.seed = static_cast<unsigned int>(zx::ticks::now().get());
59         return options;
60     }
61 
62     // Returns true if the options are valid.
63     // When invalid |err_string| will be populated with a human readable error description.
64     bool IsValid(fbl::String* err_description) const;
65 
66     // Path to the block device to use.
67     fbl::String block_device_path = "";
68 
69     // If true a ramdisk will be created and shared for the test.
70     bool use_ramdisk = false;
71 
72     // Number of blocks the ramdisk will contain.
73     size_t ramdisk_block_count = 0;
74 
75     // Size of the blocks the ramdisk will have.
76     size_t ramdisk_block_size = 0;
77 
78     // If true an fvm will be mounted on the device, and the filesystem will be
79     // mounted on top of a fresh partition.
80     bool use_fvm = false;
81 
82     // Size of each slice of the created fvm.
83     size_t fvm_slice_size = 0;
84 
85     // Type of filesystem to mount.
86     disk_format_t fs_type;
87 
88     // Format the device device with the given |fs_type|. This is useful
89     // when a test requires a block_device(and fvm) for tests.
90     bool fs_format = true;
91 
92     // Mount the device in |Fixture::fs_path()|. Format is auto detected.
93     bool fs_mount = true;
94 
95     // Seed for pseudo random number generator.
96     unsigned int seed = 0;
97 };
98 
99 // Provides a base fixture for File system tests.
100 // In main(a.k.a run_all_unittests):
101 //
102 // RunWithMemFs([argc, argv] () {  // Sets up then cleans up Local MemFs.
103 //   return run_all_unittests(argc, argv) ? 0: 1;
104 // }
105 class Fixture {
106 public:
107     Fixture() = delete;
108     explicit Fixture(const FixtureOptions& options);
109     Fixture(const Fixture&) = delete;
110     Fixture(Fixture&&) = delete;
111     Fixture& operator=(const Fixture&) = delete;
112     Fixture& operator=(Fixture&&) = delete;
113     ~Fixture();
114 
115     // Returns the options used by this fixture.
options()116     const FixtureOptions& options() const {
117         return options_;
118     }
119 
120     // Returns the path to the block device hosting the FS.
block_device_path()121     const fbl::String& block_device_path() const {
122         return block_device_path_;
123     }
124 
125     // Returns the path to the FVM partition created for the block device
126     // hosting the FS. Will return empty if !options_.use_fvm.
partition_path()127     const fbl::String& partition_path() const {
128         return partition_path_;
129     }
130 
131     // Returns either the block_device path or partition_path if using fvm.
GetFsBlockDevice()132     const fbl::String& GetFsBlockDevice() const {
133         return (options_.use_fvm) ? partition_path_ : block_device_path_;
134     }
135 
136     // Returns the path where the filesystem was mounted.
fs_path()137     const fbl::String& fs_path() const {
138         return fs_path_;
139     }
140 
141     // Returns a seed to be used along the test, for rand_r calls.
mutable_seed()142     unsigned int* mutable_seed() {
143         return &seed_;
144     }
145 
146     // Unmounts the FS from fs_path.
147     zx_status_t Umount();
148 
149     // Mounts the FsBlockDevice into fs_path.
150     zx_status_t Mount();
151 
152     // Umounts and then Mounts the device.
Remount()153     zx_status_t Remount() {
154         zx_status_t res = Umount();
155         if (res != ZX_OK) {
156             return res;
157         }
158         res = Mount();
159         return res;
160     }
161 
162     // Sets up MemFs and Ramdisk, allocating resources for the tests.
163     zx_status_t SetUpTestCase();
164 
165     // Formats the block device with the required type, creates a fvm, and mounts
166     // the fs.
167     zx_status_t SetUp();
168 
169     // Cleans up the block device by reformatting it, destroys the fvm and
170     // unmounts the fs.
171     zx_status_t TearDown();
172 
173     // Destroys the ramdisk, MemFs will die with the process. This should be
174     // called after all tests finished execution to free resources.
175     zx_status_t TearDownTestCase();
176 
177 private:
178     FixtureOptions options_;
179 
180     // State of the resources allocated by the fixture.
181     enum class ResourceState {
182         kUnallocated,
183         kAllocated,
184         kFreed,
185     };
186 
187     // Path to the block device hosting the mounted FS.
188     fbl::String block_device_path_;
189 
190     // When using fvm, the FS will be mounted here.
191     fbl::String partition_path_;
192 
193     // The root path where FS is mounted.
194     fbl::String fs_path_;
195 
196     unsigned int seed_;
197 
198     // Keep track of the resource allocation during the setup teardown process,
199     // to avoid leaks, or unnecessary errors when trying to free resources, that
200     // may have never been allocated in first place.
201     ResourceState fs_state_ = ResourceState::kUnallocated;
202     ResourceState fvm_state_ = ResourceState::kUnallocated;
203     ResourceState ramdisk_state_ = ResourceState::kUnallocated;
204 };
205 
206 } // namespace fs_test_utils
207