Exercices ({{ exercices.length }})
Ajouter un exercice
- Synchroniser
+
+ Synchroniser
+
+
diff --git a/admin/sync/README.md b/admin/sync/README.md
index 1a342516..70b2d017 100644
--- a/admin/sync/README.md
+++ b/admin/sync/README.md
@@ -24,7 +24,7 @@ Tous les textes doivent utiliser l'encodage UTF8.
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
* `ordered = false` : (facultatif, par défaut : `false`) ignore l'ordre dans lequels les éléments du tableau sont passés ;
* `ignorecase = true` : (facultatif, par défaut : `false`) ignore la case de ce drapeau ;
- * `help = "Indication"` : (facultatif) chaîne de caractères placée sous le champ du formulaire, idéale pour donner une indication de format ;
+ * `placeholder = "Indication"` : (facultatif) chaîne de caractères placée sous le champ du formulaire, idéale pour donner une indication de format ;
* `[[flag.unlock_file]]` : bloque l'accès à un fichier tant que le flag n'est pas obtenu :
+ `filename = "toto.txt"` : nom du fichier tel qu'il apparaît dans le dossier `files` ;
* `[[flag.need_flag]]` : liste des flags devant être validés avant de débloquer celui-ci :
@@ -34,13 +34,13 @@ Tous les textes doivent utiliser l'encodage UTF8.
* `[[flag_mcq.choice]]` : représente un choix, répétez autant de fois qu'il y a de choix :
+ `label = "Intitulé de la réponse"`,
+ `value = true` : (facultatif, par défaut `false`) valeur attendue pour ce choix ; pour un QCM justifié, utilisez une chaîne de caractères (notez qu'il n'est pas possible de combiner des réponses vraies justifiées et justifiées),
- + `help = "Flag correspondant"` : (facultatif) indication affichée dans le champ de texte des QCM justifiés ;
+ + `placeholder = "Flag correspondant"` : (facultatif) indication affichée dans le champ de texte des QCM justifiés ;
- `[[flag_ucq]]` : drapeau sous forme de question à choix unique :
* `id = 42` : (facultatif) identifiant du flag au sein de l'exercice, pour définir des dépendances ;
* `label = "Intitulé du groupe"` : (facultatif) intitulé du groupe de choix ;
* `raw = 'MieH2athxuPhai6u'` : drapeau attendu parmi les propositions ;
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
- * `help = "Indication"` : (facultatif, uniquement si `displayAs = select`) chaîne de caractères placée sous le champ du formulaire ;
+ * `placeholder = "Indication"` : (facultatif, uniquement si `displayAs = select`) chaîne de caractères placée sous le champ du formulaire ;
* `displayAs = "select|radio"` : (facultatif, par défaut `radio`) manière dont est affichée le choix : `select` pour une liste de choix, `radio` pour des boutons radios ;
* `choices_cost = 20` : (facultatif, par défaut `0`) coût pour afficher les choix, avant l'affichage, se comporte comme un `flag` classique (à 0, les choix sont affichés directement) ;
* `[[flag_ucq.choice]]` : représente un choix, répétez autant de fois qu'il y a de choix :
@@ -144,7 +144,7 @@ id = 2
[[flag]]
label = "Date d'exfiltration"
-help= "Format : yyyy-mm"
+placeholder = "Format : yyyy-mm"
raw = '2015-12'
[[flag]]
diff --git a/admin/sync/challengeinfo.go b/admin/sync/challengeinfo.go
new file mode 100644
index 00000000..6e3b4a73
--- /dev/null
+++ b/admin/sync/challengeinfo.go
@@ -0,0 +1,46 @@
+package sync
+
+import (
+ "path"
+ "strings"
+
+ "srs.epita.fr/fic-server/libfic"
+ "srs.epita.fr/fic-server/settings"
+)
+
+// ImportChallengeInfo imports images defined in the challengeinfo.
+func ImportChallengeInfo(ci *settings.ChallengeInfo, dashboardDir string) (err error) {
+ if len(ci.MainLogo) > 0 {
+ for i, logo := range ci.MainLogo {
+ dest := path.Join(fic.FilesDir, "logo", path.Base(logo))
+ err = importFile(GlobalImporter, logo, dest)
+ if err != nil {
+ return
+ }
+
+ ci.MainLogo[i] = path.Join("$FILES$", strings.TrimPrefix(dest, fic.FilesDir))
+ }
+ }
+
+ if len(ci.DashboardBackground) > 0 {
+ dest := path.Join(dashboardDir, path.Base(ci.DashboardBackground))
+ err = importFile(GlobalImporter, ci.DashboardBackground, dest)
+ if err != nil {
+ return
+ }
+ }
+
+ if len(ci.Partners) > 0 {
+ for i, partner := range ci.Partners {
+ dest := path.Join(fic.FilesDir, "partner", path.Base(partner.Src))
+ err = importFile(GlobalImporter, partner.Src, dest)
+ if err != nil {
+ return
+ }
+
+ ci.Partners[i].Src = path.Join("$FILES$", strings.TrimPrefix(dest, fic.FilesDir))
+ }
+ }
+
+ return nil
+}
diff --git a/admin/sync/errors.go b/admin/sync/errors.go
new file mode 100644
index 00000000..b0e2a015
--- /dev/null
+++ b/admin/sync/errors.go
@@ -0,0 +1,148 @@
+package sync
+
+import (
+ "fmt"
+ "log"
+ "path"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var (
+ ErrResolutionNotFound = fmt.Errorf("no resolution video or text file found")
+)
+
+type ThemeError struct {
+ error
+ ThemeId int64
+ ThemePath string
+ ThemeName string
+}
+
+func NewThemeError(theme *fic.Theme, err error) *ThemeError {
+ if theme == nil {
+ return &ThemeError{
+ error: err,
+ ThemePath: fic.StandaloneExercicesDirectory,
+ }
+ }
+
+ return &ThemeError{
+ error: err,
+ ThemeId: theme.Id,
+ ThemePath: path.Base(theme.Path),
+ ThemeName: theme.Name,
+ }
+}
+
+func (e *ThemeError) Error() string {
+ return fmt.Sprintf("%s: %s", e.ThemePath, e.error.Error())
+}
+
+func (e *ThemeError) GetError() error {
+ return e.error
+}
+
+type ExerciceError struct {
+ *ThemeError
+ ExerciceId int64
+ ExercicePath string
+ ExerciceName string
+}
+
+func NewExerciceError(exercice *fic.Exercice, err error, theme ...*fic.Theme) *ExerciceError {
+ ltheme := len(theme)
+ if ltheme > 1 {
+ log.Fatal("Only 1 variadic arg is accepted in NewExerciceError")
+ return nil
+ } else if ltheme == 1 {
+ return &ExerciceError{
+ ThemeError: NewThemeError(theme[0], err),
+ ExerciceId: exercice.Id,
+ ExercicePath: path.Base(exercice.Path),
+ ExerciceName: exercice.Title,
+ }
+ } else {
+ return &ExerciceError{
+ ThemeError: &ThemeError{error: err},
+ ExerciceId: exercice.Id,
+ ExercicePath: path.Base(exercice.Path),
+ ExerciceName: exercice.Title,
+ }
+ }
+}
+
+func (e *ExerciceError) Error() string {
+ return fmt.Sprintf("%s: %s", e.ExercicePath, e.ThemeError.error.Error())
+}
+
+type FileError struct {
+ *ExerciceError
+ Filename string
+}
+
+func NewFileError(exercice *fic.Exercice, filename string, err error, theme ...*fic.Theme) *FileError {
+ return &FileError{
+ ExerciceError: NewExerciceError(exercice, err, theme...),
+ Filename: filename,
+ }
+}
+
+func (e *FileError) Error() string {
+ return fmt.Sprintf("%s: file %q: %s", e.ExercicePath, e.Filename, e.ThemeError.error.Error())
+}
+
+type ChallengeTxtError struct {
+ *ExerciceError
+ ChallengeTxtLine uint
+}
+
+func NewChallengeTxtError(exercice *fic.Exercice, line uint, err error, theme ...*fic.Theme) *ChallengeTxtError {
+ return &ChallengeTxtError{
+ ExerciceError: NewExerciceError(exercice, err, theme...),
+ ChallengeTxtLine: line,
+ }
+}
+
+func (e *ChallengeTxtError) Error() string {
+ if e.ChallengeTxtLine != 0 {
+ return fmt.Sprintf("%s:%d: %s", path.Join(e.ExercicePath, "challenge.toml"), e.ChallengeTxtLine, e.ThemeError.error.Error())
+ } else {
+ return fmt.Sprintf("%s: %s", path.Join(e.ExercicePath, "challenge.toml"), e.ThemeError.error.Error())
+ }
+}
+
+type HintError struct {
+ *ChallengeTxtError
+ HintId int
+ HintTitle string
+}
+
+func NewHintError(exercice *fic.Exercice, hint *fic.EHint, line int, err error, theme ...*fic.Theme) *HintError {
+ return &HintError{
+ ChallengeTxtError: NewChallengeTxtError(exercice, 0, err, theme...),
+ HintId: line + 1,
+ HintTitle: hint.Title,
+ }
+}
+
+func (e *HintError) Error() string {
+ return fmt.Sprintf("%s: hint#%d (%s): %s", path.Join(e.ExercicePath, "challenge.toml"), e.HintId, e.HintTitle, e.ThemeError.error.Error())
+}
+
+type FlagError struct {
+ *ChallengeTxtError
+ FlagId int
+ FlagTitle string
+}
+
+func NewFlagError(exercice *fic.Exercice, flag *ExerciceFlag, line int, err error, theme ...*fic.Theme) *FlagError {
+ return &FlagError{
+ ChallengeTxtError: NewChallengeTxtError(exercice, 0, err, theme...),
+ FlagId: line,
+ }
+}
+
+func (e *FlagError) Error() string {
+ return fmt.Sprintf("%s: flag#%d: %s", path.Join(e.ExercicePath, "challenge.toml"), e.FlagId, e.ThemeError.error.Error())
+}
diff --git a/admin/sync/exceptions.go b/admin/sync/exceptions.go
new file mode 100644
index 00000000..6bc0a01f
--- /dev/null
+++ b/admin/sync/exceptions.go
@@ -0,0 +1,126 @@
+package sync
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+type CheckExceptions map[string]string
+
+func (c *CheckExceptions) GetExerciceExceptions(e *fic.Exercice) *CheckExceptions {
+ return c.GetFileExceptions(filepath.Base(e.Path))
+}
+
+func (c *CheckExceptions) GetFileExceptions(paths ...string) *CheckExceptions {
+ ret := CheckExceptions{}
+
+ if c != nil {
+ for k, v := range *c {
+ cols := strings.SplitN(k, ":", 2)
+ if len(cols) < 2 {
+ continue
+ }
+
+ for _, path := range paths {
+ if strings.HasPrefix(cols[0], path) {
+ k = strings.TrimPrefix(k, path)
+
+ if strings.HasPrefix(k, "/") {
+ k = strings.TrimPrefix(k, "/")
+ }
+
+ ret[k] = v
+ break
+ } else if eval, err := filepath.Match(cols[0], path); err == nil && eval {
+ ret[k] = v
+ break
+ }
+ }
+ }
+ }
+
+ // Ignore redondances in resolution.md
+ if len(paths) > 0 && paths[0] == "resolution.md" {
+ ret["resolution.md:*:redondances_paragraphe"] = "automatic"
+ }
+
+ return &ret
+}
+
+func (c *CheckExceptions) Filter2ndCol(str string) *CheckExceptions {
+ if c == nil {
+ return nil
+ }
+
+ ret := CheckExceptions{}
+
+ for k, v := range *c {
+ cols := strings.SplitN(k, ":", 3)
+ if len(cols) < 2 {
+ continue
+ }
+
+ if cols[1] == "spelling" {
+ ret[k] = v
+ } else if eval, err := filepath.Match(cols[1], str); err == nil && eval {
+ ret[k] = v
+ }
+ }
+
+ return &ret
+}
+
+func (c *CheckExceptions) HasException(ref string) bool {
+ if c == nil {
+ return false
+ }
+
+ for k, _ := range *c {
+ if strings.HasSuffix(k, ref) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func ParseExceptionString(fexcept string, exceptions *CheckExceptions) *CheckExceptions {
+ if exceptions == nil {
+ exceptions = &CheckExceptions{}
+ }
+
+ for n, line := range strings.Split(fexcept, "\n") {
+ (*exceptions)[strings.TrimSpace(line)] = fmt.Sprintf("repochecker-ack.txt:%d", n+1)
+ }
+
+ return exceptions
+}
+
+func LoadThemeException(i Importer, th *fic.Theme) (exceptions *CheckExceptions) {
+ if th == nil {
+ return
+ }
+
+ if fexcept, err := GetFileContent(i, filepath.Join(th.Path, "repochecker-ack.txt")); err == nil {
+ return ParseExceptionString(fexcept, nil)
+ }
+
+ return
+}
+
+func LoadExerciceException(i Importer, th *fic.Theme, e *fic.Exercice, th_exceptions *CheckExceptions) (exceptions *CheckExceptions) {
+ if th_exceptions == nil {
+ th_exceptions = LoadThemeException(i, th)
+ }
+
+ exceptions = th_exceptions.GetExerciceExceptions(e)
+
+ if fexcept, err := GetFileContent(i, filepath.Join(e.Path, "repochecker-ack.txt")); err == nil {
+ return ParseExceptionString(fexcept, exceptions)
+ }
+
+ return
+}
diff --git a/admin/sync/exceptions_test.go b/admin/sync/exceptions_test.go
new file mode 100644
index 00000000..5d0c0a37
--- /dev/null
+++ b/admin/sync/exceptions_test.go
@@ -0,0 +1,64 @@
+package sync
+
+import (
+ "testing"
+)
+
+const sampleFile = `0-exercice-1/overview.md:spelling:Sterik
+0-exercice-1/overview.md:spelling:RSSI
+0-exercice-1/statement.md:spelling:Sterik
+0-exercice-1/statement.md:spelling:RSSI
+0-exercice-1/finished.md:spelling:GoPhish
+0-exercice-1/resolution.md:not-forbidden-string:51.38.152.16
+0-exercice-1/resolution.md:not-forbidden-string:109.57.42.65
+0-exercice-1/resolution.md:not-forbidden-string:server_update.sh
+0-exercice-1/resolution.md:spelling:Flag
+0-exercice-1/resolution.md:spelling:MHA
+0-exercice-1/resolution.md:spelling:hops
+0-exercice-1/resolution.md:4:g3__gn_les_2m__b1_a4_1
+0-exercice-1/resolution.md:10:g3__gn_les_2m__b1_a4_1
+0-exercice-1/resolution.md:spelling:echo
+0-exercice-1/resolution.md:spelling:nbash
+0-exercice-1/resolution.md:spelling:bash
+0-exercice-1/resolution.md:spelling:update
+0-exercice-1/resolution.md:spelling:sh
+0-exercice-1/resolution.md:spelling:Success
+0-exercice-1/resolution.md:11:typo_guillemets_typographiques_doubles_fermants
+0-exercice-1/resolution.md:spelling:cronjob
+0-exercice-1/resolution.md:spelling:Level
+challenge.toml:spelling:time
+challenge.toml:spelling:ago
+0-exercice-1/resolution.md:spelling:SCL
+challenge.toml:spelling:SCL`
+
+func TestLoadExceptions(t *testing.T) {
+ exceptions := ParseExceptionString(sampleFile, nil)
+
+ if len(*exceptions) != 26 {
+ t.Fatalf("Expected 26 exceptions, got %d", len(*exceptions))
+ }
+}
+
+func TestFilterExceptions(t *testing.T) {
+ exceptions := ParseExceptionString(sampleFile, nil)
+
+ filteredExceptions := exceptions.GetFileExceptions("resolution.md")
+ if len(*filteredExceptions) != 1 {
+ t.Fatalf("Expected 1 exceptions, got %d", len(*filteredExceptions))
+ }
+
+ filteredExceptions = exceptions.GetFileExceptions("challenge.toml")
+ if len(*filteredExceptions) != 3 {
+ t.Fatalf("Expected 3 exceptions, got %d", len(*filteredExceptions))
+ }
+
+ filteredExceptions = exceptions.GetFileExceptions("0-exercice-1")
+ if len(*filteredExceptions) != 23 {
+ t.Fatalf("Expected 23 exceptions, got %d", len(*filteredExceptions))
+ }
+
+ filteredExceptions = exceptions.Filter2ndCol("spelling")
+ if len(*filteredExceptions) != 20 {
+ t.Fatalf("Expected 20 exceptions, got %d", len(*filteredExceptions))
+ }
+}
diff --git a/admin/sync/exercice_defines.go b/admin/sync/exercice_defines.go
index 20666fde..23b119a1 100644
--- a/admin/sync/exercice_defines.go
+++ b/admin/sync/exercice_defines.go
@@ -1,17 +1,23 @@
package sync
import (
+ "fmt"
"path"
+ "strconv"
"github.com/BurntSushi/toml"
+ "go.uber.org/multierr"
+
+ "srs.epita.fr/fic-server/libfic"
)
// ExerciceHintParams holds EHint definition infomation.
type ExerciceHintParams struct {
Filename string
Content string
- Cost int64
+ Cost *int64
Title string
+ NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
}
// ExerciceDependency holds dependency definitions information.
@@ -25,35 +31,101 @@ type ExerciceUnlockFile struct {
Filename string `toml:",omitempty"`
}
-// ExerciceFlagChoice holds informations about a choice (for MCQ and UCQ).
-type ExerciceFlagChoice struct {
- Label string `toml:",omitempty"`
- Value interface{} `toml:",omitempty"`
- Justification *ExerciceFlag `toml:",omitempty""`
+// ExerciceFile defines attributes on files.
+type ExerciceFile struct {
+ Filename string `toml:",omitempty"`
+ URL string `toml:",omitempty"`
+ Hidden bool `toml:",omitempty"`
+ Disclaimer string `toml:",omitempty"`
}
// ExerciceFlag holds informations about one flag.
type ExerciceFlag struct {
- Id int64
- Label string `toml:",omitempty"`
- Type string `toml:",omitempty"`
- Raw interface{}
- Separator string `toml:",omitempty"`
- Ordered bool `toml:",omitempty"`
- IgnoreCase bool `toml:",omitempty"`
- ValidatorRe string `toml:"validator_regexp,omitempty"`
- Help string `toml:",omitempty"`
- ChoicesCost int64 `toml:"choices_cost,omitempty"`
- Choice []ExerciceFlagChoice
- LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
- NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
- NoShuffle bool
+ Id int64
+ Label string `toml:",omitempty"`
+ Type string `toml:",omitempty"`
+ Raw interface{}
+ Separator string `toml:",omitempty"`
+ ShowLines bool `toml:",omitempty"`
+ Ordered bool `toml:",omitempty"`
+ CaseSensitive bool `toml:",omitempty"`
+ NoTrim bool `toml:",omitempty"`
+ CaptureRe string `toml:"capture_regexp,omitempty"`
+ SortReGroups bool `toml:"sort_capture_regexp_groups,omitempty"`
+ Placeholder string `toml:",omitempty"`
+ Help string `toml:",omitempty"`
+ BonusGain int32 `toml:"bonus_gain,omitempty"`
+ ChoicesCost int32 `toml:"choices_cost,omitempty"`
+ Choice []ExerciceFlagChoice
+ LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
+ NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
+ NeedFlags []int64 `toml:"need_flags,omitempty"`
+ NoShuffle bool
+ Unit string `toml:"unit,omitempty"`
+ Variant string `toml:"variant,omitempty"`
+ NumberMin interface{} `toml:"min,omitempty"`
+ NumberMax interface{} `toml:"max,omitempty"`
+ NumberStep interface{} `toml:"step,omitempty"`
+}
+
+func (f ExerciceFlag) RawString() []string {
+ switch f.Raw.(type) {
+ case string:
+ return []string{f.Raw.(string)}
+ case []string:
+ return f.Raw.([]string)
+ default:
+ return nil
+ }
+}
+
+func (f ExerciceFlag) RawNumber() ([]float64, error) {
+ switch f.Raw.(type) {
+ case float64:
+ return []float64{f.Raw.(float64)}, nil
+ case []float64:
+ return f.Raw.([]float64), nil
+ case int64:
+ return []float64{float64(f.Raw.(int64))}, nil
+ case []int64:
+ var res []float64
+ for _, raw := range f.Raw.([]int64) {
+ res = append(res, float64(raw))
+ }
+ return res, nil
+ case string:
+ if v, err := strconv.ParseFloat(f.Raw.(string), 64); err == nil {
+ return []float64{v}, nil
+ } else {
+ return nil, err
+ }
+ case []string:
+ var res []float64
+ for _, raw := range f.Raw.([]string) {
+ if v, err := strconv.ParseFloat(raw, 64); err == nil {
+ res = append(res, v)
+ } else {
+ return nil, err
+ }
+ }
+ return res, nil
+ default:
+ return nil, fmt.Errorf("invalid raw type: %T", f.Raw)
+ }
+}
+
+// ExerciceFlagChoice holds informations about a choice (for MCQ and UCQ).
+type ExerciceFlagChoice struct {
+ ExerciceFlag
+ Value interface{} `toml:",omitempty"`
}
// ExerciceParams contains values parsed from defines.txt.
type ExerciceParams struct {
+ WIP bool `toml:"wip"`
Gain int64
Tags []string
+ Files []ExerciceFile `toml:"file"`
Hints []ExerciceHintParams `toml:"hint"`
Dependencies []ExerciceDependency `toml:"depend"`
Flags []ExerciceFlag `toml:"flag"`
@@ -61,12 +133,94 @@ type ExerciceParams struct {
FlagsUCQ []ExerciceFlag `toml:"flag_ucq"`
}
-// parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge.
-func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, err error) {
- var defs string
- defs, err = getFileContent(i, path.Join(exPath, "challenge.txt"))
+func (p ExerciceParams) GetRawFlags() (ret []string) {
+ for _, f := range append(p.Flags, p.FlagsUCQ...) {
+ raw := f.RawString()
+ if len(raw) > 0 {
+ ret = append(ret, raw...)
+ }
+ }
+ for _, f := range p.FlagsMCQ {
+ for _, c := range f.Choice {
+ ret = append(ret, c.Label)
+ }
+ }
+ return
+}
- _, err = toml.Decode(defs, &p)
+// parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge.
+func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.MetaData, err error) {
+ var defs string
+
+ if i.Exists(path.Join(exPath, "challenge.toml")) {
+ defs, err = GetFileContent(i, path.Join(exPath, "challenge.toml"))
+ } else {
+ defs, err = GetFileContent(i, path.Join(exPath, "challenge.txt"))
+ }
+
+ if err != nil {
+ return
+ }
+
+ md, err = toml.Decode(defs, &p)
return
}
+
+// getExerciceParams returns normalized
+func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs error) {
+ var err error
+ if params, _, err = parseExerciceParams(i, exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
+ } else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
+ if !params.WIP {
+ errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
+ }
+ } else {
+ // Treat legacy UCQ flags as ExerciceFlag
+ for _, flag := range params.FlagsUCQ {
+ params.Flags = append(params.Flags, ExerciceFlag{
+ Id: flag.Id,
+ Label: flag.Label,
+ Type: "ucq",
+ Raw: flag.Raw,
+ CaptureRe: flag.CaptureRe,
+ Placeholder: flag.Placeholder,
+ ChoicesCost: flag.ChoicesCost,
+ Choice: flag.Choice,
+ LockedFile: flag.LockedFile,
+ NeedFlag: flag.NeedFlag,
+ })
+ }
+ params.FlagsUCQ = []ExerciceFlag{}
+
+ // Treat legacy MCQ flags as ExerciceFlag
+ for _, flag := range params.FlagsMCQ {
+ params.Flags = append(params.Flags, ExerciceFlag{
+ Id: flag.Id,
+ Label: flag.Label,
+ Type: "mcq",
+ ChoicesCost: flag.ChoicesCost,
+ Choice: flag.Choice,
+ LockedFile: flag.LockedFile,
+ NeedFlag: flag.NeedFlag,
+ })
+ }
+ params.FlagsMCQ = []ExerciceFlag{}
+ }
+ return
+}
+
+func GetExerciceFilesParams(i Importer, exercice *fic.Exercice) (map[string]ExerciceFile, error) {
+ params, _, err := parseExerciceParams(i, exercice.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ paramsFiles := map[string]ExerciceFile{}
+ for _, f := range params.Files {
+ paramsFiles[f.Filename] = f
+ }
+
+ return paramsFiles, nil
+}
diff --git a/admin/sync/exercice_files.go b/admin/sync/exercice_files.go
index a9689110..3ce45c80 100644
--- a/admin/sync/exercice_files.go
+++ b/admin/sync/exercice_files.go
@@ -1,86 +1,512 @@
package sync
import (
+ "compress/gzip"
"encoding/hex"
"fmt"
+ "io"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
"path"
"strings"
"unicode"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/multierr"
+
"srs.epita.fr/fic-server/libfic"
)
-// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
-// It takes care of DIGESTS.txt and ensure imported files match.
-func SyncExerciceFiles(i Importer, exercice fic.Exercice) (errs []string) {
+type remoteFileDomainWhitelist []string
+
+func (l *remoteFileDomainWhitelist) String() string {
+ return fmt.Sprintf("%v", *l)
+}
+
+func (l *remoteFileDomainWhitelist) Set(value string) error {
+ *l = append(*l, value)
+ return nil
+}
+
+var RemoteFileDomainWhitelist remoteFileDomainWhitelist
+
+func isURLAllowed(in string) bool {
+ if len(RemoteFileDomainWhitelist) == 0 {
+ return true
+ }
+
+ u, err := url.Parse(in)
+ if err != nil {
+ return false
+ }
+
+ for _, t := range RemoteFileDomainWhitelist {
+ if t == u.Host {
+ return true
+ }
+ }
+
+ return false
+}
+
+func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs error) {
// If no files directory, don't display error
- if ! i.exists(path.Join(exercice.Path, "files")) {
+ if !i.Exists(path.Join(exercice.Path, into)) {
return
}
- if digs, err := getFileContent(i, path.Join(exercice.Path, "files", "DIGESTS.txt")); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to read DIGESTS.txt: %s", path.Base(exercice.Path), err))
- } else if _, err := exercice.WipeFiles(); err != nil {
- errs = append(errs, err.Error())
- } else if files, err := i.listDir(path.Join(exercice.Path, "files")); err != nil {
- errs = append(errs, err.Error())
- } else {
- // Parse DIGESTS.txt
- digests := map[string][]byte{}
+ // Parse DIGESTS.txt
+ if digs, err := GetFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), err)))
+ } else if len(digs) > 0 {
+ digests = map[string][]byte{}
for nline, d := range strings.Split(digs, "\n") {
if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 {
- errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: invalid format", path.Base(exercice.Path), nline + 1))
+ errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), nline+1)))
continue
} else if hash, err := hex.DecodeString(dsplt[0]); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: %s", path.Base(exercice.Path), nline + 1, err))
+ errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), nline+1, err)))
continue
} else {
digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash
}
}
+ }
- // Import splitted files
- var splittedFiles []string
- for _, fname := range files {
- if matched, _ := path.Match("*.00", fname); matched {
+ // Read file list
+ if flist, err := i.ListDir(path.Join(exercice.Path, into)); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(exercice, err))
+ } else {
+ for _, fname := range flist {
+ if fname == "DIGESTS.txt" || fname == ".gitattributes" {
+ continue
+ }
+
+ if matched, _ := path.Match("*.[0-9][0-9]", fname); matched {
fname = fname[:len(fname)-3]
- splittedFiles = append(splittedFiles, fname + ".[0-9][0-9]")
- files = append(files, fname)
- } else if matched, _ := path.Match("*00", fname); matched {
+ } else if matched, _ := path.Match("*[0-9][0-9]", fname); matched {
fname = fname[:len(fname)-2]
- splittedFiles = append(splittedFiles, fname + "[0-9][0-9]")
- files = append(files, fname)
} else if matched, _ := path.Match("*_MERGED", fname); matched {
- splittedFiles = append(splittedFiles, fname)
+ continue
+ }
+
+ fileFound := false
+ for _, f := range files {
+ if fname == f {
+ fileFound = true
+ break
+ }
+ }
+
+ if !fileFound {
+ files = append(files, fname)
+ }
+ }
+ }
+
+ // Complete with remote file names
+ if paramsFiles, err := GetExerciceFilesParams(i, exercice); err == nil {
+ for _, pf := range paramsFiles {
+ if pf.URL != "" {
+ found := false
+ for _, file := range files {
+ if file == pf.Filename {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ files = append(files, pf.Filename)
+ }
+ }
+ }
+ }
+
+ return
+}
+
+// CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest.
+func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs error) {
+ flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
+ errs = multierr.Append(errs, berrs)
+
+ paramsFiles, _ := GetExerciceFilesParams(i, exercice)
+
+ for _, fname := range flist {
+ if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) {
+ // File not found locally, is this a remote file?
+ if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
+ continue
+ } else if !isURLAllowed(pf.URL) {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("URL hostname is not whitelisted")))
+ continue
+ } else {
+ resp, err := http.Head(pf.URL)
+ if err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
+ continue
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode >= 300 {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("Unexpected status code for the HTTP response: %d %s", resp.StatusCode, resp.Status)))
+ continue
+ }
}
}
- // Import standard files
- for _, fname := range files {
- if fname == "DIGESTS.txt" {
- continue
- }
+ if _, ok := digests[fname]; !ok {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
+ } else {
+ files = append(files, fname)
+ }
+ }
- foundSplitted := false
- for _, sf := range splittedFiles {
- if matched, _ := path.Match(sf, fname); matched {
- foundSplitted = true
+ for fname := range digests {
+ if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz")) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz.00")) {
+ if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
+ if pf, exists := paramsFiles[fname+".gz"]; !exists || pf.URL == "" {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
}
}
- if foundSplitted {
- continue
+ }
+ }
+
+ return
+}
+
+// CheckExerciceFiles checks that remote files have the right digest.
+func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (files []string, errs error) {
+ flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
+ errs = multierr.Append(errs, berrs)
+
+ paramsFiles, err := GetExerciceFilesParams(i, exercice)
+ if err != nil {
+ errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
+ }
+
+ for _, fname := range flist {
+ dest := path.Join(exercice.Path, "files", fname)
+
+ if pf, exists := paramsFiles[fname]; exists && pf.URL != "" {
+ if li, ok := i.(LocalImporter); ok {
+ errs = multierr.Append(errs, DownloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false))
+ } else {
+ errs = multierr.Append(errs, DownloadExerciceFile(paramsFiles[fname], dest, exercice, false))
+ }
+ }
+
+ if fd, closer, err := GetFile(i, dest); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
+ continue
+ } else {
+ defer closer()
+
+ hash160, hash512 := fic.CreateHashBuffers(fd)
+
+ if _, err := fic.CheckBufferHash(hash160, hash512, digests[fname]); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
+ } else if size, err := GetFileSize(i, path.Join(exercice.Path, "files", fname)); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
+ } else {
+ var digest_shown []byte
+ if strings.HasSuffix(fname, ".gz") {
+ if d, exists := digests[strings.TrimSuffix(fname, ".gz")]; exists {
+ digest_shown = d
+
+ // Check that gunzipped file digest is correct
+ if fd, closer, err := GetFile(i, path.Join(exercice.Path, "files", fname)); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
+ continue
+ } else if gunzipfd, err := gzip.NewReader(fd); err != nil {
+ closer()
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to gunzip file: %w", err)))
+ continue
+ } else {
+ defer gunzipfd.Close()
+ defer closer()
+
+ hash160_inflate, hash512_inflate := fic.CreateHashBuffers(gunzipfd)
+
+ if _, err := fic.CheckBufferHash(hash160_inflate, hash512_inflate, digest_shown); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, strings.TrimSuffix(fname, ".gz"), err))
+ }
+ }
+ }
+ }
+
+ disclaimer := ""
+ if f, exists := paramsFiles[fname]; exists {
+ // Call checks hooks
+ for _, hk := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
+ }
+ }
+
+ if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
+ }
+ }
+
+ file := exercice.NewDummyFile(path.Join(exercice.Path, "files", fname), GetDestinationFilePath(path.Join(exercice.Path, "files", fname), nil), (*hash512).Sum(nil), digest_shown, disclaimer, size)
+
+ // Call checks hooks
+ for _, h := range hooks.fileHooks {
+ for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, e))
+ }
+ }
+ }
+ }
+
+ files = append(files, fname)
+ }
+ return
+}
+
+// DownloadExerciceFile is responsible to fetch remote files.
+func DownloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, force bool) (errs error) {
+ if st, err := os.Stat(dest); !force && !os.IsNotExist(err) {
+ resp, err := http.Head(pf.URL)
+ if err == nil && resp.ContentLength == st.Size() {
+ return
+ }
+ }
+
+ if !isURLAllowed(pf.URL) {
+ errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), fmt.Errorf("URL hostname is not whitelisted")))
+ return
+ }
+
+ log.Println("Download exercice file: ", pf.URL)
+
+ resp, err := http.Get(pf.URL)
+ if err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
+ return
+ }
+ defer resp.Body.Close()
+
+ if err = os.MkdirAll(path.Dir(dest), 0751); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
+ return
+ }
+
+ // Write file
+ var fdto *os.File
+ if fdto, err = os.Create(dest); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
+ return
+ } else {
+ defer fdto.Close()
+
+ _, err = io.Copy(fdto, resp.Body)
+ if err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
+ return
+ }
+ }
+
+ return
+}
+
+type importedFile struct {
+ file interface{}
+ Name string
+}
+
+func SyncExerciceFiles(i Importer, exercice *fic.Exercice, paramsFiles map[string]ExerciceFile, actionAfterImport func(fname string, digests map[string][]byte, filePath, origin string) (interface{}, error)) (ret []*importedFile, errs error) {
+ files, digests, berrs := BuildFilesListInto(i, exercice, "files")
+ errs = multierr.Append(errs, berrs)
+
+ // Import standard files
+ for _, fname := range files {
+ var f interface{}
+ var err error
+
+ if pf, exists := paramsFiles[fname]; exists && pf.URL != "" && !i.Exists(path.Join(exercice.Path, "files", fname)) {
+ dest := GetDestinationFilePath(pf.URL, &pf.Filename)
+
+ if _, err := os.Stat(dest); !os.IsNotExist(err) {
+ if d, err := actionAfterImport(fname, digests, dest, pf.URL); err == nil {
+ f = d
+ }
}
- if f, err := i.importFile(path.Join(exercice.Path, "files", fname),
- func(filePath string, origin string) (interface{}, error) {
- return exercice.ImportFile(filePath, origin, digests[fname])
- }); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to import file %q: %s", path.Base(exercice.Path), fname, err))
- continue
- } else if f.(fic.EFile).Size == 0 {
- errs = append(errs, fmt.Sprintf("%q: WARNING imported file %q is empty!", path.Base(exercice.Path), fname))
+ if f == nil {
+ errs = multierr.Append(errs, DownloadExerciceFile(paramsFiles[fname], dest, exercice, false))
+
+ f, err = actionAfterImport(fname, digests, dest, pf.URL)
+ }
+ } else {
+ f, err = i.importFile(path.Join(exercice.Path, "files", fname), func(filePath, origin string) (interface{}, error) {
+ return actionAfterImport(fname, digests, filePath, origin)
+ })
+ }
+
+ if err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
+ continue
+ }
+
+ ret = append(ret, &importedFile{
+ f,
+ fname,
+ })
+ }
+
+ return
+}
+
+// ImportExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
+// It takes care of DIGESTS.txt and ensure imported files match.
+func ImportExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs error) {
+ if _, err := exercice.WipeFiles(); err != nil {
+ errs = multierr.Append(errs, err)
+ }
+
+ paramsFiles, err := GetExerciceFilesParams(i, exercice)
+ if err != nil {
+ errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
+ return
+ }
+
+ actionAfterImport := func(fname string, digests map[string][]byte, filePath, origin string) (interface{}, error) {
+ var digest_shown []byte
+ if strings.HasSuffix(fname, ".gz") {
+ if d, exists := digests[strings.TrimSuffix(fname, ".gz")]; exists {
+ digest_shown = d
+ }
+ }
+
+ published := true
+ disclaimer := ""
+ if f, exists := paramsFiles[fname]; exists {
+ published = !f.Hidden
+
+ // Call checks hooks
+ for _, hk := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, err))
}
+ }
+
+ if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
+ }
+ }
+
+ return exercice.ImportFile(filePath, origin, digests[fname], digest_shown, disclaimer, published)
+ }
+
+ files, berrs := SyncExerciceFiles(i, exercice, paramsFiles, actionAfterImport)
+ errs = multierr.Append(errs, berrs)
+
+ // Import files in db
+ for _, file := range files {
+ fname := file.Name
+ f := file.file
+
+ if f.(*fic.EFile).Size == 0 {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
+ } else {
+ file := f.(*fic.EFile)
+
+ // Call checks hooks
+ for _, h := range hooks.fileHooks {
+ for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
+ errs = multierr.Append(errs, NewFileError(exercice, fname, e))
+ }
+ }
+
+ // Create empty non-gziped file for nginx gzip-static module
+ if len(file.ChecksumShown) > 0 && strings.HasSuffix(file.Name, ".gz") {
+ file.Name = strings.TrimSuffix(file.Name, ".gz")
+ file.Path = strings.TrimSuffix(file.Path, ".gz")
+
+ fd, err := os.Create(path.Join(fic.FilesDir, file.Path))
+ if err == nil {
+ fd.Close()
+
+ _, err = file.Update()
+ if err != nil {
+ log.Println("Unable to update file after .gz removal:", err.Error())
+ }
+ } else {
+ log.Printf("Unable to create %q: %s", file.Path, err)
+ }
+ }
}
}
return
}
+
+func GetRemoteExerciceFiles(thid, exid string) ([]*fic.EFile, error) {
+ theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, errs
+ }
+
+ exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
+ if exercice == nil {
+ return nil, errs
+ }
+
+ files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
+ if files == nil {
+ return nil, errs
+ }
+
+ var ret []*fic.EFile
+ for _, fname := range files {
+ fPath := path.Join(exercice.Path, "files", fname)
+ fSize, _ := GetFileSize(GlobalImporter, fPath)
+
+ file := fic.EFile{
+ Path: fPath,
+ Name: fname,
+ Checksum: digests[fname],
+ Size: fSize,
+ Published: true,
+ }
+
+ if d, exists := digests[strings.TrimSuffix(file.Name, ".gz")]; exists {
+ file.Name = strings.TrimSuffix(file.Name, ".gz")
+ file.Path = strings.TrimSuffix(file.Path, ".gz")
+ file.ChecksumShown = d
+ }
+
+ ret = append(ret, &file)
+ }
+
+ // Complete with attributes
+ if paramsFiles, err := GetExerciceFilesParams(GlobalImporter, exercice); err == nil {
+ for _, file := range ret {
+ if f, ok := paramsFiles[file.Name]; ok {
+ file.Published = !f.Hidden
+
+ if disclaimer, err := ProcessMarkdown(GlobalImporter, fixnbsp(f.Disclaimer), exercice.Path); err == nil {
+ file.Disclaimer = disclaimer
+ }
+ }
+ }
+ }
+
+ return ret, nil
+}
+
+// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
+func ApiGetRemoteExerciceFiles(c *gin.Context) {
+ files, err := GetRemoteExerciceFiles(c.Params.ByName("thid"), c.Params.ByName("exid"))
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, files)
+}
diff --git a/admin/sync/exercice_hints.go b/admin/sync/exercice_hints.go
index 30f427a2..acb40bd6 100644
--- a/admin/sync/exercice_hints.go
+++ b/admin/sync/exercice_hints.go
@@ -6,83 +6,195 @@ import (
"encoding/hex"
"fmt"
"io"
+ "net/http"
"os"
"path"
"strings"
- "srs.epita.fr/fic-server/libfic"
-
+ "github.com/gin-gonic/gin"
+ "go.uber.org/multierr"
_ "golang.org/x/crypto/blake2b"
+
+ "srs.epita.fr/fic-server/libfic"
)
-// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
-func SyncExerciceHints(i Importer, exercice fic.Exercice) (errs []string) {
- params, err := parseExerciceParams(i, exercice.Path)
+type importHint struct {
+ Line int
+ Hint *fic.EHint
+ FlagsDeps []int64
+}
+
+func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (hints []importHint, errs error) {
+ params, _, err := parseExerciceParams(i, exercice.Path)
if err != nil {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err))
+ errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
+ return
}
- if _, err := exercice.WipeHints(); err != nil {
- errs = append(errs, err.Error())
- } else {
- for n, hint := range params.Hints {
- if hint.Title == "" {
- hint.Title = fmt.Sprintf("Astuce #%d", n+1)
- } else {
- hint.Title = fixnbsp(hint.Title)
- }
- if hint.Cost <= 0 {
- hint.Cost = exercice.Gain / 4
- }
+ for n, hint := range params.Hints {
+ h := &fic.EHint{}
+ if hint.Title == "" {
+ h.Title = fmt.Sprintf("Astuce #%d", n+1)
+ } else {
+ h.Title = fixnbsp(hint.Title)
+ }
+ if hint.Cost == nil {
+ h.Cost = exercice.Gain / 4
+ } else {
+ h.Cost = *hint.Cost
+ }
- if hint.Filename != "" {
- if hint.Content != "" {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be filled at the same time", path.Base(exercice.Path), hint.Title, n+1))
+ if hint.Filename != "" {
+ if hint.Content != "" {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
+ continue
+ } else if !i.Exists(path.Join(exercice.Path, "files", hint.Filename)) {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
+ continue
+ } else {
+ // Handle files as downloadable content
+ if res, err := i.importFile(path.Join(exercice.Path, "files", hint.Filename), func(filePath string, origin string) (interface{}, error) {
+ // Calculate hash
+ hash512 := crypto.BLAKE2b_512.New()
+ if fd, err := os.Open(filePath); err != nil {
+ return nil, err
+ } else {
+ defer fd.Close()
+
+ reader := bufio.NewReader(fd)
+ if _, err := io.Copy(hash512, reader); err != nil {
+ return nil, err
+ }
+ }
+ result512 := hash512.Sum(nil)
+
+ // Special format for downloadable hints: $FILES + hexhash + path from FILES/
+ return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil
+ }); err != nil {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
continue
- } else if !i.exists(path.Join(exercice.Path, "hints", hint.Filename)) {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): %s: File not found", path.Base(exercice.Path), hint.Title, n+1, hint.Filename))
+ } else if s, ok := res.(string); !ok {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
continue
} else {
- // Handle files as downloadable content
- if res, err := i.importFile(path.Join(exercice.Path, "hints", hint.Filename), func(filePath string, origin string) (interface{}, error) {
- // Calculate hash
- hash512 := crypto.BLAKE2b_512.New()
- if fd, err := os.Open(filePath); err != nil {
- return nil, err
- } else {
- defer fd.Close()
-
- reader := bufio.NewReader(fd)
- if _, err := io.Copy(hash512, reader); err != nil {
- return nil, err
- }
- }
- result512 := hash512.Sum(nil)
-
- // Special format for downloadable hints: $FILES + hexhash + path from FILES/
- return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil
- }); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: %s", path.Base(exercice.Path), hint.Filename, err))
- continue
- } else if s, ok := res.(string); !ok {
- errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: invalid string returned as filename", path.Base(exercice.Path), hint.Filename))
- continue
- } else {
- hint.Content = s
- }
+ h.Content = s
+ }
+ }
+ } else if hint.Content == "" {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
+ continue
+ } else {
+ // Call checks hooks
+ for _, hk := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(hk(h.Content, exercice.Language, exceptions)) {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, err))
}
- } else if hint.Content == "" {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be empty at the same time", path.Base(exercice.Path), hint.Title, n+1))
- continue
- } else if hint.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil{
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): error during markdown formating: %s", path.Base(exercice.Path), hint.Title, n+1, err))
}
+ if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
+ }
+ }
+
+ // Call checks hooks
+ for _, hook := range hooks.hintHooks {
+ for _, e := range multierr.Errors(hook(h, exercice, exceptions)) {
+ errs = multierr.Append(errs, NewHintError(exercice, h, n, e))
+ }
+ }
+
+ newHint := importHint{
+ Line: n + 1,
+ Hint: h,
+ }
+
+ // Read dependency to flag
+ for _, nf := range hint.NeedFlag {
+ newHint.FlagsDeps = append(newHint.FlagsDeps, nf.Id)
+ }
+
+ hints = append(hints, newHint)
+ }
+
+ return
+}
+
+// CheckExerciceHints checks if all hints are corrects..
+func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, error) {
+ exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
+
+ hints, errs := buildExerciceHints(i, exercice, exceptions)
+
+ for _, hint := range hints {
+ if hint.Hint.Cost >= exercice.Gain {
+ errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("hint's cost is higher than exercice gain")))
+ }
+ }
+
+ return hints, errs
+}
+
+// SyncExerciceHints reads the content of files/ directories and import it as EHint for the given challenge.
+func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag, exceptions *CheckExceptions) (hintsBindings map[int]*fic.EHint, errs error) {
+ if _, err := exercice.WipeHints(); err != nil {
+ errs = multierr.Append(errs, err)
+ } else {
+ exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
+
+ hints, berrs := buildExerciceHints(i, exercice, exceptions)
+ errs = multierr.Append(errs, berrs)
+
+ hintsBindings = map[int]*fic.EHint{}
+
+ for _, hint := range hints {
// Import hint
- if _, err := exercice.AddHint(hint.Title, hint.Content, hint.Cost); err != nil {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): %s", path.Base(exercice.Path), hint.Title, n+1, err))
+ if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
+ errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
+ } else {
+ hintsBindings[hint.Line] = h
+
+ // Handle hints dependencies on flags
+ for _, nf := range hint.FlagsDeps {
+ if f, ok := flagsBindings[nf]; ok {
+ if herr := h.AddDepend(f); herr != nil {
+ errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
+ }
+ } else {
+ errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
+ }
+ }
}
}
}
return
}
+
+func GetRemoteExerciceHints(thid, exid string) ([]importHint, error) {
+ theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, errs
+ }
+
+ exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
+ if exercice == nil {
+ return nil, errs
+ }
+
+ hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions)
+ if hints == nil {
+ return nil, errs
+ }
+
+ return hints, nil
+}
+
+// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
+func ApiGetRemoteExerciceHints(c *gin.Context) {
+ hints, errs := GetRemoteExerciceHints(c.Params.ByName("thid"), c.Params.ByName("exid"))
+ if hints != nil {
+ c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
+ return
+ }
+
+ c.JSON(http.StatusOK, hints)
+}
diff --git a/admin/sync/exercice_keys.go b/admin/sync/exercice_keys.go
index a20bbbd0..e62dcf34 100644
--- a/admin/sync/exercice_keys.go
+++ b/admin/sync/exercice_keys.go
@@ -2,12 +2,18 @@ package sync
import (
"fmt"
+ "math"
"math/rand"
+ "net/http"
"path"
"sort"
+ "strconv"
"strings"
"unicode"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/multierr"
+
"srs.epita.fr/fic-server/libfic"
)
@@ -31,13 +37,11 @@ func validatorRegexp(vre string) (validator_regexp *string) {
return
}
-func getRawKey(input interface{}, validatorRe string, ordered bool) (raw string, prep string, errs []string) {
- separator := ","
-
+func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs error) {
// Concatenate array
if f, ok := input.([]interface{}); ok {
if len(validatorRe) > 0 {
- errs = append(errs, "ValidatorRe cannot be defined for this kind of flag.")
+ errs = multierr.Append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
validatorRe = ""
}
@@ -45,36 +49,51 @@ func getRawKey(input interface{}, validatorRe string, ordered bool) (raw string,
separator = ","
} else if len(separator) > 1 {
separator = string(separator[0])
- errs = append(errs, "separator truncated to %q")
+ errs = multierr.Append(errs, fmt.Errorf("separator truncated to %q", separator))
}
var fitems []string
- for _, v := range f {
+ for i, v := range f {
if g, ok := v.(string); ok {
if strings.Index(g, separator) != -1 {
- errs = append(errs, "flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.")
+ errs = multierr.Append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", separator))
return
} else {
fitems = append(fitems, g)
}
} else {
- errs = append(errs, "item %d has an invalid type: can only be string, is %T.")
+ errs = multierr.Append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
return
}
}
ignord := "f"
if !ordered {
- sort.Strings(fitems)
+ // Sort the list without taking the case in count.
+ sort.Slice(fitems, func(i, j int) bool {
+ return strings.ToLower(fitems[i]) < strings.ToLower(fitems[j])
+ })
ignord = "t"
}
+
+ nbLines := 0
+ if showLines {
+ if len(fitems) > 9 {
+ errs = multierr.Append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
+ } else {
+ nbLines = len(fitems)
+ }
+ }
+
raw = strings.Join(fitems, separator) + separator
- prep = "`" + separator + ignord
+ prep = fmt.Sprintf("`%s%s%d", separator, ignord, nbLines)
} else if f, ok := input.(int64); ok {
raw = fmt.Sprintf("%d", f)
+ } else if f, ok := input.(float64); ok {
+ raw = strconv.FormatFloat(f, 'f', -1, 64)
} else if f, ok := input.(string); !ok {
- errs = append(errs, fmt.Sprintf("has an invalid type: can only be []string or string, not %T", input))
+ errs = multierr.Append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
return
} else {
raw = f
@@ -82,234 +101,630 @@ func getRawKey(input interface{}, validatorRe string, ordered bool) (raw string,
return
}
-// SyncExerciceFlags reads the content of challenge.txt and import "classic" flags as Key for the given challenge.
-func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
- if _, err := exercice.WipeFlags(); err != nil {
- errs = append(errs, err.Error())
- } else if _, err := exercice.WipeMCQs(); err != nil {
- errs = append(errs, err.Error())
- } else if params, err := parseExerciceParams(i, exercice.Path); err != nil {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err))
- } else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
- errs = append(errs, fmt.Sprintf("%q: has no flag", path.Base(exercice.Path)))
+func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (f *fic.FlagLabel, errs error) {
+ if len(flag.Label) == 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
+ return
+ }
+
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
+ }
+ }
+
+ if mdlabel, err := ProcessMarkdown(GlobalImporter, flag.Label, exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
} else {
- kmap := map[int64]fic.Flag{}
+ if strings.Count(flag.Label, "\n\n") == 0 {
+ flag.Label = mdlabel[3 : len(mdlabel)-4]
+ } else {
+ flag.Label = mdlabel
+ }
+ }
- // Import UCQ flags
- for _, flag := range params.FlagsUCQ {
- params.Flags = append(params.Flags, ExerciceFlag{
- Id: flag.Id,
- Label: flag.Label,
- Type: "ucq",
- Raw: flag.Raw,
- ValidatorRe: flag.ValidatorRe,
- Help: flag.Help,
- ChoicesCost: flag.ChoicesCost,
- Choice: flag.Choice,
- LockedFile: flag.LockedFile,
- NeedFlag: flag.NeedFlag,
+ if flag.Raw != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
+ }
+
+ if len(flag.Choice) != 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
+ }
+
+ f = &fic.FlagLabel{
+ Order: int8(flagline),
+ Label: flag.Label,
+ Variant: flag.Variant,
+ }
+
+ // Call checks hooks
+ for _, h := range hooks.flagLabelHooks {
+ for _, e := range multierr.Errors(h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
+ }
+ }
+ return
+}
+
+func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (f *fic.Flag, choices []*fic.FlagChoice, errs error) {
+ if len(flag.Label) == 0 {
+ flag.Label = defaultLabel
+ }
+
+ if len(flag.Variant) != 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
+ }
+
+ if flag.Label[0] == '`' {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
+ flag.Label = flag.Label[1:]
+ }
+
+ raw, prep, terrs := getRawKey(flag.Raw, flag.CaptureRe, flag.Ordered, flag.ShowLines, flag.Separator)
+
+ errors := multierr.Errors(terrs)
+ if len(errors) > 0 {
+ for _, terr := range errors {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
+ }
+ f = nil
+ return
+ }
+ flag.Label = prep + flag.Label
+
+ if len(flag.Label) > 255 && flag.Type != "label" {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
+ }
+
+ if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
+ }
+
+ hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.CaptureRe), flag.SortReGroups)
+ if err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
+ return
+ }
+ fk := &fic.FlagKey{
+ Type: flag.Type,
+ IdExercice: exercice.Id,
+ Order: int8(flagline),
+ Label: flag.Label,
+ Placeholder: flag.Placeholder,
+ Help: flag.Help,
+ Unit: flag.Unit,
+ IgnoreCase: !flag.CaseSensitive,
+ Multiline: flag.Type == "text",
+ CaptureRegexp: validatorRegexp(flag.CaptureRe),
+ SortReGroups: flag.SortReGroups,
+ Checksum: hashedFlag[:],
+ ChoicesCost: flag.ChoicesCost,
+ BonusGain: flag.BonusGain,
+ }
+
+ // Call checks hooks
+ for _, h := range hooks.flagKeyHooks {
+ for _, e := range multierr.Errors(h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
+ }
+ }
+
+ fl := fic.Flag(fk)
+ f = &fl
+
+ if len(flag.Choice) > 0 || (flag.Type == "ucq" || flag.Type == "radio") {
+ // Import choices
+ hasOne := false
+
+ if !flag.NoShuffle {
+ rand.Shuffle(len(flag.Choice), func(i, j int) {
+ flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
})
}
- // Import MCQ flags
- for _, flag := range params.FlagsMCQ {
- params.Flags = append(params.Flags, ExerciceFlag{
- Id: flag.Id,
- Label: flag.Label,
- Type: "mcq",
- ChoicesCost: flag.ChoicesCost,
- Choice: flag.Choice,
- LockedFile: flag.LockedFile,
- NeedFlag: flag.NeedFlag,
- })
- }
-
- // Import normal flags
- for nline, flag := range params.Flags {
- if len(flag.Label) == 0 {
- flag.Label = "Flag"
- }
-
- if flag.Label[0] == '`' {
- errs = append(errs, fmt.Sprintf("%q: flag #%d: Label should not begin with `.", path.Base(exercice.Path), nline + 1))
- flag.Label = flag.Label[1:]
- }
-
- switch strings.ToLower(flag.Type) {
- case "":
- flag.Type = "key"
- case "key":
- flag.Type = "key"
- case "ucq":
- flag.Type = "ucq"
- case "mcq":
- flag.Type = "mcq"
- default:
- errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', 'mcq' or 'ucq'.", path.Base(exercice.Path), nline + 1))
+ for _, choice := range flag.Choice {
+ val, prep, terrs := getRawKey(choice.Value, "", false, false, "")
+ errors := multierr.Errors(terrs)
+ if len(errors) > 0 {
+ for _, terr := range errors {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
+ }
continue
}
- var addedFlag fic.Flag
+ if len(choice.Label) == 0 {
+ choice.Label = val
+ }
+ choice.Label = prep + choice.Label
- if flag.Type == "key" || flag.Type == "ucq" {
- raw, prep, terrs := getRawKey(flag.Raw, flag.ValidatorRe, flag.Ordered)
+ if !flag.CaseSensitive {
+ val = strings.ToLower(val)
+ }
- if len(terrs) > 0 {
- for _, err := range terrs {
- errs = append(errs, fmt.Sprintf("%q: flag #%d: %s", path.Base(exercice.Path), nline + 1, err))
- }
- continue
- }
- flag.Label = prep + flag.Label
+ fc := &fic.FlagChoice{
+ Label: choice.Label,
+ Value: val,
+ }
- if !isFullGraphic(raw) {
- errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), nline + 1))
- }
-
- if k, err := exercice.AddRawFlagKey(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(raw), flag.ChoicesCost); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err))
- continue
- } else {
- addedFlag = k
- if len(flag.Choice) > 0 || flag.Type == "ucq" {
- // Import choices
- hasOne := false
-
- if !flag.NoShuffle {
- rand.Shuffle(len(flag.Choice), func(i, j int) {
- flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
- })
- }
-
- for cid, choice := range flag.Choice {
- val, prep, terrs := getRawKey(choice.Value, "", false)
- if len(terrs) > 0 {
- for _, err := range terrs {
- errs = append(errs, fmt.Sprintf("%q: flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
- }
- continue
- }
-
- if len(choice.Label) == 0 {
- choice.Label = val
- }
- choice.Label = prep + choice.Label
-
- if _, err := k.AddChoice(choice.Label, val); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error in UCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err))
- continue
- }
-
- if val == raw {
- hasOne = true
- }
- }
- if !hasOne {
- errs = append(errs, fmt.Sprintf("%q: error in UCQ %d: no valid answer defined.", path.Base(exercice.Path), nline + 1))
- }
- }
- }
- } else if flag.Type == "mcq" {
- if f, err := exercice.AddMCQ(flag.Label); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error flag MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
- continue
- } else {
- addedFlag = f
- hasOne := false
- isJustified := false
-
- if !flag.NoShuffle {
- rand.Shuffle(len(flag.Choice), func(i, j int) {
- flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
- })
- }
-
- for cid, choice := range flag.Choice {
- var val bool
- var justify string
-
- if choice.Justification != nil {
- if hasOne && !isJustified {
- errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1))
- continue
- }
-
- val = true
- isJustified = true
- } else if p, ok := choice.Value.(bool); ok {
- val = p
- if isJustified {
- errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1))
- continue
- }
- } else if choice.Value == nil {
- val = false
- } else {
- errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: incorrect type for value: %T is not boolean.", path.Base(exercice.Path), nline + 1, cid, choice.Value))
- continue
- }
-
- if e, err := f.AddEntry(choice.Label, val); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err))
- continue
- } else if isJustified && choice.Justification != nil {
- var prep string
- var terrs []string
- justify, prep, terrs = getRawKey(choice.Justification.Raw, choice.Justification.ValidatorRe, choice.Justification.Ordered)
- if len(terrs) > 0 {
- for _, err := range terrs {
- errs = append(errs, fmt.Sprintf("%q: flag MCQ #%d, choice #%d: %s", path.Base(exercice.Path), nline + 1, cid, err))
- }
- continue
- }
-
- if len(choice.Justification.Label) == 0 {
- choice.Justification.Label = "Flag correspondant"
- }
- choice.Justification.Label = prep + choice.Justification.Label
-
- if _, err := exercice.AddRawFlagKey(fmt.Sprintf("%%%d%%%s", e.Id, choice.Justification.Label), choice.Justification.Help, choice.Justification.IgnoreCase, validatorRegexp(choice.Justification.ValidatorRe), []byte(justify), flag.ChoicesCost); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
- continue
- }
- }
-
- if val {
- hasOne = true
- }
- }
- if !hasOne {
- errs = append(errs, fmt.Sprintf("%q: warning MCQ %d: no valid answer defined, is this really expected?", path.Base(exercice.Path), nline + 1))
- }
+ // Call checks hooks
+ for _, h := range hooks.flagChoiceHooks {
+ for _, e := range multierr.Errors(h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
- if flag.Id != 0 {
- kmap[flag.Id] = addedFlag
+ choices = append(choices, fc)
+
+ if val == "true" || val == "false" {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("value can't be %q, this is not a MCQ, the value has to be meaningful. The value is shown to players as response identifier.", val)))
}
- // Import dependency to flag
- for _, nf := range flag.NeedFlag {
- if rf, ok := kmap[nf.Id]; !ok {
- errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%d: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id))
- continue
- } else if err := addedFlag.AddDepend(rf); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to id=%d: %s", path.Base(exercice.Path), nline + 1, nf.Id, err))
- continue
- }
+ if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
+ hasOne = true
}
+ }
+ if !hasOne {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
+ }
- // Import dependency to file
- for _, lf := range flag.LockedFile {
- if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err))
- continue
- } else if err := rf.AddDepend(addedFlag); err != nil {
- errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err))
- continue
- }
+ // Call checks hooks
+ for _, h := range hooks.flagKeyWithChoicesHooks {
+ for _, e := range multierr.Errors(h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
}
return
}
+
+type importFlag struct {
+ origin ExerciceFlag
+ Line int
+ Flag fic.Flag
+ JustifyOf *fic.MCQ_entry
+ Choices []*fic.FlagChoice
+ FilesDeps []string
+ FlagsDeps []int64
+}
+
+func iface2Number(input interface{}, output *string) (norm float64, err error) {
+ if input != nil {
+ if v, ok := input.(int64); ok {
+ *output = fmt.Sprintf("%d", v)
+ norm = float64(v)
+ } else if v, ok := input.(float64); ok {
+ *output = strconv.FormatFloat(v, 'f', -1, 64)
+ norm = v
+ } else {
+ err = fmt.Errorf("has an invalid type: expected int or float, got %T", input)
+ }
+ }
+ return
+}
+
+// buildExerciceFlag read challenge.txt and extract all flags.
+func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (ret []importFlag, errs error) {
+ switch strings.ToLower(flag.Type) {
+ case "":
+ flag.Type = "key"
+ case "label":
+ flag.Type = "label"
+ case "key":
+ flag.Type = "key"
+ case "number":
+ var smin, smax, sstep string
+ fstep, err := iface2Number(flag.NumberStep, &sstep)
+ if err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
+ }
+
+ _, err = iface2Number(flag.NumberMin, &smin)
+ if err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
+ }
+
+ _, err = iface2Number(flag.NumberMax, &smax)
+ if err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
+ }
+
+ // Ensure step permit validating the flag
+ if rns, err := flag.RawNumber(); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("raw %w", err)))
+ } else {
+ if fstep == 0 {
+ fstep = 1.0
+ }
+
+ for _, rn := range rns {
+ v := math.Abs(rn) / fstep
+ if float64(int(v)) != v {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choosen step=%f doesn't include response=%f", fstep, rn)))
+ }
+ }
+ }
+
+ flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep)
+ case "text":
+ flag.Type = "text"
+ case "vector":
+ flag.Type = "vector"
+ case "ucq":
+ flag.Type = "ucq"
+ case "radio":
+ flag.Type = "radio"
+ case "mcq":
+ flag.Type = "mcq"
+ case "justified":
+ flag.Type = "justified"
+ default:
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'justified', 'ucq', 'radio' or 'vector'")))
+ return
+ }
+
+ if !strings.HasPrefix(flag.Type, "number") {
+ if flag.NumberMin != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
+ } else if flag.NumberMax != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
+ } else if flag.NumberStep != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
+ }
+ }
+
+ if len(flag.Help) > 0 {
+ // Call checks hooks
+ for _, hk := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(hk(flag.Help, exercice.Language, exceptions)) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, err))
+ }
+ }
+
+ if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
+ } else {
+ flag.Help = mdhelp[3 : len(mdhelp)-4]
+ }
+ }
+
+ if flag.Type == "label" {
+ addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1, exceptions)
+ errs = multierr.Append(errs, berrs)
+ if addedFlag != nil {
+ ret = append(ret, importFlag{
+ origin: flag,
+ Line: nline + 1,
+ Flag: addedFlag,
+ })
+ }
+ } else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
+ addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag", exceptions)
+ errs = multierr.Append(errs, berrs)
+ if addedFlag != nil {
+ ret = append(ret, importFlag{
+ origin: flag,
+ Line: nline + 1,
+ Flag: *addedFlag,
+ Choices: choices,
+ })
+ }
+ } else if flag.Type == "mcq" || flag.Type == "justified" {
+ addedFlag := fic.MCQ{
+ IdExercice: exercice.Id,
+ Order: int8(nline + 1),
+ Title: flag.Label,
+ Entries: []*fic.MCQ_entry{},
+ }
+
+ hasOne := false
+ isJustified := flag.Type == "justified"
+
+ if len(flag.Variant) != 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
+ }
+
+ if !flag.NoShuffle {
+ rand.Shuffle(len(flag.Choice), func(i, j int) {
+ flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
+ })
+ }
+
+ for cid, choice := range flag.Choice {
+ var val bool
+
+ if choice.Raw != nil {
+ if hasOne && !isJustified {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
+ continue
+ }
+
+ val = true
+ isJustified = true
+ } else if p, ok := choice.Value.(bool); ok {
+ val = p
+ if isJustified {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
+ continue
+ }
+ } else if choice.Value == nil {
+ val = false
+ } else {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
+ continue
+ }
+
+ entry := &fic.MCQ_entry{
+ Label: choice.Label,
+ Response: val,
+ }
+ addedFlag.Entries = append(addedFlag.Entries, entry)
+
+ if isJustified && choice.Raw != nil {
+ addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant", exceptions)
+ errs = multierr.Append(errs, berrs)
+ if addedFlag != nil {
+ ret = append(ret, importFlag{
+ origin: flag,
+ Line: nline + 1,
+ Flag: *addedFlag,
+ JustifyOf: entry,
+ Choices: choices,
+ })
+ }
+ }
+ }
+
+ // Call checks hooks
+ for _, h := range hooks.flagMCQHooks {
+ for _, e := range multierr.Errors(h(&addedFlag, addedFlag.Entries, exercice, exceptions)) {
+ errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, e))
+ }
+ }
+
+ ret = append([]importFlag{importFlag{
+ origin: flag,
+ Line: nline + 1,
+ Flag: &addedFlag,
+ }}, ret...)
+ }
+ return
+}
+
+// buildExerciceFlags read challenge.txt and extract all flags.
+func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (flags map[int64]importFlag, flagids []int64, errs error) {
+ params, gerrs := getExerciceParams(i, exercice)
+ if len(multierr.Errors(gerrs)) > 0 {
+ return flags, flagids, gerrs
+ }
+
+ flags = map[int64]importFlag{}
+
+ for nline, flag := range params.Flags {
+ if flag.Id == 0 {
+ // TODO: should be more smart than that. Perhaps search to increment if possible.
+ flag.Id = rand.Int63()
+ }
+
+ // Ensure flag ID is unique
+ for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] {
+ errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
+ flag.Id = rand.Int63()
+ }
+
+ newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline, exceptions)
+ errs = multierr.Append(errs, ferrs)
+ if len(newFlags) > 0 {
+ for _, newFlag := range newFlags {
+ fId := flag.Id
+ for _, ok := flags[fId]; ok; _, ok = flags[fId] {
+ fId = rand.Int63()
+ }
+
+ // Read dependency to flag
+ for _, nf := range flag.NeedFlag {
+ if len(nf.Theme) > 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
+ }
+ newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf.Id)
+ }
+ for _, nf := range flag.NeedFlags {
+ newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf)
+ }
+
+ // Read dependency to file
+ for _, lf := range flag.LockedFile {
+ newFlag.FilesDeps = append(newFlag.FilesDeps, lf.Filename)
+ }
+
+ flags[fId] = newFlag
+ flagids = append(flagids, fId)
+ }
+ }
+ }
+
+ return
+}
+
+// CheckExerciceFlags checks if all flags for the given challenge are correct.
+func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs error) {
+ exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
+
+ flags, flagsids, berrs := buildExerciceFlags(i, exercice, exceptions)
+ errs = multierr.Append(errs, berrs)
+
+ for _, flagid := range flagsids {
+ if flag, ok := flags[flagid]; ok {
+ // Check dependency to flag
+ for _, nf := range flag.FlagsDeps {
+ if _, ok := flags[nf]; !ok {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
+ }
+ }
+
+ if fk, ok := flag.Flag.(*fic.FlagKey); ok {
+ // Check dependency to flag optional flag
+ if fk.BonusGain == 0 {
+ for _, nf := range flag.FlagsDeps {
+ if fk2, ok := flags[nf].Flag.(*fic.FlagKey); ok && fk2.BonusGain != 0 {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag is not optional but depend on flag id=%d which is optional", nf)))
+ }
+ }
+ }
+
+ if int64(fk.ChoicesCost) >= exercice.Gain {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag's choice_cost is higher than exercice gain")))
+ }
+
+ if raw, ok := flag.origin.Raw.(string); ok && raw == fk.Placeholder {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag's placeholder and raw are identical")))
+ }
+ }
+
+ // Check dependency loop
+ deps := flag.FlagsDeps
+ for i := 0; i < len(deps); i++ {
+ if deps[i] == flagid {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
+ break
+ }
+
+ deploppadd:
+ for _, d := range flags[deps[i]].FlagsDeps {
+ for _, dd := range deps {
+ if dd == d {
+ continue deploppadd
+ }
+ }
+ deps = append(deps, d)
+ }
+ }
+
+ // Check dependency to file
+ for _, lf := range flag.FilesDeps {
+ found := false
+ for _, f := range files {
+ if f == lf {
+ found = true
+ break
+ }
+ }
+ if !found {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
+ }
+ }
+
+ rf = append(rf, flag.Flag)
+ }
+ }
+
+ return
+}
+
+// ExerciceFlagsMap builds the flags bindings between challenge.txt and DB.
+func ExerciceFlagsMap(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Flag) {
+ flags, flagids, _ := buildExerciceFlags(i, exercice, nil)
+
+ kmap = map[int64]fic.Flag{}
+
+ for _, flagid := range flagids {
+ if flag, ok := flags[flagid]; ok {
+ if addedFlag, err := flag.Flag.RecoverId(); err == nil {
+ kmap[flagid] = addedFlag
+ }
+ }
+ }
+
+ return
+}
+
+// SyncExerciceFlags imports all kind of flags for the given challenge.
+func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (kmap map[int64]fic.Flag, errs error) {
+ if _, err := exercice.WipeFlags(); err != nil {
+ errs = multierr.Append(errs, err)
+ } else if _, err := exercice.WipeMCQs(); err != nil {
+ errs = multierr.Append(errs, err)
+ } else {
+ exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
+
+ flags, flagids, berrs := buildExerciceFlags(i, exercice, exceptions)
+ errs = multierr.Append(errs, berrs)
+
+ kmap = map[int64]fic.Flag{}
+
+ // Import flags
+ for _, flagid := range flagids {
+ if flag, ok := flags[flagid]; ok {
+ if flag.JustifyOf != nil {
+ if f, ok := flag.Flag.(*fic.FlagKey); ok {
+ f.Label = fmt.Sprintf("%%%d%%%s", flag.JustifyOf.Id, f.Label)
+ }
+ }
+
+ if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, err))
+ } else {
+ if f, ok := addedFlag.(*fic.FlagKey); ok {
+ for _, choice := range flag.Choices {
+ if _, err := f.AddChoice(choice); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
+ }
+ }
+ }
+
+ kmap[flagid] = addedFlag
+
+ // Import dependency to flag
+ for _, nf := range flag.FlagsDeps {
+ if rf, ok := kmap[nf]; !ok {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to flag id=%d: id not defined, perhaps not available at time of processing", nf)))
+ } else if err := addedFlag.AddDepend(rf); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
+ }
+ }
+
+ // Import dependency to file
+ for _, lf := range flag.FilesDeps {
+ if rf, err := exercice.GetFileByFilename(lf); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
+ } else if err := rf.AddDepend(addedFlag); err != nil {
+ errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return
+}
+
+func GetRemoteExerciceFlags(thid, exid string) ([]fic.Flag, error) {
+ theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, errs
+ }
+
+ exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
+ if exercice == nil {
+ return nil, errs
+ }
+
+ flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions)
+ if flags == nil {
+ return nil, errs
+ }
+
+ return flags, nil
+}
+
+// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
+func ApiGetRemoteExerciceFlags(c *gin.Context) {
+ flags, err := GetRemoteExerciceFlags(c.Params.ByName("thid"), c.Params.ByName("exid"))
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, flags)
+}
diff --git a/admin/sync/exercices.go b/admin/sync/exercices.go
index 29334f6c..b452d4b6 100644
--- a/admin/sync/exercices.go
+++ b/admin/sync/exercices.go
@@ -1,32 +1,45 @@
package sync
import (
- "errors"
+ "bytes"
"fmt"
+ "hash/adler32"
+ "image"
+ "net/http"
+ "net/url"
"path"
- "strings"
"strconv"
+ "strings"
+
+ "github.com/BurntSushi/toml"
+ "github.com/gin-gonic/gin"
+ "github.com/yuin/goldmark"
+ "go.uber.org/multierr"
"srs.epita.fr/fic-server/libfic"
- "gopkg.in/russross/blackfriday.v2"
)
+// Set AllowWIPExercice if WIP exercices are accepted.
+var AllowWIPExercice bool = false
+
func fixnbsp(s string) string {
return strings.Replace(strings.Replace(strings.Replace(s, " ?", " ?", -1), " !", " !", -1), " :", " :", -1)
}
-// getExercices returns all exercice directories existing in a given theme, considering the given Importer.
-func getExercices(i Importer, theme fic.Theme) ([]string, error) {
+// GetExercices returns all exercice directories existing in a given theme, considering the given Importer.
+func GetExercices(i Importer, theme *fic.Theme) ([]string, error) {
var exercices []string
if len(theme.Path) == 0 {
return []string{}, nil
- } else if dirs, err := i.listDir(theme.Path); err != nil {
+ } else if dirs, err := i.ListDir(theme.Path); err != nil {
return []string{}, err
} else {
for _, dir := range dirs {
- if _, err := i.listDir(path.Join(theme.Path, dir)); err == nil {
- exercices = append(exercices, dir)
+ if _, err := i.ListDir(path.Join(theme.Path, dir)); err == nil {
+ if dir[0] != '.' && strings.Contains(dir, "-") {
+ exercices = append(exercices, dir)
+ }
}
}
}
@@ -34,12 +47,12 @@ func getExercices(i Importer, theme fic.Theme) ([]string, error) {
return exercices, nil
}
-func buildDependancyMap(i Importer, theme fic.Theme) (dmap map[int64]fic.Exercice, err error) {
+func buildDependancyMap(i Importer, theme *fic.Theme) (dmap map[int64]*fic.Exercice, err error) {
var exercices []string
- if exercices, err = getExercices(i, theme); err != nil {
+ if exercices, err = GetExercices(i, theme); err != nil {
return
} else {
- dmap = map[int64]fic.Exercice{}
+ dmap = map[int64]*fic.Exercice{}
for _, edir := range exercices {
var eid int
@@ -50,10 +63,17 @@ func buildDependancyMap(i Importer, theme fic.Theme) (dmap map[int64]fic.Exercic
continue
}
- var e fic.Exercice
+ // ename can be overrride by title.txt
+ if i.Exists(path.Join(theme.Path, edir, "title.txt")) {
+ if myTitle, err := GetFileContent(i, path.Join(theme.Path, edir, "title.txt")); err == nil {
+ ename = strings.TrimSpace(myTitle)
+ }
+ }
+
+ var e *fic.Exercice
e, err = theme.GetExerciceByTitle(ename)
if err != nil {
- return
+ return dmap, fmt.Errorf("unable to GetExerciceByTitle(ename=%q, tid=%d): %w", ename, theme.Id, err)
}
dmap[int64(eid)] = e
@@ -66,90 +86,224 @@ func buildDependancyMap(i Importer, theme fic.Theme) (dmap map[int64]fic.Exercic
func parseExerciceDirname(edir string) (eid int, ename string, err error) {
edir_splt := strings.SplitN(edir, "-", 2)
if len(edir_splt) != 2 {
- err = errors.New(fmt.Sprintf("%q is not a valid exercice directory: missing id prefix", edir))
+ err = fmt.Errorf("%q is not a valid exercice directory: missing id prefix", edir)
return
}
eid, err = strconv.Atoi(edir_splt[0])
if err != nil {
- err = errors.New(fmt.Sprintf("%q: invalid exercice identifier: %s", edir, err))
+ err = fmt.Errorf("%q: invalid exercice identifier: %s", edir, err)
return
}
+ // ID 0: peak a deterministic-random-ordered ID instead
+ if eid == 0 {
+ eid = int(adler32.Checksum([]byte(edir_splt[1])))
+ }
+
ename = edir_splt[1]
return
}
-// SyncExercice imports new or updates existing given exercice.
-func SyncExercice(i Importer, theme fic.Theme, epath string, dmap *map[int64]fic.Exercice) (e fic.Exercice, eid int, errs []string) {
- var err error
+// BuildExercice creates an Exercice from a given importer.
+func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, exceptions *CheckExceptions, edir string, errs error) {
+ e = &fic.Exercice{}
e.Path = epath
- edir := path.Base(epath)
+ edir = path.Base(epath)
+ var err error
eid, e.Title, err = parseExerciceDirname(edir)
if err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to parse exercice directory: %s", edir, err))
- return
+ // Ignore eid if we are certain this is an exercice directory, eid will be 0
+ if !i.Exists(path.Join(epath, "title.txt")) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
+ return nil, p, eid, exceptions_in, edir, errs
+ }
+ }
+
+ // Get exceptions
+ exceptions = LoadExerciceException(i, theme, e, exceptions_in)
+ //log.Printf("Kept repochecker exceptions for this exercice: %v", exceptions)
+
+ if theme != nil {
+ e.Language = theme.Language
+ }
+ // Overwrite language if language.txt exists
+ if language, err := GetFileContent(i, path.Join(epath, "language.txt")); err == nil {
+ language = strings.TrimSpace(language)
+ if strings.Contains(language, "\n") {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("language.txt: Language can't contain new lines"), theme))
+ } else {
+ e.Language = language
+ }
+ }
+
+ // Overwrite title if title.txt exists
+ if myTitle, err := GetFileContent(i, path.Join(epath, "title.txt")); err == nil {
+ myTitle = strings.TrimSpace(myTitle)
+ if strings.Contains(myTitle, "\n") {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
+ } else {
+ e.Title = myTitle
+ }
+ }
+
+ // Character reserved for WIP exercices
+ if len(e.Title) > 0 && e.Title[0] == '%' {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title can't contain start by '%%'"), theme))
}
e.URLId = fic.ToURLid(e.Title)
e.Title = fixnbsp(e.Title)
- // Texts to format using Markdown
- e.Overview, err = getFileContent(i, path.Join(epath, "overview.txt"))
- if err != nil {
- errs = append(errs, fmt.Sprintf("%q: overview.txt: %s", edir, err))
- } else {
- e.Overview = fixnbsp(e.Overview)
- e.Headline = string(blackfriday.Run([]byte(strings.Split(e.Overview, "\n")[0])))
- if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil {
- errs = append(errs, fmt.Sprintf("%q: overview.txt: an error occurs during markdown formating: %s", edir, err))
- }
- }
-
- e.Statement, err = getFileContent(i, path.Join(epath, "statement.txt"))
- if err != nil {
- errs = append(errs, fmt.Sprintf("%q: statement.txt: %s", edir, err))
- } else {
- if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil {
- errs = append(errs, fmt.Sprintf("%q: statement.txt: an error occurs during markdown formating: %s", edir, err))
- }
- }
-
- if i.exists(path.Join(epath, "finished.txt")) {
- e.Finished, err = getFileContent(i, path.Join(epath, "finished.txt"))
- if err != nil {
- errs = append(errs, fmt.Sprintf("%q: finished.txt: %s", edir, err))
+ if i.Exists(path.Join(epath, "AUTHORS.txt")) {
+ if authors, err := getAuthors(i, epath); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
} else {
- if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil {
- errs = append(errs, fmt.Sprintf("%q: finished.txt: an error occurs during markdown formating: %s", edir, err))
+ // Format authors
+ e.Authors = strings.Join(authors, ", ")
+ }
+ }
+
+ // Process headline
+ if i.Exists(path.Join(epath, "headline.txt")) {
+ e.Headline, err = GetFileContent(i, path.Join(epath, "headline.txt"))
+ } else if i.Exists(path.Join(epath, "headline.md")) {
+ e.Headline, err = GetFileContent(i, path.Join(epath, "headline.md"))
+ }
+ if err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get exercice's headline: %w", err)))
+ }
+ if e.Headline != "" {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(e.Headline, e.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("headline.md: %w", err)))
}
}
}
- // Parse challenge.txt
- p, err := parseExerciceParams(i, epath)
+ // Texts to format using Markdown
+ if i.Exists(path.Join(epath, "overview.txt")) {
+ e.Overview, err = GetFileContent(i, path.Join(epath, "overview.txt"))
+ } else if i.Exists(path.Join(epath, "overview.md")) {
+ e.Overview, err = GetFileContent(i, path.Join(epath, "overview.md"))
+ } else {
+ err = fmt.Errorf("Unable to find overview.txt nor overview.md")
+ }
if err != nil {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", edir, err))
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
+ } else {
+ e.Overview = fixnbsp(e.Overview)
+
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(e.Overview, e.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
+ }
+ }
+
+ var buf bytes.Buffer
+ if e.Headline == "" {
+ err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf)
+ if err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
+ } else {
+ e.Headline = string(buf.Bytes())
+ }
+ }
+
+ if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
+ }
+ }
+
+ if i.Exists(path.Join(epath, "statement.txt")) {
+ e.Statement, err = GetFileContent(i, path.Join(epath, "statement.txt"))
+ } else if i.Exists(path.Join(epath, "statement.md")) {
+ e.Statement, err = GetFileContent(i, path.Join(epath, "statement.md"))
+ } else {
+ err = fmt.Errorf("Unable to find statement.txt nor statement.md")
+ }
+ if err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
+ } else {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(e.Statement, e.Language, exceptions.GetFileExceptions("statement.md", "statement.txt"))) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err)))
+ }
+ }
+
+ if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
+ }
+ }
+
+ if i.Exists(path.Join(epath, "finished.txt")) {
+ e.Finished, err = GetFileContent(i, path.Join(epath, "finished.txt"))
+ } else if i.Exists(path.Join(epath, "finished.md")) {
+ e.Finished, err = GetFileContent(i, path.Join(epath, "finished.md"))
+ }
+ if err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err), theme))
+ } else if len(e.Finished) > 0 {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(e.Finished, e.Language, exceptions.GetFileExceptions("finished.md", "finished.txt"))) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err)))
+ }
+ }
+
+ if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: an error occurs during markdown formating: %w", err), theme))
+ }
+ }
+
+ if i.Exists(path.Join(epath, "heading.jpg")) {
+ e.Image = path.Join(epath, "heading.jpg")
+ } else if i.Exists(path.Join(epath, "heading.png")) {
+ e.Image = path.Join(epath, "heading.png")
+ } else if theme == nil || theme.Image == "" {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("heading.jpg: No such file")))
+ }
+
+ // Parse challenge.txt
+ var md toml.MetaData
+ p, md, err = parseExerciceParams(i, epath)
+ if err != nil {
+ errs = multierr.Append(errs, NewChallengeTxtError(e, 0, err, theme))
return
}
+ // Alert about unknown keys in challenge.txt
+ if len(md.Undecoded()) > 0 {
+ for _, k := range md.Undecoded() {
+ errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/", k), theme))
+ }
+ }
+
+ e.WIP = p.WIP
+ if p.WIP && !AllowWIPExercice {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("exercice declared Work In Progress in challenge.toml"), theme))
+ }
+
if p.Gain == 0 {
- errs = append(errs, fmt.Sprintf("%q: challenge.txt: Undefined gain for challenge", edir))
+ errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
} else {
e.Gain = p.Gain
}
// Handle dependency
if len(p.Dependencies) > 0 {
- if len(p.Dependencies[0].Theme) > 0 && p.Dependencies[0].Theme != theme.Name {
- errs = append(errs, fmt.Sprintf("%q: unable to treat dependency to another theme (%q): not implemented.", edir, p.Dependencies[0].Theme))
+ if len(p.Dependencies[0].Theme) > 0 && (theme == nil || p.Dependencies[0].Theme != theme.Name) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), theme))
} else {
if dmap == nil {
if dmap2, err := buildDependancyMap(i, theme); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to build dependency map: %s", edir, err))
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
} else {
dmap = &dmap2
}
@@ -167,59 +321,146 @@ func SyncExercice(i Importer, theme fic.Theme, epath string, dmap *map[int64]fic
for k, _ := range *dmap {
dmap_keys = append(dmap_keys, fmt.Sprintf("%d", k))
}
- errs = append(errs, fmt.Sprintf("%q: Unable to find required dependancy %d (available at time of processing: %s)", edir, p.Dependencies[0].Id, strings.Join(dmap_keys, ",")))
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")), theme))
}
}
}
}
- // Handle video
+ // Handle resolutions
+ resolutionFound := false
+
e.VideoURI = path.Join(epath, "resolution.mp4")
- if !i.exists(e.VideoURI) {
- errs = append(errs, fmt.Sprintf("%q: resolution.mp4: no video file found at %s", edir, e.VideoURI))
+ if !i.Exists(e.VideoURI) {
e.VideoURI = ""
- } else if size, err := getFileSize(i, e.VideoURI); err != nil {
- errs = append(errs, fmt.Sprintf("%q: resolution.mp4: ", edir, err))
+ } else if size, err := GetFileSize(i, e.VideoURI); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
e.VideoURI = ""
} else if size == 0 {
- errs = append(errs, fmt.Sprintf("%q: resolution.mp4: The file is empty!", edir))
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
e.VideoURI = ""
+ } else {
+ e.VideoURI = strings.Replace(url.PathEscape(path.Join("$RFILES$", e.VideoURI)), "%2F", "/", -1)
+ resolutionFound = true
}
- // Create or update the exercice
- err = theme.SaveNamedExercice(&e)
- if err != nil {
- errs = append(errs, fmt.Sprintf("%q: error on exercice save: %s", edir, err))
- return
+ writeup := path.Join(epath, "resolution.md")
+ if !i.Exists(writeup) {
+ writeup = path.Join(epath, "resolution.txt")
}
- // Import eercice tags
- if _, err := e.WipeTags(); err != nil {
- errs = append(errs, fmt.Sprintf("%q: Unable to wipe tags: %s", edir, err))
+ if i.Exists(writeup) {
+ if size, err := GetFileSize(i, writeup); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
+ } else if size == 0 {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
+ } else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
+ } else {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(e.Resolution, e.Language, exceptions.GetFileExceptions("resolution.md"), p.GetRawFlags()...)) {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err)))
+ }
+ }
+
+ if e.Resolution, err = ProcessMarkdown(i, e.Resolution, epath); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
+ } else {
+ resolutionFound = true
+ }
+ }
}
- for _, tag := range p.Tags {
- if _, err := e.AddTag(tag); err != nil {
- errs = append(errs, fmt.Sprintf("%q: Unable to add tag: %s", edir, err))
- return
+
+ if !resolutionFound {
+ errs = multierr.Append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
+ }
+
+ // Call checks hooks
+ for _, h := range hooks.exerciceHooks {
+ for _, err := range multierr.Errors(h(e, exceptions)) {
+ errs = multierr.Append(errs, NewExerciceError(e, err))
}
}
return
}
+// SyncExercice imports new or updates existing given exercice.
+func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, eid int, exceptions *CheckExceptions, errs error) {
+ var err error
+ var p ExerciceParams
+ var berrors error
+
+ e, p, eid, exceptions, _, berrors = BuildExercice(i, theme, epath, dmap, exceptions_in)
+ errs = multierr.Append(errs, berrors)
+
+ if e != nil {
+ if len(e.Image) > 0 {
+ if _, err := i.importFile(e.Image,
+ func(filePath string, origin string) (interface{}, error) {
+ if err := resizePicture(i, origin, filePath, image.Rect(0, 0, 500, 300)); err != nil {
+ return nil, err
+ }
+
+ e.Image = strings.TrimPrefix(filePath, fic.FilesDir)
+
+ e.BackgroundColor, _ = getBackgroundColor(filePath)
+
+ // If the theme has no image yet, use the first exercice's image found
+ if theme != nil && theme.Image == "" {
+ theme.Image = e.Image
+ _, err := theme.Update()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return nil, nil
+ }); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to import heading image: %w", err)))
+ }
+ }
+
+ // Create or update the exercice
+ err = theme.SaveNamedExercice(e)
+ if err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
+ return
+ }
+
+ // Import eercice tags
+ if _, err := e.WipeTags(); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
+ }
+ for _, tag := range p.Tags {
+ if _, err := e.AddTag(tag); err != nil {
+ errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
+ return
+ }
+ }
+ }
+
+ return
+}
+
// SyncExercices imports new or updates existing exercices, in a given theme.
-func SyncExercices(i Importer, theme fic.Theme) (errs []string) {
- if exercices, err := getExercices(i, theme); err != nil {
- errs = append(errs, err.Error())
+func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (exceptions_out map[int]*CheckExceptions, errs error) {
+ if exercices, err := GetExercices(i, theme); err != nil {
+ errs = multierr.Append(errs, err)
} else {
+ exceptions_out = make(map[int]*CheckExceptions)
emap := map[string]int{}
dmap, _ := buildDependancyMap(i, theme)
for _, edir := range exercices {
- e, eid, cur_errs := SyncExercice(i, theme, path.Join(theme.Path, edir), &dmap)
- emap[e.Title] = eid
- dmap[int64(eid)] = e
- errs = append(errs, cur_errs...)
+ e, eid, ex_exceptions, cur_errs := SyncExercice(i, theme, path.Join(theme.Path, edir), &dmap, exceptions)
+ if e != nil {
+ emap[e.Title] = eid
+ dmap[int64(eid)] = e
+ exceptions_out[eid] = ex_exceptions
+ errs = multierr.Append(errs, cur_errs)
+ }
}
// Remove old exercices
@@ -234,7 +475,57 @@ func SyncExercices(i Importer, theme fic.Theme) (errs []string) {
return
}
-// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
-func ApiListRemoteExercices(theme fic.Theme, _ []byte) (interface{}, error) {
- return getExercices(GlobalImporter, theme)
+func ListRemoteExercices(thid string) ([]string, error) {
+ if thid == "_" {
+ return GetExercices(GlobalImporter, &fic.StandaloneExercicesTheme)
+ }
+
+ theme, _, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, errs
+ }
+
+ return GetExercices(GlobalImporter, theme)
+}
+
+// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
+func ApiListRemoteExercices(c *gin.Context) {
+ exercices, err := ListRemoteExercices(c.Params.ByName("thid"))
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, exercices)
+}
+
+func GetRemoteExercice(thid, exid string, inTheme *fic.Theme) (*fic.Exercice, error) {
+ if thid == fic.StandaloneExercicesDirectory || thid == "_" {
+ exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, nil, path.Join(fic.StandaloneExercicesDirectory, exid), nil, nil)
+ return exercice, errs
+ }
+
+ theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, fmt.Errorf("Theme not found")
+ }
+
+ if inTheme == nil {
+ inTheme = theme
+ }
+
+ exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, inTheme, path.Join(theme.Path, exid), nil, exceptions)
+ return exercice, errs
+}
+
+// ApiGetRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
+func ApiGetRemoteExercice(c *gin.Context) {
+ exercice, err := GetRemoteExercice(c.Params.ByName("thid"), c.Params.ByName("exid"), nil)
+ if exercice != nil {
+ c.JSON(http.StatusOK, exercice)
+ return
+ } else {
+ c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
+ return
+ }
}
diff --git a/admin/sync/file.go b/admin/sync/file.go
index 55cbe17a..7efd47fa 100644
--- a/admin/sync/file.go
+++ b/admin/sync/file.go
@@ -4,9 +4,9 @@ import (
"bufio"
"bytes"
"encoding/base32"
- "errors"
"fmt"
"io"
+ "net/url"
"os"
"path"
"strings"
@@ -20,29 +20,52 @@ import (
type Importer interface {
// Kind returns information about the Importer, for human interrest.
Kind() string
- // exists checks if the given location exists from the Importer point of view.
- exists(filename string) bool
+ // Id returns information about the current state (commit id, ...).
+ Id() *string
+ // init performs the importer initialization.
+ Init() error
+ // sync tries to pull the latest modification of the underlying storage.
+ Sync() error
+ // Exists checks if the given location exists from the Importer point of view.
+ Exists(filename string) bool
// toURL gets the full path/URL to the given file, the Importer will look internaly (used for debuging purpose).
toURL(filename string) string
// importFile imports the file at the given URI, inside the global FILES/ directory.
// Then calls back the next function, with the downloaded location and the original URI.
// Callback return is forwarded.
importFile(URI string, next func(string, string) (interface{}, error)) (interface{}, error)
- // getFile write to the given buffer, the file at the given location.
- getFile(filename string, writer *bufio.Writer) error
+ // getFileReader returns a reader to the requested file.
+ GetFile(filename string) (io.Reader, error)
// listDir returns a list of the files and subdirectories contained inside the directory at the given location.
- listDir(filename string) ([]string, error)
+ ListDir(filename string) ([]string, error)
// stat returns many information about the given file: such as last modification date, size, ...
- stat(filename string) (os.FileInfo, error)
+ Stat(filename string) (os.FileInfo, error)
+}
+
+// DirectAccessImporter abstracts importer that support direct file access through a local path
+type DirectAccessImporter interface {
+ GetLocalPath(p ...string) string
+}
+
+// ForgeLinkedImporter abstracts importer that are linked to a forge
+type ForgeLinkedImporter interface {
+ GetThemeLink(th *fic.Theme) (*url.URL, error)
+ GetExerciceLink(e *fic.Exercice) (*url.URL, error)
+}
+
+// WritableImporter abstracts importer that we can also write on
+type WritableImporter interface {
+ // writeFile write the given buffer to the file at the given location.
+ writeFile(filename string, reader io.Reader) error
}
// GlobalImporter stores the main importer instance to use for global imports.
var GlobalImporter Importer
-// getFileSize returns the size.
-func getFileSize(i Importer, URI string) (size int64, err error) {
- if i.exists(URI) {
- if fi, err := i.stat(URI); err != nil {
+// GetFileSize returns the size.
+func GetFileSize(i Importer, URI string) (size int64, err error) {
+ if i.Exists(URI) {
+ if fi, err := i.Stat(URI); err != nil {
return 0, err
} else {
return fi.Size(), nil
@@ -50,17 +73,17 @@ func getFileSize(i Importer, URI string) (size int64, err error) {
}
dirname := path.Dir(URI)
- if i.exists(dirname) {
+ if i.Exists(dirname) {
filename := path.Base(URI)
- if files, err := i.listDir(dirname); err != nil {
+ if files, err := i.ListDir(dirname); err != nil {
return size, err
} else {
for _, fname := range []string{filename, filename + "."} {
found := false
for _, file := range files {
- if matched, _ := path.Match(fname + "[0-9][0-9]", file); matched {
+ if matched, _ := path.Match(fname+"[0-9][0-9]", file); matched {
found = true
- if fi, err := i.stat(path.Join(dirname, file)); err != nil {
+ if fi, err := i.Stat(path.Join(dirname, file)); err != nil {
return size, err
} else {
size += fi.Size()
@@ -75,54 +98,77 @@ func getFileSize(i Importer, URI string) (size int64, err error) {
}
}
- return size, errors.New(fmt.Sprintf("%q: no such file or directory", URI))
+ return size, fmt.Errorf("%q: no such file or directory", URI)
}
-// getFile helps to manage huge file transfert by concatenating splitted (with split(1)) files.
-func getFile(i Importer, URI string, writer *bufio.Writer) error {
+// GetFile helps to manage huge file transfert by concatenating splitted (with split(1)) files.
+func GetFile(i Importer, URI string) (io.Reader, func(), error) {
// Import file if it exists
- if i.exists(URI) {
- return i.getFile(URI, writer)
+ if i.Exists(URI) {
+ fd, err := i.GetFile(URI)
+ return fd, func() {
+ if fdc, ok := fd.(io.ReadCloser); ok {
+ fdc.Close()
+ }
+ }, err
}
// Try to find file parts
dirname := path.Dir(URI)
- if i.exists(dirname) {
+ if i.Exists(dirname) {
filename := path.Base(URI)
- if files, err := i.listDir(dirname); err != nil {
- return err
+ if files, err := i.ListDir(dirname); err != nil {
+ return nil, nil, err
} else {
+ var readers []io.Reader
+
for _, fname := range []string{filename, filename + "."} {
- found := false
for _, file := range files {
- if matched, _ := path.Match(fname + "[0-9][0-9]", file); matched {
- found = true
- if err := i.getFile(path.Join(dirname, file), writer); err != nil {
- return err
+ if matched, _ := path.Match(fname+"[0-9][0-9]", file); matched {
+ fd, err := i.GetFile(path.Join(dirname, file))
+ if err != nil {
+ // Close already opened files to avoid leaks
+ for _, rd := range readers {
+ if rdc, ok := rd.(io.ReadCloser); ok {
+ rdc.Close()
+ }
+ }
+
+ return nil, nil, err
}
+
+ readers = append(readers, fd)
}
}
- if found {
- return nil
+ if len(readers) > 0 {
+ return io.MultiReader(readers...), func() {
+ for _, rd := range readers {
+ if rdc, ok := rd.(io.ReadCloser); ok {
+ rdc.Close()
+ }
+ }
+ }, nil
}
}
}
}
- return errors.New(fmt.Sprintf("%q: no such file or directory", URI))
+ return nil, nil, fmt.Errorf("%q: no such file or directory", URI)
}
-// getFileContent retrieves the content of the given text file.
-func getFileContent(i Importer, URI string) (string, error) {
- cnt := bytes.Buffer{}
-
- if err := getFile(i, URI, bufio.NewWriter(io.Writer(&cnt))); err != nil {
+// GetFileContent retrieves the content of the given text file.
+func GetFileContent(i Importer, URI string) (string, error) {
+ if fd, closer, err := GetFile(i, URI); err != nil {
return "", err
} else {
+ defer closer()
+
+ buffd := bufio.NewReader(fd)
+
// Ensure we read UTF-8 content.
buf := make([]rune, 0)
- for b, _, err := cnt.ReadRune(); err == nil; b, _, err = cnt.ReadRune() {
+ for b, _, err := buffd.ReadRune(); err == nil; b, _, err = buffd.ReadRune() {
buf = append(buf, b)
}
@@ -130,18 +176,54 @@ func getFileContent(i Importer, URI string) (string, error) {
}
}
-// getDestinationFilePath generates the destination path, from the URI.
+// GetDestinationFilePath generates the destination path, from the URI.
// This function permits to obfusce to player the original URI.
// Theoricaly, changing the import method doesn't change destination URI.
-func getDestinationFilePath(URI string) string {
+func GetDestinationFilePath(URI string, filename *string) string {
+ if filename == nil {
+ tmp := path.Base(URI)
+ filename = &tmp
+ }
hash := blake2b.Sum512([]byte(URI))
- return path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), path.Base(URI))
+ return path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), *filename)
+}
+
+var fileWriter = fileWriterToFS
+
+func SetWriteFileFunc(writerFunc func(dest string) (io.WriteCloser, error)) {
+ fileWriter = writerFunc
+}
+
+func fileWriterToFS(dest string) (io.WriteCloser, error) {
+ if err := os.MkdirAll(path.Dir(dest), 0751); err != nil {
+ return nil, err
+ }
+
+ return os.Create(dest)
+}
+
+func importFile(i Importer, URI string, dest string) error {
+ if fdfrom, closer, err := GetFile(i, URI); err != nil {
+ os.Remove(dest)
+ return err
+ } else {
+ defer closer()
+
+ fdto, err := fileWriter(dest)
+ if err != nil {
+ return err
+ }
+ defer fdto.Close()
+
+ _, err = io.Copy(fdto, fdfrom)
+ return err
+ }
}
// ImportFile imports the file at the given URI, using helpers of the given Importer.
// After import, next is called with relative path where the file has been saved and the original URI.
func ImportFile(i Importer, URI string, next func(string, string) (interface{}, error)) (interface{}, error) {
- dest := getDestinationFilePath(URI)
+ dest := GetDestinationFilePath(URI, nil)
// If the present file is still valide, don't erase it
if _, err := os.Stat(dest); !os.IsNotExist(err) {
@@ -150,26 +232,36 @@ func ImportFile(i Importer, URI string, next func(string, string) (interface{},
}
}
- // Ensure no more file is registered with this path
- if f, err := fic.GetFileByPath(dest); err == nil {
- f.Delete()
- }
-
- if err := os.MkdirAll(path.Dir(dest), 0755); err != nil {
+ if err := importFile(i, URI, dest); err != nil {
return nil, err
}
- // Write file
- if fdto, err := os.Create(dest); err != nil {
- return nil, err
- } else {
- defer fdto.Close()
- writer := bufio.NewWriter(fdto)
- if err := getFile(i, URI, writer); err != nil {
- os.Remove(dest)
- return nil, err
- }
- }
-
return next(dest, URI)
}
+
+// WriteFileContent save the given content to the given text file.
+func WriteFileContent(i Importer, URI string, content []byte) error {
+ if wi, ok := i.(WritableImporter); ok {
+ return wi.writeFile(URI, bytes.NewReader(content))
+ } else {
+ return fmt.Errorf("%t is not capable of writing", i)
+ }
+}
+
+func OpenOrGetFile(i Importer, URI string) (fd io.Reader, closer func() error, err error) {
+ if strings.HasPrefix(URI, "$FILES$") {
+ var fdc io.ReadCloser
+ fdc, err = os.Open(path.Join(fic.FilesDir, strings.TrimPrefix(URI, "$FILES$/")))
+ fd = fdc
+ closer = fdc.Close
+ } else {
+ fd, err = GlobalImporter.GetFile(URI)
+ if fdcloser, ok := fd.(io.ReadCloser); ok {
+ closer = fdcloser.Close
+ } else {
+ closer = func() error { return nil }
+ }
+ }
+
+ return
+}
diff --git a/admin/sync/full.go b/admin/sync/full.go
index c92ec6b3..d112fd2e 100644
--- a/admin/sync/full.go
+++ b/admin/sync/full.go
@@ -2,14 +2,16 @@ package sync
import (
"encoding/json"
- "fmt"
+ "io"
"log"
"os"
- "time"
"sync"
+ "time"
+ "go.uber.org/multierr"
+
+ "srs.epita.fr/fic-server/admin/generation"
"srs.epita.fr/fic-server/libfic"
- "srs.epita.fr/fic-server/settings"
)
// DeepReportPath stores the path to the report generated during full recursive import.
@@ -17,47 +19,191 @@ var DeepReportPath = "full_import_report.json"
// oneDeepSync ensure there is no more than one running deep sync.
var oneDeepSync sync.Mutex
+var oneThemeDeepSync sync.Mutex
// DeepSyncProgress expose the progression of the depp synchronization (0 = 0%, 255 = 100%).
var DeepSyncProgress uint8
-// SyncDeep performs a recursive synchronisation: from themes to challenge items.
-func SyncDeep(i Importer) (errs map[string][]string) {
+func OneDeepSyncStatus() bool {
+ if oneDeepSync.TryLock() {
+ oneDeepSync.Unlock()
+ return true
+ }
+ return false
+}
+
+func OneThemeDeepSyncStatus() bool {
+ if oneThemeDeepSync.TryLock() {
+ oneThemeDeepSync.Unlock()
+ return true
+ }
+ return false
+}
+
+type SyncReport struct {
+ DateStart time.Time `json:"_started"`
+ DateEnd time.Time `json:"_ended"`
+ DateUpdated []time.Time `json:"_updated"`
+ Regeneration []string `json:"_regeneration"`
+ SyncId string `json:"_id,omitempty"`
+ ThemesSync []string `json:"_themes,omitempty"`
+ Themes map[string][]string `json:"themes"`
+ Exercices []string `json:"exercices,omitempty"`
+}
+
+// SpeedySyncDeep performs a recursive synchronisation without importing files.
+func SpeedySyncDeep(i Importer) (errs SyncReport) {
oneDeepSync.Lock()
- defer oneDeepSync.Unlock()
+ defer func() {
+ oneDeepSync.Unlock()
+ if DeepSyncProgress != 255 {
+ log.Printf("Speedy synchronization terminated at step %d/255", DeepSyncProgress)
+ }
+ }()
DeepSyncProgress = 1
- errs = map[string][]string{}
+ errs.Themes = map[string][]string{}
- errs["_date"] = []string{fmt.Sprintf("%v", time.Now())}
- errs["_themes"] = SyncThemes(i)
+ startTime := time.Now()
- if themes, err := fic.GetThemes(); err == nil {
+ errs.DateStart = startTime
+ exceptions, sterrs := SyncThemes(i)
+ for _, sterr := range multierr.Errors(sterrs) {
+ errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
+ }
+
+ if themes, err := fic.GetThemesExtended(); err == nil {
DeepSyncProgress = 2
+
var themeStep uint8 = uint8(250) / uint8(len(themes))
for tid, theme := range themes {
- DeepSyncProgress = 3 + uint8(tid) * themeStep
- errs[theme.Name] = SyncExercices(i, theme)
+ DeepSyncProgress = 3 + uint8(tid)*themeStep
+ ex_exceptions, seerrs := SyncExercices(i, theme, exceptions[theme.Path])
+ for _, seerr := range multierr.Errors(seerrs) {
+ errs.Themes[theme.Name] = append(errs.Themes[theme.Name], seerr.Error())
+ }
if exercices, err := theme.GetExercices(); err == nil {
+ if len(exercices) == 0 {
+ continue
+ }
var exerciceStep uint8 = themeStep / uint8(len(exercices))
for eid, exercice := range exercices {
- DeepSyncProgress = 3 + uint8(tid) * themeStep + uint8(eid) * exerciceStep
- errs[theme.Name] = append(errs[theme.Name], SyncExerciceFiles(i, exercice)...)
- DeepSyncProgress += exerciceStep / 3
- errs[theme.Name] = append(errs[theme.Name], SyncExerciceFlags(i, exercice)...)
- DeepSyncProgress += exerciceStep / 3
- errs[theme.Name] = append(errs[theme.Name], SyncExerciceHints(i, exercice)...)
+ log.Printf("Speedy synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path)
+
+ DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
+ flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
+ for _, ferr := range multierr.Errors(ferrs) {
+ errs.Themes[theme.Name] = append(errs.Themes[theme.Name], ferr.Error())
+ }
+
+ DeepSyncProgress += exerciceStep / 2
+ _, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
+ for _, herr := range multierr.Errors(herrs) {
+ errs.Themes[theme.Name] = append(errs.Themes[theme.Name], herr.Error())
+ }
}
}
}
}
DeepSyncProgress = 254
- errs["_date"] = append(errs["_date"], fmt.Sprintf("%v", time.Now()))
+ errs.DateEnd = time.Now()
- errs["_regeneration"] = []string{}
+ DeepSyncProgress = 255
+ log.Println("Speedy synchronization done in", time.Since(startTime))
+ return
+}
+
+// SyncDeep performs a recursive synchronisation: from themes to challenge items.
+func SyncDeep(i Importer) (errs SyncReport) {
+ oneDeepSync.Lock()
+ defer func() {
+ oneDeepSync.Unlock()
+ if DeepSyncProgress != 255 {
+ log.Printf("Full synchronization terminated at step %d/255", DeepSyncProgress)
+ }
+ }()
+ DeepSyncProgress = 1
+
+ errs.Themes = map[string][]string{}
+
+ startTime := time.Now()
+
+ // Import all themes
+ errs.DateStart = startTime
+ exceptions, sterrs := SyncThemes(i)
+ for _, sterr := range multierr.Errors(sterrs) {
+ errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
+ }
+
+ // Synchronize themes
+ if themes, err := fic.GetThemesExtended(); err == nil {
+ DeepSyncProgress = 2
+
+ var themeStep uint8 = uint8(250) / uint8(len(themes))
+
+ for tid, theme := range themes {
+ stderrs := SyncThemeDeep(i, theme, tid, themeStep, exceptions[theme.Path])
+ for _, stderr := range multierr.Errors(stderrs) {
+ errs.Themes[theme.Name] = append(errs.Themes[theme.Name], stderr.Error())
+ }
+ }
+ }
+
+ DeepSyncProgress = 254
+
+ EditDeepReport(&errs, true)
+
+ resp, err := generation.FullGeneration()
+ if err != nil {
+ errs.Regeneration = append(errs.Regeneration, err.Error())
+ } else {
+ defer resp.Body.Close()
+
+ v, _ := io.ReadAll(resp.Body)
+ errs.Regeneration = append(errs.Regeneration, string(v))
+ }
+
+ DeepSyncProgress = 255
+ log.Println("Full synchronization done in", time.Since(startTime))
+ return
+}
+
+func readDeepReport() (ret *SyncReport, err error) {
+ if fdfrom, err := os.Open(DeepReportPath); err == nil {
+ defer fdfrom.Close()
+
+ jdec := json.NewDecoder(fdfrom)
+
+ if err := jdec.Decode(&ret); err != nil {
+ return nil, err
+ }
+ } else {
+ return nil, err
+ }
+
+ return
+}
+
+func EditDeepReport(errs *SyncReport, erase bool) {
+ errs.Regeneration = []string{}
+
+ if !erase {
+ if in, err := readDeepReport(); err != nil {
+ errs.Regeneration = append(errs.Regeneration, err.Error())
+ log.Println(err)
+ } else {
+ for k, v := range errs.Themes {
+ in.Themes[k] = v
+ }
+
+ errs = in
+ }
+ }
+
+ errs.DateUpdated = append(errs.DateUpdated, time.Now())
if fdto, err := os.Create(DeepReportPath); err == nil {
defer fdto.Close()
@@ -65,18 +211,43 @@ func SyncDeep(i Importer) (errs map[string][]string) {
if out, err := json.Marshal(errs); err == nil {
fdto.Write(out)
} else {
- errs["_regeneration"] = append(errs["_regeneration"], err.Error())
+ errs.Regeneration = append(errs.Regeneration, err.Error())
log.Println(err)
}
} else {
- errs["_regeneration"] = append(errs["_regeneration"], err.Error())
+ errs.Regeneration = append(errs.Regeneration, err.Error())
log.Println(err)
}
- if err := settings.ForceRegeneration(); err != nil {
- errs["_regeneration"] = append(errs["_regeneration"], err.Error())
+}
+
+// SyncThemeDeep performs a recursive synchronisation: from challenges to challenge items.
+func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, exceptions *CheckExceptions) (errs error) {
+ var ex_exceptions map[int]*CheckExceptions
+
+ oneThemeDeepSync.Lock()
+ defer oneThemeDeepSync.Unlock()
+
+ DeepSyncProgress = 3 + uint8(tid)*themeStep
+ ex_exceptions, errs = SyncExercices(i, theme, exceptions)
+
+ if exercices, err := theme.GetExercices(); err == nil && len(exercices) > 0 {
+ var exerciceStep uint8 = themeStep / uint8(len(exercices))
+ for eid, exercice := range exercices {
+ log.Printf("Deep synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path)
+
+ DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
+ errs = multierr.Append(errs, ImportExerciceFiles(i, exercice, ex_exceptions[eid]))
+
+ DeepSyncProgress += exerciceStep / 3
+ flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
+ errs = multierr.Append(errs, ferrs)
+
+ DeepSyncProgress += exerciceStep / 3
+ _, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
+ errs = multierr.Append(errs, herrs)
+ }
}
- DeepSyncProgress = 255
return
}
diff --git a/admin/sync/hooks.go b/admin/sync/hooks.go
new file mode 100644
index 00000000..f789762c
--- /dev/null
+++ b/admin/sync/hooks.go
@@ -0,0 +1,108 @@
+package sync
+
+import (
+ "fmt"
+ "plugin"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var hooks = &CheckHooks{customHooks: map[string]CustomCheckHook{}}
+
+type CheckFlagChoiceHook func(*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
+type CheckFlagKeyHook func(*fic.FlagKey, string, *fic.Exercice, *CheckExceptions) error
+type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
+type CheckFlagLabelHook func(*fic.FlagLabel, *fic.Exercice, *CheckExceptions) error
+type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *fic.Exercice, *CheckExceptions) error
+type CheckFileHook func(*fic.EFile, *fic.Exercice, *CheckExceptions) error
+type CheckHintHook func(*fic.EHint, *fic.Exercice, *CheckExceptions) error
+type CheckMDTextHook func(string, string, *CheckExceptions, ...string) error
+type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) error
+type CustomCheckHook func(interface{}, *CheckExceptions) error
+
+type CheckHooks struct {
+ flagChoiceHooks []CheckFlagChoiceHook
+ flagKeyHooks []CheckFlagKeyHook
+ flagKeyWithChoicesHooks []CheckFlagKeyWithChoicesHook
+ flagLabelHooks []CheckFlagLabelHook
+ flagMCQHooks []CheckFlagMCQHook
+ fileHooks []CheckFileHook
+ hintHooks []CheckHintHook
+ mdTextHooks []CheckMDTextHook
+ exerciceHooks []CheckExerciceHook
+ customHooks map[string]CustomCheckHook
+}
+
+func (h *CheckHooks) RegisterFlagChoiceHook(f CheckFlagChoiceHook) {
+ h.flagChoiceHooks = append(h.flagChoiceHooks, f)
+}
+
+func (h *CheckHooks) RegisterFlagKeyHook(f CheckFlagKeyHook) {
+ h.flagKeyHooks = append(h.flagKeyHooks, f)
+}
+
+func (h *CheckHooks) RegisterFlagKeyWithChoicesHook(f CheckFlagKeyWithChoicesHook) {
+ h.flagKeyWithChoicesHooks = append(h.flagKeyWithChoicesHooks, f)
+}
+
+func (h *CheckHooks) RegisterFlagLabelHook(f CheckFlagLabelHook) {
+ h.flagLabelHooks = append(h.flagLabelHooks, f)
+}
+
+func (h *CheckHooks) RegisterFlagMCQHook(f CheckFlagMCQHook) {
+ h.flagMCQHooks = append(h.flagMCQHooks, f)
+}
+
+func (h *CheckHooks) RegisterFileHook(f CheckFileHook) {
+ h.fileHooks = append(h.fileHooks, f)
+}
+
+func (h *CheckHooks) RegisterHintHook(f CheckHintHook) {
+ h.hintHooks = append(h.hintHooks, f)
+}
+
+func (h *CheckHooks) RegisterMDTextHook(f CheckMDTextHook) {
+ h.mdTextHooks = append(h.mdTextHooks, f)
+}
+
+func (h *CheckHooks) RegisterExerciceHook(f CheckExerciceHook) {
+ h.exerciceHooks = append(h.exerciceHooks, f)
+}
+
+func (h *CheckHooks) RegisterCustomHook(hookname string, f CustomCheckHook) {
+ h.customHooks[hookname] = f
+}
+
+func (h *CheckHooks) CallCustomHook(hookname string, data interface{}, exceptions *CheckExceptions) error {
+ if v, ok := h.customHooks[hookname]; ok {
+ return v(data, exceptions)
+ }
+ return nil
+}
+
+func LoadChecksPlugin(fname string) error {
+ p, err := plugin.Open(fname)
+ if err != nil {
+ return err
+ }
+
+ register, err := p.Lookup("RegisterChecksHooks")
+ if err != nil {
+ return err
+ }
+
+ register.(func(*CheckHooks))(hooks)
+
+ return nil
+}
+
+type CheckPluginList []string
+
+func (l *CheckPluginList) String() string {
+ return fmt.Sprintf("%v", *l)
+}
+
+func (l *CheckPluginList) Set(value string) error {
+ *l = append(*l, value)
+ return nil
+}
diff --git a/admin/sync/importer_cloud.go b/admin/sync/importer_cloud.go
index 70811e6d..9085f4fe 100644
--- a/admin/sync/importer_cloud.go
+++ b/admin/sync/importer_cloud.go
@@ -1,8 +1,8 @@
package sync
import (
- "bufio"
"errors"
+ "io"
"net/http"
"net/url"
"os"
@@ -38,7 +38,19 @@ func (i CloudImporter) Kind() string {
return "cloud file importer: " + i.baseDAV.String()
}
-func (i CloudImporter) exists(filename string) bool {
+func (i CloudImporter) Id() *string {
+ return nil
+}
+
+func (i CloudImporter) Init() error {
+ return nil
+}
+
+func (i CloudImporter) Sync() error {
+ return nil
+}
+
+func (i CloudImporter) Exists(filename string) bool {
fullURL := i.baseDAV
fullURL.Path = path.Join(fullURL.Path, filename)
@@ -65,12 +77,34 @@ func (i CloudImporter) importFile(URI string, next func(string, string) (interfa
return ImportFile(i, URI, next)
}
-func (i CloudImporter) getFile(filename string, writer *bufio.Writer) error {
+func (i CloudImporter) GetFile(filename string) (io.Reader, error) {
fullURL := i.baseDAV
fullURL.Path = path.Join(fullURL.Path, filename)
client := http.Client{}
if req, err := http.NewRequest("GET", fullURL.String(), nil); err != nil {
+ return nil, err
+ } else {
+ req.SetBasicAuth(i.username, i.password)
+ if resp, err := client.Do(req); err != nil {
+ return nil, err
+ } else {
+ if resp.StatusCode != http.StatusOK {
+ resp.Body.Close()
+ return nil, errors.New(resp.Status)
+ } else {
+ return resp.Body, nil
+ }
+ }
+ }
+}
+
+func (i CloudImporter) writeFile(filename string, reader io.Reader) error {
+ fullURL := i.baseDAV
+ fullURL.Path = path.Join(fullURL.Path, filename)
+
+ client := http.Client{}
+ if req, err := http.NewRequest("PUT", fullURL.String(), reader); err != nil {
return err
} else {
req.SetBasicAuth(i.username, i.password)
@@ -82,16 +116,13 @@ func (i CloudImporter) getFile(filename string, writer *bufio.Writer) error {
if resp.StatusCode != http.StatusOK {
return errors.New(resp.Status)
} else {
- reader := bufio.NewReader(resp.Body)
- reader.WriteTo(writer)
- writer.Flush()
+ return nil
}
}
}
- return nil
}
-func (i CloudImporter) listDir(filename string) ([]string, error) {
+func (i CloudImporter) ListDir(filename string) ([]string, error) {
client := gowebdav.NewClient(i.baseDAV.String(), i.username, i.password)
if files, err := client.ReadDir(strings.Replace(url.PathEscape(filename), "%2F", "/", -1)); err != nil {
@@ -105,6 +136,6 @@ func (i CloudImporter) listDir(filename string) ([]string, error) {
}
}
-func (i CloudImporter) stat(filename string) (os.FileInfo, error) {
+func (i CloudImporter) Stat(filename string) (os.FileInfo, error) {
return gowebdav.NewClient(i.baseDAV.String(), i.username, i.password).Stat(strings.Replace(url.PathEscape(filename), "%2F", "/", -1))
}
diff --git a/admin/sync/importer_git.go b/admin/sync/importer_git.go
new file mode 100644
index 00000000..cda699d5
--- /dev/null
+++ b/admin/sync/importer_git.go
@@ -0,0 +1,214 @@
+//go:build gitgo
+// +build gitgo
+
+package sync
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/config"
+ "github.com/go-git/go-git/v5/plumbing"
+ "github.com/go-git/go-git/v5/plumbing/transport/ssh"
+)
+
+// GitImporter implements an Importer, where files to imports are located
+// inside a local directory from your filesystem, backed by git.
+type GitImporter struct {
+ li LocalImporter
+ Remote string
+ Branch string
+ Auth ssh.AuthMethod
+}
+
+func NewGitImporter(li LocalImporter, remote string, branch string) GitImporter {
+ var auth ssh.AuthMethod
+
+ // If there is no ssh-agent setup, try to use a default ssh key
+ if _, exists := os.LookupEnv("SSH_AUTH_SOCK"); !exists {
+ if home, exists := os.LookupEnv("HOME"); exists {
+ for _, d := range []string{"id_fic", "id_ed25519", "id_rsa"} {
+ pemBytes, err := ioutil.ReadFile(path.Join(home, ".ssh", d))
+ if err == nil {
+ log.Println("[GitImporter] Using", path.Join(home, ".ssh", d), "as ssh key to sync repository")
+ auth, err = ssh.NewPublicKeys("git", pemBytes, "")
+ if ccfg, err := auth.ClientConfig(); err != nil {
+ if hkc, err := ssh.NewKnownHostsCallback(); err != nil {
+ ccfg.HostKeyCallback = hkc
+ }
+ }
+ break
+ }
+ }
+ }
+ }
+
+ return GitImporter{
+ li: li,
+ Remote: remote,
+ Branch: branch,
+ Auth: auth,
+ }
+}
+
+func (i GitImporter) Id() *string {
+ var gitinfo string
+ r, err := git.PlainOpen(i.li.Base)
+ if err == nil {
+ ref, err := r.Head()
+ if err == nil {
+ gitinfo = ref.Hash().String()
+ }
+ }
+
+ return &gitinfo
+}
+
+func (i GitImporter) Init() error {
+ // Check if the directory exists, create it if needed
+ if err := i.li.Init(); err != nil {
+ return err
+ }
+
+ // If the directory is empty, clone it
+ if n, err := countFileInDir(i.li.Base); err != nil {
+ return err
+ } else if n == 0 {
+ _, err = git.PlainClone(i.li.Base, false, &git.CloneOptions{
+ URL: i.Remote,
+ ReferenceName: plumbing.ReferenceName(i.Branch),
+ RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
+ Auth: i.Auth,
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ // Check if the .git directory exists, change the origin remote to our
+ r, err := git.PlainOpen(i.li.Base)
+ if err != nil {
+ return err
+ }
+
+ r.DeleteRemote("origin")
+ _, err = r.CreateRemote(&config.RemoteConfig{
+ Name: "origin",
+ URLs: []string{i.Remote},
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (i GitImporter) Sync() error {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ r, err := git.PlainOpen(i.li.Base)
+ if err != nil {
+ return err
+ }
+
+ w, err := r.Worktree()
+ if err != nil {
+ return err
+ }
+
+ // Perform a git pull --rebase origin/master
+ err = w.Pull(&git.PullOptions{
+ RemoteName: "origin",
+ ReferenceName: plumbing.ReferenceName(i.Branch),
+ Depth: 1,
+ RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
+ Force: true,
+ Auth: i.Auth,
+ })
+ return err
+}
+
+func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ r, err := git.PlainOpen(i.li.Base)
+ if err != nil {
+ return nil, err
+ }
+
+ w, err := r.Worktree()
+ if err != nil {
+ return nil, err
+ }
+
+ modules, err := w.Submodules()
+
+ var ret []GitSubmoduleStatus
+ for _, mod := range modules {
+ st, err := mod.Status()
+ if err == nil {
+ ret = append(ret, GitSubmoduleStatus{
+ Hash: st.Expected.String(),
+ Path: st.Path,
+ Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(st.Branch.String(), "("), "refs/"), "heads/"), ")"),
+ })
+ }
+ }
+
+ return ret, err
+}
+
+func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
+ if err != nil {
+ return nil, err
+ }
+
+ st, err := r.Head()
+ if err != nil {
+ return nil, err
+ }
+
+ return &GitSubmoduleStatus{
+ Hash: st.Hash().String(),
+ Path: repopath,
+ Branch: st.Name().Short(),
+ }, nil
+}
+
+func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
+ if err != nil {
+ return nil, err
+ }
+
+ // Perform a git pull --rebase origin/master
+ err = r.Fetch(&git.FetchOptions{
+ RemoteName: "origin",
+ RefSpecs: []config.RefSpec{config.RefSpec("+refs/heads/" + i.Branch + ":refs/remotes/origin/" + i.Branch)},
+ Auth: i.Auth,
+ })
+
+ st, err := r.Reference(plumbing.ReferenceName("origin/"+i.Branch), true)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GitSubmoduleStatus{
+ Hash: st.Hash().String(),
+ Path: repopath,
+ Branch: st.Name().Short(),
+ }, nil
+}
diff --git a/admin/sync/importer_git_common.go b/admin/sync/importer_git_common.go
new file mode 100644
index 00000000..2ca9f75c
--- /dev/null
+++ b/admin/sync/importer_git_common.go
@@ -0,0 +1,85 @@
+package sync
+
+import (
+ "io"
+ "net/url"
+ "os"
+ "regexp"
+ "sync"
+)
+
+var gitRemoteRe = regexp.MustCompile(`^(?:(?:git@|https://)([\w.@]+)(?:/|:))((?:[\w-_]+)/(?:[\w-_/]+))(?:.git){0,1}(?:(?:/){0,1})$`)
+
+var oneGitPull sync.Mutex
+
+func OneGitPullStatus() bool {
+ if oneGitPull.TryLock() {
+ oneGitPull.Unlock()
+ return true
+ }
+ return false
+}
+
+func countFileInDir(dirname string) (int, error) {
+ files, err := os.ReadDir(dirname)
+ if err != nil {
+ return 0, err
+ }
+
+ return len(files), nil
+}
+
+func (i GitImporter) Exists(filename string) bool {
+ return i.li.Exists(filename)
+}
+
+func (i GitImporter) toURL(filename string) string {
+ return i.li.toURL(filename)
+}
+
+func (i GitImporter) GetLocalPath(filename ...string) string {
+ return i.li.GetLocalPath(filename...)
+}
+
+func (i GitImporter) importFile(URI string, next func(string, string) (interface{}, error)) (interface{}, error) {
+ return i.li.importFile(URI, next)
+}
+
+func (i GitImporter) GetFile(filename string) (io.Reader, error) {
+ return i.li.GetFile(filename)
+}
+
+func (i GitImporter) writeFile(filename string, reader io.Reader) error {
+ return i.li.writeFile(filename, reader)
+}
+
+func (i GitImporter) ListDir(filename string) ([]string, error) {
+ return i.li.ListDir(filename)
+}
+
+func (i GitImporter) Stat(filename string) (os.FileInfo, error) {
+ return i.li.Stat(filename)
+}
+
+func (i GitImporter) Kind() string {
+ return "git originated from " + i.Remote + " on " + i.li.Kind()
+}
+
+func (i GitImporter) DeleteDir(filename string) error {
+ return i.li.DeleteDir(filename)
+}
+
+func getForgeBaseLink(remote string) (u *url.URL, err error) {
+ res := gitRemoteRe.FindStringSubmatch(remote)
+ u, err = url.Parse(res[2])
+ u.Scheme = "https"
+ u.Host = res[1]
+ return
+}
+
+type GitSubmoduleStatus struct {
+ Hash string `json:"hash"`
+ Text string `json:"text,omitempty"`
+ Path string `json:"path"`
+ Branch string `json:"branch"`
+}
diff --git a/admin/sync/importer_gitbin.go b/admin/sync/importer_gitbin.go
new file mode 100644
index 00000000..7d511265
--- /dev/null
+++ b/admin/sync/importer_gitbin.go
@@ -0,0 +1,349 @@
+//go:build !gitgo
+// +build !gitgo
+
+package sync
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "log"
+ "net/url"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+// GitImporter implements an Importer, where files to imports are located
+// inside a local directory from your filesystem, backed by git (binary).
+type GitImporter struct {
+ li LocalImporter
+ Remote string
+ Branch string
+}
+
+func NewGitImporter(li LocalImporter, remote string, branch string) GitImporter {
+ if len(branch) == 0 {
+ branch = "master"
+ }
+
+ return GitImporter{
+ li: li,
+ Remote: remote,
+ Branch: branch,
+ }
+}
+
+func (i GitImporter) Id() *string {
+ cmdshow := exec.Command("git", "-C", i.li.Base, "show")
+ var outshow bytes.Buffer
+ cmdshow.Stdout = &outshow
+ err := cmdshow.Run()
+
+ var commit string
+ if err != nil {
+ commit = fmt.Sprintf("error (%s)", err.Error())
+ } else {
+ commit, err = outshow.ReadString('\n')
+ if err == nil {
+ commit = strings.TrimPrefix(commit, "commit ")
+ } else {
+ commit = fmt.Sprintf("error (%s)", err.Error())
+ }
+ }
+
+ return &commit
+}
+
+func (i GitImporter) Init() error {
+ // Check if the directory exists, create it if needed
+ if err := i.li.Init(); err != nil {
+ return err
+ }
+
+ // If the directory is empty, clone it
+ if n, err := countFileInDir(i.li.Base); err != nil {
+ return err
+ } else if n == 0 {
+ args := []string{"clone", "--recursive", "--depth", "1"}
+ if i.Branch != "" {
+ args = append(args, "-b", i.Branch)
+ }
+ args = append(args, "--shallow-submodules", i.Remote, i.li.Base)
+
+ log.Println("Please wait while creating the local git repository...")
+ cmdclone := exec.Command("git", args...)
+ stdout, err := cmdclone.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+ log.Println("Local git repository successfully cloned")
+ } else if _, err := os.Stat(path.Join(i.li.Base, ".git")); errors.Is(err, os.ErrNotExist) {
+ log.Println("[ERR] ", i.li.Base, " is not a valid git repository and it cannot be initialized because it's not empty.")
+ return nil
+ }
+
+ // Check if the .git directory exists, change the origin remote to our
+ cmdremote := exec.Command("git", "-C", i.li.Base, "remote", "set-url", "origin", i.Remote)
+ stdout, err := cmdremote.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ return nil
+}
+
+func (i GitImporter) Sync() error {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ log.Println("Synchronizing local git repository...")
+ cmdfetch := exec.Command("git", "-C", i.li.Base, "fetch", "origin")
+ stdout, err := cmdfetch.CombinedOutput()
+ if err != nil {
+ log.Printf("Git repository fetch failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ cmdclean := exec.Command("git", "-C", i.li.Base, "clean", "-xfde", "*_MERGED")
+ stdout, err = cmdclean.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository clean failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
+ // We have submodules, clean it
+ cmdsubclean := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "clean", "-xfde", "*_MERGED")
+ stdout, err = cmdsubclean.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository submodules clean failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ // Start by a light hard reset (without submodules, in order to init new ones)
+ cmdreset := exec.Command("git", "-C", i.li.Base, "reset", "--hard", "origin/"+i.Branch)
+ stdout, err = cmdreset.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository reset failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ cmdsubinit := exec.Command("git", "-C", i.li.Base, "submodule", "init")
+ stdout, err = cmdsubinit.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository submodule init failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ cmdsubupdate := exec.Command("git", "-C", i.li.Base, "submodule", "update")
+ stdout, err = cmdsubupdate.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository submodule update failed: %s\n%s", err, stdout)
+ }
+ }
+
+ cmdreset := exec.Command("git", "-C", i.li.Base, "reset", "--hard", "--recurse-submodule", "origin/"+i.Branch)
+ stdout, err = cmdreset.CombinedOutput()
+ if err != nil {
+ log.Printf("Local git repository reset failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
+ // Treat submodules
+ cmdsublfs := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "lfs", "pull")
+ stdout, err = cmdsublfs.CombinedOutput()
+ if err != nil {
+ log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+ } else {
+ cmdlfs := exec.Command("git", "-C", i.li.Base, "lfs", "pull")
+ stdout, err = cmdlfs.CombinedOutput()
+ if err != nil {
+ log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
+ return fmt.Errorf("%w:\n%s", err, stdout)
+ }
+ }
+
+ log.Println("Local git repository synchronized successfully")
+ return nil
+}
+
+func (i GitImporter) GetThemeLink(th *fic.Theme) (u *url.URL, err error) {
+ prefix := ""
+
+ if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
+ thdir := path.Join(i.li.Base, th.Path)
+ cmdremote := exec.Command("git", "-C", thdir, "remote", "get-url", "origin")
+ var stdout []byte
+ stdout, err = cmdremote.CombinedOutput()
+ if err != nil {
+ return
+ }
+
+ u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))
+
+ // Search .git directory
+ for {
+ if _, err = os.Stat(path.Join(thdir, ".git")); !errors.Is(err, os.ErrNotExist) {
+ break
+ }
+
+ thdir, _ = path.Split(thdir)
+ }
+ prefix = strings.TrimPrefix(thdir, i.li.Base)
+ } else {
+ u, err = getForgeBaseLink(i.Remote)
+ }
+
+ if err != nil {
+ return
+ }
+
+ u.Path = path.Join(u.Path, "-", "tree", i.Branch, strings.TrimPrefix("/"+th.Path, prefix))
+
+ return
+}
+
+func (i GitImporter) GetExerciceLink(e *fic.Exercice) (u *url.URL, err error) {
+ prefix := ""
+
+ if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
+ exdir := path.Join(i.li.Base, e.Path)
+ cmdremote := exec.Command("git", "-C", exdir, "remote", "get-url", "origin")
+ var stdout []byte
+ stdout, err = cmdremote.CombinedOutput()
+ if err != nil {
+ return
+ }
+
+ u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))
+
+ // Search .git directory
+ for {
+ if _, err = os.Stat(path.Join(exdir, ".git")); !errors.Is(err, os.ErrNotExist) {
+ break
+ }
+
+ exdir, _ = path.Split(exdir)
+ }
+ prefix = strings.TrimPrefix(exdir, i.li.Base)
+ } else {
+ u, err = getForgeBaseLink(i.Remote)
+ }
+
+ if err != nil {
+ return
+ }
+
+ u.Path = path.Join(u.Path, "-", "tree", i.Branch, strings.TrimPrefix("/"+e.Path, prefix))
+
+ return
+}
+
+func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status")
+ stdout, err := cmdsubmodule.CombinedOutput()
+ if err != nil {
+ log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ var ret []GitSubmoduleStatus
+ for _, line := range strings.Split(string(stdout), "\n") {
+ flds := strings.Fields(line)
+ if len(flds) == 3 {
+ ret = append(ret, GitSubmoduleStatus{
+ Hash: flds[0],
+ Path: flds[1],
+ Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
+ })
+ }
+ }
+
+ return ret, err
+}
+
+func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ if repopath == "" {
+ cmdsubmodule := exec.Command("git", "-C", i.li.Base, "show", "-q", "--oneline")
+ stdout, err := cmdsubmodule.CombinedOutput()
+ if err != nil {
+ log.Printf("Git repository show failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ flds := strings.SplitN(string(stdout), " ", 2)
+ return &GitSubmoduleStatus{
+ Hash: flds[0],
+ Text: strings.TrimSpace(flds[1]),
+ Path: "",
+ Branch: i.Branch,
+ }, nil
+ } else {
+ cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status", repopath)
+ stdout, err := cmdsubmodule.CombinedOutput()
+ if err != nil {
+ log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ flds := strings.Fields(strings.TrimSpace(string(stdout)))
+ if len(flds) == 3 {
+ return &GitSubmoduleStatus{
+ Hash: flds[0],
+ Path: flds[1],
+ Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
+ }, nil
+ }
+ }
+
+ return nil, fmt.Errorf("Unable to parse")
+}
+
+func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
+ oneGitPull.Lock()
+ defer oneGitPull.Unlock()
+
+ cmdsubmodule := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "fetch", "origin", i.Branch)
+ stdout, err := cmdsubmodule.CombinedOutput()
+ if err != nil {
+ log.Printf("Git repository submodule fetch failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+
+ cmdsubmodule = exec.Command("git", "-C", path.Join(i.li.Base, repopath), "show", "-q", "--oneline", "origin/"+i.Branch)
+ stdout, err = cmdsubmodule.CombinedOutput()
+ if err != nil {
+ cmdconfig := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "config", "remote.origin.fetch")
+ if cfg, err2 := cmdconfig.CombinedOutput(); err2 == nil && !strings.Contains(string(cfg), "+refs/heads/*:refs/remotes/origin/*") {
+ cmdsubmodule := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
+ if stdout, err = cmdsubmodule.CombinedOutput(); err != nil {
+ log.Printf("Git repository submodule config failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+ } else {
+ log.Printf("Git repository submodule status failed: %s\n%s", err, stdout)
+ return nil, fmt.Errorf("%w:\n%s", err, stdout)
+ }
+ }
+
+ flds := strings.SplitN(string(stdout), " ", 2)
+ return &GitSubmoduleStatus{
+ Hash: flds[0],
+ Text: strings.TrimSpace(flds[1]),
+ Path: repopath,
+ Branch: i.Branch,
+ }, nil
+}
diff --git a/admin/sync/importer_localfs.go b/admin/sync/importer_localfs.go
index 6fe32cfa..5f8301ae 100644
--- a/admin/sync/importer_localfs.go
+++ b/admin/sync/importer_localfs.go
@@ -1,7 +1,8 @@
package sync
import (
- "bufio"
+ "fmt"
+ "io"
"io/ioutil"
"os"
"path"
@@ -11,7 +12,7 @@ import (
// inside a local directory from your filesystem.
type LocalImporter struct {
// Base is the root directory used by the LocalImporter. It should contains all themes.
- Base string
+ Base string
// Symlink changes the normal file copy/concatenate behaviour to symlink/concatenate.
// If enable, your base directory must be accessible by the frontend processus as it will follow the symlink.
Symlink bool
@@ -25,7 +26,28 @@ func (i LocalImporter) Kind() string {
}
}
-func (i LocalImporter) exists(filename string) bool {
+func (i LocalImporter) Id() *string {
+ return nil
+}
+
+func (i LocalImporter) Init() error {
+ if f, err := os.Stat(i.Base); os.IsNotExist(err) {
+ if err = os.Mkdir(i.Base, 0751); err != nil {
+ return err
+ }
+ } else if err != nil {
+ return err
+ } else if !f.IsDir() {
+ return fmt.Errorf("%q exists and is not a directory", i.Base)
+ }
+ return nil
+}
+
+func (i LocalImporter) Sync() error {
+ return nil
+}
+
+func (i LocalImporter) Exists(filename string) bool {
_, err := os.Stat(i.toURL(filename))
return !os.IsNotExist(err)
}
@@ -34,37 +56,49 @@ func (i LocalImporter) toURL(filename string) string {
return path.Join(i.Base, filename)
}
+func (i LocalImporter) GetLocalPath(p ...string) string {
+ return i.toURL(path.Join(p...))
+}
+
func (i LocalImporter) importFile(URI string, next func(string, string) (interface{}, error)) (interface{}, error) {
if i.Symlink {
- dest := getDestinationFilePath(URI)
+ dest := GetDestinationFilePath(URI, nil)
- if err := os.MkdirAll(path.Dir(dest), 0755); err != nil {
+ if err := os.MkdirAll(path.Dir(dest), 0751); err != nil {
return nil, err
}
- if i.exists(URI) {
+ if i.Exists(URI) {
os.Symlink(i.toURL(URI), dest)
+ return next(dest, URI)
} else {
- os.Symlink(i.toURL(URI) + "_MERGED", dest)
+ os.Symlink(i.toURL(URI)+"_MERGED", dest)
+ return ImportFile(i, URI, next)
}
+ } else {
+ return ImportFile(i, URI, next)
}
-
- return ImportFile(i, URI, next)
}
-func (i LocalImporter) getFile(filename string, writer *bufio.Writer) error {
+func (i LocalImporter) GetFile(filename string) (io.Reader, error) {
if fd, err := os.Open(path.Join(i.Base, filename)); err != nil {
+ return nil, err
+ } else {
+ return fd, nil
+ }
+}
+
+func (i LocalImporter) writeFile(filename string, reader io.Reader) error {
+ if fd, err := os.Create(path.Join(i.Base, filename)); err != nil {
return err
} else {
defer fd.Close()
- reader := bufio.NewReader(fd)
- reader.WriteTo(writer)
- writer.Flush()
+ io.Copy(fd, reader)
return nil
}
}
-func (i LocalImporter) listDir(filename string) ([]string, error) {
+func (i LocalImporter) ListDir(filename string) ([]string, error) {
if files, err := ioutil.ReadDir(path.Join(i.Base, filename)); err != nil {
return nil, err
} else {
@@ -76,6 +110,14 @@ func (i LocalImporter) listDir(filename string) ([]string, error) {
}
}
-func (i LocalImporter) stat(filename string) (os.FileInfo, error) {
+func (i LocalImporter) Stat(filename string) (os.FileInfo, error) {
return os.Stat(path.Join(i.Base, filename))
}
+
+type DeletableImporter interface {
+ DeleteDir(filename string) error
+}
+
+func (i LocalImporter) DeleteDir(filename string) error {
+ return os.RemoveAll(path.Join(i.Base, filename))
+}
diff --git a/admin/sync/markdown.go b/admin/sync/markdown.go
index e102d4a1..5175b212 100644
--- a/admin/sync/markdown.go
+++ b/admin/sync/markdown.go
@@ -1,66 +1,98 @@
package sync
import (
- "bufio"
+ "bytes"
"encoding/base32"
- "os"
+ "net/url"
"path"
- "regexp"
"strings"
"srs.epita.fr/fic-server/libfic"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
"golang.org/x/crypto/blake2b"
- "gopkg.in/russross/blackfriday.v2"
)
func ProcessMarkdown(i Importer, input string, rootDir string) (output string, err error) {
// Define the path where save linked files
hash := blake2b.Sum512([]byte(rootDir))
- absPath := "$FILES$/" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:]))
+
+ imgImporter := NewImageImporterTransformer(i, rootDir, hash)
// Process md
- output = string(blackfriday.Run(
- []byte(input),
- blackfriday.WithRenderer(blackfriday.NewHTMLRenderer(
- blackfriday.HTMLRendererParameters{
- AbsolutePrefix: absPath,
- Flags: blackfriday.CommonHTMLFlags,
- },
- )),
- ))
+ markdown := goldmark.New(
+ goldmark.WithExtensions(extension.DefinitionList),
+ goldmark.WithExtensions(extension.Linkify),
+ goldmark.WithExtensions(extension.Strikethrough),
+ goldmark.WithExtensions(extension.Table),
+ goldmark.WithExtensions(extension.Typographer),
+ goldmark.WithParserOptions(
+ parser.WithASTTransformers(
+ util.Prioritized(imgImporter, 200),
+ ),
+ ),
+ goldmark.WithRendererOptions(
+ html.WithHardWraps(),
+ ),
+ )
- // Import files
- var re *regexp.Regexp
- re, err = regexp.Compile(strings.Replace(absPath, "$", "\\$", -1) + "/[^\"]+")
- if err != nil {
+ var buf bytes.Buffer
+ context := parser.NewContext()
+ if err = markdown.Convert([]byte(input), &buf, parser.WithContext(context)); err != nil {
return
}
- files := re.FindAllString(output, -1)
- for _, filePath := range files {
- iPath := strings.TrimPrefix(filePath, absPath)
- dPath := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), iPath)
-
- if err = os.MkdirAll(path.Dir(dPath), 0755); err != nil {
- return
- }
-
- var fdto *os.File
- if fdto, err = os.Create(dPath); err != nil {
- return
- } else {
- defer fdto.Close()
- writer := bufio.NewWriter(fdto)
- if err = getFile(i, rootDir + iPath, writer); err != nil {
- os.Remove(dPath)
- return
- }
- }
- }
+ output = string(buf.Bytes())
// Trim output
output = strings.TrimSpace(output)
- return
+ return output, imgImporter.(*imageImporterTransformer).err
+}
+
+type imageImporterTransformer struct {
+ importer Importer
+ rootDir string
+ hash [blake2b.Size]byte
+ absPath string
+ err error
+}
+
+func NewImageImporterTransformer(i Importer, rootDir string, hash [blake2b.Size]byte) parser.ASTTransformer {
+ absPath := "$FILES$/" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:]))
+ return &imageImporterTransformer{i, rootDir, hash, absPath, nil}
+}
+
+func (t *imageImporterTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
+ t.err = ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
+ if !enter {
+ return ast.WalkContinue, nil
+ }
+
+ switch child := node.(type) {
+ case *ast.Image:
+ iPath := string(child.Destination)
+
+ // Unescape string if needed (mostly %20 to space)
+ if ip, err := url.QueryUnescape(iPath); err == nil {
+ iPath = ip
+ }
+
+ dPath := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(t.hash[:])), iPath)
+ child.Destination = []byte(path.Join(t.absPath, string(child.Destination)))
+
+ err := importFile(t.importer, path.Join(t.rootDir, iPath), dPath)
+ if err != nil {
+ return ast.WalkStop, err
+ }
+ }
+
+ return ast.WalkContinue, nil
+ })
}
diff --git a/admin/sync/themes.go b/admin/sync/themes.go
index c511cdd0..09a4440b 100644
--- a/admin/sync/themes.go
+++ b/admin/sync/themes.go
@@ -1,28 +1,38 @@
package sync
import (
+ "bytes"
"fmt"
+ "image"
+ "image/jpeg"
+ "io"
"math/rand"
+ "net/http"
+ "os"
"path"
"regexp"
"strings"
"unicode"
+ "github.com/cenkalti/dominantcolor"
+ "github.com/gin-gonic/gin"
+ "github.com/yuin/goldmark"
+ "go.uber.org/multierr"
+ "golang.org/x/image/draw"
+
"srs.epita.fr/fic-server/libfic"
- "github.com/julienschmidt/httprouter"
- "gopkg.in/russross/blackfriday.v2"
)
-// getThemes returns all theme directories in the base directory.
-func getThemes(i Importer) ([]string, error) {
- var themes []string
-
- if dirs, err := i.listDir("/"); err != nil {
+// GetThemes returns all theme directories in the base directory.
+func GetThemes(i Importer) (themes []string, err error) {
+ if dirs, err := i.ListDir("/"); err != nil {
return nil, err
} else {
for _, dir := range dirs {
- if _, err := i.listDir(dir); err == nil {
- themes = append(themes, dir)
+ if !strings.HasPrefix(dir, ".") && !strings.HasPrefix(dir, "_") && dir != fic.StandaloneExercicesDirectory {
+ if _, err := i.ListDir(dir); err == nil {
+ themes = append(themes, dir)
+ }
}
}
}
@@ -30,9 +40,116 @@ func getThemes(i Importer) ([]string, error) {
return themes, nil
}
+// GetThemesExtended returns all theme directories, including standalone exercices.
+func GetThemesExtended(i Importer) (themes []string, err error) {
+ themes, err = GetThemes(i)
+ if err != nil {
+ return
+ }
+
+ if i.Exists(fic.StandaloneExercicesDirectory) {
+ themes = append(themes, fic.StandaloneExercicesDirectory)
+ }
+
+ return
+}
+
+// resizePicture makes the given image just fill the given rectangle.
+func resizePicture(i Importer, imgPath string, importedPath string, rect image.Rectangle) error {
+ if fl, err := i.GetFile(imgPath); err != nil {
+ return err
+ } else {
+ if src, _, err := image.Decode(fl); err != nil {
+ if flc, ok := fl.(io.ReadCloser); ok {
+ flc.Close()
+ }
+ return err
+ } else if src.Bounds().Max.X > rect.Max.X && src.Bounds().Max.Y > rect.Max.Y {
+ if flc, ok := fl.(io.ReadCloser); ok {
+ flc.Close()
+ }
+
+ mWidth := rect.Max.Y * src.Bounds().Max.X / src.Bounds().Max.Y
+ mHeight := rect.Max.X * src.Bounds().Max.Y / src.Bounds().Max.X
+
+ if mWidth > rect.Max.X {
+ rect.Max.X = mWidth
+ } else {
+ rect.Max.Y = mHeight
+ }
+ dst := image.NewRGBA(rect)
+ draw.CatmullRom.Scale(dst, rect, src, src.Bounds(), draw.Over, nil)
+
+ dstFile, err := fileWriter(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg")
+ if err != nil {
+ return err
+ }
+ defer dstFile.Close()
+
+ if err = jpeg.Encode(dstFile, dst, &jpeg.Options{Quality: 100}); err != nil {
+ return err
+ }
+ } else {
+ dstFile, err := fileWriter(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg")
+ if err != nil {
+ return err
+ }
+ defer dstFile.Close()
+
+ if err = jpeg.Encode(dstFile, src, &jpeg.Options{Quality: 100}); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+type SubImager interface {
+ SubImage(r image.Rectangle) image.Image
+}
+
+// getBackgroundColor retrieves the most dominant color in the bottom of the image.
+func getBackgroundColor(importedPath string) (uint32, error) {
+ fl, err := os.Open(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg")
+ if err != nil {
+ return 0, err
+ }
+
+ src, _, err := image.Decode(fl)
+ if err != nil {
+ fl.Close()
+ return 0, err
+ }
+
+ bounds := src.Bounds()
+
+ // Test if the right and left corner have the same color
+ bottomLeft := src.(SubImager).SubImage(image.Rect(0, bounds.Dy()-10, 40, bounds.Dy()))
+ bottomRight := src.(SubImager).SubImage(image.Rect(bounds.Dx()-40, bounds.Dy()-10, bounds.Dx(), bounds.Dy()))
+
+ colorLeft := dominantcolor.Find(bottomLeft)
+ colorRight := dominantcolor.Find(bottomRight)
+ if uint32(colorLeft.R>>5)<<16+uint32(colorLeft.G>>5)<<8+uint32(colorLeft.B>>5) == uint32(colorRight.R>>5)<<16+uint32(colorRight.G>>5)<<8+uint32(colorRight.B>>5) {
+ return uint32(colorLeft.R)<<16 + uint32(colorLeft.G)<<8 + uint32(colorLeft.B), nil
+ }
+
+ // Only keep the darkest color of the bottom of the image
+ bottomFull := src.(SubImager).SubImage(image.Rect(0, bounds.Dy()-5, bounds.Dx(), bounds.Dy()))
+ colors := dominantcolor.FindN(bottomFull, 4)
+
+ color := colors[0]
+ for _, c := range colors {
+ if uint32(color.R<<2)+uint32(color.G<<2)+uint32(color.B<<2) > uint32(c.R<<2)+uint32(c.G<<2)+uint32(c.B<<2) {
+ color = c
+ }
+ }
+
+ return uint32(color.R)<<16 + uint32(color.G)<<8 + uint32(color.B), nil
+}
+
// getAuthors parses the AUTHORS file.
func getAuthors(i Importer, tname string) ([]string, error) {
- if authors, err := getFileContent(i, path.Join(tname, "AUTHORS.txt")); err != nil {
+ if authors, err := GetFileContent(i, path.Join(tname, "AUTHORS.txt")); err != nil {
return nil, err
} else {
var ret []string
@@ -50,104 +167,251 @@ func getAuthors(i Importer, tname string) ([]string, error) {
}
}
-// SyncThemes imports new or updates existing themes.
-func SyncThemes(i Importer) []string {
- var errs []string
+// BuildTheme creates a Theme from a given importer.
+func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExceptions, errs error) {
+ th = &fic.Theme{}
- if themes, err := getThemes(i); err != nil {
- errs = append(errs, err.Error())
+ th.Path = tdir
+
+ // Get exceptions
+ exceptions = LoadThemeException(i, th)
+
+ // Overwrite language
+ if language, err := GetFileContent(i, path.Join(tdir, "language.txt")); err == nil {
+ language = strings.TrimSpace(language)
+ if strings.Contains(language, "\n") {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("language.txt: Language can't contain new lines")))
+ } else {
+ th.Language = language
+ }
+ }
+
+ // Extract theme's label
+ if tname, err := GetFileContent(i, path.Join(tdir, "title.txt")); err == nil {
+ th.Name = fixnbsp(tname)
+ } else if f := strings.Index(tdir, "-"); f >= 0 {
+ th.Name = fixnbsp(tdir[f+1:])
+ } else {
+ th.Name = fixnbsp(tdir)
+ }
+ th.URLId = fic.ToURLid(th.Name)
+
+ if authors, err := getAuthors(i, tdir); err != nil {
+ if tdir != fic.StandaloneExercicesDirectory {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
+ return nil, nil, errs
+ }
+ } else {
+ // Format authors
+ th.Authors = strings.Join(authors, ", ")
+ }
+
+ var err error
+ if i.Exists(path.Join(tdir, "headline.txt")) {
+ th.Headline, err = GetFileContent(i, path.Join(tdir, "headline.txt"))
+ } else if i.Exists(path.Join(tdir, "headline.md")) {
+ th.Headline, err = GetFileContent(i, path.Join(tdir, "headline.md"))
+ }
+ if err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's headline: %w", err)))
+ }
+ if th.Headline != "" {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(th.Headline, th.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("headline.md: %w", err)))
+ }
+ }
+ }
+
+ var intro string
+ if i.Exists(path.Join(tdir, "overview.txt")) {
+ intro, err = GetFileContent(i, path.Join(tdir, "overview.txt"))
+ } else if i.Exists(path.Join(tdir, "overview.md")) {
+ intro, err = GetFileContent(i, path.Join(tdir, "overview.md"))
+ } else {
+ err = fmt.Errorf("unable to find overview.txt nor overview.md")
+ }
+ if err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's overview: %w", err)))
+ } else {
+ // Call checks hooks
+ for _, h := range hooks.mdTextHooks {
+ for _, err := range multierr.Errors(h(intro, th.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
+ }
+ }
+
+ // Split headline from intro
+ if th.Headline == "" {
+ ovrvw := strings.Split(fixnbsp(intro), "\n")
+ th.Headline = ovrvw[0]
+ if len(ovrvw) > 1 {
+ intro = strings.Join(ovrvw[1:], "\n")
+ }
+ }
+
+ // Format overview (markdown)
+ th.Intro, err = ProcessMarkdown(i, intro, tdir)
+ if err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err)))
+ }
+ var buf bytes.Buffer
+ err := goldmark.Convert([]byte(th.Headline), &buf)
+ if err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err)))
+ } else {
+ th.Headline = string(buf.Bytes())
+ }
+ }
+
+ if i.Exists(path.Join(tdir, "heading.jpg")) {
+ th.Image = path.Join(tdir, "heading.jpg")
+ } else if i.Exists(path.Join(tdir, "heading.png")) {
+ th.Image = path.Join(tdir, "heading.png")
+ } else {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
+ }
+
+ if i.Exists(path.Join(tdir, "partner.jpg")) {
+ th.PartnerImage = path.Join(tdir, "partner.jpg")
+ } else if i.Exists(path.Join(tdir, "partner.png")) {
+ th.PartnerImage = path.Join(tdir, "partner.png")
+ }
+
+ if i.Exists(path.Join(tdir, "partner.txt")) {
+ if txt, err := GetFileContent(i, path.Join(tdir, "partner.txt")); err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get partner's text: %w", err)))
+ } else {
+ th.PartnerText, err = ProcessMarkdown(i, txt, tdir)
+ if err != nil {
+ errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
+ }
+ }
+ }
+ return
+}
+
+// SyncThemeFiles import all theme's related files
+func SyncThemeFiles(i Importer, btheme *fic.Theme) (errs error) {
+ if len(btheme.Image) > 0 {
+ if _, err := i.importFile(btheme.Image,
+ func(filePath string, origin string) (interface{}, error) {
+ if err := resizePicture(i, origin, filePath, image.Rect(0, 0, 500, 300)); err != nil {
+ return nil, err
+ }
+
+ btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir)
+ btheme.BackgroundColor, _ = getBackgroundColor(filePath)
+ return nil, nil
+ }); err != nil {
+ errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
+ }
+ }
+
+ if len(btheme.PartnerImage) > 0 {
+ if _, err := i.importFile(btheme.PartnerImage,
+ func(filePath string, origin string) (interface{}, error) {
+ btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir)
+ return nil, nil
+ }); err != nil {
+ errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err)))
+ }
+ }
+
+ return
+}
+
+// SyncThemes imports new or updates existing themes.
+func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs error) {
+ if themes, err := GetThemes(i); err != nil {
+ errs = multierr.Append(errs, fmt.Errorf("Unable to list themes: %w", err))
} else {
rand.Shuffle(len(themes), func(i, j int) {
themes[i], themes[j] = themes[j], themes[i]
})
+ exceptions = map[string]*CheckExceptions{}
+
for _, tdir := range themes {
- var authors []string
- var intro string
- var headline string
- var image string
- var theme fic.Theme
- var tname string
+ btheme, excepts, berrs := BuildTheme(i, tdir)
+ errs = multierr.Append(errs, berrs)
- // Extract theme's label
- if f := strings.Index(tdir, "-"); f >= 0 {
- tname = fixnbsp(tdir[f+1:])
- } else {
- tname = fixnbsp(tdir)
- }
-
- if authors, err = getAuthors(i, tdir); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to get AUTHORS: %s", tname, err))
+ if btheme == nil {
continue
- } else if intro, err = getFileContent(i, path.Join(tdir, "overview.txt")); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to get theme's overview: %s", tname, err))
- } else {
- // Split headline from intro
- ovrvw := strings.Split(fixnbsp(intro), "\n")
- headline = ovrvw[0]
- if len(ovrvw) > 1 {
- intro = strings.Join(ovrvw[1:], "\n")
- }
-
- if theme, err = fic.GetThemeByName(tname); err != nil {
- if _, err := fic.CreateTheme(tname, fic.ToURLid(tname), tdir, strings.Join(authors, ", "), intro, headline, image); err != nil {
- errs = append(errs, fmt.Sprintf("%q: an error occurs during add: %s", tdir, err))
- continue
- }
- }
}
- // Format authors
- authors_str := strings.Join(authors, ", ")
+ exceptions[tdir] = excepts
- // Format overview (markdown)
- intro, err = ProcessMarkdown(i, intro, tdir)
+ err = SyncThemeFiles(i, btheme)
if err != nil {
- errs = append(errs, fmt.Sprintf("%q: overview.txt: an error occurs during markdown formating: %s", tdir, err))
- }
- headline = string(blackfriday.Run([]byte(headline)))
-
- if i.exists(path.Join(tdir, "heading.jpg")) {
- image = path.Join(tdir, "heading.jpg")
- } else if i.exists(path.Join(tdir, "heading.png")) {
- image = path.Join(tdir, "heading.png")
+ errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
}
- if len(image) > 0 {
- if _, err := i.importFile(image,
- func(filePath string, origin string) (interface{}, error) {
- image = strings.TrimPrefix(filePath, fic.FilesDir)
- return nil, nil
- }); err != nil {
- errs = append(errs, fmt.Sprintf("%q: unable to import heading image: %s", tdir, err))
- continue
- }
+ var theme *fic.Theme
+ if theme, err = fic.GetThemeByPath(btheme.Path); err != nil {
+ if _, err := fic.CreateTheme(btheme); err != nil {
+ errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
+ continue
+ }
}
- if theme.Name != tname || theme.Authors != authors_str || theme.Headline != headline || theme.Intro != intro || theme.Image != image {
- theme.Name = tname
- theme.Authors = authors_str
- theme.Intro = intro
- theme.Headline = headline
- theme.Image = image
- theme.Path = tdir
- if _, err := theme.Update(); err != nil {
- errs = append(errs, fmt.Sprintf("%q: an error occurs during update: %s", tdir, err))
+ if !fic.CmpTheme(theme, btheme) {
+ btheme.Id = theme.Id
+ if _, err := btheme.Update(); err != nil {
+ errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
continue
}
}
}
}
- return errs
+ return
+}
+
+func LoadThemeExceptions(i Importer, theme *fic.Theme) (*CheckExceptions, error) {
+ return nil, nil
}
// ApiListRemoteThemes is an accessor letting foreign packages to access remote themes list.
-func ApiListRemoteThemes(_ httprouter.Params, _ []byte) (interface{}, error) {
- return getThemes(GlobalImporter)
+func ApiListRemoteThemes(c *gin.Context) {
+ themes, err := GetThemes(GlobalImporter)
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, themes)
+}
+
+func GetRemoteTheme(thid string) (*fic.Theme, error) {
+ if thid == fic.StandaloneExercicesTheme.URLId || thid == fic.StandaloneExercicesDirectory {
+ return &fic.StandaloneExercicesTheme, nil
+ }
+
+ theme, _, errs := BuildTheme(GlobalImporter, thid)
+ if theme == nil {
+ return nil, errs
+ }
+
+ return theme, nil
}
// ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes.
-func ApiGetRemoteTheme(ps httprouter.Params, _ []byte) (interface{}, error) {
- return getAuthors(GlobalImporter, ps.ByName("thid"))
+func ApiGetRemoteTheme(c *gin.Context) {
+ var theme *fic.Theme
+ var err error
+
+ if c.Params.ByName("thid") == fic.StandaloneExercicesTheme.URLId {
+ theme, err = GetRemoteTheme(fic.StandaloneExercicesDirectory)
+ } else {
+ theme, err = GetRemoteTheme(c.Params.ByName("thid"))
+ }
+
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, theme)
}
diff --git a/backend/.gitignore b/backend/.gitignore
deleted file mode 100644
index e34d8c32..00000000
--- a/backend/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-backend
diff --git a/backend/choices.go b/backend/choices.go
deleted file mode 100644
index 441d06bc..00000000
--- a/backend/choices.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "log"
- "io/ioutil"
- "os"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-type wantChoices struct {
- FlagId int64 `json:"id"`
-}
-
-func treatWantChoices(pathname string, team fic.Team) {
- var ask wantChoices
-
- if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
- log.Println("[ERR]", err)
- } else if err = json.Unmarshal(cnt_raw, &ask); err != nil {
- log.Println("[ERR]", err)
- } else if ask.FlagId == 0 {
- log.Println("[WRN] Invalid content in wantChoices file: ", pathname)
- os.Remove(pathname)
- } else if flag, err := fic.GetFlagKey(ask.FlagId); err != nil {
- log.Println("[ERR]", err)
- } else if err = team.DisplayChoices(flag); err != nil {
- log.Println("[ERR]", err)
- } else {
- if err = genTeamMyFile(team); err != nil {
- log.Println("my-", team.Id, ".json generation error: ", err)
- }
- if err = os.Remove(pathname); err != nil {
- log.Println("[ERR]", err)
- }
- }
-}
diff --git a/backend/generation.go b/backend/generation.go
deleted file mode 100644
index 07c750a8..00000000
--- a/backend/generation.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "path"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-// Generate my.json and wait.json for a given team
-func genTeamMyFile(team fic.Team) error {
- dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id))
-
- if s, err := os.Stat(dirPath); os.IsNotExist(err) {
- os.MkdirAll(dirPath, 0777)
- } else if !s.IsDir() {
- return errors.New(fmt.Sprintf("%s is not a directory", dirPath))
- }
-
- if my, err := fic.MyJSONTeam(&team, true); err != nil {
- return err
- } else if j, err := json.Marshal(my); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil {
- return err
- }
-
- // Speed up generation when challenge is started
- if !ChStarted {
- if my, err := fic.MyJSONTeam(&team, false); err != nil {
- return err
- } else if j, err := json.Marshal(my); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(dirPath, "wait.json"), j, 0666); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// Generate public my.json file
-func genMyPublicFile() error {
- dirPath := path.Join(TeamsDir, "public")
-
- if s, err := os.Stat(dirPath); os.IsNotExist(err) {
- os.MkdirAll(dirPath, 0777)
- } else if !s.IsDir() {
- return errors.New(fmt.Sprintf("%s is not a directory", dirPath))
- }
-
- if my, err := fic.MyJSONTeam(nil, true); err != nil {
- return err
- } else if j, err := json.Marshal(my); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil {
- return err
- }
-
- os.Symlink("my.json", path.Join(dirPath, "wait.json"))
-
- return nil
-}
-
-// Generate general evemts.json file
-func genEventsFile() error {
- if evts, err := fic.GetLastEvents(); err != nil {
- return err
- } else if j, err := json.Marshal(evts); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(TeamsDir, "events.json"), j, 0666); err != nil {
- return err
- }
-
- return nil
-}
-
-// Generate general teams.json file
-func genTeamsFile() error {
- if teams, err := fic.ExportTeams(); err != nil {
- return err
- } else if j, err := json.Marshal(teams); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(TeamsDir, "teams.json"), j, 0666); err != nil {
- return err
- }
-
- return nil
-}
-
-// Generate general themes.json file
-func genThemesFile() error {
- if themes, err := fic.ExportThemes(); err != nil {
- return err
- } else if j, err := json.Marshal(themes); err != nil {
- return err
- } else if err = ioutil.WriteFile(path.Join(TeamsDir, "themes.json"), j, 0666); err != nil {
- return err
- }
-
- return nil
-}
-
-func genTeamAll(team fic.Team) {
- if err := genThemesFile(); err != nil {
- log.Println("themes.json generation error: ", err)
- } else if err = genTeamsFile(); err != nil {
- log.Println("teams.json generation error: ", err)
- } else if err = genTeamMyFile(team); err != nil {
- log.Println("my.json(", team.Id, ") generation error: ", err)
- }
-}
-
-func genAll() {
- if err := genThemesFile(); err != nil {
- log.Println("themes.json generation error: ", err)
- } else if err = genTeamsFile(); err != nil {
- log.Println("teams.json generation error: ", err)
- } else if err = genEventsFile(); err != nil {
- log.Println("events.json generation error: ", err)
- } else if err = genMyPublicFile(); err != nil {
- log.Println("MyPublic generation error: ", err)
- } else if teams, err := fic.GetActiveTeams(); err != nil {
- log.Println("Team retrieval error: ", err)
- } else {
- for _, team := range(teams) {
- if err = genTeamMyFile(team); err != nil {
- log.Println("Team generation error: ", err)
- }
- }
- }
-}
diff --git a/backend/hint.go b/backend/hint.go
deleted file mode 100644
index 7430edb0..00000000
--- a/backend/hint.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "io/ioutil"
- "os"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-type askOpenHint struct {
- HintId int64 `json:"id"`
-}
-
-func treatOpeningHint(pathname string, team fic.Team) {
- var ask askOpenHint
-
- if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
- log.Println("[ERR]", err)
- } else if err = json.Unmarshal(cnt_raw, &ask); err != nil {
- log.Println("[ERR]", err)
- } else if ask.HintId == 0 {
- log.Println("[WRN] Invalid content in hint file: ", pathname)
- os.Remove(pathname)
- } else if hint, err := fic.GetHint(ask.HintId); err != nil {
- log.Println("[ERR]", err)
- } else if err = team.OpenHint(hint); err != nil {
- log.Println("[ERR]", err)
- } else {
- // Write event
- if exercice, err := hint.GetExercice(); err != nil {
- log.Println("[WRN]", err)
- } else if lvl, err := exercice.GetLevel(); err != nil {
- log.Println("[WRN]", err)
- } else if theme, err := fic.GetTheme(exercice.IdTheme); err != nil {
- log.Println("[WRN]", err)
- } else if _, err = fic.NewEvent(fmt.Sprintf("L'équipe %s a dévoilé un indice pour le
%de défi %s !", team.Name, lvl, theme.Name), "info"); err != nil {
- log.Println("[WRN] Unable to create event:", err)
- }
-
-
- if err = genTeamMyFile(team); err != nil {
- log.Println("my-", team.Id, ".json generation error: ", err)
- }
- if err = genEventsFile(); err != nil {
- log.Println("events.json generation error: ", err)
- }
- if err = os.Remove(pathname); err != nil {
- log.Println("[ERR]", err)
- }
- }
-}
diff --git a/backend/registration.go b/backend/registration.go
deleted file mode 100644
index cde5c1e4..00000000
--- a/backend/registration.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "log"
- "math/rand"
- "os"
- "path"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-type uTeamRegistration struct {
- TeamName string
- Members []fic.Member
-}
-
-func treatRegistration(pathname string, team_id string) {
- var nTeam uTeamRegistration
-
- if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
- log.Println("[ERR]", err)
- } else if err := json.Unmarshal(cnt_raw, &nTeam); err != nil {
- log.Println("[ERR]", err)
- } else if validTeamName(nTeam.TeamName) {
- if team, err := fic.CreateTeam(nTeam.TeamName, uint32(rand.Int31n(16581376))); err != nil {
- log.Printf("[ERR] Unable to register new team %s: %s\n", nTeam.TeamName, err)
- } else {
- for _, m := range nTeam.Members {
- // Force Id to 0, as it shouldn't have been defined yet
- m.Id = 0
- if err := team.GainMember(m); err != nil {
- log.Println("[WRN] Unable to add member (", m, ") to team (", team, "): ", err)
- }
- }
-
- if err := os.Remove(pathname); err != nil {
- log.Println("[WRN]", err)
- }
- if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe
%s qui vient de nous rejoindre !", team.Name), "info"); err != nil {
- log.Println("[WRN] Unable to create event:", err)
- }
-
- teamDirPath := fmt.Sprintf("%d", team.Id)
-
- // Create team directories into TEAMS
- if err := os.MkdirAll(path.Join(TeamsDir, teamDirPath), 0777); err != nil {
- log.Println("[ERR]", err)
- }
- if err := os.Symlink(teamDirPath, path.Join(TeamsDir, team_id)); err != nil {
- log.Println("[ERR]", err)
- }
-
- go func() {
- if err := genTeamMyFile(team); err != nil {
- log.Println("Team generation error: ", err)
- }
- if err := genEventsFile(); err != nil {
- log.Println("events.json generation error: ", err)
- }
- if err := genTeamsFile(); err != nil {
- log.Println("teams.json generation error: ", err)
- }
- }()
- }
- }
-}
diff --git a/backend/rename.go b/backend/rename.go
deleted file mode 100644
index 6c605c49..00000000
--- a/backend/rename.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "io/ioutil"
- "os"
- "regexp"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-func validTeamName(name string) bool {
- match, err := regexp.MatchString("^[A-Za-z0-9 àéèêëîïôùûü_-]{1,32}$", name)
- return err == nil && match
-}
-
-func treatRename(pathname string, team fic.Team) {
- var keys map[string]string
-
- if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
- log.Println("[ERR]", err)
- } else if err := json.Unmarshal(cnt_raw, &keys); err != nil {
- log.Println("[ERR]", err)
- } else if validTeamName(keys["newName"]) {
- team.Name = keys["newName"]
- if _, err := team.Update(); err != nil {
- log.Println("[WRN] Unable to change team name:", err)
- }
- if err := genTeamMyFile(team); err != nil {
- log.Println("my-", team.Id, ".json generation error: ", err)
- }
- if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe
%s qui vient de nous rejoindre !", team.Name), "info"); err != nil {
- log.Println("[WRN] Unable to create event:", err)
- }
- if err := genEventsFile(); err != nil {
- log.Println("events.json generation error: ", err)
- }
- if err := os.Remove(pathname); err != nil {
- log.Println("[ERR]", err)
- }
- }
-}
diff --git a/backend/submission.go b/backend/submission.go
deleted file mode 100644
index 14c6cecb..00000000
--- a/backend/submission.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "log"
- "math/rand"
- "os"
- "strconv"
-
- "golang.org/x/crypto/blake2b"
-
- "srs.epita.fr/fic-server/libfic"
-)
-
-type ResponsesUpload struct {
- Keys map[int64]string `json:"flags"`
- MCQs map[int64]bool `json:"mcqs"`
- MCQJ map[int64]string `json:"justifications"`
-}
-
-func treatSubmission(pathname string, team fic.Team, exercice_id string) {
- // Generate a unique identifier to follow the request in logs
- bid := make([]byte, 5)
- binary.LittleEndian.PutUint32(bid, rand.Uint32())
- id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
- log.Println(id, "New submission receive", pathname)
-
- // Parse exercice_id argument
- eid, err := strconv.ParseInt(exercice_id, 10, 64)
- if err != nil {
- log.Printf("%s [ERR] %s is not a valid number: %s\n", id, exercice_id, err)
- return
- }
-
- // Find the given exercice
- exercice, err := fic.GetExercice(eid)
- if err != nil {
- log.Printf("%s [ERR] Unable to find exercice %d: %s\n", id, eid, err)
- return
- }
-
- // Find the corresponding theme
- theme, err := fic.GetTheme(exercice.IdTheme)
- if err != nil {
- log.Printf("%s [ERR] Unable to retrieve theme for exercice %d: %s\n", id, eid, err)
- return
- }
-
- // Read received file
- cnt_raw, err := ioutil.ReadFile(pathname)
- if err != nil {
- log.Println(id, "[ERR] Unable to read file;", err)
- return
- }
-
- // Save checksum to treat duplicates
- cksum := blake2b.Sum512(cnt_raw)
- if err != nil {
- log.Println(id, "[ERR] JSON parsing error:", err)
- return
- }
-
- // Parse it
- var responses ResponsesUpload
- err = json.Unmarshal(cnt_raw, &responses)
- if err != nil {
- log.Println(id, "[ERR] JSON parsing error:", err)
- return
- }
-
- // Ensure the team didn't already solve this exercice
- s, tm := team.HasSolved(exercice)
- if s {
- log.Printf("%s [WRN] Team %d ALREADY solved exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
- return
- }
-
- // Handle MCQ justifications: convert to expected keyid
- for cid, j := range responses.MCQJ {
- if mcq, choice, err := fic.GetMCQbyChoice(cid); err != nil {
- log.Println(id, "[ERR] Unable to retrieve mcq from justification:", err)
- return
- } else if mcq.IdExercice != exercice.Id {
- log.Println(id, "[ERR] We retrieve an invalid MCQ: from exercice", mcq.IdExercice, "whereas expected from exercice", exercice.Id)
- return
- } else if key, err := choice.GetJustifiedFlag(exercice); err != nil {
- // Most probably, we enter here because the selected choice has not to be justified
- // So, just ignore this case as it will be invalidated by the mcq validation
- continue
- } else {
- if responses.Keys == nil {
- responses.Keys = map[int64]string{}
- }
- responses.Keys[key.Id] = j
- }
- }
-
- // Check given answers
- solved, err := exercice.CheckResponse(cksum[:], responses.Keys, responses.MCQs, team)
- if err != nil {
- log.Println(id, "[ERR] Unable to CheckResponse:", err)
- genTeamMyFile(team)
- return
- }
-
- // At this point, we have treated the file, so it can be safely deleted
- if err := os.Remove(pathname); err != nil {
- log.Println(id, "[ERR] Can't remove file:", err)
- }
-
- if solved {
- log.Printf("%s Team %d SOLVED exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
- if err := exercice.Solved(team); err != nil {
- log.Println(id, "[ERR] Unable to mark the challenge as solved:", err)
- }
-
- // Write event
- if lvl, err := exercice.GetLevel(); err != nil {
- log.Println(id, "[ERR] Unable to get exercice level:", err)
- } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s a résolu le
%de défi %s !", team.Name, lvl, theme.Name), "success"); err != nil {
- log.Println(id, "[WRN] Unable to create event:", err)
- }
- genTeamAll(team)
- } else {
- log.Printf("%s Team %d submit an invalid solution for exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
-
- // Write event (only on first try)
- if tm.Unix() == 0 {
- if lvl, err := exercice.GetLevel(); err != nil {
- log.Println(id, "[ERR] Unable to get exercice level:", err)
- } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s tente le
%de défi %s !", team.Name, lvl, theme.Name), "warning"); err != nil {
- log.Println(id, "[WRN] Unable to create event:", err)
- }
- }
- genTeamMyFile(team)
- }
-
- if err := genEventsFile(); err != nil {
- log.Println("events.json generation error: ", err)
- }
-}
diff --git a/checker/.gitignore b/checker/.gitignore
new file mode 100644
index 00000000..41646b19
--- /dev/null
+++ b/checker/.gitignore
@@ -0,0 +1 @@
+checker
\ No newline at end of file
diff --git a/checker/choices.go b/checker/choices.go
new file mode 100644
index 00000000..8687610d
--- /dev/null
+++ b/checker/choices.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+type wantChoices struct {
+ FlagId int `json:"id"`
+}
+
+func treatWantChoices(pathname string, team *fic.Team) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New wantChoices receive", pathname)
+
+ var ask wantChoices
+
+ cnt_raw, err := ioutil.ReadFile(pathname)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ err = json.Unmarshal(cnt_raw, &ask)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ if ask.FlagId == 0 {
+ log.Printf("%s [WRN] Invalid content in wantChoices file: %s\n", id, pathname)
+ os.Remove(pathname)
+ return
+ }
+
+ flag, err := fic.GetFlagKey(ask.FlagId)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ if !team.CanSeeFlag(flag) {
+ log.Printf("%s [!!!] The team asks to display choices whereas it doesn't have access to the flag\n", id)
+ return
+ }
+
+ exercice, err := flag.GetExercice()
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve the flag's underlying exercice: %s\n", id, err)
+ return
+ }
+
+ if !team.HasAccess(exercice) {
+ log.Printf("%s [!!!] The team asks to display choices whereas it doesn't have access to the exercice\n", id)
+ return
+ }
+
+ if exercice.Disabled {
+ log.Println("[!!!] The team submits something for a disabled exercice")
+ return
+ }
+
+ if exercice.IdTheme != nil {
+ theme, err := fic.GetTheme(*exercice.IdTheme)
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve theme for exercice %d: %s\n", id, exercice.Id, err)
+ return
+ }
+
+ // Theme should not be locked
+ if theme.Locked {
+ log.Printf("%s [!!!] Want choice received for locked theme %d (fid=%d): %s\n", id, exercice.IdTheme, ask.FlagId, theme.Name)
+ return
+ }
+ }
+
+ err = team.DisplayChoices(flag)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+}
diff --git a/checker/generation.go b/checker/generation.go
new file mode 100644
index 00000000..7964e111
--- /dev/null
+++ b/checker/generation.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var generatorSocket string
+
+func appendGenQueue(gs fic.GenStruct) error {
+ buf, err := json.Marshal(gs)
+ if err != nil {
+ return fmt.Errorf("Something is wrong with JSON encoder: %w", err)
+ }
+
+ sockType := "unix"
+ if strings.Contains(generatorSocket, ":") {
+ sockType = "tcp"
+ }
+
+ socket, err := net.Dial(sockType, generatorSocket)
+ if err != nil {
+ log.Printf("Unable to contact generator at: %s, retring in 1 second", generatorSocket)
+ time.Sleep(time.Second)
+ return appendGenQueue(gs)
+ }
+ defer socket.Close()
+
+ httpClient := &http.Client{
+ Transport: &http.Transport{
+ Dial: func(network, addr string) (net.Conn, error) {
+ return socket, nil
+ },
+ },
+ }
+
+ resp, err := httpClient.Post("http://localhost/enqueue", "application/json", bytes.NewReader(buf))
+ if err != nil {
+ return fmt.Errorf("Unable to enqueue new generation event: %w", err)
+ }
+ resp.Body.Close()
+
+ return nil
+}
diff --git a/checker/hint.go b/checker/hint.go
new file mode 100644
index 00000000..07d651b4
--- /dev/null
+++ b/checker/hint.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "html"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+type askOpenHint struct {
+ HintId int64 `json:"id"`
+}
+
+func treatOpeningHint(pathname string, team *fic.Team) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New openingHint receive", pathname)
+
+ var ask askOpenHint
+
+ cnt_raw, err := ioutil.ReadFile(pathname)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ err = json.Unmarshal(cnt_raw, &ask)
+ if err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ return
+ }
+
+ if ask.HintId == 0 {
+ log.Printf("%s [WRN] Invalid content in hint file: %s\n", id, pathname)
+ os.Remove(pathname)
+ return
+ }
+
+ hint, err := fic.GetHint(ask.HintId)
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve the given hint: %s\n", id, err)
+ return
+ }
+
+ exercice, err := hint.GetExercice()
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve the hint's underlying exercice: %s\n", id, err)
+ return
+ }
+
+ if exercice.Disabled {
+ log.Println("[!!!] The team submits something for a disabled exercice")
+ return
+ }
+
+ if !team.HasAccess(exercice) {
+ log.Printf("%s [!!!] The team asks to open an hint whereas it doesn't have access to the exercice\n", id)
+ return
+ }
+
+ if !team.CanSeeHint(hint) {
+ log.Printf("%s [!!!] The team asks to open an hint whereas it doesn't have access to it due to hint dependencies\n", id)
+ return
+ }
+
+ // Find the corresponding theme
+ var theme *fic.Theme
+ if exercice.IdTheme != nil {
+ theme, err = fic.GetTheme(*exercice.IdTheme)
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve theme for exercice %d: %s\n", id, exercice.Id, err)
+ return
+ }
+
+ // Theme should not be locked
+ if theme.Locked {
+ log.Printf("%s [!!!] Open hint received for locked theme %d (hid=%d): %s\n", id, exercice.IdTheme, ask.HintId, theme.Name)
+ return
+ }
+ }
+
+ if err = team.OpenHint(hint); err != nil && !fic.DBIsDuplicateKeyError(err) { // Skip DUPLICATE KEY errors
+ log.Printf("%s [ERR] Unable to open hint: %s\n", id, err)
+ return
+ }
+
+ if theme == nil {
+ if _, err = fic.NewEvent(fmt.Sprintf("L'équipe %s a dévoilé un indice pour le défi %s !", html.EscapeString(team.Name), exercice.Title), "info"); err != nil {
+ log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
+ }
+ } else {
+ // Write event
+ if lvl, err := exercice.GetLevel(); err != nil {
+ log.Printf("%s [WRN] %s\n", id, err)
+ } else if _, err = fic.NewEvent(fmt.Sprintf("L'équipe %s a dévoilé un indice pour le
%de défi %s !", html.EscapeString(team.Name), lvl, theme.Name), "info"); err != nil {
+ log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
+ }
+ }
+
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+}
diff --git a/checker/issue.go b/checker/issue.go
new file mode 100644
index 00000000..28684f19
--- /dev/null
+++ b/checker/issue.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+type IssueUpload struct {
+ Id int64 `json:"id"`
+ IdExercice int64 `json:"id_exercice"`
+ Subject string `json:"subject"`
+ Description string `json:"description"`
+}
+
+func treatIssue(pathname string, team *fic.Team) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New issue receive", pathname)
+
+ var issue IssueUpload
+
+ if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if err := json.Unmarshal(cnt_raw, &issue); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if len(issue.Subject) == 0 && len(issue.Description) == 0 {
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ log.Printf("%s Empty issue: not treated.\n", id)
+ } else if len(issue.Subject) == 0 {
+ if issue.Id <= 0 {
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ log.Printf("%s Issue with no subject: not treated.\n", id)
+ } else if claim, err := team.GetClaim(issue.Id); err != nil {
+ log.Printf("%s [ERR] Team id=%d,name=%q tries to access issue id=%d, but not granted: %s.\n", id, team.Id, team.Name, issue.Id, err)
+ } else if len(issue.Description) == 0 {
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ log.Printf("%s Empty issue: not treated.\n", id)
+ } else if desc, err := claim.AddDescription(issue.Description, &fic.ClaimAssignee{Id: 0}, true); err != nil {
+ log.Printf("%s [WRN] Unable to add description to issue: %s\n", id, err)
+ } else {
+ claim.State = "new"
+ claim.Update()
+
+ log.Printf("%s [OOK] New comment added to issue id=%d: id_description=%d\n", id, claim.Id, desc.Id)
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeamIssues, TeamId: team.Id})
+ }
+ } else {
+ var exercice *fic.Exercice = nil
+ if e, err := fic.GetExercice(issue.IdExercice); err == nil {
+ exercice = e
+ }
+
+ if claim, err := fic.NewClaim(issue.Subject, team, exercice, nil, "medium"); err != nil {
+ log.Printf("%s [ERR] Unable to create new issue: %s\n", id, err)
+ } else if len(issue.Description) > 0 {
+ if _, err := claim.AddDescription(issue.Description, &fic.ClaimAssignee{Id: 0}, true); err != nil {
+ log.Printf("%s [WRN] Unable to add description to issue: %s\n", id, err)
+ } else {
+ log.Printf("%s [OOK] New issue created: id=%d\n", id, claim.Id)
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ }
+ } else {
+ log.Printf("%s [OOK] New issue created: id=%d\n", id, claim.Id)
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeamIssues, TeamId: team.Id})
+ }
+}
diff --git a/checker/locked.go b/checker/locked.go
new file mode 100644
index 00000000..697754d8
--- /dev/null
+++ b/checker/locked.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var TeamLockedExercices = map[int64]map[string]bool{}
+
+func treatLocked(pathname string, team *fic.Team) {
+ fd, err := os.Open(pathname)
+ if err != nil {
+ log.Printf("[ERR] Unable to open %q: %s", pathname, err)
+ return
+ }
+
+ var locked map[string]bool
+
+ jdec := json.NewDecoder(fd)
+ if err := jdec.Decode(&locked); err != nil {
+ log.Printf("[ERR] Unable to parse JSON %q: %s", pathname, err)
+ return
+ }
+
+ TeamLockedExercices[team.Id] = locked
+ log.Printf("Team %q (tid=%d) has locked %d exercices", team.Name, team.Id, len(locked))
+}
diff --git a/backend/main.go b/checker/main.go
similarity index 52%
rename from backend/main.go
rename to checker/main.go
index 9a9aa5bf..1e16b1a7 100644
--- a/backend/main.go
+++ b/checker/main.go
@@ -6,9 +6,11 @@ import (
"log"
"math/rand"
"os"
+ "os/signal"
"path"
"strconv"
"strings"
+ "syscall"
"time"
"srs.epita.fr/fic-server/libfic"
@@ -31,7 +33,7 @@ func watchsubdir(watcher *fsnotify.Watcher, pathname string) error {
} else {
for _, d := range ds {
p := path.Join(pathname, d.Name())
- if d.IsDir() && d.Name() != ".tmp" && d.Mode() & os.ModeSymlink == 0 {
+ if d.IsDir() && d.Name() != ".tmp" && d.Mode()&os.ModeSymlink == 0 {
if err := watchsubdir(watcher, p); err != nil {
return err
}
@@ -43,51 +45,64 @@ func watchsubdir(watcher *fsnotify.Watcher, pathname string) error {
}
}
+func walkAndTreat(pathname string) error {
+ if ds, err := ioutil.ReadDir(pathname); err != nil {
+ return err
+ } else {
+ for _, d := range ds {
+ p := path.Join(pathname, d.Name())
+ if d.IsDir() && d.Name() != ".tmp" && d.Mode()&os.ModeSymlink == 0 {
+ if err := walkAndTreat(p); err != nil {
+ return err
+ }
+ } else if d.Mode().IsRegular() {
+ treat(p)
+ }
+ }
+ return nil
+ }
+}
+
var ChStarted = false
var lastRegeneration time.Time
var skipInitialGeneration = false
-func reloadSettings(config settings.FICSettings) {
+func reloadSettings(config *settings.Settings) {
+ allowRegistration = config.AllowRegistration
+ canJoinTeam = config.CanJoinTeam
+ denyTeamCreation = config.DenyTeamCreation
+ canResetProgression = config.WorkInProgress && config.CanResetProgression
fic.HintCoefficient = config.HintCurCoefficient
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
- ChStarted = time.Since(config.Start) >= 0
- if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness {
- fic.PartialValidation = config.PartialValidation
- fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
- fic.DisplayAllFlags = config.DisplayAllFlags
+ ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0
+ fic.PartialValidation = config.PartialValidation
+ fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
+ fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
+ fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices
+ fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation
+ fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation
+ fic.DisplayAllFlags = config.DisplayAllFlags
- fic.FirstBlood = config.FirstBlood
- fic.SubmissionCostBase = config.SubmissionCostBase
- fic.SubmissionUniqueness = config.SubmissionUniqueness
-
- if !skipInitialGeneration {
- log.Println("Generating files...")
- go func() {
- genAll()
- log.Println("Full generation done")
- }()
- } else {
- skipInitialGeneration = false
- log.Println("Regeneration skipped by option.")
- }
- lastRegeneration = config.Generation
- } else {
- log.Println("No change found. Skipping regeneration.")
- }
+ fic.FirstBlood = config.FirstBlood
+ fic.SubmissionCostBase = config.SubmissionCostBase
+ fic.SubmissionUniqueness = config.SubmissionUniqueness
+ fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
+ fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
+ fic.DiscountedFactor = config.DiscountedFactor
+ fic.QuestionGainRatio = config.QuestionGainRatio
}
func main() {
var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server")
- flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where load and save settings")
+ flag.StringVar(&generatorSocket, "generator", "./GENERATOR/generator.socket", "Path to the generator socket")
+ flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings")
flag.StringVar(&SubmissionDir, "submission", "./submissions", "Base directory where save submissions")
flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
- flag.StringVar(&fic.FilesDir, "files", "/files", "Request path prefix to reach files")
var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events")
- flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration")
flag.Parse()
- log.SetPrefix("[backend] ")
+ log.SetPrefix("[checker] ")
settings.SettingsDir = path.Clean(settings.SettingsDir)
SubmissionDir = path.Clean(SubmissionDir)
@@ -96,8 +111,8 @@ func main() {
rand.Seed(time.Now().UnixNano())
log.Println("Creating submission directory...")
- if _, err := os.Stat(SubmissionDir); os.IsNotExist(err) {
- if err := os.MkdirAll(SubmissionDir, 0777); err != nil {
+ if _, err := os.Stat(path.Join(SubmissionDir, ".tmp")); os.IsNotExist(err) {
+ if err := os.MkdirAll(path.Join(SubmissionDir, ".tmp"), 0700); err != nil {
log.Fatal("Unable to create submission directory: ", err)
}
}
@@ -122,25 +137,39 @@ func main() {
log.Fatal(err)
}
+ // Register SIGUSR1 and SIGTERM
+ interrupt1 := make(chan os.Signal, 1)
+ signal.Notify(interrupt1, syscall.SIGUSR1)
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, syscall.SIGTERM)
+
watchedNotify := fsnotify.Create
+loop:
for {
select {
+ case <-interrupt:
+ break loop
+ case <-interrupt1:
+ log.Println("SIGUSR1 received, retreating all files in queue...")
+ walkAndTreat(SubmissionDir)
+ log.Println("SIGUSR1 treated.")
case ev := <-watcher.Events:
- if d, err := os.Lstat(ev.Name); err == nil && ev.Op & fsnotify.Create == fsnotify.Create && d.Mode().IsDir() && d.Mode() & os.ModeSymlink == 0 && d.Name() != ".tmp" {
+ if d, err := os.Lstat(ev.Name); err == nil && ev.Op&fsnotify.Create == fsnotify.Create && d.Mode().IsDir() && d.Mode()&os.ModeSymlink == 0 && d.Name() != ".tmp" {
// Register new subdirectory
if err := watchsubdir(watcher, ev.Name); err != nil {
log.Println(err)
}
- } else if ev.Op & watchedNotify == watchedNotify && d.Mode().IsRegular() {
+ } else if err == nil && ev.Op&watchedNotify == watchedNotify && d.Mode().IsRegular() {
if *debugINotify {
log.Println("Treating event:", ev, "for", ev.Name)
}
go treat(ev.Name)
- } else if ev.Op & fsnotify.Write == fsnotify.Write {
+ } else if err == nil && ev.Op&fsnotify.Write == fsnotify.Write {
log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.")
watchedNotify = fsnotify.Write
- } else if *debugINotify {
+ go treat(ev.Name)
+ } else if err == nil && *debugINotify {
log.Println("Skipped event:", ev, "for", ev.Name)
}
case err := <-watcher.Errors:
@@ -164,27 +193,33 @@ func treat(raw_path string) {
if teamid, err = strconv.ParseInt(spath[1], 10, 64); err != nil {
if lnk, err := os.Readlink(path.Join(TeamsDir, spath[1])); err != nil {
- log.Println("[ERR]", err)
+ log.Printf("[ERR] Unable to readlink %q: %s\n", path.Join(TeamsDir, spath[1]), err)
return
} else if teamid, err = strconv.ParseInt(lnk, 10, 64); err != nil {
- log.Println("[ERR]", err)
+ log.Printf("[ERR] Error during ParseInt team %q: %s\n", lnk, err)
return
}
}
- var team fic.Team
+ var team *fic.Team
if team, err = fic.GetTeam(teamid); err != nil {
- log.Println("[ERR]", err)
+ log.Printf("[ERR] Unable to retrieve team %d: %s\n", teamid, err)
return
}
switch spath[2] {
case "name":
treatRename(raw_path, team)
+ case "issue":
+ treatIssue(raw_path, team)
case "hint":
treatOpeningHint(raw_path, team)
case "choices":
treatWantChoices(raw_path, team)
+ case "reset_progress":
+ treatResetProgress(raw_path, team)
+ case ".locked":
+ treatLocked(raw_path, team)
default:
treatSubmission(raw_path, team, spath[2])
}
diff --git a/checker/registration.go b/checker/registration.go
new file mode 100644
index 00000000..537eeea9
--- /dev/null
+++ b/checker/registration.go
@@ -0,0 +1,100 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "html"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var (
+ allowRegistration = false
+ canJoinTeam = false
+ denyTeamCreation = false
+)
+
+type uTeamRegistration struct {
+ TeamName string
+ JTeam int64
+ Members []fic.Member
+}
+
+func registrationProcess(id string, team *fic.Team, members []fic.Member, team_id string) {
+ for i, m := range members {
+ // Force Id to 0, as it shouldn't have been defined yet
+ m.Id = 0
+ if err := team.GainMember(&members[i]); err != nil {
+ log.Println("[WRN] Unable to add member (", m, ") to team (", team, "):", err)
+ }
+ }
+
+ teamDirPath := fmt.Sprintf("%d", team.Id)
+
+ // Create team directories into TEAMS
+ if err := os.MkdirAll(path.Join(TeamsDir, teamDirPath), 0751); err != nil {
+ log.Println(id, "[ERR]", err)
+ }
+ if err := os.Symlink(teamDirPath, path.Join(TeamsDir, team_id)); err != nil {
+ log.Println(id, "[ERR]", err)
+ }
+
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeams})
+}
+
+func treatRegistration(pathname string, team_id string) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New registration receive", pathname)
+
+ var nTeam uTeamRegistration
+
+ if !allowRegistration {
+ log.Printf("%s [ERR] Registration received, whereas disabled. Skipped.\n", id)
+ } else if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if err := json.Unmarshal(cnt_raw, &nTeam); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if nTeam.JTeam > 0 {
+ if !canJoinTeam {
+ log.Printf("%s [ERR] Join team received, whereas disabled. Skipped.\n", id)
+ } else if len(nTeam.Members) != 1 {
+ log.Printf("%s [ERR] Join team received, with incorrect member length: %d. Skipped.\n", id, len(nTeam.Members))
+ } else if team, err := fic.GetTeam(nTeam.JTeam); err != nil {
+ log.Printf("%s [ERR] Unable to join registered team %d: %s\n", id, nTeam.JTeam, err)
+ } else {
+ registrationProcess(id, team, nTeam.Members, team_id)
+
+ if err := os.Remove(pathname); err != nil {
+ log.Printf("%s [WRN] %s\n", id, err)
+ }
+ }
+ } else if denyTeamCreation {
+ log.Printf("%s [ERR] Registration received, whereas team creation denied. Skipped.\n", id)
+ } else if validTeamName(nTeam.TeamName) {
+ if team, err := fic.CreateTeam(nTeam.TeamName, fic.HSL{H: rand.Float64(), L: 1, S: 0.5}.ToRGB(), ""); err != nil {
+ log.Printf("%s [ERR] Unable to register new team %s: %s\n", id, nTeam.TeamName, err)
+ } else {
+ registrationProcess(id, team, nTeam.Members, team_id)
+
+ if err := os.Remove(pathname); err != nil {
+ log.Printf("%s [WRN] %s\n", id, err)
+ }
+ if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe
%s qui vient de nous rejoindre !", html.EscapeString(team.Name)), "info"); err != nil {
+ log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
+ }
+
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
+ }
+ }
+}
diff --git a/checker/rename.go b/checker/rename.go
new file mode 100644
index 00000000..81a7c936
--- /dev/null
+++ b/checker/rename.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "html"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "regexp"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+func validTeamName(name string) bool {
+ match, err := regexp.MatchString("^[A-Za-z0-9 àéèêëîïôùûü_-]{1,32}$", name)
+ return err == nil && match
+}
+
+func treatRename(pathname string, team *fic.Team) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New renameTeam receive", pathname)
+
+ var keys map[string]string
+
+ if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if err := json.Unmarshal(cnt_raw, &keys); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if validTeamName(keys["newName"]) {
+ team.Name = keys["newName"]
+ if _, err := team.Update(); err != nil {
+ log.Printf("%s [WRN] Unable to change team name: %s\n", id, err)
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe
%s qui vient de nous rejoindre !", html.EscapeString(team.Name)), "info"); err != nil {
+ log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
+ if err := os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ }
+}
diff --git a/checker/reset_progress.go b/checker/reset_progress.go
new file mode 100644
index 00000000..b16716b9
--- /dev/null
+++ b/checker/reset_progress.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+var canResetProgression = false
+
+type askResetProgress struct {
+ ExerciceId int64 `json:"eid"`
+}
+
+func treatResetProgress(pathname string, team *fic.Team) {
+ if !canResetProgression {
+ log.Printf("[!!!] Receive reset_progress, whereas desactivated in settings.\n")
+ return
+ }
+
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New ResetProgress receive", pathname)
+
+ var ask askResetProgress
+
+ if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if err = json.Unmarshal(cnt_raw, &ask); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ } else if ask.ExerciceId == 0 {
+ log.Printf("%s [WRN] Invalid content in reset_progress file: %s\n", id, pathname)
+ os.Remove(pathname)
+ } else if exercice, err := fic.GetExercice(ask.ExerciceId); err != nil {
+ log.Printf("%s [ERR] Unable to retrieve the given exercice: %s\n", id, err)
+ } else if exercice.Disabled {
+ log.Println("[!!!] The team submits something for a disabled exercice")
+ } else if err := team.ResetProgressionOnExercice(exercice); err != nil {
+ log.Printf("%s [ERR] Unable to reset progression: %s\n", id, err)
+ } else {
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ if err = os.Remove(pathname); err != nil {
+ log.Printf("%s [ERR] %s\n", id, err)
+ }
+ }
+}
diff --git a/checker/submission.go b/checker/submission.go
new file mode 100644
index 00000000..7a92c702
--- /dev/null
+++ b/checker/submission.go
@@ -0,0 +1,201 @@
+package main
+
+import (
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "html"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "strconv"
+
+ "golang.org/x/crypto/blake2b"
+
+ "srs.epita.fr/fic-server/libfic"
+)
+
+type ResponsesUpload struct {
+ Keys map[int]string `json:"flags"`
+ MCQs map[int]bool `json:"mcqs"`
+ MCQJ map[int]string `json:"justifications"`
+}
+
+func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
+ // Generate a unique identifier to follow the request in logs
+ bid := make([]byte, 5)
+ binary.LittleEndian.PutUint32(bid, rand.Uint32())
+ id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
+ log.Println(id, "New submission receive", pathname)
+
+ // Parse exercice_id argument
+ eid, err := strconv.ParseInt(exercice_id, 10, 64)
+ if err != nil {
+ log.Printf("%s [ERR] %s is not a valid number: %s\n", id, exercice_id, err)
+ return
+ }
+
+ // Identifier should not be blacklisted for the team
+ if blacklistteam, ok := TeamLockedExercices[team.Id]; ok {
+ if locked, ok := blacklistteam[exercice_id]; ok && locked {
+ log.Printf("%s [!!!] Submission received for team's locked exercice %d\n", id, eid)
+ return
+ }
+ }
+
+ // Find the given exercice
+ exercice, err := fic.GetExercice(eid)
+ if err != nil {
+ log.Printf("%s [ERR] Unable to find exercice %d: %s\n", id, eid, err)
+ return
+ }
+
+ // Check the exercice is not disabled
+ if exercice.Disabled {
+ log.Println("[!!!] The team submits something for a disabled exercice")
+ return
+ }
+
+ // Check the team can access this exercice
+ if !team.HasAccess(exercice) {
+ log.Println("[!!!] The team submits something for an exercice it doesn't have access yet")
+ return
+ }
+
+ // Find the corresponding theme
+ var theme *fic.Theme
+ if exercice.IdTheme != nil {
+ theme, err = fic.GetTheme(*exercice.IdTheme)
+ if err != nil {
+ log.Printf("%s [ERR] Unable to retrieve theme for exercice %d: %s\n", id, eid, err)
+ return
+ }
+
+ // Theme should not be locked
+ if theme.Locked {
+ log.Printf("%s [!!!] Submission received for locked theme %d (eid=%d): %s\n", id, exercice.IdTheme, eid, theme.Name)
+ return
+ }
+ }
+
+ // Read received file
+ cnt_raw, err := ioutil.ReadFile(pathname)
+ if err != nil {
+ log.Println(id, "[ERR] Unable to read file;", err)
+ return
+ }
+
+ // Save checksum to treat duplicates
+ cksum := blake2b.Sum512(cnt_raw)
+ if err != nil {
+ log.Println(id, "[ERR] JSON parsing error:", err)
+ return
+ }
+
+ // Parse it
+ var responses ResponsesUpload
+ err = json.Unmarshal(cnt_raw, &responses)
+ if err != nil {
+ log.Println(id, "[ERR] JSON parsing error:", err)
+ return
+ }
+
+ // Ensure the team didn't already solve this exercice
+ tm := team.HasSolved(exercice)
+ if tm != nil {
+ if theme == nil {
+ log.Printf("%s [WRN] Team %d ALREADY solved standalone exercice %d (%s), continuing for eventual bonus flags\n", id, team.Id, exercice.Id, exercice.Title)
+ } else {
+ log.Printf("%s [WRN] Team %d ALREADY solved exercice %d (%s : %s), continuing for eventual bonus flags\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
+ }
+ }
+
+ // Handle MCQ justifications: convert to expected keyid
+ for cid, j := range responses.MCQJ {
+ if mcq, choice, err := fic.GetMCQbyChoice(cid); err != nil {
+ log.Println(id, "[ERR] Unable to retrieve mcq from justification:", err)
+ return
+ } else if mcq.IdExercice != exercice.Id {
+ log.Println(id, "[ERR] We retrieve an invalid MCQ: from exercice", mcq.IdExercice, "whereas expected from exercice", exercice.Id)
+ return
+ } else if key, err := choice.GetJustifiedFlag(exercice); err != nil {
+ // Most probably, we enter here because the selected choice has not to be justified
+ // So, just ignore this case as it will be invalidated by the mcq validation
+ continue
+ } else {
+ if responses.Keys == nil {
+ responses.Keys = map[int]string{}
+ }
+ responses.Keys[key.Id] = j
+ }
+ }
+
+ // Check given answers
+ solved, err := exercice.CheckResponse(cksum[:], responses.Keys, responses.MCQs, team)
+ if err != nil {
+ log.Println(id, "[ERR] Unable to CheckResponse:", err)
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ return
+ }
+
+ // At this point, we have treated the file, so it can be safely deleted
+ if err := os.Remove(pathname); err != nil {
+ log.Println(id, "[ERR] Can't remove file:", err)
+ }
+
+ if tm != nil {
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ } else if solved {
+ if theme == nil {
+ log.Printf("%s Team %d SOLVED exercice %d (%s)\n", id, team.Id, exercice.Id, exercice.Title)
+ } else {
+ log.Printf("%s Team %d SOLVED exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
+
+ }
+ if err := exercice.Solved(team); err != nil {
+ log.Println(id, "[ERR] Unable to mark the challenge as solved:", err)
+ }
+
+ // Write event
+ if theme == nil {
+ if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s a résolu le défi %s !", html.EscapeString(team.Name), exercice.Title), "success"); err != nil {
+ log.Println(id, "[WRN] Unable to create event:", err)
+ }
+ } else {
+ if lvl, err := exercice.GetLevel(); err != nil {
+ log.Println(id, "[ERR] Unable to get exercice level:", err)
+ } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s a résolu le
%de défi %s !", html.EscapeString(team.Name), lvl, theme.Name), "success"); err != nil {
+ log.Println(id, "[WRN] Unable to create event:", err)
+ }
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenThemes})
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeams})
+ } else {
+ if theme == nil {
+ log.Printf("%s Team %d submit an invalid solution for exercice %d (%s)\n", id, team.Id, exercice.Id, exercice.Title)
+ } else {
+ log.Printf("%s Team %d submit an invalid solution for exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
+ }
+
+ // Write event (only on first try)
+ if tm == nil {
+ if theme == nil {
+ if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s tente le défi %s !", html.EscapeString(team.Name), exercice.Title), "warning"); err != nil {
+ log.Println(id, "[WRN] Unable to create event:", err)
+ }
+ } else {
+ if lvl, err := exercice.GetLevel(); err != nil {
+ log.Println(id, "[ERR] Unable to get exercice level:", err)
+ } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s tente le
%de défi %s !", html.EscapeString(team.Name), lvl, theme.Name), "warning"); err != nil {
+ log.Println(id, "[WRN] Unable to create event:", err)
+ }
+ }
+ }
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
+ }
+
+ appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
+}
diff --git a/configs/.gitignore b/configs/.gitignore
new file mode 100644
index 00000000..4bd07476
--- /dev/null
+++ b/configs/.gitignore
@@ -0,0 +1,5 @@
+id_ed25519
+id_ed25519.pub
+dm-crypt.key
+fic.srs.epita.fr
+dhparams-4096.pem
\ No newline at end of file
diff --git a/configs/anim-start-challenge.sh b/configs/anim-start-challenge.sh
new file mode 100755
index 00000000..8fccbda1
--- /dev/null
+++ b/configs/anim-start-challenge.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo "Launching challenge..."
+echo
+
+curl -s admin-challenge:8081/admin/api/settings.json | jq "{start:.start, end:.end}"
+
+echo
+
+for i in `seq 30 -1 1`
+do
+ echo -n "."
+ sleep 0.036
+done
+echo
+
+curl -s admin-challenge:8081/admin/api/settings.json | jq .
+
+for i in `seq 40 -1 1`
+do
+ echo -n "."
+ sleep 0.02
+done
+echo
+
+echo "Challenge started!" | figlet
+echo
+echo
+echo
diff --git a/configs/authorized_keys b/configs/authorized_keys
index 3f41ee8f..eb537ee9 100644
--- a/configs/authorized_keys
+++ b/configs/authorized_keys
@@ -1,5 +1,43 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDO/3qKhSUbGYZBVraFo68oScJahRDNQfG+uwDQlLv7g nemunaire@khonsou
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC19G9HGGZevJKeoDeew2VO6hbpWI71SBqemQUvYnD+WLureahuyCZYeJCalyU9QimsUTyQp6WGqwhh8I9x+K6gss79ITOUVt6yFbmj2Hdm00qJhPElduk+Jg0dTydgeGj83FJExz37dfWTq0Sp2hb4zfxoIH/BT81M+gZpQAQ3f9DIsgHlIE+/x6wLSWOcRVCOeX4YreIVXG75HaKmpY3p2gGuHtmrmuYdgLinihARKih4IvBFOMl0oC5y2AOwZP4AXvU0amc2Pkcn+Q/vzvu0Wf8CVH2ZdywC6HUIJpF41qIDM582ddwgxK1Qj0e7CDfzYRy0ChejfKIIksTDtYB4AkGRWTQ57onxwvDrAVXFq4OP76a9nD+oz7Fowc7VbqP9llJDU8wHWavsAsvbWCcLZ86kOvbbveHgBs23/L4cPwcyA7euta3j8wcW6In+eyMVhy5vs0eQFtzh9tbApFDhCUDdZAdBN5avEpjltizMy0wpEBZynafUO+goholH3t96fdJzeF+RWyzH8po0sNEXQeFJi8gGLgkqdJwWnKnJy1gb4zkemgs8T1B0Uy6wUVFaXNKEAy7gCtIF+dlFxy2pyKar9cITNds2V8cSuw5NM4z66aNB9ONdkU3ZkFQi8nyiJAvVmVhSt9JOhjGHNbGtHeN3Gaz0qW1EtM0GAy8mIw== segharish@gmail.com
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1TdooqihPGLSGp9LZxU044d+ULhDb0sjoif5DsajyXH7qpVfR8K3HNs3dVcvenyZwA9+tJomEFMFTDDg+03peQ4KYLjpdtRR2wofGCjyNcRcmzcvpi428c4JBOdNxiPkBN/XaiZUJzXZL4fEqm396ZLnyfQ6ns9flKV0zJJt9C98+TXqG01H35tcQamwZMDl1KMrIGCRvywdu+wqB4Z2AkBoI+VH8t2AqXcyKi7ltGZTzbEEItFx4HqlemxgcaM86jho/WlsCvF3AHr9sDJlO93+SuX6gX4sZmRo00KJU9ss/w1FpYjX8crOlr1Ze7YUU/wmTpzzhje50ZNhSRR9R thibaut.passilly@MacBook-Pro.local
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCWrRHAMkZolWFm3jTo0wcBUUJVNKlfO66S2z731nsiQb1r0pQjZDsZjs1YKPp92F2XncQXFUFujOPd4dNT8vkYOKLm6FeDCU8DR9omRwYQq6ZkJ65ySYCLyaKqrH9nKWb2nessAmo1lWzj+K9UmsZeV5tjguxdlpTCod7EHByrRSNTOEURzW96CDsr2IGl2h7XmUDOnMhiTn4y2Qq4sAz0BisP1Y9wBFkLgqYOAVmc+3r41W1w8oCcy2jyGaGp4Bo/mNZdHyozGrEvYXl/uJvOgdTXYufqb4oosoMLrakkEvINybyqspKKX2RNx6HitYdTYNzsZIBHKh70KIDmVvLL ron@CyberWeasley
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkLmYDbAHx+jCPS4XLQDwlgr1WbZ6oyHBYNOJCm8ektdHPrXUuXwjOAns+eieUdH+AS8/Jrgds31qne5wOHesA8vxilHRsJdJPZgndKntAky7xrfr9W7rYGNTBSMn7tiUBOWEg5uz5px/NTmtIdIhkDabG760yyDUNOpvm62MFNIjqixGpVWSjJvf/buKbCZVTq8Foqs40v8HwMaR4Z76Wvtl982B7BBgg1sf34XiN62Q2ej/Heqp+qxlMolTENQ+ezTZcDu39cBO/AV9FLhQ5Faz0SKPXWLz6nksvUMffWEajPnvJMnw/RjXxpW09ZAJuWoi4Xn1LDTTgToVmUu9B mike@IMiykyRo
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDCanbPYZGJPlXAbaUkVZPZbGMngjKctmm4dmBOv+eqAkNioeAtUKfmsNWJRZRqB+N2CcIn+0XtgIILNRS1/YC94oAkrSq7lXQ/Pb1vx/N8xQ+c8HCI+YVilKP3digpuVRa8eeCJLnOoBw0MKaXIvul6bG4rUSV+xxXMfeDb0BN9h1CLAnsdK3Ku0DIX57GqtPYbeqTN3tYZ3l+9uewAqUKJ28jEw+DTLUDLytyAw2XDalWMSlnC+WyoXMmwuYz74yKwiRzDPvOloDQf4cEZcdlywcz6HePTcH0d1oPaiTdDtIsI0eSSvZWSLBKA3hc+J3flH2Xyoz4Y7O5pdjoKK7dLoITHGK1Ue9MMCU8n9cqBFozDY7QtJuiY4bx9bdnhUc5PAXGpPR7vWM4tckEcaP8pEhED+h/u7TD4SyZlmUUZVyv7icrcCSQEbIKVLYi8bfMgXxDwkEiAOnTLCEDXT27VauSfQab68RmiMJvylW8ynl7rrM/3T0pbuzRXKJjGnuvNTNMQZqPwt+yp2BtXB/XdLEkh18PI35j/As+hhubxhtYW+esvuDw4a/JBWUtwO7MacM9VlKOtyV+LWKUljVwpgR5mCljauLnL5LulxmjRrcnm2nFCW7WzeSrilEV4AZ2lA0JJLMVI5VAWc6sP+LyM9TA3rmoaMxTxlJkni6Aw== cardno:18 059 785
+
+# adrien.langou
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDCanbPYZGJPlXAbaUkVZPZbGMngjKctmm4dmBOv+eqAkNioeAtUKfmsNWJRZRqB+N2CcIn+0XtgIILNRS1/YC94oAkrSq7lXQ/Pb1vx/N8xQ+c8HCI+YVilKP3digpuVRa8eeCJLnOoBw0MKaXIvul6bG4rUSV+xxXMfeDb0BN9h1CLAnsdK3Ku0DIX57GqtPYbeqTN3tYZ3l+9uewAqUKJ28jEw+DTLUDLytyAw2XDalWMSlnC+WyoXMmwuYz74yKwiRzDPvOloDQf4cEZcdlywcz6HePTcH0d1oPaiTdDtIsI0eSSvZWSLBKA3hc+J3flH2Xyoz4Y7O5pdjoKK7dLoITHGK1Ue9MMCU8n9cqBFozDY7QtJuiY4bx9bdnhUc5PAXGpPR7vWM4tckEcaP8pEhED+h/u7TD4SyZlmUUZVyv7icrcCSQEbIKVLYi8bfMgXxDwkEiAOnTLCEDXT27VauSfQab68RmiMJvylW8ynl7rrM/3T0pbuzRXKJjGnuvNTNMQZqPwt+yp2BtXB/XdLEkh18PI35j/As+hhubxhtYW+esvuDw4a/JBWUtwO7MacM9VlKOtyV+LWKUljVwpgR5mCljauLnL5LulxmjRrcnm2nFCW7WzeSrilEV4AZ2lA0JJLMVI5VAWc6sP+LyM9TA3rmoaMxTxlJkni6Aw== adrien
+
+# erwan.poles
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK89oym8s0B5LyQbLqTvVKS6U+yPGkHsYATDOfpCCBB8 erwan.poles@epita.fr
+
+# jules.lefebvre
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmOU1KFU3mS8Rni/4TOlNp98dTuf9DxYA6mn2Ns+gbH jules@julesdecube.com
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrWWr/v6pSF5h0JGox7ncvmgPWfAVX/A8xSXFVqgdY4 jules@cri.epita.fr
+
+# tanguy.maraux
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpS5mMfl832EKi03M0awrTbiuGOh+OII5ojM3V9Onl9 tanguy.maraux@gmail.com
+
+# paul.leroux
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCUKevt/f1n2byv5oH43iQsZ7b4kAATHlHNUF6WMQjk6BSEpdx3Ux1ly0jKy6TPq+04/H0qSC7xF9vGsv3stcbXNST47aIKRnp+27J0SYsj4oaqIGJ/gtgl1QJfJVB+Sld7Sh65zT30kqm0Il3y5PHfpTk/9Y6HOEpnOO8IvmnL4jwiwKMXM+0EyaTaBvsAFo6AYz3/dvkNKSf3SMQWZJNqGtufZcXqg16gWy2+tqALhtCDdmIV1qtK81MxWgol19ZBukk8+qfnX3SgCY1VaJIbBs0vmkixE0e89Pr+mRIPjDL/3U5yH5StUrVOtJqo2pX9mN+r0G8A+lZouprjhu8rGsIyF4zXdcELgh5/ZVR11/OLnZaThsrh1NjZvFdeEgXNZ5IBEZ9KbLUSj7c6lb1j4PtST+BNc8iQj6DdktYluj7LiEgbfpss7rXmoAUC3y01/z1L1GiKJ+FQkN9ri/eQoT3gwyAApS/80MhP4SfgNbdslN49369hY3z04DC7aDc= paul@paul-82wr
+
+# ?
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICo5yumHfQbMwhZAtEZByQR0xIVcoealS7g4MNTMEVaX roote@roote-VirtualBox
+
+# cyril.blin
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCit9xqC+/EcC0D+x4Irg/AgnTx9rJDbE4FdKK2Z2vE0nSPzp1MbAtijVi5ndvr/JPlY3jUHeGEZBJHmADXeOvdJl1nvkqry/69phfr4nDYacvH0v66nDTRipqmCmebaYOkfXYG55oy40+6C6DwAGETTYq+PIaRcA/mSf6V9UxKBfnLVqdml7LFYEo1SbihAIFd0EZkwq1wevXdVmrDwF7VLiCin/5Axa6LUOe4l1SAYBpsV8pCY3PQ/KxpgCyJuYj2szhOl0shTPiV48f194xGtYrpx1uGhOHRDx6Rm/5LKY/5DUvKbHCa/ZAdUSoMTnd1TshAPJe1sYKSAAI1xPVmffOgF/Jh98QEuAuFmHfZXVgPdvApJ9r9Ea7gEyN6Xe37emkW1Dond4ARdNdaslVu0iwV6bQnDOGcEdAl3x3seRVRAiPAKp2tAEtVEqu7uFwX6v2mmpE5/uw8rfsl/wNgttjuYa/kJURNkto3bN02XNfzOnXXZ3bRtbNjHEyJQuM= cyril@nixos
+
+# victor.chartraire
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJgDuQ4FrDuDjoo1Xv7pA0WEev1hhgJ8lXpT/17QXsds celian@DESKTOP-BQA641E
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPfUVWWSUrfuQYofWwvotQip4uqG34dF1Ybn4tTU1l0n celian@DESKTOP-BQA641E
+
+# hugo.rubio
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC68cnL/66U9dKfGZe3Bfr+ggosVXZh5OymRuHEdl26lYr3uJAQ7tKtfgXXyfuIW87vxGuzZUOEAnDafJx0J24BVyTwNjJVRarB8efHVIvlL/S9hHPVKWaQyo1ZIgizCiRdljJLWweZKjRE6Yr8xPc6zlC7grQ0wG/WGtAKaedhxYUZcCkdxPMz9Jf2ufx23DB/ALUZ63KVwbWIof8LPtlBP3G74ooeuykR+BTH+6NinoOWlL94JsSTAhuVnKsSnG1eQ742Cv4XhhGleAStP3wFhyakdsvCOfw9bKN/z3dXA0nZGzwOqvHyI9OJx01GLHhyykfRL7PtpD8MMwzyq3r5 hugo0@LAPTOP-C4LN612T
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1BINotXNgrTmHrep0BbxtUIAvCXOjSN8GrfTq03Sfd ryugo@LAPTOP-C4LN612T
+
+# alexandra.delin
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOJGg902VI7w6nOSiGm6qyUnPn9w3sD3VKlMin3fbYK6 alex@AlexandraSRS
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIArw17NpbJRNf8m9eji5K+GcD8oiyJi/3ygceojEVtBL alexa@Alexandra_SRS
+
+# maxence.michot
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqiBbRBWhnG5+LU/pKevR5oJV3+2ZQRQ6Fu87F8873DkKP0okvAYqVXYEDIm/bq9+VocZHI8y9O+zbCXwIqdWRGESJFiM7r1L3HtimFlmWhzM+nzduS8dWGcGGa9XCw+OBHFMGUFEMyzNbi/YcF7QGybOGETdcyzXQng1JtW4IURdrFJm/0/EavRwO2O2xTexmHv8l7JAe0ChteeuDatADrWgNnoUXIqeQQIsPxKQJ9D4QZ7+QrD5K2iLCx8sqC8Eu10p7cRqB3pCcBzKc6MEOgA1FXJzYgNtWkkLR9s7cOVjKL6mL3DuYQla3VaSvkTh8SAKEkadgYXpYifF6ygL298EmhQIrqRdjDYAm+VDGfCvmtcEW4NM68tt74xLuhdDutLq9Riqx4n2pGPo8ws8ecCokcu5/ESGxpZwYmY+nMj8392yH4sgmW/nrs0WmAqawdwlDj09LIqbYXiGMd5zYYOu8V2nuMKG0zRtRESKbs04Uw2DUd/gTwfP2l5YqTn3K93xDNU2iZY+yLJai3vYaBLHzt6PEvESdhAceYBMTIN2iA6hGZfaMWJ4JHImoqCgwbnJw6qrIPHaj1SCWKWLPMjjYZbwndkH3Iy4oJ6gA6I/vT9YrzNvGYY7rVspa+O0RM+huVH1a0YoXSSzx6dop1k1ZWm7VwRCUJQ2ri+L2jw== thecr@maxence-portable-desktop
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMwyRf67AH/rUzSFsKpFfGDD/La4JqpYk9xCM04kJ6+P maxence@vokunaav
+
+# leo.blanc-di-pasquale
+ssh-rsa AAAAB4NzaC1yc2EAAAADAQABAAABgQDBovLxpJA0+gJiAVWrpWmjy9fyEQwOXv3Ytou7C4Y1O3lAyvmmHfBWuW1ZqIY/8yKIpJeSLCoGeN5kyMD9ITvMVcfSAxnDHXj04Q4rJzBGjQ/LF/CInx+HHjJWzGVgJC76m0BS7J0J7g5gAxfprGKEb3Z1kJmCTRQ470Azv6WIPDCw7aLNd1o5JqAsrmWSh/LwYBSapyp3Tk0KjcbxRifvnBJfYLHwNm1ZoaLCGAT3u3TKIifawEomx5QEel/211FaEdbglkeDtRL0YuvKQSuZCf4KSg7xSRQoWHmSuPothE9eQGzEKpJONKViqkxBo12O04cBEhYzQBh7GHmH3U3/9weNUX4EaKJWkfqh43eAnWohN07IXDnANYPRxWL4ITv+MNBWxYL5Zp3Zr85rhJZYhkyKEfcGjvolHaESegnsndhC74QW3eOXazuscAeROQbiMShk5iBIceTsEFTqI02/+XqTs9gXkuq3H3StJ6+9cavDwRyObGx5nVHmhQYnMEE= leo21@Aled
diff --git a/configs/deploy-initial-config.sh b/configs/deploy-initial-config.sh
new file mode 100755
index 00000000..fe3845e8
--- /dev/null
+++ b/configs/deploy-initial-config.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -e
+
+echo "Perform initial config..."
+
+[ -z "${PXE_IFACE}" ] && PXE_IFACE=$(ip r | grep default | head -1 | cut -d " " -f 5)
+[ -z "${DEPLOY_NETWORK}" ] && DEPLOY_NETWORK=192.168.255
+
+sed -i "s/eth0/${PXE_IFACE}/;s/192.168.255/${DEPLOY_NETWORK}/" /etc/udhcpd.conf
+
+ip a show dev "${PXE_IFACE}" | grep "inet " | grep "${DEPLOY_NETWORK}" ||
+ ip a add "${DEPLOY_NETWORK}.2/24" dev "${PXE_IFACE}"
+
+echo "Initial config done."
+
+[ -f fickit-boot-kernel ] || { echo "Missing fickit-boot-kernel file. Download the latest fickit artifact."; exit 1; }
+[ -f fickit-metadata.iso ] || { echo "Missing fickit-metadata.iso file. Use configs/gen_metadata.sh to generate it."; exit 1; }
+
+exec $@
diff --git a/configs/deploy-supervisord.conf b/configs/deploy-supervisord.conf
new file mode 100644
index 00000000..956abe41
--- /dev/null
+++ b/configs/deploy-supervisord.conf
@@ -0,0 +1,24 @@
+[supervisord]
+nodaemon = true
+silent = true
+
+[program:httpd]
+command = /usr/sbin/httpd -f -vv -h /srv/s
+stdout_logfile = /dev/stdout
+stdout_logfile_maxbytes = 0
+stderr_logfile = /dev/stderr
+stderr_logfile_maxbytes = 0
+
+[program:tftpd]
+command = /usr/sbin/in.tftpd -L -a 0.0.0.0:69 -v -s /srv
+stdout_logfile = /dev/stdout
+stdout_logfile_maxbytes = 0
+stderr_logfile = /dev/stderr
+stderr_logfile_maxbytes = 0
+
+[program:udhcpd]
+command = /usr/sbin/udhcpd -f
+stdout_logfile = /dev/stdout
+stdout_logfile_maxbytes = 0
+stderr_logfile = /dev/stderr
+stderr_logfile_maxbytes = 0
\ No newline at end of file
diff --git a/configs/dex-templates/templates/header.html b/configs/dex-templates/templates/header.html
new file mode 100644
index 00000000..13ae0d1d
--- /dev/null
+++ b/configs/dex-templates/templates/header.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
{{ issuer }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/configs/dex-templates/templates/login.html b/configs/dex-templates/templates/login.html
new file mode 100644
index 00000000..5c64ccd4
--- /dev/null
+++ b/configs/dex-templates/templates/login.html
@@ -0,0 +1,21 @@
+{{ template "header.html" . }}
+
+
+
+ Bienvenue au challenge Forensic !
+
+
+ {{ range $c := .Connectors }}
+
+ {{ end }}
+
+
+
+{{ template "footer.html" . }}
diff --git a/configs/dex-templates/templates/password.html b/configs/dex-templates/templates/password.html
new file mode 100644
index 00000000..46e8d587
--- /dev/null
+++ b/configs/dex-templates/templates/password.html
@@ -0,0 +1,37 @@
+{{ template "header.html" . }}
+
+
+
+ Bienvenue au challenge Forensic !
+
+
+ {{ if .BackLink }}
+
+ {{ end }}
+
+
+{{ template "footer.html" . }}
diff --git a/configs/dex-templates/theme/styles.css b/configs/dex-templates/theme/styles.css
new file mode 100644
index 00000000..72430367
--- /dev/null
+++ b/configs/dex-templates/theme/styles.css
@@ -0,0 +1,114 @@
+.theme-body {
+ background-color: white;
+ color: #272b30;
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+.theme-navbar {
+ background-color: #272b30;
+ border-bottom: 5px solid #4eaee6;
+ color: #333;
+ font-size: 13px;
+ font-weight: 100;
+ overflow: hidden;
+ padding: 0 10px;
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+}
+
+.theme-navbar__logo-wrap {
+ display: inline-block;
+ overflow: hidden;
+ padding: 10px 15px;
+ width: 300px;
+}
+
+.theme-navbar__logo {
+ height: 90px;
+ max-height: 12vh;
+}
+
+.theme-heading {
+ font-size: 20px;
+ font-weight: 500;
+ margin-bottom: 10px;
+ margin-top: 0;
+}
+
+.theme-panel {
+ background-color: #fff;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+ padding: 30px;
+}
+
+.theme-btn-provider {
+ background-color: #fff;
+ color: #333;
+ min-width: 250px;
+}
+
+.theme-btn-provider:hover {
+ color: #999;
+}
+
+.theme-btn--primary {
+ background-color: #333;
+ border: none;
+ color: #fff;
+ min-width: 200px;
+ padding: 6px 12px;
+}
+
+.theme-btn--primary:hover {
+ background-color: #666;
+ color: #fff;
+}
+
+.theme-btn--success {
+ background-color: #2FC98E;
+ color: #fff;
+ width: 250px;
+}
+
+.theme-btn--success:hover {
+ background-color: #49E3A8;
+}
+
+.theme-form-row {
+ display: block;
+ margin: 20px auto;
+}
+
+.theme-form-input {
+ border-radius: 4px;
+ border: 1px solid #CCC;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ color: #666;
+ display: block;
+ font-size: 14px;
+ height: 36px;
+ line-height: 1.42857143;
+ margin: auto;
+ padding: 6px 12px;
+ width: 250px;
+}
+
+.theme-form-input:focus,
+.theme-form-input:active {
+ border-color: #66AFE9;
+ outline: none;
+}
+
+.theme-form-label {
+ font-size: 13px;
+ font-weight: 600;
+ margin: 4px auto;
+ position: relative;
+ text-align: left;
+ width: 250px;
+}
+
+.theme-link-back {
+ margin-top: 4px;
+}
diff --git a/configs/dex.yaml b/configs/dex.yaml
new file mode 100644
index 00000000..41ed921e
--- /dev/null
+++ b/configs/dex.yaml
@@ -0,0 +1,84 @@
+# The base path of Dex and the external name of the OpenID Connect service.
+# This is the canonical URL that all clients MUST use to refer to Dex. If a
+# path is provided, Dex's HTTP service will listen at a non-root URL.
+issuer: https://live.fic.srs.epita.fr
+
+# The storage configuration determines where dex stores its state. Supported
+# options include SQL flavors and Kubernetes third party resources.
+storage:
+ type: sqlite3
+ config:
+ file: /var/dex/dex.db
+
+# Configuration for the HTTP endpoints.
+web:
+ http: 0.0.0.0:5556
+ #allowedOrigins: ['*']
+
+# Configuration for dex appearance
+frontend:
+ issuer: Challenge forensic
+ logoURL: img/fic.png
+ dir: /srv/dex/web/
+# theme: light
+
+# Configuration for telemetry
+#telemetry:
+# http: 0.0.0.0:5558
+
+
+
+oauth2:
+ #responseTypes: ["code", "token", "id_token"]
+ skipApprovalScreen: true
+
+staticClients:
+- id: epita-challenge
+ name: Challenge Forensic
+ redirectURIs: ['https://live.fic.srs.epita.fr/challenge_access/auth']
+ secret: N4n7AXzK9kpXt3TmSn8wAgtxqxhGORgcubLaE2g
+
+
+enablePasswordDB: true
+
+staticPasswords:
+ - email: "team01"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team02"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team03"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team04"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team05"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team06"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team07"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team08"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team09"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team10"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team11"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team12"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team13"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team14"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team15"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team16"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team17"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team18"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team19"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ - email: "team20"
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
diff --git a/configs/dhcpd.conf b/configs/dhcpd.conf
index a45e3488..5d787e0a 100644
--- a/configs/dhcpd.conf
+++ b/configs/dhcpd.conf
@@ -7,8 +7,12 @@ option routers 172.23.42.254;
option domain-name-servers 172.23.42.254;
option rfc3442-classless-static-routes code 121 = array of integer 8;
option ms-classless-static-routes code 249 = array of integer 8;
-option rfc3442-classless-static-routes 32, 163, 5, 55, 58, 172, 23, 42, 1;
-option ms-classless-static-routes 32, 163, 5, 55, 58, 172, 23, 42, 1;
+# Provide Internet
+option rfc3442-classless-static-routes 32, 91, 243, 117, 241, 172, 23, 42, 1, 0, 172, 23, 42, 254;
+option ms-classless-static-routes 32, 91, 243, 117, 241, 172, 23, 42, 1, 0, 172, 23, 42, 254;
+# Don't provide Internet
+#option rfc3442-classless-static-routes 32, 91, 243, 117, 241, 172, 23, 42, 1;
+#option ms-classless-static-routes 32, 91, 243, 117, 241, 172, 23, 42, 1;
subnet 172.23.42.0 netmask 255.255.255.0 {
range 172.23.42.10 172.23.42.253;
}
diff --git a/configs/fic-auth.conf b/configs/fic-auth.conf
deleted file mode 100644
index 773c5619..00000000
--- a/configs/fic-auth.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-if ($ssl_client_verify != "SUCCESS") {
- return 401;
-}
-if ($ssl_client_verify = "SUCCESS") {
- set $team "_AUTH_ID_$ssl_client_serial";
-}
diff --git a/configs/gen_metadata.sh b/configs/gen_metadata.sh
new file mode 100755
index 00000000..f51984eb
--- /dev/null
+++ b/configs/gen_metadata.sh
@@ -0,0 +1,195 @@
+#!/bin/bash
+
+set -e
+
+export DOMAIN_NAME="live.fic.srs.epita.fr"
+export IP_FRONTEND="10.42.192.3/24"
+export IP_FRONTEND_ROUTER="10.42.192.1"
+export IP_FIC_SRS_FR=$(host ${DOMAIN_NAME} | grep -o '\([0-9]\{1,3\}.\)\+')
+export IPS_BACKEND="192.168.3.92/24\\n192.168.4.92/24\\n"
+export IP_BACKEND_ROUTER="192.168.3.1"
+
+export AIRBUS_DESTINATION="gaming.cyberrange.lan"
+export AIRBUS_BASEURL="https://${AIRBUS_DESTINATION}/api"
+export AIRBUS_TOKEN="abcdef0123456789abcdef0123456789"
+export AIRBUS_SESSION_NAME="Forensique"
+
+export IPS_FRONTEND="${IP_FRONTEND}\\n${IP_FIC_SRS_FR}\\n"
+
+escape_newline () {
+ sed 's/$/\\n/g' | tr -d '\n'
+}
+
+which mkisofs > /dev/null 2> /dev/null || { echo "Please install genisoimage (Debian/Ubuntu) or cdrkit (Alpine)" >&2; exit 1; }
+
+if [ $# -gt 0 ]
+then
+ which jq > /dev/null 2> /dev/null || { echo "Please install jq" >&2; exit 1; }
+
+ # Expect a previous ISO to update:
+ # Keep: DM_CRYPT, DHPARAMs and SYNCHRO_SSH_KEY
+
+ P=$(pwd)
+ D=$(mktemp -d)
+ pushd "${D}" > /dev/null
+
+ isoinfo -i "${P}/$1" -X -find -iname "USER_DAT*" > /dev/null || 7z x "$1" > /dev/null
+
+ FNAME="USER_DAT.;1"
+ if ! [ -f "$FNAME" ] && [ -f user-data ]
+ then
+ FNAME="user-data"
+ fi
+
+ export DM_CRYPT=$(jq -r '."dm-crypt".entries.key.content' "${FNAME}" | tr -d '\n')
+ export DHPARAM=$(jq -r '."tls_config".entries."dhparams-4096.pem".content' "${FNAME}" | escape_newline)
+ export SYNCRO_PRIVATE_KEY=$(jq -r '.synchro.entries.id_ed25519.content' "${FNAME}" | escape_newline)
+ export SYNCRO_PUBLIC_KEY=$(jq -r '.synchro.entries."id_ed25519.pub".content' "${FNAME}" | escape_newline)
+
+ popd > /dev/null
+ rm -rf "${D}"
+fi
+
+which vault > /dev/null 2> /dev/null || { echo "Please install vault" >&2; exit 1; }
+
+export VAULT_ADDR="${VAULT_ADDR:-"https://vault.srs.epita.fr:443"}"
+SSH_PATH="${SSH_PATH:-/tmp/fic_ssh}"
+DHPARAM_PATH="${DHPARAM_PATH:-/tmp/dhparam.pem}"
+OUTPUT_PATH="${OUTPUT_PATH:-"$(mktemp -d)"}"
+
+command -v vault &> /dev/null || (echo "vault could not be found" && exit)
+vault login -method=oidc -no-print 2> /dev/null
+
+[ -z "${DM_CRYPT}" ] && echo "/!\\ GENERATE NEW DM_CRYPT SECRETS" && export DM_CRYPT="$(tr -d -c "a-zA-Z0-9" < /dev/urandom | fold -w512 | head -n 1)"
+export CERT_PEM="$(vault kv get --field=cert.pem fic/cert/${DOMAIN_NAME} | escape_newline)"
+export CHAIN_PEM="$(vault kv get --field=chain.pem fic/cert/${DOMAIN_NAME} | escape_newline)"
+export FULLCHAIN_PEM="$(vault kv get --field=fullchain.pem fic/cert/${DOMAIN_NAME} | escape_newline)"
+export PRIVKEY_PEM="$(vault kv get --field=privkey.pem fic/cert/${DOMAIN_NAME} | escape_newline)"
+
+if [ -z "${SYNCRO_PUBLIC_KEY}" ] || [ -z "${SYNCRO_PRIVATE_KEY}" ]
+then
+ ssh-keygen -a 100 -t ed25519 -q -f "$SSH_PATH" -N "" <<< 'y'
+
+ export SYNCRO_PUBLIC_KEY="$(cat "$SSH_PATH".pub | escape_newline)"
+ export SYNCRO_PRIVATE_KEY="$(cat "$SSH_PATH" | escape_newline)"
+fi
+
+if [ -z "${DHPARAM}" ] && ! [ -f "$DHPARAM_PATH" ]
+then
+ command -v openssl &> /dev/null || (echo "openssl could not be found" && exit)
+
+ echo -e "\n\nGenerating DH params please wait"
+ openssl dhparam -out "$DHPARAM_PATH" 4096 &>/dev/null
+elif ! [ -f "$DHPARAM_PATH" ]
+then
+ echo "${DHPARAM}" > "${DHPARAM_PATH}"
+fi
+export DHPARAM="$(cat "$DHPARAM_PATH" | escape_newline)"
+
+export AUTHORIZED_KEYS="$(cat "$(dirname $0)/authorized_keys" | escape_newline)"
+
+TEMPLATE='
+{
+ "dm-crypt": {
+ "entries": {
+ "key": {
+ "perm": "0440",
+ "content": "${DM_CRYPT}"
+ }
+ }
+ },
+ "ssh": {
+ "entries": {
+ "authorized_keys": {
+ "perm": "0444",
+ "content": "${AUTHORIZED_KEYS}"
+ }
+ }
+ },
+ "synchro": {
+ "entries": {
+ "id_ed25519": {
+ "perm": "0400",
+ "content": "${SYNCRO_PRIVATE_KEY}"
+ },
+ "id_ed25519.pub": {
+ "perm": "0444",
+ "content": "${SYNCRO_PUBLIC_KEY}"
+ }
+ }
+ },
+ "ip_config": {
+ "entries": {
+ "frontend-players": {
+ "perm": "0444",
+ "content": "${IPS_FRONTEND}"
+ },
+ "frontend-router": {
+ "perm": "0444",
+ "content": "${IP_FRONTEND_ROUTER}"
+ },
+ "backend-admin": {
+ "perm": "0444",
+ "content": "${IPS_BACKEND}"
+ },
+ "backend-router": {
+ "perm": "0444",
+ "content": "${IP_BACKEND_ROUTER}"
+ },
+ "domain": {
+ "perm": "0444",
+ "content": "${DOMAIN_NAME}"
+ }
+ }
+ },
+ "remote_sync": {
+ "entries": {
+ "baseurl": {
+ "perm": "0444",
+ "content": "${AIRBUS_BASEURL}"
+ },
+ "destination": {
+ "perm": "0444",
+ "content": "${AIRBUS_DESTINATION}"
+ },
+ "token": {
+ "perm": "0444",
+ "content": "${AIRBUS_TOKEN}"
+ },
+ "session_name": {
+ "perm": "0444",
+ "content": "${AIRBUS_SESSION_NAME}"
+ }
+ }
+ },
+ "tls_config": {
+ "entries": {
+ "dhparams-4096.pem": {
+ "perm": "0400",
+ "content": "${DHPARAM}"
+ },
+ "cert.pem": {
+ "perm": "0400",
+ "content": "${CERT_PEM}"
+ },
+ "chain.pem": {
+ "perm": "0400",
+ "content": "${CHAIN_PEM}"
+ },
+ "fullchain.pem": {
+ "perm": "0400",
+ "content": "${FULLCHAIN_PEM}"
+ },
+ "privkey.pem": {
+ "perm": "0444",
+ "content": "${PRIVKEY_PEM}"
+ }
+ }
+ }
+}'
+
+echo "$TEMPLATE" | envsubst > "$OUTPUT_PATH"/user-data
+
+echo -e "Result in $OUTPUT_PATH\nGenerating iso"
+
+mkisofs -joliet-long -V CIDATA -o fickit-metadata.iso "${OUTPUT_PATH}"
diff --git a/configs/hosts b/configs/hosts
index 0a03ea6c..9d85f80a 100644
--- a/configs/hosts
+++ b/configs/hosts
@@ -1,14 +1,16 @@
-10.10.10.1 phobos
+10.10.10.1 deimos
172.17.0.2 admin
-172.17.0.3 backend
+172.17.0.3 checker
172.17.0.4 db
-172.17.0.5 sshd
+172.17.0.5 generator
+172.17.0.6 qa
-10.10.10.2 deimos
+10.10.10.2 phobos
172.17.1.2 nginx
-172.17.1.3 frontend
+172.17.1.3 receiver
+172.17.1.4 auth
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
diff --git a/configs/linux-kernel.config b/configs/linux-kernel.config
new file mode 100644
index 00000000..c9e03c07
--- /dev/null
+++ b/configs/linux-kernel.config
@@ -0,0 +1,4237 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Linux/x86 5.10.62 Kernel Configuration
+#
+CONFIG_CC_VERSION_TEXT="gcc (Alpine 10.2.1_pre1) 10.2.1 20201203"
+CONFIG_CC_IS_GCC=y
+CONFIG_GCC_VERSION=100201
+CONFIG_LD_VERSION=235020000
+CONFIG_CLANG_VERSION=0
+CONFIG_LLD_VERSION=0
+CONFIG_CC_CAN_LINK=y
+CONFIG_CC_CAN_LINK_STATIC=y
+CONFIG_CC_HAS_ASM_GOTO=y
+CONFIG_CC_HAS_ASM_INLINE=y
+CONFIG_IRQ_WORK=y
+CONFIG_BUILDTIME_TABLE_SORT=y
+CONFIG_THREAD_INFO_IN_TASK=y
+
+#
+# General setup
+#
+CONFIG_INIT_ENV_ARG_LIMIT=32
+# CONFIG_COMPILE_TEST is not set
+CONFIG_LOCALVERSION="-nemufic"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_BUILD_SALT=""
+CONFIG_HAVE_KERNEL_GZIP=y
+CONFIG_HAVE_KERNEL_BZIP2=y
+CONFIG_HAVE_KERNEL_LZMA=y
+CONFIG_HAVE_KERNEL_XZ=y
+CONFIG_HAVE_KERNEL_LZO=y
+CONFIG_HAVE_KERNEL_LZ4=y
+CONFIG_HAVE_KERNEL_ZSTD=y
+# CONFIG_KERNEL_GZIP is not set
+# CONFIG_KERNEL_BZIP2 is not set
+# CONFIG_KERNEL_LZMA is not set
+CONFIG_KERNEL_XZ=y
+# CONFIG_KERNEL_LZO is not set
+# CONFIG_KERNEL_LZ4 is not set
+# CONFIG_KERNEL_ZSTD is not set
+CONFIG_DEFAULT_INIT=""
+CONFIG_DEFAULT_HOSTNAME="(none)"
+CONFIG_SWAP=y
+CONFIG_SYSVIPC=y
+CONFIG_SYSVIPC_SYSCTL=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_POSIX_MQUEUE_SYSCTL=y
+# CONFIG_WATCH_QUEUE is not set
+CONFIG_CROSS_MEMORY_ATTACH=y
+# CONFIG_USELIB is not set
+CONFIG_AUDIT=y
+CONFIG_HAVE_ARCH_AUDITSYSCALL=y
+CONFIG_AUDITSYSCALL=y
+
+#
+# IRQ subsystem
+#
+CONFIG_GENERIC_IRQ_PROBE=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_PENDING_IRQ=y
+CONFIG_GENERIC_IRQ_MIGRATION=y
+CONFIG_HARDIRQS_SW_RESEND=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
+CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
+CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_SPARSE_IRQ=y
+# CONFIG_GENERIC_IRQ_DEBUGFS is not set
+# end of IRQ subsystem
+
+CONFIG_CLOCKSOURCE_WATCHDOG=y
+CONFIG_ARCH_CLOCKSOURCE_INIT=y
+CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
+CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y
+CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y
+
+#
+# Timers subsystem
+#
+CONFIG_TICK_ONESHOT=y
+CONFIG_NO_HZ_COMMON=y
+# CONFIG_HZ_PERIODIC is not set
+CONFIG_NO_HZ_IDLE=y
+# CONFIG_NO_HZ_FULL is not set
+# CONFIG_NO_HZ is not set
+CONFIG_HIGH_RES_TIMERS=y
+# end of Timers subsystem
+
+CONFIG_PREEMPT_NONE=y
+# CONFIG_PREEMPT_VOLUNTARY is not set
+# CONFIG_PREEMPT is not set
+
+#
+# CPU/Task time and stats accounting
+#
+CONFIG_TICK_CPU_ACCOUNTING=y
+# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set
+# CONFIG_IRQ_TIME_ACCOUNTING is not set
+# CONFIG_BSD_PROCESS_ACCT is not set
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+# CONFIG_PSI is not set
+# end of CPU/Task time and stats accounting
+
+CONFIG_CPU_ISOLATION=y
+
+#
+# RCU Subsystem
+#
+CONFIG_TREE_RCU=y
+# CONFIG_RCU_EXPERT is not set
+CONFIG_SRCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_TASKS_RCU_GENERIC=y
+CONFIG_TASKS_RUDE_RCU=y
+CONFIG_RCU_STALL_COMMON=y
+CONFIG_RCU_NEED_SEGCBLIST=y
+# end of RCU Subsystem
+
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+# CONFIG_IKHEADERS is not set
+CONFIG_LOG_BUF_SHIFT=17
+CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
+CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
+CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
+
+#
+# Scheduler features
+#
+# CONFIG_UCLAMP_TASK is not set
+# end of Scheduler features
+
+CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
+CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
+CONFIG_CC_HAS_INT128=y
+CONFIG_ARCH_SUPPORTS_INT128=y
+CONFIG_CGROUPS=y
+CONFIG_PAGE_COUNTER=y
+CONFIG_MEMCG=y
+CONFIG_MEMCG_SWAP=y
+CONFIG_MEMCG_KMEM=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CGROUP_WRITEBACK=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_FAIR_GROUP_SCHED=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_RT_GROUP_SCHED=y
+CONFIG_CGROUP_PIDS=y
+# CONFIG_CGROUP_RDMA is not set
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+CONFIG_PROC_PID_CPUSET=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+# CONFIG_CGROUP_DEBUG is not set
+CONFIG_SOCK_CGROUP_DATA=y
+CONFIG_NAMESPACES=y
+CONFIG_UTS_NS=y
+CONFIG_TIME_NS=y
+CONFIG_IPC_NS=y
+CONFIG_USER_NS=y
+CONFIG_PID_NS=y
+CONFIG_NET_NS=y
+# CONFIG_CHECKPOINT_RESTORE is not set
+CONFIG_SCHED_AUTOGROUP=y
+# CONFIG_SYSFS_DEPRECATED is not set
+CONFIG_RELAY=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_RD_GZIP=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_XZ is not set
+# CONFIG_RD_LZO is not set
+# CONFIG_RD_LZ4 is not set
+CONFIG_RD_ZSTD=y
+# CONFIG_BOOT_CONFIG is not set
+# CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE is not set
+CONFIG_CC_OPTIMIZE_FOR_SIZE=y
+CONFIG_LD_ORPHAN_WARN=y
+CONFIG_SYSCTL=y
+CONFIG_HAVE_UID16=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_HAVE_PCSPKR_PLATFORM=y
+CONFIG_BPF=y
+# CONFIG_EXPERT is not set
+CONFIG_UID16=y
+CONFIG_MULTIUSER=y
+CONFIG_SGETMASK_SYSCALL=y
+CONFIG_SYSFS_SYSCALL=y
+CONFIG_FHANDLE=y
+CONFIG_POSIX_TIMERS=y
+CONFIG_PRINTK=y
+CONFIG_PRINTK_NMI=y
+CONFIG_BUG=y
+CONFIG_ELF_CORE=y
+CONFIG_PCSPKR_PLATFORM=y
+CONFIG_BASE_FULL=y
+CONFIG_FUTEX=y
+CONFIG_FUTEX_PI=y
+CONFIG_EPOLL=y
+CONFIG_SIGNALFD=y
+CONFIG_TIMERFD=y
+CONFIG_EVENTFD=y
+CONFIG_SHMEM=y
+CONFIG_AIO=y
+CONFIG_IO_URING=y
+CONFIG_ADVISE_SYSCALLS=y
+CONFIG_MEMBARRIER=y
+CONFIG_KALLSYMS=y
+# CONFIG_KALLSYMS_ALL is not set
+CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y
+CONFIG_KALLSYMS_BASE_RELATIVE=y
+# CONFIG_BPF_SYSCALL is not set
+CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
+# CONFIG_USERFAULTFD is not set
+CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
+CONFIG_KCMP=y
+CONFIG_RSEQ=y
+# CONFIG_EMBEDDED is not set
+CONFIG_HAVE_PERF_EVENTS=y
+
+#
+# Kernel Performance Events And Counters
+#
+CONFIG_PERF_EVENTS=y
+# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
+# end of Kernel Performance Events And Counters
+
+CONFIG_VM_EVENT_COUNTERS=y
+CONFIG_SLUB_DEBUG=y
+# CONFIG_COMPAT_BRK is not set
+# CONFIG_SLAB is not set
+CONFIG_SLUB=y
+CONFIG_SLAB_MERGE_DEFAULT=y
+CONFIG_SLAB_FREELIST_RANDOM=y
+CONFIG_SLAB_FREELIST_HARDENED=y
+CONFIG_SHUFFLE_PAGE_ALLOCATOR=y
+CONFIG_SLUB_CPU_PARTIAL=y
+# CONFIG_PROFILING is not set
+CONFIG_TRACEPOINTS=y
+# end of General setup
+
+CONFIG_64BIT=y
+CONFIG_X86_64=y
+CONFIG_X86=y
+CONFIG_INSTRUCTION_DECODER=y
+CONFIG_OUTPUT_FORMAT="elf64-x86-64"
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_STACKTRACE_SUPPORT=y
+CONFIG_MMU=y
+CONFIG_ARCH_MMAP_RND_BITS_MIN=28
+CONFIG_ARCH_MMAP_RND_BITS_MAX=32
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=8
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16
+CONFIG_GENERIC_ISA_DMA=y
+CONFIG_GENERIC_BUG=y
+CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y
+CONFIG_ARCH_MAY_HAVE_PC_FDC=y
+CONFIG_GENERIC_CALIBRATE_DELAY=y
+CONFIG_ARCH_HAS_CPU_RELAX=y
+CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y
+CONFIG_ARCH_HAS_FILTER_PGPROT=y
+CONFIG_HAVE_SETUP_PER_CPU_AREA=y
+CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
+CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
+CONFIG_ZONE_DMA32=y
+CONFIG_AUDIT_ARCH=y
+CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y
+CONFIG_X86_64_SMP=y
+CONFIG_ARCH_SUPPORTS_UPROBES=y
+CONFIG_FIX_EARLYCON_MEM=y
+CONFIG_PGTABLE_LEVELS=5
+CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
+
+#
+# Processor type and features
+#
+CONFIG_ZONE_DMA=y
+CONFIG_SMP=y
+CONFIG_X86_FEATURE_NAMES=y
+CONFIG_X86_MPPARSE=y
+# CONFIG_GOLDFISH is not set
+CONFIG_RETPOLINE=y
+# CONFIG_X86_CPU_RESCTRL is not set
+# CONFIG_X86_EXTENDED_PLATFORM is not set
+# CONFIG_X86_INTEL_LPSS is not set
+# CONFIG_X86_AMD_PLATFORM_DEVICE is not set
+CONFIG_IOSF_MBI=y
+# CONFIG_IOSF_MBI_DEBUG is not set
+CONFIG_SCHED_OMIT_FRAME_POINTER=y
+# CONFIG_HYPERVISOR_GUEST is not set
+# CONFIG_MK8 is not set
+# CONFIG_MPSC is not set
+CONFIG_MCORE2=y
+# CONFIG_MATOM is not set
+# CONFIG_GENERIC_CPU is not set
+CONFIG_X86_INTERNODE_CACHE_SHIFT=6
+CONFIG_X86_L1_CACHE_SHIFT=6
+CONFIG_X86_INTEL_USERCOPY=y
+CONFIG_X86_USE_PPRO_CHECKSUM=y
+CONFIG_X86_P6_NOP=y
+CONFIG_X86_TSC=y
+CONFIG_X86_CMPXCHG64=y
+CONFIG_X86_CMOV=y
+CONFIG_X86_MINIMUM_CPU_FAMILY=64
+CONFIG_X86_DEBUGCTLMSR=y
+CONFIG_IA32_FEAT_CTL=y
+CONFIG_X86_VMX_FEATURE_NAMES=y
+CONFIG_CPU_SUP_INTEL=y
+CONFIG_CPU_SUP_AMD=y
+CONFIG_CPU_SUP_HYGON=y
+CONFIG_CPU_SUP_CENTAUR=y
+CONFIG_CPU_SUP_ZHAOXIN=y
+CONFIG_HPET_TIMER=y
+CONFIG_HPET_EMULATE_RTC=y
+CONFIG_DMI=y
+# CONFIG_GART_IOMMU is not set
+# CONFIG_MAXSMP is not set
+CONFIG_NR_CPUS_RANGE_BEGIN=2
+CONFIG_NR_CPUS_RANGE_END=512
+CONFIG_NR_CPUS_DEFAULT=64
+CONFIG_NR_CPUS=8
+CONFIG_SCHED_SMT=y
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_MC_PRIO=y
+CONFIG_X86_LOCAL_APIC=y
+CONFIG_X86_IO_APIC=y
+CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y
+# CONFIG_X86_MCE is not set
+
+#
+# Performance monitoring
+#
+CONFIG_PERF_EVENTS_INTEL_UNCORE=y
+CONFIG_PERF_EVENTS_INTEL_RAPL=y
+CONFIG_PERF_EVENTS_INTEL_CSTATE=y
+# CONFIG_PERF_EVENTS_AMD_POWER is not set
+# end of Performance monitoring
+
+CONFIG_X86_16BIT=y
+CONFIG_X86_ESPFIX64=y
+CONFIG_X86_VSYSCALL_EMULATION=y
+CONFIG_X86_IOPL_IOPERM=y
+# CONFIG_I8K is not set
+CONFIG_MICROCODE=y
+CONFIG_MICROCODE_INTEL=y
+# CONFIG_MICROCODE_AMD is not set
+CONFIG_MICROCODE_OLD_INTERFACE=y
+CONFIG_X86_MSR=y
+CONFIG_X86_CPUID=y
+CONFIG_X86_5LEVEL=y
+CONFIG_X86_DIRECT_GBPAGES=y
+# CONFIG_X86_CPA_STATISTICS is not set
+# CONFIG_AMD_MEM_ENCRYPT is not set
+# CONFIG_NUMA is not set
+CONFIG_ARCH_SPARSEMEM_ENABLE=y
+CONFIG_ARCH_SPARSEMEM_DEFAULT=y
+CONFIG_ARCH_SELECT_MEMORY_MODEL=y
+CONFIG_ARCH_PROC_KCORE_TEXT=y
+CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
+# CONFIG_X86_PMEM_LEGACY is not set
+# CONFIG_X86_CHECK_BIOS_CORRUPTION is not set
+CONFIG_X86_RESERVE_LOW=64
+CONFIG_MTRR=y
+CONFIG_MTRR_SANITIZER=y
+CONFIG_MTRR_SANITIZER_ENABLE_DEFAULT=0
+CONFIG_MTRR_SANITIZER_SPARE_REG_NR_DEFAULT=1
+CONFIG_X86_PAT=y
+CONFIG_ARCH_USES_PG_UNCACHED=y
+CONFIG_ARCH_RANDOM=y
+CONFIG_X86_SMAP=y
+CONFIG_X86_UMIP=y
+CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS=y
+CONFIG_X86_INTEL_TSX_MODE_OFF=y
+# CONFIG_X86_INTEL_TSX_MODE_ON is not set
+# CONFIG_X86_INTEL_TSX_MODE_AUTO is not set
+CONFIG_EFI=y
+CONFIG_EFI_STUB=y
+# CONFIG_EFI_MIXED is not set
+CONFIG_HZ_100=y
+# CONFIG_HZ_250 is not set
+# CONFIG_HZ_300 is not set
+# CONFIG_HZ_1000 is not set
+CONFIG_HZ=100
+CONFIG_SCHED_HRTICK=y
+# CONFIG_KEXEC is not set
+# CONFIG_KEXEC_FILE is not set
+# CONFIG_CRASH_DUMP is not set
+CONFIG_PHYSICAL_START=0x1000000
+CONFIG_RELOCATABLE=y
+CONFIG_RANDOMIZE_BASE=y
+CONFIG_X86_NEED_RELOCS=y
+CONFIG_PHYSICAL_ALIGN=0x1000000
+CONFIG_DYNAMIC_MEMORY_LAYOUT=y
+CONFIG_RANDOMIZE_MEMORY=y
+CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0x0
+CONFIG_HOTPLUG_CPU=y
+# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
+# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
+# CONFIG_COMPAT_VDSO is not set
+# CONFIG_LEGACY_VSYSCALL_EMULATE is not set
+# CONFIG_LEGACY_VSYSCALL_XONLY is not set
+CONFIG_LEGACY_VSYSCALL_NONE=y
+# CONFIG_CMDLINE_BOOL is not set
+CONFIG_MODIFY_LDT_SYSCALL=y
+CONFIG_HAVE_LIVEPATCH=y
+# end of Processor type and features
+
+CONFIG_ARCH_HAS_ADD_PAGES=y
+CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y
+CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y
+CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y
+CONFIG_ARCH_ENABLE_THP_MIGRATION=y
+
+#
+# Power management and ACPI options
+#
+# CONFIG_SUSPEND is not set
+# CONFIG_HIBERNATION is not set
+CONFIG_PM=y
+# CONFIG_PM_DEBUG is not set
+CONFIG_PM_CLK=y
+# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set
+# CONFIG_ENERGY_MODEL is not set
+CONFIG_ARCH_SUPPORTS_ACPI=y
+CONFIG_ACPI=y
+CONFIG_ACPI_LEGACY_TABLES_LOOKUP=y
+CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC=y
+CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT=y
+# CONFIG_ACPI_DEBUGGER is not set
+CONFIG_ACPI_SPCR_TABLE=y
+CONFIG_ACPI_LPIT=y
+# CONFIG_ACPI_REV_OVERRIDE_POSSIBLE is not set
+# CONFIG_ACPI_EC_DEBUGFS is not set
+CONFIG_ACPI_AC=y
+CONFIG_ACPI_BATTERY=y
+CONFIG_ACPI_BUTTON=y
+CONFIG_ACPI_VIDEO=y
+CONFIG_ACPI_FAN=y
+CONFIG_ACPI_DOCK=y
+CONFIG_ACPI_CPU_FREQ_PSS=y
+CONFIG_ACPI_PROCESSOR_CSTATE=y
+CONFIG_ACPI_PROCESSOR_IDLE=y
+CONFIG_ACPI_CPPC_LIB=y
+CONFIG_ACPI_PROCESSOR=y
+CONFIG_ACPI_HOTPLUG_CPU=y
+CONFIG_ACPI_PROCESSOR_AGGREGATOR=y
+CONFIG_ACPI_THERMAL=y
+CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
+CONFIG_ACPI_TABLE_UPGRADE=y
+# CONFIG_ACPI_DEBUG is not set
+# CONFIG_ACPI_PCI_SLOT is not set
+CONFIG_ACPI_CONTAINER=y
+CONFIG_ACPI_HOTPLUG_IOAPIC=y
+CONFIG_ACPI_SBS=y
+CONFIG_ACPI_HED=y
+# CONFIG_ACPI_CUSTOM_METHOD is not set
+# CONFIG_ACPI_BGRT is not set
+# CONFIG_ACPI_NFIT is not set
+CONFIG_HAVE_ACPI_APEI=y
+CONFIG_HAVE_ACPI_APEI_NMI=y
+CONFIG_ACPI_APEI=y
+CONFIG_ACPI_APEI_GHES=y
+# CONFIG_ACPI_APEI_EINJ is not set
+# CONFIG_ACPI_APEI_ERST_DEBUG is not set
+# CONFIG_ACPI_DPTF is not set
+# CONFIG_ACPI_CONFIGFS is not set
+# CONFIG_PMIC_OPREGION is not set
+CONFIG_X86_PM_TIMER=y
+# CONFIG_SFI is not set
+
+#
+# CPU Frequency scaling
+#
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_GOV_ATTR_SET=y
+# CONFIG_CPU_FREQ_STAT is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set
+# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set
+CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
+# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
+# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
+# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+
+#
+# CPU frequency scaling drivers
+#
+CONFIG_X86_INTEL_PSTATE=y
+CONFIG_X86_PCC_CPUFREQ=y
+CONFIG_X86_ACPI_CPUFREQ=y
+# CONFIG_X86_ACPI_CPUFREQ_CPB is not set
+# CONFIG_X86_POWERNOW_K8 is not set
+# CONFIG_X86_SPEEDSTEP_CENTRINO is not set
+# CONFIG_X86_P4_CLOCKMOD is not set
+
+#
+# shared options
+#
+# end of CPU Frequency scaling
+
+#
+# CPU Idle
+#
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_IDLE_GOV_LADDER=y
+CONFIG_CPU_IDLE_GOV_MENU=y
+# CONFIG_CPU_IDLE_GOV_TEO is not set
+# end of CPU Idle
+
+CONFIG_INTEL_IDLE=y
+# end of Power management and ACPI options
+
+#
+# Bus options (PCI etc.)
+#
+CONFIG_PCI_DIRECT=y
+CONFIG_PCI_MMCONFIG=y
+CONFIG_MMCONF_FAM10H=y
+CONFIG_ISA_DMA_API=y
+CONFIG_AMD_NB=y
+# CONFIG_X86_SYSFB is not set
+# end of Bus options (PCI etc.)
+
+#
+# Binary Emulations
+#
+CONFIG_IA32_EMULATION=y
+# CONFIG_X86_X32 is not set
+CONFIG_COMPAT_32=y
+CONFIG_COMPAT=y
+CONFIG_COMPAT_FOR_U64_ALIGNMENT=y
+CONFIG_SYSVIPC_COMPAT=y
+# end of Binary Emulations
+
+#
+# Firmware Drivers
+#
+# CONFIG_EDD is not set
+CONFIG_FIRMWARE_MEMMAP=y
+CONFIG_DMIID=y
+CONFIG_DMI_SYSFS=y
+CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
+# CONFIG_FW_CFG_SYSFS is not set
+# CONFIG_GOOGLE_FIRMWARE is not set
+
+#
+# EFI (Extensible Firmware Interface) Support
+#
+CONFIG_EFI_VARS=y
+CONFIG_EFI_ESRT=y
+CONFIG_EFI_VARS_PSTORE=y
+# CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE is not set
+# CONFIG_EFI_FAKE_MEMMAP is not set
+CONFIG_EFI_RUNTIME_WRAPPERS=y
+CONFIG_EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER=y
+# CONFIG_EFI_BOOTLOADER_CONTROL is not set
+# CONFIG_EFI_CAPSULE_LOADER is not set
+# CONFIG_EFI_TEST is not set
+# CONFIG_APPLE_PROPERTIES is not set
+# CONFIG_RESET_ATTACK_MITIGATION is not set
+# CONFIG_EFI_RCI2_TABLE is not set
+# CONFIG_EFI_DISABLE_PCI_DMA is not set
+# end of EFI (Extensible Firmware Interface) Support
+
+CONFIG_UEFI_CPER=y
+CONFIG_UEFI_CPER_X86=y
+CONFIG_EFI_EARLYCON=y
+CONFIG_EFI_CUSTOM_SSDT_OVERLAYS=y
+
+#
+# Tegra firmware driver
+#
+# end of Tegra firmware driver
+# end of Firmware Drivers
+
+CONFIG_HAVE_KVM=y
+# CONFIG_VIRTUALIZATION is not set
+CONFIG_AS_AVX512=y
+CONFIG_AS_SHA1_NI=y
+CONFIG_AS_SHA256_NI=y
+CONFIG_AS_TPAUSE=y
+
+#
+# General architecture-dependent options
+#
+CONFIG_CRASH_CORE=y
+CONFIG_HOTPLUG_SMT=y
+CONFIG_GENERIC_ENTRY=y
+CONFIG_HAVE_OPROFILE=y
+CONFIG_OPROFILE_NMI_TIMER=y
+# CONFIG_KPROBES is not set
+# CONFIG_JUMP_LABEL is not set
+# CONFIG_STATIC_CALL_SELFTEST is not set
+CONFIG_UPROBES=y
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
+CONFIG_ARCH_USE_BUILTIN_BSWAP=y
+CONFIG_HAVE_IOREMAP_PROT=y
+CONFIG_HAVE_KPROBES=y
+CONFIG_HAVE_KRETPROBES=y
+CONFIG_HAVE_OPTPROBES=y
+CONFIG_HAVE_KPROBES_ON_FTRACE=y
+CONFIG_HAVE_FUNCTION_ERROR_INJECTION=y
+CONFIG_HAVE_NMI=y
+CONFIG_HAVE_ARCH_TRACEHOOK=y
+CONFIG_HAVE_DMA_CONTIGUOUS=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_ARCH_HAS_FORTIFY_SOURCE=y
+CONFIG_ARCH_HAS_SET_MEMORY=y
+CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
+CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
+CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
+CONFIG_HAVE_ASM_MODVERSIONS=y
+CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
+CONFIG_HAVE_RSEQ=y
+CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
+CONFIG_HAVE_HW_BREAKPOINT=y
+CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y
+CONFIG_HAVE_USER_RETURN_NOTIFIER=y
+CONFIG_HAVE_PERF_EVENTS_NMI=y
+CONFIG_HAVE_HARDLOCKUP_DETECTOR_PERF=y
+CONFIG_HAVE_PERF_REGS=y
+CONFIG_HAVE_PERF_USER_STACK_DUMP=y
+CONFIG_HAVE_ARCH_JUMP_LABEL=y
+CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y
+CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y
+CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y
+CONFIG_HAVE_CMPXCHG_LOCAL=y
+CONFIG_HAVE_CMPXCHG_DOUBLE=y
+CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION=y
+CONFIG_ARCH_WANT_OLD_COMPAT_IPC=y
+CONFIG_HAVE_ARCH_SECCOMP=y
+CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
+CONFIG_SECCOMP=y
+CONFIG_SECCOMP_FILTER=y
+CONFIG_HAVE_ARCH_STACKLEAK=y
+CONFIG_HAVE_STACKPROTECTOR=y
+CONFIG_STACKPROTECTOR=y
+CONFIG_STACKPROTECTOR_STRONG=y
+CONFIG_HAVE_ARCH_WITHIN_STACK_FRAMES=y
+CONFIG_HAVE_CONTEXT_TRACKING=y
+CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y
+CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y
+CONFIG_HAVE_MOVE_PMD=y
+CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
+CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
+CONFIG_HAVE_ARCH_HUGE_VMAP=y
+CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
+CONFIG_HAVE_ARCH_SOFT_DIRTY=y
+CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
+CONFIG_MODULES_USE_ELF_RELA=y
+CONFIG_ARCH_HAS_ELF_RANDOMIZE=y
+CONFIG_HAVE_ARCH_MMAP_RND_BITS=y
+CONFIG_HAVE_EXIT_THREAD=y
+CONFIG_ARCH_MMAP_RND_BITS=28
+CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS=8
+CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES=y
+CONFIG_HAVE_STACK_VALIDATION=y
+CONFIG_HAVE_RELIABLE_STACKTRACE=y
+CONFIG_OLD_SIGSUSPEND3=y
+CONFIG_COMPAT_OLD_SIGACTION=y
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_HAVE_ARCH_VMAP_STACK=y
+CONFIG_VMAP_STACK=y
+CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
+CONFIG_STRICT_KERNEL_RWX=y
+CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y
+CONFIG_STRICT_MODULE_RWX=y
+CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y
+CONFIG_ARCH_USE_MEMREMAP_PROT=y
+# CONFIG_LOCK_EVENT_COUNTS is not set
+CONFIG_ARCH_HAS_MEM_ENCRYPT=y
+CONFIG_HAVE_STATIC_CALL=y
+CONFIG_HAVE_STATIC_CALL_INLINE=y
+CONFIG_ARCH_WANT_LD_ORPHAN_WARN=y
+
+#
+# GCOV-based kernel profiling
+#
+# CONFIG_GCOV_KERNEL is not set
+CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y
+# end of GCOV-based kernel profiling
+
+CONFIG_HAVE_GCC_PLUGINS=y
+# CONFIG_GCC_PLUGINS is not set
+# end of General architecture-dependent options
+
+CONFIG_RT_MUTEXES=y
+CONFIG_BASE_SMALL=0
+CONFIG_MODULES=y
+# CONFIG_MODULE_FORCE_LOAD is not set
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_MODULE_FORCE_UNLOAD is not set
+# CONFIG_MODVERSIONS is not set
+# CONFIG_MODULE_SRCVERSION_ALL is not set
+# CONFIG_MODULE_SIG is not set
+# CONFIG_MODULE_COMPRESS is not set
+# CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS is not set
+# CONFIG_UNUSED_SYMBOLS is not set
+# CONFIG_TRIM_UNUSED_KSYMS is not set
+CONFIG_MODULES_TREE_LOOKUP=y
+CONFIG_BLOCK=y
+CONFIG_BLK_SCSI_REQUEST=y
+CONFIG_BLK_CGROUP_RWSTAT=y
+CONFIG_BLK_DEV_BSG=y
+CONFIG_BLK_DEV_BSGLIB=y
+CONFIG_BLK_DEV_INTEGRITY=y
+CONFIG_BLK_DEV_INTEGRITY_T10=y
+# CONFIG_BLK_DEV_ZONED is not set
+CONFIG_BLK_DEV_THROTTLING=y
+# CONFIG_BLK_DEV_THROTTLING_LOW is not set
+# CONFIG_BLK_CMDLINE_PARSER is not set
+# CONFIG_BLK_WBT is not set
+# CONFIG_BLK_CGROUP_IOLATENCY is not set
+# CONFIG_BLK_CGROUP_IOCOST is not set
+CONFIG_BLK_DEBUG_FS=y
+# CONFIG_BLK_SED_OPAL is not set
+# CONFIG_BLK_INLINE_ENCRYPTION is not set
+
+#
+# Partition Types
+#
+CONFIG_PARTITION_ADVANCED=y
+# CONFIG_ACORN_PARTITION is not set
+# CONFIG_AIX_PARTITION is not set
+# CONFIG_OSF_PARTITION is not set
+# CONFIG_AMIGA_PARTITION is not set
+# CONFIG_ATARI_PARTITION is not set
+# CONFIG_MAC_PARTITION is not set
+CONFIG_MSDOS_PARTITION=y
+# CONFIG_BSD_DISKLABEL is not set
+# CONFIG_MINIX_SUBPARTITION is not set
+# CONFIG_SOLARIS_X86_PARTITION is not set
+# CONFIG_UNIXWARE_DISKLABEL is not set
+# CONFIG_LDM_PARTITION is not set
+# CONFIG_SGI_PARTITION is not set
+# CONFIG_ULTRIX_PARTITION is not set
+# CONFIG_SUN_PARTITION is not set
+# CONFIG_KARMA_PARTITION is not set
+CONFIG_EFI_PARTITION=y
+# CONFIG_SYSV68_PARTITION is not set
+# CONFIG_CMDLINE_PARTITION is not set
+# end of Partition Types
+
+CONFIG_BLOCK_COMPAT=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BLK_PM=y
+
+#
+# IO Schedulers
+#
+CONFIG_MQ_IOSCHED_DEADLINE=y
+# CONFIG_MQ_IOSCHED_KYBER is not set
+# CONFIG_IOSCHED_BFQ is not set
+# end of IO Schedulers
+
+CONFIG_ASN1=y
+CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
+CONFIG_INLINE_READ_UNLOCK=y
+CONFIG_INLINE_READ_UNLOCK_IRQ=y
+CONFIG_INLINE_WRITE_UNLOCK=y
+CONFIG_INLINE_WRITE_UNLOCK_IRQ=y
+CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y
+CONFIG_MUTEX_SPIN_ON_OWNER=y
+CONFIG_RWSEM_SPIN_ON_OWNER=y
+CONFIG_LOCK_SPIN_ON_OWNER=y
+CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_ARCH_USE_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y
+CONFIG_ARCH_HAS_SYNC_CORE_BEFORE_USERMODE=y
+CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y
+CONFIG_FREEZER=y
+
+#
+# Executable file formats
+#
+CONFIG_BINFMT_ELF=y
+CONFIG_COMPAT_BINFMT_ELF=y
+CONFIG_ELFCORE=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+CONFIG_BINFMT_SCRIPT=y
+CONFIG_BINFMT_MISC=y
+CONFIG_COREDUMP=y
+# end of Executable file formats
+
+#
+# Memory Management options
+#
+CONFIG_SELECT_MEMORY_MODEL=y
+CONFIG_SPARSEMEM_MANUAL=y
+CONFIG_SPARSEMEM=y
+CONFIG_SPARSEMEM_EXTREME=y
+CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
+CONFIG_SPARSEMEM_VMEMMAP=y
+CONFIG_HAVE_FAST_GUP=y
+# CONFIG_MEMORY_HOTPLUG is not set
+CONFIG_SPLIT_PTLOCK_CPUS=4
+CONFIG_COMPACTION=y
+CONFIG_PAGE_REPORTING=y
+CONFIG_MIGRATION=y
+CONFIG_PHYS_ADDR_T_64BIT=y
+CONFIG_BOUNCE=y
+CONFIG_VIRT_TO_BUS=y
+CONFIG_MMU_NOTIFIER=y
+CONFIG_KSM=y
+CONFIG_DEFAULT_MMAP_MIN_ADDR=65536
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
+# CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set
+CONFIG_ARCH_WANTS_THP_SWAP=y
+CONFIG_THP_SWAP=y
+# CONFIG_CLEANCACHE is not set
+# CONFIG_FRONTSWAP is not set
+# CONFIG_CMA is not set
+# CONFIG_ZPOOL is not set
+# CONFIG_ZBUD is not set
+# CONFIG_ZSMALLOC is not set
+CONFIG_GENERIC_EARLY_IOREMAP=y
+# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
+# CONFIG_IDLE_PAGE_TRACKING is not set
+CONFIG_ARCH_HAS_PTE_DEVMAP=y
+CONFIG_VMAP_PFN=y
+CONFIG_ARCH_USES_HIGH_VMA_FLAGS=y
+CONFIG_ARCH_HAS_PKEYS=y
+# CONFIG_PERCPU_STATS is not set
+# CONFIG_GUP_BENCHMARK is not set
+# CONFIG_READ_ONLY_THP_FOR_FS is not set
+CONFIG_ARCH_HAS_PTE_SPECIAL=y
+# end of Memory Management options
+
+CONFIG_NET=y
+CONFIG_NET_INGRESS=y
+CONFIG_SKB_EXTENSIONS=y
+
+#
+# Networking options
+#
+CONFIG_PACKET=y
+CONFIG_PACKET_DIAG=y
+CONFIG_UNIX=y
+CONFIG_UNIX_SCM=y
+CONFIG_UNIX_DIAG=y
+# CONFIG_TLS is not set
+# CONFIG_XFRM_USER is not set
+# CONFIG_NET_KEY is not set
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+# CONFIG_IP_ADVANCED_ROUTER is not set
+CONFIG_IP_ROUTE_CLASSID=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+# CONFIG_IP_PNP_BOOTP is not set
+# CONFIG_IP_PNP_RARP is not set
+# CONFIG_NET_IPIP is not set
+# CONFIG_NET_IPGRE_DEMUX is not set
+# CONFIG_IP_MROUTE is not set
+CONFIG_SYN_COOKIES=y
+# CONFIG_NET_IPVTI is not set
+# CONFIG_NET_FOU is not set
+# CONFIG_INET_AH is not set
+# CONFIG_INET_ESP is not set
+# CONFIG_INET_IPCOMP is not set
+# CONFIG_INET_DIAG is not set
+# CONFIG_TCP_CONG_ADVANCED is not set
+CONFIG_TCP_CONG_CUBIC=y
+CONFIG_DEFAULT_TCP_CONG="cubic"
+# CONFIG_TCP_MD5SIG is not set
+CONFIG_IPV6=y
+# CONFIG_IPV6_ROUTER_PREF is not set
+# CONFIG_IPV6_OPTIMISTIC_DAD is not set
+# CONFIG_INET6_AH is not set
+# CONFIG_INET6_ESP is not set
+# CONFIG_INET6_IPCOMP is not set
+# CONFIG_IPV6_MIP6 is not set
+# CONFIG_IPV6_ILA is not set
+# CONFIG_IPV6_VTI is not set
+# CONFIG_IPV6_SIT is not set
+# CONFIG_IPV6_TUNNEL is not set
+# CONFIG_IPV6_MULTIPLE_TABLES is not set
+# CONFIG_IPV6_MROUTE is not set
+# CONFIG_IPV6_SEG6_LWTUNNEL is not set
+# CONFIG_IPV6_SEG6_HMAC is not set
+# CONFIG_IPV6_RPL_LWTUNNEL is not set
+CONFIG_NETLABEL=y
+# CONFIG_MPTCP is not set
+CONFIG_NETWORK_SECMARK=y
+CONFIG_NET_PTP_CLASSIFY=y
+# CONFIG_NETWORK_PHY_TIMESTAMPING is not set
+CONFIG_NETFILTER=y
+CONFIG_NETFILTER_ADVANCED=y
+CONFIG_BRIDGE_NETFILTER=y
+
+#
+# Core Netfilter Configuration
+#
+CONFIG_NETFILTER_INGRESS=y
+CONFIG_NETFILTER_NETLINK=y
+CONFIG_NETFILTER_FAMILY_BRIDGE=y
+CONFIG_NETFILTER_FAMILY_ARP=y
+CONFIG_NETFILTER_NETLINK_ACCT=y
+CONFIG_NETFILTER_NETLINK_QUEUE=y
+CONFIG_NETFILTER_NETLINK_LOG=y
+CONFIG_NETFILTER_NETLINK_OSF=y
+CONFIG_NF_CONNTRACK=y
+CONFIG_NF_LOG_COMMON=y
+# CONFIG_NF_LOG_NETDEV is not set
+CONFIG_NETFILTER_CONNCOUNT=y
+CONFIG_NF_CONNTRACK_MARK=y
+# CONFIG_NF_CONNTRACK_SECMARK is not set
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_PROCFS=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMEOUT=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_LABELS=y
+CONFIG_NF_CT_PROTO_DCCP=y
+CONFIG_NF_CT_PROTO_GRE=y
+CONFIG_NF_CT_PROTO_SCTP=y
+CONFIG_NF_CT_PROTO_UDPLITE=y
+CONFIG_NF_CONNTRACK_AMANDA=y
+CONFIG_NF_CONNTRACK_FTP=y
+CONFIG_NF_CONNTRACK_H323=y
+CONFIG_NF_CONNTRACK_IRC=y
+CONFIG_NF_CONNTRACK_BROADCAST=y
+CONFIG_NF_CONNTRACK_NETBIOS_NS=y
+CONFIG_NF_CONNTRACK_SNMP=y
+CONFIG_NF_CONNTRACK_PPTP=y
+CONFIG_NF_CONNTRACK_SANE=y
+CONFIG_NF_CONNTRACK_SIP=y
+CONFIG_NF_CONNTRACK_TFTP=y
+CONFIG_NF_CT_NETLINK=y
+CONFIG_NF_CT_NETLINK_TIMEOUT=y
+CONFIG_NF_CT_NETLINK_HELPER=y
+CONFIG_NETFILTER_NETLINK_GLUE_CT=y
+CONFIG_NF_NAT=y
+CONFIG_NF_NAT_AMANDA=y
+CONFIG_NF_NAT_FTP=y
+CONFIG_NF_NAT_IRC=y
+CONFIG_NF_NAT_SIP=y
+CONFIG_NF_NAT_TFTP=y
+CONFIG_NF_NAT_REDIRECT=y
+CONFIG_NF_NAT_MASQUERADE=y
+CONFIG_NETFILTER_SYNPROXY=y
+CONFIG_NF_TABLES=y
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+# CONFIG_NFT_NUMGEN is not set
+CONFIG_NFT_CT=y
+CONFIG_NFT_COUNTER=y
+CONFIG_NFT_CONNLIMIT=y
+CONFIG_NFT_LOG=y
+CONFIG_NFT_LIMIT=y
+CONFIG_NFT_MASQ=y
+CONFIG_NFT_REDIR=y
+CONFIG_NFT_NAT=y
+# CONFIG_NFT_TUNNEL is not set
+# CONFIG_NFT_OBJREF is not set
+CONFIG_NFT_QUEUE=y
+# CONFIG_NFT_QUOTA is not set
+CONFIG_NFT_REJECT=y
+CONFIG_NFT_REJECT_INET=y
+CONFIG_NFT_COMPAT=y
+CONFIG_NFT_HASH=y
+# CONFIG_NFT_SOCKET is not set
+# CONFIG_NFT_OSF is not set
+# CONFIG_NFT_TPROXY is not set
+# CONFIG_NFT_SYNPROXY is not set
+CONFIG_NF_DUP_NETDEV=y
+CONFIG_NFT_DUP_NETDEV=y
+CONFIG_NFT_FWD_NETDEV=y
+# CONFIG_NF_FLOW_TABLE is not set
+CONFIG_NETFILTER_XTABLES=y
+
+#
+# Xtables combined modules
+#
+CONFIG_NETFILTER_XT_MARK=y
+CONFIG_NETFILTER_XT_CONNMARK=y
+CONFIG_NETFILTER_XT_SET=y
+
+#
+# Xtables targets
+#
+# CONFIG_NETFILTER_XT_TARGET_AUDIT is not set
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=y
+CONFIG_NETFILTER_XT_TARGET_CT=y
+CONFIG_NETFILTER_XT_TARGET_DSCP=y
+CONFIG_NETFILTER_XT_TARGET_HL=y
+CONFIG_NETFILTER_XT_TARGET_HMARK=y
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y
+CONFIG_NETFILTER_XT_TARGET_LOG=y
+CONFIG_NETFILTER_XT_TARGET_MARK=y
+CONFIG_NETFILTER_XT_NAT=y
+CONFIG_NETFILTER_XT_TARGET_NETMAP=y
+CONFIG_NETFILTER_XT_TARGET_NFLOG=y
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=y
+CONFIG_NETFILTER_XT_TARGET_RATEEST=y
+CONFIG_NETFILTER_XT_TARGET_REDIRECT=y
+CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
+CONFIG_NETFILTER_XT_TARGET_TEE=y
+CONFIG_NETFILTER_XT_TARGET_TPROXY=y
+CONFIG_NETFILTER_XT_TARGET_TRACE=y
+# CONFIG_NETFILTER_XT_TARGET_SECMARK is not set
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=y
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=y
+
+#
+# Xtables matches
+#
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
+CONFIG_NETFILTER_XT_MATCH_BPF=y
+CONFIG_NETFILTER_XT_MATCH_CGROUP=y
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=y
+CONFIG_NETFILTER_XT_MATCH_COMMENT=y
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=y
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=y
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=y
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
+CONFIG_NETFILTER_XT_MATCH_CPU=y
+CONFIG_NETFILTER_XT_MATCH_DCCP=y
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=y
+CONFIG_NETFILTER_XT_MATCH_DSCP=y
+CONFIG_NETFILTER_XT_MATCH_ECN=y
+CONFIG_NETFILTER_XT_MATCH_ESP=y
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y
+CONFIG_NETFILTER_XT_MATCH_HELPER=y
+CONFIG_NETFILTER_XT_MATCH_HL=y
+CONFIG_NETFILTER_XT_MATCH_IPCOMP=y
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=y
+CONFIG_NETFILTER_XT_MATCH_IPVS=y
+CONFIG_NETFILTER_XT_MATCH_L2TP=y
+CONFIG_NETFILTER_XT_MATCH_LENGTH=y
+CONFIG_NETFILTER_XT_MATCH_LIMIT=y
+CONFIG_NETFILTER_XT_MATCH_MAC=y
+CONFIG_NETFILTER_XT_MATCH_MARK=y
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
+CONFIG_NETFILTER_XT_MATCH_NFACCT=y
+CONFIG_NETFILTER_XT_MATCH_OSF=y
+CONFIG_NETFILTER_XT_MATCH_OWNER=y
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=y
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
+CONFIG_NETFILTER_XT_MATCH_QUOTA=y
+CONFIG_NETFILTER_XT_MATCH_RATEEST=y
+CONFIG_NETFILTER_XT_MATCH_REALM=y
+CONFIG_NETFILTER_XT_MATCH_RECENT=y
+CONFIG_NETFILTER_XT_MATCH_SCTP=y
+CONFIG_NETFILTER_XT_MATCH_SOCKET=y
+CONFIG_NETFILTER_XT_MATCH_STATE=y
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
+CONFIG_NETFILTER_XT_MATCH_STRING=y
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=y
+CONFIG_NETFILTER_XT_MATCH_TIME=y
+CONFIG_NETFILTER_XT_MATCH_U32=y
+# end of Core Netfilter Configuration
+
+CONFIG_IP_SET=y
+CONFIG_IP_SET_MAX=256
+CONFIG_IP_SET_BITMAP_IP=y
+CONFIG_IP_SET_BITMAP_IPMAC=y
+CONFIG_IP_SET_BITMAP_PORT=y
+CONFIG_IP_SET_HASH_IP=y
+# CONFIG_IP_SET_HASH_IPMARK is not set
+CONFIG_IP_SET_HASH_IPPORT=y
+CONFIG_IP_SET_HASH_IPPORTIP=y
+CONFIG_IP_SET_HASH_IPPORTNET=y
+# CONFIG_IP_SET_HASH_IPMAC is not set
+# CONFIG_IP_SET_HASH_MAC is not set
+# CONFIG_IP_SET_HASH_NETPORTNET is not set
+CONFIG_IP_SET_HASH_NET=y
+# CONFIG_IP_SET_HASH_NETNET is not set
+CONFIG_IP_SET_HASH_NETPORT=y
+CONFIG_IP_SET_HASH_NETIFACE=y
+CONFIG_IP_SET_LIST_SET=y
+CONFIG_IP_VS=y
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_DEBUG=y
+CONFIG_IP_VS_TAB_BITS=12
+
+#
+# IPVS transport protocol load balancing support
+#
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_AH_ESP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+
+#
+# IPVS scheduler
+#
+CONFIG_IP_VS_RR=y
+CONFIG_IP_VS_WRR=y
+CONFIG_IP_VS_LC=y
+CONFIG_IP_VS_WLC=y
+CONFIG_IP_VS_FO=y
+CONFIG_IP_VS_OVF=y
+CONFIG_IP_VS_LBLC=y
+CONFIG_IP_VS_LBLCR=y
+CONFIG_IP_VS_DH=y
+CONFIG_IP_VS_SH=y
+# CONFIG_IP_VS_MH is not set
+CONFIG_IP_VS_SED=y
+CONFIG_IP_VS_NQ=y
+
+#
+# IPVS SH scheduler
+#
+CONFIG_IP_VS_SH_TAB_BITS=8
+
+#
+# IPVS MH scheduler
+#
+CONFIG_IP_VS_MH_TAB_INDEX=12
+
+#
+# IPVS application helper
+#
+CONFIG_IP_VS_FTP=y
+CONFIG_IP_VS_NFCT=y
+# CONFIG_IP_VS_PE_SIP is not set
+
+#
+# IP: Netfilter Configuration
+#
+CONFIG_NF_DEFRAG_IPV4=y
+CONFIG_NF_SOCKET_IPV4=y
+CONFIG_NF_TPROXY_IPV4=y
+CONFIG_NF_TABLES_IPV4=y
+CONFIG_NFT_REJECT_IPV4=y
+CONFIG_NFT_DUP_IPV4=y
+# CONFIG_NFT_FIB_IPV4 is not set
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_DUP_IPV4=y
+CONFIG_NF_LOG_ARP=y
+CONFIG_NF_LOG_IPV4=y
+CONFIG_NF_REJECT_IPV4=y
+CONFIG_NF_NAT_SNMP_BASIC=y
+CONFIG_NF_NAT_PPTP=y
+CONFIG_NF_NAT_H323=y
+CONFIG_IP_NF_IPTABLES=y
+CONFIG_IP_NF_MATCH_AH=y
+CONFIG_IP_NF_MATCH_ECN=y
+CONFIG_IP_NF_MATCH_RPFILTER=y
+CONFIG_IP_NF_MATCH_TTL=y
+CONFIG_IP_NF_FILTER=y
+CONFIG_IP_NF_TARGET_REJECT=y
+CONFIG_IP_NF_TARGET_SYNPROXY=y
+CONFIG_IP_NF_NAT=y
+CONFIG_IP_NF_TARGET_MASQUERADE=y
+CONFIG_IP_NF_TARGET_NETMAP=y
+CONFIG_IP_NF_TARGET_REDIRECT=y
+CONFIG_IP_NF_MANGLE=y
+CONFIG_IP_NF_TARGET_CLUSTERIP=y
+CONFIG_IP_NF_TARGET_ECN=y
+CONFIG_IP_NF_TARGET_TTL=y
+CONFIG_IP_NF_RAW=y
+CONFIG_IP_NF_SECURITY=y
+CONFIG_IP_NF_ARPTABLES=y
+CONFIG_IP_NF_ARPFILTER=y
+CONFIG_IP_NF_ARP_MANGLE=y
+# end of IP: Netfilter Configuration
+
+#
+# IPv6: Netfilter Configuration
+#
+CONFIG_NF_SOCKET_IPV6=y
+CONFIG_NF_TPROXY_IPV6=y
+CONFIG_NF_TABLES_IPV6=y
+CONFIG_NFT_REJECT_IPV6=y
+CONFIG_NFT_DUP_IPV6=y
+# CONFIG_NFT_FIB_IPV6 is not set
+CONFIG_NF_DUP_IPV6=y
+CONFIG_NF_REJECT_IPV6=y
+CONFIG_NF_LOG_IPV6=y
+CONFIG_IP6_NF_IPTABLES=y
+CONFIG_IP6_NF_MATCH_AH=y
+CONFIG_IP6_NF_MATCH_EUI64=y
+CONFIG_IP6_NF_MATCH_FRAG=y
+CONFIG_IP6_NF_MATCH_OPTS=y
+CONFIG_IP6_NF_MATCH_HL=y
+CONFIG_IP6_NF_MATCH_IPV6HEADER=y
+CONFIG_IP6_NF_MATCH_MH=y
+CONFIG_IP6_NF_MATCH_RPFILTER=y
+CONFIG_IP6_NF_MATCH_RT=y
+# CONFIG_IP6_NF_MATCH_SRH is not set
+CONFIG_IP6_NF_TARGET_HL=y
+CONFIG_IP6_NF_FILTER=y
+CONFIG_IP6_NF_TARGET_REJECT=y
+CONFIG_IP6_NF_TARGET_SYNPROXY=y
+CONFIG_IP6_NF_MANGLE=y
+CONFIG_IP6_NF_RAW=y
+CONFIG_IP6_NF_SECURITY=y
+CONFIG_IP6_NF_NAT=y
+CONFIG_IP6_NF_TARGET_MASQUERADE=y
+CONFIG_IP6_NF_TARGET_NPT=y
+# end of IPv6: Netfilter Configuration
+
+CONFIG_NF_DEFRAG_IPV6=y
+CONFIG_NF_TABLES_BRIDGE=y
+CONFIG_NFT_BRIDGE_META=y
+CONFIG_NFT_BRIDGE_REJECT=y
+CONFIG_NF_LOG_BRIDGE=y
+# CONFIG_NF_CONNTRACK_BRIDGE is not set
+CONFIG_BRIDGE_NF_EBTABLES=y
+CONFIG_BRIDGE_EBT_BROUTE=y
+CONFIG_BRIDGE_EBT_T_FILTER=y
+CONFIG_BRIDGE_EBT_T_NAT=y
+CONFIG_BRIDGE_EBT_802_3=y
+CONFIG_BRIDGE_EBT_AMONG=y
+CONFIG_BRIDGE_EBT_ARP=y
+CONFIG_BRIDGE_EBT_IP=y
+CONFIG_BRIDGE_EBT_IP6=y
+CONFIG_BRIDGE_EBT_LIMIT=y
+CONFIG_BRIDGE_EBT_MARK=y
+CONFIG_BRIDGE_EBT_PKTTYPE=y
+CONFIG_BRIDGE_EBT_STP=y
+CONFIG_BRIDGE_EBT_VLAN=y
+CONFIG_BRIDGE_EBT_ARPREPLY=y
+CONFIG_BRIDGE_EBT_DNAT=y
+CONFIG_BRIDGE_EBT_MARK_T=y
+CONFIG_BRIDGE_EBT_REDIRECT=y
+CONFIG_BRIDGE_EBT_SNAT=y
+CONFIG_BRIDGE_EBT_LOG=y
+CONFIG_BRIDGE_EBT_NFLOG=y
+# CONFIG_BPFILTER is not set
+# CONFIG_IP_DCCP is not set
+# CONFIG_IP_SCTP is not set
+# CONFIG_RDS is not set
+# CONFIG_TIPC is not set
+# CONFIG_ATM is not set
+# CONFIG_L2TP is not set
+CONFIG_STP=y
+CONFIG_BRIDGE=y
+CONFIG_BRIDGE_IGMP_SNOOPING=y
+CONFIG_BRIDGE_VLAN_FILTERING=y
+# CONFIG_BRIDGE_MRP is not set
+CONFIG_HAVE_NET_DSA=y
+# CONFIG_NET_DSA is not set
+CONFIG_VLAN_8021Q=y
+# CONFIG_VLAN_8021Q_GVRP is not set
+# CONFIG_VLAN_8021Q_MVRP is not set
+# CONFIG_DECNET is not set
+CONFIG_LLC=y
+# CONFIG_LLC2 is not set
+# CONFIG_ATALK is not set
+# CONFIG_X25 is not set
+# CONFIG_LAPB is not set
+# CONFIG_PHONET is not set
+# CONFIG_6LOWPAN is not set
+# CONFIG_IEEE802154 is not set
+# CONFIG_NET_SCHED is not set
+# CONFIG_DCB is not set
+CONFIG_DNS_RESOLVER=y
+# CONFIG_BATMAN_ADV is not set
+# CONFIG_OPENVSWITCH is not set
+# CONFIG_VSOCKETS is not set
+# CONFIG_NETLINK_DIAG is not set
+# CONFIG_MPLS is not set
+# CONFIG_NET_NSH is not set
+# CONFIG_HSR is not set
+# CONFIG_NET_SWITCHDEV is not set
+# CONFIG_NET_L3_MASTER_DEV is not set
+# CONFIG_QRTR is not set
+# CONFIG_NET_NCSI is not set
+CONFIG_RPS=y
+CONFIG_RFS_ACCEL=y
+CONFIG_XPS=y
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_CGROUP_NET_CLASSID=y
+CONFIG_NET_RX_BUSY_POLL=y
+CONFIG_BQL=y
+# CONFIG_BPF_JIT is not set
+CONFIG_NET_FLOW_LIMIT=y
+
+#
+# Network testing
+#
+# CONFIG_NET_PKTGEN is not set
+# CONFIG_NET_DROP_MONITOR is not set
+# end of Network testing
+# end of Networking options
+
+# CONFIG_HAMRADIO is not set
+# CONFIG_CAN is not set
+# CONFIG_BT is not set
+# CONFIG_AF_RXRPC is not set
+# CONFIG_AF_KCM is not set
+# CONFIG_WIRELESS is not set
+# CONFIG_WIMAX is not set
+# CONFIG_RFKILL is not set
+# CONFIG_NET_9P is not set
+# CONFIG_CAIF is not set
+# CONFIG_CEPH_LIB is not set
+# CONFIG_NFC is not set
+# CONFIG_PSAMPLE is not set
+# CONFIG_NET_IFE is not set
+# CONFIG_LWTUNNEL is not set
+# CONFIG_FAILOVER is not set
+CONFIG_ETHTOOL_NETLINK=y
+CONFIG_HAVE_EBPF_JIT=y
+
+#
+# Device Drivers
+#
+CONFIG_HAVE_EISA=y
+# CONFIG_EISA is not set
+CONFIG_HAVE_PCI=y
+CONFIG_PCI=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCIEPORTBUS=y
+# CONFIG_PCIEAER is not set
+CONFIG_PCIEASPM=y
+CONFIG_PCIEASPM_DEFAULT=y
+# CONFIG_PCIEASPM_POWERSAVE is not set
+# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
+# CONFIG_PCIEASPM_PERFORMANCE is not set
+CONFIG_PCIE_PME=y
+# CONFIG_PCIE_PTM is not set
+CONFIG_PCI_MSI=y
+CONFIG_PCI_MSI_IRQ_DOMAIN=y
+CONFIG_PCI_QUIRKS=y
+# CONFIG_PCI_DEBUG is not set
+# CONFIG_PCI_REALLOC_ENABLE_AUTO is not set
+CONFIG_PCI_STUB=y
+# CONFIG_PCI_PF_STUB is not set
+CONFIG_PCI_ATS=y
+CONFIG_PCI_LOCKLESS_CONFIG=y
+CONFIG_PCI_IOV=y
+CONFIG_PCI_PRI=y
+CONFIG_PCI_PASID=y
+CONFIG_PCI_LABEL=y
+# CONFIG_HOTPLUG_PCI is not set
+
+#
+# PCI controller drivers
+#
+# CONFIG_VMD is not set
+
+#
+# DesignWare PCI Core Support
+#
+# CONFIG_PCIE_DW_PLAT_HOST is not set
+# CONFIG_PCI_MESON is not set
+# end of DesignWare PCI Core Support
+
+#
+# Mobiveil PCIe Core Support
+#
+# end of Mobiveil PCIe Core Support
+
+#
+# Cadence PCIe controllers support
+#
+# end of Cadence PCIe controllers support
+# end of PCI controller drivers
+
+#
+# PCI Endpoint
+#
+# CONFIG_PCI_ENDPOINT is not set
+# end of PCI Endpoint
+
+#
+# PCI switch controller drivers
+#
+# CONFIG_PCI_SW_SWITCHTEC is not set
+# end of PCI switch controller drivers
+
+# CONFIG_PCCARD is not set
+# CONFIG_RAPIDIO is not set
+
+#
+# Generic Driver Options
+#
+CONFIG_UEVENT_HELPER=y
+CONFIG_UEVENT_HELPER_PATH=""
+CONFIG_DEVTMPFS=y
+# CONFIG_DEVTMPFS_MOUNT is not set
+CONFIG_STANDALONE=y
+CONFIG_PREVENT_FIRMWARE_BUILD=y
+
+#
+# Firmware loader
+#
+CONFIG_FW_LOADER=y
+CONFIG_EXTRA_FIRMWARE=""
+# CONFIG_FW_LOADER_USER_HELPER is not set
+# CONFIG_FW_LOADER_COMPRESS is not set
+# end of Firmware loader
+
+CONFIG_ALLOW_DEV_COREDUMP=y
+# CONFIG_DEBUG_DRIVER is not set
+# CONFIG_DEBUG_DEVRES is not set
+# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set
+# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_CPU_VULNERABILITIES=y
+CONFIG_DMA_SHARED_BUFFER=y
+# CONFIG_DMA_FENCE_TRACE is not set
+# end of Generic Driver Options
+
+#
+# Bus devices
+#
+# CONFIG_MHI_BUS is not set
+# end of Bus devices
+
+CONFIG_CONNECTOR=y
+CONFIG_PROC_EVENTS=y
+# CONFIG_GNSS is not set
+# CONFIG_MTD is not set
+# CONFIG_OF is not set
+CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y
+# CONFIG_PARPORT is not set
+CONFIG_PNP=y
+# CONFIG_PNP_DEBUG_MESSAGES is not set
+
+#
+# Protocols
+#
+CONFIG_PNPACPI=y
+CONFIG_BLK_DEV=y
+# CONFIG_BLK_DEV_NULL_BLK is not set
+# CONFIG_BLK_DEV_FD is not set
+# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set
+# CONFIG_BLK_DEV_UMEM is not set
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_LOOP_MIN_COUNT=8
+# CONFIG_BLK_DEV_CRYPTOLOOP is not set
+# CONFIG_BLK_DEV_DRBD is not set
+# CONFIG_BLK_DEV_NBD is not set
+# CONFIG_BLK_DEV_SKD is not set
+# CONFIG_BLK_DEV_SX8 is not set
+# CONFIG_BLK_DEV_RAM is not set
+# CONFIG_CDROM_PKTCDVD is not set
+# CONFIG_ATA_OVER_ETH is not set
+# CONFIG_BLK_DEV_RBD is not set
+# CONFIG_BLK_DEV_RSXX is not set
+
+#
+# NVME Support
+#
+# CONFIG_BLK_DEV_NVME is not set
+# CONFIG_NVME_FC is not set
+# CONFIG_NVME_TCP is not set
+# end of NVME Support
+
+#
+# Misc devices
+#
+# CONFIG_AD525X_DPOT is not set
+# CONFIG_DUMMY_IRQ is not set
+# CONFIG_IBM_ASM is not set
+# CONFIG_PHANTOM is not set
+# CONFIG_TIFM_CORE is not set
+# CONFIG_ICS932S401 is not set
+# CONFIG_ENCLOSURE_SERVICES is not set
+# CONFIG_HP_ILO is not set
+# CONFIG_APDS9802ALS is not set
+# CONFIG_ISL29003 is not set
+# CONFIG_ISL29020 is not set
+# CONFIG_SENSORS_TSL2550 is not set
+# CONFIG_SENSORS_BH1770 is not set
+# CONFIG_SENSORS_APDS990X is not set
+# CONFIG_HMC6352 is not set
+# CONFIG_DS1682 is not set
+# CONFIG_SRAM is not set
+# CONFIG_PCI_ENDPOINT_TEST is not set
+# CONFIG_XILINX_SDFEC is not set
+# CONFIG_PVPANIC is not set
+# CONFIG_C2PORT is not set
+
+#
+# EEPROM support
+#
+# CONFIG_EEPROM_AT24 is not set
+# CONFIG_EEPROM_LEGACY is not set
+# CONFIG_EEPROM_MAX6875 is not set
+# CONFIG_EEPROM_93CX6 is not set
+# CONFIG_EEPROM_IDT_89HPESX is not set
+# CONFIG_EEPROM_EE1004 is not set
+# end of EEPROM support
+
+# CONFIG_CB710_CORE is not set
+
+#
+# Texas Instruments shared transport line discipline
+#
+# end of Texas Instruments shared transport line discipline
+
+# CONFIG_SENSORS_LIS3_I2C is not set
+# CONFIG_ALTERA_STAPL is not set
+# CONFIG_INTEL_MEI is not set
+# CONFIG_INTEL_MEI_ME is not set
+# CONFIG_INTEL_MEI_TXE is not set
+# CONFIG_INTEL_MEI_HDCP is not set
+# CONFIG_VMWARE_VMCI is not set
+# CONFIG_GENWQE is not set
+# CONFIG_ECHO is not set
+# CONFIG_MISC_ALCOR_PCI is not set
+# CONFIG_MISC_RTSX_PCI is not set
+# CONFIG_MISC_RTSX_USB is not set
+# CONFIG_HABANA_AI is not set
+# end of Misc devices
+
+CONFIG_HAVE_IDE=y
+# CONFIG_IDE is not set
+
+#
+# SCSI device support
+#
+CONFIG_SCSI_MOD=y
+CONFIG_RAID_ATTRS=m
+CONFIG_SCSI=y
+CONFIG_SCSI_DMA=y
+# CONFIG_SCSI_PROC_FS is not set
+
+#
+# SCSI support type (disk, tape, CD-ROM)
+#
+CONFIG_BLK_DEV_SD=y
+# CONFIG_CHR_DEV_ST is not set
+# CONFIG_BLK_DEV_SR is not set
+CONFIG_CHR_DEV_SG=y
+# CONFIG_CHR_DEV_SCH is not set
+# CONFIG_SCSI_CONSTANTS is not set
+# CONFIG_SCSI_LOGGING is not set
+# CONFIG_SCSI_SCAN_ASYNC is not set
+
+#
+# SCSI Transports
+#
+CONFIG_SCSI_SPI_ATTRS=y
+# CONFIG_SCSI_FC_ATTRS is not set
+# CONFIG_SCSI_ISCSI_ATTRS is not set
+# CONFIG_SCSI_SAS_ATTRS is not set
+# CONFIG_SCSI_SAS_LIBSAS is not set
+# CONFIG_SCSI_SRP_ATTRS is not set
+# end of SCSI Transports
+
+# CONFIG_SCSI_LOWLEVEL is not set
+# CONFIG_SCSI_DH is not set
+# end of SCSI device support
+
+CONFIG_ATA=y
+CONFIG_SATA_HOST=y
+CONFIG_PATA_TIMINGS=y
+# CONFIG_ATA_VERBOSE_ERROR is not set
+CONFIG_ATA_FORCE=y
+CONFIG_ATA_ACPI=y
+# CONFIG_SATA_ZPODD is not set
+# CONFIG_SATA_PMP is not set
+
+#
+# Controllers with non-SFF native interface
+#
+CONFIG_SATA_AHCI=y
+CONFIG_SATA_MOBILE_LPM_POLICY=0
+# CONFIG_SATA_AHCI_PLATFORM is not set
+# CONFIG_SATA_INIC162X is not set
+# CONFIG_SATA_ACARD_AHCI is not set
+# CONFIG_SATA_SIL24 is not set
+CONFIG_ATA_SFF=y
+
+#
+# SFF controllers with custom DMA interface
+#
+# CONFIG_PDC_ADMA is not set
+# CONFIG_SATA_QSTOR is not set
+# CONFIG_SATA_SX4 is not set
+CONFIG_ATA_BMDMA=y
+
+#
+# SATA SFF controllers with BMDMA
+#
+CONFIG_ATA_PIIX=y
+CONFIG_SATA_MV=y
+CONFIG_SATA_NV=y
+CONFIG_SATA_PROMISE=y
+CONFIG_SATA_SIL=y
+CONFIG_SATA_SIS=y
+CONFIG_SATA_SVW=y
+CONFIG_SATA_ULI=y
+CONFIG_SATA_VIA=y
+CONFIG_SATA_VITESSE=y
+
+#
+# PATA SFF controllers with BMDMA
+#
+# CONFIG_PATA_ALI is not set
+# CONFIG_PATA_AMD is not set
+# CONFIG_PATA_ARTOP is not set
+# CONFIG_PATA_ATIIXP is not set
+# CONFIG_PATA_ATP867X is not set
+# CONFIG_PATA_CMD64X is not set
+# CONFIG_PATA_CYPRESS is not set
+# CONFIG_PATA_EFAR is not set
+# CONFIG_PATA_HPT366 is not set
+# CONFIG_PATA_HPT37X is not set
+# CONFIG_PATA_HPT3X2N is not set
+# CONFIG_PATA_HPT3X3 is not set
+# CONFIG_PATA_IT8213 is not set
+# CONFIG_PATA_IT821X is not set
+# CONFIG_PATA_JMICRON is not set
+# CONFIG_PATA_MARVELL is not set
+# CONFIG_PATA_NETCELL is not set
+# CONFIG_PATA_NINJA32 is not set
+# CONFIG_PATA_NS87415 is not set
+# CONFIG_PATA_OLDPIIX is not set
+# CONFIG_PATA_OPTIDMA is not set
+# CONFIG_PATA_PDC2027X is not set
+# CONFIG_PATA_PDC_OLD is not set
+# CONFIG_PATA_RADISYS is not set
+# CONFIG_PATA_RDC is not set
+# CONFIG_PATA_SCH is not set
+# CONFIG_PATA_SERVERWORKS is not set
+# CONFIG_PATA_SIL680 is not set
+CONFIG_PATA_SIS=y
+# CONFIG_PATA_TOSHIBA is not set
+# CONFIG_PATA_TRIFLEX is not set
+# CONFIG_PATA_VIA is not set
+# CONFIG_PATA_WINBOND is not set
+
+#
+# PIO-only SFF controllers
+#
+# CONFIG_PATA_CMD640_PCI is not set
+# CONFIG_PATA_MPIIX is not set
+# CONFIG_PATA_NS87410 is not set
+# CONFIG_PATA_OPTI is not set
+# CONFIG_PATA_RZ1000 is not set
+
+#
+# Generic fallback / legacy drivers
+#
+# CONFIG_PATA_ACPI is not set
+CONFIG_ATA_GENERIC=y
+# CONFIG_PATA_LEGACY is not set
+CONFIG_MD=y
+CONFIG_BLK_DEV_MD=y
+CONFIG_MD_AUTODETECT=y
+# CONFIG_MD_LINEAR is not set
+# CONFIG_MD_RAID0 is not set
+CONFIG_MD_RAID1=y
+# CONFIG_MD_RAID10 is not set
+# CONFIG_MD_RAID456 is not set
+# CONFIG_MD_MULTIPATH is not set
+# CONFIG_MD_FAULTY is not set
+# CONFIG_BCACHE is not set
+CONFIG_BLK_DEV_DM_BUILTIN=y
+CONFIG_BLK_DEV_DM=y
+# CONFIG_DM_DEBUG is not set
+# CONFIG_DM_UNSTRIPED is not set
+CONFIG_DM_CRYPT=y
+# CONFIG_DM_SNAPSHOT is not set
+# CONFIG_DM_THIN_PROVISIONING is not set
+# CONFIG_DM_CACHE is not set
+# CONFIG_DM_WRITECACHE is not set
+# CONFIG_DM_EBS is not set
+# CONFIG_DM_ERA is not set
+# CONFIG_DM_CLONE is not set
+# CONFIG_DM_MIRROR is not set
+# CONFIG_DM_RAID is not set
+# CONFIG_DM_ZERO is not set
+# CONFIG_DM_MULTIPATH is not set
+# CONFIG_DM_DELAY is not set
+# CONFIG_DM_DUST is not set
+# CONFIG_DM_INIT is not set
+# CONFIG_DM_UEVENT is not set
+# CONFIG_DM_FLAKEY is not set
+# CONFIG_DM_VERITY is not set
+# CONFIG_DM_SWITCH is not set
+# CONFIG_DM_LOG_WRITES is not set
+# CONFIG_DM_INTEGRITY is not set
+# CONFIG_TARGET_CORE is not set
+CONFIG_FUSION=y
+CONFIG_FUSION_SPI=y
+# CONFIG_FUSION_SAS is not set
+CONFIG_FUSION_MAX_SGE=128
+# CONFIG_FUSION_CTL is not set
+# CONFIG_FUSION_LOGGING is not set
+
+#
+# IEEE 1394 (FireWire) support
+#
+# CONFIG_FIREWIRE is not set
+# CONFIG_FIREWIRE_NOSY is not set
+# end of IEEE 1394 (FireWire) support
+
+# CONFIG_MACINTOSH_DRIVERS is not set
+CONFIG_NETDEVICES=y
+CONFIG_MII=y
+CONFIG_NET_CORE=y
+CONFIG_BONDING=y
+# CONFIG_DUMMY is not set
+# CONFIG_WIREGUARD is not set
+# CONFIG_EQUALIZER is not set
+# CONFIG_NET_FC is not set
+# CONFIG_NET_TEAM is not set
+CONFIG_MACVLAN=y
+CONFIG_MACVTAP=y
+# CONFIG_IPVLAN is not set
+# CONFIG_VXLAN is not set
+# CONFIG_GENEVE is not set
+# CONFIG_BAREUDP is not set
+# CONFIG_GTP is not set
+# CONFIG_MACSEC is not set
+# CONFIG_NETCONSOLE is not set
+CONFIG_TUN=y
+CONFIG_TAP=y
+# CONFIG_TUN_VNET_CROSS_LE is not set
+CONFIG_VETH=y
+# CONFIG_NLMON is not set
+# CONFIG_ARCNET is not set
+
+#
+# Distributed Switch Architecture drivers
+#
+# end of Distributed Switch Architecture drivers
+
+CONFIG_ETHERNET=y
+# CONFIG_NET_VENDOR_3COM is not set
+# CONFIG_NET_VENDOR_ADAPTEC is not set
+# CONFIG_NET_VENDOR_AGERE is not set
+CONFIG_NET_VENDOR_ALACRITECH=y
+# CONFIG_SLICOSS is not set
+# CONFIG_NET_VENDOR_ALTEON is not set
+# CONFIG_ALTERA_TSE is not set
+# CONFIG_NET_VENDOR_AMAZON is not set
+# CONFIG_NET_VENDOR_AMD is not set
+# CONFIG_NET_VENDOR_AQUANTIA is not set
+# CONFIG_NET_VENDOR_ARC is not set
+# CONFIG_NET_VENDOR_ATHEROS is not set
+# CONFIG_NET_VENDOR_AURORA is not set
+# CONFIG_NET_VENDOR_BROADCOM is not set
+# CONFIG_NET_VENDOR_BROCADE is not set
+# CONFIG_NET_VENDOR_CADENCE is not set
+# CONFIG_NET_VENDOR_CAVIUM is not set
+# CONFIG_NET_VENDOR_CHELSIO is not set
+# CONFIG_NET_VENDOR_CISCO is not set
+# CONFIG_NET_VENDOR_CORTINA is not set
+# CONFIG_CX_ECAT is not set
+# CONFIG_DNET is not set
+# CONFIG_NET_VENDOR_DEC is not set
+# CONFIG_NET_VENDOR_DLINK is not set
+# CONFIG_NET_VENDOR_EMULEX is not set
+# CONFIG_NET_VENDOR_EZCHIP is not set
+# CONFIG_NET_VENDOR_GOOGLE is not set
+# CONFIG_NET_VENDOR_HUAWEI is not set
+# CONFIG_NET_VENDOR_I825XX is not set
+CONFIG_NET_VENDOR_INTEL=y
+# CONFIG_E100 is not set
+CONFIG_E1000=y
+CONFIG_E1000E=y
+CONFIG_E1000E_HWTS=y
+CONFIG_IGB=y
+CONFIG_IGB_HWMON=y
+# CONFIG_IGBVF is not set
+# CONFIG_IXGB is not set
+# CONFIG_IXGBE is not set
+# CONFIG_IXGBEVF is not set
+# CONFIG_I40E is not set
+# CONFIG_I40EVF is not set
+# CONFIG_ICE is not set
+# CONFIG_FM10K is not set
+# CONFIG_IGC is not set
+# CONFIG_JME is not set
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MELLANOX is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_MICROCHIP is not set
+# CONFIG_NET_VENDOR_MICROSEMI is not set
+# CONFIG_NET_VENDOR_MYRI is not set
+# CONFIG_FEALNX is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_NETERION is not set
+# CONFIG_NET_VENDOR_NETRONOME is not set
+# CONFIG_NET_VENDOR_NI is not set
+# CONFIG_NET_VENDOR_NVIDIA is not set
+# CONFIG_NET_VENDOR_OKI is not set
+# CONFIG_ETHOC is not set
+# CONFIG_NET_VENDOR_PACKET_ENGINES is not set
+# CONFIG_NET_VENDOR_PENSANDO is not set
+# CONFIG_NET_VENDOR_QLOGIC is not set
+# CONFIG_NET_VENDOR_QUALCOMM is not set
+# CONFIG_NET_VENDOR_RDC is not set
+CONFIG_NET_VENDOR_REALTEK=y
+CONFIG_8139CP=y
+# CONFIG_8139TOO is not set
+CONFIG_R8169=y
+# CONFIG_NET_VENDOR_RENESAS is not set
+# CONFIG_NET_VENDOR_ROCKER is not set
+# CONFIG_NET_VENDOR_SAMSUNG is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SOLARFLARE is not set
+# CONFIG_NET_VENDOR_SILAN is not set
+# CONFIG_NET_VENDOR_SIS is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_SOCIONEXT is not set
+# CONFIG_NET_VENDOR_STMICRO is not set
+# CONFIG_NET_VENDOR_SUN is not set
+# CONFIG_NET_VENDOR_SYNOPSYS is not set
+# CONFIG_NET_VENDOR_TEHUTI is not set
+# CONFIG_NET_VENDOR_TI is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+# CONFIG_NET_VENDOR_XILINX is not set
+# CONFIG_FDDI is not set
+# CONFIG_HIPPI is not set
+# CONFIG_NET_SB1000 is not set
+CONFIG_PHYLIB=y
+# CONFIG_FIXED_PHY is not set
+
+#
+# MII PHY device drivers
+#
+# CONFIG_AMD_PHY is not set
+# CONFIG_ADIN_PHY is not set
+# CONFIG_AQUANTIA_PHY is not set
+# CONFIG_AX88796B_PHY is not set
+# CONFIG_BROADCOM_PHY is not set
+# CONFIG_BCM54140_PHY is not set
+# CONFIG_BCM7XXX_PHY is not set
+# CONFIG_BCM84881_PHY is not set
+# CONFIG_BCM87XX_PHY is not set
+# CONFIG_CICADA_PHY is not set
+# CONFIG_CORTINA_PHY is not set
+# CONFIG_DAVICOM_PHY is not set
+# CONFIG_ICPLUS_PHY is not set
+# CONFIG_LXT_PHY is not set
+# CONFIG_INTEL_XWAY_PHY is not set
+# CONFIG_LSI_ET1011C_PHY is not set
+# CONFIG_MARVELL_PHY is not set
+# CONFIG_MARVELL_10G_PHY is not set
+# CONFIG_MICREL_PHY is not set
+# CONFIG_MICROCHIP_PHY is not set
+# CONFIG_MICROCHIP_T1_PHY is not set
+# CONFIG_MICROSEMI_PHY is not set
+# CONFIG_NATIONAL_PHY is not set
+# CONFIG_NXP_TJA11XX_PHY is not set
+# CONFIG_QSEMI_PHY is not set
+CONFIG_REALTEK_PHY=y
+# CONFIG_RENESAS_PHY is not set
+# CONFIG_ROCKCHIP_PHY is not set
+# CONFIG_SMSC_PHY is not set
+# CONFIG_STE10XP is not set
+# CONFIG_TERANETICS_PHY is not set
+# CONFIG_DP83822_PHY is not set
+# CONFIG_DP83TC811_PHY is not set
+# CONFIG_DP83848_PHY is not set
+# CONFIG_DP83867_PHY is not set
+# CONFIG_DP83869_PHY is not set
+# CONFIG_VITESSE_PHY is not set
+# CONFIG_XILINX_GMII2RGMII is not set
+CONFIG_MDIO_DEVICE=y
+CONFIG_MDIO_BUS=y
+CONFIG_MDIO_DEVRES=y
+# CONFIG_MDIO_BITBANG is not set
+# CONFIG_MDIO_BCM_UNIMAC is not set
+# CONFIG_MDIO_MVUSB is not set
+# CONFIG_MDIO_MSCC_MIIM is not set
+# CONFIG_MDIO_THUNDER is not set
+
+#
+# MDIO Multiplexers
+#
+
+#
+# PCS device drivers
+#
+# CONFIG_PCS_XPCS is not set
+# end of PCS device drivers
+
+# CONFIG_PPP is not set
+# CONFIG_SLIP is not set
+# CONFIG_USB_NET_DRIVERS is not set
+# CONFIG_WLAN is not set
+
+#
+# Enable WiMAX (Networking options) to see the WiMAX drivers
+#
+# CONFIG_WAN is not set
+# CONFIG_VMXNET3 is not set
+# CONFIG_FUJITSU_ES is not set
+# CONFIG_NETDEVSIM is not set
+# CONFIG_NET_FAILOVER is not set
+# CONFIG_ISDN is not set
+# CONFIG_NVM is not set
+
+#
+# Input device support
+#
+CONFIG_INPUT=y
+CONFIG_INPUT_FF_MEMLESS=y
+CONFIG_INPUT_POLLDEV=y
+CONFIG_INPUT_SPARSEKMAP=y
+# CONFIG_INPUT_MATRIXKMAP is not set
+
+#
+# Userland interfaces
+#
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_MOUSEDEV_PSAUX=y
+CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024
+CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768
+CONFIG_INPUT_JOYDEV=y
+CONFIG_INPUT_EVDEV=y
+# CONFIG_INPUT_EVBUG is not set
+
+#
+# Input Device Drivers
+#
+CONFIG_INPUT_KEYBOARD=y
+# CONFIG_KEYBOARD_ADP5588 is not set
+# CONFIG_KEYBOARD_ADP5589 is not set
+CONFIG_KEYBOARD_ATKBD=y
+# CONFIG_KEYBOARD_QT1050 is not set
+# CONFIG_KEYBOARD_QT1070 is not set
+# CONFIG_KEYBOARD_QT2160 is not set
+# CONFIG_KEYBOARD_DLINK_DIR685 is not set
+# CONFIG_KEYBOARD_LKKBD is not set
+# CONFIG_KEYBOARD_TCA6416 is not set
+# CONFIG_KEYBOARD_TCA8418 is not set
+# CONFIG_KEYBOARD_LM8333 is not set
+# CONFIG_KEYBOARD_MAX7359 is not set
+# CONFIG_KEYBOARD_MCS is not set
+# CONFIG_KEYBOARD_MPR121 is not set
+# CONFIG_KEYBOARD_NEWTON is not set
+# CONFIG_KEYBOARD_OPENCORES is not set
+# CONFIG_KEYBOARD_SAMSUNG is not set
+# CONFIG_KEYBOARD_STOWAWAY is not set
+# CONFIG_KEYBOARD_SUNKBD is not set
+# CONFIG_KEYBOARD_XTKBD is not set
+# CONFIG_INPUT_MOUSE is not set
+# CONFIG_INPUT_JOYSTICK is not set
+# CONFIG_INPUT_TABLET is not set
+# CONFIG_INPUT_TOUCHSCREEN is not set
+CONFIG_INPUT_MISC=y
+# CONFIG_INPUT_AD714X is not set
+# CONFIG_INPUT_BMA150 is not set
+# CONFIG_INPUT_E3X0_BUTTON is not set
+CONFIG_INPUT_PCSPKR=y
+# CONFIG_INPUT_MMA8450 is not set
+CONFIG_INPUT_ATLAS_BTNS=y
+# CONFIG_INPUT_ATI_REMOTE2 is not set
+# CONFIG_INPUT_KEYSPAN_REMOTE is not set
+# CONFIG_INPUT_KXTJ9 is not set
+# CONFIG_INPUT_POWERMATE is not set
+# CONFIG_INPUT_YEALINK is not set
+# CONFIG_INPUT_CM109 is not set
+CONFIG_INPUT_UINPUT=y
+# CONFIG_INPUT_PCF8574 is not set
+# CONFIG_INPUT_ADXL34X is not set
+# CONFIG_INPUT_IQS269A is not set
+# CONFIG_INPUT_CMA3000 is not set
+# CONFIG_INPUT_IDEAPAD_SLIDEBAR is not set
+# CONFIG_INPUT_DRV2665_HAPTICS is not set
+# CONFIG_INPUT_DRV2667_HAPTICS is not set
+# CONFIG_RMI4_CORE is not set
+
+#
+# Hardware I/O ports
+#
+CONFIG_SERIO=y
+CONFIG_ARCH_MIGHT_HAVE_PC_SERIO=y
+CONFIG_SERIO_I8042=y
+CONFIG_SERIO_SERPORT=y
+# CONFIG_SERIO_CT82C710 is not set
+CONFIG_SERIO_PCIPS2=y
+CONFIG_SERIO_LIBPS2=y
+CONFIG_SERIO_RAW=y
+# CONFIG_SERIO_ALTERA_PS2 is not set
+# CONFIG_SERIO_PS2MULT is not set
+# CONFIG_SERIO_ARC_PS2 is not set
+# CONFIG_USERIO is not set
+# CONFIG_GAMEPORT is not set
+# end of Hardware I/O ports
+# end of Input device support
+
+#
+# Character devices
+#
+CONFIG_TTY=y
+CONFIG_VT=y
+CONFIG_CONSOLE_TRANSLATIONS=y
+CONFIG_VT_CONSOLE=y
+CONFIG_HW_CONSOLE=y
+CONFIG_VT_HW_CONSOLE_BINDING=y
+CONFIG_UNIX98_PTYS=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_LDISC_AUTOLOAD=y
+
+#
+# Serial drivers
+#
+CONFIG_SERIAL_EARLYCON=y
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y
+CONFIG_SERIAL_8250_PNP=y
+# CONFIG_SERIAL_8250_16550A_VARIANTS is not set
+# CONFIG_SERIAL_8250_FINTEK is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_PCI=y
+CONFIG_SERIAL_8250_EXAR=y
+CONFIG_SERIAL_8250_NR_UARTS=32
+CONFIG_SERIAL_8250_RUNTIME_UARTS=4
+# CONFIG_SERIAL_8250_EXTENDED is not set
+CONFIG_SERIAL_8250_DWLIB=y
+# CONFIG_SERIAL_8250_DW is not set
+# CONFIG_SERIAL_8250_RT288X is not set
+CONFIG_SERIAL_8250_LPSS=y
+CONFIG_SERIAL_8250_MID=y
+
+#
+# Non-8250 serial port support
+#
+# CONFIG_SERIAL_UARTLITE is not set
+CONFIG_SERIAL_CORE=y
+CONFIG_SERIAL_CORE_CONSOLE=y
+# CONFIG_SERIAL_JSM is not set
+# CONFIG_SERIAL_LANTIQ is not set
+# CONFIG_SERIAL_SCCNXP is not set
+# CONFIG_SERIAL_SC16IS7XX is not set
+# CONFIG_SERIAL_ALTERA_JTAGUART is not set
+# CONFIG_SERIAL_ALTERA_UART is not set
+# CONFIG_SERIAL_ARC is not set
+# CONFIG_SERIAL_RP2 is not set
+# CONFIG_SERIAL_FSL_LPUART is not set
+# CONFIG_SERIAL_FSL_LINFLEXUART is not set
+# CONFIG_SERIAL_SPRD is not set
+# end of Serial drivers
+
+# CONFIG_SERIAL_NONSTANDARD is not set
+# CONFIG_N_GSM is not set
+# CONFIG_NOZOMI is not set
+# CONFIG_NULL_TTY is not set
+# CONFIG_TRACE_SINK is not set
+# CONFIG_SERIAL_DEV_BUS is not set
+# CONFIG_VIRTIO_CONSOLE is not set
+# CONFIG_IPMI_HANDLER is not set
+CONFIG_HW_RANDOM=y
+CONFIG_HW_RANDOM_TIMERIOMEM=y
+CONFIG_HW_RANDOM_INTEL=y
+# CONFIG_HW_RANDOM_AMD is not set
+# CONFIG_HW_RANDOM_BA431 is not set
+# CONFIG_HW_RANDOM_VIA is not set
+# CONFIG_HW_RANDOM_XIPHERA is not set
+# CONFIG_APPLICOM is not set
+# CONFIG_MWAVE is not set
+# CONFIG_DEVMEM is not set
+# CONFIG_DEVKMEM is not set
+CONFIG_NVRAM=y
+# CONFIG_RAW_DRIVER is not set
+CONFIG_DEVPORT=y
+CONFIG_HPET=y
+CONFIG_HPET_MMAP=y
+CONFIG_HPET_MMAP_DEFAULT=y
+CONFIG_HANGCHECK_TIMER=y
+CONFIG_TCG_TPM=y
+CONFIG_HW_RANDOM_TPM=y
+CONFIG_TCG_TIS_CORE=y
+CONFIG_TCG_TIS=y
+# CONFIG_TCG_TIS_I2C_ATMEL is not set
+CONFIG_TCG_TIS_I2C_INFINEON=y
+# CONFIG_TCG_TIS_I2C_NUVOTON is not set
+# CONFIG_TCG_NSC is not set
+# CONFIG_TCG_ATMEL is not set
+# CONFIG_TCG_INFINEON is not set
+# CONFIG_TCG_CRB is not set
+# CONFIG_TCG_VTPM_PROXY is not set
+# CONFIG_TCG_TIS_ST33ZP24_I2C is not set
+# CONFIG_TELCLOCK is not set
+# CONFIG_XILLYBUS is not set
+# end of Character devices
+
+# CONFIG_RANDOM_TRUST_CPU is not set
+# CONFIG_RANDOM_TRUST_BOOTLOADER is not set
+
+#
+# I2C support
+#
+CONFIG_I2C=y
+CONFIG_ACPI_I2C_OPREGION=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_COMPAT=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MUX=y
+
+#
+# Multiplexer I2C Chip support
+#
+# CONFIG_I2C_MUX_LTC4306 is not set
+# CONFIG_I2C_MUX_PCA9541 is not set
+# CONFIG_I2C_MUX_REG is not set
+# CONFIG_I2C_MUX_MLXCPLD is not set
+# end of Multiplexer I2C Chip support
+
+CONFIG_I2C_HELPER_AUTO=y
+CONFIG_I2C_ALGOBIT=y
+
+#
+# I2C Hardware Bus support
+#
+
+#
+# PC SMBus host controller drivers
+#
+# CONFIG_I2C_ALI1535 is not set
+# CONFIG_I2C_ALI1563 is not set
+# CONFIG_I2C_ALI15X3 is not set
+# CONFIG_I2C_AMD756 is not set
+# CONFIG_I2C_AMD8111 is not set
+# CONFIG_I2C_AMD_MP2 is not set
+# CONFIG_I2C_I801 is not set
+# CONFIG_I2C_ISCH is not set
+# CONFIG_I2C_ISMT is not set
+# CONFIG_I2C_PIIX4 is not set
+# CONFIG_I2C_NFORCE2 is not set
+# CONFIG_I2C_NVIDIA_GPU is not set
+# CONFIG_I2C_SIS5595 is not set
+# CONFIG_I2C_SIS630 is not set
+# CONFIG_I2C_SIS96X is not set
+# CONFIG_I2C_VIA is not set
+# CONFIG_I2C_VIAPRO is not set
+
+#
+# ACPI drivers
+#
+# CONFIG_I2C_SCMI is not set
+
+#
+# I2C system bus drivers (mostly embedded / system-on-chip)
+#
+# CONFIG_I2C_DESIGNWARE_PLATFORM is not set
+# CONFIG_I2C_DESIGNWARE_PCI is not set
+# CONFIG_I2C_EMEV2 is not set
+# CONFIG_I2C_OCORES is not set
+# CONFIG_I2C_PCA_PLATFORM is not set
+# CONFIG_I2C_SIMTEC is not set
+# CONFIG_I2C_XILINX is not set
+
+#
+# External I2C/SMBus adapter drivers
+#
+# CONFIG_I2C_DIOLAN_U2C is not set
+# CONFIG_I2C_ROBOTFUZZ_OSIF is not set
+# CONFIG_I2C_TAOS_EVM is not set
+# CONFIG_I2C_TINY_USB is not set
+
+#
+# Other I2C/SMBus bus drivers
+#
+# CONFIG_I2C_MLXCPLD is not set
+# end of I2C Hardware Bus support
+
+# CONFIG_I2C_STUB is not set
+# CONFIG_I2C_SLAVE is not set
+# CONFIG_I2C_DEBUG_CORE is not set
+# CONFIG_I2C_DEBUG_ALGO is not set
+# CONFIG_I2C_DEBUG_BUS is not set
+# end of I2C support
+
+# CONFIG_I3C is not set
+# CONFIG_SPI is not set
+# CONFIG_SPMI is not set
+# CONFIG_HSI is not set
+CONFIG_PPS=y
+# CONFIG_PPS_DEBUG is not set
+
+#
+# PPS clients support
+#
+# CONFIG_PPS_CLIENT_KTIMER is not set
+# CONFIG_PPS_CLIENT_LDISC is not set
+# CONFIG_PPS_CLIENT_GPIO is not set
+
+#
+# PPS generators support
+#
+
+#
+# PTP clock support
+#
+CONFIG_PTP_1588_CLOCK=y
+
+#
+# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
+#
+# CONFIG_PTP_1588_CLOCK_IDT82P33 is not set
+# CONFIG_PTP_1588_CLOCK_IDTCM is not set
+# end of PTP clock support
+
+# CONFIG_PINCTRL is not set
+# CONFIG_GPIOLIB is not set
+# CONFIG_W1 is not set
+# CONFIG_POWER_RESET is not set
+CONFIG_POWER_SUPPLY=y
+# CONFIG_POWER_SUPPLY_DEBUG is not set
+CONFIG_POWER_SUPPLY_HWMON=y
+# CONFIG_PDA_POWER is not set
+# CONFIG_TEST_POWER is not set
+# CONFIG_CHARGER_ADP5061 is not set
+# CONFIG_BATTERY_CW2015 is not set
+# CONFIG_BATTERY_DS2780 is not set
+# CONFIG_BATTERY_DS2781 is not set
+# CONFIG_BATTERY_DS2782 is not set
+# CONFIG_BATTERY_SBS is not set
+# CONFIG_CHARGER_SBS is not set
+# CONFIG_BATTERY_BQ27XXX is not set
+# CONFIG_BATTERY_MAX17040 is not set
+# CONFIG_BATTERY_MAX17042 is not set
+# CONFIG_CHARGER_MAX8903 is not set
+# CONFIG_CHARGER_LP8727 is not set
+# CONFIG_CHARGER_BQ2415X is not set
+# CONFIG_CHARGER_SMB347 is not set
+# CONFIG_BATTERY_GAUGE_LTC2941 is not set
+# CONFIG_BATTERY_RT5033 is not set
+# CONFIG_CHARGER_BD99954 is not set
+CONFIG_HWMON=y
+# CONFIG_HWMON_DEBUG_CHIP is not set
+
+#
+# Native drivers
+#
+# CONFIG_SENSORS_ABITUGURU is not set
+# CONFIG_SENSORS_ABITUGURU3 is not set
+# CONFIG_SENSORS_AD7414 is not set
+# CONFIG_SENSORS_AD7418 is not set
+# CONFIG_SENSORS_ADM1021 is not set
+# CONFIG_SENSORS_ADM1025 is not set
+# CONFIG_SENSORS_ADM1026 is not set
+# CONFIG_SENSORS_ADM1029 is not set
+# CONFIG_SENSORS_ADM1031 is not set
+# CONFIG_SENSORS_ADM1177 is not set
+# CONFIG_SENSORS_ADM9240 is not set
+# CONFIG_SENSORS_ADT7410 is not set
+# CONFIG_SENSORS_ADT7411 is not set
+# CONFIG_SENSORS_ADT7462 is not set
+# CONFIG_SENSORS_ADT7470 is not set
+# CONFIG_SENSORS_ADT7475 is not set
+# CONFIG_SENSORS_AS370 is not set
+# CONFIG_SENSORS_ASC7621 is not set
+# CONFIG_SENSORS_AXI_FAN_CONTROL is not set
+# CONFIG_SENSORS_K8TEMP is not set
+# CONFIG_SENSORS_K10TEMP is not set
+# CONFIG_SENSORS_FAM15H_POWER is not set
+# CONFIG_SENSORS_AMD_ENERGY is not set
+# CONFIG_SENSORS_APPLESMC is not set
+# CONFIG_SENSORS_ASB100 is not set
+# CONFIG_SENSORS_ASPEED is not set
+# CONFIG_SENSORS_ATXP1 is not set
+# CONFIG_SENSORS_CORSAIR_CPRO is not set
+# CONFIG_SENSORS_DRIVETEMP is not set
+# CONFIG_SENSORS_DS620 is not set
+# CONFIG_SENSORS_DS1621 is not set
+# CONFIG_SENSORS_DELL_SMM is not set
+# CONFIG_SENSORS_I5K_AMB is not set
+# CONFIG_SENSORS_F71805F is not set
+# CONFIG_SENSORS_F71882FG is not set
+# CONFIG_SENSORS_F75375S is not set
+# CONFIG_SENSORS_FSCHMD is not set
+# CONFIG_SENSORS_GL518SM is not set
+# CONFIG_SENSORS_GL520SM is not set
+# CONFIG_SENSORS_G760A is not set
+# CONFIG_SENSORS_G762 is not set
+# CONFIG_SENSORS_HIH6130 is not set
+# CONFIG_SENSORS_I5500 is not set
+# CONFIG_SENSORS_CORETEMP is not set
+# CONFIG_SENSORS_IT87 is not set
+# CONFIG_SENSORS_JC42 is not set
+# CONFIG_SENSORS_POWR1220 is not set
+# CONFIG_SENSORS_LINEAGE is not set
+# CONFIG_SENSORS_LTC2945 is not set
+# CONFIG_SENSORS_LTC2947_I2C is not set
+# CONFIG_SENSORS_LTC2990 is not set
+# CONFIG_SENSORS_LTC4151 is not set
+# CONFIG_SENSORS_LTC4215 is not set
+# CONFIG_SENSORS_LTC4222 is not set
+# CONFIG_SENSORS_LTC4245 is not set
+# CONFIG_SENSORS_LTC4260 is not set
+# CONFIG_SENSORS_LTC4261 is not set
+# CONFIG_SENSORS_MAX16065 is not set
+# CONFIG_SENSORS_MAX1619 is not set
+# CONFIG_SENSORS_MAX1668 is not set
+# CONFIG_SENSORS_MAX197 is not set
+# CONFIG_SENSORS_MAX31730 is not set
+# CONFIG_SENSORS_MAX6621 is not set
+# CONFIG_SENSORS_MAX6639 is not set
+# CONFIG_SENSORS_MAX6642 is not set
+# CONFIG_SENSORS_MAX6650 is not set
+# CONFIG_SENSORS_MAX6697 is not set
+# CONFIG_SENSORS_MAX31790 is not set
+# CONFIG_SENSORS_MCP3021 is not set
+# CONFIG_SENSORS_TC654 is not set
+# CONFIG_SENSORS_MR75203 is not set
+# CONFIG_SENSORS_LM63 is not set
+# CONFIG_SENSORS_LM73 is not set
+# CONFIG_SENSORS_LM75 is not set
+# CONFIG_SENSORS_LM77 is not set
+# CONFIG_SENSORS_LM78 is not set
+# CONFIG_SENSORS_LM80 is not set
+# CONFIG_SENSORS_LM83 is not set
+# CONFIG_SENSORS_LM85 is not set
+# CONFIG_SENSORS_LM87 is not set
+# CONFIG_SENSORS_LM90 is not set
+# CONFIG_SENSORS_LM92 is not set
+# CONFIG_SENSORS_LM93 is not set
+# CONFIG_SENSORS_LM95234 is not set
+# CONFIG_SENSORS_LM95241 is not set
+# CONFIG_SENSORS_LM95245 is not set
+# CONFIG_SENSORS_PC87360 is not set
+# CONFIG_SENSORS_PC87427 is not set
+# CONFIG_SENSORS_NTC_THERMISTOR is not set
+# CONFIG_SENSORS_NCT6683 is not set
+# CONFIG_SENSORS_NCT6775 is not set
+# CONFIG_SENSORS_NCT7802 is not set
+# CONFIG_SENSORS_NPCM7XX is not set
+# CONFIG_SENSORS_PCF8591 is not set
+# CONFIG_PMBUS is not set
+# CONFIG_SENSORS_SHT21 is not set
+# CONFIG_SENSORS_SHT3x is not set
+# CONFIG_SENSORS_SHTC1 is not set
+# CONFIG_SENSORS_SIS5595 is not set
+# CONFIG_SENSORS_DME1737 is not set
+# CONFIG_SENSORS_EMC1403 is not set
+# CONFIG_SENSORS_EMC2103 is not set
+# CONFIG_SENSORS_EMC6W201 is not set
+# CONFIG_SENSORS_SMSC47M1 is not set
+# CONFIG_SENSORS_SMSC47M192 is not set
+# CONFIG_SENSORS_SMSC47B397 is not set
+# CONFIG_SENSORS_STTS751 is not set
+# CONFIG_SENSORS_SMM665 is not set
+# CONFIG_SENSORS_ADC128D818 is not set
+# CONFIG_SENSORS_ADS7828 is not set
+# CONFIG_SENSORS_AMC6821 is not set
+# CONFIG_SENSORS_INA209 is not set
+# CONFIG_SENSORS_INA2XX is not set
+# CONFIG_SENSORS_INA3221 is not set
+# CONFIG_SENSORS_TC74 is not set
+# CONFIG_SENSORS_THMC50 is not set
+# CONFIG_SENSORS_TMP102 is not set
+# CONFIG_SENSORS_TMP103 is not set
+# CONFIG_SENSORS_TMP108 is not set
+# CONFIG_SENSORS_TMP401 is not set
+# CONFIG_SENSORS_TMP421 is not set
+# CONFIG_SENSORS_TMP513 is not set
+# CONFIG_SENSORS_VIA_CPUTEMP is not set
+# CONFIG_SENSORS_VIA686A is not set
+# CONFIG_SENSORS_VT1211 is not set
+# CONFIG_SENSORS_VT8231 is not set
+# CONFIG_SENSORS_W83773G is not set
+# CONFIG_SENSORS_W83781D is not set
+# CONFIG_SENSORS_W83791D is not set
+# CONFIG_SENSORS_W83792D is not set
+# CONFIG_SENSORS_W83793 is not set
+# CONFIG_SENSORS_W83795 is not set
+# CONFIG_SENSORS_W83L785TS is not set
+# CONFIG_SENSORS_W83L786NG is not set
+# CONFIG_SENSORS_W83627HF is not set
+# CONFIG_SENSORS_W83627EHF is not set
+# CONFIG_SENSORS_XGENE is not set
+
+#
+# ACPI drivers
+#
+# CONFIG_SENSORS_ACPI_POWER is not set
+# CONFIG_SENSORS_ATK0110 is not set
+CONFIG_THERMAL=y
+# CONFIG_THERMAL_NETLINK is not set
+# CONFIG_THERMAL_STATISTICS is not set
+CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0
+# CONFIG_THERMAL_HWMON is not set
+# CONFIG_THERMAL_WRITABLE_TRIPS is not set
+CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
+# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set
+# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set
+# CONFIG_THERMAL_GOV_FAIR_SHARE is not set
+CONFIG_THERMAL_GOV_STEP_WISE=y
+# CONFIG_THERMAL_GOV_BANG_BANG is not set
+# CONFIG_THERMAL_GOV_USER_SPACE is not set
+# CONFIG_DEVFREQ_THERMAL is not set
+# CONFIG_THERMAL_EMULATION is not set
+
+#
+# Intel thermal drivers
+#
+# CONFIG_INTEL_POWERCLAMP is not set
+# CONFIG_INTEL_SOC_DTS_THERMAL is not set
+
+#
+# ACPI INT340X thermal drivers
+#
+# CONFIG_INT340X_THERMAL is not set
+# end of ACPI INT340X thermal drivers
+
+# CONFIG_INTEL_PCH_THERMAL is not set
+# end of Intel thermal drivers
+
+# CONFIG_WATCHDOG is not set
+CONFIG_SSB_POSSIBLE=y
+# CONFIG_SSB is not set
+CONFIG_BCMA_POSSIBLE=y
+# CONFIG_BCMA is not set
+
+#
+# Multifunction device drivers
+#
+CONFIG_MFD_CORE=y
+# CONFIG_MFD_AS3711 is not set
+# CONFIG_PMIC_ADP5520 is not set
+# CONFIG_MFD_BCM590XX is not set
+# CONFIG_MFD_BD9571MWV is not set
+# CONFIG_MFD_AXP20X_I2C is not set
+# CONFIG_MFD_MADERA is not set
+# CONFIG_PMIC_DA903X is not set
+# CONFIG_MFD_DA9052_I2C is not set
+# CONFIG_MFD_DA9055 is not set
+# CONFIG_MFD_DA9062 is not set
+# CONFIG_MFD_DA9063 is not set
+# CONFIG_MFD_DA9150 is not set
+# CONFIG_MFD_DLN2 is not set
+# CONFIG_MFD_MC13XXX_I2C is not set
+# CONFIG_MFD_MP2629 is not set
+# CONFIG_HTC_PASIC3 is not set
+# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
+CONFIG_LPC_ICH=y
+CONFIG_LPC_SCH=y
+# CONFIG_MFD_INTEL_LPSS_ACPI is not set
+# CONFIG_MFD_INTEL_LPSS_PCI is not set
+# CONFIG_MFD_INTEL_PMC_BXT is not set
+# CONFIG_MFD_IQS62X is not set
+# CONFIG_MFD_JANZ_CMODIO is not set
+# CONFIG_MFD_KEMPLD is not set
+# CONFIG_MFD_88PM800 is not set
+# CONFIG_MFD_88PM805 is not set
+# CONFIG_MFD_88PM860X is not set
+# CONFIG_MFD_MAX14577 is not set
+# CONFIG_MFD_MAX77693 is not set
+# CONFIG_MFD_MAX77843 is not set
+# CONFIG_MFD_MAX8907 is not set
+# CONFIG_MFD_MAX8925 is not set
+# CONFIG_MFD_MAX8997 is not set
+# CONFIG_MFD_MAX8998 is not set
+# CONFIG_MFD_MT6360 is not set
+# CONFIG_MFD_MT6397 is not set
+# CONFIG_MFD_MENF21BMC is not set
+# CONFIG_MFD_VIPERBOARD is not set
+# CONFIG_MFD_RETU is not set
+# CONFIG_MFD_PCF50633 is not set
+# CONFIG_MFD_RDC321X is not set
+# CONFIG_MFD_RT5033 is not set
+# CONFIG_MFD_RC5T583 is not set
+# CONFIG_MFD_SEC_CORE is not set
+# CONFIG_MFD_SI476X_CORE is not set
+CONFIG_MFD_SM501=y
+# CONFIG_MFD_SKY81452 is not set
+# CONFIG_ABX500_CORE is not set
+# CONFIG_MFD_SYSCON is not set
+# CONFIG_MFD_TI_AM335X_TSCADC is not set
+# CONFIG_MFD_LP3943 is not set
+# CONFIG_MFD_LP8788 is not set
+# CONFIG_MFD_TI_LMU is not set
+# CONFIG_MFD_PALMAS is not set
+# CONFIG_TPS6105X is not set
+# CONFIG_TPS6507X is not set
+# CONFIG_MFD_TPS65086 is not set
+# CONFIG_MFD_TPS65090 is not set
+# CONFIG_MFD_TI_LP873X is not set
+# CONFIG_MFD_TPS6586X is not set
+# CONFIG_MFD_TPS65912_I2C is not set
+# CONFIG_MFD_TPS80031 is not set
+# CONFIG_TWL4030_CORE is not set
+# CONFIG_TWL6040_CORE is not set
+CONFIG_MFD_WL1273_CORE=y
+# CONFIG_MFD_LM3533 is not set
+# CONFIG_MFD_TQMX86 is not set
+CONFIG_MFD_VX855=y
+# CONFIG_MFD_ARIZONA_I2C is not set
+# CONFIG_MFD_WM8400 is not set
+# CONFIG_MFD_WM831X_I2C is not set
+# CONFIG_MFD_WM8350_I2C is not set
+# CONFIG_MFD_WM8994 is not set
+# end of Multifunction device drivers
+
+# CONFIG_REGULATOR is not set
+# CONFIG_RC_CORE is not set
+# CONFIG_MEDIA_CEC_SUPPORT is not set
+# CONFIG_MEDIA_SUPPORT is not set
+
+#
+# Graphics support
+#
+# CONFIG_AGP is not set
+CONFIG_INTEL_GTT=y
+CONFIG_VGA_ARB=y
+CONFIG_VGA_ARB_MAX_GPUS=16
+# CONFIG_VGA_SWITCHEROO is not set
+CONFIG_DRM=y
+CONFIG_DRM_MIPI_DSI=y
+# CONFIG_DRM_DP_AUX_CHARDEV is not set
+# CONFIG_DRM_DEBUG_MM is not set
+# CONFIG_DRM_DEBUG_SELFTEST is not set
+CONFIG_DRM_KMS_HELPER=y
+CONFIG_DRM_KMS_FB_HELPER=y
+CONFIG_DRM_FBDEV_EMULATION=y
+CONFIG_DRM_FBDEV_OVERALLOC=100
+# CONFIG_DRM_LOAD_EDID_FIRMWARE is not set
+# CONFIG_DRM_DP_CEC is not set
+
+#
+# I2C encoder or helper chips
+#
+# CONFIG_DRM_I2C_CH7006 is not set
+# CONFIG_DRM_I2C_SIL164 is not set
+# CONFIG_DRM_I2C_NXP_TDA998X is not set
+# CONFIG_DRM_I2C_NXP_TDA9950 is not set
+# end of I2C encoder or helper chips
+
+#
+# ARM devices
+#
+# end of ARM devices
+
+# CONFIG_DRM_RADEON is not set
+# CONFIG_DRM_AMDGPU is not set
+# CONFIG_DRM_NOUVEAU is not set
+CONFIG_DRM_I915=y
+CONFIG_DRM_I915_FORCE_PROBE=""
+CONFIG_DRM_I915_CAPTURE_ERROR=y
+CONFIG_DRM_I915_COMPRESS_ERROR=y
+CONFIG_DRM_I915_USERPTR=y
+# CONFIG_DRM_I915_GVT is not set
+CONFIG_DRM_I915_FENCE_TIMEOUT=10000
+CONFIG_DRM_I915_USERFAULT_AUTOSUSPEND=250
+CONFIG_DRM_I915_HEARTBEAT_INTERVAL=2500
+CONFIG_DRM_I915_PREEMPT_TIMEOUT=640
+CONFIG_DRM_I915_MAX_REQUEST_BUSYWAIT=8000
+CONFIG_DRM_I915_STOP_TIMEOUT=100
+CONFIG_DRM_I915_TIMESLICE_DURATION=1
+# CONFIG_DRM_VGEM is not set
+# CONFIG_DRM_VKMS is not set
+# CONFIG_DRM_VMWGFX is not set
+# CONFIG_DRM_GMA500 is not set
+# CONFIG_DRM_UDL is not set
+# CONFIG_DRM_AST is not set
+# CONFIG_DRM_MGAG200 is not set
+# CONFIG_DRM_QXL is not set
+# CONFIG_DRM_BOCHS is not set
+CONFIG_DRM_PANEL=y
+
+#
+# Display Panels
+#
+# CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN is not set
+# end of Display Panels
+
+CONFIG_DRM_BRIDGE=y
+CONFIG_DRM_PANEL_BRIDGE=y
+
+#
+# Display Interface Bridges
+#
+# CONFIG_DRM_ANALOGIX_ANX78XX is not set
+# end of Display Interface Bridges
+
+# CONFIG_DRM_ETNAVIV is not set
+# CONFIG_DRM_CIRRUS_QEMU is not set
+# CONFIG_DRM_GM12U320 is not set
+# CONFIG_DRM_VBOXVIDEO is not set
+# CONFIG_DRM_LEGACY is not set
+CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y
+
+#
+# Frame buffer Devices
+#
+CONFIG_FB_CMDLINE=y
+CONFIG_FB_NOTIFY=y
+CONFIG_FB=y
+CONFIG_FIRMWARE_EDID=y
+CONFIG_FB_BOOT_VESA_SUPPORT=y
+CONFIG_FB_CFB_FILLRECT=y
+CONFIG_FB_CFB_COPYAREA=y
+CONFIG_FB_CFB_IMAGEBLIT=y
+CONFIG_FB_SYS_FILLRECT=y
+CONFIG_FB_SYS_COPYAREA=y
+CONFIG_FB_SYS_IMAGEBLIT=y
+# CONFIG_FB_FOREIGN_ENDIAN is not set
+CONFIG_FB_SYS_FOPS=y
+CONFIG_FB_DEFERRED_IO=y
+# CONFIG_FB_MODE_HELPERS is not set
+# CONFIG_FB_TILEBLITTING is not set
+
+#
+# Frame buffer hardware drivers
+#
+# CONFIG_FB_CIRRUS is not set
+# CONFIG_FB_PM2 is not set
+# CONFIG_FB_CYBER2000 is not set
+# CONFIG_FB_ARC is not set
+# CONFIG_FB_ASILIANT is not set
+# CONFIG_FB_IMSTT is not set
+# CONFIG_FB_VGA16 is not set
+# CONFIG_FB_UVESA is not set
+CONFIG_FB_VESA=y
+CONFIG_FB_EFI=y
+# CONFIG_FB_N411 is not set
+# CONFIG_FB_HGA is not set
+# CONFIG_FB_OPENCORES is not set
+# CONFIG_FB_S1D13XXX is not set
+# CONFIG_FB_NVIDIA is not set
+# CONFIG_FB_RIVA is not set
+# CONFIG_FB_I740 is not set
+# CONFIG_FB_LE80578 is not set
+# CONFIG_FB_MATROX is not set
+# CONFIG_FB_RADEON is not set
+# CONFIG_FB_ATY128 is not set
+# CONFIG_FB_ATY is not set
+# CONFIG_FB_S3 is not set
+# CONFIG_FB_SAVAGE is not set
+# CONFIG_FB_SIS is not set
+# CONFIG_FB_NEOMAGIC is not set
+# CONFIG_FB_KYRO is not set
+# CONFIG_FB_3DFX is not set
+# CONFIG_FB_VOODOO1 is not set
+# CONFIG_FB_VT8623 is not set
+# CONFIG_FB_TRIDENT is not set
+# CONFIG_FB_ARK is not set
+# CONFIG_FB_PM3 is not set
+# CONFIG_FB_CARMINE is not set
+# CONFIG_FB_SM501 is not set
+# CONFIG_FB_SMSCUFX is not set
+# CONFIG_FB_UDL is not set
+# CONFIG_FB_IBM_GXT4500 is not set
+# CONFIG_FB_VIRTUAL is not set
+# CONFIG_FB_METRONOME is not set
+# CONFIG_FB_MB862XX is not set
+# CONFIG_FB_SIMPLE is not set
+# CONFIG_FB_SM712 is not set
+# end of Frame buffer Devices
+
+#
+# Backlight & LCD device support
+#
+CONFIG_LCD_CLASS_DEVICE=m
+# CONFIG_LCD_PLATFORM is not set
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+# CONFIG_BACKLIGHT_APPLE is not set
+# CONFIG_BACKLIGHT_QCOM_WLED is not set
+# CONFIG_BACKLIGHT_SAHARA is not set
+# CONFIG_BACKLIGHT_ADP8860 is not set
+# CONFIG_BACKLIGHT_ADP8870 is not set
+# CONFIG_BACKLIGHT_LM3639 is not set
+# CONFIG_BACKLIGHT_LV5207LP is not set
+# CONFIG_BACKLIGHT_BD6107 is not set
+# CONFIG_BACKLIGHT_ARCXCNN is not set
+# end of Backlight & LCD device support
+
+CONFIG_HDMI=y
+
+#
+# Console display driver support
+#
+CONFIG_VGA_CONSOLE=y
+CONFIG_DUMMY_CONSOLE=y
+CONFIG_DUMMY_CONSOLE_COLUMNS=80
+CONFIG_DUMMY_CONSOLE_ROWS=25
+CONFIG_FRAMEBUFFER_CONSOLE=y
+CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
+# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set
+# CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set
+# end of Console display driver support
+
+# CONFIG_LOGO is not set
+# end of Graphics support
+
+# CONFIG_SOUND is not set
+
+#
+# HID support
+#
+CONFIG_HID=y
+# CONFIG_HID_BATTERY_STRENGTH is not set
+# CONFIG_HIDRAW is not set
+# CONFIG_UHID is not set
+CONFIG_HID_GENERIC=y
+
+#
+# Special HID drivers
+#
+# CONFIG_HID_A4TECH is not set
+# CONFIG_HID_ACCUTOUCH is not set
+# CONFIG_HID_ACRUX is not set
+# CONFIG_HID_APPLE is not set
+# CONFIG_HID_APPLEIR is not set
+# CONFIG_HID_AUREAL is not set
+# CONFIG_HID_BELKIN is not set
+# CONFIG_HID_BETOP_FF is not set
+# CONFIG_HID_CHERRY is not set
+# CONFIG_HID_CHICONY is not set
+# CONFIG_HID_COUGAR is not set
+# CONFIG_HID_MACALLY is not set
+# CONFIG_HID_CMEDIA is not set
+# CONFIG_HID_CREATIVE_SB0540 is not set
+# CONFIG_HID_CYPRESS is not set
+# CONFIG_HID_DRAGONRISE is not set
+# CONFIG_HID_EMS_FF is not set
+# CONFIG_HID_ELECOM is not set
+# CONFIG_HID_ELO is not set
+# CONFIG_HID_EZKEY is not set
+# CONFIG_HID_GEMBIRD is not set
+# CONFIG_HID_GFRM is not set
+# CONFIG_HID_GLORIOUS is not set
+# CONFIG_HID_HOLTEK is not set
+# CONFIG_HID_VIVALDI is not set
+# CONFIG_HID_KEYTOUCH is not set
+# CONFIG_HID_KYE is not set
+# CONFIG_HID_UCLOGIC is not set
+# CONFIG_HID_WALTOP is not set
+# CONFIG_HID_VIEWSONIC is not set
+# CONFIG_HID_GYRATION is not set
+# CONFIG_HID_ICADE is not set
+CONFIG_HID_ITE=y
+# CONFIG_HID_JABRA is not set
+# CONFIG_HID_TWINHAN is not set
+# CONFIG_HID_KENSINGTON is not set
+# CONFIG_HID_LCPOWER is not set
+# CONFIG_HID_LENOVO is not set
+# CONFIG_HID_MAGICMOUSE is not set
+# CONFIG_HID_MALTRON is not set
+# CONFIG_HID_MAYFLASH is not set
+CONFIG_HID_REDRAGON=y
+# CONFIG_HID_MICROSOFT is not set
+# CONFIG_HID_MONTEREY is not set
+# CONFIG_HID_MULTITOUCH is not set
+# CONFIG_HID_NTI is not set
+# CONFIG_HID_NTRIG is not set
+# CONFIG_HID_ORTEK is not set
+# CONFIG_HID_PANTHERLORD is not set
+# CONFIG_HID_PENMOUNT is not set
+# CONFIG_HID_PETALYNX is not set
+# CONFIG_HID_PICOLCD is not set
+# CONFIG_HID_PLANTRONICS is not set
+# CONFIG_HID_PRIMAX is not set
+# CONFIG_HID_RETRODE is not set
+# CONFIG_HID_ROCCAT is not set
+# CONFIG_HID_SAITEK is not set
+# CONFIG_HID_SAMSUNG is not set
+# CONFIG_HID_SPEEDLINK is not set
+# CONFIG_HID_STEAM is not set
+# CONFIG_HID_STEELSERIES is not set
+# CONFIG_HID_SUNPLUS is not set
+# CONFIG_HID_RMI is not set
+# CONFIG_HID_GREENASIA is not set
+# CONFIG_HID_SMARTJOYPLUS is not set
+# CONFIG_HID_TIVO is not set
+# CONFIG_HID_TOPSEED is not set
+# CONFIG_HID_THRUSTMASTER is not set
+# CONFIG_HID_UDRAW_PS3 is not set
+# CONFIG_HID_WACOM is not set
+# CONFIG_HID_XINMO is not set
+# CONFIG_HID_ZEROPLUS is not set
+# CONFIG_HID_ZYDACRON is not set
+# CONFIG_HID_SENSOR_HUB is not set
+# CONFIG_HID_ALPS is not set
+# end of Special HID drivers
+
+#
+# USB HID support
+#
+CONFIG_USB_HID=y
+# CONFIG_HID_PID is not set
+# CONFIG_USB_HIDDEV is not set
+# end of USB HID support
+
+#
+# I2C HID support
+#
+# CONFIG_I2C_HID is not set
+# end of I2C HID support
+
+#
+# Intel ISH HID support
+#
+# CONFIG_INTEL_ISH_HID is not set
+# end of Intel ISH HID support
+# end of HID support
+
+CONFIG_USB_OHCI_LITTLE_ENDIAN=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_COMMON=y
+# CONFIG_USB_ULPI_BUS is not set
+CONFIG_USB_ARCH_HAS_HCD=y
+CONFIG_USB=y
+CONFIG_USB_PCI=y
+# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set
+
+#
+# Miscellaneous USB options
+#
+CONFIG_USB_DEFAULT_PERSIST=y
+# CONFIG_USB_FEW_INIT_RETRIES is not set
+# CONFIG_USB_DYNAMIC_MINORS is not set
+# CONFIG_USB_OTG is not set
+# CONFIG_USB_OTG_PRODUCTLIST is not set
+CONFIG_USB_AUTOSUSPEND_DELAY=2
+# CONFIG_USB_MON is not set
+
+#
+# USB Host Controller Drivers
+#
+# CONFIG_USB_C67X00_HCD is not set
+CONFIG_USB_XHCI_HCD=y
+# CONFIG_USB_XHCI_DBGCAP is not set
+CONFIG_USB_XHCI_PCI=y
+# CONFIG_USB_XHCI_PCI_RENESAS is not set
+# CONFIG_USB_XHCI_PLATFORM is not set
+CONFIG_USB_EHCI_HCD=y
+# CONFIG_USB_EHCI_ROOT_HUB_TT is not set
+CONFIG_USB_EHCI_TT_NEWSCHED=y
+CONFIG_USB_EHCI_PCI=y
+# CONFIG_USB_EHCI_FSL is not set
+# CONFIG_USB_EHCI_HCD_PLATFORM is not set
+# CONFIG_USB_OXU210HP_HCD is not set
+# CONFIG_USB_ISP116X_HCD is not set
+# CONFIG_USB_FOTG210_HCD is not set
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PCI=y
+# CONFIG_USB_OHCI_HCD_PLATFORM is not set
+CONFIG_USB_UHCI_HCD=y
+# CONFIG_USB_SL811_HCD is not set
+# CONFIG_USB_R8A66597_HCD is not set
+# CONFIG_USB_HCD_TEST_MODE is not set
+
+#
+# USB Device Class drivers
+#
+# CONFIG_USB_ACM is not set
+# CONFIG_USB_PRINTER is not set
+# CONFIG_USB_WDM is not set
+# CONFIG_USB_TMC is not set
+
+#
+# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may
+#
+
+#
+# also be needed; see USB_STORAGE Help for more info
+#
+CONFIG_USB_STORAGE=m
+# CONFIG_USB_STORAGE_DEBUG is not set
+# CONFIG_USB_STORAGE_REALTEK is not set
+# CONFIG_USB_STORAGE_DATAFAB is not set
+# CONFIG_USB_STORAGE_FREECOM is not set
+# CONFIG_USB_STORAGE_ISD200 is not set
+# CONFIG_USB_STORAGE_USBAT is not set
+# CONFIG_USB_STORAGE_SDDR09 is not set
+# CONFIG_USB_STORAGE_SDDR55 is not set
+# CONFIG_USB_STORAGE_JUMPSHOT is not set
+# CONFIG_USB_STORAGE_ALAUDA is not set
+# CONFIG_USB_STORAGE_ONETOUCH is not set
+# CONFIG_USB_STORAGE_KARMA is not set
+# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set
+# CONFIG_USB_STORAGE_ENE_UB6250 is not set
+# CONFIG_USB_UAS is not set
+
+#
+# USB Imaging devices
+#
+# CONFIG_USB_MDC800 is not set
+# CONFIG_USB_MICROTEK is not set
+# CONFIG_USBIP_CORE is not set
+# CONFIG_USB_CDNS3 is not set
+# CONFIG_USB_MUSB_HDRC is not set
+# CONFIG_USB_DWC3 is not set
+# CONFIG_USB_DWC2 is not set
+# CONFIG_USB_CHIPIDEA is not set
+# CONFIG_USB_ISP1760 is not set
+
+#
+# USB port drivers
+#
+# CONFIG_USB_SERIAL is not set
+
+#
+# USB Miscellaneous drivers
+#
+# CONFIG_USB_EMI62 is not set
+# CONFIG_USB_EMI26 is not set
+# CONFIG_USB_ADUTUX is not set
+# CONFIG_USB_SEVSEG is not set
+# CONFIG_USB_LEGOTOWER is not set
+# CONFIG_USB_LCD is not set
+# CONFIG_USB_CYPRESS_CY7C63 is not set
+# CONFIG_USB_CYTHERM is not set
+# CONFIG_USB_IDMOUSE is not set
+# CONFIG_USB_FTDI_ELAN is not set
+# CONFIG_USB_APPLEDISPLAY is not set
+# CONFIG_APPLE_MFI_FASTCHARGE is not set
+# CONFIG_USB_SISUSBVGA is not set
+# CONFIG_USB_LD is not set
+# CONFIG_USB_TRANCEVIBRATOR is not set
+# CONFIG_USB_IOWARRIOR is not set
+# CONFIG_USB_TEST is not set
+# CONFIG_USB_EHSET_TEST_FIXTURE is not set
+# CONFIG_USB_ISIGHTFW is not set
+# CONFIG_USB_YUREX is not set
+# CONFIG_USB_EZUSB_FX2 is not set
+# CONFIG_USB_HUB_USB251XB is not set
+# CONFIG_USB_HSIC_USB3503 is not set
+# CONFIG_USB_HSIC_USB4604 is not set
+# CONFIG_USB_LINK_LAYER_TEST is not set
+# CONFIG_USB_CHAOSKEY is not set
+
+#
+# USB Physical Layer drivers
+#
+# CONFIG_NOP_USB_XCEIV is not set
+# CONFIG_USB_ISP1301 is not set
+# end of USB Physical Layer drivers
+
+# CONFIG_USB_GADGET is not set
+# CONFIG_TYPEC is not set
+# CONFIG_USB_ROLE_SWITCH is not set
+# CONFIG_MMC is not set
+# CONFIG_MEMSTICK is not set
+# CONFIG_NEW_LEDS is not set
+# CONFIG_ACCESSIBILITY is not set
+# CONFIG_INFINIBAND is not set
+CONFIG_EDAC_ATOMIC_SCRUB=y
+CONFIG_EDAC_SUPPORT=y
+CONFIG_RTC_LIB=y
+CONFIG_RTC_MC146818_LIB=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_HCTOSYS=y
+CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
+CONFIG_RTC_SYSTOHC=y
+CONFIG_RTC_SYSTOHC_DEVICE="rtc0"
+# CONFIG_RTC_DEBUG is not set
+CONFIG_RTC_NVMEM=y
+
+#
+# RTC interfaces
+#
+CONFIG_RTC_INTF_SYSFS=y
+CONFIG_RTC_INTF_PROC=y
+CONFIG_RTC_INTF_DEV=y
+# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set
+# CONFIG_RTC_DRV_TEST is not set
+
+#
+# I2C RTC drivers
+#
+# CONFIG_RTC_DRV_ABB5ZES3 is not set
+# CONFIG_RTC_DRV_ABEOZ9 is not set
+# CONFIG_RTC_DRV_ABX80X is not set
+# CONFIG_RTC_DRV_DS1307 is not set
+# CONFIG_RTC_DRV_DS1374 is not set
+# CONFIG_RTC_DRV_DS1672 is not set
+# CONFIG_RTC_DRV_MAX6900 is not set
+# CONFIG_RTC_DRV_RS5C372 is not set
+# CONFIG_RTC_DRV_ISL1208 is not set
+# CONFIG_RTC_DRV_ISL12022 is not set
+# CONFIG_RTC_DRV_X1205 is not set
+# CONFIG_RTC_DRV_PCF8523 is not set
+# CONFIG_RTC_DRV_PCF85063 is not set
+# CONFIG_RTC_DRV_PCF85363 is not set
+# CONFIG_RTC_DRV_PCF8563 is not set
+# CONFIG_RTC_DRV_PCF8583 is not set
+# CONFIG_RTC_DRV_M41T80 is not set
+# CONFIG_RTC_DRV_BQ32K is not set
+# CONFIG_RTC_DRV_S35390A is not set
+# CONFIG_RTC_DRV_FM3130 is not set
+# CONFIG_RTC_DRV_RX8010 is not set
+# CONFIG_RTC_DRV_RX8581 is not set
+# CONFIG_RTC_DRV_RX8025 is not set
+# CONFIG_RTC_DRV_EM3027 is not set
+# CONFIG_RTC_DRV_RV3028 is not set
+# CONFIG_RTC_DRV_RV3032 is not set
+# CONFIG_RTC_DRV_RV8803 is not set
+# CONFIG_RTC_DRV_SD3078 is not set
+
+#
+# SPI RTC drivers
+#
+CONFIG_RTC_I2C_AND_SPI=y
+
+#
+# SPI and I2C RTC drivers
+#
+# CONFIG_RTC_DRV_DS3232 is not set
+# CONFIG_RTC_DRV_PCF2127 is not set
+# CONFIG_RTC_DRV_RV3029C2 is not set
+
+#
+# Platform RTC drivers
+#
+CONFIG_RTC_DRV_CMOS=y
+# CONFIG_RTC_DRV_DS1286 is not set
+# CONFIG_RTC_DRV_DS1511 is not set
+# CONFIG_RTC_DRV_DS1553 is not set
+# CONFIG_RTC_DRV_DS1685_FAMILY is not set
+# CONFIG_RTC_DRV_DS1742 is not set
+# CONFIG_RTC_DRV_DS2404 is not set
+# CONFIG_RTC_DRV_STK17TA8 is not set
+# CONFIG_RTC_DRV_M48T86 is not set
+# CONFIG_RTC_DRV_M48T35 is not set
+# CONFIG_RTC_DRV_M48T59 is not set
+# CONFIG_RTC_DRV_MSM6242 is not set
+# CONFIG_RTC_DRV_BQ4802 is not set
+# CONFIG_RTC_DRV_RP5C01 is not set
+# CONFIG_RTC_DRV_V3020 is not set
+
+#
+# on-CPU RTC drivers
+#
+# CONFIG_RTC_DRV_FTRTC010 is not set
+
+#
+# HID Sensor RTC drivers
+#
+# CONFIG_DMADEVICES is not set
+
+#
+# DMABUF options
+#
+CONFIG_SYNC_FILE=y
+# CONFIG_SW_SYNC is not set
+# CONFIG_UDMABUF is not set
+# CONFIG_DMABUF_MOVE_NOTIFY is not set
+# CONFIG_DMABUF_SELFTESTS is not set
+# CONFIG_DMABUF_HEAPS is not set
+# end of DMABUF options
+
+# CONFIG_AUXDISPLAY is not set
+CONFIG_UIO=m
+# CONFIG_UIO_CIF is not set
+# CONFIG_UIO_PDRV_GENIRQ is not set
+# CONFIG_UIO_DMEM_GENIRQ is not set
+# CONFIG_UIO_AEC is not set
+# CONFIG_UIO_SERCOS3 is not set
+# CONFIG_UIO_PCI_GENERIC is not set
+# CONFIG_UIO_NETX is not set
+# CONFIG_UIO_PRUSS is not set
+# CONFIG_UIO_MF624 is not set
+# CONFIG_VFIO is not set
+# CONFIG_VIRT_DRIVERS is not set
+CONFIG_VIRTIO_MENU=y
+# CONFIG_VIRTIO_PCI is not set
+# CONFIG_VIRTIO_MMIO is not set
+# CONFIG_VDPA is not set
+CONFIG_VHOST_MENU=y
+# CONFIG_VHOST_NET is not set
+# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
+
+#
+# Microsoft Hyper-V guest support
+#
+# end of Microsoft Hyper-V guest support
+
+# CONFIG_GREYBUS is not set
+# CONFIG_STAGING is not set
+CONFIG_X86_PLATFORM_DEVICES=y
+CONFIG_ACPI_WMI=y
+CONFIG_WMI_BMOF=y
+# CONFIG_HUAWEI_WMI is not set
+# CONFIG_INTEL_WMI_SBL_FW_UPDATE is not set
+# CONFIG_INTEL_WMI_THUNDERBOLT is not set
+# CONFIG_MXM_WMI is not set
+# CONFIG_PEAQ_WMI is not set
+# CONFIG_XIAOMI_WMI is not set
+# CONFIG_ACERHDF is not set
+# CONFIG_ACER_WIRELESS is not set
+# CONFIG_ACER_WMI is not set
+# CONFIG_APPLE_GMUX is not set
+# CONFIG_ASUS_LAPTOP is not set
+# CONFIG_ASUS_WIRELESS is not set
+# CONFIG_DCDBAS is not set
+# CONFIG_DELL_SMBIOS is not set
+# CONFIG_DELL_RBU is not set
+# CONFIG_DELL_SMO8800 is not set
+# CONFIG_DELL_WMI_AIO is not set
+# CONFIG_FUJITSU_LAPTOP is not set
+# CONFIG_FUJITSU_TABLET is not set
+# CONFIG_GPD_POCKET_FAN is not set
+# CONFIG_HP_ACCEL is not set
+# CONFIG_HP_WIRELESS is not set
+# CONFIG_HP_WMI is not set
+# CONFIG_IBM_RTL is not set
+# CONFIG_SENSORS_HDAPS is not set
+# CONFIG_THINKPAD_ACPI is not set
+# CONFIG_INTEL_ATOMISP2_PM is not set
+# CONFIG_INTEL_HID_EVENT is not set
+# CONFIG_INTEL_MENLOW is not set
+# CONFIG_INTEL_VBTN is not set
+# CONFIG_SURFACE_3_POWER_OPREGION is not set
+# CONFIG_SURFACE_PRO3_BUTTON is not set
+# CONFIG_MSI_WMI is not set
+# CONFIG_SAMSUNG_LAPTOP is not set
+# CONFIG_SAMSUNG_Q10 is not set
+# CONFIG_TOSHIBA_BT_RFKILL is not set
+# CONFIG_TOSHIBA_HAPS is not set
+# CONFIG_TOSHIBA_WMI is not set
+# CONFIG_ACPI_CMPC is not set
+# CONFIG_LG_LAPTOP is not set
+# CONFIG_PANASONIC_LAPTOP is not set
+# CONFIG_SYSTEM76_ACPI is not set
+# CONFIG_TOPSTAR_LAPTOP is not set
+# CONFIG_I2C_MULTI_INSTANTIATE is not set
+CONFIG_INTEL_IPS=y
+# CONFIG_INTEL_RST is not set
+# CONFIG_INTEL_SMARTCONNECT is not set
+
+#
+# Intel Speed Select Technology interface support
+#
+# CONFIG_INTEL_SPEED_SELECT_INTERFACE is not set
+# end of Intel Speed Select Technology interface support
+
+# CONFIG_INTEL_TURBO_MAX_3 is not set
+# CONFIG_INTEL_UNCORE_FREQ_CONTROL is not set
+# CONFIG_INTEL_PMC_CORE is not set
+# CONFIG_INTEL_PUNIT_IPC is not set
+# CONFIG_INTEL_SCU_PCI is not set
+# CONFIG_INTEL_SCU_PLATFORM is not set
+CONFIG_PMC_ATOM=y
+# CONFIG_CHROME_PLATFORMS is not set
+# CONFIG_MELLANOX_PLATFORM is not set
+CONFIG_HAVE_CLK=y
+CONFIG_CLKDEV_LOOKUP=y
+CONFIG_HAVE_CLK_PREPARE=y
+CONFIG_COMMON_CLK=y
+# CONFIG_COMMON_CLK_MAX9485 is not set
+# CONFIG_COMMON_CLK_SI5341 is not set
+# CONFIG_COMMON_CLK_SI5351 is not set
+# CONFIG_COMMON_CLK_SI544 is not set
+# CONFIG_COMMON_CLK_CDCE706 is not set
+# CONFIG_COMMON_CLK_CS2000_CP is not set
+# CONFIG_HWSPINLOCK is not set
+
+#
+# Clock Source drivers
+#
+CONFIG_CLKEVT_I8253=y
+CONFIG_I8253_LOCK=y
+CONFIG_CLKBLD_I8253=y
+# end of Clock Source drivers
+
+CONFIG_MAILBOX=y
+CONFIG_PCC=y
+# CONFIG_ALTERA_MBOX is not set
+# CONFIG_IOMMU_SUPPORT is not set
+
+#
+# Remoteproc drivers
+#
+# CONFIG_REMOTEPROC is not set
+# end of Remoteproc drivers
+
+#
+# Rpmsg drivers
+#
+# CONFIG_RPMSG_QCOM_GLINK_RPM is not set
+# CONFIG_RPMSG_VIRTIO is not set
+# end of Rpmsg drivers
+
+# CONFIG_SOUNDWIRE is not set
+
+#
+# SOC (System On Chip) specific Drivers
+#
+
+#
+# Amlogic SoC drivers
+#
+# end of Amlogic SoC drivers
+
+#
+# Aspeed SoC drivers
+#
+# end of Aspeed SoC drivers
+
+#
+# Broadcom SoC drivers
+#
+# end of Broadcom SoC drivers
+
+#
+# NXP/Freescale QorIQ SoC drivers
+#
+# end of NXP/Freescale QorIQ SoC drivers
+
+#
+# i.MX SoC drivers
+#
+# end of i.MX SoC drivers
+
+#
+# Qualcomm SoC drivers
+#
+# end of Qualcomm SoC drivers
+
+# CONFIG_SOC_TI is not set
+
+#
+# Xilinx SoC drivers
+#
+# CONFIG_XILINX_VCU is not set
+# end of Xilinx SoC drivers
+# end of SOC (System On Chip) specific Drivers
+
+CONFIG_PM_DEVFREQ=y
+
+#
+# DEVFREQ Governors
+#
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+# CONFIG_DEVFREQ_GOV_PERFORMANCE is not set
+# CONFIG_DEVFREQ_GOV_POWERSAVE is not set
+# CONFIG_DEVFREQ_GOV_USERSPACE is not set
+# CONFIG_DEVFREQ_GOV_PASSIVE is not set
+
+#
+# DEVFREQ Drivers
+#
+# CONFIG_PM_DEVFREQ_EVENT is not set
+# CONFIG_EXTCON is not set
+# CONFIG_MEMORY is not set
+# CONFIG_IIO is not set
+# CONFIG_NTB is not set
+# CONFIG_VME_BUS is not set
+# CONFIG_PWM is not set
+
+#
+# IRQ chip support
+#
+# end of IRQ chip support
+
+# CONFIG_IPACK_BUS is not set
+CONFIG_RESET_CONTROLLER=y
+# CONFIG_RESET_TI_SYSCON is not set
+
+#
+# PHY Subsystem
+#
+CONFIG_GENERIC_PHY=y
+# CONFIG_USB_LGM_PHY is not set
+# CONFIG_BCM_KONA_USB2_PHY is not set
+# CONFIG_PHY_PXA_28NM_HSIC is not set
+# CONFIG_PHY_PXA_28NM_USB2 is not set
+# CONFIG_PHY_INTEL_LGM_EMMC is not set
+# end of PHY Subsystem
+
+# CONFIG_POWERCAP is not set
+# CONFIG_MCB is not set
+
+#
+# Performance monitor support
+#
+# end of Performance monitor support
+
+# CONFIG_RAS is not set
+# CONFIG_USB4 is not set
+
+#
+# Android
+#
+# CONFIG_ANDROID is not set
+# end of Android
+
+# CONFIG_LIBNVDIMM is not set
+# CONFIG_DAX is not set
+CONFIG_NVMEM=y
+CONFIG_NVMEM_SYSFS=y
+
+#
+# HW tracing support
+#
+# CONFIG_STM is not set
+# CONFIG_INTEL_TH is not set
+# end of HW tracing support
+
+# CONFIG_FPGA is not set
+# CONFIG_TEE is not set
+CONFIG_PM_OPP=y
+# CONFIG_UNISYS_VISORBUS is not set
+# CONFIG_SIOX is not set
+# CONFIG_SLIMBUS is not set
+# CONFIG_INTERCONNECT is not set
+# CONFIG_COUNTER is not set
+# end of Device Drivers
+
+#
+# File systems
+#
+CONFIG_DCACHE_WORD_ACCESS=y
+# CONFIG_VALIDATE_FS_PARSER is not set
+CONFIG_FS_IOMAP=y
+# CONFIG_EXT2_FS is not set
+# CONFIG_EXT3_FS is not set
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_USE_FOR_EXT2=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+# CONFIG_EXT4_DEBUG is not set
+CONFIG_JBD2=y
+# CONFIG_JBD2_DEBUG is not set
+CONFIG_FS_MBCACHE=y
+# CONFIG_REISERFS_FS is not set
+# CONFIG_JFS_FS is not set
+# CONFIG_XFS_FS is not set
+# CONFIG_GFS2_FS is not set
+# CONFIG_BTRFS_FS is not set
+# CONFIG_NILFS2_FS is not set
+# CONFIG_F2FS_FS is not set
+# CONFIG_FS_DAX is not set
+CONFIG_FS_POSIX_ACL=y
+CONFIG_EXPORTFS=y
+# CONFIG_EXPORTFS_BLOCK_OPS is not set
+CONFIG_FILE_LOCKING=y
+CONFIG_MANDATORY_FILE_LOCKING=y
+# CONFIG_FS_ENCRYPTION is not set
+# CONFIG_FS_VERITY is not set
+CONFIG_FSNOTIFY=y
+CONFIG_DNOTIFY=y
+CONFIG_INOTIFY_USER=y
+CONFIG_FANOTIFY=y
+# CONFIG_FANOTIFY_ACCESS_PERMISSIONS is not set
+# CONFIG_QUOTA is not set
+# CONFIG_AUTOFS4_FS is not set
+# CONFIG_AUTOFS_FS is not set
+# CONFIG_FUSE_FS is not set
+CONFIG_OVERLAY_FS=y
+# CONFIG_OVERLAY_FS_REDIRECT_DIR is not set
+CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW=y
+# CONFIG_OVERLAY_FS_INDEX is not set
+# CONFIG_OVERLAY_FS_XINO_AUTO is not set
+# CONFIG_OVERLAY_FS_METACOPY is not set
+
+#
+# Caches
+#
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+# CONFIG_FSCACHE_HISTOGRAM is not set
+# CONFIG_FSCACHE_DEBUG is not set
+# CONFIG_FSCACHE_OBJECT_LIST is not set
+CONFIG_CACHEFILES=y
+# CONFIG_CACHEFILES_DEBUG is not set
+# CONFIG_CACHEFILES_HISTOGRAM is not set
+# end of Caches
+
+#
+# CD-ROM/DVD Filesystems
+#
+CONFIG_ISO9660_FS=y
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=y
+# end of CD-ROM/DVD Filesystems
+
+#
+# DOS/FAT/EXFAT/NT Filesystems
+#
+CONFIG_FAT_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_CODEPAGE=437
+CONFIG_FAT_DEFAULT_IOCHARSET="utf8"
+# CONFIG_FAT_DEFAULT_UTF8 is not set
+# CONFIG_EXFAT_FS is not set
+# CONFIG_NTFS_FS is not set
+# end of DOS/FAT/EXFAT/NT Filesystems
+
+#
+# Pseudo filesystems
+#
+CONFIG_PROC_FS=y
+CONFIG_PROC_KCORE=y
+CONFIG_PROC_SYSCTL=y
+CONFIG_PROC_PAGE_MONITOR=y
+CONFIG_PROC_CHILDREN=y
+CONFIG_PROC_PID_ARCH_STATUS=y
+CONFIG_KERNFS=y
+CONFIG_SYSFS=y
+CONFIG_TMPFS=y
+# CONFIG_TMPFS_POSIX_ACL is not set
+CONFIG_TMPFS_XATTR=y
+# CONFIG_TMPFS_INODE64 is not set
+CONFIG_HUGETLBFS=y
+CONFIG_HUGETLB_PAGE=y
+CONFIG_MEMFD_CREATE=y
+CONFIG_ARCH_HAS_GIGANTIC_PAGE=y
+# CONFIG_CONFIGFS_FS is not set
+CONFIG_EFIVAR_FS=y
+# end of Pseudo filesystems
+
+CONFIG_MISC_FILESYSTEMS=y
+# CONFIG_ORANGEFS_FS is not set
+# CONFIG_ADFS_FS is not set
+# CONFIG_AFFS_FS is not set
+# CONFIG_ECRYPT_FS is not set
+# CONFIG_HFS_FS is not set
+# CONFIG_HFSPLUS_FS is not set
+# CONFIG_BEFS_FS is not set
+# CONFIG_BFS_FS is not set
+# CONFIG_EFS_FS is not set
+# CONFIG_CRAMFS is not set
+CONFIG_SQUASHFS=y
+CONFIG_SQUASHFS_FILE_CACHE=y
+# CONFIG_SQUASHFS_FILE_DIRECT is not set
+CONFIG_SQUASHFS_DECOMP_SINGLE=y
+# CONFIG_SQUASHFS_DECOMP_MULTI is not set
+# CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU is not set
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_ZLIB=y
+CONFIG_SQUASHFS_LZ4=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+# CONFIG_SQUASHFS_ZSTD is not set
+# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set
+# CONFIG_SQUASHFS_EMBEDDED is not set
+CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3
+# CONFIG_VXFS_FS is not set
+# CONFIG_MINIX_FS is not set
+# CONFIG_OMFS_FS is not set
+# CONFIG_HPFS_FS is not set
+# CONFIG_QNX4FS_FS is not set
+# CONFIG_QNX6FS_FS is not set
+# CONFIG_ROMFS_FS is not set
+CONFIG_PSTORE=y
+CONFIG_PSTORE_DEFLATE_COMPRESS=y
+# CONFIG_PSTORE_LZO_COMPRESS is not set
+# CONFIG_PSTORE_LZ4_COMPRESS is not set
+# CONFIG_PSTORE_LZ4HC_COMPRESS is not set
+# CONFIG_PSTORE_842_COMPRESS is not set
+# CONFIG_PSTORE_ZSTD_COMPRESS is not set
+CONFIG_PSTORE_COMPRESS=y
+CONFIG_PSTORE_DEFLATE_COMPRESS_DEFAULT=y
+CONFIG_PSTORE_COMPRESS_DEFAULT="deflate"
+# CONFIG_PSTORE_CONSOLE is not set
+# CONFIG_PSTORE_PMSG is not set
+# CONFIG_PSTORE_FTRACE is not set
+# CONFIG_PSTORE_RAM is not set
+# CONFIG_SYSV_FS is not set
+# CONFIG_UFS_FS is not set
+# CONFIG_EROFS_FS is not set
+# CONFIG_NETWORK_FILESYSTEMS is not set
+CONFIG_NLS=y
+CONFIG_NLS_DEFAULT="iso8859-1"
+CONFIG_NLS_CODEPAGE_437=y
+# CONFIG_NLS_CODEPAGE_737 is not set
+# CONFIG_NLS_CODEPAGE_775 is not set
+# CONFIG_NLS_CODEPAGE_850 is not set
+# CONFIG_NLS_CODEPAGE_852 is not set
+# CONFIG_NLS_CODEPAGE_855 is not set
+# CONFIG_NLS_CODEPAGE_857 is not set
+# CONFIG_NLS_CODEPAGE_860 is not set
+# CONFIG_NLS_CODEPAGE_861 is not set
+# CONFIG_NLS_CODEPAGE_862 is not set
+# CONFIG_NLS_CODEPAGE_863 is not set
+# CONFIG_NLS_CODEPAGE_864 is not set
+# CONFIG_NLS_CODEPAGE_865 is not set
+# CONFIG_NLS_CODEPAGE_866 is not set
+# CONFIG_NLS_CODEPAGE_869 is not set
+# CONFIG_NLS_CODEPAGE_936 is not set
+# CONFIG_NLS_CODEPAGE_950 is not set
+# CONFIG_NLS_CODEPAGE_932 is not set
+# CONFIG_NLS_CODEPAGE_949 is not set
+# CONFIG_NLS_CODEPAGE_874 is not set
+# CONFIG_NLS_ISO8859_8 is not set
+# CONFIG_NLS_CODEPAGE_1250 is not set
+# CONFIG_NLS_CODEPAGE_1251 is not set
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=y
+# CONFIG_NLS_ISO8859_2 is not set
+# CONFIG_NLS_ISO8859_3 is not set
+# CONFIG_NLS_ISO8859_4 is not set
+# CONFIG_NLS_ISO8859_5 is not set
+# CONFIG_NLS_ISO8859_6 is not set
+# CONFIG_NLS_ISO8859_7 is not set
+# CONFIG_NLS_ISO8859_9 is not set
+# CONFIG_NLS_ISO8859_13 is not set
+# CONFIG_NLS_ISO8859_14 is not set
+# CONFIG_NLS_ISO8859_15 is not set
+# CONFIG_NLS_KOI8_R is not set
+# CONFIG_NLS_KOI8_U is not set
+# CONFIG_NLS_MAC_ROMAN is not set
+# CONFIG_NLS_MAC_CELTIC is not set
+# CONFIG_NLS_MAC_CENTEURO is not set
+# CONFIG_NLS_MAC_CROATIAN is not set
+# CONFIG_NLS_MAC_CYRILLIC is not set
+# CONFIG_NLS_MAC_GAELIC is not set
+# CONFIG_NLS_MAC_GREEK is not set
+# CONFIG_NLS_MAC_ICELAND is not set
+# CONFIG_NLS_MAC_INUIT is not set
+# CONFIG_NLS_MAC_ROMANIAN is not set
+# CONFIG_NLS_MAC_TURKISH is not set
+CONFIG_NLS_UTF8=y
+# CONFIG_UNICODE is not set
+CONFIG_IO_WQ=y
+# end of File systems
+
+#
+# Security options
+#
+CONFIG_KEYS=y
+# CONFIG_KEYS_REQUEST_CACHE is not set
+CONFIG_PERSISTENT_KEYRINGS=y
+CONFIG_TRUSTED_KEYS=m
+CONFIG_ENCRYPTED_KEYS=y
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY_DMESG_RESTRICT=y
+CONFIG_SECURITY=y
+CONFIG_SECURITYFS=y
+CONFIG_SECURITY_NETWORK=y
+CONFIG_PAGE_TABLE_ISOLATION=y
+CONFIG_SECURITY_PATH=y
+CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y
+CONFIG_HARDENED_USERCOPY=y
+# CONFIG_HARDENED_USERCOPY_FALLBACK is not set
+CONFIG_FORTIFY_SOURCE=y
+# CONFIG_STATIC_USERMODEHELPER is not set
+# CONFIG_SECURITY_SELINUX is not set
+# CONFIG_SECURITY_SMACK is not set
+# CONFIG_SECURITY_TOMOYO is not set
+# CONFIG_SECURITY_APPARMOR is not set
+# CONFIG_SECURITY_LOADPIN is not set
+CONFIG_SECURITY_YAMA=y
+# CONFIG_SECURITY_SAFESETID is not set
+# CONFIG_SECURITY_LOCKDOWN_LSM is not set
+# CONFIG_INTEGRITY is not set
+# CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set
+CONFIG_DEFAULT_SECURITY_DAC=y
+CONFIG_LSM="lockdown,yama,loadpin,safesetid,integrity,bpf"
+
+#
+# Kernel hardening options
+#
+
+#
+# Memory initialization
+#
+CONFIG_INIT_STACK_NONE=y
+CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y
+CONFIG_INIT_ON_FREE_DEFAULT_ON=y
+# end of Memory initialization
+# end of Kernel hardening options
+# end of Security options
+
+CONFIG_CRYPTO=y
+
+#
+# Crypto core or helper
+#
+CONFIG_CRYPTO_ALGAPI=y
+CONFIG_CRYPTO_ALGAPI2=y
+CONFIG_CRYPTO_AEAD=y
+CONFIG_CRYPTO_AEAD2=y
+CONFIG_CRYPTO_SKCIPHER=y
+CONFIG_CRYPTO_SKCIPHER2=y
+CONFIG_CRYPTO_HASH=y
+CONFIG_CRYPTO_HASH2=y
+CONFIG_CRYPTO_RNG=y
+CONFIG_CRYPTO_RNG2=y
+CONFIG_CRYPTO_RNG_DEFAULT=y
+CONFIG_CRYPTO_AKCIPHER2=y
+CONFIG_CRYPTO_AKCIPHER=y
+CONFIG_CRYPTO_KPP2=y
+CONFIG_CRYPTO_KPP=y
+CONFIG_CRYPTO_ACOMP2=y
+CONFIG_CRYPTO_MANAGER=y
+CONFIG_CRYPTO_MANAGER2=y
+CONFIG_CRYPTO_USER=y
+CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y
+CONFIG_CRYPTO_GF128MUL=y
+CONFIG_CRYPTO_NULL=y
+CONFIG_CRYPTO_NULL2=y
+# CONFIG_CRYPTO_PCRYPT is not set
+CONFIG_CRYPTO_CRYPTD=y
+CONFIG_CRYPTO_AUTHENC=y
+# CONFIG_CRYPTO_TEST is not set
+CONFIG_CRYPTO_SIMD=y
+CONFIG_CRYPTO_GLUE_HELPER_X86=y
+
+#
+# Public-key cryptography
+#
+CONFIG_CRYPTO_RSA=y
+CONFIG_CRYPTO_DH=y
+# CONFIG_CRYPTO_ECDH is not set
+# CONFIG_CRYPTO_ECRDSA is not set
+# CONFIG_CRYPTO_SM2 is not set
+# CONFIG_CRYPTO_CURVE25519 is not set
+# CONFIG_CRYPTO_CURVE25519_X86 is not set
+
+#
+# Authenticated Encryption with Associated Data
+#
+CONFIG_CRYPTO_CCM=y
+CONFIG_CRYPTO_GCM=y
+CONFIG_CRYPTO_CHACHA20POLY1305=y
+# CONFIG_CRYPTO_AEGIS128 is not set
+# CONFIG_CRYPTO_AEGIS128_AESNI_SSE2 is not set
+CONFIG_CRYPTO_SEQIV=y
+CONFIG_CRYPTO_ECHAINIV=y
+
+#
+# Block modes
+#
+CONFIG_CRYPTO_CBC=y
+# CONFIG_CRYPTO_CFB is not set
+CONFIG_CRYPTO_CTR=y
+CONFIG_CRYPTO_CTS=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_LRW=y
+# CONFIG_CRYPTO_OFB is not set
+CONFIG_CRYPTO_PCBC=y
+CONFIG_CRYPTO_XTS=y
+CONFIG_CRYPTO_KEYWRAP=y
+# CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set
+# CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set
+# CONFIG_CRYPTO_ADIANTUM is not set
+CONFIG_CRYPTO_ESSIV=y
+
+#
+# Hash modes
+#
+CONFIG_CRYPTO_CMAC=y
+CONFIG_CRYPTO_HMAC=y
+CONFIG_CRYPTO_XCBC=y
+CONFIG_CRYPTO_VMAC=y
+
+#
+# Digest
+#
+CONFIG_CRYPTO_CRC32C=y
+CONFIG_CRYPTO_CRC32C_INTEL=y
+CONFIG_CRYPTO_CRC32=y
+CONFIG_CRYPTO_CRC32_PCLMUL=y
+# CONFIG_CRYPTO_XXHASH is not set
+# CONFIG_CRYPTO_BLAKE2B is not set
+# CONFIG_CRYPTO_BLAKE2S is not set
+# CONFIG_CRYPTO_BLAKE2S_X86 is not set
+CONFIG_CRYPTO_CRCT10DIF=y
+# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
+CONFIG_CRYPTO_GHASH=y
+CONFIG_CRYPTO_POLY1305=y
+CONFIG_CRYPTO_POLY1305_X86_64=y
+CONFIG_CRYPTO_MD4=y
+CONFIG_CRYPTO_MD5=y
+CONFIG_CRYPTO_MICHAEL_MIC=y
+CONFIG_CRYPTO_RMD128=y
+CONFIG_CRYPTO_RMD160=y
+CONFIG_CRYPTO_RMD256=y
+CONFIG_CRYPTO_RMD320=y
+CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA1_SSSE3=y
+CONFIG_CRYPTO_SHA256_SSSE3=y
+CONFIG_CRYPTO_SHA512_SSSE3=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_SHA512=y
+# CONFIG_CRYPTO_SHA3 is not set
+# CONFIG_CRYPTO_SM3 is not set
+# CONFIG_CRYPTO_STREEBOG is not set
+CONFIG_CRYPTO_TGR192=y
+CONFIG_CRYPTO_WP512=y
+CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=y
+
+#
+# Ciphers
+#
+CONFIG_CRYPTO_AES=y
+# CONFIG_CRYPTO_AES_TI is not set
+CONFIG_CRYPTO_AES_NI_INTEL=y
+CONFIG_CRYPTO_ANUBIS=y
+CONFIG_CRYPTO_ARC4=y
+CONFIG_CRYPTO_BLOWFISH=y
+CONFIG_CRYPTO_BLOWFISH_COMMON=y
+CONFIG_CRYPTO_BLOWFISH_X86_64=y
+CONFIG_CRYPTO_CAMELLIA=y
+CONFIG_CRYPTO_CAMELLIA_X86_64=y
+CONFIG_CRYPTO_CAMELLIA_AESNI_AVX_X86_64=y
+CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64=y
+CONFIG_CRYPTO_CAST_COMMON=y
+CONFIG_CRYPTO_CAST5=y
+CONFIG_CRYPTO_CAST5_AVX_X86_64=y
+CONFIG_CRYPTO_CAST6=y
+CONFIG_CRYPTO_CAST6_AVX_X86_64=y
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_DES3_EDE_X86_64=y
+CONFIG_CRYPTO_FCRYPT=y
+CONFIG_CRYPTO_KHAZAD=y
+CONFIG_CRYPTO_SALSA20=y
+CONFIG_CRYPTO_CHACHA20=y
+CONFIG_CRYPTO_CHACHA20_X86_64=y
+CONFIG_CRYPTO_SEED=y
+CONFIG_CRYPTO_SERPENT=y
+CONFIG_CRYPTO_SERPENT_SSE2_X86_64=y
+CONFIG_CRYPTO_SERPENT_AVX_X86_64=y
+CONFIG_CRYPTO_SERPENT_AVX2_X86_64=y
+# CONFIG_CRYPTO_SM4 is not set
+CONFIG_CRYPTO_TEA=y
+CONFIG_CRYPTO_TWOFISH=y
+CONFIG_CRYPTO_TWOFISH_COMMON=y
+CONFIG_CRYPTO_TWOFISH_X86_64=y
+CONFIG_CRYPTO_TWOFISH_X86_64_3WAY=y
+CONFIG_CRYPTO_TWOFISH_AVX_X86_64=y
+
+#
+# Compression
+#
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_842=y
+CONFIG_CRYPTO_LZ4=y
+CONFIG_CRYPTO_LZ4HC=y
+# CONFIG_CRYPTO_ZSTD is not set
+
+#
+# Random Number Generation
+#
+CONFIG_CRYPTO_ANSI_CPRNG=y
+CONFIG_CRYPTO_DRBG_MENU=y
+CONFIG_CRYPTO_DRBG_HMAC=y
+# CONFIG_CRYPTO_DRBG_HASH is not set
+# CONFIG_CRYPTO_DRBG_CTR is not set
+CONFIG_CRYPTO_DRBG=y
+CONFIG_CRYPTO_JITTERENTROPY=y
+CONFIG_CRYPTO_USER_API=y
+CONFIG_CRYPTO_USER_API_HASH=y
+CONFIG_CRYPTO_USER_API_SKCIPHER=y
+CONFIG_CRYPTO_USER_API_RNG=y
+# CONFIG_CRYPTO_USER_API_RNG_CAVP is not set
+CONFIG_CRYPTO_USER_API_AEAD=y
+CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE=y
+# CONFIG_CRYPTO_STATS is not set
+CONFIG_CRYPTO_HASH_INFO=y
+
+#
+# Crypto library routines
+#
+CONFIG_CRYPTO_LIB_AES=y
+CONFIG_CRYPTO_LIB_ARC4=y
+# CONFIG_CRYPTO_LIB_BLAKE2S is not set
+CONFIG_CRYPTO_ARCH_HAVE_LIB_CHACHA=y
+CONFIG_CRYPTO_LIB_CHACHA_GENERIC=y
+# CONFIG_CRYPTO_LIB_CHACHA is not set
+# CONFIG_CRYPTO_LIB_CURVE25519 is not set
+CONFIG_CRYPTO_LIB_DES=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=11
+CONFIG_CRYPTO_ARCH_HAVE_LIB_POLY1305=y
+CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y
+# CONFIG_CRYPTO_LIB_POLY1305 is not set
+# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
+CONFIG_CRYPTO_LIB_SHA256=y
+CONFIG_CRYPTO_HW=y
+CONFIG_CRYPTO_DEV_PADLOCK=y
+CONFIG_CRYPTO_DEV_PADLOCK_AES=y
+CONFIG_CRYPTO_DEV_PADLOCK_SHA=y
+# CONFIG_CRYPTO_DEV_ATMEL_ECC is not set
+# CONFIG_CRYPTO_DEV_ATMEL_SHA204A is not set
+# CONFIG_CRYPTO_DEV_CCP is not set
+# CONFIG_CRYPTO_DEV_QAT_DH895xCC is not set
+# CONFIG_CRYPTO_DEV_QAT_C3XXX is not set
+# CONFIG_CRYPTO_DEV_QAT_C62X is not set
+# CONFIG_CRYPTO_DEV_QAT_DH895xCCVF is not set
+# CONFIG_CRYPTO_DEV_QAT_C3XXXVF is not set
+# CONFIG_CRYPTO_DEV_QAT_C62XVF is not set
+# CONFIG_CRYPTO_DEV_NITROX_CNN55XX is not set
+# CONFIG_CRYPTO_DEV_SAFEXCEL is not set
+# CONFIG_CRYPTO_DEV_AMLOGIC_GXL is not set
+CONFIG_ASYMMETRIC_KEY_TYPE=y
+CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y
+# CONFIG_ASYMMETRIC_TPM_KEY_SUBTYPE is not set
+CONFIG_X509_CERTIFICATE_PARSER=y
+# CONFIG_PKCS8_PRIVATE_KEY_PARSER is not set
+CONFIG_PKCS7_MESSAGE_PARSER=y
+
+#
+# Certificates for signature checking
+#
+# CONFIG_SYSTEM_TRUSTED_KEYRING is not set
+# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set
+# end of Certificates for signature checking
+
+CONFIG_BINARY_PRINTF=y
+
+#
+# Library routines
+#
+# CONFIG_PACKING is not set
+CONFIG_BITREVERSE=y
+CONFIG_GENERIC_STRNCPY_FROM_USER=y
+CONFIG_GENERIC_STRNLEN_USER=y
+CONFIG_GENERIC_NET_UTILS=y
+CONFIG_GENERIC_FIND_FIRST_BIT=y
+# CONFIG_CORDIC is not set
+# CONFIG_PRIME_NUMBERS is not set
+CONFIG_RATIONAL=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y
+CONFIG_ARCH_HAS_FAST_MULTIPLIER=y
+CONFIG_ARCH_USE_SYM_ANNOTATIONS=y
+CONFIG_CRC_CCITT=y
+CONFIG_CRC16=y
+CONFIG_CRC_T10DIF=y
+CONFIG_CRC_ITU_T=y
+CONFIG_CRC32=y
+# CONFIG_CRC32_SELFTEST is not set
+CONFIG_CRC32_SLICEBY8=y
+# CONFIG_CRC32_SLICEBY4 is not set
+# CONFIG_CRC32_SARWATE is not set
+# CONFIG_CRC32_BIT is not set
+# CONFIG_CRC64 is not set
+# CONFIG_CRC4 is not set
+# CONFIG_CRC7 is not set
+CONFIG_LIBCRC32C=y
+# CONFIG_CRC8 is not set
+CONFIG_XXHASH=y
+# CONFIG_RANDOM32_SELFTEST is not set
+CONFIG_842_COMPRESS=y
+CONFIG_842_DECOMPRESS=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_LZ4_COMPRESS=y
+CONFIG_LZ4HC_COMPRESS=y
+CONFIG_LZ4_DECOMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
+CONFIG_XZ_DEC=y
+CONFIG_XZ_DEC_X86=y
+CONFIG_XZ_DEC_POWERPC=y
+CONFIG_XZ_DEC_IA64=y
+CONFIG_XZ_DEC_ARM=y
+CONFIG_XZ_DEC_ARMTHUMB=y
+CONFIG_XZ_DEC_SPARC=y
+CONFIG_XZ_DEC_BCJ=y
+# CONFIG_XZ_DEC_TEST is not set
+CONFIG_DECOMPRESS_GZIP=y
+CONFIG_DECOMPRESS_ZSTD=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_TEXTSEARCH=y
+CONFIG_TEXTSEARCH_KMP=y
+CONFIG_TEXTSEARCH_BM=y
+CONFIG_TEXTSEARCH_FSM=y
+CONFIG_INTERVAL_TREE=y
+CONFIG_XARRAY_MULTI=y
+CONFIG_ASSOCIATIVE_ARRAY=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HAS_DMA=y
+CONFIG_NEED_SG_DMA_LENGTH=y
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_ARCH_DMA_ADDR_T_64BIT=y
+CONFIG_SWIOTLB=y
+# CONFIG_DMA_API_DEBUG is not set
+CONFIG_SGL_ALLOC=y
+CONFIG_CPU_RMAP=y
+CONFIG_DQL=y
+CONFIG_GLOB=y
+# CONFIG_GLOB_SELFTEST is not set
+CONFIG_NLATTR=y
+CONFIG_CLZ_TAB=y
+# CONFIG_IRQ_POLL is not set
+CONFIG_MPILIB=y
+CONFIG_OID_REGISTRY=y
+CONFIG_UCS2_STRING=y
+CONFIG_HAVE_GENERIC_VDSO=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_VDSO_TIME_NS=y
+CONFIG_FONT_SUPPORT=y
+# CONFIG_FONTS is not set
+CONFIG_FONT_8x8=y
+CONFIG_FONT_8x16=y
+CONFIG_SG_POOL=y
+CONFIG_ARCH_HAS_PMEM_API=y
+CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
+CONFIG_ARCH_HAS_COPY_MC=y
+CONFIG_ARCH_STACKWALK=y
+CONFIG_SBITMAP=y
+# CONFIG_STRING_SELFTEST is not set
+# end of Library routines
+
+#
+# Kernel hacking
+#
+
+#
+# printk and dmesg options
+#
+CONFIG_PRINTK_TIME=y
+# CONFIG_PRINTK_CALLER is not set
+CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
+CONFIG_CONSOLE_LOGLEVEL_QUIET=4
+CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
+# CONFIG_BOOT_PRINTK_DELAY is not set
+# CONFIG_DYNAMIC_DEBUG is not set
+# CONFIG_DYNAMIC_DEBUG_CORE is not set
+CONFIG_SYMBOLIC_ERRNAME=y
+CONFIG_DEBUG_BUGVERBOSE=y
+# end of printk and dmesg options
+
+#
+# Compile-time checks and compiler options
+#
+CONFIG_DEBUG_INFO=y
+# CONFIG_DEBUG_INFO_REDUCED is not set
+# CONFIG_DEBUG_INFO_COMPRESSED is not set
+CONFIG_DEBUG_INFO_SPLIT=y
+# CONFIG_DEBUG_INFO_DWARF4 is not set
+# CONFIG_GDB_SCRIPTS is not set
+CONFIG_ENABLE_MUST_CHECK=y
+CONFIG_FRAME_WARN=1024
+# CONFIG_STRIP_ASM_SYMS is not set
+# CONFIG_READABLE_ASM is not set
+# CONFIG_HEADERS_INSTALL is not set
+# CONFIG_DEBUG_SECTION_MISMATCH is not set
+CONFIG_SECTION_MISMATCH_WARN_ONLY=y
+CONFIG_STACK_VALIDATION=y
+# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
+# end of Compile-time checks and compiler options
+
+#
+# Generic Kernel Debugging Instruments
+#
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
+CONFIG_MAGIC_SYSRQ_SERIAL=y
+CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE=""
+CONFIG_DEBUG_FS=y
+CONFIG_DEBUG_FS_ALLOW_ALL=y
+# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set
+# CONFIG_DEBUG_FS_ALLOW_NONE is not set
+CONFIG_HAVE_ARCH_KGDB=y
+# CONFIG_KGDB is not set
+CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
+CONFIG_UBSAN=y
+# CONFIG_UBSAN_TRAP is not set
+CONFIG_UBSAN_BOUNDS=y
+CONFIG_UBSAN_MISC=y
+# CONFIG_UBSAN_SANITIZE_ALL is not set
+# CONFIG_UBSAN_ALIGNMENT is not set
+# CONFIG_TEST_UBSAN is not set
+CONFIG_HAVE_ARCH_KCSAN=y
+# end of Generic Kernel Debugging Instruments
+
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_MISC=y
+
+#
+# Memory Debugging
+#
+CONFIG_PAGE_EXTENSION=y
+# CONFIG_DEBUG_PAGEALLOC is not set
+# CONFIG_PAGE_OWNER is not set
+CONFIG_PAGE_POISONING=y
+CONFIG_PAGE_POISONING_NO_SANITY=y
+CONFIG_PAGE_POISONING_ZERO=y
+# CONFIG_DEBUG_PAGE_REF is not set
+# CONFIG_DEBUG_RODATA_TEST is not set
+CONFIG_ARCH_HAS_DEBUG_WX=y
+CONFIG_DEBUG_WX=y
+CONFIG_GENERIC_PTDUMP=y
+CONFIG_PTDUMP_CORE=y
+# CONFIG_PTDUMP_DEBUGFS is not set
+# CONFIG_DEBUG_OBJECTS is not set
+# CONFIG_SLUB_DEBUG_ON is not set
+# CONFIG_SLUB_STATS is not set
+CONFIG_HAVE_DEBUG_KMEMLEAK=y
+# CONFIG_DEBUG_KMEMLEAK is not set
+# CONFIG_DEBUG_STACK_USAGE is not set
+# CONFIG_SCHED_STACK_END_CHECK is not set
+CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y
+# CONFIG_DEBUG_VM is not set
+# CONFIG_DEBUG_VM_PGTABLE is not set
+CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
+# CONFIG_DEBUG_VIRTUAL is not set
+CONFIG_DEBUG_MEMORY_INIT=y
+# CONFIG_DEBUG_PER_CPU_MAPS is not set
+CONFIG_HAVE_ARCH_KASAN=y
+CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
+CONFIG_CC_HAS_KASAN_GENERIC=y
+CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y
+# CONFIG_KASAN is not set
+# end of Memory Debugging
+
+# CONFIG_DEBUG_SHIRQ is not set
+
+#
+# Debug Oops, Lockups and Hangs
+#
+CONFIG_PANIC_ON_OOPS=y
+CONFIG_PANIC_ON_OOPS_VALUE=1
+CONFIG_PANIC_TIMEOUT=0
+CONFIG_LOCKUP_DETECTOR=y
+CONFIG_SOFTLOCKUP_DETECTOR=y
+# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0
+CONFIG_HARDLOCKUP_DETECTOR_PERF=y
+CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
+CONFIG_HARDLOCKUP_DETECTOR=y
+# CONFIG_BOOTPARAM_HARDLOCKUP_PANIC is not set
+CONFIG_BOOTPARAM_HARDLOCKUP_PANIC_VALUE=0
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120
+# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set
+CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0
+CONFIG_WQ_WATCHDOG=y
+# CONFIG_TEST_LOCKUP is not set
+# end of Debug Oops, Lockups and Hangs
+
+#
+# Scheduler Debugging
+#
+CONFIG_SCHED_DEBUG=y
+CONFIG_SCHED_INFO=y
+# CONFIG_SCHEDSTATS is not set
+# end of Scheduler Debugging
+
+# CONFIG_DEBUG_TIMEKEEPING is not set
+
+#
+# Lock Debugging (spinlocks, mutexes, etc...)
+#
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+# CONFIG_PROVE_LOCKING is not set
+# CONFIG_LOCK_STAT is not set
+# CONFIG_DEBUG_RT_MUTEXES is not set
+# CONFIG_DEBUG_SPINLOCK is not set
+# CONFIG_DEBUG_MUTEXES is not set
+# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set
+# CONFIG_DEBUG_RWSEMS is not set
+# CONFIG_DEBUG_LOCK_ALLOC is not set
+# CONFIG_DEBUG_ATOMIC_SLEEP is not set
+# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
+# CONFIG_LOCK_TORTURE_TEST is not set
+# CONFIG_WW_MUTEX_SELFTEST is not set
+# CONFIG_SCF_TORTURE_TEST is not set
+# CONFIG_CSD_LOCK_WAIT_DEBUG is not set
+# end of Lock Debugging (spinlocks, mutexes, etc...)
+
+CONFIG_STACKTRACE=y
+# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
+# CONFIG_DEBUG_KOBJECT is not set
+
+#
+# Debug kernel data structures
+#
+CONFIG_DEBUG_LIST=y
+# CONFIG_DEBUG_PLIST is not set
+# CONFIG_DEBUG_SG is not set
+CONFIG_DEBUG_NOTIFIERS=y
+# CONFIG_BUG_ON_DATA_CORRUPTION is not set
+# end of Debug kernel data structures
+
+CONFIG_DEBUG_CREDENTIALS=y
+
+#
+# RCU Debugging
+#
+# CONFIG_RCU_SCALE_TEST is not set
+# CONFIG_RCU_TORTURE_TEST is not set
+# CONFIG_RCU_REF_SCALE_TEST is not set
+CONFIG_RCU_CPU_STALL_TIMEOUT=60
+# CONFIG_RCU_TRACE is not set
+# CONFIG_RCU_EQS_DEBUG is not set
+# end of RCU Debugging
+
+# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
+# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
+# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set
+# CONFIG_LATENCYTOP is not set
+CONFIG_USER_STACKTRACE_SUPPORT=y
+CONFIG_NOP_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
+CONFIG_HAVE_DYNAMIC_FTRACE=y
+CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
+CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
+CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
+CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
+CONFIG_HAVE_FENTRY=y
+CONFIG_HAVE_C_RECORDMCOUNT=y
+CONFIG_TRACE_CLOCK=y
+CONFIG_RING_BUFFER=y
+CONFIG_EVENT_TRACING=y
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_TRACING=y
+CONFIG_GENERIC_TRACER=y
+CONFIG_TRACING_SUPPORT=y
+CONFIG_FTRACE=y
+# CONFIG_BOOTTIME_TRACING is not set
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_DYNAMIC_FTRACE=y
+CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
+CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+# CONFIG_IRQSOFF_TRACER is not set
+# CONFIG_SCHED_TRACER is not set
+# CONFIG_HWLAT_TRACER is not set
+CONFIG_MMIOTRACE=y
+CONFIG_FTRACE_SYSCALLS=y
+# CONFIG_TRACER_SNAPSHOT is not set
+CONFIG_BRANCH_PROFILE_NONE=y
+# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set
+CONFIG_BLK_DEV_IO_TRACE=y
+CONFIG_UPROBE_EVENTS=y
+CONFIG_DYNAMIC_EVENTS=y
+CONFIG_PROBE_EVENTS=y
+CONFIG_FTRACE_MCOUNT_RECORD=y
+# CONFIG_SYNTH_EVENTS is not set
+# CONFIG_HIST_TRIGGERS is not set
+# CONFIG_TRACE_EVENT_INJECT is not set
+# CONFIG_TRACEPOINT_BENCHMARK is not set
+# CONFIG_RING_BUFFER_BENCHMARK is not set
+# CONFIG_TRACE_EVAL_MAP_FILE is not set
+# CONFIG_FTRACE_STARTUP_TEST is not set
+# CONFIG_RING_BUFFER_STARTUP_TEST is not set
+# CONFIG_MMIOTRACE_TEST is not set
+# CONFIG_PREEMPTIRQ_DELAY_TEST is not set
+# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
+# CONFIG_SAMPLES is not set
+CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
+# CONFIG_STRICT_DEVMEM is not set
+
+#
+# x86 Debugging
+#
+CONFIG_TRACE_IRQFLAGS_SUPPORT=y
+CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y
+CONFIG_X86_VERBOSE_BOOTUP=y
+CONFIG_EARLY_PRINTK=y
+# CONFIG_EARLY_PRINTK_DBGP is not set
+# CONFIG_EARLY_PRINTK_USB_XDBC is not set
+# CONFIG_EFI_PGT_DUMP is not set
+# CONFIG_DEBUG_TLBFLUSH is not set
+CONFIG_HAVE_MMIOTRACE_SUPPORT=y
+# CONFIG_X86_DECODER_SELFTEST is not set
+CONFIG_IO_DELAY_0X80=y
+# CONFIG_IO_DELAY_0XED is not set
+# CONFIG_IO_DELAY_UDELAY is not set
+# CONFIG_IO_DELAY_NONE is not set
+# CONFIG_DEBUG_BOOT_PARAMS is not set
+# CONFIG_CPA_DEBUG is not set
+# CONFIG_DEBUG_ENTRY is not set
+# CONFIG_DEBUG_NMI_SELFTEST is not set
+CONFIG_X86_DEBUG_FPU=y
+# CONFIG_PUNIT_ATOM_DEBUG is not set
+CONFIG_UNWINDER_ORC=y
+# CONFIG_UNWINDER_FRAME_POINTER is not set
+# end of x86 Debugging
+
+#
+# Kernel Testing and Coverage
+#
+# CONFIG_KUNIT is not set
+# CONFIG_NOTIFIER_ERROR_INJECTION is not set
+# CONFIG_FAULT_INJECTION is not set
+CONFIG_ARCH_HAS_KCOV=y
+CONFIG_CC_HAS_SANCOV_TRACE_PC=y
+# CONFIG_KCOV is not set
+CONFIG_RUNTIME_TESTING_MENU=y
+# CONFIG_LKDTM is not set
+# CONFIG_TEST_LIST_SORT is not set
+# CONFIG_TEST_MIN_HEAP is not set
+# CONFIG_TEST_SORT is not set
+# CONFIG_BACKTRACE_SELF_TEST is not set
+# CONFIG_RBTREE_TEST is not set
+# CONFIG_REED_SOLOMON_TEST is not set
+# CONFIG_INTERVAL_TREE_TEST is not set
+# CONFIG_PERCPU_TEST is not set
+# CONFIG_ATOMIC64_SELFTEST is not set
+# CONFIG_TEST_HEXDUMP is not set
+# CONFIG_TEST_STRING_HELPERS is not set
+# CONFIG_TEST_STRSCPY is not set
+# CONFIG_TEST_KSTRTOX is not set
+# CONFIG_TEST_PRINTF is not set
+# CONFIG_TEST_BITMAP is not set
+# CONFIG_TEST_UUID is not set
+# CONFIG_TEST_XARRAY is not set
+# CONFIG_TEST_OVERFLOW is not set
+# CONFIG_TEST_RHASHTABLE is not set
+# CONFIG_TEST_HASH is not set
+# CONFIG_TEST_IDA is not set
+# CONFIG_TEST_LKM is not set
+# CONFIG_TEST_BITOPS is not set
+# CONFIG_TEST_VMALLOC is not set
+# CONFIG_TEST_USER_COPY is not set
+# CONFIG_TEST_BPF is not set
+# CONFIG_TEST_BLACKHOLE_DEV is not set
+# CONFIG_FIND_BIT_BENCHMARK is not set
+# CONFIG_TEST_FIRMWARE is not set
+# CONFIG_TEST_SYSCTL is not set
+# CONFIG_TEST_UDELAY is not set
+# CONFIG_TEST_STATIC_KEYS is not set
+# CONFIG_TEST_KMOD is not set
+# CONFIG_TEST_MEMCAT_P is not set
+# CONFIG_TEST_STACKINIT is not set
+# CONFIG_TEST_MEMINIT is not set
+# CONFIG_TEST_FREE_PAGES is not set
+# CONFIG_TEST_FPU is not set
+# CONFIG_MEMTEST is not set
+# end of Kernel Testing and Coverage
+# end of Kernel hacking
diff --git a/configs/mysql_backup.sh b/configs/mysql_backup.sh
index 0f04fecd..07a4d272 100644
--- a/configs/mysql_backup.sh
+++ b/configs/mysql_backup.sh
@@ -1,5 +1,7 @@
#!/bin/sh
+[ -z "${MYSQL_PASSWORD}" ] && [ -n "${MYSQL_PASSWORD_FILE}" ] && MYSQL_PASSWORD=$(cat "${MYSQL_PASSWORD_FILE}" | tr -d '\n')
+
while true
do
sleep 360
diff --git a/configs/nginx-chbase.sh b/configs/nginx-chbase.sh
new file mode 100755
index 00000000..258e0dbd
--- /dev/null
+++ b/configs/nginx-chbase.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+[ -f "/base_changed" ] && exit 0
+
+[ -z "${FIC_BASEURL}" ] && exit 0
+[ -z "${PATH_STATIC}" ] && { >&2 echo "PATH_STATIC not defined"; exit 1; }
+[ -n "${CURRENT_BASE}" ] || CURRENT_BASE="/"
+
+run() {
+ local NEWBASE=$1
+ local FILE=$2
+
+ if [ -d "${FILE}" ]
+ then
+ for f in "${FILE}/"*
+ do
+ case "${f}" in
+ "${FILE}/"*.html|"${FILE}/"*.js|"${FILE}/"*.css)
+ run "${NEWBASE}" "${f}";;
+ esac
+ done
+ elif [ -f "${FILE}" ]
+ then
+ echo "Updating base path for $FILE..."
+ sed -ri "s@
@
@;s@\"${CURRENT_BASE}_app/@\"${NEWBASE}_app/@;s@base: \"${CURRENT_BASE%/}\"@base: \"${NEWBASE%/}\"@" ${FILE}
+ fi
+}
+
+run "${FIC_BASEURL}" "${PATH_STATIC}"
+touch "/base_changed"
+
+if [ "${FIC_BASEURL}" == "/" ] && [ -f /etc/nginx/conf.d/default.conf ]
+then
+ sed -i "s:location / {:location @notneeded {:" /etc/nginx/conf.d/default.conf
+fi
+
+[ -n "${FIC_CUSTOM_HEAD}" ] && sed -i "s||${FIC_CUSTOM_HEAD}|" "${PATH_STATIC}/index.html"
+[ -n "${FIC_CUSTOM_BODY}" ] && sed -i "s||${FIC_CUSTOM_BODY}