package cmd import ( "encoding/json" "fmt" "gocage/jail" "io/ioutil" "log" "os" "reflect" "strings" ) /******************************************************************************** * List all properties a jail have, with their internal name * Only print properties name. To get name & values, use GetJailProperties() *******************************************************************************/ func ListJailsProps(format string) (string, error) { var conf Jail var props []string var out string // Mandatory constructor to init default values jailconf, err := NewJailConfig() if err != nil { fmt.Printf("Error allocating JailConfig: %s\n", err.Error()) return out, err } conf.Config = jailconf props = getStructFieldNames(conf, props, "") if format == "json" { out = "{\"properties\":[" for i, p := range props { out += fmt.Sprintf("\"%s\"", p) if i < len(props) - 1 { out += "," } } out += "]}" } else { for _, p := range props { out += fmt.Sprintf("%s\n", p) } } return out, nil } /******************************************************************************** * Get Jails from datastores. Store config and running metadata * into gJails global var *******************************************************************************/ func ListJails(args []string, display bool) { type uniqueJailName struct { jail string unique bool uniqueName bool } var nameChecked []*uniqueJailName var curCheck *uniqueJailName var found bool var skip bool for _, ds := range gDatastores { listJailsFromDatastore(ds, args, display) } // Only when displaying jails, we accept to process multiple same name jails if false == display { for _, j := range gJails { // If already checked and was using distinctive name, skip this occurence for i, n := range nameChecked { if strings.EqualFold(n.jail, j.Name) { found = true if false == n.unique && true == n.uniqueName { skip = true } else { curCheck = nameChecked[i] } break } } if true == skip { continue } // Initialize if not found in nameChecked if false == found { curCheck = &uniqueJailName{jail: j.Name, unique: true, uniqueName: false} } else { found = false } if countOfJailsWithThisName(j.Name) > 1 { //fmt.Printf("DEBUG: Jail %s exist multiple times, now checking if specified with full name\n", j.Name) curCheck.unique = false for _, a := range args { //fmt.Printf("DEBUG: comparing %s/%s with %s\n", j.Datastore, j.Name, a) if strings.EqualFold(a, fmt.Sprintf("%s/%s", j.Datastore, j.Name)) { //fmt.Printf("DEBUG: Was specified with full name, GG!\n") curCheck.uniqueName = true } } } nameChecked = append(nameChecked, curCheck) } // Now check for _, a := range args { for _, n := range nameChecked { if strings.EqualFold(n.jail, a) && false == n.unique && false == n.uniqueName { fmt.Printf("There is more than one jail named \"%s\", please use datastore/jail format\n", n.jail) os.Exit(1) } } } } fields := strings.Split(gDisplayJColumns, ",") // This is the structure we will filter, then display var jails []Jail /*************************************************************** / Filter jails with "filter" options /**************************************************************/ if len(gFilterJails) > 0 && gFilterJails != "none" { flts := make(map[string]string) for _, flt := range strings.Split(gFilterJails, ",") { sa := strings.Split(flt, "=") if len(sa) != 2 { fmt.Printf("Invalid format for filter %s\n", gFilterJails) return } flts[sa[0]] = sa[1] } if len(flts) > 0 { // Browse global gJails array to get filtered results for _, j := range gJails { for key, val := range flts { v, _, err := getStructFieldValue(&j, key) if err != nil { fmt.Printf("ERROR: Field %s not found\n", key) return } if strings.EqualFold(fmt.Sprintf("%v", v.Interface()), val) { jails = append(jails, j) } } } } } else { jails = gJails } /*************************************************************** / Filter jails by names given on command line /**************************************************************/ if len(args) > 0 { var js []Jail for _, a := range args { for _, j := range jails { if j.Name == a { js = append(js, j) } } } jails = js } /*************************************************************** / Sort jails / We support 3 sort criteria max /**************************************************************/ if len(gSortJailFields) > 0 && gSortJailFields != "none" { js := initJailSortStruct() // The way we manage criteria quantity is not very elegant... var fct1, fct2, fct3 *reflect.Value for i, c := range strings.Split(gSortJailFields, ",") { var fctName string if strings.HasPrefix(c, "-") { fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1)) } else { // Par defaut (pas de prefix +/-) on considere un tri incremental fctName = fmt.Sprintf("%sInc", strings.Replace(c, "+", "", 1)) } // Get function by its name fct, _, err := getStructFieldValue(js, fctName) if err != nil { fieldName := strings.Replace(strings.Replace(c, "-", "", 1), "+", "", 1) fmt.Printf("ERROR getting JailSort struct field %s. Please check the field name: %s\n", fctName, fieldName) return } switch i + 1 { case 1: fct1 = fct case 2: fct2 = fct case 3: fct3 = fct } } switch len(strings.Split(gSortJailFields, ",")) { case 1: JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails) case 2: JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc)).Sort(jails) case 3: JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc), fct3.Interface().(jailLessFunc)).Sort(jails) } } /*************************************************************** / Finally, display jails /**************************************************************/ if display { displayJailsFields(jails, fields) } } func listJailsFromDatastore(ds Datastore, args []string, accept_multiple bool) { fileInfo, err := os.Stat(ds.Mountpoint) if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", ds.Mountpoint)) } if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", ds.Mountpoint)) } // A datastore have to contain a "jails" directory jailsDir := fmt.Sprintf("%s/jails", ds.Mountpoint) fileInfo, err = os.Stat(jailsDir) if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir)) } if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir)) } listJailsFromDirectory(jailsDir, ds.Name) } func listJailsFromDirectory(dir string, dsname string) ([]Jail, error) { files, err := ioutil.ReadDir(dir) if err != nil { log.Fatalln(fmt.Sprintf("Unable to browse %s, check path and/or rights", dir)) } for _, fi := range files { if fi.IsDir() == true { // 1. Get conf from config.json jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json") jailConf, err := getJailConfig(jailConfPath) if err != nil { fmt.Printf("ERROR reading jail config from %s\n", jailConfPath) } // 2. Build jail object from config jailRootPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "root") j := Jail{ Name: jailConf.Host_hostuuid, Config: jailConf, ConfigPath: jailConfPath, Datastore: dsname, RootPath: jailRootPath, Running: false, } // 3. Add current running informations rjails, err := jail.GetJails() if err != nil { log.Fatalln("Unable to list running jails") } for _, rj := range rjails { if rj.Path == j.RootPath { j.JID = rj.Jid j.Running = true // FIXME ??? Shouldn't be ioc-$Name ? j.InternalName = rj.Name j.Devfs_ruleset = rj.Devfs_ruleset // Update release r, err := getVersion(&j) if err != nil { fmt.Printf("ERROR getting jail %s version: %s\n", j.Name, err.Error()) } else { j.Config.Release = r } break } } // 4. Get Zpool /* This op take some 600ms for ~40 jails :^( */ out, err := executeCommand(fmt.Sprintf("zfs list -H -o name %s", j.RootPath)) if err != nil { fmt.Printf("ERROR getting dataset from %s: %s\n", j.RootPath, err.Error()) } else { j.Zpool = strings.Split(strings.TrimSuffix(out, "\n"), "/")[0] } // Check if jail with the same name already exist on another DS for _, jj := range gJails { if strings.EqualFold(jj.Name, j.Name) { /*fmt.Printf("Warning: A jail with name %s exist on datastore %s, use full name to interact!\n", j.Name, jj.Datastore) fmt.Printf("(Ex: gocage start %s/%s)\n", jj.Datastore, j.Name)*/ // Add Datastore to avoid confusion if false == strings.Contains(gDisplayJColumns, "Datastore") { gDisplayJColumns += ",Datastore" } } } gJails = append(gJails, j) } } return gJails, nil } func getJailConfig(jailConfigPath string) (JailConfig, error) { content, err := ioutil.ReadFile(jailConfigPath) if err != nil { log.Fatalln(fmt.Sprintf("Unable to read %s, check path and/or rights", jailConfigPath)) } // Mandatory constructor to init default values jc, err := NewJailConfig() if err != nil { return jc, err } err = json.Unmarshal([]byte(content), &jc) if err != nil { log.Fatalln(fmt.Sprintf("Error occured during unmarshaling %s: %s", jailConfigPath, err.Error())) } return jc, err }