Jekyll2023-05-11T06:52:30+00:00https://jayoh-dev.github.io/feed.xmlNeatly BuildJayjayoh.dev@gmail.comHexagonal Architecture2023-04-05T00:00:00+00:002023-04-05T00:00:00+00:00https://jayoh-dev.github.io/dev/hexagonal-architecture<p>소프트웨어 아키텍처의 개요와 육각형(Hexagonal) 아키텍처 패턴에 대해 설명한다.</p> <h1 id="소프트웨어-아키텍처-개요">소프트웨어 아키텍처 개요</h1> <p>소프트웨어 아키텍처는 소프트웨어를 쉽게 개발, 배포, 운영, 유지보수 하기 위한 방법이다. 소프트웨어 아키텍처의 목표는 시스템을 제대로 동작하도록 만드는 것이 아니다. 형편 없는 아키텍처를 갖춘 시스템도 수없이 많지만 그런대로 잘 동작한다. 이러한 시스템들은 대체로 운영에서는 문제를 겪지 않는다. 운영 보다는 배포, 유지보수, 계속되는 개발 과정에서 어려움을 겪는다. 소프트웨어 아키텍처는 처음부터 모든 것을 완벽하게 설계하는 것이 아니고, 세부적인 결정을 뒤로 미루고 쉽게 조정할 수 있도록 하는 것이다.</p> <p>소프트웨어는 행위(behavior)와 구조(structure)에서 두 가지 가치를 제공한다.</p> <ul> <li> <p><strong>행위적 가치</strong></p> <p>고객을 위해 기계가 수익을 창출하거나 비용을 절약하도록 한다. 고객의 요구사항을 구체화하고, 기계가 이러한 요구사항을 수행하도록 코드를 작성한다.</p> </li> <li> <p><strong>구조적 가치(아키텍처)</strong></p> <p>소프트웨어는 부드러운(soft) 제품(ware)의 합성어이다. 소프트웨어를 만든 이유는 기계(하드웨어)의 행위를 쉽게 변경할 수 있도록 하기 위해서다. 고객이 기능에 대한 생각, 즉 요구사항을 바꾸면 쉽게 소프트웨어를 변경할 수 있어야 한다.</p> </li> </ul> <p>둘 중 구조적 가치가 더 중요하다. 완벽하게 동작하지만 수정이 아예 불가능한 프로그램과 동작은 하지 않지만 변경이 쉬운 프로그램이 주어진다고 하면, 첫 번째 프로그램은 요구사항이 변경될 때 동작하지 않게 되고, 결국 프로그램이 돌아가도록 만들 수 없게 된다. 즉 이러한 프로그램은 쓸모가 없다. 두 번째 프로그램을 수정하면 돌아가게 만들 수 있고, 변경사항이 발생하더라도 여전히 동작하도록 유지보수 할 수 있다. 따라서 이러한 프로그램은 앞으로도 계속 유용한 채로 남을 것이다.</p> <p><strong>선택사항 열어두기</strong></p> <p>소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어 두는 것이다.</p> <p>소프트웨어는 정책(policy)과 세부사항(detail) 두 가지 구성요소로 분해할 수 있다.</p> <ul> <li> <p><strong>정책</strong></p> <p>모든 비즈니스 로직을 구체화 하는 것이다. 정책은 시스템이 제공하는 핵심 가치가 들어 있는 곳이다.</p> </li> <li> <p><strong>세부사항</strong></p> <p>사람, 외부 시스템, 프로그래머가 정책과 소통할 때 필요한 요소지만, 정책이 가진 행위에는 조금도 영향을 미치지 않는다. 이러한 세부사항에는 입출력 장치, 데이터베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등이 있다.</p> </li> </ul> <p>예를 들면,</p> <ul> <li>개발 초기에는 데이터베이스 시스템을 선택할 필요가 없다. 상위 레벨의 정책은 어떤 종류의 데이터베이스를 사용하는지, 텍스트 파일을 사용할지 신경 써서는 안된다.</li> <li>개발 초기에는 웹 서버를 선택할 필요가 없다. 상위 레벨의 정책은 자신이 웹을 통해 전달된다는 사실을 알아서는 안된다. 심지어는 시스템이 웹을 통해 전송할 것인지조차도 결정할 필요가 없다.</li> <li>개발 초기에는 REST를 적용할 필요가 없다. 상위 레벨의 정책은 외부 세계로의 인터페이스에 대해 독립적이어야 하기 때문이다.</li> <li>개발 초기에는 프레임워크를 결정할 필요가 없다. 개발 성숙도가 높아지면 프레임워크로부터 독립할 수도 있다.</li> </ul> <p>세부사항을 신경 쓰지 않고 상위 레벨의 정책을 만들 수 있다면, 이러한 세부사항에 대한 결정을 연기할 수 있다. 이를 통해 다양한 실험을 시도해 볼 수 있는 선택지도 열어 둘 수 있다. 현재 동작하고 있는 일부 상위 레벨 정책이 있고, 이들 정책이 데이터베이스에 독립적이라면 다양한 데이터베이스를 후보에 두고 그 적용 가능성과 성능을 검토해 볼 수 있다. 통신 프로토콜도 마찬가지다.</p> <p>소프트웨어 아키텍처는 처음부터 모든 것을 완벽하게 설계하는 것이 아니고, 세부적인 결정을 뒤로 미루고 쉽게 조정할 수 있도록 하는 것이다.</p> <h1 id="육각형hexagonal-아키텍처">육각형(Hexagonal) 아키텍처</h1> <p>Alistair Cockburn이 2005년 소개한 소프트웨어 아키텍처. 다음과 같은 목표를 정의하였다.</p> <ul> <li>애플리케이션은 유저, 다른 애플리케이션 및 자동화 된 테스트 환경에서 동동하게 제어할 수 있어야 한다. 비즈니스 로직은 UI, REST API 또는 테스트 프레임워크에서 호출되는지 여부에 차이가 없다.</li> <li>비즈니스 로직은 데이터베이스, 다른 인프라 및 서드파티 시스템과 격리하여 개발하고 테스트 할 수 있어야 한다. 비즈니스 로직 관점에서 데이터가 관계형 데이터베이스, NoSQL 시스템, XML 파일 혹은 자체 바이너리 포맷으로 저장되는지 여부는 차이가 없다.</li> <li>인프라 업그레이드(데이터베이스 서버 업그레이드, 외부 인터페이스 변경에 대응, 라이브러리 업그레이드 등)는 비즈니스 로직을 조정하지 않고도 가능해야 한다.</li> </ul> <h2 id="포트와-어댑터">포트와 어댑터</h2> <p>육각형 아키텍처는 포트와 어댑터 아키텍처라고도 한다.</p> <p>비즈니스 로직(육각형 아키텍처에서 애플리케이션이라고 함)을 외부 세계로부터 분리하는 것은 그림과 같이 <strong>포트</strong>와 <strong>어댑터</strong>를 통해 이루어진다.</p> <p><strong>포트</strong>는 기계적 및 전기적 프로토콜을 준수하는 어떤 장치가 연결될 수 있는 전기적 연결점을 의미한다. <strong>어댑터</strong>는 서로 다른 프로토콜을 사용하는 장치를 연결하기 위해 전기적 연결을 변환해 주는 것을 의미한다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-01.png" alt="" /></p> <p>애플리케이션은 아키텍처의 코어에 있고, 외부 세계와 통신하기 위한 인터페이스(포트)를 정의한다. 포트는 API, 사용자 인터페이스 혹은 다른 애플리케이션에 의해 제어되고, 데이터베이스, 외부 인터페이스 및 기타 인프라를 제어한다.</p> <p>애플리케이션은 포트만 알고 있고, 포트 인터페이스만 참조하여 구현된다. 기술적 세부 사항은 포트 뒤에 있으며, 애플리케이션과는 상관이 없다.</p> <p>외부 컴포넌트에 대한 연결은 어댑터에 의해 제공된다. 예시를 보자.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-02.png" alt="" /></p> <ol> <li>유저가 UI를 통해 제어</li> <li>유저가 REST API를 통해 제어</li> <li>외부 앱이 2와 동일한 REST API를 통해 제어</li> <li>애플리케이션에서 데이터베이스를 제어</li> <li>애플리케이션에서 서드파티 API를 통해 외부 애플리케이션을 제어</li> </ol> <p>예를 들어, UI는 회원 가입 양식을 제공할 수 있다. 사용자가 모든 데이터를 입력하고 ‘<code class="language-plaintext highlighter-rouge">Submit</code>’을 클릭하면 UI 어댑터가 ‘<code class="language-plaintext highlighter-rouge">Register User</code>’ 명령을 생성하여 애플리케이션으로 보낸다. 혹은 HTTP POST 요청에 의해 REST 어댑터에서 동일한 명령을 생성할 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-03.png" alt="" /></p> <p>애플리케이션의 반대편에서는 데이터베이스 어댑터가 ‘<code class="language-plaintext highlighter-rouge">Save User</code>’ 명령을 ‘<code class="language-plaintext highlighter-rouge">INSERT INTO User VALUES (…)</code>’과 같은 SQL 쿼리로 변환할 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-04.png" alt="" /></p> <p>어댑터가 어떤 데이터베이스를 사용하는지, 어떤 버전에서 이 작업을 수행하는지는 애플리케이션 코어의 관점에서는 상관이 없다.</p> <p>하나의 포트에 여러 어댑터가 연결될 수 있다. 위의 예와 같이 UI 어댑터와 REST 어댑터를 모두 포트에 연결하여 애플리케이션을 제어할 수 있다. 그리고 알림을 보내기 위한 포트에는 이메일 어댑터, SMS 어댑터, 메신저 어댑터가 연결되어 있을 수 있다.</p> <p>위의 예에서 두 가지 유형의 포트와 어댑터, 즉 애플리케이션을 제어하는 포트와 어댑터와 애플리케이션에 의해 제어되는 포트를 보았다. 첫 번째 유형의 포트를 입력(inbound), 주(primary) 혹은 드라이빙(driving) 포트 및 어댑터라고 하고 일반적으로 육각형의 왼쪽에 표시한다. 두 번째 유형의 포트를 출력(outbound), 부(secondary) 혹은 드리븐(driven) 포트 및 어댑터라고 하며 일반적으로 육각형의 오른쪽에 표시한다.</p> <h2 id="종속성-규칙">종속성 규칙</h2> <p>기술적인 세부 사항 및 라이브러리가 애플리케이션에 영향을 주지 않도록 하기 위한 종속성 규칙이 있다.</p> <p>종속성 규칙은 모든 소스 코드 종속성이 외부에서 내부로, 즉 육각형에서 애플리케이션 방향으로만 가리킬 수 있음을 의미한다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-05.png" alt="" /></p> <p>왼쪽에 있는 입력 포트의 경우 클래스 및 관계성을 매핑 하는 것이 매우 간단하다.</p> <p>사용자 등록 예제의 경우 다음 클래스와 같이 아키텍처를 구현할 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-06.png" alt="" /></p> <p><code class="language-plaintext highlighter-rouge">RegistrationController</code>는 어댑터이고, <code class="language-plaintext highlighter-rouge">RegistrationUseCase</code> 인터페이스는 입력 포트를 정의하며, <code class="language-plaintext highlighter-rouge">RegistrationService</code>는 포트에서 명세한 기능을 구현한다.</p> <p>소스 코드 종속성은 <code class="language-plaintext highlighter-rouge">RegistrationController</code>에서 <code class="language-plaintext highlighter-rouge">RegistrationUseCase</code>로 이동하며, 코어를 향한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">RegistrationUseCase</span> <span class="o">{</span> <span class="nc">Register</span><span class="o">();</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">RegistrationService</span> <span class="o">:</span> <span class="nc">RegistrationUseCase</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="nc">Register</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// do registration</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">RegistrationController</span> <span class="o">{</span> <span class="nc">RegistrationUseCase</span> <span class="n">registration</span><span class="o">;</span> <span class="nc">ProcessRegister</span><span class="o">()</span> <span class="o">{</span> <span class="n">registration</span><span class="o">.</span><span class="na">Register</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>그러나 오른쪽에 있는 출력 포트와 어댑터, 즉 소스 코드 종속성이 호출 방향과 반대 방향이어야 하는 경우는 어떻게 구현해야 할까. 예를 들어 데이터베이스가 코어 외부에 있고 소스 코드 종속성이 코어로 전달되는 경우 애플리케이션 코어가 데이터베이스에 어떻게 엑세스 할 수 있을까</p> <p>여기서 종속성 반전(dependency inversion) 원리가 적용된다.</p> <h2 id="종속성-반전">종속성 반전</h2> <p>출력 포트에서도 포트는 인터페이스로 정의된다. 그러나 클래스 간의 관계성은 역전 된다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-07.png" alt="" /></p> <p><code class="language-plaintext highlighter-rouge">PersistanceAdapter</code>는 <code class="language-plaintext highlighter-rouge">PersistancePort</code>를 사용(use)하는 것이 아니고 구현(implement)한다. 그리고 <code class="language-plaintext highlighter-rouge">RegistrationService</code>는 <code class="language-plaintext highlighter-rouge">PersistencePort</code>를 구현(implement)하는 것이 아니고 사용(use) 한다.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">PersistancePort</span> <span class="o">{</span> <span class="nc">SaveRegistration</span><span class="o">();</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">PersistanceAdapter</span> <span class="o">:</span> <span class="nc">PersistancePort</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="nc">SaveRegistration</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// save to DB</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">RegistrationService</span> <span class="o">:</span> <span class="nc">RegistrationUseCase</span> <span class="o">{</span> <span class="nc">PersistencePort</span> <span class="n">persistence</span><span class="o">;</span> <span class="nc">Register</span><span class="o">()</span> <span class="o">{</span> <span class="n">persistence</span><span class="o">.</span><span class="na">SaveRegistration</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>종속성 반전 원리를 사용하여 호출 방향과 반대 되는 출력 포트 및 어댑터에 대한 소스 코드 종속성의 방향을 선택할 수 있다.</p> <h2 id="테스트">테스트</h2> <p>좋은 아키텍처의 요구 사항 중 하나는 ‘격리되어 테스트 할 수 있는 컴포넌트’를 제공하는 것이다. 육각형 아키텍처를 사용하면 애플리케이션을 쉽게 테스트 할 수 있다.</p> <ul> <li>테스트는 입력 포트를 통해 애플리케이션을 호출할 수 있다.</li> <li>출력 포트는 애플리케이션 쿼리에 응답하는 스텁 혹은 애플리케이션에서 보낸 이벤트를 기록하는 등 테스트 환경에 연결될 수 있다.</li> </ul> <p>다음 그림은 데이터베이스에 대한 테스트 환경을 만들어 출력 데이터베이스 포트에 연결하고(Arrange), 입력 포트에서 애플리케이션을 호출하고(Act), 포트의 응답 및 데스트 환경과의 상호작용(Assert)을 확인하는 유닛 테스트를 보여 준다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-08.png" alt="" /></p> <p>애플리케이션을 어댑터와 격리하여 테스트할 수 있을 뿐만 아니라 어댑터를 애플리케이션과 격리하여 테스트할 수도 있다.</p> <p>다음 그림은 REST 어댑터에 대한 유닛 테스트를 보여준다. 입력 포트에 대한 테스트 환경을 생성하고, REST를 통해 HTTP POST 요청을 REST 어댑터로 보내고, 마지막으로 HTTP 응답 및 상호작용을 확인한다(Assert).</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-09.png" alt="" /></p> <p>다음은 데이터베이스 어댑터에 대한 유닛 테스트를 보여준다. 테스트 컨테이너를 사용하여 데이터베이스를 시작하고(Arrange), 데이터베이스 어댑터에 있는 메소드를 호출하고(Act), 마지막으로 리턴값과 데이터베이스의 변경 내용이 예상과 같은지 여부를 확인한다(Assert).</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2023-04-05-10.png" alt="" /></p> <h2 id="육각형-아키텍처의-장점">육각형 아키텍처의 장점</h2> <p><strong>확장</strong></p> <ul> <li>어댑터나 인프라를 변경하지 않고도 애플리케이션 코어를 수정할 수 있다.</li> <li>애플리케이션에서 단 한 줄의 코드도 변경하지 않고 인프라를 교체할 수 있다. 해당 어댑터만 조정하면 된다.</li> <li>애플리케이션 코어 개발부터 시작하여 인프라에 대한 결정을 미뤄둘 수 있다. 애플리케이션 개발 과정에서 얻을 경험을 통해 사용할 인프라(프레임워크, 데이터베이스 등)에 대한 더 나은 결정을 내릴 수 있다.</li> </ul> <p><strong>격리</strong></p> <ul> <li>애플리케이션은 순수하게 비즈니스 로직만 다룬다.</li> <li>모든 기술적 문제들은 입력 및 출력 포트에서 구현된다.</li> <li>애플리케이션 코어와 어댑터는 포트에 의해 격리되며, 애플리케이션 코어는 그 뒤에 숨겨진 기술적 세부사항을 알 필요 없이 포트와 상호작용한다.</li> <li>격리를 통해 코드의 책임을 지역화 할 수 있으므로, 아키텍처 경계가 흐려질 위험이 크게 줄어든다.</li> </ul> <p><strong>개발</strong></p> <ul> <li>애플리케이션 포트가 정의되면 각 컴포넌트(코어, UI, 데이터베이스, 통신 등)에 대한 작업을 여러 개발자 간에 쉽게 나눌 수 있다.</li> </ul> <p><strong>테스트</strong></p> <ul> <li>테스트 환경을 사용하여 모든 컴포넌트를 완전히 격리 된 상태로 테스트 할 수 있다.</li> </ul> <h2 id="육각형-아키텍처의-단점">육각형 아키텍처의 단점</h2> <p>포트와 어댑터를 정의하고 유지하기 위한 추가 작업이 필요하다.</p> <h1 id="references">References</h1> <ul> <li>Robert C. Martin, 클린 아키텍처, 인사이트, 2019</li> <li><a href="https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture/">https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture/</a></li> </ul>Jayjayoh.dev@gmail.com소프트웨어 아키텍처의 개요와 육각형(Hexagonal) 아키텍처 패턴에 대해 설명한다.Python PyPI 저장소 한국 미러 서버 설정2021-03-22T00:00:00+00:002021-03-22T00:00:00+00:00https://jayoh-dev.github.io/dev/set-pip-mirror-url<p>Python pip 저장소를 한국(Kakao Corp.) 미러 서버로 설정하는 방법을 설명한다.</p> <h1 id="설정-파일-경로">설정 파일 경로</h1> <h2 id="linux">Linux</h2> <p><code class="language-plaintext highlighter-rouge">/etc/xdg/pip/pip.conf</code> 파일 생성 및 수정</p> <h2 id="windows">Windows</h2> <p><code class="language-plaintext highlighter-rouge">C:\ProgramData\pip\pip.ini</code> 파일 생성 및 수정</p> <h1 id="pip-저장소-설정">pip 저장소 설정</h1> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span> <span class="py">index-url</span><span class="p">=</span><span class="s">http://ftp.daumkakao.com/pypi/simple</span> <span class="py">trusted-host</span><span class="p">=</span><span class="s">ftp.daumkakao.com</span> </code></pre></div></div>Jayjayoh.dev@gmail.comPython pip 저장소를 한국(Kakao Corp.) 미러 서버로 설정하는 방법을 설명한다.GitHub 대용량 Push 문제 해결2021-03-18T00:00:00+00:002021-03-18T00:00:00+00:00https://jayoh-dev.github.io/dev/github-push-large-repo<p>GitHub에 대용량 파일을 Push 할 때 발생하는 문제를 해결한다.</p> <h1 id="problem">Problem</h1> <p>기존 Git 저장소를 GitHub에 Migration 할 때, 한번에 2GB 이상, 한 파일당 100MB 이상의 파일을 Push 하려고 하면 거부되며, 아래와 같은 에러 메시지를 볼 수 있다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com. remote: error: Trace: 00e28c536177c44a0d5eae********** remote: error: See http://git.io/iEPt8g for more information. remote: error: File your-lar-file is 181.94 MB; this exceeds GitHub's file size limit of 100.00 MB </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>remote: fatal: pack exceeds maximum allowed size fatal: the remote end hung up unexpectedly </code></pre></div></div> <h1 id="solution">Solution</h1> <h2 id="commit을-나눠서-push">Commit을 나눠서 Push</h2> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push origin master~10000:refs/heads/master git push origin master:refs/heads/master git push origin refs/heads/* git push origin --tags </code></pre></div></div> <h2 id="commit-로그에서-용량이-큰-파일-삭제">Commit 로그에서 용량이 큰 파일 삭제</h2> <p>BFG Repo-Cleaner 사용</p> <ul> <li>https://rtyley.github.io/bfg-repo-cleaner/</li> </ul> <p><strong>Warning: 파일이 삭제 된 Commit의 hash 값이 변경된다.</strong></p> <p>e.g. Commit 로그에서 100MB 이상의 파일을 삭제</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar bfg-1.14.0.jar --strip-blobs-bigger-than 100M </code></pre></div></div>Jayjayoh.dev@gmail.comGitHub에 대용량 파일을 Push 할 때 발생하는 문제를 해결한다.udev2020-12-30T00:00:00+00:002020-12-30T00:00:00+00:00https://jayoh-dev.github.io/dev/udev<p>udev는 /dev 노드를 동적으로 생성하고 이를 위한 규칙을 관리하는 시스템 서비스이다. libudev(-ludev)를 사용하여 udev 객체에 접근하고, udev 이벤트를 모니터링 할 수 있다.</p> <h1 id="examples">Examples</h1> <h2 id="enumerate-block-devices">Enumerate block devices</h2> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include &lt;libudev.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="k">struct</span> <span class="nc">udev</span><span class="o">*</span> <span class="n">udev</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">udev_enumerate</span><span class="o">*</span> <span class="n">enumerate</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">udev_list_entry</span><span class="o">*</span> <span class="n">devices</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">udev_device</span><span class="o">*</span> <span class="n">dev</span><span class="p">;</span> <span class="c1">// Create the udev object</span> <span class="n">udev</span> <span class="o">=</span> <span class="n">udev_new</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">udev</span><span class="p">)</span> <span class="p">{</span> <span class="n">ESF_LOG_ERROR</span><span class="p">(</span><span class="n">kTag</span><span class="p">,</span> <span class="s">"Cannot create udev"</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Create the enumerator object</span> <span class="n">enumerate</span> <span class="o">=</span> <span class="n">udev_enumerate_new</span><span class="p">(</span><span class="n">udev</span><span class="p">);</span> <span class="c1">// Create a list of the devices</span> <span class="n">udev_enumerate_add_match_subsystem</span><span class="p">(</span><span class="n">enumerate</span><span class="p">,</span> <span class="s">"block"</span><span class="p">);</span> <span class="n">udev_enumerate_add_match_property</span><span class="p">(</span><span class="n">enumerate</span><span class="p">,</span> <span class="s">"DEVTYPE"</span><span class="p">,</span> <span class="s">"partition"</span><span class="p">);</span> <span class="n">udev_enumerate_scan_devices</span><span class="p">(</span><span class="n">enumerate</span><span class="p">);</span> <span class="n">devices</span> <span class="o">=</span> <span class="n">udev_enumerate_get_list_entry</span><span class="p">(</span><span class="n">enumerate</span><span class="p">);</span> <span class="c1">// For each entry enumerated, add to device_list</span> <span class="k">struct</span> <span class="nc">udev_list_entry</span><span class="o">*</span> <span class="n">entry</span><span class="p">;</span> <span class="n">udev_list_entry_foreach</span><span class="p">(</span><span class="n">entry</span><span class="p">,</span> <span class="n">devices</span><span class="p">)</span> <span class="p">{</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span><span class="p">;</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">node</span><span class="p">;</span> <span class="n">path</span> <span class="o">=</span> <span class="n">udev_list_entry_get_name</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span> <span class="n">dev</span> <span class="o">=</span> <span class="n">udev_device_new_from_syspath</span><span class="p">(</span><span class="n">udev</span><span class="p">,</span> <span class="n">path</span><span class="p">);</span> <span class="n">node</span> <span class="o">=</span> <span class="n">udev_device_get_devnode</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span> <span class="c1">// To do something for each devices</span> <span class="n">udev_device_unref</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Free the enumerator object</span> <span class="n">udev_enumerate_unref</span><span class="p">(</span><span class="n">enumerate</span><span class="p">);</span> <span class="c1">// Free the udev object</span> <span class="n">udev_unref</span><span class="p">(</span><span class="n">udev</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <h2 id="monitor-block-devices">Monitor block devices</h2> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cp">#include &lt;libudev.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="k">struct</span> <span class="nc">udev</span><span class="o">*</span> <span class="n">udev</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">udev_device</span><span class="o">*</span> <span class="n">dev</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">udev_monitor</span><span class="o">*</span> <span class="n">mon</span><span class="p">;</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">;</span> <span class="c1">// Create the udev object</span> <span class="n">udev</span> <span class="o">=</span> <span class="n">udev_new</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">udev</span><span class="p">)</span> <span class="p">{</span> <span class="n">ESF_LOG_ERROR</span><span class="p">(</span><span class="n">kTag</span><span class="p">,</span> <span class="s">"Cannot create udev"</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Set up a monitor to monitor devices</span> <span class="n">mon</span> <span class="o">=</span> <span class="n">udev_monitor_new_from_netlink</span><span class="p">(</span><span class="n">udev</span><span class="p">,</span> <span class="s">"udev"</span><span class="p">);</span> <span class="n">udev_monitor_filter_add_match_subsystem_devtype</span><span class="p">(</span><span class="n">mon</span><span class="p">,</span> <span class="s">"block"</span><span class="p">,</span> <span class="s">"partition"</span><span class="p">);</span> <span class="n">udev_monitor_enable_receiving</span><span class="p">(</span><span class="n">mon</span><span class="p">);</span> <span class="c1">// Get the file descriptor for the monitor</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">udev_monitor_get_fd</span><span class="p">(</span><span class="n">mon</span><span class="p">);</span> <span class="n">fd_set</span> <span class="n">fds</span><span class="p">;</span> <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">timeval</span> <span class="n">timeout</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span> <span class="k">while</span> <span class="p">(</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">monitor_running_</span><span class="p">)</span> <span class="p">{</span> <span class="n">FD_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fds</span><span class="p">);</span> <span class="n">FD_SET</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fds</span><span class="p">);</span> <span class="n">timeout</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="n">timeout</span><span class="p">.</span><span class="n">tv_usec</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">fd</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fds</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">timeout</span><span class="p">);</span> <span class="c1">// change to blocking IO</span> <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">FD_ISSET</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fds</span><span class="p">))</span> <span class="p">{</span> <span class="n">dev</span> <span class="o">=</span> <span class="n">udev_monitor_receive_device</span><span class="p">(</span><span class="n">mon</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">dev</span><span class="p">)</span> <span class="p">{</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">action</span> <span class="o">=</span> <span class="n">udev_device_get_action</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">node</span> <span class="o">=</span> <span class="n">udev_device_get_devnode</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"add"</span><span class="p">,</span> <span class="n">action</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// To do somthing for device added</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"remove"</span><span class="p">,</span> <span class="n">action</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// To do somthing for device removed</span> <span class="p">}</span> <span class="n">udev_device_unref</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Free the udev object</span> <span class="n">udev_unref</span><span class="p">(</span><span class="n">udev</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <h2 id="get-mounted-directory-from-block-dev-node">Get mounted directory from block dev node</h2> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include &lt;mntent.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="n">ing</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="kt">FILE</span><span class="o">*</span> <span class="n">file</span><span class="p">;</span> <span class="k">struct</span> <span class="nc">mntent</span><span class="o">*</span> <span class="n">ent</span><span class="p">;</span> <span class="n">file</span> <span class="o">=</span> <span class="n">setmntent</span><span class="p">(</span><span class="s">"/etc/mtab"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">file</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">"Cannot open /etc/mtab"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span> <span class="p">}</span> <span class="c1">// Find mounted directory of device node</span> <span class="k">while</span> <span class="p">(</span><span class="nb">nullptr</span> <span class="o">!=</span> <span class="p">(</span><span class="n">ent</span> <span class="o">=</span> <span class="n">getmntent</span><span class="p">(</span><span class="n">file</span><span class="p">)))</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">ent</span><span class="o">-&gt;</span><span class="n">mnt_fsname</span><span class="p">,</span> <span class="n">dev_node</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Dev "</span> <span class="o">&lt;&lt;</span> <span class="n">dev_node</span> <span class="o">&lt;&lt;</span> <span class="s">" Mounted to: "</span> <span class="o">&lt;&lt;</span> <span class="n">ent</span><span class="o">-&gt;</span><span class="n">mnt_dir</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="n">endmntent</span><span class="p">(</span><span class="n">file</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <h1 id="참고">참고</h1> <ul> <li><a href="https://linuxias.gitbooks.io/writing-udev-rules-kor-ver/content/">Writing udev rules</a></li> </ul>Jayjayoh.dev@gmail.comudev는 /dev 노드를 동적으로 생성하고 이를 위한 규칙을 관리하는 시스템 서비스이다. libudev(-ludev)를 사용하여 udev 객체에 접근하고, udev 이벤트를 모니터링 할 수 있다.Light Media Scanner2020-11-26T00:00:00+00:002020-11-26T00:00:00+00:00https://jayoh-dev.github.io/dev/lightmediascanner<p>LMS(Light Media Scanner)는 임베디드 시스템에 사용하기 위한 가벼운 미디어 스캐너이다.</p> <p>디렉토리를 최적화된 방법으로 재귀적으로 스캔하고, Child 프로세스에서 파서를 처리하여 파서에 문제가 발생했을 때 메인 프로세스가 종료되지 않게 한다. 파서는 Shared Object 형태의 플러그인이며, 스캐너를 다시 컴파일할 필요 없이 쉽게 추가할 수 있다.</p> <p>SQLite3를 사용하여 파일의 mtime 관련 정보를 저장하고 <code class="language-plaintext highlighter-rouge">files</code>라는 마스터 테이블에 파일 id를 관리한다. 애플리케이션은 SQLite3 데이터베이스에 엑세스 하여 스캔 된 미디어에 대한 모든 정보를 얻을 수 있다. 사용자는 SQLite3 엑세스 라이브러리(Python의 SQLObject, SQLAlchemy 및 Storm, Ruby의 Sequel 등)을 모두 사용할 수 있다.</p> <h2 id="table-files">Table: files</h2> <p>이 테이블은 파일 시스템 엔트리와 연관된 정보들, 수정 시간, 추가 혹은 삭제 시간, byte 크기 및 경로를 제공한다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2020-11-26-01.png" alt="" /></p> <p>Columns:</p> <ul> <li><strong>id:</strong> 파일을 식별하는 고유한 값. 새로운 엔트리가 생성되면 자동으로 증가.</li> <li><strong>path:</strong> 파일의 전체 경로.</li> <li><strong>mtime:</strong> <code class="language-plaintext highlighter-rouge">stat(2) st_mtime</code>에 따른 파일의 마지막 수정 시간.</li> <li><strong>dtime:</strong> 파일이 더 이상 파일 시스템에 존재하지 않는 경우, <code class="language-plaintext highlighter-rouge">lms_check()</code>를 호출한 시간. 파일이 파일 시스템에 존재하면 이 필드의 값은 0.</li> <li><strong>itime:</strong> 파일이 데이터베이스에 추가된 시간. “오늘 추가된 파일” 등을 표시할 때 유용.</li> <li><strong>size:</strong> <code class="language-plaintext highlighter-rouge">stat(2) st_size</code>에 따른 파일의 byte 크기.</li> <li><strong>update_id:</strong> 마지막으로 파일을 수정했을 때의 <code class="language-plaintext highlighter-rouge">update_id</code>. 관련 내용은 <code class="language-plaintext highlighter-rouge">lms_internal</code> 테이블 참조.</li> </ul> <p>Examples:</p> <ul> <li>모든 파일의 경로 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span> <span class="k">FROM</span> <span class="n">files</span><span class="p">;</span> </code></pre></div> </div> </li> <li>파일 시스템에 존재하는 모든 파일의 경로 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span> <span class="k">FROM</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </code></pre></div> </div> </li> <li>지난 주 이전에 삭제된 모든 파일의 경로 출력(<code class="language-plaintext highlighter-rouge">$LASTWEEK</code>는 현재 Unix 시간에서 <code class="language-plaintext highlighter-rouge">7 * 24 * 60 * 60</code>을 뺀 값): <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span> <span class="k">FROM</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">dtime</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">AND</span> <span class="n">dtime</span> <span class="o">&lt;=</span> <span class="err">$</span><span class="n">LASTWEEK</span><span class="p">;</span> </code></pre></div> </div> </li> <li>파일 시스템에 존재하는 파일 중 오늘 추가된 파일의 경로 출력(<code class="language-plaintext highlighter-rouge">$TODAY</code>는 오늘 0시 0분의 Unix 시간 값): <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span> <span class="k">FROM</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">AND</span> <span class="n">itime</span> <span class="o">&gt;=</span> <span class="err">$</span><span class="n">TODAY</span><span class="p">;</span> </code></pre></div> </div> </li> </ul> <p><strong>Note:</strong></p> <p>사용 가능한 미디어를 질의할 때는 <strong>항상 (dtime = 0)를 포함</strong> 해야 한다. LightMediaScanner는 파일 시스템에서 파일이 삭제 되어도 데이터베이스에서 엔트리를 삭제하지 않고, 파일이 삭제 되었음을 표시하는 플래그를 설정한다(<code class="language-plaintext highlighter-rouge">dtime = time(NULL);</code>). 이 동작은 이동식 미디어가 해제 후 재연결 되었을 때, <code class="language-plaintext highlighter-rouge">path</code>, <code class="language-plaintext highlighter-rouge">mtime</code> 및 <code class="language-plaintext highlighter-rouge">size</code>가 동일한 파일을 빠르게 복구할 수 있게 해준다.</p> <h2 id="table-images">Table: images</h2> <p>이미지 엔트리와 해당 속성들을 가지는 테이블. <code class="language-plaintext highlighter-rouge">files</code> 테이블과 <code class="language-plaintext highlighter-rouge">JOIN</code>을 수행하여 파일 속성들을 얻을 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2020-11-26-02.png" alt="" /></p> <p>Columns:</p> <ul> <li><strong>id:</strong> 파일을 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">files.id</code> 설명 참조.</li> <li><strong>title:</strong> 타이틀(없으면 파일 이름으로부터 생성).</li> <li><strong>artist:</strong> 아티스트.</li> <li><strong>date:</strong> 생성 시간(Unix-time).한</li> <li><strong>width:</strong> 너비(pixel).</li> <li><strong>height:</strong> 높이(pixel).</li> <li><strong>orientation:</strong> image intended display orientation, <a href="http://jpegclub.org/exif_orientation.html">EXIF Orientation</a>과 동일.</li> <li><strong>gps_lat:</strong> GPS 위도 값(degree).</li> <li><strong>gps_long:</strong> GPS 경도(degree).</li> <li><strong>gps_alt:</strong> GPS 고도(meter).</li> <li><strong>dlna_profile:</strong> 이미지가 DLNA 프로파일과 일치하면 이 속성이 설정됨.</li> <li><strong>dlna_mime:</strong> 이미지가 DLNA MIME 타입과 일치하면 이 속성이 설정됨.</li> </ul> <p>Examples:</p> <ul> <li>파일 시스템에 존재하는 모든 이미지 파일의 경로 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">files</span><span class="p">.</span><span class="n">path</span> <span class="k">FROM</span> <span class="n">images</span><span class="p">,</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">files</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">images</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">files</span><span class="p">.</span><span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </code></pre></div> </div> </li> <li>파일 시스템에 존재하는 이미지 파일 중 <code class="language-plaintext highlighter-rouge">dlna_profile</code>이 “image/jpeg”인 파일의 경로, 너비 및 폭 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">files</span><span class="p">.</span><span class="n">path</span><span class="p">,</span> <span class="n">images</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">images</span><span class="p">.</span><span class="n">height</span> <span class="k">FROM</span> <span class="n">images</span><span class="p">,</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">files</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">images</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">files</span><span class="p">.</span><span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">AND</span> <span class="n">dlna_mime</span> <span class="o">=</span> <span class="s1">'image/jpeg'</span> </code></pre></div> </div> </li> </ul> <h2 id="tables-audios-audio_albums-audio_artists-and-audio_genres">Tables: audios, audio_albums, audio_artists and audio_genres</h2> <p>오디오 파일 관련 테이블. 필수로 필요한 것은 <code class="language-plaintext highlighter-rouge">audio</code> 테이블이며, <code class="language-plaintext highlighter-rouge">audio</code>에서 앨범(<code class="language-plaintext highlighter-rouge">audio_albums</code> 참조), 아티스트(<code class="language-plaintext highlighter-rouge">audio_artists</code> 참조) 및 장르(<code class="language-plaintext highlighter-rouge">audio_genres</code>)를 가질 수 있다. 파일 속성들은 <code class="language-plaintext highlighter-rouge">files</code> 테이블과 <code class="language-plaintext highlighter-rouge">JOIN</code>을 수행하여 얻을 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2020-11-26-03.png" alt="" /></p> <p><code class="language-plaintext highlighter-rouge">audios</code> Columns :</p> <ul> <li><strong>id:</strong> 파일을 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">files.id</code> 설명 참조.</li> <li><strong>title:</strong> 타이틀(없으면 파일 이름으로부터 생성).</li> <li><strong>album_id:</strong> 앨범을 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audio_albums</code> 테이블과 연관됨.</li> <li><strong>artist_id:</strong> 아티스트를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audio_artists</code> 테이블과 연관됨.</li> <li><strong>genre_id:</strong> 장르를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audio_genres</code> 테이블과 연관됨.</li> <li><strong>trackno:</strong> 앨범 내의 트랙 번호.</li> <li><strong>rating:</strong> 사용자 평점(“별점” 혹은 “좋아요” 등을 의미. 연령 제한 등급과 혼동하지 말것).</li> <li><strong>playcnt:</strong> 플레이 횟수.</li> <li><strong>length:</strong> 길이(second).</li> <li><strong>container:</strong> 파일 타입(“asf”, “mp4” 등).</li> <li><strong>codec:</strong> 오디오 코덱(“mpeg1layer3”, “mpeg4aac-main” 등).</li> <li><strong>channels:</strong> 오디오 채널 갯수(모노는 1, 스테레오는 2 등).</li> <li><strong>sampling_rate:</strong> 오디오 샘플링 속도(44100, 48000 등).</li> <li><strong>bitrate:</strong> 샘플 당 평균 bit 수(32000, 8000 등).</li> <li><strong>dlna_profile:</strong> 오디오가 DLNA 프로파일과 일치하면 이 속성이 설정됨.</li> <li><strong>dlna_mime:</strong> 오디오가 DLNA MIME 타입과 일치하면 이 속성이 설정됨.</li> </ul> <p><code class="language-plaintext highlighter-rouge">audio_artists</code> Columns:</p> <ul> <li><strong>id:</strong> 아티스트를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audios.artist_id</code> 설명 참조.</li> <li><strong>name:</strong> 아티스트 이름.</li> </ul> <p><code class="language-plaintext highlighter-rouge">audio_albums</code> Columns:</p> <ul> <li><strong>id:</strong> 앨범을 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audios.album_id</code> 설명 참조.</li> <li><strong>artist_id:</strong> 아티스트를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audio_artists</code> 테이블과 연관됨.</li> <li><strong>name:</strong> 앨범 이름.</li> </ul> <p><code class="language-plaintext highlighter-rouge">audio_genres</code> Columns:</p> <ul> <li><strong>id:</strong> 장르를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">audios.genre_id</code> 설명 참조.</li> <li><strong>name:</strong> 장르 이름.</li> </ul> <p>Examples:</p> <ul> <li>파일 시스템에 존재하는 모든 오디오 파일의 경로 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">files</span><span class="p">.</span><span class="n">path</span> <span class="k">FROM</span> <span class="n">audios</span><span class="p">,</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">files</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">audios</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">files</span><span class="p">.</span><span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </code></pre></div> </div> </li> <li>파일 시스템에 존재하는 모든 오디오 파일의 경로, 타이틀, 아티스트, 앨범 및 장르 이름 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">files</span><span class="p">.</span><span class="n">path</span><span class="p">,</span> <span class="n">audios</span><span class="p">.</span><span class="n">title</span><span class="p">,</span> <span class="n">audio_artists</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">audio_albums</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">audio_genres</span><span class="p">.</span><span class="n">name</span> <span class="k">FROM</span> <span class="n">audios</span><span class="p">,</span> <span class="n">files</span><span class="p">,</span> <span class="n">audio_artists</span><span class="p">,</span> <span class="n">audio_albums</span><span class="p">,</span> <span class="n">audio_genres</span> <span class="k">WHERE</span> <span class="n">files</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">audios</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">files</span><span class="p">.</span><span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">AND</span> <span class="n">audio_artists</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">audios</span><span class="p">.</span><span class="n">artist_id</span> <span class="k">AND</span> <span class="n">audio_albums</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">audios</span><span class="p">.</span><span class="n">album_id</span> <span class="k">AND</span> <span class="n">audio_genres</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">audios</span><span class="p">.</span><span class="n">genre_id</span> </code></pre></div> </div> </li> </ul> <h2 id="table-videos-videos_audios-videos_subtitles-videos_videos">Table: videos, videos_audios, videos_subtitles, videos_videos</h2> <p>비디오 파일 관련 테이블. 필수로 필요한 것은 <code class="language-plaintext highlighter-rouge">video</code> 테이블이며, <code class="language-plaintext highlighter-rouge">video</code>에서 여러 개의 비디오, 오디오 및 자막 스트림을 가질 수 있다. 파일 속성들은 <code class="language-plaintext highlighter-rouge">files</code> 테이블과 <code class="language-plaintext highlighter-rouge">JOIN</code>을 수행하여 얻을 수 있다.</p> <p><img src="https://jayoh-dev.github.io/assets/images_posts/2020-11-26-04.png" alt="" /></p> <p><code class="language-plaintext highlighter-rouge">videos</code> Columns:</p> <ul> <li><strong>id:</strong> 파일을 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">files.id</code> 설명 참조.</li> <li><strong>title:</strong> 타이틀(없으면 파일 이름으로부터 생성).</li> <li><strong>artist:</strong> 아티스트.</li> <li><strong>length:</strong> 길이(second).</li> <li><strong>container:</strong> 파일 타입(“asf”, “mp4” 등).</li> <li><strong>dlna_profile:</strong> 비디오가 DLNA 프로파일과 일치하면 이 속성이 설정됨.</li> <li><strong>dlna_mime:</strong> 비디오가 DLNA MIME 타입과 일치하면 이 속성이 설정됨.</li> </ul> <p><code class="language-plaintext highlighter-rouge">videos_audios</code> Columns:</p> <ul> <li><strong>id:</strong> 오디오 스트림을 식별하는 고유한 값.</li> <li><strong>video_id:</strong> 비디오를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">videos.id</code> 설명 참조.</li> <li><strong>stream_id:</strong> 비디오 내의 스트림 식별값.</li> <li><strong>lang:</strong> 언어(“en”, “pt”, “es” 등).</li> <li><strong>codec:</strong> 오디오 스트림 코덱(“mpeg1layer3”, “mpeg4aac-main” 등).</li> <li><strong>channels:</strong> 오디오 채널 갯수(모노는 1, 스테레오는 2 등).</li> <li><strong>sampling_rate:</strong> 오디오 샘플링 속도(44100, 48000 등).</li> <li><strong>bitrate:</strong> 샘플 당 평균 bit 수(32000, 8000 등).</li> </ul> <p><code class="language-plaintext highlighter-rouge">videos_subtitles</code> Columns:</p> <ul> <li><strong>id:</strong> 자막 스트림을 식별하는 고유한 값.</li> <li><strong>video_id:</strong> 비디오를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">videos.id</code> 설명 참조.</li> <li><strong>stream_id:</strong> 비디오 내의 스트림 식별값.</li> <li><strong>lang:</strong> 언어(“en”, “pt”, “es” 등).</li> <li><strong>codec:</strong> 자막 스트림 코덱(“srt”, “ssa”, “sub” 등).</li> </ul> <p><code class="language-plaintext highlighter-rouge">videos_videos</code> Columns:</p> <ul> <li><strong>id:</strong> 비디오 스트림을 식별하는 고유한 값.</li> <li><strong>video_id:</strong> 비디오를 식별하는 고유한 값. <code class="language-plaintext highlighter-rouge">videos.id</code> 설명 참조.</li> <li><strong>stream_id:</strong> 비디오 내의 스트림 식별값.</li> <li><strong>lang:</strong> 언어(“en”, “pt”, “es” 등).</li> <li><strong>codec:</strong> 비디오 스트림 코덱(“mpeg4-simple-l1”, “mpeg4-main-l4” 등).</li> <li><strong>aspect_ratio:</strong> 문자열로 인코딩 된 화면 비율(“4:3”, “16:9” 등).</li> <li><strong>bitrate:</strong> 샘플 당 평균 bit 수(500000, 800000).</li> <li><strong>framerate:</strong> 초당 프레임 수(30.0, 29.94, 60.0 등).</li> <li><strong>interlaced:</strong> 인터레이스인 경우 1.</li> <li><strong>width:</strong> 너비(pixel).</li> <li><strong>height:</strong> 높이(pixel).</li> </ul> <p>Examples:</p> <ul> <li>파일 시스템에 존재하는 모든 비디오 파일의 경로 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">files</span><span class="p">.</span><span class="n">path</span> <span class="k">FROM</span> <span class="n">videos</span><span class="p">,</span> <span class="n">files</span> <span class="k">WHERE</span> <span class="n">files</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">videos</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">files</span><span class="p">.</span><span class="n">dtime</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </code></pre></div> </div> </li> <li>비디오에 있는 모든 오디오 스트림 출력: <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">stream_id</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">lang</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">codec</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">channels</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">sampling_rate</span><span class="p">,</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">bitrate</span> <span class="k">FROM</span> <span class="n">videos_audios</span> <span class="k">WHERE</span> <span class="n">videos_audios</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="err">$</span><span class="n">VIDEOID</span> </code></pre></div> </div> </li> </ul> <h2 id="table-lms_internal">Table: lms_internal</h2> <p>내부적으로 테이블들의 버전을 관리하기 위한 테이블. 어떤 테이블이 수정되면 이 테이블에서 해당 테이블의 버전이 수정된다.</p> <h3 id="update-id">Update ID</h3> <p><code class="language-plaintext highlighter-rouge">tab = 'update_id'</code>는 전역 <code class="language-plaintext highlighter-rouge">update_id</code> 정수값을 가리키는 특별한 행이다. 이 정수값은 <code class="language-plaintext highlighter-rouge">lms_process()</code> 또는 <code class="language-plaintext highlighter-rouge">lms_check()</code> 함수가 데이터베이스를 수정하거나, 파일을 추가, 수정 및 삭제할 때마다 증가한다. 사용자는 이 값을 사용하여 컨텐츠를 동기화 할 수 있다.</p> <p>컨텐츠를 동기화 하는 가장 단순한 방법은 데이터베이스에 질의할 때마다 전역 변수로 <code class="language-plaintext highlighter-rouge">update_id</code> 값을 저장하고, 이 전역 변수가 변경되면 모든 질의를 다시 실행하는 것이다.</p> <p>보다 효율적인 방법은 데이터베이스 질의를 <code class="language-plaintext highlighter-rouge">update_id &gt; $OLD_UPDATE_ID</code>로 제한하여 수행하고 결과를 이전 것과 병합하는 것이다. 이 방법을 위해서는 최소한 <code class="language-plaintext highlighter-rouge">file.id</code>를 저장해야 하며, 이를 사용하여 수정된 엔트리를 업데이트 하고, 파일이 파일 시스템에서 사라졌는지를 확인하기 위한 <code class="language-plaintext highlighter-rouge">files.dtime</code>을 읽고, 새로 추가된 결과가 정렬 기준에 맞게 적절하게 삽입되도록 할 수 있다.</p> <h2 id="build">Build</h2> <h3 id="1-configuration">1 Configuration</h3> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./autogen.sh <span class="nv">$CONFIGURE_FLAGS</span> <span class="nt">--prefix</span><span class="o">=</span>/home/root/lms <span class="nt">--with-dbus-services</span><span class="o">=</span>/home/root/.local/share/dbus-1/services </code></pre></div></div> <h3 id="2-build">2 Build</h3> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make </code></pre></div></div> <h3 id="3-install">3 Install</h3> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make <span class="nv">DESTDIR</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PWD</span><span class="s2">/install"</span> <span class="nb">install</span> </code></pre></div></div> <h3 id="4-result">4 Result</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>install └── home └── root ├── .local │   └── share │   └── dbus-1 │   └── services │   └── org.lightmediascanner.service └── lms ├── bin │   ├── lightmediascannerctl │   └── lightmediascannerd ├── include │   ├── lightmediascanner.h │   ├── lightmediascanner_charset_conv.h │   ├── lightmediascanner_db.h │   ├── lightmediascanner_plugin.h │   └── lightmediascanner_utils.h └── lib ├── liblightmediascanner.la ├── liblightmediascanner.so -&gt; liblightmediascanner.so.0.5.1 ├── liblightmediascanner.so.0 -&gt; liblightmediascanner.so.0.5.1 ├── liblightmediascanner.so.0.5.1 ├── lightmediascanner │   └── plugins │   ├── asf.la │   ├── asf.so │   ├── audio-dummy.la │   ├── audio-dummy.so │   ├── dummy.la │   ├── dummy.so │   ├── flac.la │   ├── flac.so │   ├── id3.la │   ├── id3.so │   ├── jpeg.la │   ├── jpeg.so │   ├── m3u.la │   ├── m3u.so │   ├── ogg.la │   ├── ogg.so │   ├── pls.la │   ├── pls.so │   ├── png.la │   ├── png.so │   ├── rm.la │   ├── rm.so │   ├── video-dummy.la │   ├── video-dummy.so │   ├── wave.la │   └── wave.so └── pkgconfig └── lightmediascanner.pc </code></pre></div></div> <h2 id="database">Database</h2> <h3 id="기본-db-경로">기본 DB 경로</h3> <ul> <li><code class="language-plaintext highlighter-rouge">~/.config/lightmediascannerd/db.sqlite3</code></li> </ul> <h3 id="sqlite3">SQLite3</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from files; </code></pre></div></div> <h2 id="dbus-서비스">DBus 서비스</h2> <ul> <li><code class="language-plaintext highlighter-rouge">/usr/share/dbus-1/services/org.lightmediascanner.service</code></li> </ul> <p>DBus 서비스 정의 파일에서 LMS 데몬 경로 및 스캔을 위한 카테고리 디렉토리 지정</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[D-BUS Service] Name=org.lightmediascanner Exec=/usr/bin/lightmediascannerd -D picture:/home/root/Pictures -D audio:/home/root/Music </code></pre></div></div> <h2 id="daemon">Daemon</h2> <h3 id="options">Options</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Application Options: -p, --db-path=PATH Path to LightMediaScanner SQLit3 data base, defaults to "~/.config/lightmediascannerd/db.sqlite3". -c, --commit-interval=NUMBER Execute SQL COMMIT after NUMBER files are processed, defaults to 100. -t, --slave-timeout=SECONDS Number of seconds to wait for slave to reply, otherwise kills it. Defaults to 60. -d, --delete-older-than=DAYS Delete from database files that have 'dtime' older than the given number of DAYS. If not specified LightMediaScanner will keep the files in the database even if they are gone from the file system and if they appear again and have the same 'mtime' and 'size' it will be restored ('dtime' set to 0) without the need to parse the file again (much faster). This is useful for removable media. Use a negative number to disable this behavior. Defaults to 30. -V, --vacuum Execute SQL VACUUM after every scan. -S, --startup-scan Execute full scan on startup. --omit-scan-progress Omit the ScanProgress signal during scans. This will avoid the overhead of D-Bus signal emission and may slightly improve the performance, but will make the listener user-interfaces less responsive as they won't be able to tell the user what is happening. -C, --charset=CHARSET Extra charset to use. (Multiple use) -P, --parser=CATEGORY:PARSER Parsers to use, defaults to all. Format is 'category:parsername' or 'parsername' to apply parser to all categories. The special parsername 'all' declares all known parsers, while 'all-category' declares all parsers of that category. If one parser is provided, then no defaults are used, you can pre-populate all categories with their parsers by using --parser=all-category. -D, --directory=CATEGORY:DIRECTORY Directories to use, defaults to FreeDesktop.Org standard. Format is 'category:directory' or 'path' to apply directory to all categories. The special directory 'defaults' declares all directories used by default for that category. If one directory is provided, then no defaults are used, you can pre-populate all categories with their directories by using --directory=defaults. </code></pre></div></div> <h2 id="controller">Controller</h2> <h3 id="usage">Usage</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Usage: lms/bin/lightmediascannerctl &lt;action&gt; Action is one of: status print server properties and exit. monitor monitor server and its properties. write-lock try to get a write-lock and keep it while running. scan [params] start scan. May receive parameters as a series of CATEGORY:PATH to limit scan. stop stop ongoing scan. help this message. </code></pre></div></div> <p>Status</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lightmediascannerctl status </code></pre></div></div> <p>Scan</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lightmediascannerctl scan </code></pre></div></div> <p>Scan 범위 지정</p> <ul> <li>데몬 실행 시 정의한 카테고리 디렉토리 및 그 하위 디렉토리만 설정 가능</li> </ul> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lightmediascannerctl scan picture:/home/root/Pictures </code></pre></div></div> <h2 id="references">References</h2> <ul> <li><a href="http://lms.garage.maemo.org/api/index.html">LightMediaScanner Documentation</a></li> <li><a href="https://github.com/profusion/lightmediascanner/wiki">LightMediaScanner Wiki - GitHub</a></li> </ul>Jayjayoh.dev@gmail.comLMS(Light Media Scanner)는 임베디드 시스템에 사용하기 위한 가벼운 미디어 스캐너이다.Ubuntu Own Configuration2019-10-07T00:00:00+00:002019-10-07T00:00:00+00:00https://jayoh-dev.github.io/dev/ubuntu-desktop-own-configuration<p>Ubuntu 설치 후 필요한 패키지와 설정들</p> <h1 id="package">Package</h1> <h2 id="numix-theme">Numix Theme</h2> <p><a href="https://numixproject.github.io">https://numixproject.github.io</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:numix/ppa sudo apt update sudo apt install numix-gtk-theme numix-icon-theme-circle numix-folders </code></pre></div></div> <h2 id="tilix-terminal">Tilix Terminal</h2> <p><a href="https://gnunn1.github.io/tilix-web">https://gnunn1.github.io/tilix-web</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:webupd8team/terminix sudo apt update sudo apt install tilix </code></pre></div></div> <h3 id="set-tilix-to-default-terminal">Set Tilix to Default Terminal</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo update-alternatives --config x-terminal-emulator </code></pre></div></div> <h2 id="reminna">Reminna</h2> <p><a href="https://remmina.org">https://remmina.org</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-add-repository ppa:remmina-ppa-team/remmina-next sudo apt update sudo apt install remmina remmina-plugin-rdp remmina-plugin-secret remmina-plugin-spice </code></pre></div></div> <h2 id="barrier">Barrier</h2> <p><a href="https://github.com/debauchee/barrier">https://github.com/debauchee/barrier</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo add-apt-repository ppa:jonathonf/barrier sudo apt update sudo apt install barrier </code></pre></div></div> <h2 id="restrictedformats">RestrictedFormats</h2> <p><a href="https://help.ubuntu.com/community/RestrictedFormats">https://help.ubuntu.com/community/RestrictedFormats</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install ubuntu-restricted-extras </code></pre></div></div> <h1 id="configuration">Configuration</h1> <h2 id="serial-device-group">Serial Device Group</h2> <p>Serial 장치(/dev/tty*) 사용 시 권한 문제로 장치 접근이 되지 않을 때, 아래와 같이 유저를 dialout 그룹에 추가하면 된다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo usermod -a -G dialout &lt;username&gt; </code></pre></div></div> <p>로그아웃 후 다시 로그인이 필요하다.</p> <h2 id="enabling-i-search-in-terminal">Enabling i-search in Terminal</h2> <p>터미널에서 <code class="language-plaintext highlighter-rouge">Ctrl + r</code>로 명령어 역방향 검색 reverse-i-search 기능을 사용 가능하다.</p> <p>그 반대로 정방향 검색 i-search 기능을 사용하려면 아래 라인을 <code class="language-plaintext highlighter-rouge">.bashrc</code>에 추가한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stty -ixon </code></pre></div></div> <p>단축키는 <code class="language-plaintext highlighter-rouge">Ctrl + s</code>이다.</p> <h2 id="enabling-process-attaching-in-gdb">Enabling Process Attaching in GDB</h2> <p>보안을 위해 일반 사용자에게는 프로세스의 메모리나 실행 상태를 들여다보는 것이 제한되어 있다. 이 제한을 해제하기 위해서 <code class="language-plaintext highlighter-rouge">/etc/sysctl.d/10-ptrace.conf</code> 파일을 열어 <code class="language-plaintext highlighter-rouge">kernel.yama.ptrace_scope</code> 값을 <code class="language-plaintext highlighter-rouge">0</code>으로 수정한다.</p> <p>시스템 파일이기 때문에 수정하기 위해서는 root 권한이 필요하다. 아래 명령어로 변경 사항을 적용한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo sysctl -p --system </code></pre></div></div>Jayjayoh.dev@gmail.comUbuntu 설치 후 필요한 패키지와 설정들Cppcheck MISRA-C:20122019-09-20T00:00:00+00:002019-09-20T00:00:00+00:00https://jayoh-dev.github.io/dev/cppcheck-misra<p>Cppcheck는 C/C++ 코드를 정적 분석해주는 오픈 소스 툴이다. 본 글에서는 Cppcheck를 사용해 MISRA-C 코딩 표준을 적용하여 C/C++ 코드를 정적 분석하는 방법에 대해 설명한다.</p> <h1 id="개요">개요</h1> <p>Cppcheck 1.88 이상이 필요하다. MISRA 적용을 위한 <code class="language-plaintext highlighter-rouge">--addon</code> 커맨드라인 옵션이 1.88 부터 제공되기 때문이다.</p> <p>Ubuntu 환경에서 소스 코드를 다운로드 받아 직접 빌드하여 사용하였다.</p> <p>현재 Cppcheck에서 지원하는 MISRA 코딩 표준은 MISRA-C:2012 이다.</p> <h1 id="다운로드">다운로드</h1> <p>Cppcheck 소스 코드는 아래 SourceForce 혹은 GitHub에서 다운로드 받을 수 있다.</p> <ul> <li>https://sourceforge.net/projects/cppcheck/</li> <li>https://github.com/danmar/cppcheck</li> </ul> <h1 id="빌드--설치">빌드 &amp; 설치</h1> <p>readme.txt 참조하여 아래와 같이 CMake를 사용하여 빌드하였다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd cppcheck-1.89 $ mkdir build $ cd build $ cmake --build .. $ sudo make install </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">cppcheck</code> 실행 파일이 <code class="language-plaintext highlighter-rouge">/usr/local/bin</code> 디렉토리에 설치된다.</p> <h1 id="실행">실행</h1> <p><code class="language-plaintext highlighter-rouge">--addon</code> 옵션을 사용하여 MISRA 체크 규칙을 적용할 수 있다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cppcheck --addon=misra.json somefile.c </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">misra.json</code> 파일은 아래와 같이 작성한다.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/usr/local/src/cppcheck-1.89/addons/misra.py"</span><span class="p">,</span><span class="w"> </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"--rule-texts=./misra-rules.txt"</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>MISRA는 오픈 표준이 아니기 때문에 Cppcheck는 Rule 번호만 표시하고, Rule에 대한 설명은 표시하지 않는다.</p> <p>Rule 설명을 모두 표시하려면 Rule Text 파일을 사용자가 직접 작성해주어야 한다. 전체 Rule Text는 구글 검색으로 찾을 수 있지만 Cppcheck에서 인식하는 포맷으로 편집해주어야 한다.</p> <p>Rule Text 포맷은 다음과 같다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Here can be any text. Incorrect definitions: Appendix A Appendix A Summary: Rule 1.1 Error! Here we go: Appendix A Summary of guidelines Rule 1.2 Rule text. Stop parsing after this line: Appendix B Rule 1.3 Error! </code></pre></div></div> <h1 id="references">References</h1> <ul> <li>http://cppcheck.sourceforge.net/</li> <li>http://cppcheck.sourceforge.net/misra.php</li> </ul>Jayjayoh.dev@gmail.comCppcheck는 C/C++ 코드를 정적 분석해주는 오픈 소스 툴이다. 본 글에서는 Cppcheck를 사용해 MISRA-C 코딩 표준을 적용하여 C/C++ 코드를 정적 분석하는 방법에 대해 설명한다.디버거에서 시그널 무시하기2019-08-06T00:00:00+00:002019-08-06T00:00:00+00:00https://jayoh-dev.github.io/dev/ignore-signals-in-debugger<p>Linux 환경에서 디버거 사용 시, 시그널에 의한 Trap을 무시하는 방법을 설명한다.</p> <h1 id="gdb">GDB</h1> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>process handle SIGTERM -n true -p true -s false </code></pre></div></div> <h1 id="lldb">LLDB</h1> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>handle SIGTERM nostop </code></pre></div></div>Jayjayoh.dev@gmail.comLinux 환경에서 디버거 사용 시, 시그널에 의한 Trap을 무시하는 방법을 설명한다.make install 위치 지정2018-11-22T00:00:00+00:002018-11-22T00:00:00+00:00https://jayoh-dev.github.io/dev/makefile-install-temporary-location<p><code class="language-plaintext highlighter-rouge">make install</code> 설치 디렉토리를 변경하는 방법</p> <p>DESTDIR 변수를 지정한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make DESTDIR=$PWD/install install </code></pre></div></div> <h1 id="references">References</h1> <ul> <li>https://www.gnu.org/software/make/manual/make.html#DESTDIR</li> </ul>Jayjayoh.dev@gmail.commake install 설치 디렉토리를 변경하는 방법Windows 탐색기에서 .gitignore 파일 생성2018-07-12T00:00:00+00:002018-07-12T00:00:00+00:00https://jayoh-dev.github.io/tool/create-dot-file-on-windows-explorer<p>Windows 탐색기에서 ‘.’으로 시작하는 파일을 생성하려면</p> <p>e.g. <code class="language-plaintext highlighter-rouge">.gitignore.</code></p>Jayjayoh.dev@gmail.comWindows 탐색기에서 ‘.’으로 시작하는 파일을 생성하려면