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 #include <fs-management/mount.h>
6 
7 #include <errno.h>
8 #include <dirent.h>
9 #include <fcntl.h>
10 #include <string.h>
11 #include <unistd.h>
12 
13 #include <fbl/alloc_checker.h>
14 #include <fbl/auto_call.h>
15 #include <fbl/unique_fd.h>
16 #include <fbl/unique_ptr.h>
17 #include <fs-management/fvm.h>
18 #include <fvm/fvm.h>
19 #include <lib/fdio/limits.h>
20 #include <lib/fdio/util.h>
21 #include <lib/fdio/vfs.h>
22 #include <lib/fdio/watcher.h>
23 #include <fs/client.h>
24 #include <zircon/compiler.h>
25 #include <zircon/device/block.h>
26 #include <zircon/device/vfs.h>
27 #include <zircon/processargs.h>
28 #include <zircon/syscalls.h>
29 
30 #include <utility>
31 
32 namespace {
33 // Checks that |fd| is a partition which matches |uniqueGUID| and |typeGUID|.
34 // If either is null, it doesn't compare |fd| with that guid.
35 // At least one of the GUIDs must be non-null.
is_partition(int fd,const uint8_t * uniqueGUID,const uint8_t * typeGUID)36 static bool is_partition(int fd, const uint8_t* uniqueGUID, const uint8_t* typeGUID) {
37     ZX_ASSERT(uniqueGUID || typeGUID);
38     uint8_t buf[GUID_LEN];
39     if (fd < 0) {
40         return false;
41     }
42     if (typeGUID) {
43         if (ioctl_block_get_type_guid(fd, buf, sizeof(buf)) < 0) {
44             return false;
45         } else if (memcmp(buf, typeGUID, GUID_LEN) != 0) {
46             return false;
47         }
48     }
49     if (uniqueGUID) {
50         if (ioctl_block_get_partition_guid(fd, buf, sizeof(buf)) < 0) {
51             return false;
52         } else if (memcmp(buf, uniqueGUID, GUID_LEN) != 0) {
53             return false;
54         }
55     }
56     return true;
57 }
58 
59 constexpr char kBlockDevPath[] = "/dev/class/block/";
60 
61 }  // namespace anonymous
62 
fvm_init(int fd,size_t slice_size)63 zx_status_t fvm_init(int fd, size_t slice_size) {
64     if (slice_size % FVM_BLOCK_SIZE != 0) {
65         // Alignment
66         return ZX_ERR_INVALID_ARGS;
67     } else if ((slice_size * VSLICE_MAX) / VSLICE_MAX != slice_size) {
68         // Overflow
69         return ZX_ERR_INVALID_ARGS;
70     }
71 
72     // The metadata layout of the FVM is dependent on the
73     // size of the FVM's underlying partition.
74     block_info_t block_info;
75     ssize_t rc = ioctl_block_get_info(fd, &block_info);
76 
77     if (rc < 0) {
78         return static_cast<zx_status_t>(rc);
79     } else if (rc != sizeof(block_info)) {
80         return ZX_ERR_BAD_STATE;
81     } else if (slice_size == 0 || slice_size % block_info.block_size) {
82         return ZX_ERR_BAD_STATE;
83     }
84 
85     size_t disk_size = block_info.block_count * block_info.block_size;
86     size_t metadata_size = fvm::MetadataSize(disk_size, slice_size);
87 
88     fbl::unique_ptr<uint8_t[]> mvmo(new uint8_t[metadata_size * 2]);
89     // Clear entire primary copy of metadata
90     memset(mvmo.get(), 0, metadata_size);
91 
92     // Superblock
93     fvm::fvm_t* sb = reinterpret_cast<fvm::fvm_t*>(mvmo.get());
94     sb->magic = FVM_MAGIC;
95     sb->version = FVM_VERSION;
96     sb->pslice_count = (disk_size - metadata_size * 2) / slice_size;
97     sb->slice_size = slice_size;
98     sb->fvm_partition_size = disk_size;
99     sb->vpartition_table_size = fvm::kVPartTableLength;
100     sb->allocation_table_size = fvm::AllocTableLength(disk_size, slice_size);
101     sb->generation = 0;
102 
103     if (sb->pslice_count == 0) {
104         return ZX_ERR_NO_SPACE;
105     }
106 
107     fvm_update_hash(mvmo.get(), metadata_size);
108 
109     const void* backup = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(mvmo.get()) +
110                                                  metadata_size);
111     zx_status_t status = fvm_validate_header(mvmo.get(), backup, metadata_size, nullptr);
112     if (status != ZX_OK) {
113         return status;
114     }
115 
116     if (lseek(fd, 0, SEEK_SET) < 0) {
117         return ZX_ERR_BAD_STATE;
118     }
119     // Write to primary copy.
120     if (write(fd, mvmo.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
121         return ZX_ERR_BAD_STATE;
122     }
123     // Write to secondary copy, to overwrite any previous FVM metadata copy that
124     // could be here.
125     if (write(fd, mvmo.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
126         return ZX_ERR_BAD_STATE;
127     }
128 
129     return ZX_OK;
130 }
131 
132 // Helper function to overwrite FVM given the slice_size
fvm_overwrite(const char * path,size_t slice_size)133 zx_status_t fvm_overwrite(const char* path, size_t slice_size) {
134     int fd = open(path, O_RDWR);
135 
136     if (fd <= 0) {
137         fprintf(stderr, "fvm_destroy: Failed to open block device\n");
138         return -1;
139     }
140 
141     block_info_t block_info;
142     ssize_t rc = ioctl_block_get_info(fd, &block_info);
143 
144     if (rc < 0 || rc != sizeof(block_info)) {
145         printf("fvm_destroy: Failed to query block device\n");
146         return -1;
147     }
148 
149     size_t disk_size = block_info.block_count * block_info.block_size;
150     size_t metadata_size = fvm::MetadataSize(disk_size, slice_size);
151 
152     fbl::AllocChecker ac;
153     fbl::unique_ptr<uint8_t[]> buf(new (&ac) uint8_t[metadata_size]);
154     if (!ac.check()) {
155         printf("fvm_destroy: Failed to allocate buffer\n");
156         return -1;
157     }
158 
159     memset(buf.get(), 0, metadata_size);
160 
161     if (lseek(fd, 0, SEEK_SET) < 0) {
162         return -1;
163     }
164 
165     // Write to primary copy.
166     if (write(fd, buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
167         fprintf(stderr, "fvm_overwrite: Failed to write metadata\n");
168         return -1;
169     }
170 
171     // Write to backup copy
172     if (write(fd, buf.get(), metadata_size) != static_cast<ssize_t>(metadata_size)) {
173         fprintf(stderr, "fvm_overwrite: Failed to write metadata (secondary)\n");
174        return -1;
175     }
176 
177     if (ioctl_block_rr_part(fd) != 0) {
178         return -1;
179     }
180 
181     close(fd);
182     return ZX_OK;
183 }
184 
185 // Helper function to destroy FVM
fvm_destroy(const char * path)186 zx_status_t fvm_destroy(const char* path) {
187     char driver_path[PATH_MAX];
188     if (strlcpy(driver_path, path, sizeof(driver_path)) >= sizeof(driver_path)) {
189         return ZX_ERR_BAD_PATH;
190     }
191     if (strlcat(driver_path, "/fvm", sizeof(driver_path)) >= sizeof(driver_path)) {
192         return ZX_ERR_BAD_PATH;
193     }
194     fbl::unique_fd driver_fd(open(driver_path, O_RDWR));
195 
196     if (!driver_fd) {
197         fprintf(stderr, "fvm_destroy: Failed to open fvm driver: %s\n", driver_path);
198         return -1;
199     }
200 
201     fvm_info_t fvm_info;
202     ssize_t r;
203     if ((r = ioctl_block_fvm_query(driver_fd.get(), &fvm_info)) <= 0) {
204         fprintf(stderr, "fvm_destroy: Failed to query fvm: %ld\n", r);
205         return -1;
206     }
207 
208     return fvm_overwrite(path, fvm_info.slice_size);
209 }
210 
211 // Helper function to allocate, find, and open VPartition.
fvm_allocate_partition(int fvm_fd,const alloc_req_t * request)212 int fvm_allocate_partition(int fvm_fd, const alloc_req_t* request) {
213     ssize_t r;
214     if ((r = ioctl_block_fvm_alloc_partition(fvm_fd, request)) != ZX_OK) {
215         return -1;
216     }
217 
218     return open_partition(request->guid, request->type, ZX_SEC(10), nullptr);
219 }
220 
open_partition(const uint8_t * uniqueGUID,const uint8_t * typeGUID,zx_duration_t timeout,char * out_path)221 int open_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID,
222                    zx_duration_t timeout, char* out_path) {
223     ZX_ASSERT(uniqueGUID || typeGUID);
224 
225     typedef struct {
226         const uint8_t* guid;
227         const uint8_t* type;
228         char* out_path;
229         fbl::unique_fd out_partition;
230     } alloc_helper_info_t;
231 
232     alloc_helper_info_t info;
233     info.guid = uniqueGUID;
234     info.type = typeGUID;
235     info.out_path = out_path;
236     info.out_partition.reset();
237 
238     auto cb = [](int dirfd, int event, const char* fn, void* cookie) {
239         if (event != WATCH_EVENT_ADD_FILE) {
240             return ZX_OK;
241         } else if ((strcmp(fn, ".") == 0) || strcmp(fn, "..") == 0) {
242             return ZX_OK;
243         }
244         auto info = static_cast<alloc_helper_info_t*>(cookie);
245         fbl::unique_fd devfd(openat(dirfd, fn, O_RDWR));
246         if (!devfd) {
247             return ZX_OK;
248         }
249         if (is_partition(devfd.get(), info->guid, info->type)) {
250             info->out_partition = std::move(devfd);
251             if (info->out_path) {
252                 strcpy(info->out_path, kBlockDevPath);
253                 strcat(info->out_path, fn);
254             }
255             return ZX_ERR_STOP;
256         }
257         return ZX_OK;
258     };
259 
260     DIR* dir = opendir(kBlockDevPath);
261     if (dir == nullptr) {
262         return -1;
263     }
264 
265     zx_time_t deadline = zx_deadline_after(timeout);
266     if (fdio_watch_directory(dirfd(dir), cb, deadline, &info) != ZX_ERR_STOP) {
267         return -1;
268     }
269     closedir(dir);
270     return info.out_partition.release();
271 }
272 
destroy_partition(const uint8_t * uniqueGUID,const uint8_t * typeGUID)273 zx_status_t destroy_partition(const uint8_t* uniqueGUID, const uint8_t* typeGUID) {
274     char path[PATH_MAX];
275     fbl::unique_fd fd(open_partition(uniqueGUID, typeGUID, 0, path));
276 
277     if (!fd) {
278         return ZX_ERR_IO;
279     }
280 
281     return static_cast<zx_status_t>(ioctl_block_fvm_destroy_partition(fd.get()));
282 }
283