Esempio n. 1
0
func commandSocketHandle(c net.Conn) {
	defer c.Close()

	enc := json.NewEncoder(c)
	dec := json.NewDecoder(c)

	var err error

	for err == nil {
		var cmd *minicli.Command

		cmd, err = readLocalCommand(dec)
		if err != nil {
			break
		}

		if cmd == nil {
			err = sendLocalResp(enc, nil, false)
			continue
		}

		// HAX: Don't record the read command
		if hasCommand(cmd, "read") {
			cmd.SetRecord(false)
		}

		// HAX: Work around so that we can add the more boolean.
		var prev minicli.Responses

		// Keep sending until we hit the first error, then just consume the
		// channel to ensure that we release any locks acquired by cmd.
		for resp := range RunCommands(cmd) {
			if prev != nil && err == nil {
				err = sendLocalResp(enc, prev, true)
			} else if err != nil && len(resp) > 0 {
				log.Info("dropping resp from %v", resp[0].Host)
			}

			prev = resp
		}

		if err == nil {
			err = sendLocalResp(enc, prev, false)
		}
	}

	// finally, log the error, if there was one
	if err == nil || err == io.EOF {
		log.Infoln("command client disconnected")
	} else if err != nil && strings.Contains(err.Error(), "write: broken pipe") {
		log.Infoln("command client disconnected without waiting for responses")
	} else if err != nil {
		log.Errorln(err)
	}
}
Esempio n. 2
0
func dnsmasqKill(id int) error {
	pid := dnsmasqPID(id)
	log.Debug("dnsmasq id %v has pid %v", id, pid)
	if pid == -1 {
		return fmt.Errorf("invalid id")
	}

	var sOut bytes.Buffer
	var sErr bytes.Buffer
	p := process("kill")
	cmd := &exec.Cmd{
		Path: p,
		Args: []string{
			p,
			fmt.Sprintf("%v", pid),
		},
		Env:    nil,
		Dir:    "",
		Stdout: &sOut,
		Stderr: &sErr,
	}
	log.Infoln("killing dnsmasq server:", pid)
	err := cmd.Run()
	if err != nil {
		return err
	}
	return nil
}
Esempio n. 3
0
// Walks the f_base directory and kills procs read from any qemu or
// dnsmasq pid files
func nukeWalker(path string, info os.FileInfo, err error) error {
	if err != nil {
		return nil
	}

	log.Debug("walking file: %v", path)

	switch info.Name() {
	case "qemu.pid", "dnsmasq.pid":
		d, err := ioutil.ReadFile(path)
		t := strings.TrimSpace(string(d))
		log.Debug("found pid: %v", t)
		if err != nil {
			return err
		}

		args := []string{
			"kill",
			t,
		}
		log.Infoln("killing process:", t)

		out, err := processWrapper(args...)
		if err != nil {
			log.Error("%v: %v", err, out)
		}
	}
	return nil
}
Esempio n. 4
0
func nukeBridges() {
	bNames := nukeBridgeNames(false)
	for _, b := range bNames {
		var sOut bytes.Buffer
		var sErr bytes.Buffer

		p := process("ovs")
		cmd := &exec.Cmd{
			Path: p,
			Args: []string{
				p,
				"del-br",
				b,
			},
			Env:    nil,
			Dir:    "",
			Stdout: &sOut,
			Stderr: &sErr,
		}
		log.Infoln("removing bridge:", b)
		//err := cmd.Run()
		err := cmdTimeout(cmd, OVS_TIMEOUT)
		if err != nil {
			log.Error("%v: %v", err, sErr.String())
		}
	}
}
Esempio n. 5
0
func (vms VMs) flush() {
	for i, vm := range vms {
		if vm.State()&(VM_QUIT|VM_ERROR) != 0 {
			log.Infoln("deleting VM: ", i)
			delete(vms, i)
		}
	}
}
Esempio n. 6
0
func (vms VMs) flush() {
	stateMask := VM_QUIT | VM_ERROR
	for i, vm := range vms {
		if vm.State&stateMask != 0 {
			log.Infoln("deleting VM: ", i)
			delete(vms, i)
		}
	}
}
Esempio n. 7
0
func (vms VMs) flush() {
	vmLock.Lock()
	defer vmLock.Unlock()

	for i, vm := range vms {
		if vm.GetState()&(VM_QUIT|VM_ERROR) != 0 {
			log.Infoln("deleting VM: ", i)
			delete(vms, i)
		}
	}
}
Esempio n. 8
0
func sendLocalResp(enc *json.Encoder, resp minicli.Responses, more bool) error {
	log.Infoln("sending resp:", resp)
	r := localResponse{
		More: more,
	}
	if resp != nil {
		r.Resp = resp
		r.Rendered = resp.String()
	}

	return enc.Encode(&r)
}
Esempio n. 9
0
func commandSocketHandle(c net.Conn) {
	var err error

	enc := json.NewEncoder(c)
	dec := json.NewDecoder(c)

outer:
	for err == nil {
		var cmd *minicli.Command
		cmd, err = readLocalCommand(dec)
		if err != nil {
			if err != io.EOF {
				// Must be incompatible versions of minimega... F***
				log.Errorln(err)
			}
			break
		}
		err = nil

		var prevResp minicli.Responses

		if cmd != nil {
			// HAX: Don't record the read command
			if hasCommand(cmd, "read") {
				cmd.Record = false
			}

			// HAX: Work around so that we can add the more boolean
			for resp := range runCommand(cmd) {
				if prevResp != nil {
					err = sendLocalResp(enc, prevResp, true)
					if err != nil {
						break outer
					}
				}

				prevResp = resp
			}
		}
		if err == nil {
			err = sendLocalResp(enc, prevResp, false)
		}
	}

	if err != nil {
		if err == io.EOF {
			log.Infoln("command client disconnected")
		} else {
			log.Errorln(err)
		}
	}
}
Esempio n. 10
0
// runCommand runs a command through a JSON pipe.
func runCommand(cmd Command) chan *localResponse {
	conn, err := net.Dial("unix", path.Join(*f_minimega, "minimega"))
	if err != nil {
		log.Errorln(err)
		return nil
	}

	enc := json.NewEncoder(conn)
	dec := json.NewDecoder(conn)

	log.Debug("encoding command: %v", cmd)

	err = enc.Encode(cmd)
	if err != nil {
		log.Errorln("local command json encode: %v", err)
		return nil
	}

	log.Debugln("encoded command:", cmd)

	respChan := make(chan *localResponse)

	go func() {
		defer close(respChan)

		for {
			var r localResponse
			err = dec.Decode(&r)
			if err != nil {
				if err == io.EOF {
					log.Infoln("server disconnected")
					return
				}

				log.Errorln("local command json decode: %v", err)
				return
			}

			respChan <- &r
			if !r.More {
				log.Debugln("got last message")
				break
			} else {
				log.Debugln("expecting more data")
			}
		}
	}()

	return respChan
}
Esempio n. 11
0
// Overlays copies any overlay directories indicated in c into the build
// directory build_path. Overlays are copied in depth-first order, so that
// the oldest parent overlay data is copied in first. This allows a child
// to overwrite any overlay data created by a parent.
func Overlays(buildPath string, c vmconfig.Config) error {
	// copy the overlays in order
	for i, o := range c.Overlays {
		log.Infoln("copying overlay:", o)

		var sourcePath string
		// check if overlay exists as absolute path or relative to cwd
		if _, err := os.Stat(o); os.IsNotExist(err) {
			// it doesn't, so we'll check relative to config file
			log.Debugln("overlay directory '%v' does not exist as an absolute path or relative to the current working directory.", o)
			var path string
			base := filepath.Base(o)    // get base path of overlay directory
			if i == len(c.Overlays)-1 { // if this is the last overlay, we'll check relative to c.Path
				log.Debugln("non-parent overlay")
				path = filepath.Join(filepath.Dir(c.Path), base)
			} else { // if not, it's a parent overlay and we'll check relative to c.Parents[i]
				log.Debugln("parent overlay")
				path = filepath.Join(filepath.Dir(c.Parents[i]), base)
			}
			log.Debugln("checking path relative to config location: '%v'", path)
			if _, err := os.Stat(path); os.IsNotExist(err) { // check if we can find overlay relative to config file
				return err // nope
			} else { // yep
				sourcePath = path
			}
		} else {
			sourcePath = o
		}

		p := process("cp")
		cmd := exec.Command(p, "-r", "-v", sourcePath+"/.", buildPath)
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			return err
		}
		stderr, err := cmd.StderrPipe()
		if err != nil {
			return err
		}
		log.LogAll(stdout, log.INFO, "cp")
		log.LogAll(stderr, log.ERROR, "cp")

		err = cmd.Run()
		if err != nil {
			return err
		}
	}
	return nil
}
Esempio n. 12
0
func dnsmasqKill(id int) error {
	pid := dnsmasqPID(id)
	log.Debug("dnsmasq id %v has pid %v", id, pid)
	if pid == -1 {
		return fmt.Errorf("invalid id")
	}

	log.Infoln("killing dnsmasq server:", pid)

	_, err := processWrapper("kill", fmt.Sprintf("%v", pid))
	if err != nil {
		return err
	}
	return nil
}
Esempio n. 13
0
func commandSocketStart() {
	l, err := net.Listen("unix", filepath.Join(*f_path, "minirouter"))
	if err != nil {
		log.Fatalln("commandSocketStart: %v", err)
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Error("commandSocketStart: accept: %v", err)
		}
		log.Infoln("client connected")

		go commandSocketHandle(conn)
	}
}
Esempio n. 14
0
func commandSocketStart() {
	l, err := net.Listen("unix", filepath.Join(*f_base, "minimega"))
	if err != nil {
		log.Error("commandSocketStart: %v", err)
		teardown()
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Error("commandSocketStart: accept: %v", err)
		}
		log.Infoln("client connected")

		go commandSocketHandle(conn)
	}
}
Esempio n. 15
0
// runCommand runs a command through a JSON pipe.
func (mm *MinimegaConn) runCommand(cmd *minicli.Command) chan *localResponse {
	err := mm.enc.Encode(*cmd)
	if err != nil {
		log.Errorln("local command gob encode: %v", err)
		return nil
	}
	log.Debugln("encoded command:", cmd)

	respChan := make(chan *localResponse)

	go func() {
		defer close(respChan)

		for {
			var r localResponse
			err = mm.dec.Decode(&r)
			if err != nil {
				if err == io.EOF {
					log.Infoln("server disconnected")
					return
				}

				log.Errorln("local command gob decode: %v", err)
				return
			}

			respChan <- &r
			if !r.More {
				log.Debugln("got last message")
				break
			} else {
				log.Debugln("expecting more data")
			}
		}
	}()

	return respChan
}
Esempio n. 16
0
func nukeWalker(path string, info os.FileInfo, err error) error {
	if err != nil {
		return nil
	}

	log.Debug("walking file: %v", path)

	switch info.Name() {
	case "qemu.pid", "dnsmasq.pid":
		d, err := ioutil.ReadFile(path)
		t := strings.TrimSpace(string(d))
		log.Debug("found pid: %v", t)
		if err != nil {
			return err
		}
		var sOut bytes.Buffer
		var sErr bytes.Buffer

		p := process("kill")
		cmd := &exec.Cmd{
			Path: p,
			Args: []string{
				p,
				t,
			},
			Env:    nil,
			Dir:    "",
			Stdout: &sOut,
			Stderr: &sErr,
		}
		log.Infoln("killing process:", t)
		err = cmd.Run()
		if err != nil {
			log.Error("%v: %v", err, sErr.String())
		}
	}
	return nil
}
Esempio n. 17
0
func localCommand() {
	a := flag.Args()

	log.Debugln("got args:", a)

	command := strings.Join(a, " ")

	// TODO: Need to escape?
	cmd, err := minicli.CompileCommand(command)
	if err != nil {
		log.Fatal(err.Error())
	}

	if cmd == nil {
		log.Debugln("cmd is nil")
		return
	}

	log.Infoln("got command:", cmd)

	mm, err := DialMinimega()
	if err != nil {
		log.Fatalln(err)
	}

	for resp := range mm.runCommand(cmd) {
		if resp.Rendered != "" {
			fmt.Println(resp.Rendered)
		}

		errs := resp.Resp.Error()
		if errs != "" {
			fmt.Fprintln(os.Stderr, errs)
		}
	}
}
Esempio n. 18
0
// aggressively cleanup container cruff, called by the nuke api
func containerNuke() {
	// walk /sys/fs/cgroup/minimega for tasks, killing each one
	err := filepath.Walk(CGROUP_PATH, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		log.Debug("walking file: %v", path)

		switch info.Name() {
		case "tasks":
			d, err := ioutil.ReadFile(path)
			pids := strings.Fields(string(d))
			for _, pid := range pids {
				log.Debug("found pid: %v", pid)

				log.Infoln("killing process:", pid)
				out, err := processWrapper("kill", "-9", pid)
				if err != nil {
					log.Error("%v: %v", err, out)
				}
			}
			// remove the directory for this vm
			dir := filepath.Dir(path)
			err = os.Remove(dir)
			if err != nil {
				log.Errorln(err)
			}
		}
		return nil
	})

	// remove cgroup structure
	err = os.Remove(CGROUP_PATH)
	if err != nil {
		log.Errorln(err)
	}

	// umount megamount_*
	d, err := ioutil.ReadFile("/proc/mounts")
	mounts := strings.Fields(string(d))
	for _, m := range mounts {
		if strings.Contains(m, "megamount") {
			err := syscall.Unmount(m, 0)
			if err != nil {
				log.Error("overlay unmount: %v", err)
			}
		}
	}

	// remove meganet_* from /var/run/netns
	netns, err := ioutil.ReadDir("/var/run/netns")
	if err != nil {
		log.Errorln(err)
	} else {
		for _, n := range netns {
			if strings.Contains(n.Name(), "meganet") {
				err := os.Remove(filepath.Join("/var/run/netns", n.Name()))
				if err != nil {
					log.Errorln(err)
				}
			}
		}
	}
}
Esempio n. 19
0
func ksmSave() {
	log.Infoln("saving ksm values")
	ksmRun = ksmGetIntFromFile(ksmPathRun)
	ksmPagesToScan = ksmGetIntFromFile(ksmPathPagesToScan)
	ksmSleepMillisecs = ksmGetIntFromFile(ksmPathSleepMillisecs)
}
Esempio n. 20
0
func dnsmasqPath() (string, error) {
	path, err := ioutil.TempDir(*f_base, "dnsmasq_")
	log.Infoln("created dnsmasq server path: ", path)
	return path, err
}
Esempio n. 21
0
func main() {
	var err error

	flag.Usage = usage
	flag.Parse()
	if !strings.HasSuffix(*f_base, "/") {
		*f_base += "/"
	}

	if *f_cli {
		doc, err := minicli.Doc()
		if err != nil {
			log.Fatal("failed to generate docs: %v", err)
		}
		fmt.Println(doc)
		os.Exit(0)
	}

	// rebase f_iomBase if f_base changed but iomBase did not
	if *f_base != BASE_PATH && *f_iomBase == IOM_PATH {
		*f_iomBase = *f_base + "files"
	}

	if !strings.HasSuffix(*f_iomBase, "/") {
		*f_iomBase += "/"
	}

	if *f_version {
		fmt.Println("minimega", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	logSetup()

	hostname, err = os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}

	if isReserved(hostname) {
		log.Warn("hostname `%s` is a reserved word -- abandon all hope, ye who enter here", hostname)
	}

	vms = make(map[int]VM)

	// special case, catch -e and execute a command on an already running
	// minimega instance
	if *f_e || *f_attach {
		// try to connect to the local minimega
		mm, err := miniclient.Dial(*f_base)
		if err != nil {
			log.Fatalln(err)
		}

		if *f_e {
			a := flag.Args()
			log.Debugln("got args:", a)

			// TODO: Need to escape?
			cmd := minicli.MustCompile(strings.Join(a, " "))
			log.Infoln("got command:", cmd)

			mm.RunAndPrint(cmd, false)
		} else {
			mm.Attach()
		}

		return
	}

	// warn if we're not root
	user, err := user.Current()
	if err != nil {
		log.Fatalln(err)
	}
	if user.Uid != "0" {
		log.Warnln("not running as root")
	}

	// check for a running instance of minimega
	_, err = os.Stat(*f_base + "minimega")
	if err == nil {
		if !*f_force {
			log.Fatalln("minimega appears to already be running, override with -force")
		}
		log.Warn("minimega may already be running, proceed with caution")
		err = os.Remove(*f_base + "minimega")
		if err != nil {
			log.Fatalln(err)
		}
	}

	// set up signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	go func() {
		first := true
		for {
			<-sig
			if *f_panic {
				panic("teardown")
			}
			if first {
				log.Info("caught signal, tearing down, ctrl-c again will force quit")
				go teardown()
				first = false
			} else {
				os.Exit(1)
			}
		}
	}()

	err = checkExternal()
	if err != nil {
		log.Warnln(err.Error())
	}

	// attempt to set up the base path
	err = os.MkdirAll(*f_base, os.FileMode(0770))
	if err != nil {
		log.Fatal("mkdir base path: %v", err)
	}
	pid := os.Getpid()
	err = ioutil.WriteFile(*f_base+"minimega.pid", []byte(fmt.Sprintf("%v", pid)), 0664)
	if err != nil {
		log.Error("write minimega pid: %v", err)
		teardown()
	}
	go commandSocketStart()

	// create a node for meshage
	host, err := os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}
	meshageInit(host, *f_namespace, uint(*f_degree), *f_port)

	fmt.Println(banner)

	// fan out to the number of cpus on the system if GOMAXPROCS env variable is
	// not set.
	if os.Getenv("GOMAXPROCS") == "" {
		cpus := runtime.NumCPU()
		runtime.GOMAXPROCS(cpus)
	}

	if !*f_nostdin {
		cliLocal()
	} else {
		<-sig
		if *f_panic {
			panic("teardown")
		}
	}
	teardown()
}
Esempio n. 22
0
// reentrant read routine. Will be called recursively if a 'parents' key exists in the config file
func read(path string, c *Config) error {
	f, err := os.Open(path)
	if err != nil {
		if strings.Contains(err.Error(), "no such file") { // file doesn't exist, let's try some path magic
			if path == c.Path {
				return err
			}
			newpath := filepath.Join(filepath.Dir(c.Path), filepath.Base(path))
			f, err = os.Open(newpath)
			if err != nil {
				return err
			}
			log.Warn("could not find %v, but found a similar one at %v, using that instead", path, newpath)
		} else {
			return err
		}
	}
	defer f.Close()

	var s scanner.Scanner
	s.Init(f)
	tok := s.Scan()
	for tok != scanner.EOF {
		pos := s.Pos()
		if tok != scanner.Ident {
			err = fmt.Errorf("%s:%s malformed config: %s, expected identifier, got %s", path, pos, s.TokenText(), scanner.TokenString(tok))
			return err
		}
		k := s.TokenText()
		tok = s.Scan()
		if tok != '=' {
			err = fmt.Errorf("%s:%s malformed config: %s, expected '=', got %s", path, pos, s.TokenText(), scanner.TokenString(tok))
			return err
		}
		tok = s.Scan()
		if tok != scanner.String {
			err = fmt.Errorf("%s:%s malformed config %s, expected string, got %s", path, pos, s.TokenText(), scanner.TokenString(tok))
			return err
		}

		v := strings.Trim(s.TokenText(), "\"`")
		d := strings.Fields(v)
		switch k {
		case "parents":
			for _, i := range d {
				log.Infoln("reading config:", i)
				err = read(i, c)
				c.Parents = append(c.Parents, i)
				if err != nil {
					return err
				}
			}
		case "packages":
			c.Packages = append(c.Packages, d...)
		case "overlay":
			// trim any trailing "/"
			for i, j := range d {
				d[i] = strings.TrimRight(j, "/")
			}
			c.Overlays = append(c.Overlays, d...)
		case "postbuild":
			c.Postbuilds = append(c.Postbuilds, v)
		default:
			err = fmt.Errorf("invalid key %s", k, d)
			return err
		}
		tok = s.Scan()
	}
	return nil
}
Esempio n. 23
0
func main() {
	var err error

	flag.Usage = usage
	flag.Parse()

	logSetup()

	// see containerShim()
	if flag.NArg() > 1 && flag.Arg(0) == CONTAINER_MAGIC {
		containerShim()
	}

	cliSetup()

	if *f_cli {
		if err := minicli.Validate(); err != nil {
			log.Fatalln(err)
		}

		doc, err := minicli.Doc()
		if err != nil {
			log.Fatal("failed to generate docs: %v", err)
		}
		fmt.Println(doc)
		os.Exit(0)
	}

	// rebase f_iomBase if f_base changed but iomBase did not
	if *f_base != BASE_PATH && *f_iomBase == IOM_PATH {
		*f_iomBase = filepath.Join(*f_base, "files")
	}

	if *f_version {
		fmt.Println("minimega", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	hostname, err = os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}

	if isReserved(hostname) {
		log.Warn("hostname `%s` is a reserved word -- abandon all hope, ye who enter here", hostname)
	}

	// special case, catch -e and execute a command on an already running
	// minimega instance
	if *f_e || *f_attach {
		// try to connect to the local minimega
		mm, err := miniclient.Dial(*f_base)
		if err != nil {
			log.Fatalln(err)
		}
		mm.Pager = minipager.DefaultPager

		if *f_e {
			a := flag.Args()
			log.Debugln("got args:", a)

			// TODO: Need to escape?
			cmd := minicli.MustCompile(strings.Join(a, " "))
			log.Infoln("got command:", cmd)

			mm.RunAndPrint(cmd, false)
		} else {
			mm.Attach()
		}

		return
	}

	// warn if we're not root
	user, err := user.Current()
	if err != nil {
		log.Fatalln(err)
	}
	if user.Uid != "0" {
		log.Warnln("not running as root")
	}

	// check for a running instance of minimega
	_, err = os.Stat(filepath.Join(*f_base, "minimega"))
	if err == nil {
		if !*f_force {
			log.Fatalln("minimega appears to already be running, override with -force")
		}
		log.Warn("minimega may already be running, proceed with caution")
		err = os.Remove(filepath.Join(*f_base, "minimega"))
		if err != nil {
			log.Fatalln(err)
		}
	}

	// set up signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	go func() {
		first := true
		for s := range sig {
			if s == os.Interrupt && first {
				// do nothing
				continue
			}

			if *f_panic {
				panic("teardown")
			}
			if first {
				log.Info("caught signal, tearing down, ctrl-c again will force quit")
				go teardown()
				first = false
			} else {
				os.Exit(1)
			}
		}
	}()

	err = checkExternal()
	if err != nil {
		log.Warnln(err.Error())
	}

	// attempt to set up the base path
	err = os.MkdirAll(*f_base, os.FileMode(0770))
	if err != nil {
		log.Fatal("mkdir base path: %v", err)
	}

	pid := os.Getpid()
	writeOrDie(filepath.Join(*f_base, "minimega.pid"), strconv.Itoa(pid))

	go commandSocketStart()

	// create a node for meshage
	host, err := os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}
	meshageInit(host, *f_context, *f_degree, *f_msaTimeout, *f_port)

	// start the cc service
	ccStart()

	// start tap reaper
	go periodicReapTaps()

	fmt.Println(banner)

	// fan out to the number of cpus on the system if GOMAXPROCS env variable is
	// not set.
	if os.Getenv("GOMAXPROCS") == "" {
		cpus := runtime.NumCPU()
		runtime.GOMAXPROCS(cpus)
	}

	if !*f_nostdin {
		cliLocal()
	} else {
		<-sig
		if *f_panic {
			panic("teardown")
		}
	}
	teardown()
}
Esempio n. 24
0
func main() {
	flag.Usage = usage
	flag.Parse()

	logSetup()

	if flag.NArg() != 1 {
		usage()
		os.Exit(1)
	}

	externalCheck()

	// stage 1 and stage 2 flags are mutually exclusive
	if *f_stage1 && *f_stage2 != "" {
		log.Fatalln("-1 cannot be used with -2")
	}

	// find any other dependent configs and get an ordered list of those
	configfile := flag.Arg(0)
	log.Debugln("using config:", configfile)
	config, err := vmconfig.ReadConfig(configfile)
	if err != nil {
		log.Fatalln(err)
	} else {
		log.Debugln("read config:", config)
	}

	// If we're doing a LiveCD, we need to add the live-boot package
	if *f_iso {
		config.Packages = append(config.Packages, "live-boot")
	}

	var buildPath string

	// stage 1
	if *f_stage2 == "" {
		// create a build path
		buildPath, err = ioutil.TempDir("", "vmbetter_build_")
		if err != nil {
			log.Fatalln("cannot create temporary directory:", err)
		}
		log.Debugln("using build path:", buildPath)

		// invoke debootstrap
		fmt.Println("invoking debootstrap (this may take a while)...")
		err = Debootstrap(buildPath, config)
		if err != nil {
			log.Fatalln(err)
		}

		// copy any overlay into place in reverse order of opened dependencies
		fmt.Println("copying overlays")
		err = Overlays(buildPath, config)
		if err != nil {
			log.Fatalln(err)
		}

		//
		// stage 1 complete
		//

		if *f_stage1 {
			stage1Target := strings.Split(filepath.Base(config.Path), ".")[0] + "_stage1"
			log.Infoln("writing stage 1 target", stage1Target)

			err = os.Mkdir(stage1Target, 0666)
			if err != nil {
				log.Fatalln(err)
			}

			p := process("cp")
			cmd := exec.Command(p, "-r", "-v", buildPath+"/.", stage1Target)
			stdout, err := cmd.StdoutPipe()
			if err != nil {
				log.Fatalln(err)
			}
			stderr, err := cmd.StderrPipe()
			if err != nil {
				log.Fatalln(err)
			}
			log.LogAll(stdout, log.INFO, "cp")
			log.LogAll(stderr, log.ERROR, "cp")

			err = cmd.Run()
			if err != nil {
				log.Fatalln(err)
			}
		}
	} else {
		buildPath = *f_stage2
	}

	// stage 2
	if *f_stage2 != "" || !*f_stage1 {
		// call post build chroot commands in reverse order as well
		fmt.Println("executing post-build commands")
		err = PostBuildCommands(buildPath, config)
		if err != nil {
			log.Fatalln(err)
		}

		// build the image file
		fmt.Println("building target files")
		if *f_qcow {
			err = Buildqcow2(buildPath, config)
		} else if *f_iso {
			err = BuildISO(buildPath, config)
		} else {
			err = BuildTargets(buildPath, config)
		}
		if err != nil {
			log.Fatalln(err)
		}
	}

	// cleanup?
	if !*f_noclean && *f_stage2 == "" {
		fmt.Println("cleaning up")
		err = os.RemoveAll(buildPath)
		if err != nil {
			log.Errorln(err)
		}
	}
	fmt.Println("done")
}