github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/diskimage/diskimage_linux.go (about) 1 // +build linux 2 3 /* 4 Copyright 2019 Mirantis 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package diskimage 20 21 import ( 22 "errors" 23 "sort" 24 "unsafe" 25 ) 26 27 /* 28 #cgo CFLAGS: -DGUESTFS_PRIVATE=1 29 #cgo pkg-config: libguestfs 30 31 #include <stdio.h> 32 #include <string.h> 33 #include <libgen.h> 34 #include <stdlib.h> 35 #include <linux/limits.h> 36 #include "guestfs.h" 37 38 #define ERROR_MSG_SIZE 1024 39 40 typedef struct _g_wrapper { 41 guestfs_h *g; 42 char error[ERROR_MSG_SIZE]; 43 char **devs, **parts, **files; 44 char *file_content; 45 int launched; 46 } g_wrapper; 47 48 typedef struct _g_file { 49 const char* path; 50 const char *content; 51 size_t size; 52 } g_file; 53 54 static void update_error(g_wrapper* w, const char* msg, int use_g_err) 55 { 56 int n, p; 57 const char *g_err = 0, *err; 58 if (*w->error) 59 return; 60 61 if (use_g_err) 62 g_err = guestfs_last_error(w->g); 63 if (!g_err && !msg) 64 return; 65 66 p = strlen(w->error); 67 if (p >= ERROR_MSG_SIZE-2) 68 return; 69 w->error[p++] = '\n'; 70 71 w->error[ERROR_MSG_SIZE-1] = 0; 72 if (msg && g_err) 73 snprintf(w->error+p, ERROR_MSG_SIZE-p, "%s: %s", msg, g_err); 74 else if (msg) 75 strncpy(w->error+p, msg, ERROR_MSG_SIZE-1-p); 76 else if (g_err) 77 strncpy(w->error+p, g_err, ERROR_MSG_SIZE-1-p); 78 } 79 80 g_wrapper* g_wrapper_new() 81 { 82 g_wrapper* w = malloc(sizeof(g_wrapper)); 83 *w->error = 0; 84 w->devs = w->parts = w->files = 0; 85 w->file_content = 0; 86 w->launched = 0; 87 return w; 88 } 89 90 const char* g_wrapper_error(g_wrapper* w) 91 { 92 return w->error[0] ? w->error : 0; 93 } 94 95 int g_wrapper_setup(g_wrapper* w, const char* path, int trace) 96 { 97 w->g = guestfs_create_flags(0); 98 if (!w->g) { 99 update_error(w, "guestfs_create_flags()", 1); 100 return -1; 101 } 102 103 guestfs_set_trace(w->g, trace); 104 105 if (guestfs_add_drive_opts(w->g, path, 106 GUESTFS_ADD_DRIVE_OPTS_FORMAT, "qcow2", 107 -1)) { 108 update_error(w, "guestfs_add_drive_opts()", 1); 109 return -1; 110 } 111 112 if (guestfs_launch(w->g) < 0) { 113 update_error(w, "guestfs_launch()", 1); 114 return -1; 115 } 116 w->launched = 1; 117 118 w->devs = guestfs_list_devices(w->g); 119 if (!w->devs) { 120 update_error(w, "guestfs_list_devices()", 1); 121 return -1; 122 } 123 124 if (!w->devs[0] || w->devs[1]) { 125 update_error(w, "exactly one device is expected", 0); 126 return -1; 127 } 128 129 return 0; 130 } 131 132 int g_wrapper_close(g_wrapper* w) 133 { 134 int r = 0; 135 if (!w->g) { 136 update_error(w, "guestfs handle already closed", 0); 137 return -1; 138 } 139 140 if (w->devs) 141 while (*w->devs) free(*w->devs++); 142 if (w->parts) 143 while (*w->parts) free(*w->parts++); 144 if (w->files) 145 while (*w->files) free(*w->files++); 146 147 if (w->launched && guestfs_shutdown(w->g) < 0) { 148 update_error(w, "guestfs_shutdown()", 1); 149 r = -1; 150 } 151 152 guestfs_close(w->g); 153 w->g = 0; 154 w->devs = w->parts = w->files = 0; 155 w->file_content = 0; 156 w->launched = 0; 157 return r; 158 } 159 160 static int g_wrapper_part_disk(g_wrapper* w) 161 { 162 if (!w->g || !w->devs) { 163 update_error(w, "guestfs setup not done", 0); 164 return -1; 165 } 166 167 if (guestfs_part_disk(w->g, w->devs[0], "mbr") < 0) { 168 update_error(w, "guestfs_part_disk()", 1); 169 return -1; 170 } 171 172 return 0; 173 } 174 175 static int g_wrapper_get_partitions(g_wrapper* w) 176 { 177 if (!w->g || !w->devs) { 178 update_error(w, "guestfs setup not done", 0); 179 return -1; 180 } 181 182 w->parts = guestfs_list_partitions(w->g); 183 if (!w->parts) { 184 update_error(w, "guestfs_list_partitions()", 1); 185 return -1; 186 } 187 188 if (!w->parts[0]) { 189 update_error(w, "at least one partition is expected", 0); 190 return -1; 191 } 192 193 return 0; 194 } 195 196 int g_wrapper_format(g_wrapper* w) 197 { 198 if (g_wrapper_part_disk(w) < 0 || g_wrapper_get_partitions(w) < 0) 199 return -1; 200 201 if (guestfs_mkfs(w->g, "ext4", w->parts[0]) < 0) { 202 update_error(w, "guestfs_mkfs()", 1); 203 return -1; 204 } 205 206 return 0; 207 } 208 209 int g_wrapper_put(g_wrapper* w, int n, g_file* files) 210 { 211 static char path[PATH_MAX+1]; 212 213 if (g_wrapper_get_partitions(w) < 0) 214 return -1; 215 216 if (guestfs_mount(w->g, w->parts[0], "/")) { 217 update_error(w, "guestfs_mount()", 1); 218 return -1; 219 } 220 221 while (n--) { 222 if (strlen(files->path) > PATH_MAX) { 223 update_error(w, "file path too long", 0); 224 return -1; 225 } 226 227 // dirname may modify the string, so we need to clone path 228 strcpy(path, files->path); 229 if (guestfs_mkdir_p(w->g, dirname(path)) < 0) { 230 update_error(w, "guestfs_mkdir_p()", 1); 231 return -1; 232 } 233 234 if (guestfs_write(w->g, files->path, files->content, files->size) < 0) { 235 update_error(w, "guestfs_write()", 1); 236 return -1; 237 } 238 239 files++; 240 } 241 242 return 0; 243 } 244 245 char** g_wrapper_ls(g_wrapper* w, const char* dir) 246 { 247 if (w->files) 248 while (*w->files) free(*w->files++); 249 w->files = 0; 250 251 if (g_wrapper_get_partitions(w) < 0) 252 return 0; 253 254 if (guestfs_mount(w->g, w->parts[0], "/")) { 255 update_error(w, "guestfs_mount()", 1); 256 return 0; 257 } 258 259 w->files = guestfs_ls(w->g, dir); 260 if (!w->files) { 261 update_error(w, "guestfs_ls()", 1); 262 return 0; 263 } 264 265 return w->files; 266 } 267 268 char* g_wrapper_cat(g_wrapper* w, const char* path) 269 { 270 if (w->file_content) 271 free(w->file_content); 272 w->file_content = 0; 273 274 if (g_wrapper_get_partitions(w) < 0) 275 return 0; 276 277 if (guestfs_mount(w->g, w->parts[0], "/")) { 278 update_error(w, "guestfs_mount()", 1); 279 return 0; 280 } 281 282 w->file_content = guestfs_cat(w->g, path); 283 if (w->file_content == 0) { 284 update_error(w, "guestfs_cat()", 1); 285 return 0; 286 } 287 288 return w->file_content; 289 } 290 */ 291 import "C" 292 293 func handleGuestfsError(w *C.g_wrapper, r int) error { 294 if r == 0 { 295 return nil 296 } 297 298 if errStr := C.g_wrapper_error(w); errStr != nil { 299 return errors.New(C.GoString(errStr)) 300 } 301 302 // this shouldn't happen, but let's be safe here 303 return errors.New("unknown libguestfs error") 304 } 305 306 func callWithGWrapper(imagePath string, toCall func(*C.g_wrapper) int) error { 307 w, err := C.g_wrapper_new() 308 if err != nil { 309 return err 310 } 311 312 cPath := C.CString(imagePath) 313 defer func() { 314 C.free(unsafe.Pointer(cPath)) 315 if w != nil { 316 C.g_wrapper_close(w) 317 } 318 }() 319 320 r := int(C.g_wrapper_setup(w, cPath, C.int(1))) 321 if err = handleGuestfsError(w, r); err != nil { 322 return err 323 } 324 325 if err = handleGuestfsError(w, toCall(w)); err != nil { 326 return err 327 } 328 329 curW := w 330 w = nil 331 return handleGuestfsError(curW, int(C.g_wrapper_close(curW))) 332 } 333 334 // FormatDisk partitions the specified image file by writing an MBR with 335 // a single partition and then formatting that partition as an ext4 filesystem. 336 func FormatDisk(imagePath string) error { 337 return callWithGWrapper(imagePath, func(w *C.g_wrapper) int { 338 return int(C.g_wrapper_format(w)) 339 }) 340 } 341 342 // Put writes files to the image, making all the necessary subdirs 343 func Put(imagePath string, files map[string][]byte) error { 344 return callWithGWrapper(imagePath, func(w *C.g_wrapper) int { 345 filenames := make([]string, 0, len(files)) 346 for filename := range files { 347 filenames = append(filenames, filename) 348 } 349 sort.Strings(filenames) 350 gFiles := make([]C.g_file, len(filenames)) 351 for n, filename := range filenames { 352 gFiles[n].path = C.CString(filename) 353 content := files[filename] 354 gFiles[n].content = C.CString(string(content)) 355 gFiles[n].size = C.size_t(len(content)) 356 } 357 defer func() { 358 for _, f := range gFiles { 359 C.free(unsafe.Pointer(f.path)) 360 C.free(unsafe.Pointer(f.content)) 361 } 362 }() 363 return int(C.g_wrapper_put(w, C.int(len(gFiles)), (*C.g_file)(unsafe.Pointer(&gFiles[0])))) 364 }) 365 } 366 367 // ListFiles returns the list of files in the specified directory. 368 func ListFiles(imagePath, dir string) ([]string, error) { 369 var r []string 370 if err := callWithGWrapper(imagePath, func(w *C.g_wrapper) int { 371 cDir := C.CString(dir) 372 defer func() { 373 C.free(unsafe.Pointer(cDir)) 374 }() 375 strs := C.g_wrapper_ls(w, cDir) 376 if strs == nil { 377 return -1 378 } 379 for *strs != nil { 380 r = append(r, C.GoString(*strs)) 381 strs = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(strs)) + unsafe.Sizeof(*strs))) 382 } 383 return 0 384 }); err != nil { 385 return nil, err 386 } 387 388 return r, nil 389 } 390 391 // Cat returns the contents of the file as a string. 392 // NOTE: this function is only suited for text files and doesn't 393 // handle zero bytes properly. 394 func Cat(imagePath, filePath string) (string, error) { 395 var r string 396 if err := callWithGWrapper(imagePath, func(w *C.g_wrapper) int { 397 cPath := C.CString(filePath) 398 defer func() { 399 C.free(unsafe.Pointer(cPath)) 400 }() 401 str := C.g_wrapper_cat(w, cPath) 402 if str == nil { 403 return -1 404 } 405 r = C.GoString(str) 406 return 0 407 }); err != nil { 408 return "", err 409 } 410 411 return r, nil 412 }