• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package main
5
6import (
7	"bufio"
8	"fmt"
9	"html/template"
10	"io"
11	"io/ioutil"
12	"net"
13	"net/http"
14	_ "net/http/pprof"
15	"os"
16	"path/filepath"
17	"runtime"
18	"sort"
19	"strconv"
20	"strings"
21	"time"
22
23	"github.com/google/syzkaller/pkg/cover"
24	"github.com/google/syzkaller/pkg/log"
25	"github.com/google/syzkaller/pkg/osutil"
26	"github.com/google/syzkaller/prog"
27)
28
29const dateFormat = "Jan 02 2006 15:04:05 MST"
30
31func (mgr *Manager) initHTTP() {
32	http.HandleFunc("/", mgr.httpSummary)
33	http.HandleFunc("/syscalls", mgr.httpSyscalls)
34	http.HandleFunc("/corpus", mgr.httpCorpus)
35	http.HandleFunc("/crash", mgr.httpCrash)
36	http.HandleFunc("/cover", mgr.httpCover)
37	http.HandleFunc("/prio", mgr.httpPrio)
38	http.HandleFunc("/file", mgr.httpFile)
39	http.HandleFunc("/report", mgr.httpReport)
40	http.HandleFunc("/rawcover", mgr.httpRawCover)
41	// Browsers like to request this, without special handler this goes to / handler.
42	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {})
43
44	ln, err := net.Listen("tcp4", mgr.cfg.HTTP)
45	if err != nil {
46		log.Fatalf("failed to listen on %v: %v", mgr.cfg.HTTP, err)
47	}
48	log.Logf(0, "serving http on http://%v", ln.Addr())
49	go func() {
50		err := http.Serve(ln, nil)
51		log.Fatalf("failed to serve http: %v", err)
52	}()
53}
54
55func (mgr *Manager) httpSummary(w http.ResponseWriter, r *http.Request) {
56	data := &UISummaryData{
57		Name:  mgr.cfg.Name,
58		Log:   log.CachedLogOutput(),
59		Stats: mgr.collectStats(),
60	}
61
62	var err error
63	if data.Crashes, err = mgr.collectCrashes(mgr.cfg.Workdir); err != nil {
64		http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError)
65		return
66	}
67
68	if err := summaryTemplate.Execute(w, data); err != nil {
69		http.Error(w, fmt.Sprintf("failed to execute template: %v", err),
70			http.StatusInternalServerError)
71		return
72	}
73}
74
75func (mgr *Manager) httpSyscalls(w http.ResponseWriter, r *http.Request) {
76	data := &UISyscallsData{
77		Name: mgr.cfg.Name,
78	}
79	for c, cc := range mgr.collectSyscallInfo() {
80		data.Calls = append(data.Calls, UICallType{
81			Name:   c,
82			Inputs: cc.count,
83			Cover:  len(cc.cov),
84		})
85	}
86	sort.Sort(UICallTypeArray(data.Calls))
87
88	if err := syscallsTemplate.Execute(w, data); err != nil {
89		http.Error(w, fmt.Sprintf("failed to execute template: %v", err),
90			http.StatusInternalServerError)
91		return
92	}
93}
94
95type CallCov struct {
96	count int
97	cov   cover.Cover
98}
99
100func (mgr *Manager) collectStats() []UIStat {
101	mgr.mu.Lock()
102	defer mgr.mu.Unlock()
103
104	stats := []UIStat{
105		{Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)},
106		{Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)},
107		{Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))},
108		{Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))},
109		{Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"},
110		{Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())},
111	}
112	if mgr.checkResult != nil {
113		stats = append(stats, UIStat{
114			Name:  "syscalls",
115			Value: fmt.Sprint(len(mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox])),
116			Link:  "/syscalls",
117		})
118	}
119
120	secs := uint64(1)
121	if !mgr.firstConnect.IsZero() {
122		secs = uint64(time.Since(mgr.firstConnect))/1e9 + 1
123	}
124
125	intStats := convertStats(mgr.stats.all(), secs)
126	intStats = append(intStats, convertStats(mgr.fuzzerStats, secs)...)
127	sort.Sort(UIStatArray(intStats))
128	stats = append(stats, intStats...)
129	return stats
130}
131
132func convertStats(stats map[string]uint64, secs uint64) []UIStat {
133	var intStats []UIStat
134	for k, v := range stats {
135		val := fmt.Sprintf("%v", v)
136		if x := v / secs; x >= 10 {
137			val += fmt.Sprintf(" (%v/sec)", x)
138		} else if x := v * 60 / secs; x >= 10 {
139			val += fmt.Sprintf(" (%v/min)", x)
140		} else {
141			x := v * 60 * 60 / secs
142			val += fmt.Sprintf(" (%v/hour)", x)
143		}
144		intStats = append(intStats, UIStat{Name: k, Value: val})
145	}
146	return intStats
147}
148
149func (mgr *Manager) collectSyscallInfo() map[string]*CallCov {
150	mgr.mu.Lock()
151	defer mgr.mu.Unlock()
152
153	calls := make(map[string]*CallCov)
154	for _, inp := range mgr.corpus {
155		if calls[inp.Call] == nil {
156			calls[inp.Call] = new(CallCov)
157		}
158		cc := calls[inp.Call]
159		cc.count++
160		cc.cov.Merge(inp.Cover)
161	}
162	return calls
163}
164
165func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) {
166	crashID := r.FormValue("id")
167	crash := readCrash(mgr.cfg.Workdir, crashID, nil, true)
168	if crash == nil {
169		http.Error(w, fmt.Sprintf("failed to read crash info"), http.StatusInternalServerError)
170		return
171	}
172	if err := crashTemplate.Execute(w, crash); err != nil {
173		http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
174		return
175	}
176}
177
178func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) {
179	mgr.mu.Lock()
180	defer mgr.mu.Unlock()
181
182	var data []UIInput
183	call := r.FormValue("call")
184	for sig, inp := range mgr.corpus {
185		if call != inp.Call {
186			continue
187		}
188		p, err := mgr.target.Deserialize(inp.Prog)
189		if err != nil {
190			http.Error(w, fmt.Sprintf("failed to deserialize program: %v", err), http.StatusInternalServerError)
191			return
192		}
193		data = append(data, UIInput{
194			Short: p.String(),
195			Full:  string(inp.Prog),
196			Cover: len(inp.Cover),
197			Sig:   sig,
198		})
199	}
200	sort.Sort(UIInputArray(data))
201
202	if err := corpusTemplate.Execute(w, data); err != nil {
203		http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
204		return
205	}
206}
207
208func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) {
209	mgr.mu.Lock()
210	defer mgr.mu.Unlock()
211
212	if mgr.checkResult == nil {
213		http.Error(w, fmt.Sprintf("machine is not checked yet"), http.StatusInternalServerError)
214		return
215	}
216	if mgr.cfg.Cover {
217		mgr.httpCoverCover(w, r)
218	} else {
219		mgr.httpCoverFallback(w, r)
220	}
221}
222
223func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request) {
224	if mgr.cfg.KernelObj == "" {
225		http.Error(w, fmt.Sprintf("no kernel_obj in config file"), http.StatusInternalServerError)
226		return
227	}
228	var cov cover.Cover
229	if sig := r.FormValue("input"); sig != "" {
230		cov.Merge(mgr.corpus[sig].Cover)
231	} else {
232		call := r.FormValue("call")
233		for _, inp := range mgr.corpus {
234			if call == "" || call == inp.Call {
235				cov.Merge(inp.Cover)
236			}
237		}
238	}
239
240	if err := generateCoverHTML(w, mgr.cfg.KernelObj, mgr.cfg.KernelSrc, mgr.cfg.TargetVMArch, cov); err != nil {
241		http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError)
242		return
243	}
244	runtime.GC()
245}
246
247func (mgr *Manager) httpCoverFallback(w http.ResponseWriter, r *http.Request) {
248	calls := make(map[int][]int)
249	for s := range mgr.maxSignal {
250		id, errno := prog.DecodeFallbackSignal(uint32(s))
251		calls[id] = append(calls[id], errno)
252	}
253	data := &UIFallbackCoverData{}
254	for _, id := range mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox] {
255		errnos := calls[id]
256		sort.Ints(errnos)
257		successful := 0
258		for len(errnos) != 0 && errnos[0] == 0 {
259			successful++
260			errnos = errnos[1:]
261		}
262		data.Calls = append(data.Calls, UIFallbackCall{
263			Name:       mgr.target.Syscalls[id].Name,
264			Successful: successful,
265			Errnos:     errnos,
266		})
267	}
268	sort.Slice(data.Calls, func(i, j int) bool {
269		return data.Calls[i].Name < data.Calls[j].Name
270	})
271
272	if err := fallbackCoverTemplate.Execute(w, data); err != nil {
273		http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
274		return
275	}
276}
277
278func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) {
279	mgr.mu.Lock()
280	defer mgr.mu.Unlock()
281
282	mgr.minimizeCorpus()
283	call := r.FormValue("call")
284	idx := -1
285	for i, c := range mgr.target.Syscalls {
286		if c.CallName == call {
287			idx = i
288			break
289		}
290	}
291	if idx == -1 {
292		http.Error(w, fmt.Sprintf("unknown call: %v", call), http.StatusInternalServerError)
293		return
294	}
295
296	data := &UIPrioData{Call: call}
297	for i, p := range mgr.prios[idx] {
298		data.Prios = append(data.Prios, UIPrio{mgr.target.Syscalls[i].Name, p})
299	}
300	sort.Sort(UIPrioArray(data.Prios))
301
302	if err := prioTemplate.Execute(w, data); err != nil {
303		http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
304		return
305	}
306}
307
308func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) {
309	file := filepath.Clean(r.FormValue("name"))
310	if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") {
311		http.Error(w, "oh, oh, oh!", http.StatusInternalServerError)
312		return
313	}
314	file = filepath.Join(mgr.cfg.Workdir, file)
315	f, err := os.Open(file)
316	if err != nil {
317		http.Error(w, "failed to open the file", http.StatusInternalServerError)
318		return
319	}
320	defer f.Close()
321	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
322	io.Copy(w, f)
323}
324
325func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) {
326	mgr.mu.Lock()
327	defer mgr.mu.Unlock()
328
329	crashID := r.FormValue("id")
330	desc, err := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "description"))
331	if err != nil {
332		http.Error(w, "failed to read description file", http.StatusInternalServerError)
333		return
334	}
335	tag, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.tag"))
336	prog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.prog"))
337	cprog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.cprog"))
338	rep, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.report"))
339
340	commitDesc := ""
341	if len(tag) != 0 {
342		commitDesc = fmt.Sprintf(" on commit %s.", trimNewLines(tag))
343	}
344	fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", trimNewLines(desc), commitDesc)
345	if len(rep) != 0 {
346		fmt.Fprintf(w, "%s\n\n", rep)
347	}
348	if len(prog) == 0 && len(cprog) == 0 {
349		fmt.Fprintf(w, "The bug is not reproducible.\n")
350	} else {
351		fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", prog)
352		if len(cprog) != 0 {
353			fmt.Fprintf(w, "C reproducer:\n%s\n\n", cprog)
354		}
355	}
356}
357
358func (mgr *Manager) httpRawCover(w http.ResponseWriter, r *http.Request) {
359	mgr.mu.Lock()
360	defer mgr.mu.Unlock()
361
362	initCoverOnce.Do(func() { initCoverError = initCover(mgr.cfg.KernelObj, mgr.cfg.TargetArch) })
363	if initCoverError != nil {
364		http.Error(w, initCoverError.Error(), http.StatusInternalServerError)
365		return
366	}
367
368	var cov cover.Cover
369	for _, inp := range mgr.corpus {
370		cov.Merge(inp.Cover)
371	}
372	pcs := make([]uint64, 0, len(cov))
373	for pc := range cov {
374		fullPC := cover.RestorePC(pc, initCoverVMOffset)
375		prevPC := previousInstructionPC(mgr.cfg.TargetVMArch, fullPC)
376		pcs = append(pcs, prevPC)
377	}
378	sort.Slice(pcs, func(i, j int) bool {
379		return pcs[i] < pcs[j]
380	})
381
382	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
383	buf := bufio.NewWriter(w)
384	for _, pc := range pcs {
385		fmt.Fprintf(buf, "0x%x\n", pc)
386	}
387	buf.Flush()
388}
389
390func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) {
391	// Note: mu is not locked here.
392	reproReply := make(chan map[string]bool)
393	mgr.reproRequest <- reproReply
394	repros := <-reproReply
395
396	crashdir := filepath.Join(workdir, "crashes")
397	dirs, err := osutil.ListDir(crashdir)
398	if err != nil {
399		return nil, err
400	}
401	var crashTypes []*UICrashType
402	for _, dir := range dirs {
403		crash := readCrash(workdir, dir, repros, false)
404		if crash != nil {
405			crashTypes = append(crashTypes, crash)
406		}
407	}
408	sort.Sort(UICrashTypeArray(crashTypes))
409	return crashTypes, nil
410}
411
412func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashType {
413	if len(dir) != 40 {
414		return nil
415	}
416	crashdir := filepath.Join(workdir, "crashes")
417	descFile, err := os.Open(filepath.Join(crashdir, dir, "description"))
418	if err != nil {
419		return nil
420	}
421	defer descFile.Close()
422	descBytes, err := ioutil.ReadAll(descFile)
423	if err != nil || len(descBytes) == 0 {
424		return nil
425	}
426	desc := string(trimNewLines(descBytes))
427	stat, err := descFile.Stat()
428	if err != nil {
429		return nil
430	}
431	modTime := stat.ModTime()
432	descFile.Close()
433
434	files, err := osutil.ListDir(filepath.Join(crashdir, dir))
435	if err != nil {
436		return nil
437	}
438	var crashes []*UICrash
439	reproAttempts := 0
440	hasRepro, hasCRepro := false, false
441	reports := make(map[string]bool)
442	for _, f := range files {
443		if strings.HasPrefix(f, "log") {
444			index, err := strconv.ParseUint(f[3:], 10, 64)
445			if err == nil {
446				crashes = append(crashes, &UICrash{
447					Index: int(index),
448				})
449			}
450		} else if strings.HasPrefix(f, "report") {
451			reports[f] = true
452		} else if f == "repro.prog" {
453			hasRepro = true
454		} else if f == "repro.cprog" {
455			hasCRepro = true
456		} else if f == "repro.report" {
457		} else if f == "repro0" || f == "repro1" || f == "repro2" {
458			reproAttempts++
459		}
460	}
461
462	if full {
463		for _, crash := range crashes {
464			index := strconv.Itoa(crash.Index)
465			crash.Log = filepath.Join("crashes", dir, "log"+index)
466			if stat, err := os.Stat(filepath.Join(workdir, crash.Log)); err == nil {
467				crash.Time = stat.ModTime()
468				crash.TimeStr = crash.Time.Format(dateFormat)
469			}
470			tag, _ := ioutil.ReadFile(filepath.Join(crashdir, dir, "tag"+index))
471			crash.Tag = string(tag)
472			reportFile := filepath.Join("crashes", dir, "report"+index)
473			if osutil.IsExist(filepath.Join(workdir, reportFile)) {
474				crash.Report = reportFile
475			}
476		}
477		sort.Sort(UICrashArray(crashes))
478	}
479
480	triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts)
481	return &UICrashType{
482		Description: desc,
483		LastTime:    modTime.Format(dateFormat),
484		ID:          dir,
485		Count:       len(crashes),
486		Triaged:     triaged,
487		Crashes:     crashes,
488	}
489}
490
491func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string {
492	status := ""
493	if hasRepro {
494		status = "has repro"
495		if hasCRepro {
496			status = "has C repro"
497		}
498	} else if reproducing {
499		status = "reproducing"
500	} else if nonReproducible {
501		status = "non-reproducible"
502	}
503	return status
504}
505
506func trimNewLines(data []byte) []byte {
507	for len(data) > 0 && data[len(data)-1] == '\n' {
508		data = data[:len(data)-1]
509	}
510	return data
511}
512
513type UISummaryData struct {
514	Name    string
515	Stats   []UIStat
516	Crashes []*UICrashType
517	Log     string
518}
519
520type UISyscallsData struct {
521	Name  string
522	Calls []UICallType
523}
524
525type UICrashType struct {
526	Description string
527	LastTime    string
528	ID          string
529	Count       int
530	Triaged     string
531	Crashes     []*UICrash
532}
533
534type UICrash struct {
535	Index   int
536	Time    time.Time
537	TimeStr string
538	Log     string
539	Report  string
540	Tag     string
541}
542
543type UIStat struct {
544	Name  string
545	Value string
546	Link  string
547}
548
549type UICallType struct {
550	Name   string
551	Inputs int
552	Cover  int
553}
554
555type UIInput struct {
556	Short string
557	Full  string
558	Calls int
559	Cover int
560	Sig   string
561}
562
563type UICallTypeArray []UICallType
564
565func (a UICallTypeArray) Len() int           { return len(a) }
566func (a UICallTypeArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
567func (a UICallTypeArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
568
569type UIInputArray []UIInput
570
571func (a UIInputArray) Len() int           { return len(a) }
572func (a UIInputArray) Less(i, j int) bool { return a[i].Cover > a[j].Cover }
573func (a UIInputArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
574
575type UIStatArray []UIStat
576
577func (a UIStatArray) Len() int           { return len(a) }
578func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
579func (a UIStatArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
580
581type UICrashTypeArray []*UICrashType
582
583func (a UICrashTypeArray) Len() int           { return len(a) }
584func (a UICrashTypeArray) Less(i, j int) bool { return a[i].Description < a[j].Description }
585func (a UICrashTypeArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
586
587type UICrashArray []*UICrash
588
589func (a UICrashArray) Len() int           { return len(a) }
590func (a UICrashArray) Less(i, j int) bool { return a[i].Time.After(a[j].Time) }
591func (a UICrashArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
592
593var summaryTemplate = template.Must(template.New("").Parse(addStyle(`
594<!doctype html>
595<html>
596<head>
597	<title>{{.Name }} syzkaller</title>
598	{{STYLE}}
599</head>
600<body>
601<b>{{.Name }} syzkaller</b>
602<br>
603<br>
604
605<table>
606	<caption>Stats:</caption>
607	{{range $s := $.Stats}}
608	<tr>
609		<td>{{$s.Name}}</td>
610		{{if $s.Link}}
611			<td><a href="{{$s.Link}}">{{$s.Value}}</a></td>
612		{{else}}
613			<td>{{$s.Value}}</td>
614		{{end}}
615	</tr>
616	{{end}}
617</table>
618<br>
619
620<table>
621	<caption>Crashes:</caption>
622	<tr>
623		<th>Description</th>
624		<th>Count</th>
625		<th>Last Time</th>
626		<th>Report</th>
627	</tr>
628	{{range $c := $.Crashes}}
629	<tr>
630		<td><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td>
631		<td>{{$c.Count}}</td>
632		<td>{{$c.LastTime}}</td>
633		<td>
634			{{if $c.Triaged}}
635				<a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a>
636			{{end}}
637		</td>
638	</tr>
639	{{end}}
640</table>
641<br>
642
643<b>Log:</b>
644<br>
645<textarea id="log_textarea" readonly rows="20">
646{{.Log}}
647</textarea>
648<script>
649	var textarea = document.getElementById("log_textarea");
650	textarea.scrollTop = textarea.scrollHeight;
651</script>
652</body></html>
653`)))
654
655var syscallsTemplate = template.Must(template.New("").Parse(addStyle(`
656<!doctype html>
657<html>
658<head>
659	<title>{{.Name }} syzkaller</title>
660	{{STYLE}}
661</head>
662<body>
663<b>Per-call coverage:</b>
664<br>
665{{range $c := $.Calls}}
666	{{$c.Name}}
667		<a href='/corpus?call={{$c.Name}}'>inputs:{{$c.Inputs}}</a>
668		<a href='/cover?call={{$c.Name}}'>cover:{{$c.Cover}}</a>
669		<a href='/prio?call={{$c.Name}}'>prio</a> <br>
670{{end}}
671</body></html>
672`)))
673
674var crashTemplate = template.Must(template.New("").Parse(addStyle(`
675<!doctype html>
676<html>
677<head>
678	<title>{{.Description}}</title>
679	{{STYLE}}
680</head>
681<body>
682<b>{{.Description}}</b>
683<br><br>
684
685{{if .Triaged}}
686Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a>
687{{end}}
688<br><br>
689
690<table>
691	<tr>
692		<th>#</th>
693		<th>Log</th>
694		<th>Report</th>
695		<th>Time</th>
696		<th>Tag</th>
697	</tr>
698	{{range $c := $.Crashes}}
699	<tr>
700		<td>{{$c.Index}}</td>
701		<td><a href="/file?name={{$c.Log}}">log</a></td>
702		{{if $c.Report}}
703			<td><a href="/file?name={{$c.Report}}">report</a></td>
704		{{else}}
705			<td></td>
706		{{end}}
707		<td>{{$c.TimeStr}}</td>
708		<td>{{$c.Tag}}</td>
709	</tr>
710	{{end}}
711</table>
712</body></html>
713`)))
714
715var corpusTemplate = template.Must(template.New("").Parse(addStyle(`
716<!doctype html>
717<html>
718<head>
719	<title>syzkaller corpus</title>
720	{{STYLE}}
721</head>
722<body>
723{{range $c := $}}
724	<span title="{{$c.Full}}">{{$c.Short}}</span>
725		<a href='/cover?input={{$c.Sig}}'>cover:{{$c.Cover}}</a>
726		<br>
727{{end}}
728</body></html>
729`)))
730
731type UIPrioData struct {
732	Call  string
733	Prios []UIPrio
734}
735
736type UIPrio struct {
737	Call string
738	Prio float32
739}
740
741type UIPrioArray []UIPrio
742
743func (a UIPrioArray) Len() int           { return len(a) }
744func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio }
745func (a UIPrioArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
746
747var prioTemplate = template.Must(template.New("").Parse(addStyle(`
748<!doctype html>
749<html>
750<head>
751	<title>syzkaller priorities</title>
752	{{STYLE}}
753</head>
754<body>
755Priorities for {{$.Call}} <br> <br>
756{{range $p := $.Prios}}
757	{{printf "%.4f\t%s" $p.Prio $p.Call}} <br>
758{{end}}
759</body></html>
760`)))
761
762type UIFallbackCoverData struct {
763	Calls []UIFallbackCall
764}
765
766type UIFallbackCall struct {
767	Name       string
768	Successful int
769	Errnos     []int
770}
771
772var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(`
773<!doctype html>
774<html>
775<head>
776	<title>syzkaller coverage</title>
777	{{STYLE}}
778</head>
779<body>
780<table>
781	<tr>
782		<th>Call</th>
783		<th>Successful</th>
784		<th>Errnos</th>
785	</tr>
786	{{range $c := $.Calls}}
787	<tr>
788		<td>{{$c.Name}}</td>
789		<td>{{if $c.Successful}}{{$c.Successful}}{{end}}</td>
790		<td>{{range $e := $c.Errnos}}{{$e}}&nbsp;{{end}}</td>
791	</tr>
792	{{end}}
793</table>
794</body></html>
795`)))
796
797func addStyle(html string) string {
798	return strings.Replace(html, "{{STYLE}}", htmlStyle, -1)
799}
800
801const htmlStyle = `
802	<style type="text/css" media="screen">
803		table {
804			border-collapse:collapse;
805			border:1px solid;
806		}
807		table caption {
808			font-weight: bold;
809		}
810		table td {
811			border:1px solid;
812			padding: 3px;
813		}
814		table th {
815			border:1px solid;
816			padding: 3px;
817		}
818		textarea {
819			width:100%;
820		}
821	</style>
822`
823