container_apm.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package containers
  2. import (
  3. "bufio"
  4. "bytes"
  5. "debug/elf"
  6. "fmt"
  7. "github.com/coroot/coroot-node-agent/flags"
  8. "os"
  9. "path"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/coroot/coroot-node-agent/ebpftracer"
  15. "github.com/coroot/coroot-node-agent/ebpftracer/l7"
  16. "github.com/coroot/coroot-node-agent/ebpftracer/tracer"
  17. "github.com/coroot/coroot-node-agent/proc"
  18. "github.com/coroot/coroot-node-agent/tracing"
  19. "github.com/coroot/coroot-node-agent/utils"
  20. . "github.com/coroot/coroot-node-agent/utils/modelse"
  21. "github.com/pkg/errors"
  22. klog "github.com/sirupsen/logrus"
  23. "inet.af/netaddr"
  24. )
  25. const (
  26. TRACE_STATUS = 1
  27. )
  28. func (c *Container) getTrace(traceId uint64) (*tracing.Trace, bool) {
  29. trace, ok := c.traceMap[traceId]
  30. return trace, ok
  31. }
  32. func (c *Container) createTraceMap(traceId uint64, trace *tracing.Trace) {
  33. c.traceMap[traceId] = trace
  34. }
  35. // 查询或创建trace信息
  36. func (c *Container) getOrInitTrace(traceId uint64) (*tracing.Trace, error) {
  37. trace, ok := c.getTrace(traceId)
  38. if !ok {
  39. //new trace
  40. trace = tracing.NewTraceFromEvent(string(c.id))
  41. //create TraceMap
  42. c.createTraceMap(traceId, trace)
  43. //create ParentSpan
  44. trace.CreateRootSpan(traceId)
  45. }
  46. return trace, nil
  47. }
  48. func (c *Container) InitTrace(traceId uint64, r *l7.RequestData) error {
  49. method, path, hostIp, port := l7.ParseHttpHost(r.Payload)
  50. ip, err := netaddr.ParseIP(hostIp)
  51. if err != nil {
  52. fmt.Println("host ip error")
  53. hostIp = "127.0.0.1"
  54. }
  55. addr := netaddr.IPPortFrom(ip, port)
  56. trace := tracing.NewTrace(string(c.id), addr)
  57. if trace == nil {
  58. return fmt.Errorf("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is null")
  59. }
  60. c.traceMap[traceId] = trace
  61. trace.TraceStart(method, path, r.Status, r.Duration)
  62. return nil
  63. }
  64. // 在任意阶段,r.TraceId 不等于0 则创建 traceMap && createParentSpan
  65. // 更新 createTraceSpan 机制,更新触发traceEnd机制,当事件个数满足时,任意event均可触发end
  66. func (c *Container) SendEvent(t *tracing.Trace, traceID uint64) {
  67. if t.AllEventReady(traceID) {
  68. t.SendEvent()
  69. klog.Infof("SendEvent %d", traceID)
  70. //fmt.Println(t.GetSpan())
  71. //fmt.Println("===============")
  72. delete(c.traceMap, traceID)
  73. }
  74. }
  75. func (c *Container) valuableTrace(traceID uint64) bool {
  76. return traceID != 0
  77. }
  78. func (c *Container) onL7RequestApm(pid uint32, fd uint64, timestamp uint64, r *l7.RequestData) map[netaddr.IP]string {
  79. c.lock.Lock()
  80. defer c.lock.Unlock()
  81. if r.Protocol == l7.ProtocolDNS {
  82. ip2fqdn, _type, fqdn := c.onDNSRequest(r)
  83. if c.l7Attach && c.valuableTrace(r.TraceId) {
  84. apmTrace, err := c.getOrInitTrace(r.TraceId)
  85. if err == nil {
  86. apmTrace.DNSTraceQueryEvent(r, _type, fqdn)
  87. c.SendEvent(apmTrace, r.TraceId)
  88. }
  89. }
  90. return ip2fqdn
  91. }
  92. //if !c.valuableTrace(r.TraceId) {
  93. // return nil
  94. //}
  95. // klog.Infof("====ProtocolTrace+++++ start==== %d %d", pid, r.TraceId)
  96. // klog.Infof("====ProtocolTrace===== start==== %d %d", r.Protocol == l7.ProtocolTrace, c.l7Attach)
  97. if r.Protocol == l7.ProtocolTrace && c.valuableTrace(r.TraceId) {
  98. // klog.Infof("====ProtocolTrace---- start==== %d %d", pid, r.TraceId)
  99. if r.TraceStart == TRACE_STATUS {
  100. // klog.Infof("====ProtocolTrace start==== %d %d", pid, r.TraceId)
  101. trace, err := c.getOrInitTrace(r.TraceId)
  102. if err == nil {
  103. method, path, hostIp, port := l7.ParseHttpHost(r.Payload)
  104. ip, _ := netaddr.ParseIP(hostIp)
  105. //codeType := c.GetCodeTypeFromCache(pid)
  106. trace.TraceStartEvent(method, path, r.Status, netaddr.IPPortFrom(ip, port), pid, c.GetAppInfo())
  107. c.SendEvent(trace, r.TraceId)
  108. }
  109. return nil
  110. }
  111. if r.TraceEnd == TRACE_STATUS {
  112. klog.Infof("====ProtocolTrace end==== %d %d", pid, r.TraceId)
  113. trace, err := c.getOrInitTrace(r.TraceId)
  114. if err == nil {
  115. trace.TraceEndEvent(r)
  116. c.SendEvent(trace, r.TraceId)
  117. }
  118. return nil
  119. }
  120. }
  121. if r.Protocol == l7.ProtocolHTTP {
  122. if c.l7Attach && c.valuableTrace(r.TraceId) {
  123. method, path, hostIp, port := l7.ParseHttpHost(r.Payload)
  124. apmTrace, err := c.getOrInitTrace(r.TraceId)
  125. //fmt.Println("ProtocolHTTP-----", r.TraceId, err)
  126. if err == nil {
  127. apmTrace.HttpTraceRequestEvent(method, path, hostIp, port, r)
  128. c.SendEvent(apmTrace, r.TraceId)
  129. }
  130. }
  131. //return nil
  132. }
  133. conn := c.connectionsByPidFd[PidFd{Pid: pid, Fd: fd}]
  134. //fmt.Println("l7.connectionsByPidFd", conn, pid, fd)
  135. if conn == nil {
  136. return nil
  137. }
  138. if timestamp != 0 && conn.Timestamp != timestamp {
  139. return nil
  140. }
  141. stats := c.l7Stats.get(r.Protocol, conn.Dest, conn.ActualDest)
  142. //trace := tracing.NewTrace(string(c.id), conn.ActualDest)
  143. switch r.Protocol {
  144. case l7.ProtocolHTTP:
  145. stats.observe(r.Status.Http(), "", r.Duration)
  146. case l7.ProtocolHTTP2:
  147. if conn.http2Parser == nil {
  148. conn.http2Parser = l7.NewHttp2Parser()
  149. }
  150. requests := conn.http2Parser.Parse(r.Method, r.Payload, uint64(r.Duration))
  151. for _, req := range requests {
  152. stats.observe(req.Status.Http(), "", req.Duration)
  153. //trace.Http2Request(req.Method, req.Path, req.Scheme, req.Status, req.Duration)
  154. }
  155. case l7.ProtocolPostgres:
  156. if r.Method != l7.MethodStatementClose {
  157. stats.observe(r.Status.String(), "", r.Duration)
  158. }
  159. //if conn.postgresParser == nil {
  160. // conn.postgresParser = l7.NewPostgresParser()
  161. //}
  162. //query := conn.postgresParser.Parse(r.Payload)
  163. //trace.PostgresQuery(query, r.Status.Error(), r.Duration)
  164. case l7.ProtocolMysql:
  165. if r.Method != l7.MethodStatementClose {
  166. stats.observe(r.Status.String(), "", r.Duration)
  167. }
  168. if c.l7Attach && c.valuableTrace(r.TraceId) {
  169. if conn.mysqlParser == nil {
  170. conn.mysqlParser = l7.NewMysqlParser()
  171. }
  172. query := conn.mysqlParser.Parse(r.Payload, r.StatementId)
  173. //trace.MysqlQuery(query, r.Status.Error(), r.Duration)
  174. //apmTrace, ok := c.getTrace(r.TraceId)
  175. apmTrace, err := c.getOrInitTrace(r.TraceId)
  176. fmt.Println(err)
  177. //fmt.Println("mysql r.TraceId:", r.TraceId)
  178. //fmt.Println("ok:", ok)
  179. //fmt.Println("traceMap:", len(c.traceMap))
  180. if err == nil {
  181. //apmTrace.MysqlTraceQuery(query, r.Status.Error(), r.Duration, conn.ActualDest)
  182. apmTrace.MysqlTraceQueryEvent(query, r, conn.ActualDest)
  183. c.SendEvent(apmTrace, r.TraceId)
  184. }
  185. }
  186. case l7.ProtocolDM:
  187. //统计dm的query次数
  188. stats.observe(r.Status.String(), "", r.Duration)
  189. //是否发送数据
  190. if c.l7Attach && c.valuableTrace(r.TraceId) {
  191. if conn.dmParser == nil {
  192. conn.dmParser = l7.NewDmParser()
  193. }
  194. query := conn.dmParser.Parse(r.Payload, r.StatementId)
  195. apmTrace, err := c.getOrInitTrace(r.TraceId)
  196. if err == nil {
  197. apmTrace.DmTraceQueryEvent(query, r, conn.ActualDest)
  198. c.SendEvent(apmTrace, r.TraceId)
  199. }
  200. }
  201. case l7.ProtocolMemcached:
  202. stats.observe(r.Status.String(), "", r.Duration)
  203. if c.l7Attach && c.valuableTrace(r.TraceId) {
  204. }
  205. //cmd, items := l7.ParseMemcached(r.Payload)
  206. //trace.MemcachedQuery(cmd, items, r.Status.Error(), r.Duration)
  207. case l7.ProtocolRedis:
  208. stats.observe(r.Status.String(), "", r.Duration)
  209. if c.l7Attach && c.valuableTrace(r.TraceId) {
  210. cmd, args := l7.ParseRedis(r.Payload)
  211. //fmt.Println("cmd", cmd)
  212. //fmt.Println("args", args)
  213. //apmTrace, ok := c.getTrace(r.TraceId)
  214. apmTrace, err := c.getOrInitTrace(r.TraceId)
  215. if err == nil {
  216. //apmTrace.RedisTraceQuery(cmd, args, r.Status.Error(), r.Duration)
  217. apmTrace.RedisTraceQueryEvent(cmd, args, r, conn.ActualDest)
  218. c.SendEvent(apmTrace, r.TraceId)
  219. }
  220. }
  221. //trace.RedisQuery(cmd, args, r.Status.Error(), r.Duration)
  222. case l7.ProtocolMongo:
  223. stats.observe(r.Status.String(), "", r.Duration)
  224. if c.l7Attach && c.valuableTrace(r.TraceId) {
  225. }
  226. //query := l7.ParseMongo(r.Payload)
  227. //trace.MongoQuery(query, r.Status.Error(), r.Duration)
  228. case l7.ProtocolKafka, l7.ProtocolCassandra:
  229. stats.observe(r.Status.String(), "", r.Duration)
  230. if c.l7Attach && c.valuableTrace(r.TraceId) {
  231. }
  232. case l7.ProtocolRabbitmq, l7.ProtocolNats:
  233. stats.observe(r.Status.String(), r.Method.String(), 0)
  234. if c.l7Attach && c.valuableTrace(r.TraceId) {
  235. }
  236. case l7.ProtocolDubbo2:
  237. stats.observe(r.Status.String(), "", r.Duration)
  238. if c.l7Attach && c.valuableTrace(r.TraceId) {
  239. }
  240. }
  241. return nil
  242. }
  243. func (c *Container) buildIDs(pid uint32) bool {
  244. c.lock.Lock()
  245. defer c.lock.Unlock()
  246. p := c.processes[pid]
  247. if p != nil {
  248. p.cmdline = string(proc.GetRealCmdline(pid))
  249. }
  250. for address, val := range c.getListens() {
  251. if val == 1 {
  252. ip := address.IP()
  253. if ip.Is4() && !ip.IsLoopback() {
  254. // 获取端口号
  255. port := address.Port()
  256. //c.instanceID.IntVal, c.instanceID.HashtVal, _ =
  257. c.AppInfo.Sn = ip.String()
  258. c.AppInfo.Sport = int(port)
  259. strInstanceID := utils.BuildInt64ID(fmt.Sprintf("%s:%d", ip.String(), port))
  260. c.AppInfo.InstanceIdHash.IntVal, _ = strInstanceID.ToInt64()
  261. c.AppInfo.InstanceIdHash.HashtVal = strInstanceID.ToHashByte()
  262. //c.AppInfo.InstanceId = c.instanceID.IntVal
  263. strAgentID := utils.BuildInt64ID(fmt.Sprintf("%s:%s", strInstanceID, string(proc.GetExe(pid))))
  264. c.AppInfo.AgentId, _ = strAgentID.ToInt64()
  265. c.AppInfo.CodeType = c.GetCodeTypeFromCache(pid)
  266. return true
  267. }
  268. }
  269. }
  270. return false
  271. }
  272. func (c *Container) StackProcess(event ebpftracer.StackEvent, tracer *ebpftracer.Tracer) {
  273. c.lock.Lock()
  274. defer c.lock.Unlock()
  275. // get the associated uprobe
  276. uprobe, err := c.GetUprobe(event, tracer)
  277. if err != nil {
  278. fmt.Println("GetUprobeGetUprobe errer: %v", err)
  279. // log.Errorf("failed to get uprobe for event %+v: %+v", event, err)
  280. return
  281. }
  282. if event.TraceId <= 0 {
  283. fmt.Println("StackProcess TraceId id 0")
  284. // log.Errorf("failed to get uprobe for event %+v: %+v", event, err)
  285. return
  286. }
  287. // fmt.Printf("StackProcess 函数入口开始处理 fun:TraceId:%lld, Funcname:%s, time: %lld\n", event.TraceId, uprobe.Funcname, event.TimeNsEnd-event.TimeNsStart)
  288. stackFun := ebpftracer.StackFunEvent{}
  289. stackFun.Uprobe = &uprobe
  290. stackFun.StackEvent = event
  291. apmTrace, ok := c.getTrace(event.TraceId)
  292. if ok {
  293. apmTrace.FunAdd(stackFun)
  294. }
  295. }
  296. func byteExtractString(nameString [100]byte) string {
  297. n := bytes.IndexByte(nameString[:], 0)
  298. if n == -1 {
  299. n = len(nameString) // 没找到零值,使用数组长度
  300. }
  301. return string(nameString[:n])
  302. }
  303. func (c *Container) StackProcess2(event ebpftracer.StackEvent, tracer *ebpftracer.Tracer) {
  304. c.lock.Lock()
  305. defer c.lock.Unlock()
  306. // get the associated uprobe
  307. switch event.Location {
  308. case 0: // ret
  309. Funcname := ""
  310. if event.Type != uint64(CodeTypeJava) {
  311. uprobe, err := c.GetUprobe(event, tracer)
  312. if err != nil {
  313. fmt.Println("GetUprobeGetUprobe errer: %v", err)
  314. // log.Errorf("failed to get uprobe for event %+v: %+v", event, err)
  315. return
  316. }
  317. Funcname = uprobe.Funcname
  318. } else {
  319. ClassName := byteExtractString(event.ClassName)
  320. MethedName := byteExtractString(event.MethedName)
  321. Funcname = ClassName + "." + MethedName
  322. }
  323. if event.TraceId <= 0 {
  324. fmt.Println("StackProcess TraceId id 0")
  325. // log.Errorf("failed to get uprobe for event %+v: %+v", event, err)
  326. return
  327. }
  328. //fmt.Printf("StackProcess 函数入口开始处理 fun:TraceId:%lld, Funcname:%s, time: %lld\n", event.TraceId, uprobe.Funcname, event.TimeNsEnd-event.TimeNsStart)
  329. apmTrace, err := c.getOrInitTrace(event.TraceId)
  330. if err == nil {
  331. //fmt.Println("append FuncTraceQuery fun:", event.TraceId, uprobe.Funcname, event.Pid)
  332. duration := event.TimeNsEnd - event.TimeNsStart
  333. apmTrace.FuncTraceQuery(Funcname, time.Duration(duration), event.TimeNsStart, event.TimeNsEnd)
  334. c.SendEvent(apmTrace, event.TraceId)
  335. }
  336. }
  337. }
  338. // ResolveAddress returns the symbol(s) and offset of the given address.
  339. func (c *Container) ResolveAddress(addr uint64, symbols []elf.Symbol) (syms []elf.Symbol, offset uint, err error) {
  340. if addr == 0 {
  341. // err = errors.Wrapf(SymbolNotFoundError, "0")
  342. return
  343. }
  344. // symbols, _, err := e.Symbols()
  345. if err != nil {
  346. return
  347. }
  348. idx := sort.Search(len(symbols), func(i int) bool { return symbols[i].Value > addr })
  349. if idx == 0 {
  350. // err = errors.Wrap(SymbolNotFoundError, fmt.Sprintf("%x", addr))
  351. return
  352. }
  353. // why diff symbol may contains the same addr?
  354. sym := symbols[idx-1]
  355. for i := idx - 1; i >= 0 && symbols[i].Value == sym.Value; i-- {
  356. syms = append(syms, symbols[i])
  357. }
  358. for i := idx; i < len(symbols) && symbols[i].Value == sym.Value; i++ {
  359. syms = append(syms, symbols[i])
  360. }
  361. return syms, uint(addr - sym.Value), nil
  362. }
  363. type MemoryMap struct {
  364. Start, End uint64
  365. }
  366. // ReadFirstLineOfMapsFile reads the first line of /proc/<pid>/maps file and return the memory map as a MemoryMap struct
  367. func ReadFirstLineOfMapsFile(pid string) (*MemoryMap, error) {
  368. file, err := os.Open(fmt.Sprintf("/proc/%s/maps", pid))
  369. if err != nil {
  370. return nil, err
  371. }
  372. defer file.Close()
  373. scanner := bufio.NewScanner(file)
  374. if scanner.Scan() {
  375. fields := strings.Fields(scanner.Text())
  376. addresses := strings.Split(fields[0], "-")
  377. if len(addresses) != 2 {
  378. return nil, errors.New("unexpected format in /proc/<pid>/maps")
  379. }
  380. start, err := strconv.ParseUint(addresses[0], 16, 64)
  381. if err != nil {
  382. return nil, err
  383. }
  384. end, err := strconv.ParseUint(addresses[1], 16, 64)
  385. if err != nil {
  386. return nil, err
  387. }
  388. return &MemoryMap{
  389. Start: start,
  390. End: end,
  391. }, nil
  392. }
  393. if err := scanner.Err(); err != nil {
  394. return nil, err
  395. }
  396. return nil, errors.New("empty /proc/<pid>/maps")
  397. }
  398. func (c *Container) GetUprobe(event ebpftracer.StackEvent, tracer *ebpftracer.Tracer) (uprobe tracer.Uprobe, err error) {
  399. //fmt.Println("GetUprobe entory:")
  400. memoryMap, _ := ReadFirstLineOfMapsFile(strconv.Itoa(int(event.Pid)))
  401. Address := event.Ip - memoryMap.Start
  402. // fmt.Printf("memoryMap.Start: %x, event.Ip: %x, Address: %x\n", memoryMap.Start, event.Ip, Address)
  403. for _, fun := range c.UprobesMap {
  404. funAddress := fun.Address + fun.AbsOffset
  405. // fmt.Printf("GetUprobeGetUprobeGetUprobe:fun.Address %x, fun.AbsOffset: %x\n", fun.Address, fun.AbsOffset)
  406. if funAddress == Address {
  407. // fmt.Printf("---GetUprobeGetUprobeGetUprobe: %x, event.Ip: %x ---- %s--%x\n", memoryMap.Start, event.Ip, fun.Funcname, fun.Address)
  408. return fun, nil
  409. }
  410. }
  411. syms, _, err := c.ResolveAddress(event.Ip, tracer.Symbols)
  412. if err != nil {
  413. return
  414. }
  415. for _, sym := range syms {
  416. //fmt.Println("GetUprobeGetUprobeGetUprobe: %s+%d", sym.Name, offset)
  417. uprobe, ok := tracer.UprobesMap[fmt.Sprintf("%s-%s", sym.Name, sym.Value)]
  418. if ok {
  419. return uprobe, nil
  420. }
  421. }
  422. err = errors.New("uprobe not found")
  423. return
  424. }
  425. func (c *Container) GetAppInfo() AppInfo {
  426. return c.AppInfo
  427. }
  428. // 可注入前置
  429. func (c *Container) checkEventReady() bool {
  430. c.lock.Lock()
  431. defer c.lock.Unlock()
  432. return c.l7EventReady
  433. }
  434. func (c *Container) eventReady() {
  435. c.lock.Lock()
  436. defer c.lock.Unlock()
  437. c.l7EventReady = true
  438. }
  439. // uprobe前置
  440. func (c *Container) checkL7AttachReady() bool {
  441. c.lock.Lock()
  442. defer c.lock.Unlock()
  443. return c.l7Attach
  444. }
  445. func (c *Container) l7AttachSuccess() {
  446. c.lock.Lock()
  447. defer c.lock.Unlock()
  448. c.l7Attach = true
  449. }
  450. func (c *Container) verifyAttachConditions(r *Registry, pid uint32) bool {
  451. p := c.processes[pid]
  452. if p != nil && c.checkEventReady() {
  453. codeType := c.GetCodeTypeFromCache(pid)
  454. if codeType.IsUnknownCode() {
  455. klog.WithField("pid", pid).Debug("[verify] unknown language.")
  456. return false
  457. }
  458. cmdline := p.GetCmdline()
  459. if len(cmdline) == 0 {
  460. return false
  461. }
  462. whiteListByCode := r.getWhiteListByCodeType(codeType)
  463. //klog.WithField("pid", pid).WithField("codeType", codeType.String()).
  464. // Infof("[verify] white list %v", utils.ToString(whiteListByCode))
  465. // 当前语言的白名单规则
  466. for _, setting := range whiteListByCode {
  467. ruleVal := setting.Filters
  468. if ruleVal == "" {
  469. continue
  470. }
  471. // 判断规则
  472. if strings.Contains(cmdline, ruleVal) {
  473. c.WhiteSettingInfo = setting
  474. klog.WithField("pid", pid).
  475. WithField("codeType", codeType.String()).
  476. WithField("ruleVal", ruleVal).
  477. WithField("cmdline", cmdline).
  478. WithField("white list", utils.ToString(whiteListByCode)).
  479. Infoln("[verify] check successful.")
  480. return true
  481. }
  482. }
  483. }
  484. return false
  485. }
  486. func (c *Container) detachUprobes(pid uint32) {
  487. c.lock.Lock()
  488. defer c.lock.Unlock()
  489. // close uprobe
  490. if p := c.processes[pid]; p != nil {
  491. if len(p.uprobes) > 0 {
  492. klog.Infof("detachUprobes", pid)
  493. // 关闭应用层uprobes
  494. p.DynamicClose()
  495. // 关闭7层监控
  496. c.l7Attach = false
  497. // 变更应用为卸载状态
  498. c.AppInfo.AppUninstall()
  499. }
  500. }
  501. }
  502. func (c *Container) getRootfs() string {
  503. if c.metadata != nil && c.metadata.rootfs != "" {
  504. return path.Join(*flags.HostDirPathPrefix, c.metadata.rootfs)
  505. }
  506. return ""
  507. }