155 lines
4.9 KiB
Markdown
155 lines
4.9 KiB
Markdown
# GenMagic
|
|
|
|
**GenMagic** provides supervised and customisable access to [libmagic](http://man7.org/linux/man-pages/man3/libmagic.3.html) using a supervised external process.
|
|
|
|
With this library, you can start an one-off process to run a single check, or run the process as a daemon if you expect to run many checks.
|
|
|
|
## Installation
|
|
|
|
The package can be installed by adding `gen_magic` to your list of dependencies in `mix.exs`:
|
|
|
|
```elixir
|
|
def deps do
|
|
[
|
|
{:gen_magic, "~> 1.0.0"}
|
|
]
|
|
end
|
|
```
|
|
|
|
You must also have [libmagic](http://man7.org/linux/man-pages/man3/libmagic.3.html) installed locally with headers, alongside common compilation tools (i.e. build-essential). These can be acquired by apt-get, yum, brew, etc.
|
|
|
|
Compilation of the underlying C program is automatic and handled by [elixir_make](https://github.com/elixir-lang/elixir_make).
|
|
|
|
## Usage
|
|
|
|
Depending on the use case, you may utilise a single (one-off) GenMagic process without reusing it as a daemon, or utilise a connection pool (such as Poolboy) in your application to run multiple persistent GenMagic processes.
|
|
|
|
To use GenMagic directly, you can use `GenMagic.Helpers.perform_once/1`:
|
|
|
|
```elixir
|
|
iex(1)> GenMagic.perform(".", once: true)
|
|
{:ok,
|
|
%GenMagic.Result{
|
|
content: "directory",
|
|
encoding: "binary",
|
|
mime_type: "inode/directory"
|
|
}}
|
|
```
|
|
|
|
To use the GenMagic server as a daemon, you can start it first, keep a reference, then feed messages to it as you require:
|
|
|
|
```elixir
|
|
{:ok, pid} = GenMagic.Server.start_link([])
|
|
{:ok, result} = GenMagic.perform(path, server: pid)
|
|
```
|
|
|
|
See `GenMagic.Server.start_link/1` and `t:GenMagic.Server.option/0` for more information on startup parameters.
|
|
|
|
See `GenMagic.Result` for details on the result provided.
|
|
|
|
## Configuration
|
|
|
|
When using `GenMagic.Server.start_link/1` to start a persistent server, or `GenMagic.Helpers.perform_once/2` to run an ad-hoc request, you can override specific options to suit your use case.
|
|
|
|
| Name | Default | Description |
|
|
| - | - | - |
|
|
| `:startup_timeout` | 1000 | Number of milliseconds to wait for client startup |
|
|
| `:process_timeout` | 30000 | Number of milliseconds to process each request |
|
|
| `:recycle_threshold` | 10 | Number of cycles before the C process is replaced |
|
|
| `:database_patterns` | `[:default]` | Databases to load |
|
|
|
|
See `t:GenMagic.Server.option/0` for details.
|
|
|
|
### Use Cases
|
|
|
|
### Ad-Hoc Requests
|
|
|
|
For ad-hoc requests, you can use the helper method `GenMagic.Helpers.perform_once/2`:
|
|
|
|
```elixir
|
|
iex(1)> GenMagic.perform(Path.join(File.cwd!(), "Makefile"), once: true)
|
|
{:ok,
|
|
%GenMagic.Result{
|
|
content: "makefile script, ASCII text",
|
|
encoding: "us-ascii",
|
|
mime_type: "text/x-makefile"
|
|
}}
|
|
```
|
|
|
|
### Supervised Requests
|
|
|
|
The Server should be run under a supervisor which provides resiliency.
|
|
|
|
Here we run it under a supervisor:
|
|
|
|
```elixir
|
|
iex(1)> {:ok, pid} = Supervisor.start_link([{GenMagic.Server, name: :gen_magic}], strategy: :one_for_one)
|
|
{:ok, #PID<0.199.0>}
|
|
```
|
|
|
|
Now we can ask it to inspect a file:
|
|
|
|
```elixir
|
|
iex(2)> GenMagic.perform(Path.expand("~/.bash_history"), server: :gen_magic)
|
|
{:ok, %GenMagic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
|
|
```
|
|
|
|
Note that in this case we have opted to use a named process.
|
|
|
|
### Pool
|
|
|
|
For concurrency *and* resiliency, you may start the `GenMagic.Pool`. By default, it will start a `GenMagic.Server`
|
|
worker per online scheduler:
|
|
|
|
You can add a pool in your application supervisor by adding it as a child:
|
|
|
|
```
|
|
children =
|
|
[
|
|
# ...
|
|
{GenMagic.Pool, [name: YourApp.GenMagicPool, pool_size: 2]}
|
|
]
|
|
|
|
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
|
Supervisor.start_link(children, opts)
|
|
```
|
|
|
|
And then you can use it with `GenMagic.perform/2` with `pool: YourApp.GenMagicPool` option:
|
|
|
|
```
|
|
iex(1)> GenMagic.perform(Path.expand("~/.bash_history"), pool: YourApp.GenMagicPool)
|
|
{:ok, %GenMagic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
|
|
```
|
|
|
|
### Check Uploaded Files
|
|
|
|
If you use Phoenix, you can inspect the file from your controller:
|
|
|
|
```elixir
|
|
def upload(conn, %{"upload" => %{path: path}}) do,
|
|
{:ok, result} = GenMagic.perform(path, server: :gen_magic)
|
|
text(conn, "Received your file containing #{result.content}")
|
|
end
|
|
```
|
|
|
|
Obviously, it will be more ideal if you have wrapped `GenMagic.Server` in a pool such as Poolboy, to avoid constantly starting and stopping the underlying C program.
|
|
|
|
## Notes
|
|
|
|
### Soak Test
|
|
|
|
Run an endless cycle to prove that the program is resilient:
|
|
|
|
```bash
|
|
find /usr/share/ -name *png | xargs mix run test/soak.exs
|
|
find . -name *ex | xargs mix run test/soak.exs
|
|
```
|
|
|
|
## Acknowledgements
|
|
|
|
During design and prototype development of this library, the Author has drawn inspiration from the following individuals, and therefore thanks all contributors for their generosity:
|
|
|
|
- Mr [James Every](https://github.com/devstopfix)
|
|
- Enhanced Elixir Wrapper (based on GenServer)
|
|
- Initial Hex packaging (v.0.22)
|
|
- Soak Testing
|