-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
557 lines (444 loc) · 20 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
<!doctype html>
<html lang="en">
<head>
<meta name="generator" content="Hugo 0.102.3" />
<meta charset="utf-8">
<title>reveal-hugo</title>
<meta name="description" content="A Hugo theme for creating Reveal.js presentations">
<meta name="author" content="Josh Dzielak">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="/reveal-js/css/reset.css">
<link rel="stylesheet" href="/reveal-js/css/reveal.css"><link rel="stylesheet" href="/reveal-hugo/themes/robot-lung.css" id="theme"><link rel="stylesheet" href="/highlight-js/color-brewer.min.css">
<style>
.reveal section pre {
box-shadow: none;
margin-top: 25px;
margin-bottom: 25px;
border: 1px solid lightgrey;
}
.reveal section pre:hover {
border: 1px solid grey;
transition: border 0.3s ease;
}
.reveal section pre > code {
padding: 10px;
}
.reveal table {
font-size: 0.65em;
}
.reveal section.side-by-side h1 {
position: absolute;
}
.reveal section.side-by-side h1:first-of-type {
left: 25%;
}
.reveal section.side-by-side h1:nth-of-type(2) {
right: 25%;
}
.reveal section[data-background-image] a,
.reveal section[data-background-image] p,
.reveal section[data-background-image] h2 {
color: white;
}
.reveal section[data-background-image] a {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<section><h1 id="heading">📽️</h1>
<h3 id="vs-code-너가-있어-편리한-git-덤으로-보는-오픈소스-기여-">VS Code 너가 있어 편리한 Git, 덤으로 보는 오픈소스 기여 🤗</h3>
<p>10분 안에 살펴보는 VS Code → Github or Mailing 기반 오픈소스 기여하기</p>
<p>인지기술팀 김윤성</p>
</section>
<section><h2 id="vs-code-를-활용한-git">VS Code 를 활용한 Git</h2>
<ul>
<li>마이크로소프트 윈도우, macOS, 리눅스용으로 electron(node.js) 기반 편집기입니다.</li>
<li>Plugin 을 추가하여 풍부한 기능을 가진 IDE 로 사용할 수 있습니다.</li>
<li>VS Code 자체적으로 지원하는 Git 기능이 참 편리합니다.
<ul>
<li>철권처럼 커맨드를 조합하여 사용하는 기능을 버튼 하나로 함축했습니다.</li>
</ul>
</li>
<li>추가적으로 Git 기능 외에도 SSH 또는 Live Share 원격 개발 환경 및 멀티 유저 편집이 가능합니다.</li>
</ul>
</section><section>
<h2 id="git-command">Git Command</h2>
<img src="0010-git-cheetsheet.png" alt="그림 2 : Git 커맨드 요약본" width="600">
</section><section>
<h2 id="vs-code-를-활용한-git-1">VS Code 를 활용한 Git</h2>
<img src="0010-vscode-git.png" alt="그림 1 : VS Code 에서 Git" width="500">
</section><section>
<h2 id="git-history">Git History</h2>
<ul>
<li>History 그래프를 볼 때 유용한 Git History 플러그인</li>
<li>커밋 히스토리를 보기 위한 <code>git log</code>, 특정 커밋의 시점으로 바꾸는 <code>git checkout</code> 에 대응하는 기능인데 직관적으로 보기 편합니다.</li>
</ul>
<br>
<p><a href="http://www.youtube.com/watch?feature=player_embedded&v=hW6mhIaZjgw
" target="_blank"><img src="http://img.youtube.com/vi/hW6mhIaZjgw/0.jpg"
alt="Git History" width="400" height="300" /></a></p>
</section><section>
<h2 id="git-lens">Git Lens</h2>
<ul>
<li>코드 라인 & 파일 별 히스토리 추적에 용이한 Git Lens 플러그인</li>
</ul>
<br>
<p><a href="http://www.youtube.com/watch?feature=player_embedded&v=WzIpMlS89HI
" target="_blank"><img src="http://img.youtube.com/vi/WzIpMlS89HI/0.jpg"
alt="Git Lens" width="400" height="300" /></a></p>
</section><section>
<h2 id="contributing">CONTRIBUTING</h2>
<ul>
<li>오픈소스 프로젝트
<ul>
<li><em>Github</em>, Gitlab, cgit 등의 git 기반 호스팅에 레포지토리들</li>
</ul>
</li>
<li>각 프로젝트에 Core, Util, Docs, unit-test, run-test, CI/CD 등에 기여합니다.
<ul>
<li>
<ol>
<li>게시판 형태의 플랫폼은 issue 를 확인</li>
</ol>
</li>
<li>
<ol start="2">
<li>메일링 기반은 메일링 등록</li>
</ol>
</li>
</ul>
</li>
<li>wiki 또는 소스 최상단 컨트리뷰션 가이드 문서 예시입니다.
<ul>
<li><a href="https://github.com/pytorch/pytorch/blob/master/CONTRIBUTING.md">pytorch - CONTRIBUTING.md</a></li>
<li><a href="https://github.com/microsoft/ebpf-for-windows/blob/main/CONTRIBUTING.md">ebpf-for-windows - CONTRIBUTING.md</a></li>
<li><a href="https://www.kernel.org/doc/html/latest/process/maintainer-tip.html?highlight=x86#patch-submission-notes">Linux Kernel - Patch submission notes</a></li>
</ul>
</li>
</ul>
</section><section>
<h2 id="contribution---review">Contribution - Review</h2>
<p>국제적으로 메일링 기록이 남기 때문에,<br>
메인테이너들이 사소한 패치라도 신중하게 리뷰해줍니다.</p>
<br>
<p>특히, Pull Request(패치)를 보낸 기여자를 설득하기 위해서<br>
어려운 동작을 자세히 설명해주어 많은 도움이 되었습니다.</p>
</section><section>
<h2 id="github-uftrace---commit">Github Uftrace - Commit</h2>
<p>코드 정적 분석기로 나온 항목 중에 Null 관련 보안 사항을 확인한 후 패치를 해봅니다.</p>
<pre><code class="language-patch">cmds/graph.c:282: error: Null Dereference
pointer `graph` last assigned on line 279 could be null
and is dereferenced at line 282, column 3.
281. if (tg->utg.graph && tg->utg.graph != graph) {
282. pr_dbg("detect new session: %.*s\n", SESSION_ID_LEN, graph->sess->sid);
^
</code></pre>
<p>깃허브 이슈로 등록한 뒤에 해당 사항을 보완하는 작업을 진행합니다.<br>
<a href="http://www.youtube.com/watch?feature=player_embedded&v=JDUcRbE1AsU
" target="_blank"><img src="http://img.youtube.com/vi/JDUcRbE1AsU/0.jpg"
alt="Commit" width="300" height="240" /></a></p>
</section><section>
<h3 id="before-pr">Before PR</h3>
<p>커밋을 하고 난 뒤에 <em>마지막 커밋 실행 취소</em>로 쉽게 스테이징 단계로 되돌아 올 수 있습니다.<br>
코드리뷰를 받고 보완할 부분이 있다면, 활용하면 좋습니다.</p>
<p><a href="http://www.youtube.com/watch?feature=player_embedded&v=TIVsQCtw9ME
" target="_blank"><img src="http://img.youtube.com/vi/TIVsQCtw9ME/0.jpg"
alt="Before PR" width="450" height="400" /></a></p>
</section><section>
<h3 id="before-pr-1">Before PR</h3>
<p>커밋하고 푸쉬로 origin 브랜치에 반영되었는데 또 수정이 필요하다면?<br>
수정 커밋을 origin에 올라간 브랜치에 쌓지 않고(보통 커밋 히스토리 관리하기 때문에 좋아하지 않습니다.) 마지막 커밋을 수정하여 <code>git push -f</code> 로 덮어 써줍니다.</p>
<img src="0022-commit-rollback-update-origin-branch.png" alt="commit-rollback-update-origin-branch.png" width="600">
</section><section>
<h2 id="pr">PR</h2>
<p>fork 한 브랜치(Downstream)를 origin(Upstream)에 머지할 수 있도록 Pull Request 을 작성하는 단계입니다. Pull Request 는 당김을 요청한다. 즉, 오픈소스 메인테이너에게 내 소스를 잡아 당겨서 넣으라는 의미입니다.</p>
<p><a href="http://www.youtube.com/watch?feature=player_embedded&v=wr6dkL0FGPA
" target="_blank"><img src="http://img.youtube.com/vi/wr6dkL0FGPA/0.jpg"
alt="PR" width="600" height="480" /></a></p>
</section><section>
<h2 id="linux-kernel---send-patch">Linux Kernel - Send Patch</h2>
<p>소스 최상단의 <code>MAINTAINERS</code> 참고하여 github 와 1 ~ 2 와 유사하지만, Pull Request과 코드 리뷰를 모두 담당자의 메일로 보냅니다.</p>
<pre><code class="language-bash">git send-email --smtp-pass="비밀번호" \
--to="메인테이너@이메일.주소" \
--cc="참조할@메일.주소들" \
--confirm=always -M -1
</code></pre>
<p>뒤에 카운트는 작업한 커밋 개수를 넣습니다.</p>
<pre><code class="language-bash">git send-email \
--to="Thomas Gleixner <[email protected]>, Marc Zyngier <[email protected]>"
--cc="[email protected], Austin Kim <[email protected]>"
--confirm=always -M -1
</code></pre>
</section><section>
<h3 id="send-patchcontd">Send Patch(Cont’d)</h3>
<p>커밋 메세지를 작성할 때, 영어 작문 실력이 필요하지만,<br>
우리에겐 <em>구글 번역기</em>가 있습니다.</p>
<pre><code class="language-patch">Hello
Since we have a macro defined in our IRQ subsystem internal functions to
traverse the list of actions, how about refactoring this loop?
- genirq: Use a common macro to go through the actions list
(f944b5a7aff05a244a6c8cac297819af09a199e4)
have a good day!
---
kernel/irq/irqdesc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index 939d21cd55c3..34a0cefff712 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -246,12 +246,12 @@ static ssize_t actions_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
- struct irqaction *action;
+ struct irqaction *action = NULL;
ssize_t ret = 0;
char *p = "";
raw_spin_lock_irq(&desc->lock);
- for (action = desc->action; action != NULL; action = action->next) {
+ for_each_action_of_desc(desc, action) {
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%s",
p, action->name);
p = ",";
--
</code></pre>
</section><section>
<h3 id="maintainers-code-review">Maintainer’s Code Review</h3>
<p>해당 서브시스템의 메인테이너가 보낸 패치에 대해서 보완해야할 사항을 리뷰해줍니다.</p>
<pre><code class="language-patch">Re: [PATCH] genirq: Refactor actions_show loop block using a common macro to go through the actions list
- by Thomas Gleixner @ 2022-04-10 19:17 UTC [7%]
On Fri, Apr 08 2022 at 20:41, you wrote:
thanks for providing this patch.
> Hello.
>
> Since we have a macro defined in our IRQ subsystem internal functions to
> traverse the list of actions, how about refactoring this loop?
>
> - genirq: Use a common macro to go through the actions list
> (f944b5a7aff05a244a6c8cac297819af09a199e4)
>
> have a good day!
Neither 'Hello' nor 'have a good day' are part of the change log.
Also please write the changelog in a factual way and not in form of a
question. If you want to add a reference to a git commit, then please
use the canonical form as described in Documentation/process, where you
also find the general patch submission rules. There is also a tip tree
specific chapter:
https://www.kernel.org/doc/html/latest/process/maintainer-tip.html?highlight=x86#patch-submission-notes
Following these rules makes everyones life simpler.
> ---
> kernel/irq/irqdesc.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
> index 939d21cd55c3..34a0cefff712 100644
> --- a/kernel/irq/irqdesc.c
> +++ b/kernel/irq/irqdesc.c
> @@ -246,12 +246,12 @@ static ssize_t actions_show(struct kobject *kobj,
> struct kobj_attribute *attr, char *buf)
> {
> struct irq_desc *desc = container_of(kobj, struct irq_desc, kobj);
> - struct irqaction *action;
> + struct irqaction *action = NULL;
There is no NULL initialization required.
Thanks,
tglx
</code></pre>
</section><section>
<h3 id="resend-patch-v2">Resend Patch V2</h3>
<p>보완한 패치를 다시 보냅니다.<br>
충분하다면 머지 예정을 알려주고, 또는 추가 보완 사항을 알려줍니다.</p>
<pre><code class="language-patch">Refactor for loop to macro for_each_action_of_desc
---
kernel/irq/irqdesc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index d323b180b0f3..5db0230aa6b5 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -251,7 +251,7 @@ static ssize_t actions_show(struct kobject *kobj,
char *p = "";
raw_spin_lock_irq(&desc->lock);
- for (action = desc->action; action != NULL; action = action->next) {
+ for_each_action_of_desc(desc, action) {
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%s",
p, action->name);
p = ",";
</code></pre>
</section><section>
<h3 id="linux-kernel---check-patch-merge">Linux Kernel - Check Patch Merge</h3>
<p>보낸 패치는 v6.0-rc1 에서 <code>[GIT pull] irq/core for v6.0-rc1 - by Thomas Gleixner @ 2022-08-01 14:48 UTC [1%]</code> 머지되었습니다.</p>
<pre><code class="language-patch">The following commit has been merged into the irq/irqchip-next branch of irqchip:
Commit-ID: c904cda04482d5ab545e5a82cee6084078ef9543
Gitweb: https://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms/c904cda04482d5ab545e5a82cee6084078ef9543
AuthorDate: Sun, 10 Jul 2022 20:26:14 +09:00
Committer: Marc Zyngier <[email protected]>
CommitterDate: Wed, 20 Jul 2022 15:21:32 +01:00
genirq: Use for_each_action_of_desc in actions_show()
Refactor action_show() to use for_each_action_of_desc instead
of a similar open-coded loop.
[maz: reword commit message]
Signed-off-by: Marc Zyngier <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
---
kernel/irq/irqdesc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index d323b18..5db0230 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -251,7 +251,7 @@ static ssize_t actions_show(struct kobject *kobj,
char *p = "";
raw_spin_lock_irq(&desc->lock);
- for (action = desc->action; action != NULL; action = action->next) {
+ for_each_action_of_desc(desc, action) {
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%s%s",
p, action->name);
p = ",";
</code></pre>
</section><section>
<h1 id="heading">🤗</h1>
<p>이상입니다. ^^7 고생하셨습니다!</p>
</section><section>
<h3 id="contribution---ps">Contribution - P.S.</h3>
<p>기억에 남는 몇 가지 패치 항목들입니다.</p>
<ol>
<li><code>Xen Hypervisor</code> 암묵적인 casting 으로 실제 코드가 길어지는 현상</li>
</ol>
Well... I don't think the one less operation is because of introduction
of the local variable (see more below).
>
> (1) before clean up
>
> 0000000000001bb4 <p2m_set_entry>:
> while ( nr )
> 1bb4: b40005e2 cbz x2, 1c70 <p2m_set_entry+0xbc>
> {
> ...
> if ( rc )
> 1c1c: 350002e0 cbnz w0, 1c78 <p2m_set_entry+0xc4>
> sgfn = gfn_add(sgfn, (1 << order));
1 << order is a 32-bit value but the second parameter is a 64-bit value
(assuming arm64). So...
> 1c20: 1ad32373 lsl w19, w27, w19 // <<< CES works
> 1c24: 93407e73 sxtw x19, w19 // <<< well!
... this instruction is extending the 32-bit value to 64-bit value.
> return _gfn(gfn_x(gfn) + i);
> 1c28: 8b1302d6 add x22, x22, x19
> return _mfn(mfn_x(mfn) + i);
> 1c2c: 8b130281 add x1, x20, x19
> 1c30: b100069f cmn x20, #0x1
> 1c34: 9a941034 csel x20, x1, x20, ne // ne = any
> while ( nr )
> 1c38: eb1302b5 subs x21, x21, x19
> 1c3c: 540001e0 b.eq 1c78 <p2m_set_entry+0xc4> // b.none
>
> (2) Using again mask variable. mask = 1UL << order
> code show me sxtw x19, w19 operation disappeared.
This code is not only using a local variable but also using "1UL". So, I
suspect that if you were using 1 << order, the instruction would re-appear.
Cheers,
Julien Grall
</code></pre>
</section><section>
<h3 id="contribution---pscontd">Contribution - P.S.(Cont’d)</h3>
<ol start="2">
<li><code>Uftrace</code> 함수를 뛰어다니는 분기(try-catch 구현) longjmp() 가 컴파일러 최적화로 동작을 안하는 현상</li>
</ol>
<pre><code class="language-patch">diff --git a/tests/s-longjmp.c b/tests/s-longjmp.c
index 487e9264..64fc7c38 100644
--- a/tests/s-longjmp.c
+++ b/tests/s-longjmp.c
@@ -8,6 +8,9 @@ int foo(void)
return 0;
}
+#if __clang__
+__attribute__((optnone))
+#endif
int bar(void)
{
return -1;
Another solution is to make bar function has more contents
so that it can't be optimized in clang.
</code></pre>
</section><section>
<h1 id="heading-1">🤗</h1>
<p>진짜 끝 🤗</p>
</section>
</div>
<div class="line top"></div>
<div class="line bottom"></div>
<div class="line left"></div>
<div class="line right"></div>
</div>
<script type="text/javascript" src=/reveal-hugo/object-assign.js></script>
<a href="/reveal-js/css/print/" id="print-location" style="display: none;"></a>
<script type="text/javascript">
var printLocationElement = document.getElementById('print-location');
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = printLocationElement.href + (window.location.search.match(/print-pdf/gi) ? 'pdf.css' : 'paper.css');
document.getElementsByTagName('head')[0].appendChild(link);
</script>
<script type="application/json" id="reveal-hugo-site-params">{"history":true,"templates":{"grey":{"background":"#424242","transition":"convex"}}}</script>
<script type="application/json" id="reveal-hugo-page-params">{"custom_theme":"reveal-hugo/themes/robot-lung.css","highlight_theme":"color-brewer","margin":0.2,"templates":{"hotpink":{"background":"#FF4081","class":"hotpink"}},"transition":"slide","transition_speed":"fast"}</script>
<script src="/reveal-js/js/reveal.js"></script>
<script type="text/javascript">
function camelize(map) {
if (map) {
Object.keys(map).forEach(function(k) {
newK = k.replace(/(\_\w)/g, function(m) { return m[1].toUpperCase() });
if (newK != k) {
map[newK] = map[k];
delete map[k];
}
});
}
return map;
}
var revealHugoDefaults = { center: true, controls: true, history: true, progress: true, transition: "slide" };
var revealHugoSiteParams = JSON.parse(document.getElementById('reveal-hugo-site-params').innerHTML);
var revealHugoPageParams = JSON.parse(document.getElementById('reveal-hugo-page-params').innerHTML);
var options = Object.assign({},
camelize(revealHugoDefaults),
camelize(revealHugoSiteParams),
camelize(revealHugoPageParams));
Reveal.initialize(options);
</script>
<script type="text/javascript" src="/reveal-js/plugin/markdown/marked.js"></script>
<script type="text/javascript" src="/reveal-js/plugin/markdown/markdown.js"></script>
<script type="text/javascript" src="/reveal-js/plugin/highlight/highlight.js"></script>
<script type="text/javascript" src="/reveal-js/plugin/zoom-js/zoom.js"></script>
<script type="text/javascript" src="/reveal-js/plugin/notes/notes.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({startOnLoad: false});
let render = (event) => {
let mermaidElems = event.currentSlide.querySelectorAll('.mermaid');
if (!mermaidElems.length){
return
}
mermaidElems.forEach(mermaidElem => {
let processed = mermaidElem.getAttribute('data-processed');
if (!processed){
mermaid.init(undefined, mermaidElem);
}
});
};
Reveal.addEventListener('slidechanged', render);
Reveal.addEventListener('ready', render);
</script>
<script type="text/javascript">
Reveal.addEventListener('slidechanged', function(event) {
console.log("🎞️ Slide is now " + event.indexh);
});
</script>
</body>
</html>