/* * Copyright (C) 2018 "IoT.bzh" * Author Sebastien Douheret <sebastien@iot.bzh> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xdsserver import ( "encoding/xml" "fmt" "log" "os" "strings" common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib" "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig" "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1" "github.com/syncthing/syncthing/lib/sync" ) // Targets Represent a XDS targets type Targets struct { *Context fileOnDisk string tgts map[string]*ITARGET terminals map[string]*Terminals } // Mutex to make add/delete atomic var tcMutex = sync.NewMutex() /*** * Targets ***/ // TargetsConstructor Create a new instance of Model Target func TargetsConstructor(ctx *Context) *Targets { file, _ := xdsconfig.TargetsConfigFilenameGet() return &Targets{ Context: ctx, fileOnDisk: file, tgts: make(map[string]*ITARGET), terminals: make(map[string]*Terminals), } } // LoadConfig Load targets configuration from disk func (t *Targets) LoadConfig() error { var tgts []xsapiv1.TargetConfig if t.fileOnDisk != "" { t.Log.Infof("Use target config file: %s", t.fileOnDisk) err := targetsConfigRead(t.fileOnDisk, &tgts) if err != nil { if strings.HasPrefix(err.Error(), "EOF") { t.Log.Warnf("Empty target config file") } else if strings.HasPrefix(err.Error(), "No target config") { t.Log.Warnf(err.Error()) } else { return err } } } else { t.Log.Warnf("Targets config filename not set") } // Update targets t.Log.Infof("Loading initial targets config: %d targets found", len(tgts)) for _, tc := range tgts { if _, err := t.createUpdate(tc, false, true, nil); err != nil { return err } } return nil } // SaveConfig Save targets configuration to disk func (t *Targets) SaveConfig() error { if t.fileOnDisk == "" { return fmt.Errorf("Targets config filename not set") } // FIXME: buffered save or avoid to write on disk each time return targetsConfigWrite(t.fileOnDisk, t.getConfigArrUnsafe()) } // ResolveID Complete a Target ID (helper for user that can use partial ID value) func (t *Targets) ResolveID(id string) (string, error) { if id == "" { return "", nil } match := []string{} for iid := range t.tgts { if strings.HasPrefix(iid, id) { match = append(match, iid) } } if len(match) == 1 { return match[0], nil } else if len(match) == 0 { return id, fmt.Errorf("Unknown id") } return id, fmt.Errorf("Multiple IDs found %v", match) } // Get returns the target config or nil if not existing func (t *Targets) Get(id string) *ITARGET { if id == "" { return nil } tc, exist := t.tgts[id] if !exist { return nil } return tc } // GetConfigArr returns the config of all targets as an array func (t *Targets) GetConfigArr() []xsapiv1.TargetConfig { tcMutex.Lock() defer tcMutex.Unlock() return t.getConfigArrUnsafe() } // getConfigArrUnsafe Same as GetConfigArr without mutex protection func (t *Targets) getConfigArrUnsafe() []xsapiv1.TargetConfig { conf := []xsapiv1.TargetConfig{} for _, v := range t.tgts { conf = append(conf, (*v).GetConfig()) } return conf } // Add adds a new target func (t *Targets) Add(newT xsapiv1.TargetConfig, sess *ClientSession) (*xsapiv1.TargetConfig, error) { return t.createUpdate(newT, true, false, sess) } // CreateUpdate creates or update a target func (t *Targets) createUpdate(newT xsapiv1.TargetConfig, create bool, initial bool, sess *ClientSession) (*xsapiv1.TargetConfig, error) { var err error tcMutex.Lock() defer tcMutex.Unlock() // Sanity check if _, exist := t.tgts[newT.ID]; exist { return nil, fmt.Errorf("ID already exists") } var tgt ITARGET switch newT.Type { case xsapiv1.TypeTgtStandard: tgt = NewTargetStandard(t.Context) default: return nil, fmt.Errorf("Unsupported target type") } // Allocate a new UUID if create { newT.ID = tgt.NewUID("") } if !create && newT.ID == "" { return nil, fmt.Errorf("Cannot update target with null ID") } if newT.Name == "" { newT.Name = "Target" if len(newT.ID) > 8 { newT.Name += "_" + newT.ID[0:8] } else { newT.Name += "_" + newT.ID } } // Call terminals constructor the first time var terms *Terminals if _, exist := t.terminals[newT.ID]; !exist { terms = TerminalsConstructor(t.Context) t.terminals[newT.ID] = terms } else { terms = t.terminals[newT.ID] } var newTarget *xsapiv1.TargetConfig if create { // Add target if newTarget, err = tgt.Add(newT, terms); err != nil { newT.Status = xsapiv1.StatusTgtErrorConfig log.Printf("ERROR Adding target: %v\n", err) return newTarget, err } } else { // Just update target config if newTarget, err = tgt.Setup(newT, terms); err != nil { newT.Status = xsapiv1.StatusTgtErrorConfig log.Printf("ERROR Updating target: %v\n", err) return newTarget, err } } // Create terminals for _, tc := range newT.Terms { _, err := t.CreateUpdateTerminal(newT.ID, tc, initial) if err != nil { return newTarget, err } } // Add to folders list t.tgts[newT.ID] = &tgt // Notify target Add newTgt := tgt.GetConfig() if !initial { if err = t.events.Emit(xsapiv1.EVTTargetAdd, &newTgt, sess.ID); err != nil { t.Log.Errorf("WS Emit EVTTargetAdd : %v", err) } } // Save config on disk if !initial { if err := t.SaveConfig(); err != nil { return newTarget, err } } return &newTgt, nil } // Delete deletes a specific target func (t *Targets) Delete(id string, sess *ClientSession) (xsapiv1.TargetConfig, error) { var err error tcMutex.Lock() defer tcMutex.Unlock() tgc := xsapiv1.TargetConfig{} tc, exist := t.tgts[id] if !exist { return tgc, fmt.Errorf("unknown id") } tgc = (*tc).GetConfig() if err = (*tc).Delete(); err != nil { return tgc, err } delete(t.tgts, id) // Save config on disk err = t.SaveConfig() // Notify target remove if err = t.events.Emit(xsapiv1.EVTTargetRemove, &tgc, sess.ID); err != nil { t.Log.Errorf("WS Emit EVTTargetRemove : %v", err) } return tgc, err } /*** * Terminals ***/ // GetTerminalsArr Return list of existing terminals func (t *Targets) GetTerminalsArr(targetID string) ([]xsapiv1.TerminalConfig, error) { arr := []xsapiv1.TerminalConfig{} tm, exist := t.terminals[targetID] if !exist { return arr, fmt.Errorf("unknown target id") } for _, tt := range (*tm).terms { arr = append(arr, (*tt).GetConfig()) } return arr, nil } // GetTerminal Return info of a specific terminal func (t *Targets) GetTerminal(targetID, termID string) (*ITERMINAL, error) { tm, exist := t.terminals[targetID] if !exist { return nil, fmt.Errorf("unknown target id") } term, exist := (*tm).terms[termID] if !exist { return nil, fmt.Errorf("unknown terminal id") } return term, nil } // ResolveTerminalID Complete a Terminal ID (helper for user that can use partial ID value) func (t *Targets) ResolveTerminalID(termID string) (string, error) { if termID == "" { return "", fmt.Errorf("unknown terminal id") } match := []string{} for _, tm := range t.terminals { for tid := range tm.terms { if strings.HasPrefix(tid, termID) { match = append(match, tid) } } } if len(match) == 1 { return match[0], nil } else if len(match) == 0 { return termID, fmt.Errorf("Unknown id") } return termID, fmt.Errorf("Multiple IDs found %v", match) } // CreateUpdateTerminal Create or Update a target terminal definition func (t *Targets) CreateUpdateTerminal(targetID string, tmCfg xsapiv1.TerminalConfig, initial bool) (*xsapiv1.TerminalConfig, error) { var term *xsapiv1.TerminalConfig iTerm, err := t.GetTerminal(targetID, tmCfg.ID) if err != nil && strings.Contains(err.Error(), "unknown target") { return nil, err } if iTerm != nil { // Update terminal config term = (*iTerm).UpdateConfig(tmCfg) } else { // Auto create a new terminal when needed var err error if term, err = t.terminals[targetID].New(tmCfg, targetID); err != nil { return nil, err } } if !initial { // Save config on disk if err := t.SaveConfig(); err != nil { return term, err } } return term, nil } // DeleteTerminal Delete a target terminal definition func (t *Targets) DeleteTerminal(targetID, termID string) (*xsapiv1.TerminalConfig, error) { terms, exist := t.terminals[targetID] if !exist { return nil, fmt.Errorf("unknown target id") } term, err := (*terms).Free(termID) if err != nil { return term, err } // Save config on disk if err := t.SaveConfig(); err != nil { return term, err } return term, nil } // OpenTerminal Open a target terminal func (t *Targets) OpenTerminal(targetID, termID string, sess *ClientSession) (*xsapiv1.TerminalConfig, error) { terms, exist := t.terminals[targetID] if !exist { return nil, fmt.Errorf("unknown target id") } return (*terms).Open(termID, sess) } // CloseTerminal Close a target terminal func (t *Targets) CloseTerminal(targetID, termID string, sess *ClientSession) (*xsapiv1.TerminalConfig, error) { terms, exist := t.terminals[targetID] if !exist { return nil, fmt.Errorf("unknown target id") } return (*terms).Close(termID, sess) } // ResizeTerminal Set size (row+col) of a target terminal func (t *Targets) ResizeTerminal(targetID, termID string, cols, rows uint16, sess *ClientSession) (*xsapiv1.TerminalConfig, error) { terms, exist := t.terminals[targetID] if !exist { return nil, fmt.Errorf("unknown target id") } return (*terms).Resize(termID, cols, rows, sess) } // SignalTerminal Send a signal to a target terminal func (t *Targets) SignalTerminal(targetID, termID, sigNum string) error { terms, exist := t.terminals[targetID] if !exist { return fmt.Errorf("unknown target id") } return (*terms).Signal(termID, sigNum) } /** * Private functions **/ // Use XML format and not json to be able to save/load all fields including // ones that are masked in json (IOW defined with `json:"-"`) type xmlTargets struct { XMLName xml.Name `xml:"targets"` Version string `xml:"version,attr"` Targets []xsapiv1.TargetConfig `xml:"targets"` } // targetsConfigRead reads targets config from disk func targetsConfigRead(file string, targets *[]xsapiv1.TargetConfig) error { if !common.Exists(file) { return fmt.Errorf("No target config file found (%s)", file) } ffMutex.Lock() defer ffMutex.Unlock() fd, err := os.Open(file) defer fd.Close() if err != nil { return err } data := xmlTargets{} err = xml.NewDecoder(fd).Decode(&data) if err == nil { *targets = data.Targets } return err } // targetsConfigWrite writes targets config on disk func targetsConfigWrite(file string, targets []xsapiv1.TargetConfig) error { ffMutex.Lock() defer ffMutex.Unlock() fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) defer fd.Close() if err != nil { return err } data := &xmlTargets{ Version: "1", Targets: targets, } enc := xml.NewEncoder(fd) enc.Indent("", " ") return enc.Encode(data) }