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  }